//========= Copyright © 1996-2004, Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= #include "stdafx.h" #ifdef _WIN32 #include "winperfcounter.h" #ifdef _WIN32 #include #include #endif #ifdef GC #include "gclogger.h" using namespace GCSDK; #endif //----------------------------------------------------------------------------- // Purpose: threaded reading & access of PDH data //----------------------------------------------------------------------------- class CWinPerfCounterReadThread : public CThread { public: CWinPerfCounterReadThread( CWinPerfCountersPriv *pPrivData ) : m_pPrivData( pPrivData ), m_pPerfCounterMap( NULL ) { } void SetPerfCounterMap( const PerfCounter_t *pPerfCounterMap ) { m_pPerfCounterMap = pPerfCounterMap; } virtual int Run(); private: CWinPerfCountersPriv *m_pPrivData; const PerfCounter_t *m_pPerfCounterMap; }; //----------------------------------------------------------------------------- // Purpose: Pimpl pattern - Hides all the Pdh* stuff from the global include //----------------------------------------------------------------------------- class CWinPerfCountersPriv { #pragma warning(suppress : 4355) // warning C4355: 'this' : used in base member initializer list CWinPerfCountersPriv( int nCounters ) : m_threadQueryPerfCounters( this ) { m_ppdhHCounters = NULL; #ifdef _WIN32 m_ppdhHCounters = new PDH_HCOUNTER[nCounters]; #endif } ~CWinPerfCountersPriv() { delete[] m_ppdhHCounters; } #ifdef DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: Run a global validation pass on all of our data structures and memory // allocations. // Input: validator - Our global validator object // pchName - Our name (typically a member var in our container) //----------------------------------------------------------------------------- void CWinPerfCountersPriv::Validate( CValidator &validator, const char *pchName ) { VALIDATE_SCOPE(); AUTO_LOCK( m_mutexDataAccess ); if ( m_ppdhHCounters ) validator.ClaimArrayMemory( m_ppdhHCounters ); ValidateObj( m_vecData ); ValidateObj( m_vecDataInFlight ); } #endif // DBGFLAG_VALIDATE friend class CWinPerfCounters; friend class CWinPerfCounterReadThread; protected: #ifdef _WIN32 PDH_HQUERY m_pdhHQuery; PDH_HCOUNTER *m_ppdhHCounters; CUtlVector m_vecData, m_vecDataInFlight; CThreadMutex m_mutexDataAccess; CThreadEvent m_eventStartQuery; volatile bool m_bThreadRunning; CWinPerfCounterReadThread m_threadQueryPerfCounters; #endif }; //----------------------------------------------------------------------------- // Purpose: c'tor //----------------------------------------------------------------------------- CWinPerfCounters::CWinPerfCounters( ) { m_pPrivData = NULL; m_bInited = false; } //----------------------------------------------------------------------------- // Purpose: d'tor //----------------------------------------------------------------------------- CWinPerfCounters::~CWinPerfCounters() { Shutdown(); } //----------------------------------------------------------------------------- // Purpose: registers the interesting counters via the Pdh (perf data handling) API //----------------------------------------------------------------------------- bool CWinPerfCounters::Init( const PerfCounter_t *counterMap, int nCounters ) { VPROF_BUDGET( "CWinPerfCounters::Init", VPROF_BUDGETGROUP_STEAM ); Assert( nCounters > 0 ); Assert( counterMap ); Assert( !m_pPrivData ); bool bRet = false; #ifdef _WIN32 m_pPerfCounterMap = counterMap; m_nCounters = nCounters; m_pPrivData = new CWinPerfCountersPriv( nCounters ); AUTO_LOCK( m_pPrivData->m_mutexDataAccess ); m_pPrivData->m_threadQueryPerfCounters.SetPerfCounterMap( counterMap ); m_pPrivData->m_vecData.SetSize( m_nCounters ); m_pPrivData->m_vecDataInFlight.SetSize( m_nCounters ); m_pPrivData->m_bThreadRunning = true; bRet = true; PDH_STATUS pdhStat = PdhOpenQuery( NULL, NULL, &m_pPrivData->m_pdhHQuery ); AssertMsg( ERROR_SUCCESS == pdhStat, "CWinPerfCounters::Init(): Failed to initalize performance data gathering.\n" ); for ( int i = 0; i < m_nCounters; ++i ) { pdhStat = PdhAddCounter( m_pPrivData->m_pdhHQuery, m_pPerfCounterMap[i].m_rgchPerfObject, NULL, &(m_pPrivData->m_ppdhHCounters[i]) ); if ( ERROR_SUCCESS != pdhStat && NULL != m_pPerfCounterMap[i].m_rgchPerfObjectAlternative ) { // first counter name didn't work; try our backup if we have one pdhStat = PdhAddCounter( m_pPrivData->m_pdhHQuery, m_pPerfCounterMap[i].m_rgchPerfObjectAlternative, NULL, &(m_pPrivData->m_ppdhHCounters[i]) ); } m_pPrivData->m_vecData[i] = 0; m_pPrivData->m_vecDataInFlight[i] = 0; if ( ERROR_SUCCESS != pdhStat ) { char rgchCounterNames[ 1024 ]; if ( NULL != m_pPerfCounterMap[ i ].m_rgchPerfObjectAlternative ) { Q_snprintf( rgchCounterNames, Q_ARRAYSIZE( rgchCounterNames ), "%s or %s", m_pPerfCounterMap[ i ].m_rgchPerfObject, m_pPerfCounterMap[ i ].m_rgchPerfObjectAlternative ); } else { Q_snprintf( rgchCounterNames, Q_ARRAYSIZE( rgchCounterNames ), "%s", m_pPerfCounterMap[ i ].m_rgchPerfObject ); } if ( m_pPerfCounterMap[i].m_bAssertOnFailure ) AssertMsg1( false, "CWinPerfCounters::Init():Failed to add %s to performance monitoring.", rgchCounterNames ); else { #ifdef GC EmitError( SPEW_GC, "CWinPerfCounters::Init():Failed to add %s to performance monitoring.\n", rgchCounterNames ); #else Warning( "CWinPerfCounters::Init():Failed to add %s to performance monitoring.\n", rgchCounterNames ); #endif pdhStat = ERROR_SUCCESS; } } bRet &= ( ERROR_SUCCESS == pdhStat ); } m_bInited = true; Assert( m_pPrivData->m_vecDataInFlight.Count() == m_nCounters ); Assert( m_pPrivData->m_vecData.Count() == m_nCounters ); // take a sample immediately TakeSample(); #endif return bRet; } //----------------------------------------------------------------------------- // Purpose: Performs the read of the perf counters being monitored //----------------------------------------------------------------------------- bool CWinPerfCounters::TakeSample() { #ifdef _WIN32 if ( m_bInited ) // this gets called before InitOnServerRunning { if ( !m_pPrivData->m_threadQueryPerfCounters.IsAlive() ) { m_pPrivData->m_threadQueryPerfCounters.Start(); } m_pPrivData->m_eventStartQuery.Set(); return true; } #endif return false; } //----------------------------------------------------------------------------- // Purpose: pokes the measured values into the provided stats struct //----------------------------------------------------------------------------- bool CWinPerfCounters::WriteStats( void *pStatsStruct ) { bool bRet = false; #ifdef _WIN32 AUTO_LOCK( m_pPrivData->m_mutexDataAccess ); // pull out our already retrieved data for ( int i = 0; i < m_nCounters; ++i ) { //Get destination byte *dst = (byte *) pStatsStruct + m_pPerfCounterMap[i].m_statsOffset; *(int32 *)dst = m_pPrivData->m_vecData[i]; //perform rollup if necessary if ( m_pPerfCounterMap[i].m_bCounterRequiresRollup == true && ( i + 1 < m_nCounters ) ) { while( m_pPerfCounterMap[i].m_rgchPerfObject == m_pPerfCounterMap[i+1].m_rgchPerfObject ) { ++i; *(int32 *)dst += m_pPrivData->m_vecData[i]; if ( m_nCounters == i ) break; } } } bRet = true; #endif // _WIN32 return bRet; } //----------------------------------------------------------------------------- // Purpose: collects data on a thread //----------------------------------------------------------------------------- int CWinPerfCounterReadThread::Run() { while ( m_pPrivData->m_bThreadRunning ) { // wait to be signalled m_pPrivData->m_eventStartQuery.Wait(); if ( !m_pPrivData->m_bThreadRunning ) break; PDH_STATUS pdhStat = PdhCollectQueryData( m_pPrivData->m_pdhHQuery ); // pull out all the stats PDH_FMT_COUNTERVALUE pdhCounterVal; for ( int i = 0; i < m_pPrivData->m_vecDataInFlight.Count(); ++i ) { DWORD fmt = PDH_FMT_LONG; switch ( m_pPerfCounterMap[i].m_eFmt ) { case k_EFormatInt: fmt = PDH_FMT_LONG; break; case k_EFormatFloat: fmt = PDH_FMT_DOUBLE; break; default: Assert(false); } pdhStat = PdhGetFormattedCounterValue( m_pPrivData->m_ppdhHCounters[i], fmt, NULL, &pdhCounterVal ); byte *dst = (byte *)&m_pPrivData->m_vecDataInFlight[i]; switch ( m_pPerfCounterMap[i].m_eFmt ) { case k_EFormatInt: *(int32 *)dst = ERROR_SUCCESS == pdhStat ? pdhCounterVal.longValue : (int) m_pPerfCounterMap[i].m_fUnsetValue; break; case k_EFormatFloat: *(float *)dst = ERROR_SUCCESS == pdhStat ? (float) pdhCounterVal.doubleValue : (float) m_pPerfCounterMap[i].m_fUnsetValue; break; default: Assert(false); } } // swap in the new data AUTO_LOCK( m_pPrivData->m_mutexDataAccess ); m_pPrivData->m_vecData.Swap( m_pPrivData->m_vecDataInFlight ); } return 0; } //----------------------------------------------------------------------------- // Purpose: closes the Pdh Query (which frees system resources) //----------------------------------------------------------------------------- void CWinPerfCounters::Shutdown() { if ( m_pPrivData ) { m_pPrivData->m_bThreadRunning = false; if ( m_pPrivData->m_threadQueryPerfCounters.IsAlive() ) { m_pPrivData->m_eventStartQuery.Set(); m_pPrivData->m_threadQueryPerfCounters.Join( 200 ); } #ifdef _WIN32 if (m_bInited) { PdhCloseQuery( m_pPrivData->m_pdhHQuery ); m_bInited = false; } #endif } m_pPerfCounterMap = NULL; SAFE_DELETE( m_pPrivData ); } #ifdef DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: Run a global validation pass on all of our data structures and memory // allocations. // Input: validator - Our global validator object // pchName - Our name (typically a member var in our container) //----------------------------------------------------------------------------- void CWinPerfCounters::Validate( CValidator &validator, const char *pchName ) { VALIDATE_SCOPE(); ValidatePtr( m_pPrivData ); } #endif // DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CWinNetworkPerfCounters::CWinNetworkPerfCounters( ) { m_unNumInterfaces = 0; m_bInited = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CWinNetworkPerfCounters::~CWinNetworkPerfCounters() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CWinNetworkPerfCounters::Init() { // Enumerate all network interfaces and create counters for each bool bRet = false; HQUERY hQuery; PDH_STATUS pdhStatus = PdhOpenQuery( NULL, 1, & hQuery ); if ( pdhStatus != ERROR_SUCCESS ) { return false; } CUtlBuffer bufCounterList; DWORD dwCounterListSize = 0; CUtlBuffer bufInstanceList; DWORD dwInstanceListSize = 0; LPTSTR pszThisInstance = NULL; // Determine the required buffer size for the data. pdhStatus = PdhEnumObjectItems ( NULL, // reserved NULL, // local machine TEXT("Network Interface"), // object to enumerate NULL, // pass in NULL buffers & dwCounterListSize, // an 0 length to get NULL,// required size & dwInstanceListSize, // of the buffers in chars PERF_DETAIL_WIZARD, // counter detail level 0 ); // Note: old MSDN example tests against ERROR_SUCCESS (works on Win2k, fails on XP, // new (.NET) MSDN example tests against PDH_MORE_DATA (works on XP, fails on Win2k Server). // A usenet post has code that tests against both. if ( pdhStatus != ERROR_SUCCESS && pdhStatus != PDH_MORE_DATA ) //lint !e650 !e737 constant out of range for '!=' (code from MSDN) { // failed to determine buffer size return bRet; } else { // Allocate the buffers and try the call again. bufCounterList.EnsureCapacity( dwCounterListSize * sizeof(TCHAR) ); bufInstanceList.EnsureCapacity( dwInstanceListSize * sizeof (TCHAR) ); pdhStatus = PdhEnumObjectItems ( NULL, // reserved NULL, // local machine TEXT("Network Interface"), // object to enumerate (LPTSTR) bufCounterList.Base(), & dwCounterListSize, (LPTSTR) bufInstanceList.Base(), & dwInstanceListSize, PERF_DETAIL_WIZARD, // counter detail level 0 ); if ( pdhStatus != ERROR_SUCCESS ) { return bRet; } else { // If the machine has multiple network cards with identical names then we need // to count them and append '#n' to the name, beginning with #1 for the first // duplicate. typedef CUtlDict< uint > mapIdenticalInstanceCount_t; mapIdenticalInstanceCount_t mapIdenticalInstanceCount; // Walk the return instance list. for ( pszThisInstance = (LPTSTR) bufInstanceList.Base(); * pszThisInstance != 0; pszThisInstance += lstrlen( pszThisInstance ) + 1 ) { // reached our limit if ( m_unNumInterfaces >= sm_unMaxNetworkInterfacesToMeasure ) break; CUtlString sThisInstance; // If the machine has multiple network cards with identical names then we need // to count them and append '#n' to the name, beginning with #1 for the first // duplicate. // note 11/20/2012 I'm not sure this is true anymore, Windows might finally // be giving us the instance names we actually need. Which is good because sometimes // it's reall " 2" or " _2" on the end. . . int iDict = mapIdenticalInstanceCount.Find( pszThisInstance ); if ( iDict == mapIdenticalInstanceCount.InvalidIndex() ) { mapIdenticalInstanceCount.Insert( pszThisInstance, 1 ); sThisInstance = pszThisInstance; } else { mapIdenticalInstanceCount[iDict]++; sThisInstance.Format( "%s #%d", pszThisInstance, mapIdenticalInstanceCount[iDict] ); } CUtlString sBytesSentCounterName; sBytesSentCounterName.Format( "\\Network Interface(%s)\\Bytes Sent/sec", sThisInstance.String() ); CUtlString sBytesRecvCounterName; sBytesRecvCounterName.Format( "\\Network Interface(%s)\\Bytes Received/sec", sThisInstance.String() ); PerfCounter_t &BytesSentCounter = m_rgPerfCounterInfo[ 2 * m_unNumInterfaces ]; PerfCounter_t &BytesReceivedCounter = m_rgPerfCounterInfo[ 2 * m_unNumInterfaces + 1 ]; BytesSentCounter.m_rgchPerfObject = strdup( sBytesSentCounterName.Get() ); BytesSentCounter.m_rgchPerfObjectAlternative = NULL; BytesSentCounter.m_statsOffset = offsetof( Stats_t, m_rgunNetworkBytesSentStats ) + ( m_unNumInterfaces * sizeof( uint32 ) ); BytesSentCounter.m_eFmt = k_EFormatInt; BytesSentCounter.m_fUnsetValue = 0; BytesSentCounter.m_bAssertOnFailure = false; BytesSentCounter.m_bCounterRequiresRollup = false; BytesReceivedCounter.m_rgchPerfObject = strdup( sBytesRecvCounterName.Get() ); BytesReceivedCounter.m_rgchPerfObjectAlternative = NULL; BytesReceivedCounter.m_statsOffset = offsetof( Stats_t, m_rgunNetworkBytesReceivedStats ) + ( m_unNumInterfaces * sizeof( uint32 ) ); BytesReceivedCounter.m_eFmt = k_EFormatInt; BytesReceivedCounter.m_fUnsetValue = 0; BytesReceivedCounter.m_bAssertOnFailure = false; BytesReceivedCounter.m_bCounterRequiresRollup = false; m_unNumInterfaces++; } bRet = m_PerfCounters.Init( m_rgPerfCounterInfo, m_unNumInterfaces*2 ); m_bInited = bRet; } } PdhCloseQuery( hQuery ); return bRet; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CWinNetworkPerfCounters::TakeSample() { if ( m_bInited ) return m_PerfCounters.TakeSample(); else return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CWinNetworkPerfCounters::WriteStats( uint64 *pu64BytesSentPerSec, uint64 *pu64BytesRecvPerSec ) { if ( !m_bInited ) return false; bool bRet = false; if ( m_PerfCounters.WriteStats( &m_Stats ) ) { *pu64BytesSentPerSec = 0; *pu64BytesRecvPerSec = 0; for ( uint32 i=0; i < m_unNumInterfaces; ++i ) { *pu64BytesSentPerSec += m_Stats.m_rgunNetworkBytesSentStats[i]; *pu64BytesRecvPerSec += m_Stats.m_rgunNetworkBytesReceivedStats[i]; } bRet = true; } return bRet; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWinNetworkPerfCounters::Shutdown() { if ( m_bInited ) m_PerfCounters.Shutdown(); } #ifdef DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWinNetworkPerfCounters::Validate( CValidator &validator, const char *pchName ) { ValidateObj( m_PerfCounters ); for ( uint32 i=0; i < m_unNumInterfaces; ++i ) { validator.ClaimMemory( (void*) m_rgPerfCounterInfo[2*i].m_rgchPerfObject ); validator.ClaimMemory( (void*) m_rgPerfCounterInfo[2*i + 1].m_rgchPerfObject ); } } #endif // DBGFLAG_VALIDATE #endif // _WIN32