Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1285 lines
44 KiB

//+-----------------------------------------------------------------------------
//
// 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