Hold fan speed unless the change is worth making

The setpoint was a continuous function of instantaneous temperature and
was pushed to the BMC every pass, so ~1% of fan per degree of CPU noise
meant the fans never settled. Measured on iz-pve0: stock iDRAC held one
speed for 5 minutes through 57-59c jitter while this script made 7
changes in 16 minutes off the same signal.

Adds a 5% deadband around the applied speed, with full-speed requests
never held back and a periodic re-assert in case the BMC forgets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Zeb Hering
2026-08-28 20:07:33 -07:00
parent 64e6e03dba
commit 56ae7542a2
3 changed files with 69 additions and 3 deletions

View File

@@ -38,6 +38,34 @@ DISK_ALARM_OFFSET=${DISK_ALARM_OFFSET:-5}
HDD_LIMIT_CAP=${HDD_LIMIT_CAP:-60}
SSD_LIMIT_CAP=${SSD_LIMIT_CAP:-70}
# The fan speed is only pushed to the BMC when it moves by at least this much.
#
# Without it the setpoint is a continuous function of instantaneous temperature:
# the CPU curve is ~1% of fan per degree, idle CPU noise is +/-2c, so the fan
# gets a new speed every single pass and never settles. Measured on iz-pve0,
# stock iDRAC held one speed for 5 minutes straight through the same jitter
# while this script made 7 changes in 16 minutes.
FAN_SPEED_DEADBAND=${FAN_SPEED_DEADBAND:-5}
# Re-push the held speed this often anyway, in case the BMC forgets it. Not
# measured - the MD1200 EMM does forget, iDRAC is believed not to, and one
# ipmitool call every 5 minutes is cheaper than finding out the hard way.
FAN_REASSERT_INTERVAL=${FAN_REASSERT_INTERVAL:-300}
# should_apply_fan_speed <wanted> <currently applied> <epoch it was applied>
# True when the change is worth making. A request for full speed is never held
# back, and the first pass always applies.
should_apply_fan_speed() {
local wanted=$1 applied=$2 applied_at=$3 delta now
[ -n "$applied" ] || return 0
[ "$wanted" -ge "$HIGH_FAN_SPEED" ] && return 0
now=$(date +%s)
[ $((now - applied_at)) -ge "$FAN_REASSERT_INTERVAL" ] && return 0
delta=$((wanted - applied))
[ "$delta" -lt 0 ] && delta=$((-delta))
[ "$delta" -ge "$FAN_SPEED_DEADBAND" ]
}
ALERT_EMAIL=${ALERT_EMAIL:-Servers@ntfy1.izebra.xyz}
ALERT_COOLDOWN=${ALERT_COOLDOWN:-3600}
STATE_DIR=${STATE_DIR:-/root/fan_speed/state}