Files
pulse/internal/models/temperature_primary.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

72 lines
2.1 KiB
Go

package models
import (
"math"
"strings"
)
// PrimaryCPUTemperatureCelsius is Pulse's one rule for a machine's headline
// CPU temperature: the hottest current CPU reading, taken from the package
// (Intel Package id, AMD Tctl/Tdie), the producer's CPU maximum, or any core.
//
// Every surface that names a single CPU temperature reads this value: node and
// agent metrics history, the unified resource temperature, node temperature
// alerts and the CPU low/record tracking. The node drawer shows the resource
// value instead of recomputing it, so its headline matches the line drawn
// beneath it. The hottest reading wins because it is the one nearest a thermal
// limit: AMD Tctl and Tccd, or a core running above the package, can differ by
// several degrees.
func PrimaryCPUTemperatureCelsius(temperature *Temperature) *float64 {
if temperature == nil || !temperature.Available {
return nil
}
var best float64
found := false
consider := func(value float64) {
if !usableCPUTemperature(value) {
return
}
if !found || value > best {
best = value
found = true
}
}
consider(temperature.CPUPackage)
consider(temperature.CPUMax)
for _, core := range temperature.Cores {
consider(core.Temp)
}
if !found {
return nil
}
return &best
}
// HostSensorPrimaryCPUTemperatureCelsius applies PrimaryCPUTemperatureCelsius
// to an agent's flat sensor map: the hottest reading whose key names a CPU
// sensor ("cpu_package", "cpu_core_N"). For the keys the agent reports this
// equals PrimaryCPUTemperatureCelsius of the node temperature built from the
// same map, so a linked node and its agent carry one value.
func HostSensorPrimaryCPUTemperatureCelsius(sensors HostSensorSummary) *float64 {
var best float64
found := false
for key, value := range sensors.TemperatureCelsius {
if !strings.HasPrefix(strings.ToLower(strings.TrimSpace(key)), "cpu") ||
!usableCPUTemperature(value) {
continue
}
if !found || value > best {
best = value
found = true
}
}
if !found {
return nil
}
return &best
}
func usableCPUTemperature(value float64) bool {
return value > 0 && !math.IsNaN(value) && !math.IsInf(value, 0)
}