diff --git a/v3/host/host.go b/v3/host/host.go index 5a836073f..09910b6a1 100644 --- a/v3/host/host.go +++ b/v3/host/host.go @@ -40,6 +40,8 @@ type UserStat struct { type TemperatureStat struct { SensorKey string `json:"sensorKey"` Temperature float64 `json:"temperature"` + High float64 `json:"sensorHigh"` + Critical float64 `json:"sensorCritical"` } func (h InfoStat) String() string { diff --git a/v3/host/host_linux.go b/v3/host/host_linux.go index 199fd77df..39488704b 100644 --- a/v3/host/host_linux.go +++ b/v3/host/host_linux.go @@ -27,7 +27,11 @@ type lsbStruct struct { } // from utmp.h -const user_PROCESS = 7 +const ( + user_PROCESS = 7 + + hostTemperatureScale = 1000.0 +) func HostIDWithContext(ctx context.Context) (string, error) { sysProductUUID := common.HostSys("class/dmi/id/product_uuid") @@ -366,19 +370,27 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) { } func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) { - var temperatures []TemperatureStat - files, err := filepath.Glob(common.HostSys("/class/hwmon/hwmon*/temp*_*")) - if err != nil { + var err error + + var files []string + + temperatures := make([]TemperatureStat, 0) + + // Only the temp*_input file provides current temperature + // value in millidegree Celsius as reported by the temperature to the device: + // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface + if files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/temp*_input")); err != nil { return temperatures, err } + if len(files) == 0 { // CentOS has an intermediate /device directory: // https://github.com/giampaolo/psutil/issues/971 - files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/device/temp*_*")) - if err != nil { + if files, err = filepath.Glob(common.HostSys("/class/hwmon/hwmon*/device/temp*_input")); err != nil { return temperatures, err } } + var warns Warnings if len(files) == 0 { // handle distributions without hwmon, like raspbian #391, parse legacy thermal_zone files @@ -413,6 +425,8 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err return temperatures, warns.Reference() } + temperatures = make([]TemperatureStat, 0, len(files)) + // example directory // device/ temp1_crit_alarm temp2_crit_alarm temp3_crit_alarm temp4_crit_alarm temp5_crit_alarm temp6_crit_alarm temp7_crit_alarm // name temp1_input temp2_input temp3_input temp4_input temp5_input temp6_input temp7_input @@ -420,44 +434,81 @@ func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, err // subsystem/ temp1_max temp2_max temp3_max temp4_max temp5_max temp6_max temp7_max // temp1_crit temp2_crit temp3_crit temp4_crit temp5_crit temp6_crit temp7_crit uevent for _, file := range files { - filename := strings.Split(filepath.Base(file), "_") - if filename[1] == "label" { - // Do not try to read the temperature of the label file - continue - } + var raw []byte + + var temperature float64 + + // Get the base directory location + directory := filepath.Dir(file) + + // Get the base filename prefix like temp1 + basename := strings.Split(filepath.Base(file), "_")[0] + + // Get the base path like /temp1 + basepath := filepath.Join(directory, basename) // Get the label of the temperature you are reading - var label string - c, _ := ioutil.ReadFile(filepath.Join(filepath.Dir(file), filename[0]+"_label")) - if c != nil { - //format the label from "Core 0" to "core0_" - label = fmt.Sprintf("%s_", strings.Join(strings.Split(strings.TrimSpace(strings.ToLower(string(c))), " "), "")) + label := "" + + if raw, _ = ioutil.ReadFile(basepath + "_label"); len(raw) != 0 { + // Format the label from "Core 0" to "core_0" + label = strings.Join(strings.Split(strings.TrimSpace(strings.ToLower(string(raw))), " "), "_") } // Get the name of the temperature you are reading - name, err := ioutil.ReadFile(filepath.Join(filepath.Dir(file), "name")) - if err != nil { + if raw, err = ioutil.ReadFile(filepath.Join(directory, "name")); err != nil { warns.Add(err) continue } + name := strings.TrimSpace(string(raw)) + + if label != "" { + name = name + "_" + label + } + // Get the temperature reading - current, err := ioutil.ReadFile(file) - if err != nil { + if raw, err = ioutil.ReadFile(file); err != nil { warns.Add(err) continue } - temperature, err := strconv.ParseFloat(strings.TrimSpace(string(current)), 64) - if err != nil { + + if temperature, err = strconv.ParseFloat(strings.TrimSpace(string(raw)), 64); err != nil { warns.Add(err) continue } - tempName := strings.TrimSpace(strings.ToLower(string(strings.Join(filename[1:], "")))) + // Add discovered temperature sensor to the list temperatures = append(temperatures, TemperatureStat{ - SensorKey: fmt.Sprintf("%s_%s%s", strings.TrimSpace(string(name)), label, tempName), - Temperature: temperature / 1000.0, + SensorKey: name, + Temperature: temperature / hostTemperatureScale, + High: optionalValueReadFromFile(basepath+"_max") / hostTemperatureScale, + Critical: optionalValueReadFromFile(basepath+"_crit") / hostTemperatureScale, }) } + return temperatures, warns.Reference() } + +func optionalValueReadFromFile(filename string) float64 { + var raw []byte + + var err error + + var value float64 + + // Check if file exists + if _, err := os.Stat(filename); os.IsNotExist(err) { + return 0 + } + + if raw, err = ioutil.ReadFile(filename); err != nil { + return 0 + } + + if value, err = strconv.ParseFloat(strings.TrimSpace(string(raw)), 64); err != nil { + return 0 + } + + return value +} diff --git a/v3/host/host_test.go b/v3/host/host_test.go index 34e373331..e4a75f941 100644 --- a/v3/host/host_test.go +++ b/v3/host/host_test.go @@ -137,10 +137,12 @@ func TestTemperatureStat_String(t *testing.T) { v := TemperatureStat{ SensorKey: "CPU", Temperature: 1.1, + High: 30.1, + Critical: 0.1, } - s := `{"sensorKey":"CPU","temperature":1.1}` + s := `{"sensorKey":"CPU","temperature":1.1,"sensorHigh":30.1,"sensorCritical":0.1}` if s != fmt.Sprintf("%v", v) { - t.Errorf("TemperatureStat string is invalid") + t.Errorf("TemperatureStat string is invalid, %v", fmt.Sprintf("%v", v)) } }