hermes-agent-features/agent/system_health.py
Anton Palgunov f936968c5c feat: telemetry dashboard /stats command with CLI + gateway handlers
- agent/session_stats.py: SessionDB context/compression metrics
- agent/skill_stats.py: curator usage.json reader + prune history
- agent/system_health.py: gateway uptime, version, cron activity
- agent/stats_dashboard.py: Telegram-friendly bullet renderer
- cli.py: /stats dispatch + _handle_stats_command method
- gateway/run.py: /stats dispatch + _handle_stats_command for messaging platforms
- hermes_cli/commands.py: /stats CommandDef registration
2026-05-29 16:31:33 +00:00

91 lines
2.9 KiB
Python

"""System health and cron telemetry collectors for /stats."""
from __future__ import annotations
import os
import time
from datetime import datetime, timedelta, timezone
from typing import Any, Dict
def _parse_dt(value: Any):
if not value:
return None
try:
dt = datetime.fromisoformat(str(value))
except (TypeError, ValueError):
return None
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt
def format_duration(seconds: int | float | None) -> str:
if seconds is None:
return "unknown"
seconds = max(0, int(seconds))
days, rem = divmod(seconds, 86400)
hours, rem = divmod(rem, 3600)
minutes, _ = divmod(rem, 60)
if days:
return f"{days}d {hours}h"
if hours:
return f"{hours}h {minutes}m"
return f"{minutes}m"
def collect_cron_activity(hours: int = 24) -> Dict[str, Any]:
cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
try:
from cron.jobs import list_jobs
jobs = list_jobs(include_disabled=True)
except Exception:
jobs = []
recent = []
ok = error = health_checks = 0
for job in jobs:
if not isinstance(job, dict):
continue
dt = _parse_dt(job.get("last_run_at"))
if dt is None or dt < cutoff:
continue
status = str(job.get("last_status") or "unknown")
if status == "ok":
ok += 1
elif status == "error":
error += 1
haystack = " ".join(str(job.get(k) or "") for k in ("name", "prompt", "id")).lower()
if "health" in haystack or "doctor" in haystack:
health_checks += 1
recent.append({"id": job.get("id"), "name": job.get("name"), "status": status, "last_run_at": job.get("last_run_at")})
recent.sort(key=lambda r: str(r.get("last_run_at") or ""), reverse=True)
return {"hours": hours, "runs": len(recent), "ok": ok, "error": error, "health_checks": health_checks, "recent": recent[:5]}
def collect_system_health(*, started_at: Any = None, start_monotonic: float | None = None) -> Dict[str, Any]:
try:
from hermes_cli import __version__ as version
except Exception:
version = "unknown"
uptime_seconds = None
if start_monotonic is not None:
uptime_seconds = time.monotonic() - float(start_monotonic)
else:
dt = started_at
if isinstance(dt, (int, float)):
uptime_seconds = time.time() - float(dt)
elif isinstance(dt, datetime):
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
uptime_seconds = (datetime.now(timezone.utc) - dt).total_seconds()
return {
"version": version,
"pid": os.getpid(),
"uptime_seconds": int(uptime_seconds) if uptime_seconds is not None else None,
"uptime": format_duration(uptime_seconds),
"cron": collect_cron_activity(hours=24),
}