178 lines
5.3 KiB
Go
178 lines
5.3 KiB
Go
package system
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestReadHwmonTemps(t *testing.T) {
|
|
root := t.TempDir()
|
|
// k10temp: CPU, labelled Tctl.
|
|
write(t, root, "hwmon0", "name", "k10temp")
|
|
write(t, root, "hwmon0", "temp1_input", "62125")
|
|
write(t, root, "hwmon0", "temp1_label", "Tctl")
|
|
// amdgpu: GPU, no label -> falls back to chip name. Also a sensor reading
|
|
// an implausible value that must be dropped.
|
|
write(t, root, "hwmon1", "name", "amdgpu")
|
|
write(t, root, "hwmon1", "temp1_input", "56000")
|
|
write(t, root, "hwmon1", "temp2_input", "-150") // disabled placeholder
|
|
// empty input -> skipped without error.
|
|
write(t, root, "hwmon2", "name", "cros_ec")
|
|
write(t, root, "hwmon2", "temp1_input", "")
|
|
|
|
got := readHwmonTemps(root)
|
|
if len(got) != 2 {
|
|
t.Fatalf("want 2 temps, got %d: %+v", len(got), got)
|
|
}
|
|
if got[0].Chip != "k10temp" || got[0].Label != "Tctl" || got[0].Celsius != 62.125 {
|
|
t.Errorf("cpu sensor wrong: %+v", got[0])
|
|
}
|
|
if got[1].Chip != "amdgpu" || got[1].Label != "amdgpu" || got[1].Celsius != 56 {
|
|
t.Errorf("gpu sensor wrong (label should fall back to chip): %+v", got[1])
|
|
}
|
|
}
|
|
|
|
func write(t *testing.T, root, chip, file, val string) {
|
|
t.Helper()
|
|
dir := filepath.Join(root, chip)
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, file), []byte(val), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestParseMeminfo(t *testing.T) {
|
|
data := []byte(`MemTotal: 16000 kB
|
|
MemFree: 2000 kB
|
|
MemAvailable: 8000 kB
|
|
SwapTotal: 4000 kB
|
|
SwapFree: 3000 kB
|
|
`)
|
|
m := parseMeminfo(data)
|
|
const kb = 1024
|
|
if m.TotalBytes != 16000*kb {
|
|
t.Errorf("TotalBytes = %d", m.TotalBytes)
|
|
}
|
|
if m.AvailableBytes != 8000*kb {
|
|
t.Errorf("AvailableBytes = %d", m.AvailableBytes)
|
|
}
|
|
if m.UsedBytes != (16000-8000)*kb {
|
|
t.Errorf("UsedBytes = %d, want %d", m.UsedBytes, (16000-8000)*kb)
|
|
}
|
|
if m.SwapTotalBytes != 4000*kb || m.SwapFreeBytes != 3000*kb {
|
|
t.Errorf("swap parsed wrong: %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestParseLoadavg(t *testing.T) {
|
|
l := parseLoadavg("0.42 0.55 0.61 1/234 5678")
|
|
if l.Load1 != 0.42 || l.Load5 != 0.55 || l.Load15 != 0.61 {
|
|
t.Errorf("got %+v", l)
|
|
}
|
|
bad := parseLoadavg("garbage")
|
|
if bad.Load1 != 0 || bad.Load5 != 0 || bad.Load15 != 0 {
|
|
t.Error("short input should yield zero LoadInfo")
|
|
}
|
|
}
|
|
|
|
func TestCpuFreqMHz(t *testing.T) {
|
|
root := t.TempDir()
|
|
// Hardware limits live under cpu0 only.
|
|
write(t, root, "cpu0/cpufreq", "cpuinfo_min_freq", "400000") // 400 MHz
|
|
write(t, root, "cpu0/cpufreq", "cpuinfo_max_freq", "5137000") // 5137 MHz
|
|
// Per-core current frequencies (kHz). Core 2 has the peak.
|
|
write(t, root, "cpu0/cpufreq", "scaling_cur_freq", "2500000")
|
|
write(t, root, "cpu1/cpufreq", "scaling_cur_freq", "1100000")
|
|
write(t, root, "cpu2/cpufreq", "scaling_cur_freq", "3200000")
|
|
write(t, root, "cpu3/cpufreq", "scaling_cur_freq", "2800000")
|
|
|
|
min, max, cur := cpuFreqMHz(root)
|
|
if min != 400 {
|
|
t.Errorf("min = %d, want 400", min)
|
|
}
|
|
if max != 5137 {
|
|
t.Errorf("max = %d, want 5137", max)
|
|
}
|
|
if cur != 3200 {
|
|
t.Errorf("cur = %d, want 3200 (peak across cores)", cur)
|
|
}
|
|
|
|
// Absent cpufreq → all zeros.
|
|
emptyRoot := t.TempDir()
|
|
min, max, cur = cpuFreqMHz(emptyRoot)
|
|
if min != 0 || max != 0 || cur != 0 {
|
|
t.Errorf("absent cpufreq: got min=%d max=%d cur=%d, want all 0", min, max, cur)
|
|
}
|
|
}
|
|
|
|
func TestCPUModel(t *testing.T) {
|
|
x86 := "processor\t: 0\nmodel name\t: AMD Ryzen 7 7840U\ncpu MHz\t: 3000\n"
|
|
if got := cpuModel(x86); got != "AMD Ryzen 7 7840U" {
|
|
t.Errorf("x86: got %q", got)
|
|
}
|
|
arm := "processor\t: 0\nModel\t: Raspberry Pi 5 Model B\n"
|
|
if got := cpuModel(arm); got != "Raspberry Pi 5 Model B" {
|
|
t.Errorf("arm fallback: got %q", got)
|
|
}
|
|
if got := cpuModel("processor\t: 0\n"); got != "" {
|
|
t.Errorf("no model: got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestReadProcStat(t *testing.T) {
|
|
content := `cpu 100 20 30 400 10 5 3 2 0 0
|
|
cpu0 50 10 15 200 5 3 1 1 0 0
|
|
cpu1 50 10 15 200 5 2 2 1 0 0
|
|
intr 12345
|
|
`
|
|
f := filepath.Join(t.TempDir(), "stat")
|
|
if err := os.WriteFile(f, []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cores := readProcStat(f)
|
|
if len(cores) != 2 {
|
|
t.Fatalf("want 2 cores, got %d", len(cores))
|
|
}
|
|
// cpu0: user=50 nice=10 sys=15 idle=200 iowait=5 irq=3 softirq=1 steal=1 guest=0 guest_nice=0
|
|
// total = 50+10+15+200+5+3+1+1+0+0 = 285
|
|
// idle = 200 + 5 = 205
|
|
if cores[0].core != 0 {
|
|
t.Errorf("core index = %d, want 0", cores[0].core)
|
|
}
|
|
if cores[0].total != 285 {
|
|
t.Errorf("core0 total = %d, want 285", cores[0].total)
|
|
}
|
|
if cores[0].idle != 205 {
|
|
t.Errorf("core0 idle = %d, want 205", cores[0].idle)
|
|
}
|
|
}
|
|
|
|
func TestComputeUsage(t *testing.T) {
|
|
prev := []cpuCoreTicks{
|
|
{core: 0, total: 1000, idle: 800},
|
|
{core: 1, total: 1000, idle: 900},
|
|
}
|
|
cur := []cpuCoreTicks{
|
|
{core: 0, total: 1100, idle: 850}, // 100 total delta, 50 idle delta → 50% usage
|
|
{core: 1, total: 1200, idle: 950}, // 200 total delta, 50 idle delta → 75% usage
|
|
}
|
|
usage := computeUsage(prev, cur)
|
|
if len(usage) != 2 {
|
|
t.Fatalf("want 2 entries, got %d", len(usage))
|
|
}
|
|
if usage[0].Core != 0 || usage[0].UsagePct != 50.0 {
|
|
t.Errorf("core0: got %+v, want 50%%", usage[0])
|
|
}
|
|
if usage[1].Core != 1 || usage[1].UsagePct != 75.0 {
|
|
t.Errorf("core1: got %+v, want 75%%", usage[1])
|
|
}
|
|
|
|
// Empty prev → empty result.
|
|
if got := computeUsage(nil, cur); len(got) != 0 {
|
|
t.Errorf("nil prev should yield empty, got %d", len(got))
|
|
}
|
|
}
|