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) } } }