/*===================================================================
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();
    }
}