1. 程式人生 > >win7之後的系統的CPU佔用計算的原理與實現

win7之後的系統的CPU佔用計算的原理與實現

        經過比對,發現procexp和工作管理員在計算程序cpu佔用上面存在很大的差異,經過研究發現,procexp顯示的是正確的,而工作管理員顯示的是錯誤的,工作管理員是用以前老的方式計算的。

        新的cpu計算原理應該是:

程序CPU佔用率 = 程序消耗CPU時間 / 所有程序消耗CPU總時間 * 100%


CycleTime:週期時間(即從程序啟動開始到當前所消耗的CPU時間總和)


所有程序消耗CPU總時間 = 所有程序的週期時間 + 核心時間(DPC以及中斷服務)


Idle程序消耗的CPU時間 = 每個核消耗的CPU時間相加

以下是具體實現:

#include <conio.h>
#include <atlbase.h>
#include <iostream>

#include "SysProcess.h"

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define NT_INFORMATION(Status) ((((ULONG)(Status)) >> 30) == 1)
#define NT_WARNING(Status) ((((ULONG)(Status)) >> 30) == 2)
#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3)

#define SYSTEM_IDLE_PROCESS_ID ((HANDLE)0)

#define FIND_FIRST_PROCESS(Processes) ((PSYSTEM_PROCESS_INFORMATION)(Processes))


