"""Attach alert_logs to fleet metric sample windows (10min flush alignment).""" from __future__ import annotations from datetime import datetime, timedelta from typing import Any from server.utils.display_time import to_naive_utc ALERT_TYPE_LABELS = { "cpu": "CPU", "mem": "内存", "disk": "磁盘", "time_drift": "时钟漂移", "system": "系统", } def _parse_naive_utc(value: datetime | str | None) -> datetime | None: if value is None: return None if isinstance(value, datetime): return to_naive_utc(value) raw = str(value).strip() if not raw: return None normalized = raw.replace(" ", "T") if " " in raw and "T" not in raw else raw if normalized.endswith("Z"): normalized = normalized[:-1] + "+00:00" try: dt = datetime.fromisoformat(normalized) except ValueError: return None return to_naive_utc(dt) def _alert_to_dict(alert: Any) -> dict[str, Any]: atype = str(getattr(alert, "alert_type", "") or "") return { "server_name": getattr(alert, "server_name", None) or "", "server_id": getattr(alert, "server_id", None), "alert_type": atype, "alert_type_label": ALERT_TYPE_LABELS.get(atype, atype), "value": getattr(alert, "value", None) or "", "is_recovery": bool(getattr(alert, "is_recovery", False)), "created_at": str(getattr(alert, "created_at", "") or ""), } def attach_alerts_to_fleet_points( points: list[dict[str, Any]], alerts: list[Any], *, interval_minutes: int = 10, ) -> list[dict[str, Any]]: """Map each sample to alerts in (previous_sample, current_sample] window.""" if not points: return [] parsed: list[tuple[datetime | None, dict[str, Any]]] = [] for p in points: parsed.append((_parse_naive_utc(p.get("recorded_at")), p)) alert_rows: list[tuple[datetime, dict[str, Any]]] = [] for a in alerts: adt = _parse_naive_utc(getattr(a, "created_at", None)) if adt is not None: alert_rows.append((adt, _alert_to_dict(a))) window = timedelta(minutes=max(1, interval_minutes)) enriched: list[dict[str, Any]] = [] for i, (pt_dt, p) in enumerate(parsed): if pt_dt is None: enriched.append({**p, "alerts": [], "alert_count": 0}) continue prev_dt = parsed[i - 1][0] if i > 0 else None start = prev_dt if prev_dt is not None else pt_dt - window bucket = [ item for adt, item in alert_rows if start < adt <= pt_dt ] enriched.append({**p, "alerts": bucket, "alert_count": len(bucket)}) return enriched