File: sensors_linux.go

package info (click to toggle)
golang-github-shirou-gopsutil 4.25.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,824 kB
  • sloc: makefile: 76; ansic: 19; sh: 11
file content (172 lines) | stat: -rw-r--r-- 4,929 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux

package sensors

import (
	"context"
	"fmt"
	"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 warns Warnings

	files, err := getTemperatureFiles(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get temperature files, %w", err)
	}

	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 nil, err
		}
		temperatures := make([]TemperatureStat, 0, len(files))

		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 getTemperatureFiles(ctx context.Context) ([]string, error) {
	var files []string
	var err error

	// 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 nil, 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 nil, err
		}
	}

	return files, nil
}

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
}