반응형

 응용 프로그램 사용 현황, 시스템 속성, 사용자 활동을 감시하여 노트북과 데스크톱 컴퓨터의 전력 관리 시스템을 좀 더 효율적으로 활용하자

리눅스(Linux®) 컴퓨터에서 응용 프로그램 사용 현황과 사용자 활동을 감시하여 전력 소모를 줄이는 방법을 소개합니다.

현대 컴퓨터에 내장된 ACPI(Advanced Configuration and Power Interface)와 전력 관리 시스템은 전반적인 전력 소모를 줄여주는 다양한 옵션을 제공한다. 또한 리눅스는 다양한 상황에서 PC 전력 소모량을 파악하는 도구를 사용자 영역 프로그램 형태로 다양하게 제공한다.

대다수 문서는 커널 매개변수와 hdparm 설정을 수정하여 불필요한 디스크 활동을 줄이는 방법에 초점을 맞춘다. 또한 전력 공급원에 따라서 주파수를 동적으로 조정하도록 프로세서 설정을 변경하는 방법을 상세하게 알려주는 문서도 있다.

이 기사에서는 응용 프로그램 사용 현항을 감시하여 전력을 한층 더 절약하는 도구와 코드를 소개한다. 여기서 소개하는 기술을 사용하면 현재 사용하는 응용 프로그램, 사용자 활동, 전반적인 시스템 성능에 따라 전력 설정을 변경할 수 있다.

하드웨어/소프트웨어 요구사항

2000년 이후에 제작된 PC는 하드웨어와 소프트웨어 모두 절전 기능을 제공한다. 여기서는 현대적인 리눅스 커널이 필요하다. 절전 도구가 많이 내장된 리눅스 배포판이라면 더 좋겠지만, 단순히 화면이나 시스템을 자동으로 꺼버리는 정도로도 상당한 전력을 절약한다. ACPI 기능이 없는 하드웨어나 옛날 하드웨어라도 여기서 소개하는 코드를 유용하게 활용할 수 있다.

여기서는 사용자가 직접 사용하는 PC를 예제로 삼았지만, 서버나 원격 터미널에도 같은 개념을 적용하여 사용자 활동에 따라 전력 소모를 줄일 수 있겠다.





focusTracker.pl 프로그램

응용 프로그램 사용 현황을 감시해 전력을 절약하는 방법은 다양하다. 이 기사에서는, 가장 먼저 전력을 “낭비”하는 특징적인 사용 패턴을 추적한다. 그런 다음, 시스템이 이런 패턴에 따라 전력을 낭비한다고 판단하면, 절전 모드를 활성화한다. Listing 1은 추적 작업을 수행하는 focustTracker.pl 프로그램 시작 부분이다.


Listing 1. focusTracker.pl 프로그램 헤더
#!/usr/bin/perl -w
# focusTracker.pl - 사용량 시각화를 위한 초점 자료를 수집한다.
use strict;
use X11::GUITest qw( :ALL );    # 응용 초점을 찾는다.
use threads;                    # xev에서 비차단 읽기를 수행한다.
use Thread::Queue;              # xev에서 비차단 읽기를 수행한다.
$SIG{INT} = \&printHeader;      # 종료 시에 stderr로 헤더 파일을 출력한다.

$|=1;                # 버퍼링을 끈 출력
my %log = ();        # 초점 추적 자료 구조
my $lastId = "";     # 마지막으로 초점이 맞춰진 윈도우 식별자
my $pipe = "";       # xev를 거쳐 비차단 사건 읽기
my @win = ();        # maxWindows 응용을 위한 이진 활성 자료
my $cpu = "";        # iostat에서 얻은 CPU 사용량
my $mbread_s = "";   # iostat에서 얻은 디스크 읽기(mb/s)
my $totalWindow = 0; # 전체 사용된 윈도우
my $maxWindows = 50; # 전체 추적된 윈도우

위 코드는 필요한 모듈을 인클루드하고 변수를 정의하는 외에도 시그널 인터럽트 처리기를 정의한다. 시그널 인터럽트 처리기는 자료 헤더 정보를 출력한다. 이렇게 자료와 헤더 파일을 분리하면 kst와 같은 도구로 자료 시각화 작업이 쉬워진다. Listing 2는 주 루프를 시작하는 부분이다.


Listing 2. focusTracker.pl 주 루프 시작부
while(my $line = <STDIN> )
{
  for my $c (0..$maxWindows){ $win[$c] = 0 }  # 모든 자료 위치를 초기화한다.

  next if( $line =~ /Linux/ || length($line) < 10 ); # 헤더 행, 빈 행

  my $windowId   = GetInputFocus();
  my $windowName = GetWindowName( $windowId ) || "NoWindowName";

  if( ! exists($log{$windowId}) )
  {
    # 새로운 윈도우라면 자료 집합에서 다음 위치에 대입한다.
    $log{ $windowId }{ order } = $totalWindow;
    $log{ $windowId }{ name }  = $windowName;
    $totalWindow++ if( $totalWindow < $maxWindows );

  }# 새롭게 추적된 윈도우라면

표준 입력(stdin)에서 입력이 들어올 때마다 현재 초점 정보를 저장하는 이진 변수 @win0으로 재설정한다. 사용자가 아직 초점이 맞춰지지 않은 윈도우로 초점을 맞출 때마다 윈도우 위치와 이름이 %log 해시로 기록된다(한 번 기록된 윈도우는 다시 기록되지 않는다). 현재 초점/비초점 정보로 윈도우 이름을 찾으려면 이 단계가 필수다. Listing 3은 계속하여 입력을 읽은 후 파이프 관리를 시작하는 코드다.


Listing 3. focustTracker.pl 파이프 관리
  if( $line =~ /avg-cpu/ )
  {
    # CPU 사용량을 읽어서 시각화를 위해 2-12 사이 값으로 변환한다.
    ( $cpu ) = split " ", <STDIN>;
    $cpu = sprintf("%2.0f", ($cpu /10) + 2 );

  }elsif( $line =~ /Device/ )
  {
    # 디스크 읽기 사용량을 읽어서 시각화를 위해 2-12 사이 값으로 변환한다.
    ( 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 "" )
      {
        # 옛날 파이프를 닫는다.
        my $cmd = qq{ps -aef | grep $lastId | grep xev | perl -lane '`kill \$F[1]`'};
        system($cmd);
      }
      $lastId = $windowId;
  
      # 새 파이프를 연다.
      my $res = "xev -id $windowId |";
      $pipe = createPipe( $res ) or die "no pipe ";

CPU 사용량과 디스크 사용량을 읽어 2-12 등급 중 하나로 변환한다. 순전히 시각화를 위한 값으로, CPU가 여러 개이거나 광섬유 채널 디스크를 사용한다면 등급 범위를 바꿔도 괜찮다. 여기서 사용한 등급 2-12는 IBM® 씽크패드 T42p, 단일 CPU, IDE 디스크에서 적당하다.

입력을 읽는 동안 초점이 바뀌었다면 기존 xev 파이프를 죽이고 새 파이프를 시작한다. 현재 초점 윈도우에 붙은 xev 프로그램은 키 입력이나 마우스 이동을 추적한다. Listing 4는 키 입력이나 마우스 이동 이벤트를 처리하는 코드다.


Listing 4. focusTracker.pl 파이프 읽기
    }else
    { 
      # 파이프에 자료가 넘어오면 활동하고 있음을 알려준다.
      if( $pipe->pending )
      { 
        for my $c (0..$maxWindows){ $win[$c] = 0 }  # 모든 자료 위치를 초기화한다.
        $win[ $log{$windowId}{order} ] = 1;
        
        # 파이프를 정리한다.
        while( $pipe->pending ){  my $line = $pipe->dequeue or next }
      
      }# 해당 윈도우에 대한 사건을 감지했을 경우
    
    }# 파이프를 설정했을 경우
    
    # CPU 사용량, 디스크 읽기 사용량, 초점 추적 자료
    print "$cpu $mbread_s @win \n";
  
  }# 디바이스가 연결되어 있을 경우

}# 입력받는 동안

위 블록은 직전 루프 반복에서 입력을 읽은 윈도우와 이번 루프 반복에서 입력을 읽은 윈도우가 똑같은 경우에만 실행된다. 초점 윈도우에 파이프 자료가 있다면, 즉 사용자가 초점 윈도우에서 키를 누르거나 마우스를 움직였다면, 초점 윈도우의 활동 이진 상태 변수를 (@win) 설정한다. 파이프를 정리하려면 xev 출력 행을 모두 읽으면 된다.

개별 입력을 처리한 후에는 CPU 사용량, 디스크 사용량, 초점 이진 변수 값을 모두 출력한다. Listing 5는 xev 모니터링 프로그램에 비차단 링크를 생성하는 createPipe 하위 루틴과, 표준 오류(stderr)로 자료 헤더 정보를 출력하는 printHeader 하위 루틴이다.


Listing 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;
  
  # 분리할 경우 프로그램을 마칠 때 스레드를 조용히 끝내도록 만든다.
  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 프로그램에서 주기적으로 입력을 받는다. Listing 6은 응용 프로그램 사용 현황을 기록하는 명령 행 예다.


Listing 6. focusTracker.pl 예제 명령
iostat -m -d -c 1 | \
  perl focusTracker.pl 2> activityLog.header | tee activityLog.data

focusTracker.pl 프로그램을 종료하려면 Ctrl+C를 누른다. 위에서 \ 문자는 줄바꿈을 뜻하므로, 실제 명령에 입력하지 않도록 주의한다. -m 스위치를 지정하면 iostat은 (가능한 경우) 값을 초당 메가바이트 단위로 표시한다. -d 스위치는 디바이스 정보를 (이 경우에는 디스크 처리량을) 출력하고, -c 스위치는 출력할 CPU 사용량 정보를 지정한다. 마지막 옵션 1은 iostat에게 매 초마다 새로 획득한 정보를 표시하라는 뜻이다. Listing 7은 이 명령이 출력하는 결과 일부다. 후반부는 사용자가 Ctrl+C를 눌렀을 때 activityLog.header에 출력된 헤더 파일 내용이다.


Listing 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과 파이어폭스 윈도우 초점 정보를 보여준다. 키보드나 마우스로 윈도우를 전환하면 해당 윈도우의 초점 지시자가 0에서 1로 변한다.

컴퓨터를 사용하는 중에 focusTracker 프로그램을 잠깐 동안 실행하면서 응용 프로그램 사용 현황과 컴퓨터 비활성 상태를 감시해도 좋겠다. 아니면 종일이나 컴퓨터를 사용하는 내내 프로그램을 돌려서 자료를 대량으로 수집한 후 나중에 분석하고 살펴봐도 좋겠다.




위로


사용 현황 시각화

focusTracker.pl 프로그램이 생성하는 대량의 자료를 분석하려면 kst와 같은 도구가 가장 적합하다. 그림 1은 예제로 생성한 activityLog.dat 파일을 분석한 모습이다.


그림 1. kst 시각화 예제
kst 시각화 예제

위 그래프는 CPU 사용량과 디스크 사용량을 선으로 표시하고 이진 초점 정보를 점으로만 표시한다. 점은 응용 프로그램마다 색깔과 모양이 다르다. X축으로 196 즈음에서 Firefox 윈도우로 초점이 옮겨진다. 그리고 동시에 CPU 사용량과 디스크 사용량이 증가한다. 이후로 별다른 입력이 없어서 CPU 사용량과 디스크 사용량은 '보통' 상태로 돌아온다.

실제로 사용자는 여러 Eterm 윈도우를 열어서 코드를 작성하다가 196 즈음에서 파이어폭스로 초점을 옮겨서 근처 교통 정보를 확인한 후 컴퓨터를 켜둔 상태로 점심을 먹으러 나갔다.

