![](//www.ibm.com/i/c.gif) | Level: Intermediate Nathan Harrington, Programmer, IBM
04 Nov 2008 Learn how to reduce your power consumption in your Linux® computers by monitoring
application-usage patterns and user activity.
Advanced Configuration and Power Interface (ACPI) and the power configuration systems built into modern computers provide a wide
range of options for reducing overall power consumption. Linux and its associated user
space programs have many of the tools necessary to master your PC power consumption in a variety of contexts.
Much of the current documentation focuses on modifying your kernel parameters and
hdparm settings to reduce unnecessary disk activity. In addition, extensive
documentation is available for changing your processor settings to maximize the
benefits of dynamic frequency scaling based on your current power source.
This article provides tools and code to build on these power-saving measures by
monitoring your application-usage patterns. Use the techniques presented here to change
your power settings based on the application in focus, user activity, and general system performance.
Hardware and software requirements
Any PC manufactured after 2000 should provide hardware and software capable of reducing
power consumption. You'll need a modern Linux kernel, and it helps to have a Linux
distribution with many of the power-saving tools built in. However, simply blanking a
screen or triggering an automatic shutdown can provide substantial power-consumption
benefits. Older hardware or those without ACPI capability should still find the code presented here useful.
Although demonstrated with a PC designed for direct input, servers or remote terminals
can use the same concepts to reduce their power consumption based on user activity.
focusTracker.pl program
Reducing power consumption by monitoring application usage can take many forms. In
this article, the first step is to identify characteristic usage patterns associated
with "wasted" power, then activate a power-saving mode when those patterns are detected.
The focusTracker.pl program in Listing 1 contains code to begin the identification process.
Listing 1. focusTracker.pl program header
#!/usr/bin/perl -w
# focusTracker.pl - collect focus data for usage visualizations
use strict;
use X11::GUITest qw( :ALL ); # find application focus
use threads; # non blocking reads from xev
use Thread::Queue; # non blocking reads from xev
$SIG{INT} = \&printHeader; # print header file to stderr on exit
$|=1; # non-buffered output
my %log = (); # focus tracking data structure
my $lastId = ""; # last focused window id
my $pipe = ""; # non blocking event reads via xev
my @win = (); # binary activity data for maxWindows applications
my $cpu = ""; # cpu usage from iostat
my $mbread_s = ""; # disk read mb/s from iostat
my $totalWindow = 0; # total used windows
my $maxWindows = 50; # total tracked windows
|
In addition to the necessary module includes and variable declarations, the
signal-interrupt catcher is defined to allow the associated header for each data run to
be printed out. This separation of data and header files makes visualization
processing easier with tools like kst. Listing 2 shows the beginning of the main processing loop.
Listing 2. focusTracker.pl main loop start
while(my $line = <STDIN> )
{
for my $c (0..$maxWindows){ $win[$c] = 0 } #initialize all data positions
next if( $line =~ /Linux/ || length($line) < 10 ); # header line, empty line
my $windowId = GetInputFocus();
my $windowName = GetWindowName( $windowId ) || "NoWindowName";
if( ! exists($log{$windowId}) )
{
# if this is a new window, assign it to the next position in the data set
$log{ $windowId }{ order } = $totalWindow;
$log{ $windowId }{ name } = $windowName;
$totalWindow++ if( $totalWindow < $maxWindows );
}# if a newly tracked window
|
At each read from stdin , the current focus binary data (as
stored in @win ) is reset to 0 .
Every time a window gains focus that has not been focused before, its position and name
are recorded in the %log hash. This step is key to
associating the correct focus/nonfocused data with the appropriate window name.
Listing 3 shows the continuation of input reading and the beginning of the pipe management.
Listing 3. focusTracker.pl pipe management
if( $line =~ /avg-cpu/ )
{
# read the CPU usage, transform to 2-12 value for visualization
( $cpu ) = split " ", <STDIN>;
$cpu = sprintf("%2.0f", ($cpu /10) + 2 );
}elsif( $line =~ /Device/ )
{
# read the disk reads, transform to 2-12 value for visualization
( undef, undef, $mbread_s ) = split " ", <STDIN>;
$mbread_s = $mbread_s * 10;
if( $mbread_s > 10 ){ $mbread_s = 10 }
$mbread_s = sprintf("%2.0f", $mbread_s + 2);
# check focus information
if( $windowId ne $lastId )
{
if( $lastId ne "" )
{
# close old pipe
my $cmd = qq{ps -aef | grep $lastId | grep xev | perl -lane '`kill \$F[1]`'};
system($cmd);
}
$lastId = $windowId;
# open new pipe
my $res = "xev -id $windowId |";
$pipe = createPipe( $res ) or die "no pipe ";
|
After reading the CPU and disk usage, the usage numbers are transformed into a series
of values on a scale of 2-12. This is for visualization purposes only, and the
transformation values may have to be changed to support your multi-CPU or fibre-channel
disk setup. The values specified here work well for a single CPU and IDE disk access
on an IBM® ThinkPad T42p.
If the focus has changed between input reads, the old pipe to the xev process is killed and a new one started. A xev program attached
to the currently focused window will track any key presses or mouse movements. Listing
4 shows how these events are processed.
Listing 4. focusTracker.pl pipe reading
}else
{
# data on the pipe indicates activity
if( $pipe->pending )
{
for my $c (0..$maxWindows){ $win[$c] = 0 } #initialize all data positions
$win[ $log{$windowId}{order} ] = 1;
# clear the pipe
while( $pipe->pending ){ my $line = $pipe->dequeue or next }
}#if events detected for that window
}#if pipe settings
# CPU usage, disk reads, focus tracking data
print "$cpu $mbread_s @win \n";
}#if device line
}#while input
|
The logic block is only reached if the focused application is the same between input
reads. If there is pipe data (a key press or mouse movement) from the focused
application, the appropriate activity binary state is set for the appropriate
application. Clearing the pipe is a simple matter of reading off all the xev output lines.
After each full pass of input the CPU, disk and binary focus data is printed out.
Listing 5 shows the createPipe subroutine used to
create a nonblocking link to the xev monitoring program, as well as the printHeader subroutine, which prints the data header information to
STDERR .
Listing 5. focusTracker.pl subroutines
sub createPipe
{
my $cmd = shift;
my $queue = new Thread::Queue;
async{
my $pid = open my $pipe, $cmd or die $!;
$queue->enqueue( $_ ) while <$pipe>;
$queue->enqueue( undef );
}->detach;
# detach causes the threads to be silently terminated on program exit
return $queue;
}#createPipe
sub printHeader
{
for my $key ( sort { $log{$a}{order} <=> $log{$b}{order} } keys %log )
{
print STDERR "$log{$key}{order} $key $log{$key}{name} \n";
}
}#printResult
|
focusTracker.pl usage
focusTracker.pl expects input from the iostat program at regular intervals.
Listing 6 shows an example command line for recording application usage information.
Listing 6. focusTracker.pl example command
iostat -m -d -c 1 | \
perl focusTracker.pl 2> activityLog.header | tee activityLog.data
|
Press Ctrl+C to terminate the focusTracker.pl program. Note that the \ character is for line continuation only and should not be included
in the command line. The -m switch tells iostat to display
values in megabytes per second (where applicable), the -d
switch displays device information (disk throughput in this case), and -c specifies what CPU usage information should be displayed. The
final option (1 ) tells iostat to display newly acquired
information every second. Listing 7 shows example output from this command, along with
lines from the header file printed to activityLog.header when the user presses
Ctrl+C.
Listing 7. focusTracker.pl example output
5 2 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
6 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
5 2 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
7 2 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
...
0 10485764 NoWindowName
1 33554434 Eterm
2 44040194 Eterm
3 41943042 focusTracker.pl (~/smartInactivityDetector) - VIM
4 27263164 Controlling ACPI Centrino features - Mozilla Firefox
|
Note how the second and fifth entries show focus information for the Eterm and Firefox
windows. Switching between windows and typing or moving the mouse will cause the focus
indicator for that window to switch from 0 to 1.
Running the focusTracker program for short periods of time centered around your
presence is a good way to collect data on application usage and computer inactivity.
Alternatively, run the focusTracker.pl for an entire computing session or workday to
generate a large amount of data for later analysis and visualization.
Usage visualization
Processing the large amount of data generated by the focusTracker.pl program is best
performed with a tool like kst. See Figure 1 for an example of data analysis of an activityLog.data file.
Figure 1. kst visualization example
The graph above is generated by specifying the CPU and disk usage as lines and the
binary focus data per application as dots only. Starting at about position 196 on the
X axis, the Firefox application is focused, and an associated spike in CPU and disk
activity is shown. This activity is followed by no further input events and a return to "normal" CPU and disk usage.
An observer of the user at this point would see a transition from writing code in
various Eterm windows to loading a local traffic information page in Firefox. This is
followed by the user leaving the computer to head out to lunch.
A typical power-management setting would simply wait for the screensaver to activate
based on the system-inactivity timer. With the knowledge of the usage patterns above, a
rule can be specified that when the Firefox application is activated between 11:30 and
12:30, and there is only brief user activity, trigger the entrance into a low power mode.
Additionally, consider the low CPU and disk usage associated with the Eterm/vim
usage. Activating a lower power-consumption mode is logical here, as little processing
power is required when entering text in vim. Spinning down the hard disk and setting a
reduced CPU speed setting can be performed for any time range when the CPU and disk activity is below a set threshold.
inactivityRulesFile settings
Codfying the rules as described above is shown below.
Listing 8. Example inactivityRulesFile
# format is:
# start end CPU disk timeOut application command
1130_#_1230_#_null_#_null_#_10_#_firefox_#_xscreensaver-command -activate
0610_#_1910_#_6_#_2_#_30_#_vim_#_echo 6 > /proc/acpi/processor/CPU0/performance
|
Note that CPU and disk values can be specified as null if checking for user activity
only is desired. If CPU and disk values are specified, they are treated as minimum
usage values that need to be reached before the command is run. The timeOut variable specifies how many seconds of user inactivity to
wait before checking the CPU and disk minimums. The application variable specifies any
text in the applications' title in X Window System.
monitorUsage.pl program
Processing the inactivityRulesFile and measuring system application usage is the
function of the monitorUsage.pl program. Listing 9 shows the first part of this
program.
Listing 9. monitorUsage.pl program header
#!/usr/bin/perl -w
# monitorUsage.pl - track application usage patterns and run commands
use strict;
use X11::GUITest qw( :ALL ); # find application focus
use threads; # non blocking reads from xev
use Thread::Queue; # non blocking reads from xev
$|=1; # non-buffered output
my $cpu = ""; # CPU usage from iostat
my $mbread_s = ""; # disk read mb/s from iostat
my @rules = (); # rules from inactivityRulesFile
my $ruleCount = 0; # total rules
my $lastId = ""; # last focused window id
my $pipe = ""; # non blocking event reads via xev
my %app = (); # current focused app attributes
open(INFILE," inactivityRulesFile") or die "can't open rules file";
while( my $line = <INFILE> )
{
next if( $line =~ /^#/ ); # skip comment lines
my( $start, $stop, $cpu, $disk,
$timeOut, $appName, $cmd ) = split "_#_", $line;
$rules[$ruleCount]{ start } = $start;
$rules[$ruleCount]{ stop } = $stop;
$rules[$ruleCount]{ cpu } = $cpu;
$rules[$ruleCount]{ disk } = $disk;
$rules[$ruleCount]{ timeOut } = $timeOut;
$rules[$ruleCount]{ appName } = $appName;
$rules[$ruleCount]{ cmd } = $cmd;
$ruleCount++;
}#while infile
close(INFILE);
|
Similar to focusTracker.pl, the libraries are included and variables defined. The
inactivityRulesFile contents are loaded into the %rules hash
for later processing. Listing 10 shows more of the similar iostat input processing and
focus checking.
Listing 10. monitorUsage.pl data reads, pipe handling
while(my $line = <STDIN> )
{
next if( $line =~ /Linux/ ); # header line;
next if( length($line) < 10 ); # any blank line
my $windowId = GetInputFocus();
my $windowName = GetWindowName( GetInputFocus() ) || "NoWindowName";
if( $line =~ /avg-cpu/ )
{
( $cpu ) = split " ", <STDIN>;
$cpu = sprintf("%2.0f", ($cpu /10) + 2 );
}elsif( $line =~ /Device/ )
{
#read the disk reads, transform to 2-12 value for visualization
( undef, undef, $mbread_s ) = split " ", <STDIN>;
$mbread_s = $mbread_s * 10;
if( $mbread_s >10 ){ $mbread_s = 10 }
$mbread_s = sprintf("%2.0f", $mbread_s + 2);
if( $windowId ne $lastId )
{
if( $lastId ne "" )
{
# close old pipe
my $cmd = qq{ps -aef | grep $lastId | grep xev | perl -lane '`kill \$F[1]`'};
system($cmd);
}
$lastId = $windowId;
# open new pipe
my $res = "xev -id $windowId |";
$pipe = createPipe( $res ) or die "no pipe ";
# reset currently tracked app
%app = ();
$app{ id } = $windowId;
$app{ name } = $windowName;
$app{ cmdRun } = 0;
$app{ lastActivity } = 0;
|
There is no need to track multiple applications, so the %app
hash simply tracks the attributes of the currently focused application. Listing 11
shows the logic branch when the same application has the focus through multiple input
reads.
Listing 11. monitorUsage.pl pipe reads
}else
{
# data on the pipe indicates activity
if( $pipe->pending )
{
# clear the pipe
while( $pipe->pending ){ my $line = $pipe->dequeue or next }
$app{ cmdRun } = 0;
$app{ lastActivity } = 0;
|
Rules-matching and commands are only performed when the applications have been
inactive, so any keyboard or mouse activity resets the application-inactivity timers.
Listing 12 is called when there is no user activity data on the pipe.
Listing 12. monitorUsage.pl rule-checking
}else
{
$app{ lastActivity }++;
print "no events for window $windowName last "
print "activity seconds $app{lastActivity} ago\n";
my $currTime = `date +%H%M`;
for my $ruleNum ( 0..$ruleCount-1)
{
next unless( $app{cmdRun} == 0 );
next unless( $windowName =~ /$rules[$ruleNum]{appName}/i );
next unless( $app{lastActivity} >= $rules[$ruleNum]{timeOut} );
next unless( $currTime >= $rules[$ruleNum]{start} &&
$currTime <= $rules[$ruleNum]{stop} );
my $conditions = 0;
$conditions++ if( $rules[$ruleNum]{cpu} eq "null" );
$conditions++ if( $rules[$ruleNum]{disk} eq "null" );
$conditions++ if( $rules[$ruleNum]{cpu} ne "null" &&
$rules[$ruleNum]{cpu} <= $cpu );
$conditions++ if( $rules[$ruleNum]{disk} ne "null" &&
$rules[$ruleNum]{disk} <= $mbread_s );
next unless( $conditions > 1 );
print "running $rules[$ruleNum]{cmd}\n";
$app{ cmdRun } = 1;
system( $rules[$ruleNum]{cmd} );
}#for each rule to process
}#if events detected for that window
}#if pipe settings
}#if device line
}#while input
|
Each rule is processed according to the list shown above, beginning with a check to
make sure that rule's command has not already been processed. The name-matching check
is then performed, where a window name like "Controlling ACPI Centrino features /
enhanced speedstep via software in Linux - Mozilla Firefox" will match the rule
application name of "firefox." Next, the total time (in seconds) of inactivity must be
reached, as well as the time-of-day window for the command to be processed. Finally, if
the CPU and disk conditions are met, the command is run and the next rule is processed.
Listing 13 shows the familiar createPipe subroutine needed
to complete the monitorUsage.pl program.
Listing 13. monitorUsage.pl createPipe subroutine
sub createPipe
{
my $cmd = shift;
my $queue = new Thread::Queue;
async{
my $pid = open my $pipe, $cmd or die $!;
$queue->enqueue( $_ ) while <$pipe>;
$queue->enqueue( undef );
}->detach;
# detach causes the threads to be silently terminated on program exit
return $queue;
}#createPipe
|
monitorUsage.pl usage
Run the monitorUsage.pl program with iostat -m -d -c 1 | perl
monitorUsage.pl . For testing purposes, consider modifying the inactivityRulesFile
commands to run programs like "beep," or "xmessage" to provide more effective feedback
when your conditions are met. Also, try modifying the rule start and end times to be more inclusive to your testing scenarios.
Conclusion, further examples
With the tools and code presented, you can reduce your power consumption through a
series of rules regarding application usage. After tuning your kernel, hdparm, ACPI,
and CPU settings, add these application monitors to more effectively enter your
low-power states. Use the focusTracker and kst visualizations to find gaps of
inactivity, and create rules that can save you some green and help make you greener.
Download Description | Name | Size | Download method |
---|
Sample code | os-smart-monitors-InactivityDetector.0.1.zip | 4KB | HTTP |
---|
Resources Learn
Get products and technologies
Discuss
About the author ![](//www.ibm.com/i/c.gif) | ![Nathan Harrington](http://www.ibm.com/developerworks/i/p-nharrington.jpg)
| ![](//www.ibm.com/i/c.gif) | Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies. |
Rate this page
| ![](//www.ibm.com/i/c.gif) |