#!/usr/bin/python #=---------------------------------------------------------------------------------------------=# # # speedstep.py - Version 0.3 - by Bernd Wurst bernd@bwurst.org - GPG-Key: E2F3A43F # # Changelog: # # 0.1 Original Version by Bernd Wurst # 0.2 Modified by Hanno Boeck to support sysfs-interface # 0.3 Bernd Wurst: # - Converted to object-orientated Python # - added named pipe & signal handler for live control # # This small python-script runs as a daemon ond is able to control the speedstep-faeture of your # Pentium-3 mobile, Pentium-4 mobile or Pentium-M (Centrino) Processor. Maybe it'll also work # with mobile Athlon (PowerNow!) but I am not able to test this. # If you do not have one of then, this one will be useless for you. # # This version is based on the new cpufreq-interface found in the sysfs. # You need kernel 2.6 and an appropriate cpufreq-driver for it. # # Surely this is not perfect yet and it is also extendable. If you want to do so, feel free to # add all features you want to. But please give your changes back to me (and the community) so # that others can also use your effort. # # This one is derived from various other speedstep-daemons which were only built to step between # the number of two different states, as mobile P3 and mobile P4 do only support 2 states. # The new Pentium-M supports 5 states of CPU performance and so I had to extend the daemon for # my use. Because I do not really like C and I wanted to do anything in python just for fun and # in hope that I can learn something with it, I did a complete re-write in python. # # Bugs, wishes or any suggestions are welcome, please feel free to write me an e-mail if you # have any comments. # #=---------------------------------------------------------------------------------------------=# import sys,os,os.path,re,time,signal class speedstep: severity = { 'INFO': 0, 'WARN': 1, 'ERR': 2, 'CRIT': 3 } mode = { 'DYNAMIC': 0, 'POWERSAVE': 1, 'PERFORMANCE': 2 } config = { 'debug': True, 'loglevel': severity['INFO'], 'speedmode': mode['DYNAMIC'], 'cpu_interface': "/sys/devices/system/cpu/cpu0/cpufreq", 'fifo': '/var/run/speedstep.py', 'loval': 20, 'hival': 70, } available_freqs = [] active_state = 0 FiFo = None def cleanup(self): if self.FiFo != None: self.FiFo.close() if os.path.exists(self.config['fifo']): os.unlink(self.config['fifo']) sys.exit(0) def log_entry(self, severity, message): # Log an entry to stderr, This is piped to /var/log/speedstep by default if severity >= self.config['loglevel']: print >>sys.stderr, time.strftime("%Y-%m-%d [%H:%M:%S]:"), message if severity >= self.severity['CRIT']: print time.strftime("%Y-%m-%d [%H:%M:%S]:"), message def SignalHandler(self, sig, id): if sig == signal.SIGUSR1: self.log_entry(self.severity['WARN'], 'received signal USR1, entering powersave mode') self.config['speedmode'] = self.mode['POWERSAVE'] elif sig == signal.SIGHUP: self.log_entry(self.severity['WARN'], 'received signal HUP, resuming normal operation') self.config['speedmode'] = self.mode['PERFORMANCE'] elif sig == signal.SIGTERM: self.log_entry(self.severity['WARN'], 'received SIGTERM, shutting down') self.cleanup() def cpu_set_state(self, state): self.log_entry(self.severity['INFO'], 'Switching to %i MHz' % (state/1000)) try: f = open(self.config['cpu_interface'] + "/scaling_setspeed", "w") except: self.log_entry(self.severity['CRIT'], "Cannot access scaling_setspeed") sys.exit(1) f.write(str(state)) f.close() def get_cpu_load(self, old_cuse = [0]): # Let's try if we can calc system load. try: f = open("/proc/stat", "r") tmp = f.readlines(200) f.close() except: self.log_entry(self.severity['CRIT'], "Something went terribly wrong when trying to open /proc/stat") sys.exit(1) # 200 bytes should be enough because the information we need ist typically stored in the first line # Info about individual processors (not yet supported) is in the second (third, ...?) line for line in tmp: if line[0:4] == "cpu ": reg = re.compile('[0-9]+') load_values = reg.findall(line) # extract values from /proc/stat cuse = int(load_values[0]) csys = int(load_values[2]) load = cuse + csys - old_cuse[0] old_cuse[0] = cuse + csys return load def cpu_get_current_step(self): try: f = open(self.config['cpu_interface'] + "/scaling_setspeed", "r") except: self.log_entry(self.severity['CRIT'], "Cannot access scaling_setspeed") sys.exit(1) line = f.readline() f.close() self.active_state = int(line) return self.active_state def cpu_get_steps(self): global const try: f = open(self.config['cpu_interface'] + "/scaling_available_frequencies", "r") except: self.log_entry(self.severity['CRIT'], "Cannot access scaling_available_frequencies") sys.exit(1) tmp = f.readline() f.close() return map(int,tmp.rstrip().split(" ")) def main_loop(self): timer = 0 timer_val = 5 temp = 0 sleepval = 1 freq_count=len(self.available_freqs) self.active_state=self.available_freqs.index(self.cpu_get_current_step()) while True: try: user_input = self.FiFo.read().strip() if user_input == 'powersave': self.config['speedmode'] = self.mode['POWERSAVE'] self.log_entry(self.severity['WARN'], 'Set to POWERSAVE mode via FiFo') elif user_input == 'performance': self.config['speedmode'] = self.mode['PERFORMANCE'] self.log_entry(self.severity['WARN'], 'Set to PERFORMANCE mode via FiFo') elif user_input == 'dynamic': self.config['speedmode'] = self.mode['DYNAMIC'] timer = 0 self.log_entry(self.severity['WARN'], 'Set to DYNAMIC mode via FiFo') else: self.log_entry(self.severity['ERR'], 'Illegal user input: %s' % user_input) except IOError: pass old_state = self.active_state if self.config['speedmode'] == self.mode['POWERSAVE']: self.active_state = 0 sleepval = 10 elif self.config['speedmode'] == self.mode['PERFORMANCE']: self.active_state = len(self.available_freqs)-1 sleepval = 10 else: sleepval = 1 time.sleep(sleepval) load = self.get_cpu_load() # if load > 100: #? # continue #? # We only want to switch down after 5 seconds of continuous low cpu load if load > self.config['hival']: timer = timer_val elif timer > 0: timer -= 1 if load > self.config['hival'] and self.active_state < freq_count-1: self.active_state=self.active_state+1 elif load < self.config['loval'] and self.active_state > 0 and timer == 0: self.active_state=self.active_state-1 if old_state != self.active_state: self.cpu_set_state(self.available_freqs[self.active_state]) def run(self): # because we want to be able to manage as much states as there are, we should look for that information on startup self.available_freqs = self.cpu_get_steps() self.log_entry(self.severity['INFO'], "Your processor supports " + str(len(self.available_freqs)) + " performance states.") # # The following fork-code is stolen by any other python-daemon, I cannot remember which one it was. # try: pid = os.fork() if pid > 0: # exit first parent sys.exit(0) except OSError, e: self.log_entry(self.severity['CRIT'], "fork #1 failed: %d (%s)" % (e.errno, e.strerror)) sys.exit(1) # Let's go outta the way. This is needed if any changes in filesystem are made. os.chdir("/") os.setsid() os.umask(0) try: pid = os.fork() if pid > 0: # exit from second parent sys.exit(0) except OSError, e: self.log_entry(self.severity['CRIT'], "fork #2 failed: %d (%s)" % (e.errno, e.strerror)) sys.exit(1) # redirecting stdout and stderr so that we do not spam the console we are started from si = file("/dev/null", 'r') so = file("/var/log/speedstep", 'a+') se = file("/var/log/speedstep", 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) import grp self.log_entry(self.severity['INFO'], 'Creating FiFo device on %s' % self.config['fifo']) os.mkfifo(self.config['fifo'], 0660) self.FiFo = os.fdopen(os.open(self.config['fifo'], os.O_NONBLOCK | os.O_RDWR)) os.chown(self.config['fifo'], 0, grp.getgrnam("users").gr_gid) signal.signal(signal.SIGUSR1, self.SignalHandler) signal.signal(signal.SIGHUP, self.SignalHandler) signal.signal(signal.SIGTERM, self.SignalHandler) self.log_entry(self.severity['WARN'], "speedstep.py started...") # start the daemon main loop self.main_loop() # The main program... main = speedstep() try: main.run() finally: if main.FiFo != None: main.cleanup()