Reduce your PC's power consumption through smart activity monitors

Monitor application usage, system attributes, and user activity to more effectively use the power-management systems of your laptop or desktop computer

Learn how to reduce your power consumption in your Linux® computers by monitoring application-usage patterns and user activity.

Nathan Harrington, Programmer, IBM

Nathan HarringtonNathan Harrington is a programmer working with Linux at IBM. Check out his recently published articles.



04 November 2008

Also available in Russian

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
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

DescriptionNameSize
Sample codeos-smart-monitors-InactivityDetector.0.1.zip4KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=348428
ArticleTitle=Reduce your PC's power consumption through smart activity monitors
publish-date=11042008