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