/*++ Copyright (c) 2000-2001 Microsoft Corporation Module Name: thread_manager.cxx Abstract: implementation for THREAD_MANAGER Author: Jeffrey Wall (jeffwall) 11-28-2000 Revision History: --*/ #include #include #include "thread_manager.h" #include "thread_pool_private.h" TCHAR g_szModuleName[MAX_PATH]; DWORD g_dwcCPU = 1; HMODULE g_hmodW3TPDLL = NULL; BOOL TooMuchContextSwitchingLoad(ULONG ulFirstSample, DWORD dwFirstSampleTime, ULONG ulPerSecondSwitchRateMax, DWORD dwNumProcs); BOOL TooMuchProcessorUsage(LARGE_INTEGER liOriginalBusy, LARGE_INTEGER liOriginalTotal, LONG lPercentageUseMax, DWORD dwNumProcs); BOOL GetContextSwitchCount(ULONG * pulSwitchCount); HRESULT GetCPUData(LARGE_INTEGER * pBusyTime, LARGE_INTEGER * pTotalTime, DWORD dwNumProcs); //static HRESULT THREAD_MANAGER::CreateThreadManager(THREAD_MANAGER ** ppManager, THREAD_POOL * pPool, THREAD_POOL_DATA * pPoolData) /*++ Routine Description: Allocate and initialize a THREAD_MANAGER Arguments: ppManager - where to store allocated manager pointer pPool - pointer to THREAD_POOL associated with this manager pPoolData - pointer to THREAD_POOL_DATA associated with this manager Return Value: HRESULT --*/ { DBG_ASSERT(NULL != ppManager); DBG_ASSERT(NULL != pPool); DBG_ASSERT(NULL != pPoolData); *ppManager = NULL; HRESULT hr = S_OK; THREAD_MANAGER * pManager = new THREAD_MANAGER(pPool, pPoolData); if (NULL == pManager) { hr = E_OUTOFMEMORY; goto done; } hr = pManager->Initialize(); if (FAILED(hr)) { // don't call TerminateThreadManager - just delete it delete pManager; goto done; } *ppManager = pManager; hr = S_OK; done: return hr; } VOID THREAD_MANAGER::TerminateThreadManager(LPTHREAD_STOP_ROUTINE lpStopAddress, LPVOID lpParameter) /*++ Routine Description: Shutdown ALL THREAD_MANAGER theads and destroy a THREAD_MANAGER Arguments: lpStopAddress - address of function that will stop threads lpParameter - argument to pass to stop function Return Value: VOID --*/ { // block until threads are gone DrainThreads(lpStopAddress, lpParameter); // do some initializion succeeded only cleanup if (m_hShutdownEvent) { CloseHandle(m_hShutdownEvent); m_hShutdownEvent = NULL; } if (m_hParkEvent) { CloseHandle(m_hParkEvent); m_hParkEvent = NULL; } DeleteCriticalSection(&m_CriticalSection); // don't do anything else after deletion delete this; return; } THREAD_MANAGER::THREAD_MANAGER(THREAD_POOL * pPool, THREAD_POOL_DATA * pPoolData) : m_dwSignature(SIGNATURE_THREAD_MANAGER), m_fShuttingDown(FALSE), m_fWaitingForCreationCallback(FALSE), m_hTimer(NULL), m_pParam(NULL), m_hParkEvent(NULL), m_hShutdownEvent(NULL), m_lParkedThreads(0), m_pPool(pPool), m_pPoolData(pPoolData), m_lTotalThreads(0) /*++ Routine Description: Constructs the ThreadManager Arguments: None Return Value: None --*/ { DBG_ASSERT(m_pPool); DBG_ASSERT(m_pPoolData); ZeroMemory(&m_liOriginalBusy, sizeof(LARGE_INTEGER)); ZeroMemory(&m_liOriginalTotal, sizeof(LARGE_INTEGER)); return; } HRESULT THREAD_MANAGER::Initialize() /*++ Routine Description: Do initialization for THREAD_MANAGER Arguments: VOID Return Value: HRESULT --*/ { HRESULT hr = S_OK; DWORD dwRet; HKEY hKey; BOOL fRet; // Must be set by the DLL main of the DLL loading w3tp DBG_ASSERT( NULL != g_hmodW3TPDLL ); if ( GetModuleFileNameW( g_hmodW3TPDLL, g_szModuleName, MAX_PATH ) == 0 ) { DBG_ASSERT(FALSE && "Failed getting module name for w3tp.dll"); hr = E_FAIL; goto done; } m_hParkEvent = CreateEvent(NULL, // security descriptor FALSE, // auto reset FALSE, // not signaled at creation NULL // event name ); if (NULL == m_hParkEvent) { DBG_ASSERT(FALSE && "Could not create parking event"); hr = E_FAIL; goto done; } m_hShutdownEvent = CreateEvent(NULL, // security descriptor TRUE, // manual reset FALSE, // not signaled at creation NULL // event name ); if (NULL == m_hShutdownEvent) { DBG_ASSERT(FALSE && "Could not create shutdown event"); hr = E_FAIL; goto done; } // keep this at the end of Initialize - if it fails, no need to clean it up ever // If it succeededs, no need to clean it up in this function. // By setting the high order bit for dwSpinCount, we preallocate the CriticalSection fRet = InitializeCriticalSectionAndSpinCount(&m_CriticalSection, 0x80000000 ); if (FALSE == fRet) { DBG_ASSERT(FALSE && "Could not initialize critical section!"); hr = E_FAIL; goto done; } hr = S_OK; done: if (FAILED(hr)) { if (m_hParkEvent) { CloseHandle(m_hParkEvent); m_hParkEvent = NULL; } if (m_hShutdownEvent) { CloseHandle(m_hShutdownEvent); m_hShutdownEvent = NULL; } } return hr; } THREAD_MANAGER::~THREAD_MANAGER() /*++ Routine Description: Destructs the ThreadManager Arguments: None Return Value: None --*/ { DBG_ASSERT(SIGNATURE_THREAD_MANAGER == m_dwSignature); m_dwSignature = SIGNATURE_THREAD_MANAGER_FREE; DBG_ASSERT(TRUE == m_fShuttingDown && "DrainThreads was not called!"); DBG_ASSERT(NULL == m_hTimer); DBG_ASSERT(NULL == m_pParam); DBG_ASSERT(0 == m_lParkedThreads); DBG_ASSERT(0 == m_lTotalThreads); m_pPool = NULL; m_pPoolData = NULL; } //static DWORD THREAD_MANAGER::ThreadManagerThread(LPVOID ThreadParam) /*++ Routine Description: Starter thread for THREAD_MANAGER created threads Takes a reference against the current DLL Notifies THREAD_MANAGER that it has started execution Calls out to "real" thread procedure Notifies THREAD_MANAGER that it is about to terminate Releases reference to current DLL and terminates Arguments: ThreadParam - parameters for control of thread Return Value: win32 error or return value from "real" thread proc --*/ { HMODULE hModuleDll; DWORD dwReturnCode; THREAD_PARAM *pParam = NULL; pParam = (THREAD_PARAM*)ThreadParam; // grab a reference to this DLL hModuleDll = LoadLibrary(g_szModuleName); if (NULL == hModuleDll) { dwReturnCode = GetLastError(); goto done; } // verify the thread parameter passed was reasonable DBG_ASSERT(NULL != pParam); DBG_ASSERT(NULL != pParam->pThreadManager); DBG_ASSERT(NULL != pParam->pThreadFunc); if (pParam->fCallbackOnCreation) { // Inform thread manager that this thread has successfully gotten through the loader lock pParam->pThreadManager->CreatedSuccessfully(pParam); } // actually do work thread is supposed to do dwReturnCode = pParam->pThreadFunc(pParam->pvThreadArg); done: // Inform thread manager that this thread is going away pParam->pThreadManager->RemoveThread(pParam); // Thread owns[ed] memory passed delete pParam; // release reference to this DLL FreeLibraryAndExitThread(hModuleDll, dwReturnCode); // never executed return dwReturnCode; } VOID THREAD_MANAGER::RequestThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpStartParameter) /*++ Routine Description: Creates a timer to determine the correct thread action to take. May create a thread in a little while May take away a thread in a little while May not create the timer if there is another thread creation going on Arguments: lpStartAddress - address of function to begin thread execution lpParameter - argument to pass to start function Return Value: VOID --*/ { BOOL fRet = FALSE; HRESULT hr; DBG_ASSERT(NULL != lpStartAddress); if (TRUE == m_fShuttingDown || TRUE == m_fWaitingForCreationCallback) { return; } // // only want to create one timer at a time // EnterCriticalSection(&m_CriticalSection); if (TRUE == m_fShuttingDown || TRUE == m_fWaitingForCreationCallback) { LeaveCriticalSection(&m_CriticalSection); return; } // only want one thread at a time to be created m_fWaitingForCreationCallback = TRUE; DBGPRINTF(( DBG_CONTEXT, "W3TP: Thread Request received\n")); DWORD dwCurrentTime = GetTickCount(); fRet = GetContextSwitchCount(&m_ulContextSwitchCount); if (FALSE == fRet) { goto done; } if ( THREAD_POOL_MAX_CPU_USAGE_DEFAULT != m_pPoolData->m_poolConfig.dwMaxCPUUsage) { hr = GetCPUData(&m_liOriginalBusy, &m_liOriginalTotal, g_dwcCPU); if (FAILED(hr)) { fRet = FALSE; goto done; } } DBG_ASSERT(NULL == m_pParam); m_pParam = new THREAD_PARAM; if (NULL == m_pParam) { fRet = FALSE; goto done; } m_pParam->pThreadFunc = lpStartAddress; m_pParam->pvThreadArg = lpStartParameter; m_pParam->pThreadManager = this; m_pParam->dwRequestTime = dwCurrentTime; m_pParam->fCallbackOnCreation = TRUE; if (NULL != m_hTimer) { // if this isn't the first time we've requested a thread, // we have a previous TimerQueueTimer. This timer was not // removed during the callback, so we have to clean it up now. // this is the blocking form of the delete operation, however // since the timer has already fired it will not block. fRet = DeleteTimerQueueTimer(NULL, // default timer queue m_hTimer, // previous timer handle INVALID_HANDLE_VALUE // wait until it is removed ); m_hTimer = NULL; } DBG_ASSERT(NULL == m_hTimer); fRet = CreateTimerQueueTimer(&m_hTimer, // storage for timer handle NULL, // default timer queue ControlTimerCallback, // callback function this, // callback argument m_pPoolData->m_poolConfig.dwTimerPeriod, // time til callback 0, // repeat time WT_EXECUTEONLYONCE // no repition ); if (FALSE == fRet) { goto done; } fRet = TRUE; done: if (FALSE == fRet) { delete m_pParam; m_pParam = NULL; m_fWaitingForCreationCallback = FALSE; } LeaveCriticalSection(&m_CriticalSection); return; } //static VOID THREAD_MANAGER::ControlTimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired) /*++ Routine Description: Callback for timer created in RequestThread restores this pointer and forwards to DetermineThreadAction Arguments: lpParameter - THREAD_MANAGER this pointer Return Value: VOID --*/ { THREAD_MANAGER* pThreadManager = (THREAD_MANAGER*)lpParameter; DBG_ASSERT(NULL != pThreadManager); DBG_ASSERT(NULL != pThreadManager->m_pParam); DBG_ASSERT(TRUE == pThreadManager->m_fWaitingForCreationCallback); pThreadManager->DetermineThreadAction(); return; } VOID THREAD_MANAGER::DetermineThreadAction() /*++ Routine Description: Try to determine correct action, create or take away a thread Will take away a thread if context switch rate is too high m_pParam must be populated Arguments: VOID Return Value: VOID --*/ { BOOL fRet = FALSE; DWORD dwElapsedTime = 0; DWORD dwCurrentTime = 0; EnterCriticalSection(&m_CriticalSection); DBG_ASSERT(NULL != m_pParam); if (TRUE == m_fShuttingDown) { fRet = FALSE; goto done; } DBG_ASSERT(TRUE == m_fWaitingForCreationCallback); if (THREAD_POOL_MAX_CPU_USAGE_DEFAULT != m_pPoolData->m_poolConfig.dwMaxCPUUsage) { if (TooMuchProcessorUsage(m_liOriginalBusy, m_liOriginalTotal, m_pPoolData->m_poolConfig.dwMaxCPUUsage, g_dwcCPU)) { // Too much processor usage to create a new thread fRet = FALSE; goto done; } } if (TooMuchContextSwitchingLoad(m_ulContextSwitchCount, m_pParam->dwRequestTime, m_pPoolData->m_poolConfig.dwPerSecondContextSwitchMax, g_dwcCPU)) { // Switching too much DoThreadParking(); fRet = FALSE; goto done; } fRet = DoThreadCreation(m_pParam); if (!fRet) { goto done; } fRet = TRUE; done: if (FALSE == fRet) { delete m_pParam; m_pParam = NULL; m_fWaitingForCreationCallback = FALSE; } else { // thread now has responsibility for the memory m_pParam = NULL; } LeaveCriticalSection(&m_CriticalSection); return; } BOOL THREAD_MANAGER::DoThreadCreation(THREAD_PARAM *pParam) /*++ Routine Description: If there are no threads in parked state Creates a thread, add the HANDLE to the internal list of handles add the creation time to the internal list of times Otherwise, bring a thread out of parked state Arguments: pParam - parameter to pass to created thread Return Value: TRUE if thread is created successfully FALSE if thread is not created - either a thread was returned from parked state, OR there was a problem with creation. --*/ { BOOL fRet = FALSE; HANDLE hThread = NULL; if (DoThreadUnParking()) { // we have not created a new thread DBGPRINTF(( DBG_CONTEXT, "W3TP: Signaled a thread to be unparked\n")); // return false - signal that pParam needs to be freed by the caller fRet = FALSE; goto done; } // bugbug: use _beginthreadex? hThread = ::CreateThread( NULL, // default security descriptor m_pPoolData->m_poolConfig.dwInitialStackSize, // Initial size as configured ThreadManagerThread, // thread function pParam, // thread argument 0, // create running NULL // don't care for thread identifier ); if( NULL == hThread ) { fRet = FALSE; goto done; } // don't keep the handle around CloseHandle(hThread); DBGPRINTF(( DBG_CONTEXT, "W3TP: Created a new thread\n")); InterlockedIncrement(&m_lTotalThreads); // we've successfully created the thread! fRet = TRUE; done: return fRet; } VOID THREAD_MANAGER::DoThreadParking() /*++ Routine Description: Called when context switch rate has been determined to be too high Removes a thread from participation in the thread pool, and parks it Arguments: none Return Value: VOID --*/ { BOOL fRet; // make sure that we leave the starting number of threads in the pool if (m_pPoolData && m_lTotalThreads - m_lParkedThreads <= (LONG) m_pPoolData->m_poolConfig.dwInitialThreadCount) { return; } DBGPRINTF(( DBG_CONTEXT, "W3TP: Posting to park a thread\n")); fRet = m_pPool->PostCompletion(0, ParkThread, (LPOVERLAPPED)this); DBG_ASSERT(TRUE == fRet); return; } BOOL THREAD_MANAGER::DoThreadUnParking() /*++ Routine Description: Release one thread from the parked state Arguments: VOID Return Value: BOOL - TRUE if thread was released FALSE if no threads were available to release --*/ { if (0 == m_lParkedThreads) { return FALSE; } SetEvent(m_hParkEvent); return TRUE; } //static VOID THREAD_MANAGER::ParkThread(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpo) /*++ Routine Description: Put a THREAD_MANAGER thread in a parked state Arguments: dwErrorCode - not used dwNumberOfBytesTransferred - not used lpo - pointer to overlapped that is really a pointer to a THREAD_MANAGER Return Value: VOID --*/ { DWORD dwRet = 0; THREAD_MANAGER * pThis= (THREAD_MANAGER*)lpo; DBG_ASSERT(NULL != pThis); HANDLE arrHandles[2]; arrHandles[0] = pThis->m_hParkEvent; arrHandles[1] = pThis->m_hShutdownEvent; DBGPRINTF(( DBG_CONTEXT, "W3TP: Thread parking\n")); LONG lNewParkedThreads = InterlockedIncrement(&pThis->m_lParkedThreads); if (pThis->m_pPoolData && pThis->m_lTotalThreads - lNewParkedThreads <= (LONG) pThis->m_pPoolData->m_poolConfig.dwInitialThreadCount) { InterlockedDecrement(&pThis->m_lParkedThreads); return; } THREAD_POOL_DATA * pPoolData = pThis->m_pPoolData; DWORD dwTimeout = pPoolData->m_poolConfig.dwThreadTimeout; dwRet = WaitForMultipleObjects(2, arrHandles, FALSE, dwTimeout); InterlockedDecrement(&pThis->m_lParkedThreads); DBGPRINTF(( DBG_CONTEXT, "W3TP: Thread unparked\n")); if (WAIT_TIMEOUT == dwRet) { THREAD_POOL_DATA::ThreadPoolStop(pPoolData); return; } DBG_ASSERT(WAIT_OBJECT_0 == dwRet || WAIT_OBJECT_0 + 1 == dwRet); return; } BOOL THREAD_MANAGER::CreateThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter) /*++ Routine Description: Creates a thread if no other thread is being created currently Arguments: lpStartAddress - address of function to begin thread execution lpParameter - argument to pass to start function Return Value: TRUE if thread is created successfully FALSE if thread is not created --*/ { BOOL fRet = FALSE; THREAD_PARAM * pParam = NULL; EnterCriticalSection(&m_CriticalSection); if (TRUE == m_fShuttingDown) { fRet = FALSE; goto done; } pParam = new THREAD_PARAM; if (NULL == pParam) { fRet = FALSE; goto done; } DBGPRINTF(( DBG_CONTEXT, "W3TP: CreateThread thread creation\n")); pParam->pThreadFunc = lpStartAddress; pParam->pvThreadArg = lpParameter; pParam->pThreadManager = this; pParam->dwRequestTime = GetTickCount(); pParam->fCallbackOnCreation = FALSE; fRet = DoThreadCreation(pParam); if (FALSE == fRet) { goto done; } // created thread has responsibility for this memory pParam = NULL; fRet = TRUE; done: if (FALSE == fRet) { delete pParam; pParam = NULL; m_fWaitingForCreationCallback = FALSE; } LeaveCriticalSection(&m_CriticalSection); return fRet; } VOID THREAD_MANAGER::RemoveThread(THREAD_PARAM * pParam) /*++ Routine Description: Removes given thread from list of active threads and closes handle Arguments: hThreadSelf - handle to current thread Return Value: void --*/ { InterlockedDecrement(&m_lTotalThreads); return; } VOID THREAD_MANAGER::CreatedSuccessfully(THREAD_PARAM * pParam) /*++ Routine Description: Notification that given thread has successfully started Arguments: hThread - current thread handle Return Value: VOID --*/ { DBG_ASSERT(pParam); EnterCriticalSection(&m_CriticalSection); DBG_ASSERT(m_fWaitingForCreationCallback); m_fWaitingForCreationCallback = FALSE; LeaveCriticalSection(&m_CriticalSection); return; } VOID THREAD_MANAGER::DrainThreads(LPTHREAD_STOP_ROUTINE lpStopAddress, LPVOID lpParameter) /*++ Routine Description: stop all threads currently being managed. Doesn't return until all threads are stopped. Arguments: lpStopAddress - address of function to call to signal one thread to stop Return Value: TRUE if all threads are stopped FALSE if one or more threads could not be stopped --*/ { if (TRUE == m_fShuttingDown) { DBG_ASSERT(FALSE && "DrainThreads has been called previously!"); return; } EnterCriticalSection(&m_CriticalSection); // stop any additional thread creation m_fShuttingDown = TRUE; LeaveCriticalSection(&m_CriticalSection); // release all parked threads SetEvent(m_hShutdownEvent); // push as many stops are there are threads running for (INT i = m_lTotalThreads; i >= 0; i--) //TODO: think about sync with interlocked and m_lTotalThreads - is there a race here? { lpStopAddress(lpParameter); } // stop the callback timer if (NULL != m_hTimer) { // block until timer is deleted DeleteTimerQueueTimer(NULL, m_hTimer, INVALID_HANDLE_VALUE); m_hTimer = NULL; if (m_pParam) { // the ownership for m_pParam moves from the creator // to the timer to the thread. // However, we just destroyed the timer - need to cleanup // the memory delete m_pParam; m_pParam = NULL; } } while(m_lTotalThreads > 0) { DBGPRINTF(( DBG_CONTEXT, "W3TP: Waiting for threads to drain, sleep 1000 \n")); Sleep(1000); } DBGPRINTF(( DBG_CONTEXT, "W3TP: All threads drained\n")); return; } BOOL GetContextSwitchCount(ULONG * pulSwitchCount) /*++ Routine Description: Get the current machine context switch count Arguments: pulSwitchCount - where to store switch count Return Value: TRUE if context switch count is read correctly FALSE if context switch count could not be read --*/ { DBG_ASSERT(NULL != pulSwitchCount); SYSTEM_PERFORMANCE_INFORMATION spi; ULONG ulReturnLength; NTSTATUS status; status = NtQuerySystemInformation(SystemPerformanceInformation, &spi, sizeof(spi), &ulReturnLength); if (!NT_SUCCESS(status)) { return FALSE; } *pulSwitchCount = spi.ContextSwitches; return TRUE; } BOOL TooMuchContextSwitchingLoad(ULONG ulFirstSample, DWORD dwFirstSampleTime, ULONG ulPerSecondSwitchRateMax, DWORD dwNumProcs) /*++ Routine Description: Determine if the system is under too much load in terms of context switches / second If dwNumProcs > 1 - the per second switch rate per processor is multiplied by two Arguments: ulFirstSample - first context switch count number dwSampleTimeInMilliseconds - how much time between first sample and calling this function ulPerSecondSwitchRateMax - Maximum switch rate per processor dwNumProcs - number of processors on machine Return Value: TRUE if context switch rate per second per processor is > ulPerSecondSwitchRateMax FALSE if context switch rate is below ulPerSecondSwitchRateMax --*/ { ULONG ulSecondSample = 0; ULONG ulContextSwitchDifference = 0; double dblPerSecondSwitchRate = 0; double dblPerSecondSwitchRatePerProcessor = 0; DWORD dwCurrentTime = 0; DWORD dwElapsedTime = 0; BOOL fRet = FALSE; fRet = GetContextSwitchCount(&ulSecondSample); if (FALSE == fRet) { goto done; } dwCurrentTime = GetTickCount(); if (dwCurrentTime <= dwFirstSampleTime) { // wrap around on time occurred - assume only one wrap around const DWORD MAXDWORD = MAXULONG; dwElapsedTime = MAXDWORD - dwFirstSampleTime + dwCurrentTime; } else { // no wrap around dwElapsedTime = dwCurrentTime - dwFirstSampleTime; } DBG_ASSERT(dwElapsedTime > 0); if (ulSecondSample <= ulFirstSample) { // wrap around on counter occurred - assume only one wrap around ulContextSwitchDifference = (MAXULONG - ulFirstSample) + ulSecondSample; } else { // no wrap around ulContextSwitchDifference = ulSecondSample - ulFirstSample; } DBG_ASSERT(ulContextSwitchDifference > 0); dblPerSecondSwitchRate = ulContextSwitchDifference / ( dwElapsedTime / 1000.0); dblPerSecondSwitchRatePerProcessor = dblPerSecondSwitchRate / dwNumProcs; if (dwNumProcs > 1) { // on multiproc boxes, double the allowed context switch rate per processor ulPerSecondSwitchRateMax *= 2; } if (dblPerSecondSwitchRatePerProcessor > ulPerSecondSwitchRateMax) { DBGPRINTF(( DBG_CONTEXT, "W3TP: Not creating thread, ContextSwitch rate is: %g\n", dblPerSecondSwitchRate )); fRet = TRUE; goto done; } DBGPRINTF(( DBG_CONTEXT, "W3TP: OK to create thread, ContextSwitch rate is: %g\n", dblPerSecondSwitchRate )); fRet = FALSE; done: return fRet; } HRESULT GetCPUData(LARGE_INTEGER * pBusyTime, LARGE_INTEGER * pTotalTime, DWORD dwNumProcs) /*++ Routine Description: Collects the percent CPU load for all machine processors. Arguments: None Return Value: Std HRESULT. --*/ { DBG_ASSERT(pBusyTime && pTotalTime); DBG_ASSERT(dwNumProcs > 0); HRESULT hr = S_OK; NTSTATUS status = STATUS_SUCCESS; LARGE_INTEGER cpuIdleTime = {0}, cpuUserTime = {0}, cpuKernelTime = {0}, cpuBusyTime = {0}, cpuTotalTime = {0}, sumBusyTime = {0}, sumTotalTime = {0}; SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION * psppi = new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[dwNumProcs]; if (NULL == psppi) { hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); goto CLEANUP; } // get the new snapshot status = NtQuerySystemInformation( SystemProcessorPerformanceInformation, psppi, sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * dwNumProcs, NULL ); if(status != STATUS_SUCCESS){ hr = HRESULT_FROM_NT(status); goto CLEANUP; } // calculate for (DWORD i = 0; i < dwNumProcs; i++) { cpuTotalTime = RtlLargeIntegerAdd(psppi[i].UserTime, psppi[i].KernelTime); cpuBusyTime = RtlLargeIntegerSubtract(cpuTotalTime, psppi[i].IdleTime); sumBusyTime = RtlLargeIntegerAdd(sumBusyTime, cpuBusyTime); sumTotalTime = RtlLargeIntegerAdd(sumTotalTime, cpuTotalTime); } *pBusyTime = sumBusyTime; *pTotalTime = sumTotalTime; CLEANUP: delete [] psppi; psppi = NULL; return hr; } LONG GetPercentage(LARGE_INTEGER part, LARGE_INTEGER total) { if (0 == total.QuadPart) { return 100; } LARGE_INTEGER li; part.QuadPart *= 100; li.QuadPart = part.QuadPart / total.QuadPart; return li.LowPart; } BOOL TooMuchProcessorUsage(LARGE_INTEGER liOriginalBusy, LARGE_INTEGER liOriginalTotal, LONG lPercentageUseMax, DWORD dwNumProcs) { HRESULT hr = S_OK; LARGE_INTEGER liNewBusy = {0}, liNewTotal = {0}, liDiffBusy = {0}, liDiffTotal = {0}; hr = GetCPUData(&liNewBusy, &liNewTotal, dwNumProcs); if (FAILED(hr)) { return TRUE; } liDiffBusy = RtlLargeIntegerSubtract(liNewBusy, liOriginalBusy); liDiffTotal = RtlLargeIntegerSubtract(liNewTotal, liOriginalTotal); LONG lPercentageUse = GetPercentage(liDiffBusy, liDiffTotal); if (lPercentageUse >= lPercentageUseMax) { return TRUE; } return FALSE; }