Files
2026-06-02 09:15:15 +10:00

116 lines
3.2 KiB
Python

"""Host and DB metrics for SAC sidebar (best-effort, no heavy queries)."""
from __future__ import annotations
import os
import shutil
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path
from sqlalchemy import func, select, text
from sqlalchemy.orm import Session
from app.models import Event, Problem
def _disk_path() -> str:
explicit = os.environ.get("SAC_STATS_DISK_PATH", "").strip()
if explicit:
return explicit
return str(Path("/").resolve())
def _read_cpu_percent() -> float | None:
try:
import psutil # type: ignore[import-untyped]
return round(float(psutil.cpu_percent(interval=None)), 1)
except Exception:
return None
def _read_memory() -> dict[str, float | int] | None:
try:
import psutil # type: ignore[import-untyped]
vm = psutil.virtual_memory()
return {
"used_mb": int(vm.used / (1024 * 1024)),
"total_mb": int(vm.total / (1024 * 1024)),
"percent": round(float(vm.percent), 1),
}
except Exception:
return None
def _read_disk(path: str) -> dict[str, float | int | str] | None:
try:
usage = shutil.disk_usage(path)
total = int(usage.total)
used = int(usage.used)
if total <= 0:
return None
return {
"path": path,
"used_gb": round(used / (1024**3), 1),
"total_gb": round(total / (1024**3), 1),
"percent": round(100.0 * used / total, 1),
}
except Exception:
return None
def collect_system_stats(db: Session) -> dict:
collected_at = datetime.now(timezone.utc).isoformat()
disk_path = _disk_path()
db_status = "ok"
db_latency_ms: float | None = None
db_connections: int | None = None
t0 = time.perf_counter()
try:
db.execute(text("SELECT 1"))
db_latency_ms = round((time.perf_counter() - t0) * 1000, 1)
try:
db_connections = int(
db.scalar(
text(
"SELECT count(*) FROM pg_stat_activity "
"WHERE datname = current_database() AND pid <> pg_backend_pid()"
)
)
or 0
)
except Exception:
db_connections = None
except Exception:
db_status = "error"
since = datetime.now(timezone.utc) - timedelta(hours=1)
events_last_hour = 0
problems_open = 0
if db_status == "ok":
events_last_hour = int(
db.scalar(select(func.count()).select_from(Event).where(Event.received_at >= since)) or 0
)
problems_open = int(
db.scalar(select(func.count()).select_from(Problem).where(Problem.status == "open")) or 0
)
return {
"collected_at": collected_at,
"cpu_percent": _read_cpu_percent(),
"memory": _read_memory(),
"disk": _read_disk(disk_path),
"database": {
"status": db_status,
"latency_ms": db_latency_ms,
"active_connections": db_connections,
},
"app": {
"events_last_hour": events_last_hour,
"problems_open": problems_open,
},
}