#define FIND_NEXT_PROCESS(Process) ( \
	((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \
	(PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \
	((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : \
	NULL \
	)

#define PhUpdateDelta(DltMgr, NewValue) \
	((DltMgr)->Delta = (NewValue) - (DltMgr)->Value, \
	(DltMgr)->Value = (NewValue), (DltMgr)->Delta)

typedef NTSTATUS (NTAPI *NTQUERYSYSTEMINFORMATION)(IN SYSTEM_INFORMATION_CLASS, IN OUT PVOID, IN ULONG, OUT PULONG OPTIONAL);
typedef NTSTATUS (NTAPI *NTQUERYINFORMATIONPROCESS)(IN HANDLE ProcessHandle, 
													IN PROCESSINFOCLASS ProcessInformationClass, 
													OUT PVOID ProcessInformation, 
													IN ULONG ProcessInformationLength, 
													OUT PULONG ReturnLength OPTIONAL);

NTQUERYSYSTEMINFORMATION NtQuerySystemInformation = NULL;
NTQUERYINFORMATIONPROCESS NtQueryInformationProcess = NULL;

ULONG g_ulCpuNumber = 0;

std::vector<PROCESS_ITEM_INFORMATION> g_vecSysProInfos;
std::map<HANDLE, HANDLE> g_mapProInfos; //key - UniqueProcessId

PH_UINT64_DELTA g_phCpuCycleDelta = {0};

//查詢程序是否已存在上一次儲存的程序列表裡面
//vecProInfos上一次儲存的程序列表
//pProcess當前需要查詢的程序
inline BOOL GetProcessInfo(const std::vector<PROCESS_ITEM_INFORMATION> &vecProInfos, ULONG64 ProcessId, ULONG64 CreateTime, PROCESS_ITEM_INFORMATION &itemProcess)
{
	BOOL bRet = FALSE;

	for (std::vector<PROCESS_ITEM_INFORMATION>::const_iterator iter = vecProInfos.begin(); iter != vecProInfos.end(); ++ iter)
	{
		PROCESS_ITEM_INFORMATION item = *iter;
		if (ProcessId == item.ProcessId && CreateTime == item.CreateTime)
		{
			itemProcess = item;
			bRet = TRUE;
		}
	}

	return bRet;
}

inline HANDLE GetProcessHandle(HANDLE hUniqueProcessId)
{
	return g_mapProInfos[hUniqueProcessId];
}


//獲取CPU核數
inline ULONG GetCpuNumberOfProcessors()
{
	NTSTATUS status;
	ULONG ulRet = g_ulCpuNumber;
	PSYSTEM_BASIC_INFORMATION pSysBasicInfo = NULL;

	do 
	{
		if (g_ulCpuNumber != 0)
		{
			break;
		}

		if (NULL == NtQuerySystemInformation)
		{
			NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
			if (NULL == NtQuerySystemInformation)
			{
				break;
			}
		}
		
		ULONG ulSize = sizeof(SYSTEM_BASIC_INFORMATION);
		pSysBasicInfo = (PSYSTEM_BASIC_INFORMATION)malloc(ulSize);
		if (NULL == pSysBasicInfo)
		{
			break;
		}

		status = NtQuerySystemInformation(SystemBasicInformation, pSysBasicInfo, ulSize, NULL);
		if (!NT_SUCCESS(status))
		{
			break;
		}

		ulRet = pSysBasicInfo->NumberOfProcessors;
		
	} while (0);

	if (NULL != pSysBasicInfo)
	{
		free(pSysBasicInfo);
	}

	return ulRet;
}

//獲取Idle程序的CPU使用時間
//https://msdn.microsoft.com/en-us/library/windows/desktop/ms684922(v=vs.85).aspx
//Retrieves the cycle time for the idle thread of each processor in the system.
//On a system with more than 64 processors, this function retrieves the cycle time for the 
//idle thread of each processor in the processor group to which the calling thread is assigned. 
//Use the QueryIdleProcessorCycleTimeEx function to retrieve the cycle time for the 
//idle thread on each logical processor for a specific processor group.
inline ULONG64 GetCpuIdleCycleTime()
{
	NTSTATUS status;
	ULONGLONG ullRet = 0;
	PLARGE_INTEGER pCpuIdleCycleTime = NULL;

	do 
	{
		if (NULL == NtQuerySystemInformation)
		{
			NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
			if (NULL == NtQuerySystemInformation)
			{
				break;
			}
		}

		ULONG ulNumberOfProcessors = GetCpuNumberOfProcessors();
		ULONG ulSize = ulNumberOfProcessors * sizeof(LARGE_INTEGER);

		pCpuIdleCycleTime = (PLARGE_INTEGER)malloc(ulSize);
		if (NULL == pCpuIdleCycleTime)
		{
			break;
		}

		status = NtQuerySystemInformation(SystemProcessorIdleCycleTimeInformation, pCpuIdleCycleTime, ulSize, NULL);
		if (!NT_SUCCESS(status))
		{
			break;
		}
	
		for (ULONG i = 0; i < ulNumberOfProcessors; i++)
		{
			ullRet += pCpuIdleCycleTime[i].QuadPart;
		}

	} while (0);

	if (NULL != pCpuIdleCycleTime)
	{
		free(pCpuIdleCycleTime);
	}

	return ullRet;
}


//https://msdn.microsoft.com/en-us/library/windows/desktop/dd405497(v=vs.85).aspx
//Retrieves the cycle time each processor in the specified processor group spent executing deferred procedure calls (DPCs) and interrupt service routines (ISRs) since the processor became active.
//檢索指定組中的每個處理器週期推遲執行過程呼叫(dpc)和中斷服務程式
inline ULONG64 GetCpuSystemCycleTime()
{
	NTSTATUS status;
	ULONGLONG ullRet = 0;
	ULONGLONG ullTotal = 0;
	PLARGE_INTEGER pSysCycleTime = NULL;

	do 
	{
		if (NULL == NtQuerySystemInformation)
		{
			NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
			if (NULL == NtQuerySystemInformation)
			{
				break;
			}
		}

		ULONG ulNumberOfProcessors = GetCpuNumberOfProcessors();
		ULONG ulSize = ulNumberOfProcessors * sizeof(LARGE_INTEGER);

		pSysCycleTime = (PLARGE_INTEGER)malloc(ulSize);
		if (NULL == pSysCycleTime)
		{
			break;
		}

		status = NtQuerySystemInformation(SystemProcessorCycleTimeInformation, pSysCycleTime, ulSize, NULL);
		if (!NT_SUCCESS(status))
		{
			break;
		}

		for (ULONG i = 0; i < ulNumberOfProcessors; i++)
		{
			ullTotal += pSysCycleTime[i].QuadPart;
		}

		PhUpdateDelta(&g_phCpuCycleDelta, ullTotal);

		ullRet = g_phCpuCycleDelta.Delta;

	} while (0);

	if (NULL != pSysCycleTime)
	{
		free(pSysCycleTime);
	}

	return ullRet;
}


//獲取程序累計時間
inline ULONGLONG GetProcessCycleTime(HANDLE hProcessHandle)
{
	NTSTATUS status;
	ULONGLONG ullRet = 0;
	PPROCESS_CYCLE_TIME_INFORMATION pProcessCycleTime = NULL;

	do 
	{
		if (NULL == NtQueryInformationProcess)
		{
			NtQueryInformationProcess = (NTQUERYINFORMATIONPROCESS)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryInformationProcess");
			if (NULL == NtQueryInformationProcess)
			{
				break;
			}
		}

		ULONG ulNeedSize = 0;
		status = NtQueryInformationProcess(hProcessHandle, ProcessCycleTime, NULL, 0, &ulNeedSize);
		if (!NT_SUCCESS(status))
		{
			break;
		}

		pProcessCycleTime = (PPROCESS_CYCLE_TIME_INFORMATION)malloc(ulNeedSize);
		if (NULL == pProcessCycleTime)
		{
			break;
		}

		status = NtQueryInformationProcess(hProcessHandle, ProcessCycleTime, pProcessCycleTime, ulNeedSize, NULL);
		if (!NT_SUCCESS(status))
		{
			break;
		}

		ullRet = pProcessCycleTime->AccumulatedCycles;

	} while (0);

	if (NULL != pProcessCycleTime)
	{
		free(pProcessCycleTime);
	}

	return ullRet;
}

//獲取已退出程序在該時間段內使用的週期時間
inline ULONGLONG GetDeadProcessCycleTime(const std::vector<PROCESS_ITEM_INFORMATION> &oldVecProInfos, const std::vector<PROCESS_ITEM_INFORMATION> &newVecProInfos)
{
	ULONGLONG ullRet = 0;

	for (std::vector<PROCESS_ITEM_INFORMATION>::const_iterator iter = oldVecProInfos.begin(); iter != oldVecProInfos.end(); ++ iter)
	{
		PROCESS_ITEM_INFORMATION oldProcess = *iter;
		PROCESS_ITEM_INFORMATION newProcess = {0};
		if (GetProcessInfo(newVecProInfos, oldProcess.ProcessId, oldProcess.CreateTime, newProcess))
		{
			HANDLE hProcess = GetProcessHandle((HANDLE)oldProcess.ProcessId);
			ULONGLONG ullCycleTime = GetProcessCycleTime(hProcess);

			ullRet += ullCycleTime;
		}
	}

	return ullRet;
}

//計算程序的CPU佔用率
inline void CalProcessCPUUsage(std::map<ULONG, double> &mapPros)
{
	PVOID pProcInfo = NULL;
	PSYSTEM_PROCESS_INFORMATION pProcess =NULL;
	
	ULONG ulNeedSize = 0;

	ULONG64 sysTotalCycleTime = 0;

	std::map<ULONG, ULONG64> mapCycleTime;


	std::vector<PROCESS_ITEM_INFORMATION> vecSysProInfos;

	if (NULL == NtQuerySystemInformation)
	{
		NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
		if (NULL == NtQuerySystemInformation)
		{
			return;
		}
	}

	NtQuerySystemInformation(SystemProcessInformation, NULL, 0, &ulNeedSize);

	pProcInfo = (PVOID)malloc(ulNeedSize);

	NtQuerySystemInformation(SystemProcessInformation, pProcInfo, ulNeedSize, NULL);


	pProcess = FIND_FIRST_PROCESS(pProcInfo);

	while(pProcess)
	{
		//Idle程序
		if (pProcess->UniqueProcessId == SYSTEM_IDLE_PROCESS_ID)
		{
			pProcess->CycleTime = GetCpuIdleCycleTime();

		}

		//所以當前程序
		PROCESS_ITEM_INFORMATION ProcessItem = {0};
		if (GetProcessInfo(g_vecSysProInfos, (ULONG64)pProcess->UniqueProcessId, pProcess->CreateTime.QuadPart, ProcessItem))
		{
			sysTotalCycleTime += pProcess->CycleTime - ProcessItem.CycleTime;
			
			for (std::map<ULONG, double>::iterator iter = mapPros.begin(); iter != mapPros.end(); ++ iter)
			{
				ULONG ulPid = iter->first;
				if ((ULONGLONG)pProcess->UniqueProcessId == ulPid)
				{
					ULONG64 cycleTime = pProcess->CycleTime - ProcessItem.CycleTime;
					mapCycleTime[ulPid] = cycleTime;
				}
			}
		}
		else
		{
			sysTotalCycleTime += pProcess->CycleTime;
			for (std::map<ULONG, double>::iterator iter = mapPros.begin(); iter != mapPros.end(); ++ iter)
			{
				ULONG ulPid = iter->first;
				if ((ULONGLONG)pProcess->UniqueProcessId == ulPid)
				{
					ULONG64 cycleTime = pProcess->CycleTime;
					mapCycleTime[ulPid] = cycleTime;
				}
			}
		}
		
		//HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, (DWORD)pProcess->UniqueProcessId);
		//g_mapProInfos[pProcess->UniqueProcessId] = hProcess;

		PROCESS_ITEM_INFORMATION process = {0};
		process.CreateTime = pProcess->CreateTime.QuadPart;
		process.CycleTime = pProcess->CycleTime;
		process.ProcessId = (ULONG64)pProcess->UniqueProcessId;

 		vecSysProInfos.push_back(process);
		pProcess = FIND_NEXT_PROCESS(pProcess);
	}

	//核心時間(DPC以及中斷服務時間)
	ULONGLONG ullSysCycleTime = GetCpuSystemCycleTime();

	////已退出程序使用時間
	//ULONGLONG ullDeadProCycleTime = GetDeadProcessCycleTime(g_vecSysProInfos, vecSysProInfos);

	sysTotalCycleTime += ullSysCycleTime;
	//sysTotalCycleTime += ullDeadProCycleTime;

	g_vecSysProInfos.clear();
	for (std::vector<PROCESS_ITEM_INFORMATION>::iterator iter = vecSysProInfos.begin(); iter != vecSysProInfos.end(); ++ iter)
	{
		g_vecSysProInfos.push_back(*iter);
	}

	//先清空下
	mapPros.clear();
	for (std::map<ULONG, ULONG64>::iterator iter = mapCycleTime.begin(); iter != mapCycleTime.end(); ++ iter)
	{
		ULONG ulPid = iter->first;
		ULONG64 ullCycleTime = iter->second;
		if (ullCycleTime != 0 && sysTotalCycleTime != 0)
		{
			double dUsage = 100 * (double)ullCycleTime / (double)sysTotalCycleTime;
			mapPros[ulPid] = dUsage;
		}
	}

	if (NULL != pProcInfo)
	{
		free(pProcInfo);
	}
}



程序CPU佔用率= 程序消耗CPU時間/ 所有程序消耗CPU總時間*100%

CycleTime週期時間(即從程序啟動開始到當前所消耗的CPU時間總和)

所有程序消耗CPU總時間= 所有程序的週期時間+核心時間(DPC以及中斷服務)

Idle程序消耗的CPU時間= 每個核消耗的CPU時間相加