diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d7f42f --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +all: superfans-gpu-controller superfans-gpu-controller.service +.PHONY: all superfans-gpu-controller-daemon install uninstall clean + +service_dir=/etc/systemd/system +conf_dir=/etc +awk_script='BEGIN {FS="="; OFS="="}{if ($$1=="ExecStart") {$$2=exec_path} if (substr($$1,1,1) != "\#") {print $$0}}' + +superfans-gpu-controller: superfans_gpu_controller.py setup.py + pip3 install . + +superfans-gpu-controller.service: superfans_gpu_controller.py +# awk is needed to replace the absolute path of mydaemon executable in the .service file + awk -v exec_path='$(shell which superfans-gpu-controller) $(conf_dir)/superfans-gpu-controller.json' $(awk_script) superfans-gpu-controller.service.template > superfans-gpu-controller.service + +install: $(service_dir) $(conf_dir) superfans-gpu-controller.service + cp superfans-gpu-controller.json $(conf_dir) + cp superfans-gpu-controller.service $(service_dir) + -systemctl enable superfans-gpu-controller.service + -systemctl enable superfans-gpu-controller + +uninstall: + -systemctl stop superfans-gpu-controller + -rm -r $(service_dir)/superfans-gpu-controller.service + -rm -r $(conf_dir)/superfans-gpu-controller.json + +clean: + -rm superfans-gpu-controller.service diff --git a/install_daemon.sh b/install_daemon.sh deleted file mode 100755 index ae8775b..0000000 --- a/install_daemon.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh - -echo "Created '/etc/systemd/system/superfans-gpu-controller.service' service file" -echo "[Unit] -Description=GPU-based controller of SUPERMICRO server FANs -After=syslog.target nvidia-persistenced.service - -[Service] -Type=simple -User=root -Group=root -WorkingDirectory=`pwd` -Environment=PYTHONUNBUFFERED=true -ExecStart=/usr/bin/python -u `pwd`/superfans_gpu_controller.py -StandardOutput=syslog -StandardError=syslog - -[Install] -WantedBy=multi-user.target" > /etc/systemd/system/superfans-gpu-controller.service -echo "\n" - -echo "Registering superfans-gpu-controller.service" -systemctl enable superfans-gpu-controller.service -systemctl daemon-reload - -echo "Enabled start at system startup" -systemctl enable superfans-gpu-controller - -echo "Starting the service" -systemctl start superfans-gpu-controller diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b815185 --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup +setup(name='superfans-gpu-controller', + version='0.1', + description='Supermicro FAN controller based on NVIDIA GPU temperature', + py_modules=['superfans_gpu_controller','superfans' ], + entry_points={ + 'console_scripts': [ + 'superfans-gpu-controller = superfans_gpu_controller:main', + ], + }, + ) diff --git a/superfans-gpu-controller.json b/superfans-gpu-controller.json new file mode 100644 index 0000000..1e3716c --- /dev/null +++ b/superfans-gpu-controller.json @@ -0,0 +1,8 @@ +{ + "fan_settings" : {"0": 20, + "60": 25, + "70": 30, + "80": 35, + "87": 40, + "90": 43} +} diff --git a/superfans-gpu-controller.service.template b/superfans-gpu-controller.service.template new file mode 100644 index 0000000..4d19c08 --- /dev/null +++ b/superfans-gpu-controller.service.template @@ -0,0 +1,15 @@ +[Unit] +Description=GPU-based controller of SUPERMICRO server FANs +After=syslog.target nvidia-persistenced.service + +[Service] +Type=simple +User=root +Group=root +Environment="PYTHONUNBUFFERED=true" +ExecStart=/placeholder/path/to/superfans-gpu-controller "/etc/superfans-gpu-controller.json" +StandardOutput=syslog +StandardError=syslog + +[Install] +WantedBy=multi-user.target diff --git a/superfans.py b/superfans.py index 946c230..e6c52fd 100644 --- a/superfans.py +++ b/superfans.py @@ -184,10 +184,10 @@ def ipmi_raw_cmd(raw_cmd, hostname = 'localhost', username=None, password=None, try: s = subprocess.check_output(cmd + " 2>&1", shell=True) - except subprocess.CalledProcessError, ex: + except subprocess.CalledProcessError as ex: print("Error: Problem running ipmitool") print("Command: %s" % cmd) - print("Return code: %d" % ex) + print("Return code: %s" % ex) return False out = s.strip() @@ -212,10 +212,10 @@ def ipmi_fan_status(hostname = 'localhost', username=None, password=None, use_en cmd = 'ipmitool -I lanplus -U %s %s -H %s sensor | grep FAN' % (shlex.quote(username), cmd_pass, hostname) try: s = subprocess.check_output(cmd + " 2>&1", shell=True) - except subprocess.CalledProcessError, ex: + except subprocess.CalledProcessError as ex: print("Error: Problem running ipmitool") print("Command: %s" % cmd) - print("Return code: %d" % ex) + print("Return code: %s" % ex) return False fan_status_return = {} diff --git a/superfans_gpu_controller.py b/superfans_gpu_controller.py index 51dcc10..1149de6 100644 --- a/superfans_gpu_controller.py +++ b/superfans_gpu_controller.py @@ -3,7 +3,7 @@ # author: Domen Tabernik # 2019 -import time, superfans, subprocess, signal, sys +import time, superfans, subprocess, signal, sys, json class GracefulKiller: kill_now = False @@ -24,6 +24,7 @@ def retrieve_nvidia_gpu_temperature(): s = subprocess.check_output(cmd + " 2>&1", shell=True) if len(s) <= 0: return False + s = s.decode('ascii') out = [int(x.strip()) for x in s.split("\n") if len(x.strip()) > 0] if out: @@ -45,6 +46,9 @@ def superfans_gpu_controller(fan_settings, FAN_INCREASED_MIN_TIME=120, sleep_sec """ superfan_config = dict(hostname= 'localhost') + # convert fan_settings keys from string to int + fan_settings = { int(k): fan_settings[k] for k in sorted(fan_settings.keys()) } + # save default present before changing anything default_preset = superfans.get_preset(superfan_config) print('Started fan control using GPU temperature.') @@ -113,16 +117,16 @@ def superfans_gpu_controller(fan_settings, FAN_INCREASED_MIN_TIME=120, sleep_sec # Allow for 1% difference in target update_sys1_fan = any([d > fan_target_eps for d in diff_sys1_fan]) update_sys2_fan = any([d > fan_target_eps for d in diff_sys2_fan]) - if update_sys1_fan: - superfans.set_fan(superfan_config, target_fan, superfans.FAN_ZONE_SYS1) + #if update_sys1_fan: + # superfans.set_fan(superfan_config, target_fan, superfans.FAN_ZONE_SYS1) - if update_sys2_fan: - superfans.set_fan(superfan_config, target_fan, superfans.FAN_ZONE_SYS2) + #if update_sys2_fan: + # superfans.set_fan(superfan_config, target_fan, superfans.FAN_ZONE_SYS2) if update_sys1_fan or update_sys2_fan: print('\tCurrent GPU measurements (in C): %s' % ','.join(map(str,GPU_temp))) print('\tMoving average GPU measurements (in C): %s (max=%d)' % (','.join(map(str,mean_GPU_temp)),max_gpu_temp)) - print('\tTarget FAN speed: %d C => FAN %d %% (difference: SYS1 fan = %.2f; SYS2 fan = %.2f)' % (max_gpu_temp, target_fan, max(diff_sys1_fan), max(diff_sys2_fan))) + print('\tTarget FAN speed: %d C => FAN %d %% (difference: SYS1 fan = %.2f; SYS2 fan = %.2f)' % (max_gpu_temp, target_fan, max(diff_sys1_fan), max(diff_sys2_fan))) print('\n\n') previous_target_fan = target_fan @@ -134,13 +138,20 @@ def superfans_gpu_controller(fan_settings, FAN_INCREASED_MIN_TIME=120, sleep_sec superfans.set_preset(superfan_config, default_preset) print('Reverted back to system default.') -if __name__ == "__main__": - # fan settings = {[in deg C]: [% fan], ...} - fan_settings = {0: 20, - 60: 25, - 70: 30, - 80: 35, - 87: 40, - 90: 43} +def main(): + if len(sys.argv) != 2: + print('Invalid number of arguments: missing configuration file!!') + print('') + print(' Usage: %s [PATH_TO_JSON_CONFIG]' % sys.argv[0]) + print('') + print(' Configuration file in JSON format should include "fan_settings" = {[in deg C]: [% fan], ...} ') + print + exit(0) - superfans_gpu_controller(fan_settings) + with open(sys.argv[1]) as cfg_file: + cfg = json.load(cfg_file) + + superfans_gpu_controller(cfg['fan_settings']) + +if __name__ == "__main__": + main()