package probes import ( "os" "path/filepath" "strconv" "strings" ) // ThermalSample is one reading from /sys/class/hwmon. Kind is "temp", // Key is the label (or chip-relative name) and Value is degrees C. type ThermalSample struct { Kind string Key string Value float64 Unit string } // Thermals walks /sys/class/hwmon looking for temp*_input files. The // kernel reports millidegrees C; we divide by 1000. Labels come from // temp*_label (preferred) or a chip-relative fallback. // // This is also used by the thermal sidecar; it re-reads on each tick // rather than holding open handles so hot-plugged sensors (e.g. a PCIe // card enumerating late) get picked up. func Thermals() []ThermalSample { root := "/sys/class/hwmon" chips, err := os.ReadDir(root) if err != nil { return nil } var out []ThermalSample for _, c := range chips { base := filepath.Join(root, c.Name()) chipName := strings.TrimSpace(readFileStr(filepath.Join(base, "name"))) files, err := os.ReadDir(base) if err != nil { continue } for _, f := range files { name := f.Name() if !strings.HasPrefix(name, "temp") || !strings.HasSuffix(name, "_input") { continue } idx := strings.TrimSuffix(strings.TrimPrefix(name, "temp"), "_input") label := strings.TrimSpace(readFileStr(filepath.Join(base, "temp"+idx+"_label"))) if label == "" { label = chipName + "/temp" + idx } raw := strings.TrimSpace(readFileStr(filepath.Join(base, name))) milli, err := strconv.Atoi(raw) if err != nil { continue } out = append(out, ThermalSample{Kind: "temp", Key: label, Value: float64(milli) / 1000, Unit: "C"}) } } return out } func readFileStr(p string) string { b, err := os.ReadFile(p) if err != nil { return "" } return string(b) }