Add temperature-aware MD1200 fan control
das_fanctl.sh reads drive temps via smartctl (falling back to the EMM backplane sensors), interpolates a fan percentage, and re-sends _shutup on a 5s keepalive so the shelf does not revert to full speed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
127
das_fanctl.sh
Executable file
127
das_fanctl.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
|
||||
#Zeb H.
|
||||
#Dell MD1200 DAS fan control - read temps, set fan speed to match.
|
||||
#Drives the curve off drive temperature, falls back to the enclosure backplane sensors.
|
||||
#Usage: das_fanctl.sh [loop|once|temps|disks|selftest] (default: loop)
|
||||
|
||||
source /root/fan_speed/functions.sh
|
||||
|
||||
PORT=/dev/ttyS1
|
||||
LOG=/root/fan_speed/log/das_fan.log
|
||||
CHECK_INTERVAL=30 # seconds between temperature readings
|
||||
KEEPALIVE_INTERVAL=5 # re-send _shutup this often, the DAS forgets it otherwise
|
||||
LOW_FAN_SPEED=10
|
||||
HIGH_FAN_SPEED=60
|
||||
|
||||
# Disks in the enclosure - anything behind the DAS SAS expander. The server's own
|
||||
# drives hang off pci-...-scsi-* instead, so this glob does not pick them up.
|
||||
DISK_GLOB='/dev/disk/by-path/*-sas-exp*-lun-0'
|
||||
DISK_LOW_TEMPERATURE_THRESHOLD=40 # at/below this -> LOW_FAN_SPEED (idle spread is 32-39c)
|
||||
DISK_HIGH_TEMPERATURE_THRESHOLD=50 # at/above this -> HIGH_FAN_SPEED
|
||||
|
||||
# Backplane air sensors, only used when no disk will answer (all spun down, smartctl gone)
|
||||
BP_LOW_TEMPERATURE_THRESHOLD=30
|
||||
BP_HIGH_TEMPERATURE_THRESHOLD=45
|
||||
|
||||
SPEED=$LOW_FAN_SPEED
|
||||
|
||||
# Only one talker on the serial line (detach screen/attachDAS.sh first)
|
||||
exec 9>/run/das_fanctl.lock
|
||||
flock -n 9 || { echo "another das_fanctl is running (or /dev/ttyS1 is busy)" >&2; exit 1; }
|
||||
|
||||
stty -F $PORT 38400 raw -echo -echoe -echok -echoctl -echoke
|
||||
|
||||
# Fire and forget - the DAS answers with a bare prompt, nothing worth reading
|
||||
set_fan() { printf '_shutup %s\r' "$1" > $PORT; }
|
||||
|
||||
# Send a command, return whatever the DAS says back
|
||||
das_cmd() {
|
||||
local out; out=$(mktemp)
|
||||
timeout 3 cat $PORT > "$out" &
|
||||
local pid=$!
|
||||
sleep 0.2
|
||||
printf '%s\r' "$1" > $PORT
|
||||
wait $pid
|
||||
cat "$out"; rm -f "$out"
|
||||
}
|
||||
|
||||
# Hottest drive-backplane sensor from a _temp_rd readout on stdin.
|
||||
# ponytail: BP_* only - EXP0 (SAS expander) idles ~59c and would peg the fans,
|
||||
# SIM0 is the enclosure module. Add them here if drives ever run cool but the DAS cooks.
|
||||
hottest_bp() {
|
||||
awk -F'[=c]' '/BP_[0-9]+\[/ {gsub(/ /,"",$2); if ($2+0 > m) m = $2+0} END {print m+0}'
|
||||
}
|
||||
|
||||
# Hottest drive in the enclosure, 0 if nothing answered.
|
||||
# -n standby so a sleeping drive is skipped rather than spun up just to be measured.
|
||||
# SATA reports attribute 194, SAS reports "Current Drive Temperature".
|
||||
hottest_disk() {
|
||||
local link dev temp max=0
|
||||
for link in $DISK_GLOB; do
|
||||
[ -e "$link" ] || continue
|
||||
dev=$(readlink -f "$link")
|
||||
temp=$(smartctl -n standby -A "$dev" 2>/dev/null |
|
||||
awk '/Temperature_Celsius/ {print $10; exit} /Current Drive Temperature/ {print $4; exit}')
|
||||
[ -n "$temp" ] && [ "$temp" -gt "$max" ] 2>/dev/null && max=$temp
|
||||
done
|
||||
echo $max
|
||||
}
|
||||
|
||||
# Read temps, recalculate SPEED, apply it
|
||||
one_pass() {
|
||||
local disk bp source temp low high
|
||||
disk=$(hottest_disk)
|
||||
if [ "$disk" -gt 0 ]; then
|
||||
source=disk; temp=$disk
|
||||
low=$DISK_LOW_TEMPERATURE_THRESHOLD; high=$DISK_HIGH_TEMPERATURE_THRESHOLD
|
||||
else
|
||||
bp=$(das_cmd _temp_rd | hottest_bp)
|
||||
if [ "$bp" -le 0 ] 2>/dev/null; then
|
||||
echo "$(date +'%Y-%m-%d %H:%M:%S') || no disk or backplane reading, holding fan at ${SPEED}%" >> $LOG
|
||||
return 1
|
||||
fi
|
||||
source=backplane; temp=$bp
|
||||
low=$BP_LOW_TEMPERATURE_THRESHOLD; high=$BP_HIGH_TEMPERATURE_THRESHOLD
|
||||
fi
|
||||
|
||||
SPEED=$(calculate_interpolated_fan_speed "$temp" $low $high $LOW_FAN_SPEED $HIGH_FAN_SPEED)
|
||||
set_fan "$SPEED"
|
||||
echo "$(date +'%Y-%m-%d %H:%M:%S') || DAS ${source}:${temp}c | Fan Speed:$SPEED" >> $LOG
|
||||
echo "DAS ${source}:${temp}c -> fan ${SPEED}%"
|
||||
}
|
||||
|
||||
selftest() {
|
||||
local canned=' BP_1[2] = 30c
|
||||
BP_2[3] = 44c
|
||||
SIM0[0] = 36c
|
||||
EXP0[4] = 59c
|
||||
AVG = 39c'
|
||||
[ "$(printf '%s' "$canned" | hottest_bp)" = 44 ] || { echo "FAIL: bp parser"; exit 1; }
|
||||
[ "$(printf '%s' 'garbage' | hottest_bp)" = 0 ] || { echo "FAIL: bp parser on junk"; exit 1; }
|
||||
[ "$(calculate_interpolated_fan_speed 40 40 50 10 60)" = 10 ] || { echo "FAIL: low clamp"; exit 1; }
|
||||
[ "$(calculate_interpolated_fan_speed 55 40 50 10 60)" = 60 ] || { echo "FAIL: high clamp"; exit 1; }
|
||||
[ "$(calculate_interpolated_fan_speed 45 40 50 10 60)" = 35 ] || { echo "FAIL: midpoint"; exit 1; }
|
||||
[ "$(hottest_disk)" -gt 0 ] || { echo "FAIL: no disk temperatures readable"; exit 1; }
|
||||
echo "selftest OK"
|
||||
}
|
||||
|
||||
case "${1:-loop}" in
|
||||
temps) das_cmd _temp_rd ;;
|
||||
disks) for l in $DISK_GLOB; do
|
||||
d=$(readlink -f "$l")
|
||||
printf '%-12s %s\n' "$d" "$(smartctl -n standby -A "$d" 2>/dev/null |
|
||||
awk '/Temperature_Celsius/ {print $10"c"; exit} /Current Drive Temperature/ {print $4"c"; exit}')"
|
||||
done; echo "hottest: $(hottest_disk)c" ;;
|
||||
once) one_pass ;;
|
||||
selftest) selftest ;;
|
||||
loop) while true; do
|
||||
one_pass
|
||||
# keepalive between readings - matches the old 5s timer cadence
|
||||
for _ in $(seq $((CHECK_INTERVAL / KEEPALIVE_INTERVAL))); do
|
||||
sleep $KEEPALIVE_INTERVAL
|
||||
set_fan "$SPEED"
|
||||
done
|
||||
done ;;
|
||||
*) echo "usage: $0 [loop|once|temps|disks|selftest]" >&2; exit 1 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user