전형적인 전력 관리 설정에서는 시스템 비활성 타이머를 이용하여 화면 보호기를 띄운다. 하지만 위와 같이 사용자가 보이는 패턴을 안다면 11:30에서 12:30 사이에 파이어폭스 프로그램이 잠시 사용되는 경우 저전력 모드로 전환할 수 있다.

게다가 Eterm/vim을 사용하는 중에는 CPU 사용량과 디스크 사용량이 아주 낮다. 그러므로 Eterm/vim을 사용하는 도중에도 저전력 모드가 합리적이다. Vim에서 텍스트를 입력할 때는 전력이 많이 필요하지 않다. CPU 사용량과 디스크 사용량이 임계값 이하일 때 일정한 기간 동안 디스크 회전 속력과 CPU 속력을 낮추는 방법도 가능하다.

inactivityRulesFile 설정

Listing 8에 방금 설명한 규칙을 코드화했다.


Listing 8. 예제 inactivityRulesFile
# 형식은 다음과 같다.
# 시작 끝 CPU 디스크 타임아웃 응용 명령
1130_#_1230_#_null_#_null_#_10_#_firefox_#_xscreensaver-command -activate
0610_#_1910_#_6_#_2_#_30_#_vim_#_echo 6 > /proc/acpi/processor/CPU0/performance

사용자 활성 상태만 확인하는 경우에는 CPU 값과 디스크 값을 null로 설정한다. CPU 값과 디스크 값을 설정한 경우에는 각 값을 임계값으로 취급한다. 즉, CPU 사용량과 디스크 사용량이 임계값 이하로 떨어지면 명령을 실행한다. timeOut 변수는 사용자가 비활성인 시점부터 CPU 사용량과 디스크 사용량을 임계값과 비교할 시점까지 기다리는 시간을 가리킨다. 응용 프로그램 변수는 X 윈도우 시스템에서 응용 프로그램 제목에 들어가는 문자열을 가리킨다.




위로


monitorUsage.pl 프로그램

monitorUsage.pl 프로그램은 inactivityRuleFile을 읽어 응용 프로그램 사용 현황을 확인하는 기능을 제공한다. Listing 9는 프로그램 첫 부분이다.


Listing 9. monitorUsage.pl 프로그램 헤더
#!/usr/bin/perl -w
# monitorUsage.pl - 응용 사용 패턴을 추적하고 명령을 수행한다.
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;               # 버퍼링을 끈 출력
my $cpu = "";       # iostat에서 얻은 CPU 사용량
my $mbread_s = "";  # iostat에서 얻은 디스크 읽기(mb/s)
my @rules = ();     # inactivityRulesFile에서 얻은 규칙
my $ruleCount = 0;  # 전체 규칙
my $lastId = "";    # 마지막으로 초점이 맞춰진 윈도우 식별자
my $pipe = "";      # xev를 거쳐 비차단 사건 읽기
my %app = ();       # 현재 초점이 맞춰진 응용 속성
    
