dashboard/app/routers/overview.py

98 lines
3.3 KiB
Python

import asyncio
import math
import socket
import time
from typing import Any
import psutil
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, JSONResponse
from app.collect import net as net_col
from app.config import get_settings
from app.render import render, uptime_str
router = APIRouter(prefix="/api", tags=["overview"])
RowAgg = dict[str, float | int | None]
def _window(
snap: list[tuple[float, dict[str, float | int | None]]], max_points: int
) -> list[tuple[float, dict[str, RowAgg]]]:
n = len(snap)
w = max(1, math.ceil(n / max_points))
out: list[tuple[float, dict[str, RowAgg]]] = []
for start in range(0, n, w):
chunk = snap[start : start + w]
vals: dict[str, list[int | float]] = {}
for _, sample in chunk:
for k, v in sample.items():
if isinstance(v, (int, float)) and not isinstance(v, bool):
vals.setdefault(k, []).append(v)
row: dict[str, RowAgg] = {}
for k, lst in vals.items():
ints = all(isinstance(v, int) for v in lst)
avg = sum(lst) / len(lst)
row[k] = {
"avg": round(avg) if ints else round(avg, 1),
"min": min(lst) if ints else round(min(lst), 1),
"max": max(lst) if ints else round(max(lst), 1),
}
out.append((chunk[-1][0], row))
return out
@router.get("/overview")
async def overview(request: Request):
store = request.app.state.store
s: dict[str, Any] = store.latest() or {}
mem_total = s.get("mem_total") or 0
vram_total = s.get("vram_total") or 0
vram_used = s.get("vram_used")
c = {
"cpu": s.get("cpu"),
"cpu_temp": s.get("cpu_temp"),
"load1": s.get("load1"),
"load5": s.get("load5"),
"load15": s.get("load15"),
"mem_used": s.get("mem_used"),
"mem_total": mem_total,
"mem_pct": s.get("mem_pct"),
"swap_used": s.get("swap_used"),
"swap_total": s.get("swap_total") or 0,
"swap_pct": s.get("swap_pct"),
"gpu": s.get("gpu"),
"gpu_name": s.get("gpu_name"),
"gpu_temp": s.get("gpu_temp"),
"vram_used": vram_used,
"vram_total": vram_total,
"vram_pct": s.get("vram_pct")
or ((vram_used / vram_total * 100) if (vram_total and vram_used is not None) else None),
"battery": s.get("battery"),
"battery_status": s.get("battery_status"),
"ac_online": s.get("ac_online"),
"uptime": uptime_str(time.time() - psutil.boot_time()),
"hostname": socket.gethostname(),
"cores": psutil.cpu_count(logical=True) or 1,
**await asyncio.to_thread(net_col.sample),
}
return HTMLResponse(render("overview.html", c=c))
@router.get("/history")
async def history(request: Request):
snap = _window(request.app.state.store.snapshot(), get_settings().chart_max_points)
ts = [round(t, 1) for t, _ in snap]
keys: set[str] = set()
for _, row in snap:
keys.update(row)
series: dict[str, dict[str, list[Any]]] = {
k: {"avg": [], "min": [], "max": []} for k in keys
}
for _, row in snap:
for k in keys:
agg = row.get(k)
for m in ("avg", "min", "max"):
series[k][m].append(agg[m] if agg else None)
return JSONResponse({"ts": ts, "series": series})