You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
500 lines
13 KiB
500 lines
13 KiB
//========= 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 |