csgo-2018-source/common/winperfcounter.cpp
2021-07-24 21:11:47 -07:00

573 lines
17 KiB
C++

//========= Copyright © 1996-2004, Valve LLC, All rights reserved. ============
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#include "stdafx.h"
#ifdef _WIN32
#include "winperfcounter.h"
#ifdef _WIN32
#include <pdh.h>
#include <pdhmsg.h>
#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<uint32> 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