API

CPU 코어별, 토탈 사용률과 개별 프로세스별 사용률 구해보기

디버그정 2012. 10. 26. 11:29

PDH(performancer data helper)를 사용해서 구한다.

주의할 게 있는데,  프로세스 이름이 같은 경우 가령 svchost라든지 iexplorer의 경우는 #인덱스를 붙여줌으로써 구분할 수 있다.(인덱스는 0부터 시작)

그리고 PdhCollectQueryData 함수로 값을 수집하는 경우 첫 실행으로는 값이 항상 99.9999이므로

Sleep(타임)을 줘서 최소 2번 실행해야 제대로 값을 구할 수 있다.(이전 샘플과 현재 샘플을 비교해서 값을 산출한다고 한다.)

 

참고로 주기적으로 갱신된 프로세스 목록을 구하려면

PdhEnumObjects(NULL, szMachine, NULL, &cchBuffer, PERF_DETAIL_WIZARD, TRUE); // 이 과정이 시스템 부하가 좀 걸리는 듯,,,

코드를 실행해야 업데이트된 목록을 구할 수 있는데 이 과정이 부하가 커서 다른 방법도 고려해봄직하다.

psapi의 CreateToolhelp32Snapshot, ProcessFirst, ProcessNext를 사용할 수 있을 것이다.

 

 

출력 결과) 듀얼 코어인 경우

★ CPU Total: 94.976077
☆ CPU Core 0: 96.650718
☆ CPU Core 1: 93.301435

-----------------------------------------
System: 7.142857
smss: 0.000000
csrss: 0.000000
winlogon: 0.000000
services: 0.000000
lsass: 0.000000
svchost: 0.000000
svchost: 0.000000
svchost: 0.000000
svchost: 0.000000
svchost: 0.000000
spoolsv: 0.000000
RTHDCPL: 0.000000
ctfmon: 0.000000
daemon: 0.000000
svchost: 0.000000
alg: 0.000000
conime: 0.000000
Explorer: 0.000000

HeavyLoad: 0.000000
HeavyLoad: 14.285714
HeavyLoad: 0.000000
HeavyLoad: 42.857143
HeavyLoad: 7.142857
wmiprvse: 0.000000
MSDEV: 0.000000
NOTEPAD: 0.000000
iexplore: 0.000000
HeavyLoad: 7.142857
HeavyLoad: 21.428571
Test: 0.000000

 

소스: CoreAndProcessUsageByPDH.txt

 

// 추가해야 할 라이브러리와 헤더 파일
#pragma comment(lib, "pdh.lib")
#include <pdh.h>
#include <pdhmsg.h>

 


