#!/usr/bin/perl -w # # userfreqd CPU frequency governor # # chkconfig: 2345 10 90 # description: Simple userspace CPU frequency governor. Works only # with the 2.6 kernel's ACPI interface. # use strict; use Sys::Syslog; use POSIX; my $DAEMON_NAME = "userfreqd"; my $PIDFILE = "/var/run/$DAEMON_NAME"; my $UPDATE_PERIOD = 2; my $SYSFSDIR = "/sys/devices/system/cpu/cpu0/cpufreq"; my $ACPOWER_FILE = "/proc/acpi/ac_adapter/ACAD/state"; my $FREQ_IDX = 0; my @FREQS; my @lastJiffies = (); system("modprobe", "powernow_k8"); main(@ARGV); ######################################################################## # daemon/initscript management code ######################################################################## sub main { my $cmd = shift; $cmd = "" if ! defined $cmd; if($cmd eq "start") { if(!findpid()) { start(); } else { print "$DAEMON_NAME is already running.\n"; } } elsif($cmd eq "stop") { stop(); } elsif($cmd eq "restart") { stop(); start(); } elsif($cmd eq "status") { if(findpid()) { print "$DAEMON_NAME is running.\n"; } else { print "$DAEMON_NAME is stopped.\n"; } } else { print "Usage: $DAEMON_NAME {start|stop|restart|status}\n"; exit 1; } } sub stop { print "Stopping $DAEMON_NAME...\n"; my $pid = findpid(); return if !$pid; kill 15, $pid; # SIGTERM sleep 1; if($pid = findpid()) { kill 9, $pid; } } sub start { print "Starting $DAEMON_NAME...\n"; # Daemonize my $pid = fork; if(!defined $pid) { print STDERR "Could not fork!\n"; exit 1; } elsif($pid != 0) { exit 0; } # Parent process POSIX::setsid(); # detach from controlling terminal umask 0; chdir "/"; open PID, ">$PIDFILE"; print PID "$$\n"; close PID; my $err = eval rundaemon(); logmsg("Fatal error: $err"); exit 1; } sub logmsg { openlog $DAEMON_NAME, 0, "kern"; syslog "info", join("", @_); closelog; } sub readfile { my $file = shift; open IN, $file or return undef; my $line = ; close IN; chomp $line; return $line; } sub findpid { my $pid = readfile $PIDFILE; return 0 if !defined $pid; return 0 if ! -d "/proc/$pid"; return 0 if ! -f "/proc/$pid/stat"; return $pid if readfile("/proc/$pid/stat") =~ /$DAEMON_NAME/; return 0; } ######################################################################## # "real" code ######################################################################## # TODO: # Check available governors for "userspace" and set accordingly. sub rundaemon { # Make sure the governor is "userspace" # ... # Run forever while(1) { update(); sleep($UPDATE_PERIOD); } } sub getfreqs { # Get the list of available frequencies my @freqs = (); open FREQS, "$SYSFSDIR/scaling_available_frequencies" or die "Could not get available frequencies from sysfs"; while() { chomp; push @freqs, split ' ', $_; } close FREQS; return sort { $a <=> $b } @freqs; } sub update { my @jiffies = (); open STAT, "/proc/stat"; while() { chomp; if(/^cpu ([0-9 ]+)/) { @jiffies = split ' ', $1; } } die "Unrecognized cpu line in /proc/stat" if @jiffies < 4; if(!@lastJiffies) { # First time. Set to the slowest speed. $FREQ_IDX = 0; @FREQS = getfreqs(); setspeed($FREQS[0]); @lastJiffies = @jiffies; return; } my @deltas; for(my $i=0; $i<@jiffies; $i++) { push @deltas, $jiffies[$i] - $lastJiffies[$i]; if($deltas[$i] < 0) { logmsg("Counter wraparound detected, skipping iteration"); @lastJiffies = @jiffies; return; } } @lastJiffies = @jiffies; my $idle = $deltas[3]; my $total = 0; foreach my $j (@deltas) { $total += $j; } my $usage = ($total - $idle) / $total; @FREQS = getfreqs(); # FIXME: be smarter about relative performance. When stepping # down, pick the frequency target that best fits current usage... my $newidx = $FREQ_IDX; if($usage > 0.9) { $newidx++; } if($usage < 0.5) { $newidx--; } $newidx = $newidx < 0 ? 0 : $newidx; $newidx = $newidx > $#FREQS ? $#FREQS : $newidx; # Clamp to maximum when wall power is available #$newidx = $#FREQS if readfile($ACPOWER_FILE) =~ /on-line/ ? 1 : 0; if($newidx != $FREQ_IDX) { $FREQ_IDX = $newidx; setspeed($FREQS[$newidx]); } } sub setspeed { my $khz = shift; my $mhz = $khz/1000; logmsg("Setting CPU speed to $mhz MHz"); open SET, ">$SYSFSDIR/scaling_setspeed" or die "Coult not set CPU speed"; print SET $khz; close SET; }