|
|
//+-----------------------------------------------------------------------------
//
// File: lockperf.cxx
//
// Contents: Implementation of lock monitoring for locks used by ole32.dll
//
// Classes: CLockPerfTracker
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
#if LOCK_PERF==1
#include <ole2int.h>
#include <lockperf.hxx>
// gbLockPerf == (Is lock monitoring ON?)
// (It is possible to turn this on & off multiple times during a single test run.)
// Ideally, it would be good to turn it OFF after proc-attach, let the test
// run for a while and then turn it ON. This will get the startup skew out of
// the way.
BOOL gbLockPerf = TRUE;
// Lock perf tracker. Gathers perf measurements for all locks.
CLockPerfTracker gLockTracker;
// static member table of the global tracker class.
CFileLineEntry CLockPerfTracker::_FileLineData[MAX_LOCKPERF_FILEENTRY];
// static member table of the global tracker: this keeps a track of shared time.
CLockEntry CLockPerfTracker::_LockData[MAX_LOCKS];
// counts of locks & {file,line} instances tracked.
ULONG CLockPerfTracker::_numLocks; ULONG CLockPerfTracker::_numCritSecs;
// perf frequency on the system
LARGE_INTEGER CLockPerfTracker::_liFreq;
//Flagged if tables get full
BOOL gbLockPerfOverflow=FALSE;
//Count of entries in the shared table (code critical sections)
LONG glFileLine = 0;
//Strings to used to print report
LPSTR gszLockPerfErr = "##### Error: LockPerfOverFlow!!"; char gszLockPerfBuf[256];
//Used to debug the perf monitoring code.
#define perfPrint(x)
#if DBG==1
#define LockAssert(X) if (!(X)) wsprintfA(gszLockPerfBuf,#X ## "\n"),OutputDebugStringA(gszLockPerfBuf);
#else
#define LockAssert(X)
#endif
// **** Functions to manage the Private TLS used by LockPerf *********
// Heap Handle (copied from tls.cxx)
extern HANDLE g_hHeap; #define HEAP_SERIALIZE 0
DWORD gTlsLockPerfIndex = 0xFFFFFFFF;
//345678901234567890123456789012345678901234567890123456789012345678901234567890
//+-----------------------------------------------------------------------------
//
// Function: AllocLockPerfPvtTlsData
//
// Synopsis: Allocates the Tls data for a thread (at Thread_Attach)
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
HRESULT AllocLockPerfPvtTlsData() { void *pMem = HeapAlloc(g_hHeap, HEAP_SERIALIZE, TLS_LOCKPERF_MAX*sizeof(CTlsLockPerf)); if (!pMem) { LockAssert(!"Could not alloc private Tls data for LockPerf"); return E_OUTOFMEMORY; } LockAssert(gTlsLockPerfIndex!=0xFFFFFFFF); memset(pMem, 0, TLS_LOCKPERF_MAX*sizeof(CTlsLockPerf)); TlsSetValue(gTlsLockPerfIndex, pMem); return S_OK; }
//+-----------------------------------------------------------------------------
//
// Function: FreeLockPerfPvtTlsData
//
// Synopsis: Frees the Tls data for a thread (at Thread_Detach)
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
// REVIEW: What about cases when DllMain is not called with Thread_Detach?
void FreeLockPerfPvtTlsData() { LockAssert(gTlsLockPerfIndex!=0xFFFFFFFF); void *pMem = TlsGetValue(gTlsLockPerfIndex); if (pMem) { HeapFree(g_hHeap, HEAP_SERIALIZE, pMem); } }
//**** End: TLS functions ********************************************
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::Init(), public
//
// Synopsis: Initializes lock perf data, marking entries as unused.
// Called during Proc_Attach
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
HRESULT CLockPerfTracker::Init() { QueryPerformanceFrequency(&_liFreq); // We use lpLockPtr to tell if an entry in these tables is in-use.
for (int i=0;i<MAX_LOCKPERF_FILEENTRY;i++) { _FileLineData[i].lpLockPtr = NULL; } for (i=0; i<MAX_LOCKS; i++) { _LockData[i].lpLockPtr = NULL; } _numLocks = 0; _numCritSecs = 0; return S_OK; }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::RegisterLock(), public
//
// Synopsis: Must be called by the lock creation function.
// lpLockPtr == this-ptr of the lock (or unique ptr)
// bReadWrite == TRUE for Reader-Writer Locks.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::RegisterLock(void *lpLockPtr, BOOL bReadWrite) { BOOL bDone = FALSE; int i=0; while (!bDone) { while (_LockData[i].lpLockPtr) { i++; } LockAssert(i < MAX_LOCKS); // REVIEW: 64-bit implications of this cast?
bDone = !(InterlockedCompareExchange((LONG *)&_LockData[i].lpLockPtr, (LONG)lpLockPtr, NULL)); }
_LockData[i].dwTotalWriteWaitTime = 0; _LockData[i].dwTotalWriteEntrys = 0; _LockData[i].dwTotalWriteLockTime = 0; _LockData[i].pszLockName = NULL; // REVIEW: These 5 could be skipped for non reader-writer locks?
_LockData[i].dwSharedTime = 0; _LockData[i].dwNumReaders = 0;
_LockData[i].dwTotalReadWaitTime = 0; _LockData[i].dwTotalReadLockTime = 0; _LockData[i].dwTotalReadEntrys = 0; }
//+-----------------------------------------------------------------------------
//
// Function: Hash
//
// Synopsis: Maps line #s to index in the perf data table.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
ULONG Hash (ULONG line) { // buckets of 256, 128, 64, 64 totaling to MAX_LOCKPERF_FILEENTRY
// this was based on a grep in Ole32 tree for LOCK macro (Sep98):
// Call counts:
// 137 from 1 - 1024
// 92 from 1024 - 2048
// 45 from 2048 - 3072
// 28 from 3072 - 4096
// 10 from 4096 - 5120
// 11 from 5124 - 6144
// 8 from 6148 - 7168
// highest lineNum was 6872 in marshal.cxx
// The Hash function & array size may need updating if the highest lineNum
// goes beyond 7176 or if any bucket gets maxed out.
ULONG base, offset; if (line < 1024) { base = 0; offset = line>>2; //0 to 255
} else if (line < 2048) { base = 256; offset = (line-1024)>>3; //0 to 127
} else if (line < 3072) { base = 384; offset = (line-2048)>>4; //0 to 63
} else { //this covers lines from 3072 to 7168
base = 448; offset = (line-3072)>>6; //0 to 63
} return base+offset; }
//+-----------------------------------------------------------------------------
//
// Function: GetTlsLockPerfEntry
//
// Synopsis: Finds the entry in Tls for a lock or returns a free entry
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
CTlsLockPerf *GetTlsLockPerfEntry(void *lpLockPtr) { CTlsLockPerf *lpEntry = NULL, *lpFree = NULL; CTlsLockPerf *lpCurrent = (CTlsLockPerf *)TlsGetValue(gTlsLockPerfIndex); for (int i=0; i<TLS_LOCKPERF_MAX; i++) { if (lpCurrent->_dwFlags & TLS_LOCKPERF_INUSE) { if (lpCurrent->_lpLockPtr == lpLockPtr) { lpEntry = lpCurrent; break; } } else if (!lpFree) { // Remember the first free entry in case we need it.
lpFree = lpCurrent; } lpCurrent++; } return (lpEntry!=NULL) ? lpEntry : lpFree; }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::ReaderWaiting(), public
//
// Synopsis: Called by the lock code when a thread attempts to enter
// a critical section for reading.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::ReaderWaiting(const char*pszFile, DWORD dwLine, const char* pszLockName, void *lpLockPtr) { if (gbLockPerf) { // Just call WriterWaiting till we need different semantics...
WriterWaiting(pszFile, dwLine, pszLockName, lpLockPtr, FALSE /*bWriter*/); } }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::WriterWaiting(), public
//
// Synopsis: Called by the lock code when a thread attempts to enter
// a critical section for writing.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::WriterWaiting(const char*pszFile, DWORD dwLine, const char* pszLockName, void *lpLockPtr, BOOL bWriter /*default TRUE*/) { if (gbLockPerf) { CTlsLockPerf *pTlsLP = GetTlsLockPerfEntry(lpLockPtr);
// Will assert if we are getting more than TLS_LOCKPERF_MAX
// locks one after another without releasing any.
LockAssert(pTlsLP); if (pTlsLP->_dwFlags & TLS_LOCKPERF_INUSE) { // Recursion on the lock. Request for the lock while holding it.
// Can't be waiting recursively!
LockAssert(pTlsLP->_dwFlags & TLS_LOCKPERF_ENTERED); } else { ULONG idx; ULONG loc = FindOrCreateFileTableEntry(pszFile, dwLine, pszLockName, lpLockPtr, bWriter, &idx);
LockAssert(loc < MAX_LOCKPERF_FILEENTRY); //save table indices in TLS, for quick access later.
pTlsLP->_loc = loc; pTlsLP->_idx = idx; pTlsLP->_lpLockPtr = lpLockPtr; pTlsLP->_dwFlags = TLS_LOCKPERF_WAITING | TLS_LOCKPERF_INUSE; //new Tls entry!
pTlsLP->_dwRecursion = 0; //we set this to 1 upon the first xxxEntered
//store request time in TLS (last thing done to not skew lock wait/use times)
QueryPerformanceCounter(&pTlsLP->_liRequest); } } }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::ReaderEntered(), public
//
// Synopsis: Called by lock code when a thread is granted access to
// a critical section for reading.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
// REVIEW: Life will be easier for us if we enforce that the lock code
// must detect and inform if this is the first reader in the critical
// section. (Similarly for the last reader leaving.)
//------------------------------------------------------------------------------
void CLockPerfTracker::ReaderEntered(const char*pszFile, DWORD dwLine, const char* pszLockName, void *lpLockPtr) { if (gbLockPerf) { ULONG idx = FindLockTableEntry(lpLockPtr); if (InterlockedIncrement((LONG*)&_LockData[idx].dwNumReaders)==1) { QueryPerformanceCounter(&_LockData[idx].liEntered); } // rest of the work is done by WriterEntered
WriterEntered(pszFile, dwLine, pszLockName, lpLockPtr, /*bWriter*/ FALSE); } }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::WriterEntered(), public
//
// Synopsis: Called by lock code when a thread is granted access to
// a critical section for writing.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::WriterEntered(const char*pszFile, DWORD dwLine, const char* pszLockName, void *lpLockPtr, BOOL bWriter /*default TRUE*/) { if (gbLockPerf) { CTlsLockPerf *pTlsLP = GetTlsLockPerfEntry(lpLockPtr); /*
// REVIEW: Should we force lock implementation to call ReaderWaiting/WriterWaiting
// even if there is no reason to wait? In that case the following assertion is true.
// There has to be an entry, either marked waiting or entered (recursive lock)
LockAssert( pTlsLP && ((pTlsLP->_dwFlags & TLS_LOCKPERF_WAITING) || (pTlsLP->_dwFlags & TLS_LOCKPERF_ENTERED)) ); */
if (!(pTlsLP->_dwFlags & TLS_LOCKPERF_INUSE)) { // Someone called xxxEntered directly (without calling xxxWaiting)
ULONG idx; ULONG loc = FindOrCreateFileTableEntry(pszFile, dwLine, pszLockName, lpLockPtr, bWriter, &idx); LockAssert(loc < MAX_LOCKPERF_FILEENTRY); // save the table indices in TLS, for quick access later.
pTlsLP->_loc = loc; pTlsLP->_idx = idx; pTlsLP->_lpLockPtr = lpLockPtr; pTlsLP->_dwFlags = TLS_LOCKPERF_ENTERED | TLS_LOCKPERF_INUSE; pTlsLP->_dwRecursion = 0; QueryPerformanceCounter(&pTlsLP->_liEntered); pTlsLP->_liRequest = pTlsLP->_liEntered; } else if (pTlsLP->_dwFlags & TLS_LOCKPERF_WAITING) { QueryPerformanceCounter(&pTlsLP->_liEntered); // Not waiting any more.
pTlsLP->_dwFlags |= TLS_LOCKPERF_ENTERED; pTlsLP->_dwFlags &= ~TLS_LOCKPERF_WAITING; } pTlsLP->_dwRecursion++; // 1 means first level entry (i.e. no recursion)
} }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::ReaderLeaving(), public
//
// Synopsis: Called by the lock code when a reader is leaving a critical
// section.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::ReaderLeaving(void *lpLockPtr) { if (gbLockPerf) { ULONG idx = FindLockTableEntry(lpLockPtr); LARGE_INTEGER liEntered = _LockData[idx].liEntered; if (InterlockedDecrement((LONG*)&_LockData[idx].dwNumReaders) == 0) { // Last reader leaving
LARGE_INTEGER liDelta, liRem; QueryPerformanceCounter(&liDelta); liDelta = RtlLargeIntegerSubtract(liDelta, liEntered); liDelta = RtlExtendedIntegerMultiply(liDelta,1000000); liDelta = RtlLargeIntegerDivide(liDelta, _liFreq, &liRem); LockAssert(liDelta.HighPart == 0); // no one must hold a lock for so long!
// This must be done inter-locked in case someother thread does
// a 0->1, 1->0 transition while one thread is in this block.
InterlockedExchangeAdd((LONG*)&_LockData[idx].dwSharedTime, liDelta.LowPart); } //Call WriterLeaving to do the rest.
WriterLeaving(lpLockPtr); } }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::WriterLeaving(), public
//
// Synopsis: Called by the lock code when a writer is leaving a critical
// section.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::WriterLeaving(void *lpLockPtr) { if (gbLockPerf) { CTlsLockPerf *pTlsLP = GetTlsLockPerfEntry(lpLockPtr); // There has be to an entry marked entered!
LockAssert(pTlsLP && (pTlsLP->_dwFlags & TLS_LOCKPERF_ENTERED)); pTlsLP->_dwRecursion--; if (pTlsLP->_dwRecursion == 0) { // The thread is *really* leaving the lock. Do the math!
LARGE_INTEGER liUnlockTime; QueryPerformanceCounter(&liUnlockTime); UpdateFileTableEntry(pTlsLP, &liUnlockTime); // Mark the Tls entry as free.
pTlsLP->_dwFlags &= ~TLS_LOCKPERF_INUSE; } else { // The thread is still in the lock!
LockAssert(pTlsLP->_dwFlags & TLS_LOCKPERF_ENTERED); } } }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::ReportContention(), public
//
// Synopsis: Must be called by the lock destroy/cleanup function.
// This is single threaded by definition.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::ReportContention(void *lpLockPtr, DWORD dwWriteEntrys, DWORD dwWriterContention, DWORD dwReadEntrys, DWORD dwReaderContention) { // This happens during DLL_PROCESS_DETACH hence single-threaded
for (int i=0; i<MAX_LOCKS; i++) { if (_LockData[i].lpLockPtr == lpLockPtr) { _LockData[i].dwWriterContentionCount = dwWriterContention; _LockData[i].dwReaderContentionCount = dwReaderContention;
// These asserts may not be very useful since some locks are entered
// before lock monitoring can be started! Also, monitoring can be
// turned OFF and ON in windows.
// LockAssert( _LockData[i].dwTotalWriteEntrys == dwWriteEntrys);
// LockAssert( _LockData[i].dwTotalReadEntrys == dwReadEntrys);
break; } } LockAssert( i<MAX_LOCKS ); }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::FindLockTableEntry(), private
//
// Synopsis: Finds the entry for a critical section in the lock table.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
ULONG CLockPerfTracker::FindLockTableEntry(void *lpLockPtr) { for (int idx=0; idx<MAX_LOCKS; idx++) { if (_LockData[idx].lpLockPtr == lpLockPtr) { return idx; } } LockAssert(!"Lock not registered for monitoring!"); return MAX_LOCKS-1; // just to avoid AVs
}
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::FindOrCreateFileTableEntry(), private
//
// Synopsis: Finds the entry (or creates one) for a critical section guarded
// by a lock at a {file, line}.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
ULONG CLockPerfTracker::FindOrCreateFileTableEntry(const char*pszFile, DWORD dwLine, const char* pszLockName, void *lpLockPtr, BOOL bWriter, DWORD *lpLockTableIndex) { BOOL bFoundEntry = FALSE; CFileLineEntry *pNewTableEntry = NULL; ULONG loc = Hash(dwLine);
if (loc >= MAX_LOCKPERF_FILEENTRY) { LockAssert(!"Lock PerfTable full! Increase size."); gbLockPerfOverflow = TRUE; loc = MAX_LOCKPERF_FILEENTRY-1; goto errRet; }
// If hashing works well this should not take too much time.
while (!bFoundEntry) { while (_FileLineData[loc].lpLockPtr && loc<MAX_LOCKPERF_FILEENTRY) { if ( (_FileLineData[loc].dwLine==dwLine) &&(_FileLineData[loc].pszFile==pszFile) ) { bFoundEntry = TRUE; break; //done
} loc++; } if (loc >= MAX_LOCKPERF_FILEENTRY) { gbLockPerfOverflow = TRUE; loc = MAX_LOCKPERF_FILEENTRY-1; goto errRet; } if (!bFoundEntry && !( InterlockedCompareExchange( (LONG*)&_FileLineData[loc].lpLockPtr, (LONG)lpLockPtr, NULL) ) ) { // We are seeing a new critical section in the code base
bFoundEntry = TRUE; pNewTableEntry = &_FileLineData[loc]; InterlockedIncrement(&glFileLine); // Global count of code CritSec locations
} }
if (pNewTableEntry) { // finish rest of initialization the entry is secured for this code location.
// REVIEW: Ignoring races here.
pNewTableEntry->bWriteCritSec = bWriter; pNewTableEntry->dwNumEntrys = 0; pNewTableEntry->pszLockName = pszLockName; pNewTableEntry->pszFile = pszFile; pNewTableEntry->dwLine = dwLine; pNewTableEntry->dwWaitTime = 0; pNewTableEntry->dwLockedTime = 0; pNewTableEntry->ulLockTableIdx = FindLockTableEntry(lpLockPtr); } errRet: *lpLockTableIndex = _FileLineData[loc].ulLockTableIdx; return loc; }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::UpdateFileTableEntry(), private
//
// Synopsis: Adds the waiting time and the locked time for this visit
// to the cumulative data for the {file,line} entry.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::UpdateFileTableEntry(CTlsLockPerf *pTlsEntry, LARGE_INTEGER *pliUnlockTime) { LockAssert( (pTlsEntry->_dwFlags & TLS_LOCKPERF_INUSE) && (pTlsEntry->_dwFlags & TLS_LOCKPERF_ENTERED) );
ULONG idx = pTlsEntry->_idx; ULONG loc = pTlsEntry->_loc;
LockAssert(loc>=0 && loc < MAX_LOCKPERF_FILEENTRY);
LARGE_INTEGER liRem;
LARGE_INTEGER liWait = RtlLargeIntegerSubtract(pTlsEntry->_liEntered, pTlsEntry->_liRequest); liWait = RtlExtendedIntegerMultiply(liWait,1000000); liWait = RtlLargeIntegerDivide(liWait,_liFreq,&liRem); // liWait is now in micro-seconds
LockAssert(liWait.HighPart == 0); // hopefully no one waits for so long!!
LARGE_INTEGER liLocked = RtlLargeIntegerSubtract(*pliUnlockTime, pTlsEntry->_liEntered); liLocked = RtlExtendedIntegerMultiply(liLocked,1000000); liLocked = RtlLargeIntegerDivide(liLocked, _liFreq, &liRem);
LockAssert(liLocked.HighPart == 0); // no one must hold a lock for so long!
if (_FileLineData[loc].bWriteCritSec) { // Since this is a write location, the lock itself guarantees exclusion
_FileLineData[loc].dwNumEntrys++; _FileLineData[loc].dwWaitTime += liWait.LowPart; _FileLineData[loc].dwLockedTime += liLocked.LowPart;
/*
This needs to be here if we wish to compare entry counts reported by the lock with our own. For now this is in ProcessPerfData _LockData[idx].dwTotalWriteEntrys++; _LockData[idx].dwTotalWriteWaitTime += liWait.LowPart; _LockData[idx].dwTotalWriteLockTime += liLocked.LowPart; */ } else { // This is a read location.
// Hence we have to exclude other readers from updating data
InterlockedIncrement( (LONG*) &_FileLineData[loc].dwNumEntrys ); InterlockedExchangeAdd( (LONG*) &_FileLineData[loc].dwWaitTime, liWait.LowPart ); InterlockedExchangeAdd( (LONG*) &_FileLineData[loc].dwLockedTime, liLocked.LowPart );
/*
This needs to be here if we wish to compare entry counts reported by the lock with our own. For now this is in ProcessPerfData InterlockedIncrement( (LONG*) &_LockData[idx].dwTotalReadEntrys ); InterlockedExchangeAdd( (LONG*) &_LockData[idx].dwTotalReadWaitTime, liWait.LowPart ); InterlockedExchangeAdd( (LONG*) &_LockData[idx].dwTotalReadLockTime, liLocked.LowPart ); */ }
#if 0
// Turn this ON, if you want a live log of every Update.
wsprintfA(gszLockPerfBuf,"\n Lock at %-25s : line %u : Entry # %u", (pTableEntry->pszFile[1]==':') ? pTableEntry->pszFile+24 : pTableEntry->pszFile, pTableEntry->dwLine, pTableEntry->dwNumEntrys); OutputDebugStringA(gszLockPerfBuf);
wsprintfA(gszLockPerfBuf," Held for: %u mic-sec.",liLockHeld.LowPart); OutputDebugStringA(gszLockPerfBuf); #endif
}
#define HOGLIST_TIME 0
#define HOGLIST_ENTRY 1
#define HOGLIST_AVGTIME 2
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::UpdateHoggers(), private
//
// Synopsis: Manages top ten lists.
// This is done only during Proc_detach (hence thread safe!)
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::UpdateHoggers(ULONG* hogList, ULONG index, ULONG listType) { int i,j;
switch (listType) { case HOGLIST_TIME: for (i=0; i<10; i++) { if (hogList[i]==-1) { break; } else if (_FileLineData[hogList[i]].dwLockedTime < _FileLineData[index].dwLockedTime) { break; } } break;
case HOGLIST_ENTRY: for (i=0; i<10; i++) { if (hogList[i]==-1) { break; } else if (_FileLineData[hogList[i]].dwNumEntrys < _FileLineData[index].dwNumEntrys) { break; } } break; case HOGLIST_AVGTIME: for (i=0; i<10; i++) { if (hogList[i]==-1) { break; } else if (_FileLineData[hogList[i]].dwAvgTime < _FileLineData[index].dwAvgTime) { break; } } break;
default: break; }
if (i<10) { for (j=9; j>i;j--) { hogList[j] = hogList[j-1] ; } hogList[i] = index; } }
//+-----------------------------------------------------------------------------
//
// Function: PercentToString
//
// Synopsis: Converts numbers like 74326 to "74.33".
// We do not have float printing support in retail builds.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
// The percent argument is passed in 1000 times magnified.
char gPerc[7]; inline char *PercentToString(long percent) { //round-off
percent = percent/10 + ((percent%10 >= 5)?1:0);
//set to fixed length
percent+=10000;
//create room for decimal point (4th char)
percent = (percent/100)*1000 + (percent%100);
_itoa(percent,gPerc,10); gPerc[0] = gPerc[0]-1; //remove the 10000 we added.
gPerc[3] = '.';
return gPerc + (gPerc[0]=='0'? (gPerc[1]=='0'?2:1):0) ; }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::OutputFileTableEntry, private
//
// Synopsis: Prints out cumulative data for a {file,line} entry
// We are doing this during Process_Detach, hence thread-safe.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::OutputFileTableEntry(ULONG index, ULONG bByName, ULONG percent) { ULONG trimFileName; CFileLineEntry *pTableEntry = &_FileLineData[index];
if (pTableEntry->pszFile[1]==':') { trimFileName = 24; } else { trimFileName = 0; } if (bByName) { wsprintfA(gszLockPerfBuf,"\n %20s %4d %s %6d %7u %6s %% ", pTableEntry->pszFile+trimFileName, pTableEntry->dwLine, pTableEntry->bWriteCritSec ? "Write" : " Read", pTableEntry->dwNumEntrys, pTableEntry->dwLockedTime, PercentToString(percent));
OutputDebugStringA(gszLockPerfBuf); } else { wsprintfA(gszLockPerfBuf,"\n %20s %4d %-14.14s %s %5d %8u %8u", pTableEntry->pszFile+trimFileName, pTableEntry->dwLine, pTableEntry->pszLockName, pTableEntry->bWriteCritSec?"Write":" Read", pTableEntry->dwNumEntrys, pTableEntry->dwLockedTime, pTableEntry->dwAvgTime); OutputDebugStringA(gszLockPerfBuf); } }
#define TITLES_1 \
wsprintfA(gszLockPerfBuf,"\n File Line LockName Type Entrys TotalTime Avg/Entry"); OutputDebugStringA(gszLockPerfBuf)
#define TITLES_2 \
wsprintfA(gszLockPerfBuf,"\n File Line Type Entrys TotalTime %%-Time "); OutputDebugStringA(gszLockPerfBuf)
#define SEPARATOR_1 \
wsprintfA(gszLockPerfBuf,"\n ==================== ===== ============ ===== ====== ========= ========="); OutputDebugStringA(gszLockPerfBuf)
#define SEPARATOR_2 \
wsprintfA(gszLockPerfBuf,"\n ==================== ===== ====== ====== ========= ======"); OutputDebugStringA(gszLockPerfBuf)
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::OutputHoggers(), private
//
// Synopsis: Prints out a top ten list given an array of indices for the same.
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::OutputHoggers(ULONG *hogList) { TITLES_1; SEPARATOR_1;
for (int i=0; i<10; i++) { if (hogList[i]!=-1) { OutputFileTableEntry(hogList[i],0,0); } } SEPARATOR_1; }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::ProcessPerfData(), public
//
// Synopsis: Organizes the perf table data by lockName.
// Prints lock summary data, top-ten lists.
// This is done only during Proc_detach (hence thread safe!)
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::ProcessPerfData() { // #### Begin : organize data
CFileLineEntry *pTableEntry;
ULONG hogListTime[10]; ULONG hogListEntry[10]; ULONG hogListAvgTime[10]; ULONG iName; DWORD totalLockTime = 0; // lock sharing not reflected!
DWORD totalRealLockTime = 0; // takes lock sharing into account!
DWORD totalLocksCreated = 0; // some locks may never get used!
for (int i=0; i<10; i++) { hogListTime[i] = -1; hogListEntry[i] = -1; hogListAvgTime[i] = -1; } for (i=0; i<MAX_LOCKPERF_FILEENTRY; i++) { if (_FileLineData[i].lpLockPtr) { pTableEntry = &_FileLineData[i]; if (pTableEntry->dwNumEntrys) { pTableEntry->dwAvgTime = pTableEntry->dwLockedTime/pTableEntry->dwNumEntrys; } else { pTableEntry->dwAvgTime = 0; } UpdateHoggers(hogListTime,i, 0); UpdateHoggers(hogListEntry,i, 1); UpdateHoggers(hogListAvgTime,i, 2); _numCritSecs++;
//REVIEW: how should we take shared time into account?
totalLockTime = totalLockTime + pTableEntry->dwLockedTime;
iName = FindLockTableEntry(_FileLineData[i].lpLockPtr); if (_LockData[iName].pszLockName==NULL) { // First file table entry for this lock
// We use pszLockName==NULL to tell if a lock got used!
_LockData[iName].pszLockName = pTableEntry->pszLockName; _LockData[iName].dwHead = i; _LockData[iName].dwTail = i;
_numLocks++; } else { //CritSec is already in our list.
_FileLineData[_LockData[iName].dwTail].dwNext = i; _LockData[iName].dwTail = i; }
if (pTableEntry->bWriteCritSec) { _LockData[iName].dwTotalWriteLockTime += pTableEntry->dwLockedTime; _LockData[iName].dwTotalWriteEntrys += pTableEntry->dwNumEntrys; _LockData[iName].dwTotalWriteWaitTime += pTableEntry->dwWaitTime; } else { _LockData[iName].dwTotalReadLockTime += pTableEntry->dwLockedTime; _LockData[iName].dwTotalReadEntrys += pTableEntry->dwNumEntrys; _LockData[iName].dwTotalReadWaitTime += pTableEntry->dwWaitTime; } } // if In Use
} // for each table entry
// #### End : organize data
wsprintfA(gszLockPerfBuf, "\n\n ============= LOCK_PERF: TOP TEN LISTS ===========\n"); OutputDebugStringA(gszLockPerfBuf);
wsprintfA(gszLockPerfBuf, "\n\n ============= Top Ten Hoggers by total time ===========\n"); OutputDebugStringA(gszLockPerfBuf); OutputHoggers(hogListTime);
wsprintfA(gszLockPerfBuf, "\n\n ============= Top Ten Hoggers by crit-sec Entrys ===========\n"); OutputDebugStringA(gszLockPerfBuf); OutputHoggers(hogListEntry);
wsprintfA(gszLockPerfBuf, "\n\n ============= Top Ten Hoggers by avg time per Entry ===========\n"); OutputDebugStringA(gszLockPerfBuf); OutputHoggers(hogListAvgTime);
SEPARATOR_1; wsprintfA(gszLockPerfBuf, "\n\n ============= LOCK_PERF: OVERALL LOCK STATS ===========\n"); OutputDebugStringA(gszLockPerfBuf);
for (i=0; i<MAX_LOCKS; i++) { if (_LockData[i].lpLockPtr) { totalLocksCreated++; if (_LockData[i].pszLockName) { // lock got used!
totalRealLockTime += _LockData[i].dwTotalWriteLockTime; if (_LockData[i].dwTotalReadEntrys) { totalRealLockTime += _LockData[i].dwSharedTime; } } } }
wsprintfA(gszLockPerfBuf, "\n\n TOTAL locks created = %u", totalLocksCreated); OutputDebugStringA(gszLockPerfBuf);
wsprintfA(gszLockPerfBuf, "\n TOTAL locks used = %u", _numLocks); OutputDebugStringA(gszLockPerfBuf); wsprintfA(gszLockPerfBuf, "\n\n TOTAL code critSec areas covered = %u", _numCritSecs); OutputDebugStringA(gszLockPerfBuf);
wsprintfA(gszLockPerfBuf, "\n\n\n TOTAL time spent in all critSecs = %u micro-sec\n\n", totalLockTime); OutputDebugStringA(gszLockPerfBuf); wsprintfA(gszLockPerfBuf, " [This is the sum of individual thread times.\n This does not take overlaps (shared work) into account.]"); OutputDebugStringA(gszLockPerfBuf); wsprintfA(gszLockPerfBuf, "\n\n\n TOTAL real time spent in all critSecs = %u micro-sec\n\n", totalRealLockTime); OutputDebugStringA(gszLockPerfBuf); wsprintfA(gszLockPerfBuf," [This takes shared time into account.]"); OutputDebugStringA(gszLockPerfBuf); wsprintfA(gszLockPerfBuf, "\n\n ## Warning ##: The total time counters overflow in about 70 minutes!\n\n"); OutputDebugStringA(gszLockPerfBuf); }
//+-----------------------------------------------------------------------------
//
// Member: CLockPerfTracker::OutputPerfData(), public
//
// Synopsis: Prints out the {file,line} table.
// This is done only during Proc_detach (hence thread safe!)
//
// History: 20-Dec-98 mprabhu Created
//
//------------------------------------------------------------------------------
void CLockPerfTracker::OutputPerfData() { if (glFileLine) //if monitoring was ON at any time!
{ if (gbLockPerfOverflow) { wsprintfA(gszLockPerfBuf, "\n ### Warning: Overflow in lock perf data buffers!\n \
### Increase array sizes and recompile!!\n");
OutputDebugStringA(gszLockPerfBuf); }
ULONG iStart, iNext, iEnd; const char *pszThisFile; ULONG iName; LARGE_INTEGER liPerc, liDiv, liRem ;
//TITLES_1;
//SEPARATOR_1;
ProcessPerfData();
// #### Begin: output by LockName:
SEPARATOR_2;
wsprintfA(gszLockPerfBuf, "\n\n ============= LOCK_PERF: PER LOCK STATS ===========\n"); OutputDebugStringA(gszLockPerfBuf); BOOL bShared; int iShared; for (iName=0; iName<_numLocks; iName++) { if (_LockData[iName].pszLockName) { //if the lock got used
SEPARATOR_2; wsprintfA(gszLockPerfBuf, "\n\n\n #### ++++++++ Summary for %14s , this = %lx ++++++++ ####", _LockData[iName].pszLockName, (DWORD)_LockData[iName].lpLockPtr); OutputDebugStringA(gszLockPerfBuf);
wsprintfA(gszLockPerfBuf, "\n\n WrtLockTime:%12u WrtEntrys:%14u\n WrtWait:%16u WrtContention:%10u \n", _LockData[iName].dwTotalWriteLockTime, _LockData[iName].dwTotalWriteEntrys, _LockData[iName].dwTotalWriteWaitTime, _LockData[iName].dwWriterContentionCount); OutputDebugStringA(gszLockPerfBuf);
wsprintfA(gszLockPerfBuf, "\n RdLockTime:%13u RdEntrys:%15u\n RdWait:%17u RdContention:%11u \n", _LockData[iName].dwTotalReadLockTime, _LockData[iName].dwTotalReadEntrys, _LockData[iName].dwTotalReadWaitTime, _LockData[iName].dwReaderContentionCount); OutputDebugStringA(gszLockPerfBuf); wsprintfA(gszLockPerfBuf,"\n Shared Read Time: %10u\n", _LockData[iName].dwSharedTime); OutputDebugStringA(gszLockPerfBuf);
TITLES_2; SEPARATOR_2; iNext = _LockData[iName].dwHead; iEnd = _LockData[iName].dwTail; while (1) { liPerc.HighPart = 0; liPerc.LowPart = _FileLineData[iNext].dwLockedTime; liPerc = RtlExtendedIntegerMultiply(liPerc,100000); liDiv.HighPart = 0; if (_FileLineData[iNext].bWriteCritSec) { liDiv.LowPart = _LockData[iName].dwTotalWriteLockTime; } else { liDiv.LowPart = _LockData[iName].dwTotalReadLockTime; }
liPerc = RtlLargeIntegerDivide(liPerc, liDiv, &liRem);
OutputFileTableEntry(iNext,1, liPerc.LowPart); if (iNext == iEnd) break; iNext = _FileLineData[iNext].dwNext; }
SEPARATOR_2; wsprintfA(gszLockPerfBuf, "\n ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ \n"); OutputDebugStringA(gszLockPerfBuf); } //if the lock got used!
}
SEPARATOR_2; // #### End: output by LockName:
SEPARATOR_1;
#if 0 //This lists lock data by fileNames
// #### Begin: output by fileName : location:
wsprintfA(gszLockPerfBuf, "\n\n\n ======= CritSec data listed by FileName: =======\n"); OutputDebugStringA(gszLockPerfBuf);
TITLES_1; SEPARATOR_1;
pszThisFile = NULL; i=0; while (!_FileLineData[i].bInUse) i++; iNext = i;
while (iNext < MAX_LOCKPERF_FILEENTRY) { iStart = iNext; iNext = MAX_LOCKPERF_FILEENTRY; pszThisFile= _FileLineData[iStart].pszFile; for (i=iStart;i<MAX_LOCKPERF_FILEENTRY;i++) { if (_FileLineData[i].bInUse) { if (pszThisFile==_FileLineData[i].pszFile) { OutputFileTableEntry(i,0,0); _FileLineData[i].bInUse = FALSE; } else if (iNext==MAX_LOCKPERF_FILEENTRY) { iNext=i; } } } } // #### End: output by fileName : location:
#endif //0 This lists lock data by fileNames
} //if glFileLine
}
#endif //LOCK_PERF==1
|