/********************************************************************************* /* File: /* PROFILE.H /* Author: /* Max-H. Windisch, SDE-T /* Date: /* October 1996 /* Macros: /* BEGIN_PROFILING_BLOCK /* END_PROFILING_BLOCK /* DUMP_PROFILING_RESULTS /* IMPLEMENT_PROFILING /* IMPLEMENT_PROFILING_CONDITIONAL /* Classes: /* CMaxLargeInteger /* CMaxTimerAbstraction /* CMaxMiniProfiler_Node_Base /* CMaxMiniProfiler_Node_Standard /* CMaxMiniProfiler_Node_NoHistory /* CMaxMiniProfiler_Base /* CMaxMiniProfiler_Standard /* CMaxMiniProfiler_NoHistory /* CMaxMultithreadProfiler /* CMaxProfilingDLLWrapper /* CMaxProfilingObject /* CMaxProfilingBlockWrapper /* Summary: /* This mini profiler allows you to place BEGIN_PROFILING_BLOCK and /* END_PROFILING_BLOCK directives in your code, or use the /* CMaxProfilingBlockWrapper object, and collect results /* in a logfile on termination of the profiled application (or by /* using the DUMP_PROFILING_RESULTS macro). The /* profiling blocks can be nested. Each module (DLL/EXE) using /* the profiler must use IMPLEMENT_PROFILING or /* IMPLEMENT_PROFILING_CONDITIONAL exactly once (defines /* static variables for the profiler) /* More details: /* The default result file is c:/result.txt. It is not erased /* automatically. For each completed instance of a profiler, it /* contains: 1) a header, 2) the history of all profiled blocks (optional), /* 3) merged results. For merging, results are sorted by {level, name}, /* merged, then sorted again by {full name}. Therefore, block names /* must be unique. In any case, absolute results are always /* given (in seconds) /* How to enable in your code: /* To enable the profiler, define MAX_PROFILING_ENABLED before including /* this file. To use the profiler through s:/ds/util/maxprof.dll /* (built in release), define MAX_PROFILING_ENABLED_DLL instead. This /* allows to use one single instance of a profiler from multiple /* modules /* Other comments: /* At runtime, you can disable history output by defining the following /* environment variable to YES: MAX_DISABLE_PROFILING_HISTORY. /* In DLL mode, if you define MAX_PROFILING_CONDITIONAL before including /* this file, the profiler will work only if the following environment /* variable is defined to YES: MAX_ENABLE_PROFILING /* Note: /* It's on purpose that I avoid using virtual methods here /* /* (c) Copyright 1996 Microsoft-Softimage Inc. /********************************************************************************/ #ifndef __MAX_PROFILING_H // { #define __MAX_PROFILING_H #include // for CTime and CString #include // for asserts #include // for streams #include //#pragma warning( disable : 4786 ) // stl antivirus ;-) //#include // for STL #define MAX_ENV_ENABLE_PROFILING _T( "MAX_ENABLE_PROFILING" ) #define MAX_ENV_DISABLE_PROFILING_HISTORY _T( "MAX_DISABLE_PROFILING_HISTORY" ) #define MAX_ENV_YES _T( "YES" ) #define MAX_ENV_ALL _T( "ALL" ) #if !defined( DS_ON_AXP ) && !defined( _NO_THROW ) #define MAXPROFNOTHROW __declspec( nothrow ) #else #define MAXPROFNOTHROW #endif #define MAX_PROFTAGNODE_TOP "PROFILER: ALL" #define MAX_PROFTAGNODE_HEAPALLOCATION "PROFILER: HEAPALLOCATION" #define MAX_PROFTAGNODE_BIAS "PROFILER: BIAS" #define MAX_PROFTAGNODE_NOTHINGNESS "PROFILER: NOTHINGNESS" // Note: disable profiling in _SHIP (unless specified otherwise by DS_PROFILE_SHIP), // and in unix (not sure why) #if ( defined _SHIP && !defined DS_PROFILE_SHIP ) || defined unix #undef MAX_PROFILING_ENABLED_DLL #undef MAX_PROFILING_ENABLED #endif /********************************************************************************* /* Macros: /* BEGIN_PROFILING_BLOCK /* END_PROFILING_BLOCK /* DUMP_PROFILING_RESULTS /* IMPLEMENT_PROFILING /* IMPLEMENT_PROFILING_CONDITIONAL /* Comments: /* . For simplified use of CMaxMiniProfiler's. /* . For the comment parameter, use a non-unicode string, without "return" /* character /* . For the enabler parameter, use a unicode string (the name of your /* environment variable) /* . Use unique comments, since the profiler might use them as sorting keys /* . The use of DUMP_PROFILING_RESULTS is not compulsory, since profilings /* are always dumped at the end of a profiling session /********************************************************************************/ #ifndef unix #define __MAX_RESULTFILE_NAME "c:\\result.txt" #else #define __MAX_RESULTFILE_NAME "result.txt" #endif #ifdef MAX_PROFILING_ENABLED_DLL #define __MAX_MINIPROFILER_IMPLEMENTATION ; #else #define __MAX_MINIPROFILER_IMPLEMENTATION \ const char *CMaxMiniProfiler_Base::s_poDefaultFileName = __MAX_RESULTFILE_NAME; \ CMaxTimerAbstraction CMaxMiniProfiler_Base::s_oOutOfBraceBiasApproximation; \ CMaxTimerAbstraction CMaxMiniProfiler_Base::s_oInOfBraceBiasApproximation; \ bool CMaxMiniProfiler_Base::s_bBiasIsKnown = false; \ unsigned long CMaxMiniProfiler_Base::s_lHeapBlockSize = 5000; #endif #if defined MAX_PROFILING_ENABLED || defined MAX_PROFILING_ENABLED_DLL // {{ #define BEGIN_PROFILING_BLOCK( comment ) \ CMaxProfilingObject::SCreateNewNode( comment ); #define END_PROFILING_BLOCK \ CMaxProfilingObject::SCloseCurrentNode(); #define DUMP_PROFILING_RESULTS \ CMaxProfilingObject::SDumpResults(); #define IMPLEMENT_PROFILING \ __MAX_MINIPROFILER_IMPLEMENTATION \ CMaxProfilingObject::MPOProfiler CMaxProfilingObject::s_oProfiler; \ CMaxProfilingObject::__CBiasApproximation CMaxProfilingObject::s_oBiasApproximation; #define IMPLEMENT_PROFILING_CONDITIONAL( enabler ) \ __MAX_MINIPROFILER_IMPLEMENTATION \ CMaxProfilingObject::MPOProfiler CMaxProfilingObject::s_oProfiler( enabler ); \ CMaxProfilingObject::__CBiasApproximation CMaxProfilingObject::s_oBiasApproximation; #else // }{ #define BEGIN_PROFILING_BLOCK( comment ) ( void )( comment ); #define END_PROFILING_BLOCK ; #define DUMP_PROFILING_RESULTS ; #define IMPLEMENT_PROFILING ; #define IMPLEMENT_PROFILING_CONDITIONAL( enabler ) ; #endif // }} #if defined MAX_PROFILING_ENABLED || defined MAX_PROFILING_ENABLED_DLL || defined MAX_PROFILING_DLL_IMPLEMENTATION // { /********************************************************************************* /* Helper function: /* bGIsEnabledEnvVar /* Comments: /********************************************************************************/ MAXPROFNOTHROW static inline bool bGIsEnabledEnvVar( const TCHAR *pszEnvironmentVariableName, const TCHAR *pszCriteria = MAX_ENV_YES ) { const int nLength = 80; TCHAR szBuffer[ nLength ]; DWORD dwValue; // NULL string means enabled (default) if ( NULL == pszEnvironmentVariableName ) return true; dwValue = ::GetEnvironmentVariable( pszEnvironmentVariableName, szBuffer, nLength ); if ( dwValue > 0 && _tcsicmp( szBuffer, pszCriteria ) == 0 ) return true; return false; }; #endif // } #if defined MAX_PROFILING_ENABLED || defined MAX_PROFILING_DLL_IMPLEMENTATION // { /********************************************************************************* /* Class: /* CMaxLargeInteger /* Comments: /* Minimal encapsulation of LARGE_INTEGER, considered as a time value /********************************************************************************/ class CMaxLargeInteger { protected: LARGE_INTEGER m_oValue; public: MAXPROFNOTHROW CMaxLargeInteger( LONG lHighPart = 0, DWORD dwLowPart = 0 ) { m_oValue.u.HighPart = lHighPart; m_oValue.u.LowPart = dwLowPart; } MAXPROFNOTHROW CMaxLargeInteger( LONGLONG llQuadPart ) { m_oValue.QuadPart = llQuadPart; } MAXPROFNOTHROW CMaxLargeInteger operator +( const CMaxLargeInteger &roAdded ) const { return CMaxLargeInteger( m_oValue.QuadPart + roAdded.m_oValue.QuadPart ); } MAXPROFNOTHROW CMaxLargeInteger operator -( const CMaxLargeInteger &roSubstracted ) const { return CMaxLargeInteger( m_oValue.QuadPart - roSubstracted.m_oValue.QuadPart ); } MAXPROFNOTHROW CMaxLargeInteger operator /( unsigned long lDivisor ) const { return CMaxLargeInteger( m_oValue.QuadPart / ( LONGLONG )lDivisor ); } MAXPROFNOTHROW bool operator <( const CMaxLargeInteger &roCompared ) const { return m_oValue.QuadPart < roCompared.m_oValue.QuadPart; } MAXPROFNOTHROW operator LARGE_INTEGER*() { return &m_oValue; } MAXPROFNOTHROW LONG lFGetHighPart() const { return m_oValue.u.HighPart; } MAXPROFNOTHROW DWORD dwFGetLowPart() const { return m_oValue.u.LowPart; } MAXPROFNOTHROW double dFInSecondsF( const CMaxLargeInteger &roFreq ) const { const DWORD dwMaxDword = 0xffffffff; double highunit; assert( 0 == roFreq.m_oValue.u.HighPart && 0 != roFreq.m_oValue.u.LowPart ); highunit = ( ( double )dwMaxDword + 1.0 ) / ( double )roFreq.m_oValue.u.LowPart; return ( ( ( double )m_oValue.u.HighPart * highunit ) + ( ( double )m_oValue.u.LowPart / roFreq.m_oValue.u.LowPart ) ); } }; MAXPROFNOTHROW inline ostream& operator<<( ostream &os, const CMaxLargeInteger &val ) { return os << "(" << ( unsigned long )val.lFGetHighPart() << ";" << ( unsigned long )val.dwFGetLowPart() << ")"; }; /********************************************************************************* /* Class: /* CMaxTimerAbstraction /* Comments: /* Defines the interface CMaxMiniProfiler's expect from any timer /* implementation /********************************************************************************/ class CMaxTimerAbstraction { protected: CMaxLargeInteger m_oTime; static const CMaxLargeInteger s_oFrequency; public: MAXPROFNOTHROW CMaxTimerAbstraction(){ /* assumed to zero its internal value */ } MAXPROFNOTHROW CMaxTimerAbstraction( int ){ ::QueryPerformanceCounter( m_oTime ); } MAXPROFNOTHROW CMaxTimerAbstraction( const CMaxTimerAbstraction &roSrc ) : m_oTime( roSrc.m_oTime ){} MAXPROFNOTHROW const CMaxTimerAbstraction& operator =( const CMaxTimerAbstraction &roSrc ){ m_oTime = roSrc.m_oTime; return *this; } protected: // Note: not part of the interface; for internal use only MAXPROFNOTHROW CMaxTimerAbstraction( const CMaxLargeInteger &roSrc ) : m_oTime( roSrc ){}; public: MAXPROFNOTHROW void FLog() { ::QueryPerformanceCounter( m_oTime ); } MAXPROFNOTHROW double dFInSeconds() const { return m_oTime.dFInSecondsF( s_oFrequency ); } public: MAXPROFNOTHROW void FAdd( const CMaxTimerAbstraction &roAdded ) { m_oTime = m_oTime + roAdded.m_oTime; } MAXPROFNOTHROW void FSubstract( const CMaxTimerAbstraction &roSubstracted ) { #if 0 // special case for negative differences - hide them if ( m_oTime < roSubstracted.m_oTime ) { m_oTime = CMaxLargeInteger( 0, 1 ); return; } #endif m_oTime = m_oTime - roSubstracted.m_oTime; } MAXPROFNOTHROW void FDivide( unsigned long lDivisor ) { m_oTime = m_oTime / lDivisor; } public: MAXPROFNOTHROW static CMaxTimerAbstraction oSSum( const CMaxTimerAbstraction &roArg1, const CMaxTimerAbstraction &roArg2 ) { CMaxTimerAbstraction sum; sum.m_oTime = roArg1.m_oTime + roArg2.m_oTime; return sum; } MAXPROFNOTHROW static CMaxTimerAbstraction oSDifference( const CMaxTimerAbstraction &roArg1, const CMaxTimerAbstraction &roArg2 ) { CMaxTimerAbstraction difference; #if 0 // special case for negative differences - hide them if ( roArg1.m_oTime < roArg2.m_oTime ) return CMaxTimerAbstraction( CMaxLargeInteger( 0, 1 ) ); #endif difference.m_oTime = roArg1.m_oTime - roArg2.m_oTime; return difference; } MAXPROFNOTHROW static bool bSLess( const CMaxTimerAbstraction &roArg1, const CMaxTimerAbstraction &roArg2 ) { return roArg1.m_oTime < roArg2.m_oTime; } MAXPROFNOTHROW static CMaxTimerAbstraction oSFrequency() { return CMaxTimerAbstraction( s_oFrequency ); } private: MAXPROFNOTHROW static CMaxLargeInteger oSCentralFrequency() { CMaxLargeInteger frequency; ::QueryPerformanceFrequency( frequency ); return frequency; } friend ostream& operator<<( ostream &os, const CMaxTimerAbstraction &val ); }; MAXPROFNOTHROW inline ostream& operator<<( ostream &os, const CMaxTimerAbstraction &val ) { return os << val.m_oTime; }; /********************************************************************************* /* Class: /* CMaxMiniProfiler_Node_Base /* Comments: /* Basic profiling node that behaves like a chronometer, and provides /* standard logging services. Both the Standard and NoHistory profilers /* use this basic implementation /********************************************************************************/ class CMaxMiniProfiler_Node_Base { public: typedef CString MMPNBString; public: // comparison by index // ------------------- class CCompareIndexes { public: MAXPROFNOTHROW bool operator()( const CMaxMiniProfiler_Node_Base &o1, const CMaxMiniProfiler_Node_Base &o2 ) const { assert( &o1 != &o2 ); return ( o1.m_lIndex < o2.m_lIndex ); }; }; friend CCompareIndexes; protected: // acquired at initialization // -------------------------- unsigned long m_lLevel; const char *m_pszTitle; unsigned long m_lIndex; // internal time counting mechanism // -------------------------------- CMaxTimerAbstraction m_taOrigin; CMaxTimerAbstraction m_taDelta; unsigned int m_nCount; #ifdef _DEBUG bool m_bIsCounting; #endif // for final output // ---------------- double m_dDelta; public: // constructor etc. // ---------------- // Note: uses default assignment and copy constructor // Note: it doesn't cost anything to initialize lots of things here - this // is not done within profiling braces MAXPROFNOTHROW CMaxMiniProfiler_Node_Base() : m_lLevel( 0 ) , m_pszTitle( NULL ) , m_lIndex( 0 ) , m_nCount( 0 ) , m_dDelta( 0 ) #ifdef _DEBUG , m_bIsCounting( false ) #endif { }; // chrono // ------ MAXPROFNOTHROW void FStart() { #ifdef _DEBUG assert( !m_bIsCounting ); m_taOrigin.FLog(); m_nCount++; m_bIsCounting = true; #else m_taOrigin.FLog(); m_nCount++; #endif }; MAXPROFNOTHROW void FStop() { CMaxTimerAbstraction destination( 1 ); #ifdef _DEBUG assert( m_bIsCounting ); m_taDelta.FAdd( CMaxTimerAbstraction::oSDifference( destination, m_taOrigin ) ); m_bIsCounting = false; #else m_taDelta.FAdd( CMaxTimerAbstraction::oSDifference( destination, m_taOrigin ) ); #endif }; // access to members // ----------------- MAXPROFNOTHROW unsigned long lFGetLevel() const { return m_lLevel; }; MAXPROFNOTHROW const char *pszFGetTitle() const { return m_pszTitle; }; MAXPROFNOTHROW unsigned long lFGetIndex() const { return m_lIndex; }; MAXPROFNOTHROW const CMaxTimerAbstraction &roFGetOrigin() const { return m_taOrigin; }; MAXPROFNOTHROW CMaxTimerAbstraction &roFGetDelta() { return m_taDelta; }; MAXPROFNOTHROW unsigned int nFGetCount() const { return m_nCount; }; MAXPROFNOTHROW double dFGetDelta() { if ( 0 == m_dDelta ) FComputeDelta(); return m_dDelta; }; // misc. services // -------------- MAXPROFNOTHROW bool bFIsIn( const CMaxMiniProfiler_Node_Base &roNode ) const { // Note: times cannot be equal, so we don't need to worry about that if ( CMaxTimerAbstraction::bSLess( m_taOrigin, roNode.m_taOrigin ) ) { CMaxTimerAbstraction d1 = m_taOrigin; CMaxTimerAbstraction d2 = roNode.m_taOrigin; d1.FAdd( m_taDelta ); d2.FAdd( roNode.m_taDelta ); if ( CMaxTimerAbstraction::bSLess( d2, d1 ) ) return true; } return false; }; MAXPROFNOTHROW void FConditionalRemove( const CMaxMiniProfiler_Node_Base &roNode, const CMaxTimerAbstraction &roBias ) { if ( bFIsIn( roNode ) ) { CMaxTimerAbstraction d = roNode.m_taDelta; d.FAdd( roBias ); m_taDelta.FSubstract( d ); } }; // output to file // -------------- void FOutput( ostream &os ) { // don't output dead (merged) nodes if ( 0 == m_nCount ) return; // output our index os << setw( 10 ) << m_lIndex << ": "; // indent STab( os, m_lLevel ); // output our title os << "@@Name="; if ( NULL != m_pszTitle ) os << m_pszTitle; // output our block count os << " @@Count=" << m_nCount; // output our delta t os << " @@Duration="; SStampDeltaInSeconds( os, dFGetDelta() ); }; void FStampAbsoluteRange( ostream &os ) const { SStampAbsoluteRange( os, m_taOrigin, m_taDelta ); }; protected: // computations at output time (outside of profiling) // -------------------------------------------------- MAXPROFNOTHROW void FComputeDelta() { m_dDelta = m_taDelta.dFInSeconds(); }; public: // mini helpers for facilitated and standardized output of results // --------------------------------------------------------------- static ostream& STab( ostream &os, int level ) { for ( int i = 0; i < level; i++ ) os << " "; return os; }; static ostream& SStampDeltaInSeconds( ostream &os, double delta ) { os << delta << "s"; return os; }; static ostream& SStampAbsoluteRange( ostream &os, const CMaxTimerAbstraction &rO, const CMaxTimerAbstraction &rD ) { os << "[origin" << rO; os << ",duration" << rD << "]"; return os; }; }; /********************************************************************************* /* Classes: /* CMaxMiniProfiler_Node_Standard /* Comments: /********************************************************************************/ class CMaxMiniProfiler_Node_Standard : public CMaxMiniProfiler_Node_Base { public: // comparison by full titles // ------------------------- class CCompareFullTitles { public: MAXPROFNOTHROW bool operator()( const CMaxMiniProfiler_Node_Standard &o1, const CMaxMiniProfiler_Node_Standard &o2 ) const { assert( &o1 != &o2 ); return ( o1.m_oFullTitle < o2.m_oFullTitle ); }; }; friend CCompareFullTitles; // comparison for node merging (a) level, b) full title, c) index) // --------------------------------------------------------------- class CCompareForNodeMerging { public: MAXPROFNOTHROW bool operator()( const CMaxMiniProfiler_Node_Standard &o1, const CMaxMiniProfiler_Node_Standard &o2 ) const { assert( &o1 != &o2 ); if ( o1.m_lLevel < o2.m_lLevel ) return true; else if ( o1.m_lLevel == o2.m_lLevel ) { if ( o1.m_oFullTitle < o2.m_oFullTitle ) return true; else if ( o1.m_oFullTitle == o2.m_oFullTitle ) { if ( o1.m_lIndex < o2.m_lIndex ) return true; } } return false; }; }; friend CCompareForNodeMerging; // for the unique algorithm; modifies the parameters // ------------------------------------------------- class CMergeSimilarNodes { public: MAXPROFNOTHROW bool operator()( CMaxMiniProfiler_Node_Standard &o1, CMaxMiniProfiler_Node_Standard &o2 ) { assert( &o1 != &o2 ); if ( ( o1.m_lLevel == o2.m_lLevel ) && ( o1.m_oFullTitle == o2.m_oFullTitle ) ) { if ( o1.m_nCount > 0 && o2.m_nCount > 0 ) { CMaxMiniProfiler_Node_Standard &kept = ( o1.m_lIndex < o2.m_lIndex ) ? o1 : o2; CMaxMiniProfiler_Node_Standard &thrown = ( o1.m_lIndex < o2.m_lIndex ) ? o2 : o1; kept.m_nCount++; kept.m_taDelta.FAdd( thrown.m_taDelta ); kept.m_dDelta = 0; thrown.m_nCount = 0; thrown.m_taDelta = CMaxTimerAbstraction(); thrown.m_dDelta = 0; } return true; } return false; }; }; friend CMergeSimilarNodes; protected: MMPNBString m_oFullTitle; public: // initialization // -------------- MAXPROFNOTHROW void FInitialize( unsigned long lLevel, const char *pszTitle ) { m_lLevel = lLevel; m_pszTitle = pszTitle; }; MAXPROFNOTHROW void FIndex( unsigned long lIndex ) { m_lIndex = lIndex; }; MAXPROFNOTHROW void FSetFullTitle( const MMPNBString &roFullTitle ) { m_oFullTitle = roFullTitle; }; // access to members // ----------------- MAXPROFNOTHROW const MMPNBString &roFGetFullTitle() const { return m_oFullTitle; }; }; /********************************************************************************* /* Class: /* CMaxMiniProfiler_Node_NoHistory /* Comments: /********************************************************************************/ class CMaxMiniProfiler_Node_NoHistory : public CMaxMiniProfiler_Node_Base { public: // unique key to a profiler node // ----------------------------- class CKey { public: unsigned long m_lLevel; ULONG_PTR m_lCheckSum; const char *m_pszTitle; public: MAXPROFNOTHROW CKey( unsigned long lLevel = 0, const char *pszTitle = NULL, ULONG_PTR lCheckSum = 0 ) : m_lLevel( lLevel ) , m_lCheckSum( lCheckSum ) , m_pszTitle( pszTitle ) { }; }; // comparison of unique keys // ------------------------- class CCompareKeys { public: MAXPROFNOTHROW bool operator()( const CKey &o1, const CKey &o2 ) const { assert( &o1 != &o2 ); if ( o1.m_lLevel < o2.m_lLevel ) return true; else if ( o1.m_lLevel == o2.m_lLevel ) { if ( o1.m_pszTitle < o2.m_pszTitle ) return true; else if ( o1.m_pszTitle == o2.m_pszTitle ) { if ( o1.m_lCheckSum < o2.m_lCheckSum ) return true; } } return false; }; }; protected: CMaxTimerAbstraction m_oInternalOverhead; ULONG_PTR m_lCheckSum; public: MAXPROFNOTHROW CMaxMiniProfiler_Node_NoHistory() : CMaxMiniProfiler_Node_Base() , m_lCheckSum( 0 ) { }; // initialization // -------------- MAXPROFNOTHROW void FInitialize( unsigned long lLevel, const char *pszTitle, unsigned long lIndex, const CMaxTimerAbstraction oInternalOverhead ) { if ( 0 == m_lIndex ) { m_lLevel = lLevel; m_pszTitle = pszTitle; m_lIndex = lIndex; } #ifdef _DEBUG else { assert( lLevel == m_lLevel ); assert( pszTitle == m_pszTitle ); } #endif m_oInternalOverhead.FAdd( oInternalOverhead ); }; MAXPROFNOTHROW void FSetCheckSum( ULONG_PTR lCheckSum ) { m_lCheckSum = lCheckSum; }; // access to members // ----------------- MAXPROFNOTHROW const CMaxTimerAbstraction &roFGetInternalOverhead() const { return m_oInternalOverhead; }; MAXPROFNOTHROW ULONG_PTR lFGetCheckSum() const { return m_lCheckSum; }; }; /********************************************************************************* /* Class: /* CMaxMiniProfiler_Base /* Comments: /********************************************************************************/ class CMaxMiniProfiler_Base { protected: // output file name const char *m_poFileName; // internal info DWORD m_dwThreadId; CTime m_oStartTimeOfProfilings; protected: // Note: the lock in CMaxMultithreadProfiler takes care of protecting // the static data below in multithread mode // default values for initialization static const char *s_poDefaultFileName; static unsigned long s_lHeapBlockSize; // BIAS values static CMaxTimerAbstraction s_oOutOfBraceBiasApproximation; static CMaxTimerAbstraction s_oInOfBraceBiasApproximation; static bool s_bBiasIsKnown; public: // constructor / destructor // ------------------------ CMaxMiniProfiler_Base( const TCHAR * = NULL ) : m_poFileName( s_poDefaultFileName ) , m_dwThreadId( ::GetCurrentThreadId() ) , m_oStartTimeOfProfilings( CTime::GetCurrentTime() ) { }; ~CMaxMiniProfiler_Base() { }; // locking - public interface // -------------------------- void FLockProfiler(){}; void FUnlockProfiler(){}; // bias approximation // ------------------ // Note: the result of this operation is used at output time uniquely bool bFIsBiasKnown() const { return s_bBiasIsKnown; }; protected: // for final output // ---------------- void FOutputEmptySession() { // open the output file ofstream os( m_poFileName, ios::out | ios::ate ); // just stamp a message saying that there was nothing to profile CTime t = CTime::GetCurrentTime(); os << endl; os << "PROFILER INSTANTIATED THE "; os << t.GetYear() << "/" << t.GetMonth() << "/" << t.GetDay() << " BETWEEN "; SStampCTime( os, m_oStartTimeOfProfilings ) << " AND "; SStampCTime( os, t ) << " WAS NOT USED." << endl; }; void FOutputHeaderCore( ostream &os, unsigned long lNumberOfOpenNodes, const CMaxMiniProfiler_Node_Base &roRootNode, unsigned long lTotalNumberOfNodes ) { // stamp the current time in our logfile CTime t = CTime::GetCurrentTime(); os << endl; os << "***************************" << endl; os << "*** @@ProfilingDate=" << t.GetYear() << "/" << t.GetMonth() << "/" << t.GetDay() << endl; os << "*** @@ProfilingStartTime="; SStampCTime( os, m_oStartTimeOfProfilings ) << endl; os << "*** @@ProfilingEndTime="; SStampCTime( os, t ) << endl; os << "*** @@ProfilingRange="; roRootNode.FStampAbsoluteRange( os ); os << endl; if ( 0 != lNumberOfOpenNodes ) os << "*** "<< lNumberOfOpenNodes << " NODES WERE NOT CLOSED BY THE USER" << endl; os << "***************************" << endl; // output the counter's frequency and thread id os << "*** @@CounterFrequency=" << CMaxTimerAbstraction::oSFrequency() << endl; os << "*** @@ThreadId=" << ( unsigned long )m_dwThreadId << endl; // output the profiler's finest possible unit of measurement CMaxTimerAbstraction origin( 1 ), destination( 1 ); CMaxTimerAbstraction delta( CMaxTimerAbstraction::oSDifference( destination, origin ) ); os << "*** @@FinestMeasurement="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, delta.dFInSeconds() ) << "=" << delta << endl; // output the profiler's approximated bias assert( s_bBiasIsKnown ); os << "*** @@OutsideBias="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, s_oOutOfBraceBiasApproximation.dFInSeconds() ) << endl; os << "*** @@InsideBias="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, s_oInOfBraceBiasApproximation.dFInSeconds() ) << endl; // output the total number of blocks os << "*** @@TotalNumberOfBlocks=" << lTotalNumberOfNodes << endl; }; void FOutputMergedSectionHeader( ostream &os ) const { os << "*** @@MergedResults=" << endl; }; bool bFHistoryOutputDisabled() const { return bGIsEnabledEnvVar( MAX_ENV_DISABLE_PROFILING_HISTORY ); }; public: static ostream& SStampCTime( ostream &os, const CTime &roTime ) { os << roTime.GetHour() << ":" << roTime.GetMinute() << ":" << roTime.GetSecond(); return os; }; private: CMaxMiniProfiler_Base( const CMaxMiniProfiler_Base &o ); const CMaxMiniProfiler_Base& operator =( const CMaxMiniProfiler_Base & ); }; /********************************************************************************* /* Functions: /* GOutputProfilings /* lGGetNumberOfProfilingSubNodes /* lGDetermineMaxLevelOfProfilings /* GRemoveInAndOutBiasFromProfilingNodes /* Comments: /* Done this way to avoid virtuals at node level (and to have common /* output code for Standard and NoHistory profilers) /********************************************************************************/ template void GOutputProfilings( ostream &os, std::vector &roProfilings, unsigned long lMaxLevel, double dPrecisionThreshold, bool bOutputAbsoluteTimeRange ) { std::vector::iterator i; std::vector::size_type n; std::vector::size_type> parents( 1 + lMaxLevel ); parents[ 0 ] = 0; for ( i = roProfilings.begin(), n = 0; roProfilings.end() != i; i++, n++ ) { // signal the validity of the node assert( 0 != ( *i ).nFGetCount() ); os << ( ( ( ( *i ).dFGetDelta() / ( *i ).nFGetCount() ) < dPrecisionThreshold ) ? "X" : " " ); // output the node ( *i ).FOutput( os ); // register it as the last parent of its level long currentlevel = ( *i ).lFGetLevel(); parents[ currentlevel ] = n; // output the % for all parents of the node os << " @@PERCENT="; double deltat = ( *i ).dFGetDelta(); for ( long j = currentlevel - 1; j >= 0; j-- ) os << 100.0 * deltat / roProfilings[ parents[ j ] ].dFGetDelta() << "% "; // output the time range in units if ( bOutputAbsoluteTimeRange ) { os << " @@Range="; ( *i ).FStampAbsoluteRange( os ); } // finish output for this node os << endl; } }; template unsigned long lGGetNumberOfProfilingSubNodes( const std::vector &roProfilings, TVectorIterator &roOrg ) { unsigned long level = ( *roOrg ).lFGetLevel(); unsigned long n; TVectorIterator i = roOrg; i++; for ( n = 0; roProfilings.end() != i; i++, n++ ) if ( ( *i ).lFGetLevel() <= level ) break; return n; }; template unsigned long lGDetermineMaxLevelOfProfilings( const std::vector &roProfilings ) { unsigned long l = 0; std::vector::const_iterator i; for ( i = roProfilings.begin(); roProfilings.end() != i; i++ ) if ( ( *i ).lFGetLevel() > l ) l = ( *i ).lFGetLevel(); return l; }; template void GRemoveInAndOutBiasFromProfilingNodes( std::vector &roProfilings, const CMaxTimerAbstraction &roOutOfBraceBiasApproximation, const CMaxTimerAbstraction &roInOfBraceBiasApproximation ) { std::vector::iterator i; unsigned long t, k; for ( i = roProfilings.begin(); roProfilings.end() != i; i++ ) { CMaxTimerAbstraction &rtaDelta = ( *i ).roFGetDelta(); t = ::lGGetNumberOfProfilingSubNodes( roProfilings, i ); for ( k = 0; k < t; k++ ) rtaDelta.FSubstract( roOutOfBraceBiasApproximation ); for ( k = 0; k < t + 1; k++ ) rtaDelta.FSubstract( roInOfBraceBiasApproximation ); } }; /********************************************************************************* /* Class: /* CMaxMiniProfiler_Standard /* Comments: /********************************************************************************/ class CMaxMiniProfiler_Standard : public CMaxMiniProfiler_Base { protected: typedef std::vector MMPNodes; typedef MMPNodes::size_type MMPNodesRandomAccess; typedef std::vector MMPNodesReferences; typedef std::stack MMPStack; typedef MMPStack::size_type MMPStackSizeType; protected: // profiling nodes MMPNodes m_oProfilings; MMPNodesRandomAccess m_oLastNode; // stack for nested blocks MMPStack m_oStack; // heap acquisition timings MMPNodes m_oHeapAcquisitionTimings; public: // constructor / destructor // ------------------------ CMaxMiniProfiler_Standard( const TCHAR *pszSpecificEnabler = NULL ) : CMaxMiniProfiler_Base( pszSpecificEnabler ) , m_oProfilings( 0 ) , m_oLastNode( 0 ) , m_oHeapAcquisitionTimings( 0 ) { FInitDumpingSession(); }; ~CMaxMiniProfiler_Standard() { FDumpSession(); FTermDumpingSession(); }; // dumping results - public interface // ---------------------------------- void FDumpResults( bool bForced = false, bool = true ) { if ( !bForced ) { // can dump results only when all profiling nodes are closed // (except the main one); we don't want to artificially close the nodes // here at this point if ( 1 != m_oStack.size() ) { assert( false ); return; } } // dump FDumpSession(); FTermDumpingSession(); // prepare for next dump FInitDumpingSession(); }; // profiling nodes generation // -------------------------- // Note: FCreateNewNode and FCloseCurrentNode are meant to be as fast as possible; // also, the bracket between FStart and FStop is as small as possible void FCreateNewNode( const char *pszTitle ) { assert( ( 0 == m_oStack.size() ) || ( ::GetCurrentThreadId() == m_dwThreadId ) ); if ( m_oProfilings.size() == m_oLastNode ) FReserveMoreHeap(); // Note: this is time constant m_oStack.push( m_oLastNode ); CMaxMiniProfiler_Node_Standard &roNode = m_oProfilings[ m_oLastNode++ ]; roNode.FInitialize( static_cast(m_oStack.size()) - 1, pszTitle ); roNode.FStart(); }; void FCloseCurrentNode() { assert( ( 1 == m_oStack.size() ) || ( ::GetCurrentThreadId() == m_dwThreadId ) ); // Note: this is time constant if ( m_oStack.size() > 0 ) { m_oProfilings[ m_oStack.top() ].FStop(); m_oStack.pop(); } else assert( false ); }; // bias approximation // ------------------ // Note: the result of this operation is used at output time uniquely void FSetBiasApproximationFrom( unsigned long lBiasSample ) { unsigned int i; assert( !s_bBiasIsKnown ); // Note: this function should be called immediately after having created // 1 BIAS (b) node // and x NOTHINGNESS (N) subnodes (n1 ... nx), // where x = lBiasSample assert( m_oLastNode > 1 + lBiasSample ); // our out of brace bias is equal to (b - (n1 + n2 + ... + nx)) / x s_oOutOfBraceBiasApproximation = m_oProfilings[ m_oLastNode - ( 1 + lBiasSample ) ].roFGetDelta(); for ( i = lBiasSample; i > 0; i-- ) s_oOutOfBraceBiasApproximation.FSubstract( m_oProfilings[ m_oLastNode - i ].roFGetDelta() ); s_oOutOfBraceBiasApproximation.FDivide( lBiasSample ); // our in of brace bias is equal to ((n1 + n2 + ... + nx) - N.x) / x // Note: on purpose, we re-evaluate N as many times as there are samples s_oInOfBraceBiasApproximation = CMaxTimerAbstraction(); CMaxTimerAbstraction delta; for ( i = lBiasSample; i > 0; i-- ) { CMaxTimerAbstraction origin( 1 ), destination( 1 ); delta.FAdd( CMaxTimerAbstraction::oSDifference( destination, origin ) ); s_oInOfBraceBiasApproximation.FAdd( m_oProfilings[ m_oLastNode - i ].roFGetDelta() ); } s_oInOfBraceBiasApproximation.FSubstract( delta ); s_oInOfBraceBiasApproximation.FDivide( lBiasSample ); #if 1 // remove those BIAS and NOTHINGNESS nodes from the profiler's output nodes MMPNodes::iterator iter; MMPNodesRandomAccess n; for ( iter = m_oProfilings.begin(), n = 0; ( m_oProfilings.end() != iter ) && ( n < m_oLastNode - ( 1 + lBiasSample ) ); iter++, n++ ); std::fill( iter, m_oProfilings.end(), CMaxMiniProfiler_Node_Standard() ); m_oLastNode -= ( 1 + lBiasSample ); #endif s_bBiasIsKnown = true; }; protected: // dumping session management // -------------------------- void FInitDumpingSession() { // prepare some heap FReserveMoreHeap(); // put a main node FCreateNewNode( MAX_PROFTAGNODE_TOP ); // verify that we start cleanly assert( 1 == m_oStack.size() ); assert( 0 == m_oStack.top() ); }; void FDumpSession() { MMPStackSizeType lNumberOfOpenNodes; // terminate our main node FCloseCurrentNode(); // make sure all nodes are closed lNumberOfOpenNodes = m_oStack.size(); while ( !m_oStack.empty() ) FCloseCurrentNode(); if ( m_oLastNode > 1 ) { unsigned long lMaxLevel; // final trimming and initializations FTrimProfilings(); FIndexProfilings(); lMaxLevel = ::lGDetermineMaxLevelOfProfilings( m_oProfilings ); FComputeFullTitles( lMaxLevel ); // open the output file ofstream os( m_poFileName, ios::out | ios::ate ); // output the raw profilings FOutputHeader( os, lNumberOfOpenNodes ); if ( !bFHistoryOutputDisabled() ) FOutputProfilings( os, true, lMaxLevel ); // merge nodes and output merged results FMergeProfilings(); FOutputMergedSectionHeader( os ); FOutputProfilings( os, false, lMaxLevel ); } else FOutputEmptySession(); }; void FTermDumpingSession() { while ( !m_oStack.empty() ) m_oStack.pop(); m_oLastNode = 0; m_oProfilings.erase( m_oProfilings.begin(), m_oProfilings.end() ); m_oHeapAcquisitionTimings.erase( m_oHeapAcquisitionTimings.begin(), m_oHeapAcquisitionTimings.end() ); }; protected: // for final output // ---------------- void FOutputHeader( ostream &os, MMPStackSizeType lNumberOfOpenNodes ) { FOutputHeaderCore( os, static_cast(lNumberOfOpenNodes), m_oProfilings[ 0 ], static_cast(m_oLastNode) ); // output the total number of heap allocations double dTotalTimeInAllocations = m_oHeapAcquisitionTimings[ 0 ].dFGetDelta(); for ( MMPNodes::iterator i = m_oHeapAcquisitionTimings.begin(); m_oHeapAcquisitionTimings.end() != i; i++ ) dTotalTimeInAllocations += ( *i ).dFGetDelta(); os << "*** @@TotalNumberOfHeapAllocations=" << static_cast(m_oHeapAcquisitionTimings.size()) << "="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, dTotalTimeInAllocations ) << endl; // output the total profiling overhead double dTotalOverhead = ( ( double )( m_oLastNode - 1.0 ) * s_oOutOfBraceBiasApproximation.dFInSeconds() ) + ( ( double )m_oLastNode * s_oInOfBraceBiasApproximation.dFInSeconds() ); double dTotalOverheadPercent = 100.0 * ( dTotalOverhead / ( dTotalOverhead + m_oProfilings[ 0 ].dFGetDelta() ) ); os << "*** @@TotalProfilerOverhead=" << dTotalOverheadPercent << "%="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, dTotalOverhead ) << endl; // that's it os << "***************************" << endl; os << "*** @@History=" << endl; }; void FOutputProfilings( ostream &os, bool bOutputAbsoluteTimeRange, unsigned long lMaxLevel ) { double dPrecisionThreshold = 2.0 * ( s_oOutOfBraceBiasApproximation.dFInSeconds() + s_oInOfBraceBiasApproximation.dFInSeconds() ); ::GOutputProfilings( os, m_oProfilings, lMaxLevel, dPrecisionThreshold, bOutputAbsoluteTimeRange ); }; // final management of profiling nodes // ----------------------------------- void FTrimProfilings() { MMPNodes::iterator i, j; MMPNodesRandomAccess n; // find the iterator that corresponds to the last node for ( i = m_oProfilings.begin(), n = 0; ( m_oProfilings.end() != i ) && ( n < m_oLastNode ); i++, n++ ); // remove uninitialized nodes m_oProfilings.erase( i, m_oProfilings.end() ); // remove heap allocation timings from affected nodes for ( i = m_oHeapAcquisitionTimings.begin(); m_oHeapAcquisitionTimings.end() != i; i++ ) for ( j = m_oProfilings.begin(); m_oProfilings.end() != j; j++ ) ( *j ).FConditionalRemove( *i, s_oOutOfBraceBiasApproximation ); // remove from nodes the profiling bias ::GRemoveInAndOutBiasFromProfilingNodes( m_oProfilings, s_oOutOfBraceBiasApproximation, s_oInOfBraceBiasApproximation ); }; void FIndexProfilings() { MMPNodes::iterator i; unsigned long n; for ( i = m_oProfilings.begin(), n = 1; m_oProfilings.end() != i; i++, n++ ) ( *i ).FIndex( n ); }; void FComputeFullTitles( unsigned long lMaxLevel ) { MMPNodes::iterator i; MMPNodesRandomAccess j, n; MMPNodesReferences parents( 1 + lMaxLevel ); parents[ 0 ] = 0; for ( i = m_oProfilings.begin(), n = 0; m_oProfilings.end() != i; i++, n++ ) { // register the node as the last parent of its level unsigned long currentlevel = ( *i ).lFGetLevel(); parents[ currentlevel ] = n; // compute the iterated node's full title CMaxMiniProfiler_Node_Base::MMPNBString fulltitle; for ( j = 0; j <= currentlevel; j++ ) fulltitle += CMaxMiniProfiler_Node_Base::MMPNBString( m_oProfilings[ parents[ j ] ].pszFGetTitle() ); ( *i ).FSetFullTitle( fulltitle ); } }; void FMergeProfilings() { MMPNodes::iterator i; // sort by level/name/index std::sort( m_oProfilings.begin(), m_oProfilings.end(), CMaxMiniProfiler_Node_Standard::CCompareForNodeMerging() ); // merge the nodes that have same level/name i = std::unique( m_oProfilings.begin(), m_oProfilings.end(), CMaxMiniProfiler_Node_Standard::CMergeSimilarNodes() ); m_oProfilings.erase( i, m_oProfilings.end() ); // sort by full name std::sort( m_oProfilings.begin(), m_oProfilings.end(), CMaxMiniProfiler_Node_Standard::CCompareFullTitles() ); }; protected: // heap management // --------------- void FReserveMoreHeap() { CMaxMiniProfiler_Node_Standard node; // log the time we used to generate new heap node.FStart(); node.FInitialize( 0, MAX_PROFTAGNODE_HEAPALLOCATION ); // reserve a new chunk of nodes m_oProfilings.reserve( m_oProfilings.size() + s_lHeapBlockSize ); m_oProfilings.insert( m_oProfilings.end(), m_oProfilings.capacity() - m_oProfilings.size(), CMaxMiniProfiler_Node_Standard() ); // that's it m_oHeapAcquisitionTimings.push_back( node ); m_oHeapAcquisitionTimings.back().FStop(); }; private: CMaxMiniProfiler_Standard( const CMaxMiniProfiler_Standard &o ); const CMaxMiniProfiler_Standard& operator =( const CMaxMiniProfiler_Standard & ); }; /********************************************************************************* /* Class: /* CMaxMiniProfiler_NoHistory /* Comments: /* This implementation is targetted for massive amounts of nodes /********************************************************************************/ class CMaxMiniProfiler_NoHistory : public CMaxMiniProfiler_Base { protected: typedef CMaxMiniProfiler_Node_NoHistory::CKey MMPNHKey; typedef CMaxMiniProfiler_Node_NoHistory::CCompareKeys MMPNHKeyCompare; typedef std::map MMPNHNodes; typedef MMPNHNodes::iterator MMPNHNodesIterator; typedef std::vector MMPNHNodesReferences; typedef std::stack MMPNHStack; typedef MMPNHStack::size_type MMPNHStackSizeType; protected: typedef std::vector MMPNHFinalNodes; typedef MMPNHFinalNodes::iterator MMPNHFinalNodesIterator; protected: // profiling nodes MMPNHNodes m_oProfilings; unsigned long m_lLastNode; // stack for nested blocks MMPNHStack m_oStack; public: // constructor / destructor // ------------------------ CMaxMiniProfiler_NoHistory( const TCHAR *pszSpecificEnabler = NULL ) : CMaxMiniProfiler_Base( pszSpecificEnabler ) , m_lLastNode( 0 ) { FInitDumpingSession(); }; ~CMaxMiniProfiler_NoHistory() { FDumpSession(); FTermDumpingSession(); }; // dumping results - public interface // ---------------------------------- void FDumpResults( bool bForced = false, bool = true ) { if ( !bForced ) { // can dump results only when all profiling nodes are closed // (except the main one); we don't want to artificially close the nodes // here at this point if ( 1 != m_oStack.size() ) { assert( false ); return; } } // dump FDumpSession(); FTermDumpingSession(); // prepare for next dump FInitDumpingSession(); }; // profiling nodes generation // -------------------------- // Note: FCreateNewNode and FCloseCurrentNode are meant to be as fast as possible; // also, the bracket between FStart and FStop is as small as possible void FCreateNewNode( const char *pszTitle ) { MMPNHNodesIterator i; assert( ( 0 == m_oStack.size() ) || ( ::GetCurrentThreadId() == m_dwThreadId ) ); // A) this is not time constant // ---------------------------- // Note: therefore we measure how much time we spend here CMaxTimerAbstraction before( 1 ); { // compute the checksum ULONG_PTR lCheckSum = ( ULONG_PTR )pszTitle; if ( !m_oStack.empty() ) lCheckSum += ( *m_oStack.top() ).first.m_lCheckSum; // compute the key MMPNHKey oKey( static_cast(m_oStack.size()), pszTitle, lCheckSum ); // get the corresponding node, if any i = m_oProfilings.find( oKey ); // otherwise, create a new node if ( m_oProfilings.end() == i ) i = m_oProfilings.insert( MMPNHNodes::value_type( oKey, CMaxMiniProfiler_Node_NoHistory() ) ).first; } CMaxTimerAbstraction after( 1 ); // B) this is time constant // ------------------------ // Note: therefore taken care of by bias computation CMaxTimerAbstraction oInternalOverhead( CMaxTimerAbstraction::oSDifference( after, before ) ); m_lLastNode++; ( *i ).second.FInitialize( static_cast(m_oStack.size()), pszTitle, m_lLastNode, oInternalOverhead ); m_oStack.push( i ); ( *i ).second.FStart(); }; void FCloseCurrentNode() { assert( ( 1 == m_oStack.size() ) || ( ::GetCurrentThreadId() == m_dwThreadId ) ); // Note: this is time constant if ( m_oStack.size() > 0 ) { ( *m_oStack.top() ).second.FStop(); m_oStack.pop(); } else assert( false ); }; // bias approximation // ------------------ // Note: the result of this operation is used at output time uniquely void FSetBiasApproximationFrom( unsigned long lBiasSample ) { unsigned int i; MMPNHNodes::iterator j, j1, j2; CMaxTimerAbstraction b, n, ib; assert( !s_bBiasIsKnown ); // Note: this function should be called immediately after having created // 1 BIAS (b) node // and x NOTHINGNESS (N) subnodes (n1 ... nx), // where x = lBiasSample assert( m_lLastNode > 1 + lBiasSample ); // find bias and nothingness nodes // Note: here we search by name, because it's not time critical and // we don't know the checksum CMaxMiniProfiler_Node_Base::MMPNBString id_bias( MAX_PROFTAGNODE_BIAS ); CMaxMiniProfiler_Node_Base::MMPNBString id_nothingness( MAX_PROFTAGNODE_NOTHINGNESS ); char cDone = 0; for ( j = m_oProfilings.begin(); ( m_oProfilings.end() != j ) && ( ( 1 | 2 ) != cDone ); j++ ) { CMaxMiniProfiler_Node_Base::MMPNBString id_iterated( ( *j ).second.pszFGetTitle() ); if ( id_iterated == id_bias ) { assert( !( cDone & 1 ) ); b = ( *j ).second.roFGetDelta(); j1 = j; cDone |= 1; } else if ( id_iterated == id_nothingness ) { assert( !( cDone & 2 ) ); n = ( *j ).second.roFGetDelta(); ib = ( *j ).second.roFGetInternalOverhead(); j2 = j; cDone |= 2; } } assert( ( 1 | 2 ) == cDone ); if ( cDone & 1 ) m_oProfilings.erase( j1 ); if ( cDone & 2 ) m_oProfilings.erase( j2 ); // our out of brace bias is equal to (b - (n1 + n2 + ... + nx) - (ib1 + ib2 + ... + ibx)) / x // Note: ib is the internal bias (or overhead), and is taken care of separately s_oOutOfBraceBiasApproximation = b; s_oOutOfBraceBiasApproximation.FSubstract( n ); s_oOutOfBraceBiasApproximation.FSubstract( ib ); s_oOutOfBraceBiasApproximation.FDivide( lBiasSample ); // our in of brace bias is equal to ((n1 + n2 + ... + nx) - N.x) / x // Note: on purpose, we re-evaluate N as many times as there are samples CMaxTimerAbstraction delta; for ( i = lBiasSample; i > 0; i-- ) { CMaxTimerAbstraction origin( 1 ), destination( 1 ); delta.FAdd( CMaxTimerAbstraction::oSDifference( destination, origin ) ); } s_oInOfBraceBiasApproximation = n; s_oInOfBraceBiasApproximation.FSubstract( delta ); s_oInOfBraceBiasApproximation.FDivide( lBiasSample ); s_bBiasIsKnown = true; }; protected: // dumping session management // -------------------------- void FInitDumpingSession() { // put a main node FCreateNewNode( MAX_PROFTAGNODE_TOP ); // verify that we start cleanly assert( 1 == m_oStack.size() ); }; void FDumpSession() { MMPNHStackSizeType lNumberOfOpenNodes; MMPNHFinalNodes oFinalNodes; unsigned long lMaxLevel; // terminate our main node FCloseCurrentNode(); // make sure all nodes are closed lNumberOfOpenNodes = m_oStack.size(); while ( !m_oStack.empty() ) FCloseCurrentNode(); // get the final list of nodes, sorted by index FGetFinalNodes( oFinalNodes ); if ( oFinalNodes.size() > 1 ) { // final trimming and initializations ::GRemoveInAndOutBiasFromProfilingNodes( oFinalNodes, s_oOutOfBraceBiasApproximation, s_oInOfBraceBiasApproximation ); lMaxLevel = ::lGDetermineMaxLevelOfProfilings( oFinalNodes ); CMaxTimerAbstraction oTotalInternalOverhead = oFRemoveInternalOverheadFromFinalNodes( oFinalNodes ); // open the output file ofstream os( m_poFileName, ios::out | ios::ate ); // output the raw profilings FOutputHeader( oFinalNodes, os, lNumberOfOpenNodes, oTotalInternalOverhead ); FOutputFinalNodes( oFinalNodes, os, lMaxLevel ); } else FOutputEmptySession(); }; void FTermDumpingSession() { while ( !m_oStack.empty() ) m_oStack.pop(); m_oProfilings.erase( m_oProfilings.begin(), m_oProfilings.end() ); }; protected: // for final output // ---------------- void FOutputHeader( MMPNHFinalNodes &roFinalNodes, ostream &os, MMPNHStackSizeType lNumberOfOpenNodes, const CMaxTimerAbstraction &roTotalInternalOverhead ) { FOutputHeaderCore( os, static_cast(lNumberOfOpenNodes), roFinalNodes[ 0 ], m_lLastNode ); // output the total profiling overhead double dTotalOverhead = ( ( double )( m_lLastNode - 1.0 ) * s_oOutOfBraceBiasApproximation.dFInSeconds() ) + ( ( double )m_lLastNode * s_oInOfBraceBiasApproximation.dFInSeconds() ) + roTotalInternalOverhead.dFInSeconds(); double dTotalOverheadPercent = 100.0 * ( dTotalOverhead / ( dTotalOverhead + roFinalNodes[ 0 ].dFGetDelta() ) ); os << "*** @@TotalProfilerOverhead=" << dTotalOverheadPercent << "%="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, dTotalOverhead ) << endl; // that's it os << "***************************" << endl; FOutputMergedSectionHeader( os ); }; // final management of profiling nodes // ----------------------------------- void FGetFinalNodes( MMPNHFinalNodes &roFinalNodes ) { assert( !m_oProfilings.empty() ); // copy the map of profiling nodes into a simple vector for ( MMPNHNodes::iterator i = m_oProfilings.begin(); m_oProfilings.end() != i; i++ ) { ( *i ).second.FSetCheckSum( ( *i ).first.m_lCheckSum ); roFinalNodes.push_back( ( *i ).second ); } // sort the vector by nodes indexes std::sort( roFinalNodes.begin(), roFinalNodes.end(), CMaxMiniProfiler_Node_Base::CCompareIndexes() ); // reparent the lost nodes // Note: sorting by nodes indexes is not good enough when the profiled code has some // conditional branches; suppose a new node appears in a branch, its index might // be greater than nodes that don't belong to that branch; therefore reparenting // those lost nodes is necessary // Note: top node doesn't have a parent, so skip it // Note: this algorithm is O(n2) right now, and could be improved, but since it is // executed at output time only, I don't care MMPNHFinalNodesIterator j = roFinalNodes.begin(); j++; while ( roFinalNodes.end() != j ) { const MMPNHFinalNodesIterator oldj = j; bool bWrongParent = false; unsigned long lTargetLevel = ( *j ).lFGetLevel() - 1; ULONG_PTR lTargetCheckSum = ( *j ).lFGetCheckSum() - ( ULONG_PTR )( *j ).pszFGetTitle(); // find the real parent of j (must appear before j in the sorted vector) for ( MMPNHFinalNodesIterator k = j; roFinalNodes.end() != k; k-- ) { unsigned long lIteratedLevel = ( *k ).lFGetLevel(); // the real parent must have a level equal to lTargetLevel if ( lIteratedLevel != lTargetLevel ) { // maybe j didn't even have an immediate wrong parent if ( lIteratedLevel < lTargetLevel ) bWrongParent = true; continue; } // the parent must have a checksum equal to lTargetCheckSum, // otherwise it is a wrong parent if ( ( *k ).lFGetCheckSum() != lTargetCheckSum ) bWrongParent = true; // we found the real parent else { // if no wrong parent was encountered, nothing to do if ( !bWrongParent ) { j++; break; } // otherwise, we must move the node below its real parent else { CMaxMiniProfiler_Node_NoHistory nodecopy = *j; j++; k++; roFinalNodes.erase( oldj ); roFinalNodes.insert( k, nodecopy ); bWrongParent = false; break; } } } assert( !bWrongParent ); assert( oldj != j ); } } CMaxTimerAbstraction oFRemoveInternalOverheadFromFinalNodes( MMPNHFinalNodes &roFinalNodes ) { CMaxTimerAbstraction oTotalOverhead; MMPNHFinalNodes::iterator i; std::vector parents; std::vector::iterator j; unsigned long l, s; for ( i = roFinalNodes.begin(); roFinalNodes.end() != i; i++ ) { // get the current node level (l) and stack of parents size (s) l = ( *i ).lFGetLevel(); s = static_cast(parents.size()); // get the iterated node's internal overhead const CMaxTimerAbstraction &roOverhead = ( *i ).roFGetInternalOverhead(); oTotalOverhead.FAdd( roOverhead ); // update the stack of parents if ( s > 0 ) { while ( s > l ) { parents.pop_back(); s--; } } assert( l == s ); // remove internal overhead from all parents for ( j = parents.begin(); parents.end() != j; j++ ) { assert( ( *j ) != i ); CMaxTimerAbstraction &rtaDelta = ( *( *j ) ).roFGetDelta(); rtaDelta.FSubstract( roOverhead ); } // insert the current node in the stack of parents parents.push_back( i ); } return oTotalOverhead; }; void FOutputFinalNodes( MMPNHFinalNodes &roFinalNodes, ostream &os, unsigned long lMaxLevel ) { double dPrecisionThreshold = 2.0 * ( s_oOutOfBraceBiasApproximation.dFInSeconds() + s_oInOfBraceBiasApproximation.dFInSeconds() ); ::GOutputProfilings( os, roFinalNodes, lMaxLevel, dPrecisionThreshold, false ); }; private: CMaxMiniProfiler_NoHistory( const CMaxMiniProfiler_NoHistory &o ); const CMaxMiniProfiler_NoHistory& operator =( const CMaxMiniProfiler_NoHistory & ); }; /********************************************************************************* /* Class: /* CMaxMultithreadProfiler /* Comments: /* Instantiates and manages one CMaxMiniProfiler per calling thread /********************************************************************************/ template class CMaxMultithreadProfiler { protected: typedef std::less MTPThreadIdsCompare; typedef std::map MTPMap; protected: class __CMaxCriticalSection { protected: CRITICAL_SECTION m_oNTCriticalSection; public: __CMaxCriticalSection(){ ::InitializeCriticalSection( &m_oNTCriticalSection ); }; ~__CMaxCriticalSection(){ ::DeleteCriticalSection( &m_oNTCriticalSection ); }; bool Lock() const { ::EnterCriticalSection( &( ( __CMaxCriticalSection * )this )->m_oNTCriticalSection ); return true; }; bool Unlock() const { ::LeaveCriticalSection( &( ( __CMaxCriticalSection * )this )->m_oNTCriticalSection ); return true; }; operator CRITICAL_SECTION*() const { return ( CRITICAL_SECTION* )&m_oNTCriticalSection; }; }; protected: MTPMap m_oProfilers; __CMaxCriticalSection m_oLockProfilers; public: CMaxMultithreadProfiler( const TCHAR * = NULL ) { m_oProfilers[ ::GetCurrentThreadId() ] = new TMiniProfiler(); }; ~CMaxMultithreadProfiler() { if ( !m_oProfilers.empty() ) FFlushProfilers(); }; void FLockProfiler() { m_oLockProfilers.Lock(); }; void FUnlockProfiler() { m_oLockProfilers.Unlock(); }; void FDumpResults( bool bForced = false, bool bCurrentThreadOnly = true ) { m_oLockProfilers.Lock(); { DWORD id = ::GetCurrentThreadId(); MTPMap::iterator i; if ( m_oProfilers.empty() ) { m_oLockProfilers.Unlock(); return; } for ( i = m_oProfilers.begin(); m_oProfilers.end() != i; i++ ) if ( !bCurrentThreadOnly || ( ( *i ).first == id ) ) ( *i ).second->FDumpResults( bForced ); if ( bForced ) FFlushProfilers(); } m_oLockProfilers.Unlock(); }; void FCreateNewNode( const char *pszTitle ) { m_oLockProfilers.Lock(); { DWORD id = ::GetCurrentThreadId(); MTPMap::iterator i = m_oProfilers.find( id ); if ( m_oProfilers.end() != i ) ( *i ).second->FCreateNewNode( pszTitle ); else { TMiniProfiler *pNewProfiler = new TMiniProfiler(); m_oProfilers[ id ] = pNewProfiler; pNewProfiler->FCreateNewNode( pszTitle ); } } m_oLockProfilers.Unlock(); }; void FCloseCurrentNode() { m_oLockProfilers.Lock(); { DWORD id = ::GetCurrentThreadId(); MTPMap::iterator i = m_oProfilers.find( id ); assert( m_oProfilers.end() != i ); ( *i ).second->FCloseCurrentNode(); } m_oLockProfilers.Unlock(); }; bool bFIsBiasKnown() const { bool b; m_oLockProfilers.Lock(); assert( !m_oProfilers.empty() ); b = ( *m_oProfilers.begin() ).second->bFIsBiasKnown(); m_oLockProfilers.Unlock(); return b; }; void FSetBiasApproximationFrom( unsigned long lBiasSample ) { m_oLockProfilers.Lock(); assert( !m_oProfilers.empty() ); ( *m_oProfilers.begin() ).second->FSetBiasApproximationFrom( lBiasSample ); m_oLockProfilers.Unlock(); }; protected: void FFlushProfilers() { m_oLockProfilers.Lock(); assert( !m_oProfilers.empty() ); for ( MTPMap::iterator i = m_oProfilers.begin(); m_oProfilers.end() != i; i++ ) delete ( *i ).second; m_oProfilers.erase( m_oProfilers.begin(), m_oProfilers.end() ); m_oLockProfilers.Unlock(); }; private: CMaxMultithreadProfiler( const CMaxMultithreadProfiler &o ); const CMaxMultithreadProfiler& operator =( const CMaxMultithreadProfiler & ); }; #endif // } #ifdef MAX_PROFILING_ENABLED_DLL // { /********************************************************************************* /* Class: /* CMaxProfilingDLLWrapper /* Comments: /* For simplified use through the macros defined above /********************************************************************************/ class CMaxProfilingDLLWrapper { protected: class __CMaxLoadLibrary { protected: HINSTANCE m_hLibrary; public: __CMaxLoadLibrary( LPCTSTR pszLibraryFileName ) : m_hLibrary( NULL ) { if ( NULL != pszLibraryFileName ) m_hLibrary = ::LoadLibrary( pszLibraryFileName ); }; ~__CMaxLoadLibrary() { if ( NULL != m_hLibrary ) ::FreeLibrary( m_hLibrary ); }; operator HINSTANCE() const { return m_hLibrary; }; }; protected: __CMaxLoadLibrary m_oLibrary; protected: void ( *m_pfn_LockProfiler )(); void ( *m_pfn_UnlockProfiler )(); void ( *m_pfn_DumpResults )( bool, bool ); void ( *m_pfn_CreateNewNode )( const char * ); void ( *m_pfn_CloseCurrentNode )(); bool ( *m_pfn_IsBiasKnown )(); void ( *m_pfn_SetBiasApproximationFrom )( unsigned long ); protected: static void SLockProfiler_Bogus(){}; static void SUnlockProfiler_Bogus(){}; static void SDumpResults_Bogus( bool, bool ){}; static void SCreateNewNode_Bogus( const char * ){}; static void SCloseCurrentNode_Bogus(){}; static bool bSIsBiasKnown_Bogus(){ return true; }; static void SSetBiasApproximationFrom_Bogus( unsigned long ){}; public: CMaxProfilingDLLWrapper( const TCHAR *pszSpecificEnabler = NULL ) #ifndef unix : m_oLibrary( _T( "s:\\ds\\util\\maxprof.dll" ) ) #else : m_oLibrary( NULL ) #endif , m_pfn_LockProfiler( NULL ) , m_pfn_UnlockProfiler( NULL ) , m_pfn_DumpResults( NULL ) , m_pfn_CreateNewNode( NULL ) , m_pfn_CloseCurrentNode( NULL ) , m_pfn_IsBiasKnown( NULL ) , m_pfn_SetBiasApproximationFrom( NULL ) { // if the profiler is enabled, get the dll's entry points for profiling // (if possible) if ( bFProfilerEnabled( pszSpecificEnabler ) && ( NULL != ( HINSTANCE )m_oLibrary ) ) { m_pfn_LockProfiler = ( void ( * )() )::GetProcAddress( m_oLibrary, "LockProfiler" ); assert( NULL != m_pfn_LockProfiler ); m_pfn_UnlockProfiler = ( void ( * )() )::GetProcAddress( m_oLibrary, "UnlockProfiler" ); assert( NULL != m_pfn_UnlockProfiler ); m_pfn_DumpResults = ( void ( * )( bool, bool ) )::GetProcAddress( m_oLibrary, "DumpResults" ); assert( NULL != m_pfn_DumpResults ); m_pfn_CreateNewNode = ( void ( * )( const char * ) )::GetProcAddress( m_oLibrary, "CreateNewNode" ); assert( NULL != m_pfn_CreateNewNode ); m_pfn_CloseCurrentNode = ( void ( * )() )::GetProcAddress( m_oLibrary, "CloseCurrentNode" ); assert( NULL != m_pfn_CloseCurrentNode ); m_pfn_IsBiasKnown = ( bool ( * )() )::GetProcAddress( m_oLibrary, "IsBiasKnown" ); assert( NULL != m_pfn_IsBiasKnown ); m_pfn_SetBiasApproximationFrom = ( void ( * )( unsigned long ) )::GetProcAddress( m_oLibrary, "SetBiasApproximationFrom" ); assert( NULL != m_pfn_SetBiasApproximationFrom ); } // otherwise, create bogus entry points // Note: this technique is preferred to using "if"s on each call, so this // switch does not affect the profiling mode at all else { m_pfn_LockProfiler = SLockProfiler_Bogus; m_pfn_UnlockProfiler = SUnlockProfiler_Bogus; m_pfn_DumpResults = SDumpResults_Bogus; m_pfn_CreateNewNode = SCreateNewNode_Bogus; m_pfn_CloseCurrentNode = SCloseCurrentNode_Bogus; m_pfn_IsBiasKnown = bSIsBiasKnown_Bogus; m_pfn_SetBiasApproximationFrom = SSetBiasApproximationFrom_Bogus; } }; // Note: this is the easiest way to avoid a severe bug in the DLL version of the // profiler; basically, if a client DLL detaches the profiled process, the // "titles" maintained by address by the profiling nodes become invalid, and // can no longer be dereferenced; therefore, to avoid this problem, I make // sure all profiling nodes are dumped when a client DLL detaches. This // may affect results in some circumstances, but should be OK in most cases ~CMaxProfilingDLLWrapper() { FDumpResults( true, false ); }; void FLockProfiler() { ( *m_pfn_LockProfiler )(); }; void FUnlockProfiler() { ( *m_pfn_UnlockProfiler )(); }; void FDumpResults( bool bForced = false, bool bCurrentThreadOnly = true ) { ( *m_pfn_DumpResults )( bForced, bCurrentThreadOnly ); }; void FCreateNewNode( const char *pszTitle ) { ( *m_pfn_CreateNewNode )( pszTitle ); }; void FCloseCurrentNode() { ( *m_pfn_CloseCurrentNode )(); }; bool bFIsBiasKnown() const { return ( *m_pfn_IsBiasKnown )(); }; void FSetBiasApproximationFrom( unsigned long lBiasSample ) { ( *m_pfn_SetBiasApproximationFrom )( lBiasSample ); }; protected: #ifdef MAX_PROFILING_CONDITIONAL bool bFProfilerEnabled( const TCHAR *pszSpecificEnabler ) const { // Note: the global enabler allows you to enable/disable // all "subsystems" at once // Note: the specific enabler allows you to enable/disable // specific "subsystems", given that the global // enabler is set return ( bGIsEnabledEnvVar( MAX_ENV_ENABLE_PROFILING, MAX_ENV_ALL ) || ( bGIsEnabledEnvVar( pszSpecificEnabler ) && bGIsEnabledEnvVar( MAX_ENV_ENABLE_PROFILING ) ) ); } #else bool bFProfilerEnabled( const TCHAR * ) const { return true; } #endif }; #endif // } #if defined MAX_PROFILING_ENABLED || defined MAX_PROFILING_ENABLED_DLL // { //#ifdef _DEBUG // #pragma message( "MAXPROFILER Warning: beware of profilings generated with a DEBUG build." ) //#endif /********************************************************************************* /* Class: /* CMaxProfilingObject /* Comments: /* For simplified use through the macros defined above. The typedef /* allows easy substitution between multi-threaded (default) and /* single-threaded profilers /********************************************************************************/ class CMaxProfilingObject { public: #ifdef MAX_PROFILING_ENABLED_DLL typedef CMaxProfilingDLLWrapper MPOProfiler; #else typedef CMaxMultithreadProfiler MPOProfiler; #endif protected: static MPOProfiler s_oProfiler; protected: class __CBiasApproximation { public: __CBiasApproximation() { const unsigned long lBiasSample = 20; // if bias has already been computed once, do nothing if ( CMaxProfilingObject::s_oProfiler.bFIsBiasKnown() ) return; // compute bias through the used profiler CMaxProfilingObject::s_oProfiler.FLockProfiler(); { CMaxProfilingObject::SCreateNewNode( MAX_PROFTAGNODE_BIAS ); for ( int i = 0; i < lBiasSample; i++ ) { CMaxProfilingObject::SCreateNewNode( MAX_PROFTAGNODE_NOTHINGNESS ); CMaxProfilingObject::SCloseCurrentNode(); } CMaxProfilingObject::SCloseCurrentNode(); CMaxProfilingObject::s_oProfiler.FSetBiasApproximationFrom( lBiasSample ); } CMaxProfilingObject::s_oProfiler.FUnlockProfiler(); }; }; friend __CBiasApproximation; protected: static __CBiasApproximation s_oBiasApproximation; public: static void SDumpResults( bool bForced = false, bool bCurrentThreadOnly = true ) { s_oProfiler.FDumpResults( bForced, bCurrentThreadOnly ); }; static void SCreateNewNode( const char *pszTitle ) { s_oProfiler.FCreateNewNode( pszTitle ); }; static void SCloseCurrentNode() { s_oProfiler.FCloseCurrentNode(); }; }; #endif // } #ifndef MAX_PROFILING_DLL_IMPLEMENTATION // { /********************************************************************************* /* Class: /* CMaxProfilingBlockWrapper /* Comments: /* As a substitute to the macros (Ray's request). Hoping that those inlines /* disappear completely when profiling is turned off. The alternative /* would have been to define a new set of macros taking an additional /* parameter (a unique name for each instance of the wrapper within the /* same scope) /* Note: /* I use nothrow here, but the current implementations don't guaranty /* that no exception will be thrown /********************************************************************************/ class CMaxProfilingBlockWrapper { public: MAXPROFNOTHROW CMaxProfilingBlockWrapper( const char *pszTitle ) { BEGIN_PROFILING_BLOCK( pszTitle ); }; MAXPROFNOTHROW ~CMaxProfilingBlockWrapper() { END_PROFILING_BLOCK; }; public: MAXPROFNOTHROW static void SDump() { DUMP_PROFILING_RESULTS; }; }; #endif // } /********************************************************************************* /* Comments: /* Here is the code used to generate maxprof.dll /********************************************************************************* #define MAX_PROFILING_DLL_IMPLEMENTATION #include #include #pragma warning( disable : 4786 ) __MAX_MINIPROFILER_IMPLEMENTATION typedef CMaxMultithreadProfiler GDllProfiler_Type1; typedef CMaxMultithreadProfiler GDllProfiler_Type2; GDllProfiler_Type1 g_oProfiler; __declspec(dllexport) void LockProfiler() { g_oProfiler.FLockProfiler(); } __declspec(dllexport) void UnlockProfiler() { g_oProfiler.FUnlockProfiler(); } __declspec(dllexport) void DumpResults( bool bForced, bool bCurrentThreadOnly ) { g_oProfiler.FDumpResults( bForced, bCurrentThreadOnly ); } __declspec(dllexport) void CreateNewNode( const char *pszTitle ) { g_oProfiler.FCreateNewNode( pszTitle ); } __declspec(dllexport) void CloseCurrentNode() { g_oProfiler.FCloseCurrentNode(); } __declspec(dllexport) bool IsBiasKnown() { return g_oProfiler.bFIsBiasKnown(); } __declspec(dllexport) void SetBiasApproximationFrom( unsigned long lBiasSample ) { g_oProfiler.FSetBiasApproximationFrom( lBiasSample ); } #pragma warning( default : 4786 ) /********************************************************************************* /* Comments: /* Here is the DEF file used to generate maxprof.dll /********************************************************************************* EXPORTS LockProfiler @1 UnlockProfiler @2 DumpResults @3 CreateNewNode @4 CloseCurrentNode @5 IsBiasKnown @6 SetBiasApproximationFrom @7 /********************************************************************************/ // Note: I don't reenable C4786, and I know it... #endif // }