[sensors] move Temperatures from host to sensors.

pull/1628/head
shirou 1 year ago
parent bc060cc227
commit ab0d977709

@ -41,13 +41,6 @@ type UserStat struct {
Started int `json:"started"`
}
type TemperatureStat struct {
SensorKey string `json:"sensorKey"`
Temperature float64 `json:"temperature"`
High float64 `json:"sensorHigh"`
Critical float64 `json:"sensorCritical"`
}
func (h InfoStat) String() string {
s, _ := json.Marshal(h)
return string(s)
@ -58,11 +51,6 @@ func (u UserStat) String() string {
return string(s)
}
func (t TemperatureStat) String() string {
s, _ := json.Marshal(t)
return string(s)
}
var enableBootTimeCache bool
// EnableBootTimeCache change cache behavior of BootTime. If true, cache BootTime value. Default is false.
@ -158,10 +146,6 @@ func KernelVersion() (string, error) {
return KernelVersionWithContext(context.Background())
}
func SensorsTemperatures() ([]TemperatureStat, error) {
return SensorsTemperaturesWithContext(context.Background())
}
func timeSince(ts uint64) uint64 {
return uint64(time.Now().Unix()) - ts
}

@ -41,10 +41,6 @@ func PlatformInformationWithContext(ctx context.Context) (string, string, string
return "", "", "", common.ErrNotImplementedError
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}
func KernelArch() (string, error) {
return "", common.ErrNotImplementedError
}

@ -141,10 +141,6 @@ func getUsersFromUtmp(utmpfile string) ([]UserStat, error) {
return ret, nil
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}
func KernelVersionWithContext(ctx context.Context) (string, error) {
_, _, version, err := PlatformInformationWithContext(ctx)
return version, err

@ -10,9 +10,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"golang.org/x/sys/unix"
@ -30,8 +28,6 @@ type lsbStruct struct {
// from utmp.h
const (
user_PROCESS = 7
hostTemperatureScale = 1000.0
)
func HostIDWithContext(ctx context.Context) (string, error) {
@ -392,147 +388,3 @@ func getSusePlatform(contents []string) string {
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
return common.VirtualizationWithContext(ctx)
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
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.HostSysWithContext(ctx, "/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
if files, err = filepath.Glob(common.HostSysWithContext(ctx, "/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
files, err = filepath.Glob(common.HostSysWithContext(ctx, "/class/thermal/thermal_zone*/"))
if err != nil {
return temperatures, err
}
for _, file := range files {
// Get the name of the temperature you are reading
name, err := os.ReadFile(filepath.Join(file, "type"))
if err != nil {
warns.Add(err)
continue
}
// Get the temperature reading
current, err := os.ReadFile(filepath.Join(file, "temp"))
if err != nil {
warns.Add(err)
continue
}
temperature, err := strconv.ParseInt(strings.TrimSpace(string(current)), 10, 64)
if err != nil {
warns.Add(err)
continue
}
temperatures = append(temperatures, TemperatureStat{
SensorKey: strings.TrimSpace(string(name)),
Temperature: float64(temperature) / 1000.0,
})
}
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
// power/ temp1_label temp2_label temp3_label temp4_label temp5_label temp6_label temp7_label
// 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 {
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 <dir>/temp1
basepath := filepath.Join(directory, basename)
// Get the label of the temperature you are reading
label := ""
if raw, _ = os.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
if raw, err = os.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
if raw, err = os.ReadFile(file); err != nil {
warns.Add(err)
continue
}
if temperature, err = strconv.ParseFloat(strings.TrimSpace(string(raw)), 64); err != nil {
warns.Add(err)
continue
}
// Add discovered temperature sensor to the list
temperatures = append(temperatures, TemperatureStat{
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 = os.ReadFile(filename); err != nil {
return 0
}
if value, err = strconv.ParseFloat(strings.TrimSpace(string(raw)), 64); err != nil {
return 0
}
return value
}

@ -45,10 +45,6 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) {
return ret, common.ErrNotImplementedError
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}
func KernelVersionWithContext(ctx context.Context) (string, error) {
_, _, version, err := PlatformInformationWithContext(ctx)
return version, err

@ -95,10 +95,6 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) {
return ret, nil
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}
func KernelVersionWithContext(ctx context.Context) (string, error) {
_, _, version, err := PlatformInformationWithContext(ctx)
return version, err

@ -1,13 +1,13 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build solaris
package host
import (
"bufio"
"bytes"
"context"
"encoding/csv"
"fmt"
"io"
"os"
"regexp"
"strconv"
@ -95,43 +95,6 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) {
return []UserStat{}, common.ErrNotImplementedError
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
var ret []TemperatureStat
out, err := invoke.CommandWithContext(ctx, "ipmitool", "-c", "sdr", "list")
if err != nil {
return ret, err
}
r := csv.NewReader(strings.NewReader(string(out)))
// Output may contain errors, e.g. "bmc_send_cmd: Permission denied", don't expect a consistent number of records
r.FieldsPerRecord = -1
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
return ret, err
}
// CPU1 Temp,40,degrees C,ok
if len(record) < 3 || record[1] == "" || record[2] != "degrees C" {
continue
}
v, err := strconv.ParseFloat(record[1], 64)
if err != nil {
return ret, err
}
ts := TemperatureStat{
SensorKey: strings.TrimSuffix(record[0], " Temp"),
Temperature: v,
}
ret = append(ret, ts)
}
return ret, nil
}
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
return "", "", common.ErrNotImplementedError
}

@ -137,19 +137,6 @@ func TestHostGuid(t *testing.T) {
}
}
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,"sensorHigh":30.1,"sensorCritical":0.1}`
if s != fmt.Sprintf("%v", v) {
t.Errorf("TemperatureStat string is invalid, %v", fmt.Sprintf("%v", v))
}
}
func TestVirtualization(t *testing.T) {
wg := sync.WaitGroup{}
testCount := 10

@ -6,7 +6,6 @@ package host
import (
"context"
"fmt"
"math"
"strconv"
"strings"
"sync/atomic"
@ -16,7 +15,6 @@ import (
"github.com/shirou/gopsutil/v4/internal/common"
"github.com/shirou/gopsutil/v4/process"
"github.com/yusufpapurcu/wmi"
"golang.org/x/sys/windows"
)
@ -57,13 +55,6 @@ type systemInfo struct {
wProcessorRevision uint16
}
type msAcpi_ThermalZoneTemperature struct {
Active bool
CriticalTripPoint uint32
CurrentTemperature uint32
InstanceName string
}
func HostIDWithContext(ctx context.Context) (string, error) {
// there has been reports of issues on 32bit using golang.org/x/sys/windows/registry, see https://github.com/shirou/gopsutil/pull/312#issuecomment-277422612
// for rationale of using windows.RegOpenKeyEx/RegQueryValueEx instead of registry.OpenKey/GetStringValue
@ -232,33 +223,6 @@ func UsersWithContext(ctx context.Context) ([]UserStat, error) {
return ret, common.ErrNotImplementedError
}
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
var ret []TemperatureStat
var dst []msAcpi_ThermalZoneTemperature
q := wmi.CreateQuery(&dst, "")
if err := common.WMIQueryWithContext(ctx, q, &dst, nil, "root/wmi"); err != nil {
return ret, err
}
for _, v := range dst {
ts := TemperatureStat{
SensorKey: v.InstanceName,
Temperature: kelvinToCelsius(v.CurrentTemperature, 2),
}
ret = append(ret, ts)
}
return ret, nil
}
func kelvinToCelsius(temp uint32, n int) float64 {
// wmi return temperature Kelvin * 10, so need to divide the result by 10,
// and then minus 273.15 to get °Celsius.
t := float64(temp/10) - 273.15
n10 := math.Pow10(n)
return math.Trunc((t+0.5/n10)*n10) / n10
}
func VirtualizationWithContext(ctx context.Context) (string, string, error) {
return "", "", common.ErrNotImplementedError
}

@ -0,0 +1,30 @@
// SPDX-License-Identifier: BSD-3-Clause
package sensors
import (
"context"
"encoding/json"
"github.com/shirou/gopsutil/v4/internal/common"
)
type Warnings = common.Warnings
var invoke common.Invoker = common.Invoke{}
type TemperatureStat struct {
SensorKey string `json:"sensorKey"`
Temperature float64 `json:"temperature"`
High float64 `json:"sensorHigh"`
Critical float64 `json:"sensorCritical"`
}
func (t TemperatureStat) String() string {
s, _ := json.Marshal(t)
return string(s)
}
func SensorsTemperatures() ([]TemperatureStat, error) {
return TemperaturesWithContext(context.Background())
}

@ -1,14 +1,14 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin && cgo
package host
package sensors
// #cgo LDFLAGS: -framework IOKit
// #include "smc_darwin.h"
import "C"
import "context"
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
temperatureKeys := []string{
C.AMBIENT_AIR_0,
C.AMBIENT_AIR_1,

@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin && !cgo
package host
package sensors
import (
"context"
@ -9,6 +9,6 @@ import (
"github.com/shirou/gopsutil/v4/internal/common"
)
func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}

@ -0,0 +1,14 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build !darwin && !linux && !freebsd && !openbsd && !netbsd && !solaris && !windows
package sensors
import (
"context"
"github.com/shirou/gopsutil/v4/internal/common"
)
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}

@ -0,0 +1,14 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build freebsd
package freebsd
import (
"context"
"github.com/shirou/gopsutil/v4/internal/common"
)
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}

@ -0,0 +1,163 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
package sensors
import (
"context"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/shirou/gopsutil/v4/internal/common"
)
// from utmp.h
const (
hostTemperatureScale = 1000.0
)
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
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.HostSysWithContext(ctx, "/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
if files, err = filepath.Glob(common.HostSysWithContext(ctx, "/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
files, err = filepath.Glob(common.HostSysWithContext(ctx, "/class/thermal/thermal_zone*/"))
if err != nil {
return temperatures, err
}
for _, file := range files {
// Get the name of the temperature you are reading
name, err := os.ReadFile(filepath.Join(file, "type"))
if err != nil {
warns.Add(err)
continue
}
// Get the temperature reading
current, err := os.ReadFile(filepath.Join(file, "temp"))
if err != nil {
warns.Add(err)
continue
}
temperature, err := strconv.ParseInt(strings.TrimSpace(string(current)), 10, 64)
if err != nil {
warns.Add(err)
continue
}
temperatures = append(temperatures, TemperatureStat{
SensorKey: strings.TrimSpace(string(name)),
Temperature: float64(temperature) / 1000.0,
})
}
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
// power/ temp1_label temp2_label temp3_label temp4_label temp5_label temp6_label temp7_label
// 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 {
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 <dir>/temp1
basepath := filepath.Join(directory, basename)
// Get the label of the temperature you are reading
label := ""
if raw, _ = os.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
if raw, err = os.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
if raw, err = os.ReadFile(file); err != nil {
warns.Add(err)
continue
}
if temperature, err = strconv.ParseFloat(strings.TrimSpace(string(raw)), 64); err != nil {
warns.Add(err)
continue
}
// Add discovered temperature sensor to the list
temperatures = append(temperatures, TemperatureStat{
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 = os.ReadFile(filename); err != nil {
return 0
}
if value, err = strconv.ParseFloat(strings.TrimSpace(string(raw)), 64); err != nil {
return 0
}
return value
}

@ -0,0 +1,14 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build netbsd
package sensors
import (
"context"
"github.com/shirou/gopsutil/v4/internal/common"
)
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}

@ -0,0 +1,14 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build openbsd
package openbsd
import (
"context"
"github.com/shirou/gopsutil/v4/internal/common"
)
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
return []TemperatureStat{}, common.ErrNotImplementedError
}

@ -0,0 +1,49 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build solaris
package sensors
import (
"context"
"encoding/csv"
"io"
"strconv"
"strings"
)
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
var ret []TemperatureStat
out, err := invoke.CommandWithContext(ctx, "ipmitool", "-c", "sdr", "list")
if err != nil {
return ret, err
}
r := csv.NewReader(strings.NewReader(string(out)))
// Output may contain errors, e.g. "bmc_send_cmd: Permission denied", don't expect a consistent number of records
r.FieldsPerRecord = -1
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
return ret, err
}
// CPU1 Temp,40,degrees C,ok
if len(record) < 3 || record[1] == "" || record[2] != "degrees C" {
continue
}
v, err := strconv.ParseFloat(record[1], 64)
if err != nil {
return ret, err
}
ts := TemperatureStat{
SensorKey: strings.TrimSuffix(record[0], " Temp"),
Temperature: v,
}
ret = append(ret, ts)
}
return ret, nil
}

@ -0,0 +1,21 @@
// SPDX-License-Identifier: BSD-3-Clause
package sensors
import (
"fmt"
"testing"
)
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,"sensorHigh":30.1,"sensorCritical":0.1}`
if s != fmt.Sprintf("%v", v) {
t.Errorf("TemperatureStat string is invalid, %v", fmt.Sprintf("%v", v))
}
}

@ -0,0 +1,46 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build windows
package sensors
import (
"context"
"math"
"github.com/shirou/gopsutil/v4/internal/common"
"github.com/yusufpapurcu/wmi"
)
type msAcpi_ThermalZoneTemperature struct {
Active bool
CriticalTripPoint uint32
CurrentTemperature uint32
InstanceName string
}
func TemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
var ret []TemperatureStat
var dst []msAcpi_ThermalZoneTemperature
q := wmi.CreateQuery(&dst, "")
if err := common.WMIQueryWithContext(ctx, q, &dst, nil, "root/wmi"); err != nil {
return ret, err
}
for _, v := range dst {
ts := TemperatureStat{
SensorKey: v.InstanceName,
Temperature: kelvinToCelsius(v.CurrentTemperature, 2),
}
ret = append(ret, ts)
}
return ret, nil
}
func kelvinToCelsius(temp uint32, n int) float64 {
// wmi return temperature Kelvin * 10, so need to divide the result by 10,
// and then minus 273.15 to get °Celsius.
t := float64(temp/10) - 273.15
n10 := math.Pow10(n)
return math.Trunc((t+0.5/n10)*n10) / n10
}
Loading…
Cancel
Save