在进程的性能数据采集过程中,经常用到的一个性能指标就是进程的cpu占用率,下面给出它的计算方法及示例代码。
1、进程CPU占用率的定义
进程CPU占用率:指进程在一个时间段内消耗的CPU时间与该时间段长度的比值。
2、进程CPU占用率计算方法
根据上述定义,可以得到进程CPU占用率计算公式如下:
进程消耗的CPU时间 = 进程消耗的内核态时间 + 进程消耗的用户态时间,即 costTime = kernelTime + UserTime
进程的CPU占用率 = 进程消耗的CPU时间 / 刷新周期 / CPU核数
计算线程的CPU占用率,要不要考虑CPU核数呢?在Process Explorer中的目标进程的属性页面,在Threads标签页下能看到目标进程中所有线程的CPU占用情况,如下图所示:
3、CPU占用率计算涉及到的API
示例程序用到的主要API
GetSystemInfo 我们主要用它来获取系统中CPU核心个数
OpenProcess 用来打开指定进程的句柄
GetProcessTimes 根据OpenProcess返回的句柄,获取进程的KernelTime和UserTime(可用于进程的CPU占用率计算)
其它API(用于线程等其它情况下的计算)
OpenThread 获取指定线程的句柄
GetThreadTimes 根据OpenThread返回的句柄,获取线程的KernelTime和UserTime (可用于线程的CPU占用率计算)
GetSystemTimes 获取总的CPU时间IdleTime、KernelTime和UserTime,可用于系统总的CPU占用率计算(注:多核CPU中返回的是所有CPU核时间的总和)
NtQuerySystemInformation 这是个native api,可以获取到许多信息
4、示例代码
#include "stdafx.h"
#include <conio.h>
#include <windows.h>
#include <TlHelp32.h>
#include <process.h>
#define MY_PROCESS_ERROR(Condition) do{ if (!(Condition)) goto Exit0; } while (false)
static DWORD g_sdwTickCountOld = 0; // 上一次的tick计数
static LARGE_INTEGER g_slgProcessTimeOld; // 保存进程上一次的时间占用
static DWORD g_sdwProcessorCoreNum = 0; // 处理器核心数
static HANDLE g_shExitEvent = NULL; // 线程退出控制
typedef struct _TARGET_PROCESS
{
DWORD dwProcessId; // 进程ID
}TARGET_PROCESS, *PTARGET_PROCESS;
/*@brief 获取找到的与指定进程名相符的第一个进程ID
* @param [in] cpszExeFileName 进程可执行文件名(不带路径)
* @param [in/out] dwPID 返回找到的名字符合的第一个进程ID
* @return 成功 : S_OK 失败 : 错误码
*/
HRESULT FindFirstProcessIdByName(const TCHAR* cpszExeFileName, DWORD &dwPID)
{
HRESULT hr = E_FAIL;
PROCESSENTRY32 pe = { 0 };
HANDLE hSnapshot = NULL;
if (NULL == cpszExeFileName)
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_ARGUMENTS);
goto Exit0;
}
pe.dwSize = sizeof(PROCESSENTRY32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit0;
}
if (FALSE == Process32First(hSnapshot, &pe))
{
hr = HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES);
goto Exit0;
}
hr = S_FALSE;
do
{
if (0 == _tcsicmp(cpszExeFileName, pe.szExeFile))
{
dwPID = pe.th32ProcessID;
hr = S_OK;
break;
}
}while(Process32Next(hSnapshot, &pe));
Exit0:
if(hSnapshot)
{
CloseHandle(hSnapshot);
hSnapshot = NULL;
}
return hr;
}
/*@brief 获取进程的Cpu占用率
* @param [in] hProcess 进程句柄
* @param [in] dwElepsedTime 取样间隔时间(毫秒)
* @return 成功 : cpu占用率 失败 : -1
*/
int GetProcessCpuPercent(const HANDLE hProcess, const DWORD dwElepsedTime)
{
int nProcCpuPercent = 0;
BOOL bRetCode = FALSE;
FILETIME CreateTime, ExitTime, KernelTime,UserTime;
LARGE_INTEGER lgKernelTime;
LARGE_INTEGER lgUserTime;
LARGE_INTEGER lgCurTime;
bRetCode = GetProcessTimes(hProcess, &CreateTime, &ExitTime, &KernelTime, &UserTime);
if (bRetCode)
{
lgKernelTime.HighPart = KernelTime.dwHighDateTime;
lgKernelTime.LowPart = KernelTime.dwLowDateTime;
lgUserTime.HighPart = UserTime.dwHighDateTime;
lgUserTime.LowPart = UserTime.dwLowDateTime;
lgCurTime.QuadPart = (lgKernelTime.QuadPart + lgUserTime.QuadPart) / 10000;
nProcCpuPercent = (int)((lgCurTime.QuadPart - g_slgProcessTimeOld.QuadPart) * 100 / dwElepsedTime);
g_slgProcessTimeOld = lgCurTime;
nProcCpuPercent = nProcCpuPercent / g_sdwProcessorCoreNum;
}
else
{
nProcCpuPercent = -1;
}
return nProcCpuPercent;
}
unsigned __stdcall WorkerThread(void *pArg)
{
HRESULT hr = E_FAIL;
HANDLE hProcess = NULL;
DWORD dwProcessId = 0;
DWORD dwRetVal = 0;
DWORD dwCurrentTickCount = 0;
DWORD dwElapsedTime = 0;
int nProcessCpuPercent = 0;
TARGET_PROCESS *pTargetProcess = (TARGET_PROCESS *)pArg;
dwProcessId = pTargetProcess->dwProcessId;
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION , FALSE, dwProcessId);
MY_PROCESS_ERROR(hProcess);
do
{
dwRetVal = WaitForSingleObject(g_shExitEvent, 1000);
if (WAIT_OBJECT_0 == dwRetVal ||
WAIT_FAILED == dwRetVal
)
{
break;
}
dwCurrentTickCount = GetTickCount();
dwElapsedTime = dwCurrentTickCount - g_sdwTickCountOld;
g_sdwTickCountOld = dwCurrentTickCount;
nProcessCpuPercent = GetProcessCpuPercent(hProcess, dwElapsedTime);
wprintf(L"cpu = %d\n", nProcessCpuPercent);
} while (1);
Exit0:
if (hProcess)
{
CloseHandle(hProcess);
hProcess = NULL;
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr = E_FAIL;
HANDLE hThread = NULL;
unsigned int uiTid = 0;
SYSTEM_INFO sysInfo = { 0 };
TARGET_PROCESS struTargetProcess;
if (argc > 1)
{
hr = FindFirstProcessIdByName(argv[1], struTargetProcess.dwProcessId);
if (S_OK != hr)
{
_tprintf(_T("Can't find process \'%s\' ret=0x%X\n"), argv[1], hr);
goto Exit0;
}
GetSystemInfo(&sysInfo);
g_sdwProcessorCoreNum = sysInfo.dwNumberOfProcessors;
g_shExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
MY_PROCESS_ERROR(g_shExitEvent);
hThread = (HANDLE)_beginthreadex(NULL, 0, WorkerThread, &struTargetProcess, 0, &uiTid);
MY_PROCESS_ERROR(hThread);
_getch();
SetEvent(g_shExitEvent);
WaitForSingleObject(hThread, INFINITE);
}
else
{
_tprintf(_T("Please input a process name.\n"));
}
Exit0:
if (hThread)
{
CloseHandle(hThread);
hThread = NULL;
}
if (g_shExitEvent)
{
CloseHandle(g_shExitEvent);
g_shExitEvent = NULL;
}
return 0;
}