A CPU that had thermally throttled (sysfs thermal_throttle counter > 0) still reported status "OK" everywhere: dmidecode-derived CPU status only distinguishes populated/enabled/disabled and never looked at the throttle flag the collector already recorded next to it, and neither SAT path meant to catch this actually could: - The routine "cpu" SAT pack (RunCPUAcceptancePack) only checked lscpu/sensors/stress-ng exit codes — stress-ng exits 0 whether or not the CPU throttled while running it, so an 89°C/throttled CPU right after a "successful" run still showed cpu:all as OK in component-status.json. - The more thorough platform-stress test already detected throttling and fan-spindown correctly, but wrote its verdict as "Overall: FAIL — ..." with no "=", which parseSATKV can't parse — so even a real detected throttle event never reached the component-status DB. Fixes: - cpu_telemetry.go: escalate a CPU's status to Warning (only-escalate, same severity ranking already used elsewhere) when Throttled is set. - sat.go: add a before/after thermal-throttle-counter check job around the "cpu" pack's stress-ng run, so a throttle event during the run fails that job and (via the existing FAILED->Warning DB mapping) flips cpu:all to Warning. - platform_stress.go: emit a machine-readable overall_status= line alongside the human-readable verdict so platform-stress results actually reach ApplySATResultToDB. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
85 lines
2.5 KiB
Go
85 lines
2.5 KiB
Go
package platform
|
|
|
|
import (
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPlatformStressCPUThreadsOverride(t *testing.T) {
|
|
t.Setenv("BEE_PLATFORM_STRESS_THREADS", "7")
|
|
if got := platformStressCPUThreads(); got != 7 {
|
|
t.Fatalf("platformStressCPUThreads=%d want 7", got)
|
|
}
|
|
}
|
|
|
|
func TestPlatformStressCPUThreadsDefaultLeavesHeadroom(t *testing.T) {
|
|
t.Setenv("BEE_PLATFORM_STRESS_THREADS", "")
|
|
got := platformStressCPUThreads()
|
|
if got < 1 {
|
|
t.Fatalf("platformStressCPUThreads=%d want >= 1", got)
|
|
}
|
|
if got > runtime.NumCPU() {
|
|
t.Fatalf("platformStressCPUThreads=%d want <= NumCPU=%d", got, runtime.NumCPU())
|
|
}
|
|
if runtime.NumCPU() > 2 && got >= runtime.NumCPU() {
|
|
t.Fatalf("platformStressCPUThreads=%d want headroom below NumCPU=%d", got, runtime.NumCPU())
|
|
}
|
|
}
|
|
|
|
func TestPlatformStressMemoryMBOverride(t *testing.T) {
|
|
t.Setenv("BEE_PLATFORM_STRESS_MB", "8192")
|
|
if got := platformStressMemoryMB(); got != 8192 {
|
|
t.Fatalf("platformStressMemoryMB=%d want 8192", got)
|
|
}
|
|
}
|
|
|
|
// TestWritePlatformSummaryOverallStatusKV verifies writePlatformSummary emits
|
|
// a parseable "overall_status=" line matching its human-readable "Overall:"
|
|
// verdict. ApplySATResultToDB/parseSATKV (internal/app/component_status_db.go)
|
|
// only recognizes "key=value" lines — a summary with only "Overall: FAIL —
|
|
// ..." (no "=") silently never reaches component-status.json, so a real
|
|
// thermal-throttle FAIL detected by this test would never show up as cpu:all
|
|
// going Warning.
|
|
func TestWritePlatformSummaryOverallStatusKV(t *testing.T) {
|
|
opts := PlatformStressOptions{Cycles: []PlatformStressCycle{{LoadSec: 60, IdleSec: 30}}}
|
|
|
|
tests := []struct {
|
|
name string
|
|
analyses []cycleAnalysis
|
|
want string
|
|
}{
|
|
{
|
|
name: "clean run reports OK",
|
|
analyses: []cycleAnalysis{{maxCPUTemp: 70, maxGPUTemp: 60}},
|
|
want: "overall_status=OK",
|
|
},
|
|
{
|
|
name: "throttle detected reports FAILED",
|
|
analyses: []cycleAnalysis{{maxCPUTemp: 95, throttled: true}},
|
|
want: "overall_status=FAILED",
|
|
},
|
|
{
|
|
name: "fast fan spindown reports FAILED",
|
|
analyses: []cycleAnalysis{{fanAtCutAvg: 8000, fanMin15s: 2000, fanDropPct: 75}},
|
|
want: "overall_status=FAILED",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
summary := writePlatformSummary(opts, tt.analyses)
|
|
found := false
|
|
for _, line := range strings.Split(summary, "\n") {
|
|
if line == tt.want {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("summary missing %q:\n%s", tt.want, summary)
|
|
}
|
|
})
|
|
}
|
|
}
|