mirror of https://github.com/shirou/gopsutil
[sensors] move Temperatures from host to sensors.
parent
bc060cc227
commit
ab0d977709
@ -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,
|
@ -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…
Reference in New Issue