/*=================================================================== Microsoft IIS 5.0 (ASP) Microsoft Confidential. Copyright 1998 Microsoft Corporation. All Rights Reserved. Component: Thread Gate The thread gate limits number of threads executing at the moment by sleeping some of them. File: thrdgate.cpp Owner: DmitryR This file contains the code for the Thread Gate ===================================================================*/ #include "denpre.h" #pragma hdrstop #include "thrdgate.h" #include "memchk.h" /*=================================================================== Constants for tuning ===================================================================*/ /*=================================================================== Class to track the processor load ===================================================================*/ inline DWORD GetNumberOfProcessors() { SYSTEM_INFO si; GetSystemInfo(&si); return si.dwNumberOfProcessors; } inline LONG GetPercentage(LARGE_INTEGER part, LARGE_INTEGER total) { if (total.HighPart == 0 && total.LowPart == 0) { return 100; } ULONG ul; LARGE_INTEGER t1, t2, t3; if (total.HighPart == 0) { t1 = RtlEnlargedIntegerMultiply(part.LowPart, 100); t2 = RtlExtendedLargeIntegerDivide(t1, total.LowPart, &ul); } else { t1 = RtlExtendedLargeIntegerDivide(total, 100, &ul); t2 = RtlLargeIntegerDivide(part, t1, &t3); } return t2.LowPart; } class CCPULoad { private: DWORD m_cCPU; DWORD m_cbData; // data struct length SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *m_psppiOld; SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *m_psppiNew; public: /*=================================================================== Constructor ===================================================================*/ CCPULoad() { // get the CPU count m_cCPU = GetNumberOfProcessors(); m_cbData = m_cCPU * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); m_psppiOld = new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[m_cCPU]; m_psppiNew = new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[m_cCPU]; if (m_psppiOld == NULL || m_psppiNew == NULL) { return; } // get the original snapshot NtQuerySystemInformation( SystemProcessorPerformanceInformation, m_psppiOld, m_cbData, NULL ); } /*=================================================================== Destructor ===================================================================*/ ~CCPULoad() { if (m_psppiOld != NULL) { delete m_psppiOld; } if (m_psppiNew != NULL) { delete m_psppiNew; } } /*=================================================================== GetReading get the current reading as a percentage of CPU load averaged across processors ===================================================================*/ DWORD GetReading() { if (m_psppiOld == NULL || m_psppiNew == NULL) { return 0; } // get the new snapshot NtQuerySystemInformation( SystemProcessorPerformanceInformation, m_psppiNew, m_cbData, NULL ); // calculate LARGE_INTEGER cpuIdleTime, cpuUserTime, cpuKernelTime, cpuBusyTime, cpuTotalTime, sumBusyTime = RtlConvertLongToLargeInteger(0), sumTotalTime = RtlConvertLongToLargeInteger(0); for (DWORD i = 0; i < m_cCPU; i++) { cpuIdleTime = RtlLargeIntegerSubtract(m_psppiNew[i].IdleTime, m_psppiOld[i].IdleTime); cpuUserTime = RtlLargeIntegerSubtract(m_psppiNew[i].UserTime, m_psppiOld[i].UserTime); cpuKernelTime = RtlLargeIntegerSubtract(m_psppiNew[i].KernelTime, m_psppiOld[i].KernelTime); cpuTotalTime = RtlLargeIntegerAdd(cpuUserTime, cpuKernelTime); cpuBusyTime = RtlLargeIntegerSubtract(cpuTotalTime, cpuIdleTime); IF_DEBUG(THREADGATE) { LONG p = GetPercentage(cpuBusyTime, cpuTotalTime); DBGPRINTF((DBG_CONTEXT, "ThreadGate: load(%d)=%d", i+1, p)); } sumBusyTime = RtlLargeIntegerAdd(sumBusyTime, cpuBusyTime); sumTotalTime = RtlLargeIntegerAdd(sumTotalTime, cpuTotalTime); } LONG nPercentage = GetPercentage(sumBusyTime, sumTotalTime); IF_DEBUG(THREADGATE) { DBGPRINTF((DBG_CONTEXT, "ThreadGate: **** load = %d\r\n", nPercentage)); } // move new to old memcpy(m_psppiOld, m_psppiNew, m_cbData); return nPercentage; } /*=================================================================*/ }; // class CCPULoad /*=================================================================== The thread gate class ===================================================================*/ class CThreadGate { private: DWORD m_msSlice; // granularity DWORD m_msSleep; // sleep length DWORD m_cSleepsMax; // max wait 50 sleeps LONG m_nLowLoad; // low CPU load is < 75% LONG m_nHighLoad; // hight CPU load is > 90% LONG m_cThreadLimitMin; // hunting range low LONG m_cThreadLimitMax; // hunting range high LONG m_cThreadLimit; // current limit LONG m_nTrend; // last change DWORD m_msT0; // starting time LONG m_iCurrentSlice; // current time slice index LONG m_nRequests; // number of active requests CCPULoad m_CPULoad; // track the CPU load public: /*=================================================================== Constructor ===================================================================*/ CThreadGate( DWORD msSlice, DWORD msSleep, DWORD cSleepsMax, DWORD nLowLoad, DWORD nHighLoad, DWORD cLimitMin, DWORD cLimitMax ) { m_msSlice = msSlice; m_msSleep = msSleep; m_cSleepsMax = cSleepsMax; m_nLowLoad = nLowLoad, m_nHighLoad = nHighLoad; m_cThreadLimitMin = cLimitMin; m_cThreadLimitMax = cLimitMax; m_cThreadLimit = m_cThreadLimitMin; m_nTrend = 0; m_msT0 = GetTickCount(); m_iCurrentSlice = 0; m_nRequests = 0; } /*=================================================================== Destructor ===================================================================*/ ~CThreadGate() { } /*=================================================================== HuntLoad Do the load hunting ===================================================================*/ void HuntLoad() { LONG nLoad = m_CPULoad.GetReading(); if (m_nRequests == 0) { // no requests - don't change m_nTrend = 0; return; } LONG cThreadLimit = m_cThreadLimit; LONG nTrend = m_nTrend; if (nLoad < m_nLowLoad) { nTrend = nTrend <= 0 ? 1 : nTrend+3; // grow faster cThreadLimit += nTrend; if (cThreadLimit >= m_cThreadLimitMax) { cThreadLimit = m_cThreadLimitMax; nTrend = 0; } } else if (nLoad > m_nHighLoad) { nTrend = nTrend > 0 ? -1 : nTrend-1; cThreadLimit += nTrend; if (cThreadLimit <= m_cThreadLimitMin) { cThreadLimit = m_cThreadLimitMin; nTrend = 0; } } // set the new limit and trend m_cThreadLimit = cThreadLimit; m_nTrend = nTrend; } /*=================================================================== Enter Pass through the gate. Can make the thread sleep Returns Thread Gate Pass ===================================================================*/ void Enter(DWORD msCurrentTickCount) { DWORD cSleeps = 0; while (cSleeps++ < m_cSleepsMax) { // if shutting down, let the request go. Later it will find // out again that the server is shutting down and not actually // fire the request. if (IsShutDownInProgress()) { break; } // calculate the current time slice DWORD msElapsedSinceT0 = (msCurrentTickCount >= m_msT0) ? (msCurrentTickCount - m_msT0) : ((0xffffffff - m_msT0) + msCurrentTickCount); LONG iSlice = msElapsedSinceT0 / m_msSlice; if (iSlice > m_iCurrentSlice) { // set it as the new one if (InterlockedExchange(&m_iCurrentSlice, iSlice) != iSlice) { // this is the first thread to jump the time slice - go hunting HuntLoad(); } } // enforce the gate limit if (m_nRequests < m_cThreadLimit) { break; } // Too many active threads -- sleep Sleep(m_msSleep); } // let it through InterlockedIncrement(&m_nRequests); } /*=================================================================== Leave Return. The user lets us know that the request finished. ===================================================================*/ void Leave() { InterlockedDecrement(&m_nRequests); } /*=================================================================*/ }; // class CThreadGate // Pointer to the sole instance of the above static CThreadGate *gs_pThreadGate = NULL; /*=================================================================== E x t e r n a l A P I ===================================================================*/ /*=================================================================== InitThreadGate Initialization Parameters ptgc configuration Returns: HRESULT ===================================================================*/ HRESULT InitThreadGate(THREADGATE_CONFIG *ptgc) { DWORD cCPU = GetNumberOfProcessors(); if (ptgc->fEnabled) { gs_pThreadGate = new CThreadGate( ptgc->msTimeSlice, ptgc->msSleepDelay, ptgc->nSleepMax, ptgc->nLoadLow, ptgc->nLoadHigh, ptgc->nMinProcessorThreads * cCPU, ptgc->nMaxProcessorThreads * cCPU ); return (gs_pThreadGate != NULL) ? S_OK : E_OUTOFMEMORY; } gs_pThreadGate = NULL; return S_OK; } /*=================================================================== UnInitThreadGate To be called from DllUnInit() Parameters Returns: n/a ===================================================================*/ void UnInitThreadGate() { if (gs_pThreadGate) { delete gs_pThreadGate; gs_pThreadGate = NULL; } } /*=================================================================== PassThroughThreadGate Pass through the gate. The current thread could be delayed in case there are too many running threads at this moment Parameters msCurrentTickCount current tick count ===================================================================*/ void EnterThreadGate(DWORD msCurrentTickCount) { if (gs_pThreadGate) { gs_pThreadGate->Enter(msCurrentTickCount); } } /*=================================================================== LeaveThreadGate Request done executing ===================================================================*/ void LeaveThreadGate() { if (gs_pThreadGate) { gs_pThreadGate->Leave(); } }