from __future__ import annotations import os import re from datetime import datetime from typing import Type import numpy as np import yaml from context import ClinicContext from task import BaseTask def condition_match(o, v, line): if o == "contains" or o == "contains_many": return v in line elif o == "regex": return bool(re.match(v, line)) def parse_condition(c): for o, v in c.items(): return (o, v) class Rule: def __init__(self, r: dict): self._rule = r self.matched_conditions = {} def process(self, comp, line): if self._rule["component"] != comp: return for i, c in enumerate(self._rule["conditions"]): o, v = parse_condition(c) if condition_match(o, v, line): if i not in self.matched_conditions: self.matched_conditions[i] = 1 else: self.matched_conditions[i] += 1 def match(self): conditions = [] for i, c in enumerate(self._rule["conditions"]): if i not in self.matched_conditions: self.matched_conditions[i] = 0 o, v = parse_condition(c) conditions.append(v) if o == "contains_many" and self.matched_conditions[i] < 60: return None elif self.matched_conditions[i] == 0: return None return { "id": self._rule["id"], "name": self._rule["name"], "component": self._rule["component"], "level": self._rule["level"], "type": self._rule["type"], "category": self._rule["category"], "conditions": conditions, "content": {"key": self._rule["advices"]}, "count": np.min([v for v in self.matched_conditions.values()]).item(), } class LogDiagProcessor: def __init__(self): with open(os.path.join(os.path.dirname(__file__), "log_diag_rule.yml")) as f: rules = yaml.load(f.read(), Loader=yaml.Loader) self.rules = [Rule(r) for r in rules] def process(self, component, line): for r in self.rules: r.process(component, line) def done(self): symptoms = [] for r in self.rules: m = r.match() if m is not None: symptoms.append(m) return symptoms class DiagTask(BaseTask): cluster: str start_ts: int stop_ts: int org_type: str ctx_mark: datetime def __init__(self, cluster: str, start_ts: int, stop_ts: int, **kwargs) -> None: self.cluster = cluster self.start_ts = start_ts * 1000 self.stop_ts = stop_ts * 1000 @classmethod def resume(cls, ctx: str) -> DiagTask: raise Exception("not supported") async def run(self) -> dict: dps = {} r = [] async with ClinicContext("cloud", "", self.cluster, "", self.start_ts, self.stop_ts) as ctx: count = 0 async for log in ctx.get_logs(): inst = log[1] if inst not in dps: dps[inst] = LogDiagProcessor() dps[inst].process(log[0], log[2]) count += 1 print(f"{count} logs processed") for inst, dp in dps.items(): symptoms = dp.done() r += [{"instance": inst, **sym} for sym in symptoms] return { "abnormal": len(r) > 0, "advice": r, } def suspend(self) -> str: raise Exception("not supported") def task_class() -> Type[BaseTask]: return DiagTask