Files
pulse/internal/models/temperature_primary_test.go
T
rcourtman 4f14ad6ab9 Use one primary CPU temperature rule everywhere
Three rules chose a node's single CPU temperature. Node history took
CPUMax first, agent history took cpu_package first, and the node drawer
recomputed the maximum of package, CPUMax and every core in the
browser. On an agent-fed node CPUMax is built from cores alone and can
sit below the package, and elsewhere a core can run above the package,
so the drawer's CPU temperature headline could disagree with the line
drawn beneath it and with the History tab. Node alerts and the CPU
low/record tracking used yet another package-first rule.

models.PrimaryCPUTemperatureCelsius is now the one rule: the hottest
usable package, CPU-maximum or core reading, with a sensor-map form for
agents. Node and agent history, the router's live fallbacks, the
unified resource temperature, node temperature alerts and the CPU
low/record tracking all call it. The drawer reads the resource value
through a new Node.cpuTemperature field instead of recomputing it, and
getCpuTemperature is removed.

Mock mode wrote a second package reading under a cpu.package key into
every agent host, including node-linked ones, from a series unrelated
to the node's static reading. A node and its linked agent now carry one
reading, sampled from the agent's series under the agent's real keys,
and both histories seed from that series, so the current reading
continues the line.
2026-10-13 20:15:36 +01:00

77 lines
2.9 KiB
Go

package models
import (
"math"
"testing"
)
func TestPrimaryCPUTemperatureCelsiusTakesTheHottestReading(t *testing.T) {
for _, tc := range []struct {
name string
in *Temperature
want *float64
}{
{name: "nil", in: nil},
{name: "unavailable", in: &Temperature{Available: false, CPUPackage: 90}},
{name: "no CPU reading", in: &Temperature{Available: true}},
{name: "package only", in: &Temperature{Available: true, CPUPackage: 58}, want: ptr(58)},
{name: "producer max above package", in: &Temperature{Available: true, CPUPackage: 58, CPUMax: 60}, want: ptr(60)},
{name: "package above max of cores", in: &Temperature{Available: true, CPUPackage: 61, CPUMax: 57}, want: ptr(61)},
{name: "core above package and max", in: &Temperature{
Available: true, CPUPackage: 60, CPUMax: 60,
Cores: []CoreTemp{{Core: 0, Temp: 55}, {Core: 1, Temp: 66}},
}, want: ptr(66)},
{name: "cores only", in: &Temperature{
Available: true,
Cores: []CoreTemp{{Core: 0, Temp: 42}, {Core: 1, Temp: 55}},
}, want: ptr(55)},
{name: "unusable readings ignored", in: &Temperature{
Available: true, CPUPackage: math.NaN(), CPUMax: math.Inf(1),
Cores: []CoreTemp{{Core: 0, Temp: -1}, {Core: 1, Temp: 0}, {Core: 2, Temp: 47}},
}, want: ptr(47)},
{name: "only unusable readings", in: &Temperature{
Available: true, CPUPackage: math.NaN(),
Cores: []CoreTemp{{Core: 0, Temp: -1}},
}},
} {
t.Run(tc.name, func(t *testing.T) {
assertTemperature(t, PrimaryCPUTemperatureCelsius(tc.in), tc.want)
})
}
}
func TestHostSensorPrimaryCPUTemperatureCelsiusTakesTheHottestCPUKey(t *testing.T) {
for _, tc := range []struct {
name string
sensors map[string]float64
want *float64
}{
{name: "no sensors"},
{name: "package only", sensors: map[string]float64{"cpu_package": 62.5}, want: ptr(62.5)},
{name: "package above cores", sensors: map[string]float64{"cpu_package": 61, "cpu_core_0": 55}, want: ptr(61)},
{name: "core above package", sensors: map[string]float64{"cpu_package": 60, "cpu_core_0": 66}, want: ptr(66)},
{name: "non-CPU sensors ignored", sensors: map[string]float64{"nvme0": 80, "gpu_edge": 75, "cpu_core_1": 50}, want: ptr(50)},
{name: "unusable readings ignored", sensors: map[string]float64{"cpu_package": 0, "cpu_core_0": math.NaN(), "cpu_core_1": 44}, want: ptr(44)},
{name: "no CPU key", sensors: map[string]float64{"nvme0": 40}},
} {
t.Run(tc.name, func(t *testing.T) {
got := HostSensorPrimaryCPUTemperatureCelsius(HostSensorSummary{TemperatureCelsius: tc.sensors})
assertTemperature(t, got, tc.want)
})
}
}
func ptr(value float64) *float64 { return &value }
func assertTemperature(t *testing.T, got, want *float64) {
t.Helper()
switch {
case want == nil && got != nil:
t.Fatalf("got %v, want nil", *got)
case want != nil && got == nil:
t.Fatalf("got nil, want %v", *want)
case want != nil && *got != *want:
t.Fatalf("got %v, want %v", *got, *want)
}
}