feat: autotune PSU capacity from the same full-load run as fan ceilings

Extract the fan peak-tracking into observedPeakStore (observe max under
load, hold >= minHold to reject spikes, round, persist JSON) and add a
second instance for PSU draw (psu-observation.json, keyed by PSU
ordinal). Fed from samplePSUPower like fans are from sampleFanSpeeds, so
any full-load run refines it — the Fan Ceiling Check (which also samples
PSU power at a slow cadence off its loop and writes psu_<i>_peak_w), a
burn, thermal cycling, and the 5s web metrics collector.

/topo PSU cards now scale the load fill by wattage_w when the BMC
reports it, else by the observed peak draw — marked "~N% load". This
MSI stand's BMC gives only instantaneous input power, so the observed
peak is the only capacity figure available.

Fan behaviour is unchanged (tests exercise updateFanObservation /
estimateFanDutyCyclePctFromObservation / ResolveFanMaxRPM through the
new store).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VHG21rgTUiR1G3qFHTVmN
This commit is contained in:
Mikhail Chusavitin
2026-09-04 12:53:13 +03:00
co-authored by Claude Sonnet 5
parent e713504fc9
commit 7ed652c5b1
7 changed files with 396 additions and 175 deletions
+4
View File
@@ -393,5 +393,9 @@ func samplePSUPower() []PSUReading {
if len(psus) == 0 {
return nil
}
// Feed the observed-capacity store (the "autotune" for PSU load scaling on
// BMCs that report only instantaneous power) — every load run that samples
// PSU power, including the 5 s metrics collector, refines it.
updatePSUObservation(psus, time.Now())
return psus
}