98 lines
3.3 KiB
Python
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})
|