Mac OS X Energy Scripting
We’ve got a variety of Macs running OS X versions 10.4, 10.5 and 10.6. They’re all managed from an Xserve with MCX preferences through Workgroup Manager, but for some reason I’ve just never managed to get them all to settle nicely with their power management settings.
While it’s lovely to have the control of these things through Workgroup Manager, I finally got miffed with it not working, particularly with the Tiger clients. It’s also not good to have rooms of machines burning away power and their components pointlessly.
Terminal
So I did what I usually do. Write a script. There’s an excellent blog called Managing OS X which had already covered this issue, so using GregN’s script as inspiration I came up with this in perl.
1 #!/usr/bin/perl -w 2 3 #powerman.pl v1.01 (23/03/10) 4 # Installed in /usr/local/bin 5 6 # Makes sure basic power management settings are maintained, and 7 # power down machines when we can get away with it. 8 9 use strict; 10 11 my $clires; 12 my $logfile = '/var/log/powerman.log'; 13 my $lockfile = '/tmp/powermanpmsetdone'; 14 15 # Log housekeeping (erase log if >100KB). 16 $clires = `touch $logfile`; 17 my $logsize = -s $logfile; 18 if ($logsize > 102400) { 19 $clires = `echo > $logfile`; 20 } 21 22 # Let us alter the power management settings if we force it. 23 if ("@ARGV" =~ 'force') { 24 $clires = `rm $lockfile 2>/dev/null`; 25 } 26 27 # Figure out where we are. 28 my $hostname = `hostname`; 29 30 # Discover system version. 31 my $osxver = `sw_vers`; 32 if ($osxver =~ "10.6") { 33 $osxver = "6" 34 } elsif ($osxver =~ "10.5") { 35 $osxver = "5" 36 } elsif ($osxver =~ "10.4") { 37 $osxver = "4" 38 } 39 40 41 # Set some basics, unless we've already done so recently. 42 if (! -e $lockfile) { 43 44 my $pmbasics = `pmset -a hibernatemode 0 halfdim 1 womp 1 sleep 0 powerbutton 0 disksleep 30 autorestart 1 displaysleep 9 repeat wakeorpoweron MTWRF 08:45:00`; 45 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: UPDATE: General power management settings have been updated." >> $logfile`; 46 47 if ($osxver >= 5) { 48 my $pmleo = `pmset -a ttyskeepawake 1`; 49 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: UPDATE: Power management settings for Mac OS X 10.5 and later have been updated." >> $logfile`; 50 } 51 52 if ($hostname !~ 'cluster') { 53 my $pmleo = `pmset -a displaysleep 20`; 54 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: UPDATE: Power management settings for studio (non-cluster) machines have been updated." >> $logfile`; 55 } 56 57 } 58 59 60 # Now we get active with some potential shutdowns. 61 my $currenthour = `/bin/date +%H`; chomp $currenthour; 62 my $idletime; 63 my $consoleuser; 64 my $ttysuser; 65 my $sysload; 66 67 # How long have we been idle? 68 my @idle = `/usr/sbin/ioreg -c IOHIDSystem`; 69 foreach (@idle) { 70 if (/Idle/) { 71 my @idlevalue = split /= /, $_; 72 $idletime = int((pop @idlevalue)/1000000000); 73 last; 74 } 75 } 76 77 # Is anybody using the console? 78 my @console = `/usr/bin/who`; 79 foreach (@console) { 80 if (/console/) { 81 $consoleuser = 1; 82 } 83 if (/ttys/) { 84 $ttysuser = 1; 85 } 86 } 87 88 # Are we madly processing away on something? 89 my @uptime = `uptime`; 90 foreach (@uptime) { 91 my @load = split / /, $_; 92 $sysload = int((pop @load)); 93 last; 94 } 95 96 # Things to do if we're outside our usual hours (0800-2100). 97 if (($currenthour < 8) || ($currenthour > 20)) { 98 99 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: snore: powerman.pl starting an out-of-hours check..." >> $logfile`; 100 101 # If there's no console user, nobody on a terminal session, the system has been idle 20 mins and there's not much activity, shut down. 102 if (($idletime > 1200) && ($consoleuser < 1) && ($ttysuser < 1) && ($sysload <= 2)) { 103 104 if ($hostname =~ "cluster") { 105 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: SHUTDOWN: Shutting down the system due to lack of activity out-of-hours." >> $logfile`; 106 my $shutdown = `/sbin/shutdown -h now`; 107 } else { 108 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: If this had been a cluster machine, it would have been shut down due to lack of out-of-hours activity." >> $logfile`; 109 } 110 111 } 112 113 # If there IS a console user, but they've been inactive for an hour and there's barely a hint of processor usage, shut down. 114 if (($idletime > 3600) && ($consoleuser == 1) && ($ttysuser < 1) && ($sysload <= 1)) { 115 116 if ($hostname =~ "cluster") { 117 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: SHUTDOWN: Forcing an out-of-hours shutdown with an inactive console user present." >> $logfile`; 118 my $shutdown = `/sbin/shutdown -h now`; 119 } else { 120 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: If this had been a cluster machine, we would have forced an out-of-hours shutdown with an inactive console user present." >> $logfile`; 121 } 122 123 } 124 125 } else { 126 127 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: heartbeat: powerman.pl just completed an in-hours check." >> $logfile`; 128 129 } 130 131 if (! -e $lockfile) { 132 133 $clires = `echo "\`date +'%Y-%m-%d %H:%M:%S'\`: heartbeat: powerman.pl successfully completed it's first run for this boot." >> $logfile`; 134 $clires = `touch $lockfile`; 135 136 }
powerman.pl [4.10KB] (107)
The idea is that this runs via launchd however often you would like. Using Lingon to create a .plist file, I have it triggered every hour. It uses the hostname of the machine to determine where it is, with different rules applied if the system is a ‘cluster’ or a ‘studio’ machine. Once per boot it reapplies the power management policy, and on every run it examines the state of the machine and decides whether it should be powered down.
Lifted straight from GregN’s script is the idle time discovery; this is perfect for testing whether someone is using a machine ‘out of hours’ and not shutting it down prematurely if they are. :)
One extra feature in this script is the processor usage check. As we occasionally farm out jobs via Xgrid or Logic Node, it’s not good to turn off while processing something. While I wouldn’t say that this bit has been extensively tested, it does seem to work pretty well for us at the moment.