source-engine/utils/vmpi/vmpi_service/perf_counters.cpp

500 lines
13 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "stdafx.h"
#include "tier1/utldict.h"
#include <pdh.h>
#include <pdhmsg.h>
#include "perf_counters.h"
#if 1
class CPerfTracker : public IPerfTracker
{
public:
CPerfTracker()
{
m_hProcessorTimeCounter = NULL;
m_dwProcessID = 0;
if ( PdhOpenQuery( NULL, 0, &m_hQuery ) != ERROR_SUCCESS )
m_hQuery = NULL;
SYSTEM_INFO info;
GetSystemInfo( &info );
m_nProcessors = (int)info.dwNumberOfProcessors;
}
~CPerfTracker()
{
if ( m_hQuery )
PdhCloseQuery( m_hQuery );
}
virtual void Init( unsigned long dwProcessID )
{
Term();
m_dwProcessID = dwProcessID;
char instanceName[512];
if ( GetInstanceNameFromProcessID( m_dwProcessID, instanceName, sizeof( instanceName ) ) )
{
// Create a counter to watch this process' time.
char str[512];
V_snprintf( str, sizeof( str ), "\\Process(%s)\\%% Processor Time", instanceName );
if ( PdhAddCounter( m_hQuery, str, 0, &m_hProcessorTimeCounter ) != ERROR_SUCCESS )
{
m_hProcessorTimeCounter = NULL;
}
V_snprintf( str, sizeof( str ), "\\Process(%s)\\Private Bytes", instanceName );
if ( PdhAddCounter( m_hQuery, str, 0, &m_hPrivateBytesCounter ) != ERROR_SUCCESS )
{
m_hPrivateBytesCounter = NULL;
}
}
}
void Term()
{
if ( m_hProcessorTimeCounter )
PdhRemoveCounter( m_hProcessorTimeCounter );
if ( m_hPrivateBytesCounter )
PdhRemoveCounter( m_hPrivateBytesCounter );
m_hProcessorTimeCounter = NULL;
m_hPrivateBytesCounter = NULL;
}
virtual void Release()
{
delete this;
}
virtual unsigned long GetProcessID()
{
return m_dwProcessID;
}
virtual void GetPerfData( int &processorPercentage, int &memoryUsageMegabytes )
{
processorPercentage = 101;
memoryUsageMegabytes = 0;
// Collect query data..
PDH_STATUS ret = PdhCollectQueryData( m_hQuery );
if ( ret != ERROR_SUCCESS )
return;
// Check processor usage.
DWORD dwType;
PDH_FMT_COUNTERVALUE counterValue;
if ( PdhGetFormattedCounterValue( m_hProcessorTimeCounter, PDH_FMT_LONG | PDH_FMT_NOCAP100, &dwType, &counterValue ) == ERROR_SUCCESS )
processorPercentage = counterValue.longValue / m_nProcessors;
else
processorPercentage = 101;
// Check memory usage.
if ( PdhGetFormattedCounterValue( m_hPrivateBytesCounter, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &dwType, &counterValue ) == ERROR_SUCCESS )
memoryUsageMegabytes = (int)(counterValue.doubleValue / (1024.0 * 1024.0));
else
memoryUsageMegabytes = 0;
}
private:
bool GetInstanceNameFromProcessID( DWORD processID, char *instanceName, int instanceNameLen )
{
instanceName[0] = 0;
bool bRet = false;
// This refreshes the object list. If we don't do this, it won't get new process IDs correctly.
DWORD dummy = 0;
PdhEnumObjects( NULL, NULL, NULL, &dummy, PERF_DETAIL_NOVICE, true );
// Find out how much data we need.
DWORD counterListLen=2, instanceListLen=2;
char *counterList = new char[counterListLen];
char *instanceList = new char[instanceListLen];
PDH_STATUS stat = PdhEnumObjectItems( NULL, NULL, "Process", counterList, &counterListLen, instanceList, &instanceListLen, PERF_DETAIL_NOVICE, 0 );
if ( stat == PDH_MORE_DATA )
{
delete [] counterList;
delete [] instanceList;
char *counterList = new char[counterListLen];
char *instanceList = new char[instanceListLen];
stat = PdhEnumObjectItems( NULL, NULL, "Process", counterList, &counterListLen, instanceList, &instanceListLen, PERF_DETAIL_NOVICE, 0 );
if ( stat == ERROR_SUCCESS )
{
// We need the # of each one..
CUtlDict<int,int> counts;
// The instance name list is a bunch of strings terminated with nulls. The final one has two nulls after it.
// Walk through the list and get the process ID associated with each instance name.
const char *pCur = instanceList;
while ( *pCur )
{
int index = counts.Find( pCur );
if ( index == counts.InvalidIndex() )
counts.Insert( pCur, 1 );
else
counts[index]++;
pCur += strlen( pCur ) + 1;
}
// Each instance (like "vrad") might have multiple versions, like if you're running multiple vrad processes at the same time.
for ( int i=counts.First(); i != counts.InvalidIndex(); i=counts.Next( i ) )
{
const char *pInstanceName = counts.GetElementName( i );
int nInstances = counts[i];
for ( int iInstance=0; iInstance < nInstances; iInstance++ )
{
char testInstanceName[256], fullObjectName[256];
V_snprintf( testInstanceName, sizeof( testInstanceName ), "%s#%d", pInstanceName, iInstance );
V_snprintf( fullObjectName, sizeof( fullObjectName ), "\\Process(%s)\\ID Process", testInstanceName );
HCOUNTER hCounter = NULL;
stat = PdhAddCounter( m_hQuery, fullObjectName, 0, &hCounter );
if ( stat == ERROR_SUCCESS )
{
stat = PdhCollectQueryData( m_hQuery );
if ( stat == ERROR_SUCCESS )
{
DWORD dwType;
PDH_FMT_COUNTERVALUE counterValue;
stat = PdhGetFormattedCounterValue( hCounter, PDH_FMT_LONG, &dwType, &counterValue );
if ( stat == 0 && counterValue.longValue == (long)processID )
{
// Finall! We found it.
V_strncpy( instanceName, testInstanceName, instanceNameLen );
bRet = true;
PdhRemoveCounter( hCounter );
break;
}
}
PdhRemoveCounter( hCounter );
}
}
if ( bRet )
break;
}
}
delete [] counterList;
delete [] instanceList;
}
return bRet;
}
private:
DWORD m_dwProcessID;
PDH_HQUERY m_hQuery;
HCOUNTER m_hProcessorTimeCounter;
HCOUNTER m_hPrivateBytesCounter;
int m_nProcessors;
};
IPerfTracker* CreatePerfTracker()
{
return new CPerfTracker;
}
#else
#include <winperf.h>
// --------------------------------------------------------------------------------------------------------------------- //
// NOTE: THIS IS THE OLD, UGLY WAY TO DO IT.
// --------------------------------------------------------------------------------------------------------------------- //
class CPerfTracker
{
public:
CPerfTracker();
void Init( unsigned long dwProcessID );
unsigned long GetProcessID();
// Get the percentage of CPU time that the process is using.
int GetCPUPercentage();
private:
DWORD m_dwProcessID;
LONGLONG m_lnOldValue;
LARGE_INTEGER m_OldPerfTime100nSec;
int m_nProcessors;
};
#define TOTALBYTES 100*1024
#define BYTEINCREMENT 10*1024
#define SYSTEM_OBJECT_INDEX 2 // 'System' object
#define PROCESS_OBJECT_INDEX 230 // 'Process' object
#define PROCESSOR_OBJECT_INDEX 238 // 'Processor' object
#define TOTAL_PROCESSOR_TIME_COUNTER_INDEX 240 // '% Total processor time' counter (valid in WinNT under 'System' object)
#define PROCESSOR_TIME_COUNTER_INDEX 6 // '% processor time' counter (for Win2K/XP)
//
// The performance data is accessed through the registry key
// HKEY_PEFORMANCE_DATA.
// However, although we use the registry to collect performance data,
// the data is not stored in the registry database.
// Instead, calling the registry functions with the HKEY_PEFORMANCE_DATA key
// causes the system to collect the data from the appropriate system
// object managers.
//
// QueryPerformanceData allocates memory block for getting the
// performance data.
//
//
void QueryPerformanceData(PERF_DATA_BLOCK **pPerfData, DWORD dwObjectIndex, DWORD dwCounterIndex)
{
//
// Since i want to use the same allocated area for each query,
// i declare CBuffer as static.
// The allocated is changed only when RegQueryValueEx return ERROR_MORE_DATA
//
static CUtlVector<char> Buffer;
if ( Buffer.Count() == 0 )
Buffer.SetSize( TOTALBYTES );
DWORD BufferSize = Buffer.Count();
LONG lRes;
char keyName[32];
V_snprintf(keyName, sizeof(keyName), "%d",dwObjectIndex);
memset( Buffer.Base(), 0, Buffer.Count() );
while( (lRes = RegQueryValueEx( HKEY_PERFORMANCE_DATA,
keyName,
NULL,
NULL,
(LPBYTE)Buffer.Base(),
&BufferSize )) == ERROR_MORE_DATA )
{
// Get a buffer that is big enough.
BufferSize += BYTEINCREMENT;
Buffer.SetSize( BufferSize );
}
*pPerfData = (PPERF_DATA_BLOCK)Buffer.Base();
}
/*****************************************************************
* *
* Functions used to navigate through the performance data. *
* *
*****************************************************************/
inline PPERF_OBJECT_TYPE FirstObject( PPERF_DATA_BLOCK PerfData )
{
return( (PPERF_OBJECT_TYPE)((PBYTE)PerfData + PerfData->HeaderLength) );
}
inline PPERF_OBJECT_TYPE NextObject( PPERF_OBJECT_TYPE PerfObj )
{
return( (PPERF_OBJECT_TYPE)((PBYTE)PerfObj + PerfObj->TotalByteLength) );
}
inline PPERF_COUNTER_DEFINITION FirstCounter( PPERF_OBJECT_TYPE PerfObj )
{
return( (PPERF_COUNTER_DEFINITION) ((PBYTE)PerfObj + PerfObj->HeaderLength) );
}
inline PPERF_COUNTER_DEFINITION NextCounter( PPERF_COUNTER_DEFINITION PerfCntr )
{
return( (PPERF_COUNTER_DEFINITION)((PBYTE)PerfCntr + PerfCntr->ByteLength) );
}
inline PPERF_INSTANCE_DEFINITION FirstInstance( PPERF_OBJECT_TYPE PerfObj )
{
return( (PPERF_INSTANCE_DEFINITION)((PBYTE)PerfObj + PerfObj->DefinitionLength) );
}
inline PPERF_INSTANCE_DEFINITION NextInstance( PPERF_INSTANCE_DEFINITION PerfInst )
{
PPERF_COUNTER_BLOCK PerfCntrBlk;
PerfCntrBlk = (PPERF_COUNTER_BLOCK)((PBYTE)PerfInst + PerfInst->ByteLength);
return( (PPERF_INSTANCE_DEFINITION)((PBYTE)PerfCntrBlk + PerfCntrBlk->ByteLength) );
}
template<class T>
T GetCounterValueForProcessID(PPERF_OBJECT_TYPE pPerfObj, DWORD dwCounterIndex, DWORD dwProcessID)
{
unsigned long PROC_ID_COUNTER = 784;
BOOL bProcessIDExist = FALSE;
PPERF_COUNTER_DEFINITION pPerfCntr = NULL;
PPERF_COUNTER_DEFINITION pTheRequestedPerfCntr = NULL;
PPERF_COUNTER_DEFINITION pProcIDPerfCntr = NULL;
PPERF_INSTANCE_DEFINITION pPerfInst = NULL;
PPERF_COUNTER_BLOCK pCounterBlock = NULL;
// Get the first counter.
pPerfCntr = FirstCounter( pPerfObj );
for( DWORD j=0; j < pPerfObj->NumCounters; j++ )
{
if (pPerfCntr->CounterNameTitleIndex == PROC_ID_COUNTER)
{
pProcIDPerfCntr = pPerfCntr;
if (pTheRequestedPerfCntr)
break;
}
if (pPerfCntr->CounterNameTitleIndex == dwCounterIndex)
{
pTheRequestedPerfCntr = pPerfCntr;
if (pProcIDPerfCntr)
break;
}
// Get the next counter.
pPerfCntr = NextCounter( pPerfCntr );
}
if( pPerfObj->NumInstances == PERF_NO_INSTANCES )
{
pCounterBlock = (PPERF_COUNTER_BLOCK) ((LPBYTE) pPerfObj + pPerfObj->DefinitionLength);
}
else
{
pPerfInst = FirstInstance( pPerfObj );
for( int k=0; k < pPerfObj->NumInstances; k++ )
{
pCounterBlock = (PPERF_COUNTER_BLOCK) ((LPBYTE) pPerfInst + pPerfInst->ByteLength);
if (pCounterBlock)
{
DWORD processID = *(DWORD*)((LPBYTE) pCounterBlock + pProcIDPerfCntr->CounterOffset);
if (processID == dwProcessID)
{
bProcessIDExist = TRUE;
break;
}
}
// Get the next instance.
pPerfInst = NextInstance( pPerfInst );
}
}
if (bProcessIDExist && pCounterBlock)
{
T *lnValue = NULL;
lnValue = (T*)((LPBYTE) pCounterBlock + pTheRequestedPerfCntr->CounterOffset);
return *lnValue;
}
return -1;
}
template<class T>
T GetCounterValueForProcessID(PERF_DATA_BLOCK **pPerfData, DWORD dwObjectIndex, DWORD dwCounterIndex, DWORD dwProcessID)
{
QueryPerformanceData(pPerfData, dwObjectIndex, dwCounterIndex);
PPERF_OBJECT_TYPE pPerfObj = NULL;
T lnValue = {0};
// Get the first object type.
pPerfObj = FirstObject( *pPerfData );
// Look for the given object index
for( DWORD i=0; i < (*pPerfData)->NumObjectTypes; i++ )
{
if (pPerfObj->ObjectNameTitleIndex == dwObjectIndex)
{
lnValue = GetCounterValueForProcessID<T>(pPerfObj, dwCounterIndex, dwProcessID);
break;
}
pPerfObj = NextObject( pPerfObj );
}
return lnValue;
}
// ------------------------------------------------------------------------------------------- //
// CPerfTracker implementation.
// ------------------------------------------------------------------------------------------- //
CPerfTracker::CPerfTracker()
{
Init( 0 );
SYSTEM_INFO info;
GetSystemInfo( &info );
m_nProcessors = (int)info.dwNumberOfProcessors;
}
void CPerfTracker::Init( unsigned long dwProcessID )
{
m_dwProcessID = dwProcessID;
m_lnOldValue = 0;
}
unsigned long CPerfTracker::GetProcessID()
{
return m_dwProcessID;
}
int CPerfTracker::GetCPUPercentage()
{
DWORD dwObjectIndex = PROCESS_OBJECT_INDEX;
DWORD dwCpuUsageIndex = PROCESSOR_TIME_COUNTER_INDEX;
PPERF_DATA_BLOCK pPerfData = NULL;
LONGLONG lnNewValue = GetCounterValueForProcessID<LONGLONG>( &pPerfData, dwObjectIndex, dwCpuUsageIndex, m_dwProcessID );
LARGE_INTEGER NewPerfTime100nSec = pPerfData->PerfTime100nSec;
if ( m_lnOldValue == 0 )
{
m_lnOldValue = lnNewValue;
m_OldPerfTime100nSec = NewPerfTime100nSec;
return 0;
}
LONGLONG lnValueDelta = lnNewValue - m_lnOldValue;
double DeltaPerfTime100nSec = (double)NewPerfTime100nSec.QuadPart - (double)m_OldPerfTime100nSec.QuadPart;
m_lnOldValue = lnNewValue;
m_OldPerfTime100nSec = NewPerfTime100nSec;
double a = (double)lnValueDelta / DeltaPerfTime100nSec;
int CpuUsage = (int) (a*100);
if (CpuUsage < 0)
return 0;
return CpuUsage / m_nProcessors;
}
#endif