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
5 years ago
|
//========= 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
|