mirror of
https://github.com/henrygd/beszel.git
synced 2026-08-19 08:47:46 +02:00
feat: fan RPM monitoring (#2032)
Adds fan RPM monitoring as a peer to the existing temperature collection, addressing #1918. --------- Co-authored-by: henrygd <hank@henrygd.me>
This commit is contained in:
87
agent/fans_test.go
Normal file
87
agent/fans_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
//go:build testing
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// writeFile creates path with parents and writes contents.
|
||||
func writeFile(t *testing.T, path, contents string) {
|
||||
t.Helper()
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
|
||||
require.NoError(t, os.WriteFile(path, []byte(contents), 0o644))
|
||||
}
|
||||
|
||||
// TestReadHwmonFans verifies the /sys/class/hwmon walker:
|
||||
// - picks up fan*_input from every chip,
|
||||
// - keys entries by chip name + sensor label (or fan idx if no label),
|
||||
// - retains 0 RPM for stopped fans,
|
||||
// - tolerates chips with no fan files at all.
|
||||
func TestReadHwmonFans(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
// hwmon0: Raspberry Pi 5 active cooler — one fan, no label.
|
||||
writeFile(t, filepath.Join(root, "hwmon0", "name"), "pwmfan\n")
|
||||
writeFile(t, filepath.Join(root, "hwmon0", "fan1_input"), "6500\n")
|
||||
|
||||
// hwmon1: a thermal-only chip, no fan files. Must not error.
|
||||
writeFile(t, filepath.Join(root, "hwmon1", "name"), "cpu_thermal\n")
|
||||
writeFile(t, filepath.Join(root, "hwmon1", "temp1_input"), "55000\n")
|
||||
|
||||
// hwmon2: two fans — one stopped (0 RPM) and one labeled "chassis".
|
||||
writeFile(t, filepath.Join(root, "hwmon2", "name"), "nct6798\n")
|
||||
writeFile(t, filepath.Join(root, "hwmon2", "fan1_input"), "0\n")
|
||||
writeFile(t, filepath.Join(root, "hwmon2", "fan2_input"), "1200\n")
|
||||
writeFile(t, filepath.Join(root, "hwmon2", "fan2_label"), "chassis\n")
|
||||
|
||||
fans, err := readHwmonFans(root)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, map[string]uint16{
|
||||
"pwmfan_fan1": 6500,
|
||||
"nct6798_fan1": 0,
|
||||
"nct6798_chassis": 1200,
|
||||
}, fans)
|
||||
}
|
||||
|
||||
// TestReadHwmonFansMissingRoot returns an error rather than panicking when the
|
||||
// hwmon root doesn't exist (e.g. running on a kernel without hwmon support).
|
||||
func TestReadHwmonFansMissingRoot(t *testing.T) {
|
||||
_, err := readHwmonFans(filepath.Join(t.TempDir(), "does-not-exist"))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// TestReadHwmonFansEmpty returns an empty map (not nil error) when the root
|
||||
// exists but contains no chips at all.
|
||||
func TestReadHwmonFansEmpty(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fans, err := readHwmonFans(root)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, fans)
|
||||
}
|
||||
|
||||
func TestFanDiscoveryCache(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
input := filepath.Join(root, "hwmon0", "fan1_input")
|
||||
writeFile(t, filepath.Join(root, "hwmon0", "name"), "chip\n")
|
||||
writeFile(t, input, "1000\n")
|
||||
|
||||
getSensors := newFanSensorCache(root)
|
||||
sensors, err := getSensors()
|
||||
require.NoError(t, err)
|
||||
fans := readFanSensors(sensors)
|
||||
assert.Equal(t, uint16(1000), fans["chip_fan1"])
|
||||
|
||||
writeFile(t, input, "1200\n")
|
||||
writeFile(t, filepath.Join(root, "hwmon0", "fan1_label"), "case\n")
|
||||
sensors, err = getSensors()
|
||||
require.NoError(t, err)
|
||||
fans = readFanSensors(sensors)
|
||||
assert.Equal(t, map[string]uint16{"chip_fan1": 1200}, fans)
|
||||
}
|
||||
Reference in New Issue
Block a user