DWORD WINAPI CoreAndProcessUsageByPDH(LPVOID lp)
{
 LPCTSTR szMachine = NULL;
 LPCTSTR szObject = _T("Process");

 PDH_STATUS status;
 DWORD cchBuffer = 0;
 DWORD cchCounterList = 0;
 DWORD cchInstanceList = 0;
 LPTSTR mszCounterList = NULL; // msz는 멀티스트링을 의미한다. 개별 문자열의 구분은 NULL로 하고 맨 끝에는 NULL이 연속 2개가 온다.
 LPTSTR mszInstanceList = NULL;
 LPTSTR szInstance = NULL;
 LPTSTR szCounter = NULL;

 DWORD cInstance = 0;
 
 HQUERY hQuery = NULL;
 HCOUNTER *phCounter_Instance = NULL;
 TCHAR szFullCounterPath[1024] = {0};
 PDH_FMT_COUNTERVALUE CounterValue;
 DWORD i, j, k;
 
 int *pIndexOfInstance = NULL;  // 인스턴스 이름이 같은 경우 인덱스 작업을 위해
 LPTSTR pszCur = NULL;
 int index;

 HCOUNTER *phCounter_Core = NULL; // CPU 코어 사용률을 구하기 위해
 SYSTEM_INFO si;

 TCHAR szErrorCap[256]; // 에러 메시지박스 캡션
 LPTSTR lpszErrorBuf; // 에러 메시지박스 내용(FormatMessage 사용시)
 TCHAR szErrorText[256]; // 에러 메시지박스 내용


 // 코어의 갯수를 구한다.
 GetSystemInfo(&si); // si.dwNumberOfProcessors
 
 // 쿼리를 열어준다.
 status = PdhOpenQuery(NULL, 0, &hQuery);
 if (ERROR_SUCCESS != status) {
  _tcscpy(szErrorCap, _T("PdhOpenQuery - CoreAndProcessUsageByPDH"));
  goto error_pdh;
 }

 // CPU 코어별로 카운터 핸들을 저장할 메모리를 할당한다. 토탈 부분도 고려 +1한다.
 phCounter_Core = (HCOUNTER *)malloc(sizeof(HCOUNTER) * (si.dwNumberOfProcessors + 1));
 if (!phCounter_Core) {
  MessageBoxW(NULL, L"phCounter_Core 메모리 할당(malloc)에 실패하였습니다.", L"CoreAndProcessUsageByPDH", MB_ICONERROR | MB_TOPMOST);
  goto finish;
 }
 memset(phCounter_Core, 0, sizeof(HCOUNTER) * (si.dwNumberOfProcessors + 1));

 // CPU 코어별 Processor Time 카운터를 추가하고 핸들을 구한다. Processor 문자열 주의한다.(Process가 아니다.)
 for (i = 0; i < si.dwNumberOfProcessors; i++) {
  wsprintf(szFullCounterPath, _T("\\Processor(%d)\\%% Processor Time"), i);
  status = PdhAddCounter(hQuery, szFullCounterPath, 0, phCounter_Core + i);
  if (ERROR_SUCCESS != status) {
   _tcscpy(szErrorCap, _T("PdhAddCounter - CoreAndProcessUsageByPDH"));
   goto error_pdh;
  }
 }

 // CPU 토탈 Processor Time 카운터를 추가하고 핸들을 구한다.
 lstrcpy(szFullCounterPath, _T("\\Processor(_Total)\\% Processor Time"));
 status = PdhAddCounter(hQuery, szFullCounterPath, 0, phCounter_Core + si.dwNumberOfProcessors);
 if (ERROR_SUCCESS != status) {
  _tcscpy(szErrorCap, _T("PdhAddCounter - CoreAndProcessUsageByPDH"));
  goto error_pdh;
 }

 /////// 반복해서 체크
 for (k = 0; k < 3; k++) {

  /////////// 개별 프로세스를 체크한다.  
  ///// 특정 오브젝트의 아이템들을 열거한다.
  // 일단 필요한 버퍼 사이즈를 구한다.
  mszCounterList = NULL; // m은 멀티를 의미
  mszInstanceList = NULL;

  // 버퍼 사이즈 인수를 0으로 전달하면 아래 함수 실행시 필요한 버퍼 사이즈를 해당 인수에 올바르게 입력해준다.
  cchCounterList = 0;
  cchInstanceList = 0;
  status = PdhEnumObjectItems(NULL, szMachine, szObject,
         NULL, &cchCounterList,
         NULL, &cchInstanceList, PERF_DETAIL_WIZARD, 0);
  
  // 버퍼 사이즈를 구하는 경우는 에러 검사를 하지 않는다. 결과값은 항상 ERROR_SUCCESS가 아니다.
  //if (status != ERROR_SUCCESS) {
  // goto error_pdh;
  //}

  // 산출한 버퍼 길이가 2인 경우(NULL 연속 2개)는 객체가 존재하지 않는다는 의미다. 다음 과정을 진행할 필요 없다.
  if (cchInstanceList == 2) {
   goto finish;
  }

  // 메모리를 할당한다. 위에서 구한 버퍼 사이즈 값은 문자열의 길이(NULL 포함)이므로 할당사이즈에 주의한다.
  mszCounterList = (LPTSTR)malloc(cchCounterList * sizeof(TCHAR));
  if (!mszCounterList) {
   MessageBoxW(NULL, L"mszCounterList 메모리 할당(malloc)에 실패하였습니다.", L"CoreAndProcessUsageByPDH", MB_ICONERROR | MB_TOPMOST);
   goto finish;
  }

  mszInstanceList = (LPTSTR)malloc(cchInstanceList * sizeof(TCHAR));
  if (!mszInstanceList) {
   MessageBoxW(NULL, L"mszInstanceList 메모리 할당(malloc)에 실패하였습니다.", L"CoreAndProcessUsageByPDH", MB_ICONERROR | MB_TOPMOST);
   goto finish;
  }

  // 할당된 버퍼와 사이즈를 넣고 열거함수를 다시 실행한다.
  status = PdhEnumObjectItems(NULL, szMachine, szObject,
         mszCounterList, &cchCounterList,
         mszInstanceList, &cchInstanceList, PERF_DETAIL_WIZARD, 0);
  
  if (status != ERROR_SUCCESS) {
   _tcscpy(szErrorCap, _T("PdhEnumObjectItems - CoreAndProcessUsageByPDH"));
   goto error_pdh;
  }

  // 인스턴스의 총 갯수를 구한다.
  cInstance = 0;
  szInstance = mszInstanceList;
  while (*szInstance) {
   cInstance++;
   szInstance += _tcslen(szInstance) + 1;
  }

  ///// ★ 인스턴스 이름이 같은 경우 인덱싱 작업을 해줘야 된다. 인덱스는 0부터 시작한다. ex) iexplorer.exe가 2개 이상 존재하는 경우 등
  // 각 인스턴스들의 인덱스를 저장할 메모리 할당
  pIndexOfInstance = (int *)malloc(sizeof(int) * cInstance);
  if (!pIndexOfInstance) {
   MessageBoxW(NULL, L"pIndexOfInstance 메모리 할당(malloc)에 실패하였습니다.", L"CoreAndProcessUsageByPDH", MB_ICONERROR | MB_TOPMOST);
   goto finish;
  }

  // 각 인스턴스별로  검사
  for (i = 0; i < cInstance; i++) {
   
   // i번째 인스턴스 이름을 구한다.
   pszCur = mszInstanceList;
   for (j = 0; j < i; j++) {
    pszCur += _tcslen(pszCur) + 1;
   }

   // 첫 부분부터 같은 이름이 있는지 조사한다. 해당 인스턴스 부분에 도달하기 전까지만 검사하면 된다.
   // 같은 문자열이 발견되면 인덱스를 1 증가시키면 된다.
   index = 0;
   szInstance = mszInstanceList;
   for (j = 0; j < i; j++) {
    if (0 == _tcsicmp(szInstance, pszCur)) { // 문자열이 같으면 인덱스를 1 증가시킨다.
     index++;
    }
    szInstance += _tcslen(szInstance) + 1;
   }
   
   // 저장한다.
   pIndexOfInstance[i] = index;
  }
  
  // 인스턴스별로 카운터 핸들을 저장할 메모리를 할당한다.
  phCounter_Instance = (HCOUNTER *)malloc(sizeof(HCOUNTER) * cInstance);
  if (!phCounter_Instance) {
   MessageBoxW(NULL, L"phCounter_Instance 메모리 할당(malloc)에 실패하였습니다.", L"CoreAndProcessUsageByPDH", MB_ICONERROR | MB_TOPMOST);
   goto finish;
  }
  memset(phCounter_Instance, 0, sizeof(HCOUNTER) * cInstance);

  // 인스턴스별로 해당 형식에 맞게 카운터 이름(Processor Time), 인덱스와 조합해(#index) 카운터를 추가하고 핸들을 구한다.
  szInstance = mszInstanceList;
  for (i = 0; i < cInstance; i++) {
   memset(szFullCounterPath, 0, sizeof(szFullCounterPath));
   
   // ★ FullCounterPath 형식: \오브젝트 이름(인스턴스 이름#인덱스)\% 카운터 이름  ex) \Process(iexplorer#1)\% Processor Time
   wsprintf(szFullCounterPath, _T("\\%s(%s#%d)\\%% Processor Time"), szObject, szInstance, pIndexOfInstance[i]);

   status = PdhAddCounter(hQuery, szFullCounterPath, 0, phCounter_Instance + i);
   if (ERROR_SUCCESS != status) {
    _tcscpy(szErrorCap, _T("PdhAddCounter - CoreAndProcessUsageByPDH"));
    goto error_pdh;
   }

   //Log(_T("%s\r\n"), szFullCounterPath);
   szInstance += _tcslen(szInstance) + 1;
  }

  ///// 값을 구한다.
  /*
  ★ PdhCollectQueryData 한번 호출만으로는 값을 제대로 구할 수 없다.
  두번 이상 호출해야 된다.(이전 샘플과 현재 샘플 비교해서 값을 산출한다.)
  한번만 호출하면 99.9999나 0.0000값이 들어가 있을 것이다.
  
  참고로 코어 사용률처럼 구하는 카운터가 일정한 경우
  if (k == 0) PdhCollectQueryData(hQuery);
  Sleep(1000);
  PdhCollectQueryData(hQuery);
  위와 같은 식으로 항상 2번 호출이 아니라 첫 루프의 경우만 괄호로 처리해주면 될 것이다.
  (이 경우 작성자 입맛에 맞게 코딩해서 Sleep을 마지막 부근 등 다른 위치로도 보낼 수도 있을 것이다.)

  그런데 프로세스 인스턴스는 주기적으로 리프레시되므로 제때제때 이전 카운터를 해제(PdhRemoveCounter)하고 새로 추가(PdhAddCounter)해야 한다.
  쿼리에 새로 추가(등록)하는 경우는 이전 샘플이 없는 상태이므로  항상 PdhCollectQueryData를 2번 이상 호출해줘야 제대로 값을 구할 수 있다.
  */
  PdhCollectQueryData(hQuery);
  Sleep(1000);
  PdhCollectQueryData(hQuery);

  //////////// 각 코어의 사용률을 표시한다.
  PdhGetFormattedCounterValue(phCounter_Core[si.dwNumberOfProcessors], PDH_FMT_DOUBLE, NULL, &CounterValue);
  Log(_T("★ CPU Total: %f\r\n"), CounterValue.doubleValue);
  
  for (i = 0; i < si.dwNumberOfProcessors; i++) {
   PdhGetFormattedCounterValue(phCounter_Core[i], PDH_FMT_DOUBLE, NULL, &CounterValue);
   Log(_T("☆ CPU Core %d: %f\r\n"), i, CounterValue.doubleValue);
  }
  Log(_T("-----------------------------------------\r\n"));


  //////////// 각 프로세스 인스턴스의 사용률을 표시한다.
  szInstance = mszInstanceList;
  for (i = 0; i < cInstance; i++) {  
   PdhGetFormattedCounterValue(phCounter_Instance[i], PDH_FMT_DOUBLE, NULL, &CounterValue);
   
   // 처음 Idle과 마지막 _Total 인스턴스는 표시 제외한다. 참고로 토탈은 항상 100이고 Idle은 100 - CPU 사용률이다.
   if (i != 0 && i != cInstance - 1) {

    // ★ 멀티코어의 경우 해당 코어수만큼 나눠줘야 백분율로 환산할 수 있다.
    Log(_T("%s: %f\r\n"), szInstance, CounterValue.doubleValue / si.dwNumberOfProcessors);
   }

   szInstance += _tcslen(szInstance) + 1;
  }

  Log(_T("=======================\r\n"));

 
  // 해제한다.
  for (i = 0; i < cInstance; i++) {
   PdhRemoveCounter(phCounter_Instance[i]);
  }
  memset(phCounter_Instance, 0, sizeof(HCOUNTER) * cInstance);

  if (mszCounterList) free(mszCounterList);
  if (mszInstanceList) free(mszInstanceList);
  if (pIndexOfInstance) free(pIndexOfInstance);
  if (phCounter_Instance) free(phCounter_Instance);

  mszCounterList = NULL;
  mszInstanceList = NULL;
  pIndexOfInstance = NULL;
  phCounter_Instance = NULL;

  // 지정된 머신을 리프레쉬한다.(오브젝트 열거 목적이 아님) NULL값일 경우 로컬 컴퓨터.
  // 아래 과정을 하지 않으면 프로세스 목록이 업데이트되지 않고 이전에 구했던 것 그대로이다.
  cchBuffer = 0;
  PdhEnumObjects(NULL, szMachine, NULL, &cchBuffer, PERF_DETAIL_WIZARD, TRUE); // 이 과정이 시스템 부하가 좀 걸리는 듯,,,
 }

 goto finish;


error_pdh:
 // pdh.dll 모듈로부터 에러메시지를 추출해야 하므로 일반 시스템 에러메시지와는 인수가 다름을 유의한다.(다른 부분 주석 처리)
 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, //FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
       GetModuleHandle(_T("pdh.dll")), //NULL,
       status, //GetLastError(),
       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
       (LPTSTR)&lpszErrorBuf,
       0,
       NULL))
 { 
  MessageBox(NULL, (LPCTSTR)lpszErrorBuf, szErrorCap, MB_ICONERROR | MB_TOPMOST);
  LocalFree(lpszErrorBuf);
 } else {
  wsprintf(szErrorText, _T("해당 작업이 실패하였습니다.(0x%08X)"), status);
  MessageBox(NULL, szErrorText, szErrorCap, MB_ICONERROR | MB_TOPMOST);
 }
 
 
finish:
 for (i = 0; i < si.dwNumberOfProcessors + 1; i++) {
  if (phCounter_Core && phCounter_Core[i]) PdhRemoveCounter(phCounter_Core[i]);
 }
 for (i = 0; i < cInstance; i++) {
  if (phCounter_Instance && phCounter_Instance[i]) PdhRemoveCounter(phCounter_Instance[i]);
 }
 if (hQuery) PdhCloseQuery(hQuery);
 if (phCounter_Core) free(phCounter_Core);
 if (mszCounterList) free(mszCounterList);
 if (mszInstanceList) free(mszInstanceList);
 if (pIndexOfInstance) free(pIndexOfInstance);
 if (phCounter_Instance) free(phCounter_Instance);

 return 0;
}