dashboard: mark service latency sources - #93
Conversation
ai-hpc
left a comment
There was a problem hiding this comment.
Right shape for #86's gap. PR #84 expanded /api/services to include systemd-only units but left their latency field as a misleading --. This PR adds a third explicit state, so the wire format now distinguishes:
latency_source = "health"— cached row fromgenie-health's SQLite (the original path).latency_source = "live"— fresh probe issued from this request, used when a service exposes HTTP but doesn't have a cached row yet (typically the dashboard's owngenie-api).latency_source = "not_applicable"— systemd-only services with no HTTP endpoint (governor, mqtt, audio, whisper, wakeword by default).
Dashboard JS maps the three states to Nms / Nms live / n/a accordingly. The n/a is the user-facing payoff — operators can finally tell "this service is healthy but has no HTTP probe by design" apart from "this service should have a probe but isn't responding".
Implementation pieces worth calling out:
latency_url: Option<String>on eachServiceTarget— present only for services that actually expose HTTP.core/llm/homeassistant/nextcloud/jellyfinread fromconfig.services.<name>.url(so the LLM probe automatically follows[services.llm].backendbetweengenie_llmandgenie_ai_runtimeURLs).apihardcodeshttp://127.0.0.1:3080/api/status— self-probe, a tiny circularity but always-reachable for a healthy genie-api. The rest areNone, which is what cleanly drives thenot_applicablebranch inmerge_service_rows.collect_live_latency_rows— gated onif health.contains_key(&target.service) { continue; }so we only probe when no cached health row exists. Prevents redundant fork-of-systemctl-equivalents on the hot path.probe_http_latency— raw TCP + minimal HTTP/1.1 GET with two 750 ms timeouts (connect, read), only reads first 256 bytes (enough for the status line), parses status code, returns healthy on 2xx-3xx. Intentionally minimalist — noreqwest/hyperdependency, no TLS path, no chunked-response handling. Right call for a local-only probe.
Worth flagging, not blocking:
- Sequential probes in the request handler. Each
/api/servicesrequest walks the target list andawaits eachprobe_http_latencyserially. Worst-case (all 5 probable targets time out at 1.5 s = 750 ms connect + 750 ms read): ~7.5 s for one/api/servicesresponse. Realistic case under healthy operation: ~50 ms × 5 = ~250 ms, fine for a 5 s dashboard poll. If you ever notice the dashboard stalling under failure mode, parallelizing withfutures::future::join_allover the probe set would be a one-screen change. Not blocking — failure-mode dashboard lag is a poor symptom but not a critical one. genie-apidoesn't have PR #87'sLocalSet::spawn_localrefactor. That fix only landed ongenie-core::server. So/api/servicesblocking insidegenie-apiis currently a single-threaded queue with everything else on:3080. Same flagging as above — if probe timeouts cascade and block/api/status, that's the lever to pull.- Self-probe of
apivia127.0.0.1:3080/api/statusis essentially "if I can serve this request, can I serve this request?" — guaranteed to succeed in practice, since the response is generated by the same daemon answering the probe. Useful as an end-to-end response-time signal in the rollup, but the operator should know it's measured from inside the box.
Tests are exactly the right shape:
service_rows_merge_health_and_systemd_statusupdated to assert bothlatency_source="health"(for core, via cachedHealthRow) andlatency_source="live"(for api, via injectedLiveLatencyRow { healthy: false, response_ms: 17, error: Some("HTTP 503") }). The 5xx-with-non-zero-response-ms case is the trickiest combination — "probe got a response, just not a healthy one" — and the test pins it.- New
systemd_only_service_rows_mark_latency_not_applicablecovers thelatency_url: Nonepath for wakeword, assertslatency_source="not_applicable"+source="systemd"+response_ms = None.
Operator's Jetson run in the PR body is the strongest signal — all 10 service rows now have explicit latency_source values that match the design (core/llm from health, api from live, the rest from not_applicable).
All 6 CI checks green on 0e6d55a (fmt, clippy, test, aarch64 cross-compile, --no-default-features, PR body checklist). Going in.
|
Merged at |
Summary
latency_sourceto/api/servicesso rows distinguish cached health latency, live endpoint latency, and systemd-only statusn/ainstead of ambiguous--Fixes #86
Testing
genie-apiReal Behavior Proof
not_applicablelatency.latency_source=not_applicableton/aand marks live probes asms live./api/servicesreports explicit latency sources for all service rows.Jetson Validation
Sanitized operator-run Jetson result: