SSISO Community

시소당

자바(JAVA)로 시스템 CPU 사용량 측정하기

- 개요
 
 인공지능이 접목된 웹에이전트 같은 프로그램의 경우 사용자의 별도 지시가 없이도 프로그램이 스스로 실행됩니다. 시스템의 사용 패턴이 사용자마다 상이 하기때문에 프로그램이 적절한, 최석 실행 시간을 찾기 위해서는 시스템 모니터링이 필요한데, 그중 가장 많이 쓰이 방법이 CPU 모니터링입니다.
 
 
 
- 자바로 구현
 
 일단 CPU의 사용량을 추출하기 위해서는 운영체제의 시스템 함수의 Call이 요구되는데 불행히도 자바에서는 이 함수를 호출하거나, 혹은 CPU의 사용정보를 직접적으로 얻어올수 있는 API나 기능이 없습니다.
 
 그래서 시스템으로 접근이 가능한, 운영체제에 종속적인 타언어로 작성된 프로그램과의 연동이 필요합니다. 정확히 말해 다른 프로그램을 실행시키고 그 결과값을 리턴받아 자바가 리턴값을 2차 처리하고 사용자에게 보여주는 구조가 될 것입니다.
 
 이러한 원리로 구현을 하기 위한 방법은 크게 두가지 방법이 있습니다. 자바 JNI(Java Native Invocation)을 이용하여 자바 프로그램 상에서 C나 C++로 작성된 프로그램의 함수나 메소드를 직접호출하여 그 결과값을 리턴받아 자바에서 처리해 주는 방식과, pslist.exe와 같은 시스템의 프로세스의 상태를 콘솔모드로 출력하는 프로그램을 실행시켜 결과값에 대해서 스트림을 생성해서 필요한 값을 추출해서 처리하는 방식입니다.
 
 전자의 방식은 프로그램 함수, 메소드간 직접 호출에 의한 방식이므로 후자의 방식에 비해서 자원의 사용이나 부하, 효율, 데이터의 정확도 면에서 더 우수하다고 할 수 있습니다. 하지만 JNI를 이용한 프로그램의 경우 구현이 까다롭고 운영체제에 종속적인 부분이 많고 설정이 까다로워 지기때문에 다양한 버전의 윈도우 플랫폼상에서 실행시키기에는 안정성에 문제가 있습니다.
 반면 후자의 방식은 작업 효율면에서 떨어지는 부분은 있지만 구현이 비교적 쉽고 pslist.exe파일이 어떠한 윈도우 플랫폼 상에서도 잘 동작하므로 실행의 안정성은 전자보다 우수하다고 할 수 있습니다.
 
 그래서 후자의 방식, 즉 윈도우 콘솔 프로그램을 출력시켜서 결과값을 리턴받아 결과값을 자바에서 처리해서 사용자에게 보여주는 프로그램을 작성해 보겠습니다.
 
 일단 아래의 화면은'"pslist.exe'의 출력화면으로 프로세스별 상태정보를 출력합니다.
Pid는 프로세스ID, Pri는 우선순위, ... , CPU Time은 시스템이 실행되고 현재까지 CPU의 총 사용시간, Elapsed Time 최근 실행후 경과시간을 의미합니다.
 
 

 
일단 전체 CPU 사용량을 측정하기 위해서는 pslist를 실행하고 출력되는 각 프로세스의 pid와 cpuTime의 정보를 저장후, 사용자가 지정한  시간간격으로 해당 프로세스의 시간격차를 구합니다.
(일단 cpuTime은 프로그램상에서 TextType의 Format의 값으로 출력되므로 long type의 수치로 변환시켜 저장해야 합니다.)
 
Idle 프로세스의 경우 유휴상태 점유 프로세스이므로 전체 CPU의 사용량을 산출하기 위한 식은 아래와 같습니다.
 
long realUsage = Idle을 제외한 프로세스의 시간차의 합;
long totalUsage = Idle 프로세스의 시간차 + realUsage;
시스템의 CPU 사용량(%) = (int)(realUsage / totalUsage * 100);

 


위의 식을 참조하여 코드를 작성하면 아래와 같습니다.
package system;
/*
 * 제작 :
being20c@naver.com
 * 일자 : 05.6.11
 *
http://blog.naver.com/being20c
 * */
import java.util.*;
import java.io.*;
/** CPU사용량을 측정, 윈도우 계열 운영체제에서만 동작합니다.*/
public class cpuMonitor implements Runnable{
 long cycleTime;
 Process process;
 Vector v_beforCpuData;
 
 /** cycleTime = 측정 주기 : 1. 측정 주기값이 커질수록 값의 정확도는 증가 */
 public cpuMonitor(long cycleTime)
 {
  this.cycleTime = cycleTime;
  this.v_beforCpuData = new Vector();
 }
 
 public cpuMonitor()
 {
  this.v_beforCpuData = new Vector();
 }
 
 /** 해당 파일을 실행 */
 void excuteFile()
 {
  try
  {
   this.process = Runtime.getRuntime().exec("system/pslist.exe");
  }
  catch(Exception e)
  {
   System.err.println("윈도우 응용프로그램 'pslist.exe'를 실행할 수 없습니다..\n"+e);
   System.exit(0);
  }
 }
 
