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:
@@ -0,0 +1,71 @@
|
||||
"""Shared fixtures: real captures for shape, fakes for behaviour.
|
||||
|
||||
Anything that must match an upstream (an agent snapshot, an engine's /metrics text, the NUT
|
||||
wire protocol) is asserted against a verbatim capture in tests/fixtures/. Everything that
|
||||
needs a *failure* — a dead host, a refused command, a counter reset — is a fake here.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / "src"
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
|
||||
from dashboards.rates import RateEstimator # noqa: E402
|
||||
from dashboards.store import SampleStore # noqa: E402
|
||||
|
||||
FIXTURES = Path(__file__).resolve().parent / "fixtures"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db(tmp_path: Path) -> Any:
|
||||
store = SampleStore(tmp_path / "t.sqlite3")
|
||||
try:
|
||||
yield store
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store(db: Any) -> Any:
|
||||
"""Alias: some tests read better with `store`, others with `db`. Same object."""
|
||||
return db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent_snapshot() -> dict[str, Any]:
|
||||
"""A real driveripper snapshot (Threadripper + R9700, 48 logical CPUs)."""
|
||||
return json.loads((FIXTURES / "agent_driveripper.json").read_text())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent_snapshots() -> dict[str, dict[str, Any]]:
|
||||
"""Every captured host, keyed by name — fleet code paths need >1 host to mean anything."""
|
||||
out: dict[str, dict[str, Any]] = {}
|
||||
for path in sorted(FIXTURES.glob("agent_*.json")):
|
||||
out[path.stem.removeprefix("agent_")] = json.loads(path.read_text())
|
||||
return out
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def metrics_text() -> Any:
|
||||
"""metrics_text('gufo') / metrics_text('vllm') -> the captured exposition verbatim."""
|
||||
def load(engine: str) -> str:
|
||||
path = FIXTURES / f"metrics_{engine}.txt"
|
||||
if not path.exists():
|
||||
pytest.skip(f"missing fixture {path.name} — run scripts/capture-fixtures.sh --with-llm")
|
||||
return path.read_text()
|
||||
|
||||
return load
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rates() -> RateEstimator:
|
||||
return RateEstimator(span=10.0)
|
||||
Reference in New Issue
Block a user