Files
md1200-fan-control/fan_speed.sh
Zeb Hering db69c08c73 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>
2026-08-25 10:10:43 -07:00

192 lines
7.9 KiB
Bash
Executable File

#!/bin/bash
source /root/fan_speed/functions.sh
# Trap the signals for container exit and run graceful_exit function
trap 'graceful_exit' SIGINT SIGQUIT SIGTERM
# Variable defintions
# LOW_FAN_SPEED = Lowest fan speed
# HIGH_FAN_SPEED = max fan speed. (0-100)
# IDRAC_HOST = local or IP of iDRAC
# IDRAC_USERNAME = username of iDRAC
# IDRAC_PASSWORD = password if iDRAC
# CHECK_INTERVAL = time to wait in seconds before performing another temp check (if doing GPU might want to keep this low like 5 seconds)
# CPU_TEMPERATURE_THRESHOLD = max CPU temp before disabling this automation and setting idrac back to auto
# GPU_TEMPERATURE_THRESHOLD = max GPU TEMP before running a kill on the container running LLM
# TABLE_HEADER_PRINT_INTERVAL = Number of loops before we output temps
IDRAC_HOST=local
IDRAC_USERNAME=root
IDRAC_PASSWORD=calvin
CHECK_INTERVAL=10 #takes about 8 seconds to query all data
LOW_TEMPERATURE_THRESHOLD=45 #only decimal numbers
CPU_TEMPERATURE_THRESHOLD=90 #only decimal numbers
GPU_TEMPERATURE_THRESHOLD=75 #only decimal numbers
LOW_FAN_SPEED=18 #only decimal numbers
HIGH_FAN_SPEED=50 #only decimal numbers
TABLE_HEADER_PRINT_INTERVAL=2
#######################################
# Check if the iDRAC host is set to 'local' or not then set the IDRAC_LOGIN_STRING accordingly
if [[ $IDRAC_HOST == "local" ]]; then
# Check that the Docker host IPMI device (the iDRAC) has been exposed to the Docker container
if [ ! -e "/dev/ipmi0" ] && [ ! -e "/dev/ipmi/0" ] && [ ! -e "/dev/ipmidev/0" ]; then
echo "/!\ Could not open device at /dev/ipmi0 or /dev/ipmi/0 or /dev/ipmidev/0, check that you added the device to your Docker container or stop using local mode. Exiting." >&2
exit 1
fi
IDRAC_LOGIN_STRING='open'
else
echo "iDRAC/IPMI username: $IDRAC_USERNAME"
echo "iDRAC/IPMI password: $IDRAC_PASSWORD"
IDRAC_LOGIN_STRING="lanplus -H $IDRAC_HOST -U $IDRAC_USERNAME -P $IDRAC_PASSWORD"
fi
#######################################
#######################################
# This script ONLY runs on Dell Servers
get_Dell_server_model
if [[ ! $SERVER_MANUFACTURER == "DELL" ]]; then
echo "/!\ Your server isn't a Dell product. Exiting." >&2
exit 1
fi
#######################################
#######################################
#****Probably don't need this any more****
# Prepare, format and define initial variables
# Check if LOW_FAN_SPEED variable is in hexadecimal format. If not, convert it to hexadecimal
if [[ $LOW_FAN_SPEED == 0x* ]]; then
readonly DECIMAL_LOW_FAN_SPEED=$(printf '%d' $LOW_FAN_SPEED)
readonly HEXADECIMAL_LOW_FAN_SPEED=$LOW_FAN_SPEED
else
readonly DECIMAL_LOW_FAN_SPEED=$LOW_FAN_SPEED
readonly HEXADECIMAL_LOW_FAN_SPEED=$(convert_decimal_value_to_hexadecimal $LOW_FAN_SPEED)
fi
#######################################
#######################################
# This returns the lowest temp between CPU and GPU highest thresholds
# Used to calcuate fan speed interpolation
# I picked the lowest temp as the fan speeds will ramp up faster to account for the lower temp
if [ $CPU_TEMPERATURE_THRESHOLD -le $GPU_TEMPERATURE_THRESHOLD ]; then
HIGH_TEMPERATURE_THRESHOLD=$CPU_TEMPERATURE_THRESHOLD
else
HIGH_TEMPERATURE_THRESHOLD=$GPU_TEMPERATURE_THRESHOLD
fi
#######################################
#######################################
# Determine where to get CPU temps from iDRAC
# If server model is Gen 14 (*40) or newer
if [[ $SERVER_MODEL =~ .*[RT][[:space:]]?[0-9][4-9]0.* ]]; then
DELL_POWEREDGE_GEN_14_OR_NEWER=true
CPU1_TEMPERATURE_INDEX=2
CPU2_TEMPERATURE_INDEX=4
else
DELL_POWEREDGE_GEN_14_OR_NEWER=false
CPU1_TEMPERATURE_INDEX=1
CPU2_TEMPERATURE_INDEX=2
fi
#######################################
#######################################
# Check if sensors are present on the server
IS_EXHAUST_TEMPERATURE_SENSOR_PRESENT=true
IS_CPU2_TEMPERATURE_SENSOR_PRESENT=true
retrieve_cpu_temperatures $IS_EXHAUST_TEMPERATURE_SENSOR_PRESENT $IS_CPU2_TEMPERATURE_SENSOR_PRESENT
if [ -z "$EXHAUST_TEMPERATURE" ]; then
echo "No exhaust temperature sensor detected."
IS_EXHAUST_TEMPERATURE_SENSOR_PRESENT=false
fi
if [ -z "$CPU2_TEMPERATURE" ]; then
echo "No CPU2 temperature sensor detected."
IS_CPU2_TEMPERATURE_SENSOR_PRESENT=false
fi
#######################################
#######################################
# Print out config information before starting
#echo "Server Manufacturer: $SERVER_MANUFACTURER"
#echo "Server Model: $SERVER_MODEL"
#echo "iDRAC Host: $IDRAC_HOST"
#echo "Check Interval: $CHECK_INTERVAL"
#echo "Low Temp Threshold: $LOW_TEMPERATURE_THRESHOLD"
#echo "CPU High Temp threshold: $CPU_TEMPERATURE_THRESHOLD"
#echo "GPU High Temp Threshold: $GPU_TEMPERATURE_THRESHOLD"
#echo "Low Fan Speed: $LOW_FAN_SPEED"
#echo "High Fan Speed: $HIGH_FAN_SPEED"
#######################################
#######################################
# Loop (this is where the magic happens)
while true; do
# Sleep for the specified interval before taking another reading
sleep $CHECK_INTERVAL &
SLEEP_PROCESS_PID=$!
#Get Temps
retrieve_cpu_temperatures $IS_EXHAUST_TEMPERATURE_SENSOR_PRESENT $IS_CPU2_TEMPERATURE_SENSOR_PRESENT
retrieve_gpu_temperature
i=0
# Get highest CPU Temp and check if any of them are over heating
HIGHEST_CPU_TEMPERATURE=$CPU1_TEMPERATURE
if $IS_CPU2_TEMPERATURE_SENSOR_PRESENT; then
if [ $CPU2_TEMPERATURE -gt $CPU1_TEMPERATURE ]; then
HIGHEST_CPU_TEMPERATURE=$CPU2_TEMPERATURE
fi
fi
#echo $HIGHEST_CPU_TEMPERATURE
# Define Function to check for if CPU temps are greater than the threshold
function CPU_OVERHEAT() { [ $HIGHEST_CPU_TEMPERATURE -gt $CPU_TEMPERATURE_THRESHOLD ]; }
# Define function to check if GPU temperature are above the threshold
function GPU_OVERHEAT() { [ $GPU_TEMPERATURE -gt $GPU_TEMPERATURE_THRESHOLD ]; }
# Check if CPU is overheating then apply Dell default dynamic fan control profile if true
# or set CPU fan based on interpolated temps
if CPU_OVERHEAT; then
apply_Dell_fan_control_profile
if ! $IS_DELL_FAN_CONTROL_PROFILE_APPLIED; then
IS_DELL_FAN_CONTROL_PROFILE_APPLIED=true
COMMENT="CPU temperature is too high, Dell default dynamic fan control profile applied for safety"
fi
else
#this combines CPU and GPU temps and outputs which one is highest
HIGHEST_TEMPERATURE=$HIGHEST_CPU_TEMPERATURE
if [ $GPU_TEMPERATURE -gt $HIGHEST_CPU_TEMPERATURE ]; then
HIGHEST_TEMPERATURE=$GPU_TEMPERATURE
fi
if [ "$HIGHEST_TEMPERATURE" -gt "$LOW_TEMPERATURE_THRESHOLD" ]; then
DECIMAL_CURRENT_FAN_SPEED=$(calculate_interpolated_fan_speed $HIGHEST_TEMPERATURE $LOW_TEMPERATURE_THRESHOLD $HIGH_TEMPERATURE_THRESHOLD $LOW_FAN_SPEED $HIGH_FAN_SPEED)
else
DECIMAL_CURRENT_FAN_SPEED=$DECIMAL_LOW_FAN_SPEED
fi
apply_user_fan_control "$DECIMAL_CURRENT_FAN_SPEED"
COMMENT=$(echo "CPU1:$CPU1_TEMPERATURE | CPU2:$CPU2_TEMPERATURE | GPU:$GPU_TEMPERATURE | Inlet:$INLET_TEMPERATURE | Exhaust:$EXHAUST_TEMPERATURE | Fan Speed:$DECIMAL_CURRENT_FAN_SPEED")
fi
if GPU_OVERHEAT; then
COMMENT="GPU temperature is too high, killing CT(s) to prevent GPU damage"
# This will eventually have a function to kill the CT(s) that use the Tesla P4
fi
# Print temperatures, active fan control profile and comment if any change happened during last time interval
if [ $i -eq $TABLE_HEADER_PRINT_INTERVAL ]; then
#echo " ------- Temperatures -------"
#$echo " Date & time Inlet CPU 1 CPU 2 Exhaust Active fan speed profile Third-party PCIe card Dell default cooling response Comment"
i=0
fi
#printf "%19s %3d°C %3d°C %3s°C %5s°C %40s %51s %s\n" "$(date +"%d-%m-%Y %T")" $INLET_TEMPERATURE $CPU1_TEMPERATURE "$CPU2_TEMPERATURE" "$EXHAUST_TEMPERATURE" "$CURRENT_FAN_CONTROL_PROFILE" "$THIRD_PARTY_PCIE_CARD_DELL_DEFAULT_COOLING_RESPONSE_STATUS" "$COMMENT"
z_date=$(date +"%Y-%m-%d %H:%M:%S")
echo "$z_date || $COMMENT" >> /root/fan_speed/log/fan_speed.log
((i++))
wait $SLEEP_PROCESS_PID
done