OpenSolaris Home Server Scripting 2: Setting Up Power Management

Power Management Scripting

Last week, we looked at how essential scripting is for administering home servers (one of the 7 tips for home server bliss) and we wrote us a little script for enabling automatic snapshots.

Another thing that you'll almost certainly want to do on your OpenSolaris home server is enabling power management. This will ensure your server spends as little power as possible when idle, while still being powerful when needed.

There's a whole community group devoted to power management and it's a good idea to check out their FAQ and check in on their discussions.

How To Configure Power Management in OpenSolaris

To enable power management on OpenSolaris, we need to do two things:

  • Enable CPU power management: This will tell the system to switch the CPU into a lower power mode when idle.
  • Enable disk power management: This tells the disks to switch to a lower power mode when there's no activity for a specified amount of time.

Both can be configured by editing the power.conf(4) file and then calling pmconfig(1M) to make the changes go live.

Let's write ourselves a script that does all of the configuration automatically, so we just need to call it and all of the system will be optimized for lower power consumption:

Manipulating the power.conf File

We'll assume full control of the relevant settings in power.conf. Whatever was written there before, after we're done with it, power management will be enabled. Likewise, if we decide to shut off power management, all settings that concern CPU and disk power management will be gone.

This makes our script more simple: We can create a function that rips off any CPU and disk PM settings, then either write back the result (to disable PM) or attach our own settings (to enable it).

(CPU PM is enabled by default on a fresh install of OpenSolaris 2009.06, but it's good to not make that assumption in case someone did change power.conf after all or when switching PM back on after having switched it explicitly off.)

Here's the piece of code that produces a "clean" version of power.conf, without any CPU or disk power management options:

function filter_power {
	# Purge power.conf from any custom settings
	cat $POWERCONF | \
		grep -v cpupm | \
		grep -v cpu-threshold | \
		grep -v device-thresholds
}

It makes sense to put it into its own function, as we'll use it for both setting up and tearing down power management.

The Proper Use Of Temporary Files

We'll use a temporary file to construct our new version of power.conf for two reasons:

  • We can exchange the old version with a new one by using pfexec mv and don't need to run the whole script with full privileges (which would be needed to make any power.conf bits work).
  • We'll learn how to properly use temporary files in shell scripts.

In fact, Chris Gerhard recently wrote a blog post about how to properly set up temporary files so they clean up after themselves automatically. This is such a good practice that I want to emphasize it here by making it part of our power management script.

So here's the piece to clean up any CPU and disk power settings from power.conf and make the settings live:

# Create a temporary file name, make sure it's gone when we are.
trap '${TMPFILE:+rm ${TMPFILE}}' EXIT
US=$(basename $0)
TMPFILE=$(/usr/bin/mktemp /tmp/${US}.XXXXXX)
 
function unconfigure_power {
	# Get a clean version of power.conf
	filter_power >$TMPFILE
 
	# Copy back the temporary file into power.conf
	pfexec mv $TMPFILE /etc/power.conf && unset TMPFILE
 
	# Activate the new power.conf file
	pfexec pmconfig
}

Taking Care Of Ownership And Permissions

But wait! Since we're exchanging the system's power.conf with one of our own, we need to make sure to set the file owners and permissions properly after the exchange. This is a job for another function:

# Adjust owner and permissions for power.conf
function powerconf_perms {
	pfexec chown root:sys power.conf
	pfexec chmod 644 power.conf
}

Of course we'll need to call this in the function above after the pfexec mv bit.

Detecting the Disks in the System

Now comes the tricky part: Each disk in the system needs its own entry with its device path in power.conf. But what's the best way to detect how many disks there are in the system and what their device paths are?

It turns out that format(1M) is our friend, because the first thing it does after starting it is to present us with a list of all disks, and their device paths.

Very well, but there's one caveat: format expects us to use it interactively, but we want to use it in a script. If we called format inside our script, it would wait for the user to navigate its menus by listening on STDIN, which we really don't want!

After some tweaking, I found out that you can send an empty input stream into format through echo, then it will automatically terminate itself after giving us its list of drives. The reason for this is most probably the EOF that STDIN will return to format after the (empty) input has been processed and thus, format will have no option but to terminate.

Here's the line that gives us all of the disks in the system:

disks=$( echo | $PFFORMAT | grep pci )

We use grep to filter out just the lines with the device paths.

Putting it All Together

Now that we have all the pieces, let's put together our power management configuration script. We'll abstract away all pfexec commands and the power.conf file itself into a block of constants at the beginning to make the code slightly cleaner:

#!/bin/ksh93
#
# powersave
#
# Setup power saving parameters
#
 
# Useful constants
POWERCONF="/etc/power.conf"
PFCHOWN="pfexec chown"
PFCHMOD="pfexec chmod"
PFMV="pfexec mv"
PFPMCONFIG="pfexec pmconfig"
PFFORMAT="pfexec format"
 
# Create a temporary file name, make sure it's gone when we are.
trap '${TMPFILE:+rm ${TMPFILE}}' EXIT
US=$(/usr/bin/basename $0)
TMPFILE=$(mktemp /tmp/${US}.XXXXXX)
 
# Get parameters
CMD=$1
 
# Adjust owner and permissions for power.conf
function powerconf_perms {
	$PFCHOWN root:sys $POWERCONF
	$PFCHMOD 644 $POWERCONF
}
 
function filter_power {
	# Purge power.conf from any custom settings
	cat $POWERCONF | \
		grep -v cpupm | \
		grep -v cpu-threshold | \
		grep -v device-thresholds
}
 
function unconfigure_power {
	# Get a clean version of power.conf
	filter_power >$TMPFILE
 
	# Copy back the temporary file into power.conf
	$PFMV $TMPFILE $POWERCONF && unset TMPFILE
 
	# Adjust permissions and owner
	powerconf_perms
 
	# Activate the new power.conf file
	$PFPMCONFIG
}
 
function configure_power {
	# Get a clean config to start with.
	filter_power >$TMPFILE
 
	# Enable CPU power management
	echo "cpupm	enable" >>$TMPFILE
	echo "cpu-threshold	1s" >>$TMPFILE
 
	# Get a list of hard disk devices in the system by asking format
	disks=$( echo | $PFFORMAT | grep pci )
 
	for i in $disks ; do
		echo "device-thresholds	$i	5m" >>$TMPFILE
	done
 
	# Copy the new power.conf back
	$PFMV $TMPFILE $POWERCONF && unset TMPFILE
 
	# Adjust permissions and owner
	powerconf_perms
 
	# Activate the new power.conf file
	$PFPMCONFIG
}
 
# Show a help message.
function show_help {
	echo "Usage: $0 command"
	echo "Supported commands are:"
	echo "  on"
	echo "  off"
}
 
#
# Main program.
#
# We support different subcommands. Switch to the corresponding subroutine,
# then exit.
#
case $CMD in
        'on' )
                configure_power
        ;;
        'off' )
                unconfigure_power
        ;;
        'help' | * )
                show_help
        ;;
