"""Fleet-wide metric aggregation from Agent heartbeat payloads.""" from __future__ import annotations import json from dataclasses import dataclass from typing import Any def _safe_pct(value: Any) -> float | None: if value is None: return None try: num = float(value) except (TypeError, ValueError): return None if num < 0 or num > 100: return None return num def _metric_from_system_info(system_info: dict[str, Any], primary: str, fallback: str) -> float | None: return _safe_pct(system_info.get(primary) if system_info.get(primary) is not None else system_info.get(fallback)) @dataclass(frozen=True) class FleetMetricAggregate: online_count: int sample_count: int cpu_avg: int | None mem_avg: int | None disk_avg: int | None def aggregate_fleet_metrics(heartbeats: list[dict[str, Any]]) -> FleetMetricAggregate: """Aggregate Redis heartbeat hash rows (is_online + system_info JSON string).""" online_count = 0 cpu_vals: list[float] = [] mem_vals: list[float] = [] disk_vals: list[float] = [] for row in heartbeats: if row.get("is_online") in (True, "True"): online_count += 1 raw = row.get("system_info") or "{}" try: info = json.loads(raw) if isinstance(raw, str) else (raw or {}) except json.JSONDecodeError: info = {} if not isinstance(info, dict): continue cpu = _metric_from_system_info(info, "cpu_usage", "cpu") mem = _metric_from_system_info(info, "mem_usage", "mem") disk = _metric_from_system_info(info, "disk_usage", "disk") if cpu is not None: cpu_vals.append(cpu) if mem is not None: mem_vals.append(mem) if disk is not None: disk_vals.append(disk) sample_count = max(len(cpu_vals), len(mem_vals), len(disk_vals)) def _avg(vals: list[float]) -> int | None: if not vals: return None return round(sum(vals) / len(vals)) return FleetMetricAggregate( online_count=online_count, sample_count=sample_count, cpu_avg=_avg(cpu_vals), mem_avg=_avg(mem_vals), disk_avg=_avg(disk_vals), )