#!/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