Add homelab dashboards: system, LLM, and UPS on one domain

Three dashboards served by one FastAPI app with a SQLite history store
(raw / 1m / 1h) and hand-written SVG charts: no Prometheus, no Grafana,
no CDN, no build step. Nothing is fetched from the internet at runtime.

* system: a stdlib-only agent (agent/host_agent.py) reports /proc and
  /sys counters; the dashboard turns them into rates over a labelled
  window, so every number says how it was measured.
* llm: gufo/llama.cpp and vLLM /metrics are parsed by an in-house
  exposition parser. Measured delta/elapsed throughput beats a native
  per-request gauge, and each value carries its provenance.
* ups: a NUT wire-protocol client plus health checks that name what
  they looked at. Commands are gated by NUT_ALLOW_ACTIONS,
  DASH_READ_ONLY and a DANGER confirmation, re-checked server-side.

Missing values render as n/a rather than 0, and an unconfigured source
answers 404 {"configured": false} without dialling anything, so "not
deployed here" never looks like an outage.

253 tests at 94% coverage against captured real fixtures; ruff and
pyright clean.

LLM-Generated: true
This commit is contained in:
2026-10-16 17:20:45 -04:00
commit c4bb374ccc
56 changed files with 13018 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
"""Rate estimation over a labelled window.
Every throughput number in this dashboard is Δcounter/Δt. If the estimator returns a
plausible-looking number for a counter that restarted, or a silent 0 for a scrape that
arrived late, the UI cannot tell the difference — so the None cases matter more here than
the happy path.
"""
from __future__ import annotations
import math
import pytest
from dashboards.rates import RateEstimator
def test_first_sample_has_no_rate() -> None:
r = RateEstimator(span=10)
assert r.rate("cpu", 100.0, now=1_000.0) == (None, None)
def test_second_sample_gives_rate_and_window() -> None:
r = RateEstimator(span=10)
r.rate("cpu", 100.0, now=1_000.0)
rate, window = r.rate("cpu", 300.0, now=1_010.0)
assert rate == 20.0
assert window == 10.0
def test_window_is_labelled_with_the_span_actually_used() -> None:
r = RateEstimator(span=10)
r.rate("x", 0.0, now=0.0)
r.rate("x", 10.0, now=2.0) # inside the span
rate, window = r.rate("x", 30.0, now=11.0)
# base is the sample 11s ago (outside the span) -> falls back to the previous sample
assert window == 9.0
assert rate == pytest.approx(20.0 / 9.0)
def test_none_value_does_not_poison_the_window() -> None:
r = RateEstimator(span=10)
r.rate("x", 10.0, now=0.0)
assert r.rate("x", None, now=1.0) == (None, None)
rate, _window = r.rate("x", 30.0, now=2.0)
assert rate == 10.0, "the None scrape must not become the baseline"
def test_nan_and_inf_are_treated_as_missing() -> None:
r = RateEstimator(span=10)
r.rate("x", 1.0, now=0.0)
for bad in (float("nan"), float("inf")):
assert r.rate("x", bad, now=1.0) == (None, None)
def test_counter_reset_yields_none_once_then_recovers() -> None:
"""A restart must not report a huge negative — or, worse, a huge positive — rate."""
r = RateEstimator(span=60)
r.rate("tokens", 5_000.0, now=0.0)
rate, _ = r.rate("tokens", 10.0, now=10.0) # process restarted
assert rate is None
rate, _ = r.rate("tokens", 60.0, now=20.0)
assert rate == 5.0, "the window re-seeds from the post-restart counter"
def test_flat_counter_reports_zero_not_none() -> None:
r = RateEstimator(span=10)
r.rate("idle", 5.0, now=0.0)
rate, window = r.rate("idle", 5.0, now=5.0)
assert rate == 0.0 and window == 5.0
def test_keys_are_independent() -> None:
r = RateEstimator(span=10)
r.rate("a", 0.0, now=0.0)
r.rate("b", 100.0, now=0.0)
assert r.rate("a", 10.0, now=1.0)[0] == 10.0
assert r.rate("b", 100.0, now=1.0)[0] == 0.0
def test_forget_drops_a_removed_target() -> None:
r = RateEstimator(span=10)
for key in ("driveripper:cpu.total", "driveripper:disk.read", "deskwork:cpu.total"):
r.rate(key, 1.0, now=0.0)
assert r.forget("driveripper:") == 2
assert r.keys() == ["deskwork:cpu.total"]
# and the surviving target is untouched
assert r.rate("deskwork:cpu.total", 11.0, now=1.0)[0] == 10.0
def test_history_is_trimmed_to_max_samples() -> None:
r = RateEstimator(span=1e6, max_samples=8)
for i in range(50):
r.rate("x", float(i), now=float(i))
assert len(r._samples["x"]) <= 8 # bounded memory is the contract
assert all(math.isfinite(v) for _, v in r._samples["x"])