#!/usr/bin/perl use strict; use warnings; use List::Util qw[min max]; # This script controls SuperMicro server fan speeds via IPMI in response to CPU and SYS temperatures. # A fork of https://github.com/missmah/ipmi_tools by Layla Mah # Sensor treshold reset to allow slower fans `ipmitool sensor thresh FANA lower 0 100 200`; `ipmitool sensor thresh FANB lower 0 100 200`; `ipmitool sensor thresh FAN1 lower 0 100 200`; `ipmitool sensor thresh FAN2 lower 0 100 200`; `ipmitool sensor thresh FAN3 lower 0 100 200`; `ipmitool sensor thresh FAN4 lower 0 100 200`; `ipmitool sensor thresh FAN5 lower 0 100 200`; # cache SDR data for faster access `rm /opt/ipmi-cache.sdr`; `while ipmitool sdr dump /opt/ipmi-cache.sdr; [ $? != 0 ]; do sleep 2; done`; my $min_temp_change = 2; # °C minimum change to actually cause a fan speed update my $seconds_to_sleep = 2; # Number of seconds to sleep between update loops # 0x64 = 100, 0x00 = 0. # CPU Temp -> Fan Speed Mappings my %cpu_temp_to_fan_speed; $cpu_temp_to_fan_speed{80} = 0x64; $cpu_temp_to_fan_speed{75} = 0x62; $cpu_temp_to_fan_speed{70} = 0x56; $cpu_temp_to_fan_speed{65} = 0x52; $cpu_temp_to_fan_speed{60} = 0x48; $cpu_temp_to_fan_speed{55} = 0x46; $cpu_temp_to_fan_speed{50} = 0x42; $cpu_temp_to_fan_speed{45} = 0x40; $cpu_temp_to_fan_speed{40} = 0x36; $cpu_temp_to_fan_speed{35} = 0x34; $cpu_temp_to_fan_speed{30} = 0x32; # SYS Temp -> Fan Speed Mappings my %sys_temp_to_fan_speed; $sys_temp_to_fan_speed{66} = 0x64; $sys_temp_to_fan_speed{60} = 0x60; $sys_temp_to_fan_speed{57} = 0x42; $sys_temp_to_fan_speed{55} = 0x40; $sys_temp_to_fan_speed{52} = 0x36; $sys_temp_to_fan_speed{50} = 0x34; $sys_temp_to_fan_speed{47} = 0x32; $sys_temp_to_fan_speed{45} = 0x30; $sys_temp_to_fan_speed{30} = 0x28; my $g_current_sys_fan_duty_cycle = 0; my $g_current_cpu_fan_duty_cycle = 0; my $g_current_sys_temp = 0; my $g_current_cpu_temp = 0; my $g_last_set_cpu_temp = 0; my $g_last_set_sys_temp = 0; sub UpdateFanSpeed { # Gather statistics for fan speed and CPU Temp my $ipmi_output = `ipmitool sdr list full -S /opt/ipmi-cache.sdr`; my @vals = split( "\n", $ipmi_output ); my $current_cpu_temp = 0; my $current_sys_temp = 0; my $cpu_temp_difference = 0; my $sys_temp_difference = 0; foreach my $value (@vals) { if( $value =~ /^(CPU\sTemp).*\s(\d+)\s.*degrees\sC.*/gi ) { my $cpu_temp = $2; $current_cpu_temp = max( $cpu_temp, $current_cpu_temp ); } if( $value =~ /^(System\sTemp).*\s(\d+)\s.*degrees\sC.*/gi ) { my $sys_temp = $2; $current_sys_temp = max( $sys_temp, $current_sys_temp ); } } # foreach my $value (@vals) $g_current_cpu_temp = $current_cpu_temp; $g_current_sys_temp = $current_sys_temp; my $desired_cpu_fan_speed = 0x0; my $desired_sys_fan_speed = 0x0; my @cpu_temps = keys %cpu_temp_to_fan_speed; for my $cpu_temp (@cpu_temps) { if( $current_cpu_temp >= $cpu_temp ) { # If the current CPU temperature is higher than the temperature enumerated by this hash lookup, # Then set the desired fan speed (if our value is larger than the existing value) $desired_cpu_fan_speed = max( $cpu_temp_to_fan_speed{ $cpu_temp }, $desired_cpu_fan_speed ); # print "The fan speed setting for CPU Temp $cpu_temp *C is $cpu_temp_to_fan_speed{$cpu_temp} % duty cycle\n"; } } my @sys_temps = keys %sys_temp_to_fan_speed; for my $sys_temp (@sys_temps) { if( $current_sys_temp >= $sys_temp ) { # If the current gPU temperature is higher than the temperature enumerated by this hash lookup, # Then set the desired fan speed (if our value is larger than the existing value) $desired_sys_fan_speed = max( $sys_temp_to_fan_speed{ $sys_temp }, $desired_sys_fan_speed ); # print "The fan speed setting for GPU Temp $sys_temp *C is $sys_temp_to_fan_speed{$sys_temp} % duty cycle\n"; } } if( $desired_cpu_fan_speed == 0x0 ) { print "\n***** ERROR: Failed to determine a desired fan speed. Forcing CPU fans to 100% duty cycle as a safety fallback measure. *****\n"; $desired_cpu_fan_speed = 0x64; } if( $desired_sys_fan_speed == 0x0 ) { print "\n***** ERROR: Failed to determine a desired fan speed. Forcing SYS fans to 100% duty cycle as a safety fallback measure. *****\n"; $desired_sys_fan_speed = 0x64; } $cpu_temp_difference = $g_current_cpu_temp - $g_last_set_cpu_temp; $sys_temp_difference = $g_current_sys_temp - $g_last_set_sys_temp; if( (abs $sys_temp_difference) > $min_temp_change ) { print "(SYS) Temp change of $sys_temp_difference °C, current $g_current_sys_temp °C, fan speed set to $desired_sys_fan_speed %\n"; $g_last_set_sys_temp = $g_current_sys_temp; $g_current_sys_fan_duty_cycle = $desired_sys_fan_speed; `ipmitool raw 0x30 0x70 0x66 0x01 0x01 $desired_sys_fan_speed`; } if( (abs $cpu_temp_difference) > $min_temp_change ) { print "(CPU) Temp change of $cpu_temp_difference °C, current $g_current_cpu_temp °C, fan speed set to $desired_cpu_fan_speed %\n"; $g_last_set_cpu_temp = $g_current_cpu_temp; $g_current_cpu_fan_duty_cycle = $desired_cpu_fan_speed; `ipmitool raw 0x30 0x70 0x66 0x01 0x00 $desired_cpu_fan_speed`; } } print "Setting Fan mode to FULL SPEED.\n"; `ipmitool raw 0x30 0x45 0x01 0x01`; while( 1 ) { #print "Calling UpdateFanSpeed()...\n"; UpdateFanSpeed(); #print "Update Complete - going to sleep for $seconds_to_sleep seconds...\n"; sleep $seconds_to_sleep; }