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 <noreply@anthropic.com>
85 lines
3.7 KiB
Markdown
85 lines
3.7 KiB
Markdown
# 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.<n>.usage cpu.temp load.1|5|15 uptime
|
||
mem.total|used|avail|free|cached|buffers|used_pct swap.total|used|avail|used_pct
|
||
temp.<chip>.<label> fan.<chip>.<label>
|
||
net.<iface>.rx|tx|rx_total|tx_total|up
|
||
disk.<dev>.read|write|util|size|temp
|
||
fs.<mount>.size|used|used_pct
|
||
ups.battery|runtime|load|load_pct|in_v|out_v|on_battery|state|source
|
||
```
|
||
|
||
- Segments are lower case `[a-z0-9_-]`: `Package id 0` → `package_id_0`, `/mnt/tank` →
|
||
`mnt_tank`, `/` → `root`. The real names are in the sensor's `labels`.
|
||
- Built from names the hardware reports, never enumeration order (`hwmon3` moves between
|
||
boots). Two identical chips collide; the second gets `_2`.
|
||
- Which sensors exist differs per machine. That is the point of the catalog.
|
||
|
||
## A sensor
|
||
|
||
```json
|
||
{ "id": "temp.coretemp.package_id_0", "name": "coretemp Package id 0",
|
||
"kind": "temperature", "unit": "°C", "value": 51, "t": 1789826600878,
|
||
"warn": 105, "crit": 105, "labels": { "chip": "coretemp", "label": "Package id 0" } }
|
||
```
|
||
|
||
`value` is a number, bool or string. `t` is when it was really read, in unix ms — slow
|
||
sections (filesystems 30 s, UPS 10 s) are older than the tick. `min`/`max` are the scale
|
||
(0–100 for percentages, 0–total for used bytes); `warn`/`crit` are the hardware's own
|
||
thresholds where it states them.
|
||
|
||
## Endpoints
|
||
|
||
All behind the same auth as the rest of `/api` (session cookie or `Authorization: Bearer`).
|
||
On a hub, every one is also at `/api/nodes/<machine>/sensors…` for each managed machine.
|
||
|
||
| | |
|
||
|---|---|
|
||
| `GET /api/sensors` | Catalog, with current values. `?ids=fan.*,cpu.usage` narrows it. |
|
||
| `GET /api/sensors/{id}` | One sensor. `?format=text` → the bare value as text/plain. |
|
||
| `GET /api/sensors/{id}/history` | `{id, unit, t[], v[]}` — what the ring holds (300 samples at the sampling interval). Numbers only. |
|
||
| `POST /api/sensors/query` | Shaped query, below. |
|
||
| `GET /api/sensors/query?k=selector&…` | The same, flat, for consumers that can only fetch a URL. |
|
||
|
||
A selector is an exact ID or a glob. `*` crosses dots: `fan.*` is every fan,
|
||
`cpu.core.*.usage` every core.
|
||
|
||
## Shaped query
|
||
|
||
Send the object you want, with selectors where the values go. Get it back filled in.
|
||
|
||
```
|
||
POST /api/sensors/query
|
||
{ "cpu": "cpu.usage", "fans": "fan.*", "box": { "temp": "cpu.temp", "up": "uptime" } }
|
||
|
||
→ { "cpu": 12.4,
|
||
"fans": { "fan.nct6775.fan1": 820, "fan.nct6775.fan2": 1140 },
|
||
"box": { "temp": 41, "up": 80520 } }
|
||
```
|
||
|
||
- Exact ID → its value; `null` if this machine has no such sensor (so one query works
|
||
across machines that differ).
|
||
- Glob → an object keyed by ID; `{}` if nothing matches.
|
||
- Objects and arrays are walked; anything else in a query is a 400.
|
||
- `?full=1` → every value is the whole sensor object instead of the bare value. (In the
|
||
GET form `full` is therefore not usable as a key.)
|
||
|
||
## Not yet
|
||
|
||
Read-only scoped tokens, Prometheus `/metrics`, a push stream, widgets built on this,
|
||
an embeddable widget page, and non-metric sensors (services, SMART, ZFS, updates). Until
|
||
scoped tokens exist, **do not hand a Ham Lab token to a dashboard** — today's tokens are
|
||
full control of the machine.
|