esac
 
exit 0

Conclusion

I hear the following question quite often: "How can I set up power management on my server?". Now we have a script that does exactly that. We also learned a bit about the handling of temporary files and found a trick for getting a list of all of the disks that are available in the system.

You can tweak the script to your needs of course. Maybe you want to change the times after which CPU or disk PM kicks in. Or maybe you want to preserve the original power.conf file just in case before meddling with it. Or maybe you don't want to power manage all of the disks in your system but only some. Feel free to enhance the script and contribute your changes in the comments section of this post.

You can easily observe the effects of CPU power management with the powertop(1M) utility.

Observing the effects of disk power management is trickier: The setting is just forwarded to the disk and it's up to the drive to decide what to do after the idle time is up. Some disks will spin down, some merely spin slower and some won't do anything at all. Using a watt-meter and some patience may be the only way to find out what really happens.

Also, it may be difficult to achieve the idle state for your boot drives due to different system activities writing to log files etc. This is why separating data pools from the root pool is a good idea. Some even separate /var into a USB stick or other solid state media because of this.

Your Turn

What's your experience with OpenSolaris power management? Do you have ideas on how to improve this script? Have you measured the effects of disk power management on your server or did you find a way to programmatically verify that it works?
Share your views in the comments section below!

Related Articles

Getting More Out of This Blog

If you found this article useful, then add Constant Thinking to your feed reader and get more Technology Thoughts delivered right to your desktop!

Update: Gregor suggested in the comments to use mktemp with it's path as the GNU and the Solaris version differ slightly. He also suggested to use basename to strip the script name when generating the temporary file. I just added the mods to the above code sample. Thanks!

Update (18/03/2010): My personal scripting role-model Chris pointed out in his latest blog post "Don’t keep opening those files" that writing to a file in a loop over and over again incurs the overhead of opening and closing the destination file multiple times which is not optimal from a performance point of view.

Here's an alternative version of the part that creates the disk entries for power.conf and avoids the loop altogether, opening the destination file just one time:

# Get a list of hard disk devices in the system by asking format
# Then construct the device-thresholds lines out of it.
disks=$( echo | $PFFORMAT | grep pci ) 
echo $disks | \
        sed -e 's/\([^ ]* *\)/device-thresholds \1      5m\n/g' \
        >>$TMPFILE

This should scale ok for arbitrary numbers of disks, say in an X4540 or better.

Thanks, Chris!

Stay in Touch!

Did you like this article? Have you found it useful, interesting or entertaining?

Then click here to get free regular updates and help me reach my goal of 1,000 regular blog readers this summer!

Thank you for reading Constant Thinking.