- sensors:read tokens open the sensor endpoints (here and on managed machines through the hub) and nothing else; full tokens are never accepted from a URL - /api/sensors/stream: a shaped query that keeps answering, over SSE - /metrics: Prometheus text format - services, updates, ZFS and SMART as background status sensors - widget library over the sensor API; custom widgets on the dashboard - /embed/widget: one live widget in its own bundle, the only frameable path - Dashboard tab is now Monitors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
77 lines
2.1 KiB
Go
77 lines
2.1 KiB
Go
package sensors
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// promFamily names the metric family for a sensor and scales its value to the base unit
|
|
// Prometheus convention wants (seconds, not minutes).
|
|
func promFamily(s Sensor) (name string, scale float64) {
|
|
scale = 1
|
|
unit := map[string]string{"%": "percent", "°C": "celsius", "rpm": "rpm", "B/s": "bytes_per_second", "B": "bytes",
|
|
"s": "seconds", "V": "volts", "W": "watts", "MHz": "megahertz"}[s.Unit]
|
|
if s.Unit == "min" {
|
|
unit, scale = "seconds", 60
|
|
}
|
|
if s.Kind == "count" { // hamlab_count{id=…} would say nothing; name it after the thing counted
|
|
return "hamlab_" + strings.ReplaceAll(Seg(strings.ReplaceAll(s.ID, ".", "_")), "-", "_"), 1
|
|
}
|
|
name = "hamlab_" + s.Kind
|
|
if unit != "" {
|
|
name += "_" + unit
|
|
}
|
|
return
|
|
}
|
|
|
|
var promEsc = strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\n", `\n`)
|
|
|
|
// WriteProm writes sensors in the Prometheus text exposition format: one family per kind
|
|
// of measurement, the sensor's ID and its real-world labels as labels. Booleans are 0/1;
|
|
// a text state is a 1 carrying the text as a label, the usual way to export an enum.
|
|
func WriteProm(w io.Writer, all []Sensor) {
|
|
fams := map[string][]string{}
|
|
for _, s := range all {
|
|
name, scale := promFamily(s)
|
|
labels := []string{`id="` + promEsc.Replace(s.ID) + `"`}
|
|
keys := make([]string, 0, len(s.Labels))
|
|
for k := range s.Labels {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
if s.Labels[k] != "" {
|
|
labels = append(labels, Seg(k)+`="`+promEsc.Replace(s.Labels[k])+`"`)
|
|
}
|
|
}
|
|
var v float64
|
|
switch x := s.Value.(type) {
|
|
case float64:
|
|
v = x * scale
|
|
case bool:
|
|
if x {
|
|
v = 1
|
|
}
|
|
case string:
|
|
v = 1
|
|
labels = append(labels, `value="`+promEsc.Replace(x)+`"`)
|
|
default:
|
|
continue
|
|
}
|
|
fams[name] = append(fams[name], fmt.Sprintf("%s{%s} %v", name, strings.Join(labels, ","), v))
|
|
}
|
|
names := make([]string, 0, len(fams))
|
|
for n := range fams {
|
|
names = append(names, n)
|
|
}
|
|
sort.Strings(names)
|
|
for _, n := range names {
|
|
fmt.Fprintf(w, "# TYPE %s gauge\n", n)
|
|
for _, line := range fams[n] {
|
|
fmt.Fprintln(w, line)
|
|
}
|
|
}
|
|
}
|