|
现代计算机中内置的高级电源配置管理界面(Advanced Configuration and Power Interface,ACPI)和电源配置系统为降低整体功耗提供了各种方法。Linux 及其相关用户空间程序配有大量可以在各种环境下控制 PC 功耗所需的工具。
目前的大多数文档主要介绍如何修改内核参数及 hdparm 设置以减少不必要的磁盘活动。此外,还有丰富的文档可供更改处理器设置时参考,从而最大程度地发挥基于当前电源动态调整频率的优点。
本文提供了通过监视应用程序使用模式构建这些能够节省能源的工具和代码。使用本文提供的技术,根据焦点中的应用程序、用户活动及一般系统性能更改电源设置。
硬件和软件要求
2000 年以后制造的所有 PC 都应当能够提供降低功耗的硬件和软件。您需要一个新 Linux 内核,并且它将有助于获得内置有许多节能工具的 Linux 发行版。不过,关闭屏幕或者触发自动关机就可以提供显著的节能优点。如果您拥有的硬件较旧或者不具有 ACPI 功能,应当仍然会发现本文提供的代码十分有用。
虽然演示所使用的是针对直接输入设计的 PC,但是服务器或远程终端也可以使用相同的概念来根据用户活动降低功耗。
focusTracker.pl 程序
通过监视应用程序使用情况来降低功耗可以采取许多形式。在本文中,第一步是识别与 “费电” 相关的典型使用模式,然后在检测到这些模式时激活节电模式。清单 1 中的 focusTracker.pl 程序包含了启动识别过程的代码。
清单 1. focusTracker.pl 程序头文件
#!/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 _cnnew1_cnnew1@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
|
除了必要的模块包含及变量声明之外,信号中断捕捉程序被定义为允许输出每个数据运行的相关的头文件。这种数据与头文件的分离使您可以更轻松地使用诸如 kst 之类的工具进行可视化处理。清单 2 显示了主处理循环的开头。
清单 2. focusTracker.pl 主循环开始
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
|
每次从 stdin 中读取时,当前的焦点二进制数据(存储于 @win 中)将被重设为 0。每次在以前从未获得过焦点的窗口获得焦点时,系统都会把它的位置及名称记录到 %log 散列中。此步骤是将正确的焦点/非焦点数据与相应的窗口名称关联在一起的关键。清单 3 显示了输入读取的附加部分以及通道(pipe)管理的开头。
清单 3. focusTracker.pl 通道管理
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 ";
|
在读取了 CPU 和磁盘使用情况后,系统将把使用量转换为在 2 到 12 范围内的一系列值。这仅限于显示目的,而且可能必须更改转换值才能支持多 CPU 或光纤通道磁盘设置。本文指定的值适用于 IBM® ThinkPad T42p 中的单一 CPU 和 IDE 磁盘访问。
如果焦点在输入读取之间发生切换,则终止 xev 进程的旧通道并启动新通道。与当前获得焦点的窗口关联的 xev 程序将跟踪所有按键或鼠标移动。清单 4 显示了如何处理这些事件。
清单 4. focusTracker.pl 通道读取
}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
|
仅当输入读取之间的获得焦点的应用程序是同一个应用程序时,才会到达逻辑块。如果有来自获得焦点的应用程序的通道数据(按键或鼠标移动),则为相应的应用程序设置相应的活动二进制状态。要清除通道,只需读完所有 xev 输出行。
在每次完整地将输入传递给 CPU 后,将输出磁盘及二进制焦点数据。清单 5 显示了用于创建与 xev 监视程序之间的非阻塞链接的 createPipe 子例程,以及用于将数据头信息输出到 STDERR 中的 printHeader 子例程。
清单 5. focusTracker.pl 子例程
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 用法
focusTracker.pl 期望每隔一段时间就获得 iostat 程序的输入。清单 6 显示了用于记录应用程序使用信息的示例命令行。
清单 6. focusTracker.pl 示例命令
iostat -m -d -c 1 | \
perl focusTracker.pl 2> activityLog.header | tee activityLog.data
|
按 Ctrl+C 组合键将终止 focusTracker.pl 程序。注意,\ 字符仅用作续行符并且不应当包含在命令行中。-m 命令用于告诉 iostat 显示以每秒 MB 为单位的值(可用时),-d 命令用于显示设备信息(本例中为磁盘吞吐量),-c 用于指定应当显示哪些 CPU 使用信息。最后一个选项(1)用于告诉 iostat 显示每秒新采集的信息。清单 7 显示了这条命令的示例输出,以及用户按下 Ctrl+C 组合键时输出到 activityLog.header 的头文件中的代码行。
清单 7. focusTracker.pl 示例输出
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
|
注意,第二条和第五条如何显示 Eterm 和 Firefox 窗口的焦点信息。切换窗口以及敲击或移动鼠标将导致该窗口的焦点指示器从 0 变为 1。
如果您在场的话,分几个时间段来运行 focusTracker 程序是收集应用程序使用情况和计算机不活动状态的数据的好方法。此外,针对整个计算会话或工作日运行 focusTracker.pl 以生成大量数据供以后分析和显示。
使用情况显示
处理 focusTracker.pl 程序生成的大量数据的最佳工具是 kst。有关 activityLog.data 文件的数据分析示例,请参见图 1。
图 1. kst 显示示例
上图是通过简单地把 CPU 和磁盘使用率指定为线并把每个应用程序的二进制焦点数据指定为点生成的。从 X 轴上大约 196 的位置开始,Firefox 应用程序获得了焦点,并且显示 CPU 和磁盘活动的关联峰值。此活动后面没有进一步的输入事件并且返回到 “正常” 的 CPU 和磁盘使用率。
用户此时将会看到从在各种 Eterm 窗口中编写代码到在 Firefox 中载入本地流量??息页面的转变。然后,用户离开计算机去吃午饭。
典型的电源管理设置只需等待屏幕保护程序根据系统闲置计时器被激活。通过上面的使用模式知识,可以指定一条规则:在 11:30 到 12:30 之间激活 Firefox 应用程序时,并且只有短暂的用户活动,则触发进入低电量模式。
此外,考虑与 Eterm/vim 使用情况关联的低 CPU 及磁盘使用率。激活较低的功耗模式在这里是合乎逻辑的,因为在 vim 中输入文本只需少量的电量。让硬盘停转并且降低 CPU 速度可以在 CPU 及磁盘活动低于设定阈值的任何时间范围内执行。
inactivityRulesFile 设置
根据上面的说明,修改后的规则如下所示:
清单 8. 示例 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
|
注意,如果只希望检查用户活动,则可以将 CPU 和磁盘值指定为空。如果 CPU 和磁盘值已指定,那么它们将被设置为运行命令前必须达到的最低使用量值。timeOut 变量将指定检查 CPU 和磁盘最低值之前需要等待的用户处于不活动状态的秒数。应用程序变量可以在 X Window System 中将任意文本指定为应用程序标题。
monitorUsage.pl 程序
monitorUsage.pl 程序的功能是处理 inactivityRulesFile 和测量系统应用程序使用情况。清单 9 显示了此程序的第一部分。
清单 9. monitorUsage.pl 程序头文件
#!/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);
|
类似于 focusTracker.pl,这段程序包括了库并且定义了变量。inactivityRulesFile 内容将被载入 %rules 散列以供后续处理。清单 10 显示了类似的 iostat 输入处理和焦点检查的更多内容。
清单 10. monitorUsage.pl 数据读取,通道处理
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;
|
无需跟踪多个应用程序,因此 %app 散列只需跟踪目前拥有焦点的应用程序的属性。清单 11 显示了当同一个应用程序在多次输入读取中都拥有焦点时的逻辑分支。
清单 11. monitorUsage.pl 通道读取
}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;
|
规则匹配和命令仅在应用程序一直处于不活动状态时才执行,因此任何键盘或鼠标活动都将重置应用程序不活动计时器。当通道中没有用户活动数据时将调用清单 12。
清单 12. monitorUsage.pl 规则检查
}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
|
从确保规则命令尚未处理的检查开始,将根据上面所示的列表处理每个规则。然后将执行名称匹配检查,其中诸如 “Controlling ACPI Centrino features / enhanced speedstep via software in Linux - Mozilla Firefox” 之类的窗口名称将匹配 “firefox” 的规则应用程序名称。接下来,必须达到全部不活动时间(以秒为单位),以及需要处理的命令的时间窗口。最后,如果满足 CPU 和磁盘条件,则运行该命令并且处理下一条规则。清单 13 显示了完成 monitorUsage.pl 程序所需的常见 createPipe 子例程。
清单 13. monitorUsage.pl createPipe 子例程
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 用法
用 iostat -m -d -c 1 | perl monitorUsage.pl 运行 monitorUsage.pl 程序。出于测试目的,考虑修改 inactivityRulesFile 命令以运行诸如 “beep” 或 “xmessage” 之类的程序,从而在条件满足时提供更有效的反馈。同时,尝试修改一下规则的开始和结束时间,从而更好地适用于测试场景。
结束语
通过提供的工具和代码,您可以通过一系列有关应用程序使用情况的规则来降低功耗。在调整内核、hdparm、ACPI 及 CPU 设置后,添加这些应用程序监视器可以更有效地进入低电量状态。使用 focusTracker 和 kst 显示以查找不活动间隙,从而创建更环保的规则。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 样例代码 | os-smart-monitors-InactivityDetector.0.1.zip | 4KB | HTTP |
|---|
|