 /** 이전내용과 비교를 통한 */
 public float checkData()
 {
  this.excuteFile();
  float result = -1f;
  Scanner reader;
  int pid = 0;
  long time = 0;
  long idleUsage = 0, realUsage = 0;
  //this.v_Temp.clear();
  
  try
  {
   reader = new Scanner(
     new InputStreamReader(this.process.getInputStream()));
   while(!reader.nextLine().startsWith("Name"));  // sync
   if(this.v_beforCpuData.size() == 0)
   {
    while(reader.hasNext())
    {
     reader.next();
     pid = reader.nextInt();
     reader.next();reader.next();reader.next();reader.next();
     time = time2long(reader.next());
     reader.next();
     this.v_beforCpuData.add(new vList(pid,time));
    }
    return -1f;
   }
   // 이전 정보가 존재한다면
   else
   {
    
    String procName = "";
    vList vTemp = null;
    while(reader.hasNext())
    {
     
     procName = reader.next();
     pid = reader.nextInt();
     reader.next();reader.next();reader.next();reader.next();
     time = time2long(reader.next());
     reader.next();
     
     if(procName.startsWith("Idle"))
     {
      for(int i=0;i<this.v_beforCpuData.size();i++){
       vTemp=(vList)this.v_beforCpuData.get(i);
       if(vTemp.pid == pid) break;
      }
      idleUsage = (time - vTemp.cpuTime);
      vTemp.cpuTime = time;
     }
     //else if(procName.equals("pslist")){}
     else
     {
      // 해당 정보의 존재여부 , 없으면 추가
      boolean isNew = true;
      for(int i=0;i<this.v_beforCpuData.size();i++){
       vTemp=(vList)this.v_beforCpuData.get(i);
       if(vTemp.pid == pid){
        isNew = false;
        break;
       }
      }
      if(!isNew){
       realUsage += (time - vTemp.cpuTime);
       vTemp.cpuTime = time;
      }
      else{
       realUsage += vTemp.cpuTime;
       this.v_beforCpuData.add(new vList(pid,time));
      }
     }
    }
   }
   reader.close();
  }
  catch(Exception e)
  {
   System.out.println("분석과정에서 문제발생..\n" + e);
  }
  
  //System.out.println("r>"+realUsage+", >"+idleUsage);
  result = (float)realUsage/(float)(realUsage+idleUsage);
  
  return result;
 }
 
 /** 결과값의 시간 String을 long타입으로 변환 */
 public static long time2long(String strTime)
 {
  long time = 0;
  byte tCount = 0;
  String token = "";
  char c;
  int tTime;
  for(int i=0;i<strTime.length();i++)
  {
   c = strTime.charAt(i);
   if(c == ':' || c == '.')
   {
    tTime = Integer.parseInt(token);
    if(tCount == 0) time += tTime * 60 * 60 * 1000;
    else if(tCount == 1) time += tTime * 60 * 1000;
    else if(tCount == 2) time += tTime * 1000;
    //else if(tCount == 3) time += tTime;
    tCount++;
    token = "";
   }
   else
    token += c;
  }
  tTime = Integer.parseInt(token);
  time += tTime;
  return time;
 }
 
 public void run()
 {
  for(;;)
  {
   try{Thread.sleep(this.cycleTime);}catch(Exception e){}
   System.out.println("cpu Usage >" + this.checkData());
  }
 }
 
 
 
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  cpuMonitor test = new cpuMonitor(1000);
  //test.checkData();
  new Thread(test).start();
 }
}
class vList
{
 public Long cpuTime;
 public int pid;
 
 public vList(int pid, long cpuTime)
 {
  this.pid = pid;
  this.cpuTime = cpuTime;
 }

 
pslist.exe 프로세스의 리턴값을 효율적으로 처리하기 위해서 자바1.5의 Scanner 클래스를 사용해 보았습니다.(프로그램은 자바 1.5이상 환경에서 동작합니다.)
Runnable 인터페이스를 구현하였으므로 Thread를 실행시켜 실시간으로 사용량의 값을 확인 할 수 있습니다.
 
생성자의 cycleTime 매개변수로 검사주기를 설정 할 수 있으며 cpuMonitor.class가 cpu 사용량을 연산하는 부분도 cpu 사용량에 포함되기 때문에 cycleTime값이 작아지면 cpu 사용률은 전반적으로 증가된 값을 리턴합니다.
 
시간 간격이 클 수록 값이 정확해 집니다.
 
 
 
- 성능 테스트
 
 운영체제 작업관리자의 시스템 사용률과 비교했을때 4%정도 내외의 오차가 존재했으며 시간을 어떻게 잡아주느냐에 따라, 시스템 사용환경에 따라 출력 값의 정확도는 달라 질 수 있습니다만..
전반적으로 유사한 패턴의 그래프가 출력되는 것으로 보아 프로세스의 cpu 사용률의 상대적인 값을 분석하는 모니터링 도구로 쓰기에는 큰 문제는 없을듯 합니다.
 
 
- 접는 말
 
 아래는 본 프로그램의 결과값을 시각적으로 출력하는 프로그램으로 만들어 본 것입니다.
위의 코드만 잘 이해하셨다면 아래와 같이 GUI API를 써서 시각적으로 표현하는 것은 별로 어렵지 않을 거라 생각되어 GUI 관련 코드는 생략합니다.
 
 
 바쁜 가운데 제한된 시간 내에서 작성된 코드라 코드가 좀 지저분 하고 비효율적인 부분도 몇군데 있습니다. 일단 프로그램을 제대로 구현할려면 연산 과정을 최소화 해서 이 프로그램이 사용하는 CPU점유량을 최대로 낮춰야 할 것이고 연산 방법에 변화를 줘서 프로그램이 점유하는 CPU 사용시간을 연산과정에서 제외시킨다면 좀더 순수한(?) 시스템의 CPU 사용량이 출력되지 않을까 생각됩니다. 이상!


 작성 : 김영곤 (being20c@naver.com, http://blog.naver.com/being20c)

6962 view

4.0 stars