From 43bf787696ac45ffcf02657972ca71485f2bf860 Mon Sep 17 00:00:00 2001 From: Mitchell-Johnson Date: Sat, 19 Sep 2026 07:04:03 -0700 Subject: [PATCH] =?UTF-8?q?Sensors:=20one=20standard=20read=20interface=20?= =?UTF-8?q?=E2=80=94=20catalog,=20single=20value,=20history,=20shaped=20qu?= =?UTF-8?q?ery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every number the collector samples becomes a sensor with a stable dotted ID (cpu.core.3.usage, fan.nct6775.fan2, fs.root.used_pct). Clients discover them from the catalog and ask for exactly what they want, including as a shaped query: send the object you want, get it back with the values filled in. Co-Authored-By: Claude Fable 5.1 --- PLAN.md | 12 ++ SENSORS.md | 84 ++++++++++++ internal/api/server.go | 85 +++++++++++- internal/metrics/metrics.go | 6 +- internal/metrics/sensors.go | 169 ++++++++++++++++++++++++ internal/metrics/sensors_test.go | 50 +++++++ internal/sensors/sensors.go | 217 +++++++++++++++++++++++++++++++ internal/sensors/sensors_test.go | 53 ++++++++ 8 files changed, 673 insertions(+), 3 deletions(-) create mode 100644 SENSORS.md create mode 100644 internal/metrics/sensors.go create mode 100644 internal/metrics/sensors_test.go create mode 100644 internal/sensors/sensors.go create mode 100644 internal/sensors/sensors_test.go diff --git a/PLAN.md b/PLAN.md index ebafed8..ecce4af 100644 --- a/PLAN.md +++ b/PLAN.md @@ -176,6 +176,18 @@ something long in one, switch away and back, refresh — it's all still there. the daemon's sampling clock; the chart window is always 300 samples, so 30 s at 100 ms and 10 min at 2 s. Still to do: persistent history (SQLite ring, e.g. 10 s × 7 days), SMART (`smartctl -j`), longer time ranges. +- **Sensors** — **step 1 shipped 2026-09-19** (`internal/sensors`, + `internal/metrics/sensors.go`, reference in `SENSORS.md`): everything measured is a + sensor with a stable dotted ID; catalog, single value (`?format=text`), per-sensor + history, and a **shaped query** where the client sends the object it wants and gets it + back filled in (GraphQL's idea without GraphQL: sensors differ per machine, so a static + schema would collapse to `sensor(id:)` anyway). `/api/metrics` and the Dashboard are + untouched so far. **Remaining, in order:** (2) read-only scoped tokens + Prometheus + `/metrics`; (3) push stream + widget library, Dashboard rebuilt on its own sensors; + (4) embeddable widget page + Glance config (Glance's `custom-api` only refetches on page + load, an `iframe` of our embed is what's live); (5) non-metric sensors — services, + SMART, ZFS, updates, containers. One sampler rate, set in one place, is part of (3): + today the open dashboard still drives it. - **Multi-host** — **Phase 1 shipped 2026-09-18** (`internal/nodes`, `internal/tlsauto`, `MachineSwitcher.tsx`, `Machines.tsx`). One binary, hub and spoke: a host whose `config.json` lists `nodes` is a hub; any other is a standalone node with its full UI. diff --git a/SENSORS.md b/SENSORS.md new file mode 100644 index 0000000..d7782c9 --- /dev/null +++ b/SENSORS.md @@ -0,0 +1,84 @@ +# Sensors — the standard read interface + +Everything Ham Lab measures is a **sensor**: a stable dotted ID, a little metadata, a +current value. Any consumer — Ham Lab's own dashboard, Glance, Home Assistant, a shell +script — discovers them from the catalog and asks for exactly the ones it wants. + +Code: `internal/sensors` (types, registry, selectors, shaped query) and +`internal/metrics/sensors.go` (the collector as a provider). The registry measures +nothing; providers do. A new source of sensors implements `sensors.Provider` and is +added to the registry in `api.New`. + +## IDs + +``` +cpu.usage cpu.core..usage cpu.temp load.1|5|15 uptime +mem.total|used|avail|free|cached|buffers|used_pct swap.total|used|avail|used_pct +temp..