dashboard/app/routers/overview.py

101 lines
3.3 KiB
Python

import asyncio
import math
import socket
import time
from dataclasses import fields
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
from app.sample import Sample
router = APIRouter(prefix="/api", tags=["overview"])
RowAgg = dict[str, float | int | None]
def _window(snap: list[Sample], 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 f in fields(sample):
if f.name == "ts":
continue
v = getattr(sample, f.name)
if isinstance(v, (int, float)) and not isinstance(v, bool):
vals.setdefault(f.name, []).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].ts, row))
return out
@router.get("/overview")
async def overview(request: Request):
store = request.app.state.store
s = store.latest() or Sample()
mem_total = s.mem_total or 0
vram_total = s.vram_total or 0
vram_used = s.vram_used
c = {
"cpu": s.cpu,
"cpu_temp": s.cpu_temp,
"load1": s.load1,
"load5": s.load5,
"load15": s.load15,
"mem_used": s.mem_used,
"mem_total": mem_total,
"mem_pct": s.mem_pct,
"swap_used": s.swap_used,
"swap_total": s.swap_total or 0,
"swap_pct": s.swap_pct,
"gpu": s.gpu,
"gpu_name": s.gpu_name,
"gpu_temp": s.gpu_temp,
"vram_used": vram_used,
"vram_total": vram_total,
"vram_pct": s.vram_pct
or ((vram_used / vram_total * 100) if (vram_total and vram_used is not None) else None),
"battery": s.battery,
"battery_status": s.battery_status,
"ac_online": s.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})