open(INFILE," inactivityRulesFile") or die "can't open rules file";
  while( my $line = <INFILE> )
  { 
    next if( $line =~ /^#/ );        # 주석 행을 건너뛴다.
    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 해시에 저장했다가 나중에 처리 과정에서 사용한다. Listing 10은 iostat 입력을 처리하고 초점을 확인하는 코드다.


Listing 10. monitorUsage.pl 자료 읽기, 파이프 처리
while(my $line = <STDIN> )
{
  next if( $line =~ /Linux/ ); # 헤더 행

  next if( length($line) < 10 ); # 공백 행

  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/ )
  {
    # 디스크 읽기 사용량을 읽어서 시각화를 위해 2-12 사이 값으로 변환한다.
    ( 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 "" )
      { 
        # 옛날 파이프를 닫는다.
        my $cmd = qq{ps -aef | grep $lastId | grep xev | perl -lane '`kill \$F[1]`'};
        system($cmd);
      }
      $lastId = $windowId;

      # 새 파이프를 연다.
      my $res = "xev -id $windowId |";
      $pipe = createPipe( $res ) or die "no pipe ";

      # 현재 추적 중인 응용을 다시 설정한다.
      %app = ();
      $app{ id }           = $windowId;
      $app{ name }         = $windowName;
      $app{ cmdRun }       = 0;
      $app{ lastActivity } = 0;

여러 응용 프로그램을 추적할 필요가 없으므로 %app는 단순히 현재 초점이 있는 응용 프로그램의 속성만 추적한다. Listing 11은 같은 응용 프로그램에서 여러 경로로 입력이 계속 들어오는 경우를 처리하는 논리 분기를 보여준다.


Listing 11. monitorUsage.pl 파이프 읽기
    }else
    {
      # 파이프에 자료가 넘어오면 활동하고 있음을 알려준다.
      if( $pipe->pending )
      { 
        # 파이프를 정리한다.
        while( $pipe->pending ){ my $line = $pipe->dequeue or next }

        $app{ cmdRun } = 0;
        $app{ lastActivity } = 0;

응용 프로그램이 일정 기간 동안 비활성 상태인 경우에만 규칙을 확인하고 명령을 실행한다. 그러므로 키보드 입력이나 마우스 움직임은 응용 비활성 타이머를 재설정한다. Listing 12는 파이프에 사용자 활동 자료가 없을 때 실행되는 코드다.


Listing 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} );

        }# 개별 규칙을 처리한다.

      }# 해당 윈도우에 대한 사건을 감지했을 경우

    }# 파이프를 설정했을 경우

  }# 디바이스가 연결되어 있을 경우

}# 입력받는 동안

앞서 살펴본 목록에 따라 개별 규칙을 확인한다. 가장 먼저 규칙에서 지정하는 명령을 이미 실행했는지 확인한다. 다음으로 응용 프로그램 이름을 확인한다. "Controlling ACPI Centrino features / enhanced speedstep via software in Linux - Mozilla Firefox"와 같은 이름은 "firefox"라는 응용 규칙 이름과 일치한다. 다음으로, 비활성 기간(초)이 임계값에 도달했는지 확인한다. 이 때 규칙 파일에 지정된 시간 범위도 확인한다. 마지막으로 CPU 조건과 디스크 조건이 맞으면 명령을 실행한 후 다음 규칙으로 넘어간다. Listing 13은 monitorUsage.pl 프로그램이 사용하는 익숙한 createPipe 하위 루틴이다.


Listing 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;
  
  # 분리할 경우 프로그램을 마칠 때 스레드를 조용히 끝내도록 만든다.
  return $queue;

}#createPipe

monitorUsage.pl 사용법

monitorUsage.pl 프로그램을 실행하려면 명령행에서 iostat -m -d -c 1 | perl monitorUsage.pl을 실행한다. inactivityRulesFile 파일에 "beep"나 "xmessage" 같은 명령을 지정하면 조건이 맞았을 때 좀 더 효과적인 반응이 오므로 테스트하기에 편하다. 테스트 시나리오에서 규칙 시작 시간과 종료 시간을 넓힌 다음에 시도해 봐도 좋겠다.





결론, 추가 예제

지금까지 소개한 도구와 코드를 사용하면 응용 프로그램 사용 현황으로 몇 가지 규칙을 지정하여 전력 소모를 줄일 수 있다. 커널, hdparm, ACPI, CPU 설정을 조율한 후 위 응용 프로그램 감시기를 추가하면 좀 더 효율적으로 저전력 모드로 들어갈 수 있다. focusTracker와 kst를 사용하여 비활성 기간을 찾은 다음 세상을 녹색으로 만들게끔 전력을 절약할 규칙을 세워보자.






다운로드 하십시오

설명 이름 크기 다운로드 방식
예제 코드 os-smart-monitors-InactivityDetector.0.1.zip 4KB HTTP

+ Recent posts