mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-25 02:47:46 +02:00
fix(agent): SKIP_GPU excludes GPU hwmon from temperatures and fans (#2313)
This commit is contained in:
@@ -252,7 +252,11 @@ func (a *Agent) gatherStats(options common.DataRequestOptions) *system.CombinedD
|
|||||||
// Start initializes and starts the agent with optional WebSocket connection
|
// Start initializes and starts the agent with optional WebSocket connection
|
||||||
func (a *Agent) Start(serverOptions ServerOptions) error {
|
func (a *Agent) Start(serverOptions ServerOptions) error {
|
||||||
a.keys = serverOptions.Keys
|
a.keys = serverOptions.Keys
|
||||||
return a.connectionManager.Start(serverOptions)
|
err := a.connectionManager.Start(serverOptions)
|
||||||
|
if err != nil {
|
||||||
|
a.cleanupSensorShadow()
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) getFingerprint() string {
|
func (a *Agent) getFingerprint() string {
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ func (c *ConnectionManager) stop() error {
|
|||||||
_ = c.agent.StopServer()
|
_ = c.agent.StopServer()
|
||||||
c.agent.monitorManager.Stop()
|
c.agent.monitorManager.Stop()
|
||||||
c.closeWebSocket()
|
c.closeWebSocket()
|
||||||
|
c.agent.cleanupSensorShadow()
|
||||||
return health.CleanUp()
|
return health.CleanUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type fanSensor struct {
|
type fanSensor struct {
|
||||||
key, path string
|
key, path, chip string
|
||||||
}
|
}
|
||||||
|
|
||||||
var getFanSensors = newFanSensorCache(hwmonRoot)
|
var getFanSensors = newFanSensorCache(hwmonRoot)
|
||||||
@@ -34,6 +34,10 @@ func (a *Agent) updateFans(systemStats *system.Stats) {
|
|||||||
slog.Debug("Error reading fans", "err", err)
|
slog.Debug("Error reading fans", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Filter before reading fan*_input: each read can wake an idle GPU.
|
||||||
|
if a.sensorConfig != nil && a.sensorConfig.skipGPU {
|
||||||
|
sensors = filterGpuFans(sensors)
|
||||||
|
}
|
||||||
fans := readFanSensors(sensors)
|
fans := readFanSensors(sensors)
|
||||||
if len(fans) == 0 {
|
if len(fans) == 0 {
|
||||||
return
|
return
|
||||||
@@ -100,7 +104,7 @@ func discoverHwmonFans(root string) ([]fanSensor, error) {
|
|||||||
if label != "" {
|
if label != "" {
|
||||||
key = chipName + "_" + label
|
key = chipName + "_" + label
|
||||||
}
|
}
|
||||||
sensors = append(sensors, fanSensor{key, inputPath})
|
sensors = append(sensors, fanSensor{key, inputPath, chipName})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sensors, nil
|
return sensors, nil
|
||||||
@@ -115,3 +119,15 @@ func readFanSensors(sensors []fanSensor) map[string]uint16 {
|
|||||||
}
|
}
|
||||||
return fans
|
return fans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterGpuFans drops GPU chips without touching the shared cache backing array.
|
||||||
|
func filterGpuFans(sensors []fanSensor) []fanSensor {
|
||||||
|
kept := make([]fanSensor, 0, len(sensors))
|
||||||
|
for _, sensor := range sensors {
|
||||||
|
if isGpuChipName(sensor.chip) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kept = append(kept, sensor)
|
||||||
|
}
|
||||||
|
return kept
|
||||||
|
}
|
||||||
|
|||||||
@@ -103,3 +103,20 @@ func TestFanDiscoveryCache(t *testing.T) {
|
|||||||
fans = readFanSensors(sensors)
|
fans = readFanSensors(sensors)
|
||||||
assert.Equal(t, map[string]uint16{"chip_fan1": 1200}, fans)
|
assert.Equal(t, map[string]uint16{"chip_fan1": 1200}, fans)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilterGpuFans(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
writeFile(t, filepath.Join(root, "hwmon0", "name"), "xe\n")
|
||||||
|
writeFile(t, filepath.Join(root, "hwmon0", "fan1_input"), "1200\n")
|
||||||
|
writeFile(t, filepath.Join(root, "hwmon1", "name"), "nct6798\n")
|
||||||
|
writeFile(t, filepath.Join(root, "hwmon1", "fan1_input"), "800\n")
|
||||||
|
|
||||||
|
discovered, err := discoverHwmonFans(root)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, discovered, 2)
|
||||||
|
|
||||||
|
filtered := filterGpuFans(discovered)
|
||||||
|
require.Len(t, filtered, 1)
|
||||||
|
assert.Equal(t, "nct6798_fan1", filtered[0].key)
|
||||||
|
assert.Len(t, discovered, 2)
|
||||||
|
}
|
||||||
|
|||||||
31
agent/gpu.go
31
agent/gpu.go
@@ -454,8 +454,8 @@ func (gm *GPUManager) storeSnapshot(id string, gpu *system.GPUData, cacheKey uin
|
|||||||
// It only reports capability presence and does not apply policy decisions.
|
// It only reports capability presence and does not apply policy decisions.
|
||||||
func (gm *GPUManager) discoverGpuCapabilities() gpuCapabilities {
|
func (gm *GPUManager) discoverGpuCapabilities() gpuCapabilities {
|
||||||
caps := gpuCapabilities{
|
caps := gpuCapabilities{
|
||||||
hasAmdSysfs: gm.hasAmdSysfs(),
|
hasAmdSysfs: gm.hasAmdSysfs(),
|
||||||
hasXe: gm.hasXe(),
|
hasXe: gm.hasXe(),
|
||||||
hasIntelSysfs: gm.hasIntelSysfs(),
|
hasIntelSysfs: gm.hasIntelSysfs(),
|
||||||
}
|
}
|
||||||
if _, err := exec.LookPath(nvidiaSmiCmd); err == nil {
|
if _, err := exec.LookPath(nvidiaSmiCmd); err == nil {
|
||||||
@@ -750,9 +750,36 @@ func (gm *GPUManager) resolveLegacyCollectorPriority(caps gpuCapabilities) []col
|
|||||||
return priorities
|
return priorities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gpuHwmonChips are hwmon chip names belonging to GPUs. Sensor reads on some
|
||||||
|
// of these drivers (notably Intel Xe, where each read is a runtime PM resume)
|
||||||
|
// wake the card, so SKIP_GPU must avoid touching them, not just hide them.
|
||||||
|
var gpuHwmonChips = []string{"xe", "i915", "amdgpu", "radeon", "nvidia", "nouveau"}
|
||||||
|
|
||||||
|
func isGpuChipName(name string) bool {
|
||||||
|
name = strings.ToLower(strings.TrimSpace(name))
|
||||||
|
for _, chip := range gpuHwmonChips {
|
||||||
|
if name == chip {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SensorKeys are "<chip>" or "<chip>_<label>".
|
||||||
|
func isGpuSensorKey(key string) bool {
|
||||||
|
key = strings.ToLower(strings.TrimSpace(key))
|
||||||
|
for _, chip := range gpuHwmonChips {
|
||||||
|
if key == chip || strings.HasPrefix(key, chip+"_") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// NewGPUManager creates and initializes a new GPUManager
|
// NewGPUManager creates and initializes a new GPUManager
|
||||||
func NewGPUManager() (*GPUManager, error) {
|
func NewGPUManager() (*GPUManager, error) {
|
||||||
if skipGPU, _ := utils.GetEnv("SKIP_GPU"); skipGPU == "true" {
|
if skipGPU, _ := utils.GetEnv("SKIP_GPU"); skipGPU == "true" {
|
||||||
|
slog.Info("SKIP_GPU enabled, skipping GPU monitoring (collectors, temperatures, and fans)")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var gm GPUManager
|
var gm GPUManager
|
||||||
|
|||||||
125
agent/sensors.go
125
agent/sensors.go
@@ -5,7 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -32,6 +34,8 @@ type SensorConfig struct {
|
|||||||
isBlacklist bool
|
isBlacklist bool
|
||||||
hasWildcards bool
|
hasWildcards bool
|
||||||
skipCollection bool
|
skipCollection bool
|
||||||
|
skipGPU bool
|
||||||
|
sensorShadow string
|
||||||
firstRun bool
|
firstRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,13 +45,14 @@ func (a *Agent) newSensorConfig() *SensorConfig {
|
|||||||
sensorsEnvVal, sensorsSet := utils.GetEnv("SENSORS")
|
sensorsEnvVal, sensorsSet := utils.GetEnv("SENSORS")
|
||||||
skipCollection := sensorsSet && sensorsEnvVal == ""
|
skipCollection := sensorsSet && sensorsEnvVal == ""
|
||||||
sensorsTimeout, _ := utils.GetEnv("SENSORS_TIMEOUT")
|
sensorsTimeout, _ := utils.GetEnv("SENSORS_TIMEOUT")
|
||||||
|
skipGPU, _ := utils.GetEnv("SKIP_GPU")
|
||||||
|
|
||||||
return a.newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal, sensorsTimeout, skipCollection)
|
return a.newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal, sensorsTimeout, skipCollection, skipGPU == "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSensorConfigWithEnv creates a SensorConfig with the provided environment variables
|
// newSensorConfigWithEnv creates a SensorConfig with the provided environment variables
|
||||||
// sensorsSet indicates if the SENSORS environment variable was explicitly set (even to empty string)
|
// sensorsSet indicates if the SENSORS environment variable was explicitly set (even to empty string)
|
||||||
func (a *Agent) newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal, sensorsTimeout string, skipCollection bool) *SensorConfig {
|
func (a *Agent) newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal, sensorsTimeout string, skipCollection, skipGPU bool) *SensorConfig {
|
||||||
timeout := 2 * time.Second
|
timeout := 2 * time.Second
|
||||||
if sensorsTimeout != "" {
|
if sensorsTimeout != "" {
|
||||||
if d, err := time.ParseDuration(sensorsTimeout); err == nil {
|
if d, err := time.ParseDuration(sensorsTimeout); err == nil {
|
||||||
@@ -62,6 +67,7 @@ func (a *Agent) newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal,
|
|||||||
primarySensor: primarySensor,
|
primarySensor: primarySensor,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
skipCollection: skipCollection,
|
skipCollection: skipCollection,
|
||||||
|
skipGPU: skipGPU,
|
||||||
firstRun: true,
|
firstRun: true,
|
||||||
sensors: make(map[string]struct{}),
|
sensors: make(map[string]struct{}),
|
||||||
}
|
}
|
||||||
@@ -73,6 +79,19 @@ func (a *Agent) newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal,
|
|||||||
common.EnvKey, common.EnvMap{common.HostSysEnvKey: sysSensors},
|
common.EnvKey, common.EnvMap{common.HostSysEnvKey: sysSensors},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if skipGPU && runtime.GOOS == "linux" {
|
||||||
|
// gopsutil reads every temp*_input before results can be filtered, so
|
||||||
|
// point it at a shadow tree built from the effective sysfs root instead.
|
||||||
|
if shadow, err := buildNonGpuSysShadow(effectiveSysRoot(config.context)); err == nil {
|
||||||
|
slog.Info("SKIP_GPU enabled, using non-GPU sensor sysfs shadow", "path", shadow)
|
||||||
|
config.sensorShadow = shadow
|
||||||
|
config.context = context.WithValue(config.context,
|
||||||
|
common.EnvKey, common.EnvMap{common.HostSysEnvKey: shadow},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
slog.Warn("SKIP_GPU sensor shadow unavailable, falling back to post-read filtering", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handle blacklist
|
// handle blacklist
|
||||||
if strings.HasPrefix(sensorsEnvVal, "-") {
|
if strings.HasPrefix(sensorsEnvVal, "-") {
|
||||||
@@ -149,6 +168,9 @@ func (a *Agent) updateTemperatures(systemStats *system.Stats) {
|
|||||||
if !isValidSensor(sensorName, a.sensorConfig) {
|
if !isValidSensor(sensorName, a.sensorConfig) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if a.sensorConfig.skipGPU && isGpuSensorKey(sensorName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// set dashboard temperature
|
// set dashboard temperature
|
||||||
switch a.sensorConfig.primarySensor {
|
switch a.sensorConfig.primarySensor {
|
||||||
case "":
|
case "":
|
||||||
@@ -245,3 +267,102 @@ func scaleTemperature(temp float64) float64 {
|
|||||||
}
|
}
|
||||||
return scaled100
|
return scaled100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// effectiveSysRoot mirrors gopsutil's HostSys lookup, which lives in its
|
||||||
|
// internal package: context override, then HOST_SYS env, then /sys.
|
||||||
|
func effectiveSysRoot(ctx context.Context) string {
|
||||||
|
if envMap, ok := ctx.Value(common.EnvKey).(common.EnvMap); ok {
|
||||||
|
if v := envMap[common.HostSysEnvKey]; v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := os.Getenv("HOST_SYS"); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return "/sys"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *SensorConfig) cleanupSensorShadow() {
|
||||||
|
if config.sensorShadow == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(config.sensorShadow); err != nil {
|
||||||
|
slog.Warn("Error removing sensor sysfs shadow", "path", config.sensorShadow, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.sensorShadow = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) cleanupSensorShadow() {
|
||||||
|
if a.sensorConfig != nil {
|
||||||
|
a.sensorConfig.cleanupSensorShadow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGpuThermalZone(zoneType string) bool {
|
||||||
|
zoneType = strings.ToLower(strings.TrimSpace(zoneType))
|
||||||
|
return isGpuChipName(zoneType) || strings.Contains(zoneType, "gpu")
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildNonGpuSysShadow links non-GPU sensor directories into a temp dir. Only
|
||||||
|
// static chip names and thermal-zone types are read; no sensor values are touched.
|
||||||
|
func buildNonGpuSysShadow(sysRoot string) (string, error) {
|
||||||
|
shadow, err := os.MkdirTemp("", "beszel-sensors-*")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
shadowHwmon := filepath.Join(shadow, "class", "hwmon")
|
||||||
|
if err := os.MkdirAll(shadowHwmon, 0o755); err != nil {
|
||||||
|
os.RemoveAll(shadow)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
entries, err := os.ReadDir(filepath.Join(sysRoot, "class", "hwmon"))
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
os.RemoveAll(shadow)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
chipDir := filepath.Join(sysRoot, "class", "hwmon", entry.Name())
|
||||||
|
// Some hwmon devices expose name under device/ (gopsutil's CentOS fallback).
|
||||||
|
name, ok := utils.ReadStringFileOK(filepath.Join(chipDir, "name"))
|
||||||
|
if !ok {
|
||||||
|
name, ok = utils.ReadStringFileOK(filepath.Join(chipDir, "device", "name"))
|
||||||
|
}
|
||||||
|
if !ok || isGpuChipName(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := os.Symlink(chipDir, filepath.Join(shadowHwmon, entry.Name())); err != nil {
|
||||||
|
os.RemoveAll(shadow)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thermalEntries, err := os.ReadDir(filepath.Join(sysRoot, "class", "thermal"))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return shadow, nil
|
||||||
|
}
|
||||||
|
os.RemoveAll(shadow)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
shadowThermal := filepath.Join(shadow, "class", "thermal")
|
||||||
|
if err := os.MkdirAll(shadowThermal, 0o755); err != nil {
|
||||||
|
os.RemoveAll(shadow)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, entry := range thermalEntries {
|
||||||
|
if !strings.HasPrefix(entry.Name(), "thermal_zone") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
zoneDir := filepath.Join(sysRoot, "class", "thermal", entry.Name())
|
||||||
|
zoneType, ok := utils.ReadStringFileOK(filepath.Join(zoneDir, "type"))
|
||||||
|
if !ok || isGpuThermalZone(zoneType) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := os.Symlink(zoneDir, filepath.Join(shadowThermal, entry.Name())); err != nil {
|
||||||
|
os.RemoveAll(shadow)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shadow, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -328,7 +330,7 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := agent.newSensorConfigWithEnv(tt.primarySensor, tt.sysSensors, tt.sensors, tt.sensorsTimeout, tt.skipCollection)
|
result := agent.newSensorConfigWithEnv(tt.primarySensor, tt.sysSensors, tt.sensors, tt.sensorsTimeout, tt.skipCollection, false)
|
||||||
|
|
||||||
// Check primary sensor
|
// Check primary sensor
|
||||||
assert.Equal(t, tt.expectedConfig.primarySensor, result.primarySensor)
|
assert.Equal(t, tt.expectedConfig.primarySensor, result.primarySensor)
|
||||||
@@ -620,3 +622,143 @@ func TestUpdateTemperaturesSkipsOnTimeout(t *testing.T) {
|
|||||||
assert.Equal(t, 0.0, agent.systemInfo.DashboardTemp)
|
assert.Equal(t, 0.0, agent.systemInfo.DashboardTemp)
|
||||||
assert.Equal(t, map[string]float64{}, stats.Temperatures)
|
assert.Equal(t, map[string]float64{}, stats.Temperatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsGpuSensorKey(t *testing.T) {
|
||||||
|
for _, key := range []string{"xe", "XE_temp1", "amdgpu_edge", "NVIDIA"} {
|
||||||
|
assert.True(t, isGpuSensorKey(key), key)
|
||||||
|
}
|
||||||
|
for _, key := range []string{"coretemp_core_0", "acpitz", "xen_temp", "myxe", ""} {
|
||||||
|
assert.False(t, isGpuSensorKey(key), key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipGpuSensorShadow(t *testing.T) {
|
||||||
|
sysRoot := t.TempDir()
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0", "name"), "coretemp\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0", "temp1_input"), "55000\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon1", "name"), "xe\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon1", "temp1_input"), "48000\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "thermal", "thermal_zone0", "type"), "cpu-thermal\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "thermal", "thermal_zone0", "temp"), "55000\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "thermal", "thermal_zone1", "type"), "gpu\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "thermal", "thermal_zone1", "temp"), "48000\n")
|
||||||
|
|
||||||
|
shadow, err := buildNonGpuSysShadow(sysRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { os.RemoveAll(shadow) })
|
||||||
|
|
||||||
|
assert.FileExists(t, filepath.Join(shadow, "class", "hwmon", "hwmon0", "temp1_input"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(shadow, "class", "hwmon", "hwmon1"))
|
||||||
|
assert.FileExists(t, filepath.Join(shadow, "class", "thermal", "thermal_zone0", "temp"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(shadow, "class", "thermal", "thermal_zone1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipGpuSensorShadowDeviceName(t *testing.T) {
|
||||||
|
sysRoot := t.TempDir()
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0", "device", "name"), "coretemp\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0", "device", "temp1_input"), "55000\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon1", "device", "name"), "xe\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon1", "device", "temp1_input"), "48000\n")
|
||||||
|
|
||||||
|
shadow, err := buildNonGpuSysShadow(sysRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { os.RemoveAll(shadow) })
|
||||||
|
|
||||||
|
assert.FileExists(t, filepath.Join(shadow, "class", "hwmon", "hwmon0", "device", "temp1_input"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(shadow, "class", "hwmon", "hwmon1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipGpuSensorShadowKeepsThermalZonesWithoutNonGpuHwmon(t *testing.T) {
|
||||||
|
sysRoot := t.TempDir()
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0", "name"), "xe\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0", "temp1_input"), "48000\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "thermal", "thermal_zone0", "type"), "cpu-thermal\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "thermal", "thermal_zone0", "temp"), "55000\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "thermal", "thermal_zone1", "type"), "gpu\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "thermal", "thermal_zone1", "temp"), "48000\n")
|
||||||
|
|
||||||
|
shadow, err := buildNonGpuSysShadow(sysRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { os.RemoveAll(shadow) })
|
||||||
|
|
||||||
|
hwmonTemps, err := filepath.Glob(filepath.Join(shadow, "class", "hwmon", "hwmon*", "temp*_input"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, hwmonTemps)
|
||||||
|
assert.FileExists(t, filepath.Join(shadow, "class", "thermal", "thermal_zone0", "temp"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(shadow, "class", "thermal", "thermal_zone1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSensorConfigSkipGpuWiresShadow(t *testing.T) {
|
||||||
|
t.Setenv("SKIP_GPU", "true")
|
||||||
|
|
||||||
|
agent := &Agent{}
|
||||||
|
config := agent.newSensorConfig()
|
||||||
|
|
||||||
|
assert.True(t, config.skipGPU)
|
||||||
|
envMap, ok := config.context.Value(common.EnvKey).(common.EnvMap)
|
||||||
|
require.True(t, ok, "SKIP_GPU should point the sensor context at a sysfs shadow")
|
||||||
|
shadow, ok := envMap[common.HostSysEnvKey]
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.DirExists(t, filepath.Join(shadow, "class", "hwmon"))
|
||||||
|
assert.Equal(t, shadow, config.sensorShadow)
|
||||||
|
config.cleanupSensorShadow()
|
||||||
|
assert.NoDirExists(t, shadow)
|
||||||
|
assert.Empty(t, config.sensorShadow)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipGpuShadowUsesSysSensorsRoot(t *testing.T) {
|
||||||
|
sysRoot := t.TempDir()
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0", "name"), "coretemp\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0", "temp1_input"), "55000\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon1", "name"), "xe\n")
|
||||||
|
writeFile(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon1", "temp1_input"), "48000\n")
|
||||||
|
|
||||||
|
agent := &Agent{}
|
||||||
|
config := agent.newSensorConfigWithEnv("", sysRoot, "", "", false, true)
|
||||||
|
t.Cleanup(config.cleanupSensorShadow)
|
||||||
|
|
||||||
|
envMap, ok := config.context.Value(common.EnvKey).(common.EnvMap)
|
||||||
|
require.True(t, ok, "SKIP_GPU should point the sensor context at a sysfs shadow")
|
||||||
|
shadow, ok := envMap[common.HostSysEnvKey]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotEqual(t, sysRoot, shadow, "shadow must not be the SYS_SENSORS tree itself")
|
||||||
|
|
||||||
|
target, err := os.Readlink(filepath.Join(shadow, "class", "hwmon", "hwmon0"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, filepath.Join(sysRoot, "class", "hwmon", "hwmon0"), target)
|
||||||
|
assert.NoFileExists(t, filepath.Join(shadow, "class", "hwmon", "hwmon1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateTemperaturesSkipGpu(t *testing.T) {
|
||||||
|
originalGetSensorTemps := getSensorTemps
|
||||||
|
t.Cleanup(func() {
|
||||||
|
getSensorTemps = originalGetSensorTemps
|
||||||
|
})
|
||||||
|
getSensorTemps = func(ctx context.Context) ([]sensors.TemperatureStat, error) {
|
||||||
|
return []sensors.TemperatureStat{
|
||||||
|
{SensorKey: "coretemp_core_0", Temperature: 55},
|
||||||
|
{SensorKey: "XE", Temperature: 48},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newAgent := func(skipGPU bool) *Agent {
|
||||||
|
agent := &Agent{
|
||||||
|
systemInfo: system.Info{},
|
||||||
|
sensorConfig: &SensorConfig{
|
||||||
|
context: context.Background(),
|
||||||
|
timeout: 2 * time.Second,
|
||||||
|
sensors: map[string]struct{}{},
|
||||||
|
skipGPU: skipGPU,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return agent
|
||||||
|
}
|
||||||
|
|
||||||
|
stats := &system.Stats{}
|
||||||
|
newAgent(true).updateTemperatures(stats)
|
||||||
|
assert.Equal(t, map[string]float64{"coretemp_core_0": 55}, stats.Temperatures)
|
||||||
|
|
||||||
|
stats = &system.Stats{}
|
||||||
|
newAgent(false).updateTemperatures(stats)
|
||||||
|
assert.Len(t, stats.Temperatures, 2)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user