smlflg's picture
Initial public upload from Projekte/agent-process-dashboard
06357e7 verified
Raw History Blame Contribute Delete
2.98 kB
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
RISK_ORDER = {"ok": 0, "info": 1, "warn": 2, "critical": 3}
def max_risk(*levels: str) -> str:
return max(levels or ("ok",), key=lambda level: RISK_ORDER.get(level, 0))
@dataclass(frozen=True)
class ProcessSample:
sample_ts: float
boot_id: str
pid: int
ppid: int
pgid: int
sid: int
uid: int
user: str
comm: str
state: str
start_time_ticks: int
process_key: str
age_seconds: float
rss_bytes: int
vms_bytes: int
utime_ticks: int
stime_ticks: int
cpu_percent_interval: float
num_threads: int
argv: tuple[str, ...]
argv_display: str
argv_masked_count: int
exe_path: str = ""
cwd: str = ""
cgroup: str = ""
read_errors: tuple[str, ...] = ()
@property
def cpu_ticks(self) -> int:
return self.utime_ticks + self.stime_ticks
@property
def rss_mb(self) -> float:
return self.rss_bytes / 1024 / 1024
@dataclass(frozen=True)
class Classification:
known_class: str
confidence: float
reasons: tuple[str, ...] = ()
is_agent_related: bool = False
is_bridge: bool = False
is_mcp: bool = False
@property
def is_agent(self) -> bool:
return self.known_class.startswith("agent.")
@dataclass
class ProcessNode:
sample: ProcessSample
classification: Classification
parent_key: str | None = None
children: list["ProcessNode"] = field(default_factory=list)
inherited_agent: bool = False
stale_state: str = "healthy"
stale_reasons: list[str] = field(default_factory=list)
risk_level: str = "ok"
risk_reasons: list[str] = field(default_factory=list)
@property
def process_key(self) -> str:
return self.sample.process_key
@property
def pid(self) -> int:
return self.sample.pid
@dataclass
class ProcessGroup:
group_id: str
root: ProcessNode
nodes: list[ProcessNode]
label: str
child_count: int
bridge_count: int
mcp_count: int
total_rss_bytes: int
max_age_seconds: float
risk_level: str = "ok"
risk_reasons: list[str] = field(default_factory=list)
stale_candidates: list[ProcessNode] = field(default_factory=list)
@dataclass(frozen=True)
class DryRunCandidate:
pid: int
process_key: str
known_class: str
age_seconds: float
rss_bytes: int
command: str
reasons: tuple[str, ...]
@dataclass(frozen=True)
class DryRunReport:
candidates: tuple[DryRunCandidate, ...]
exclusions: tuple[str, ...]
note: str = "Dry-run only. No signal or executable cleanup command is generated."
@dataclass
class DashboardSnapshot:
sample_ts: float
processes: list[ProcessSample]
classifications: dict[str, Classification]
nodes: dict[str, ProcessNode]
groups: list[ProcessGroup]
dry_run: DryRunReport
stats: dict[str, Any]
warnings: list[str]