|
|
//========= Copyright � 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Purpose: module for gathering performance stats for upload so that we can
// monitor performance regressions and improvements
//
//=====================================================================================//
#include "cbase.h"
#include "statgather.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define STATS_WINDOW_SIZE ( 60 * 10 ) // # of records to hold
#define STATS_RECORD_INTERVAL 1 // # of seconds between data grabs. 2 * 300 = every 10 minutes
struct StatsBufferRecord_t { float m_flFrameRate; // fps
};
const int PERFDATA_LEVEL = 1; const int PERFDATA_SHUTDOWN = 2;
class CStatsRecorder : public CAutoGameSystem {
StatsBufferRecord_t m_StatsBuffer[STATS_WINDOW_SIZE]; bool m_bBufferFull; float m_flLastRealTime; float m_flLastSampleTime;
template<class T> T AverageStat( T StatsBufferRecord_t::*field ) const { T sum = 0; for( int i = 0; i < STATS_WINDOW_SIZE; i++ ) sum += m_StatsBuffer[i].*field; return sum / STATS_WINDOW_SIZE; }
template<class T> T MaxStat( T StatsBufferRecord_t::*field ) const { T maxsofar = -16000000; for( int i = 0; i < STATS_WINDOW_SIZE; i++ ) maxsofar = MAX( maxsofar, m_StatsBuffer[i].*field ); return maxsofar; }
template<class T> T MinStat( T StatsBufferRecord_t::*field ) const { T minsofar = 16000000; for( int i = 0; i < STATS_WINDOW_SIZE; i++ ) minsofar = MIN( minsofar, m_StatsBuffer[i].*field ); return minsofar; }
inline void AdvanceIndex( void ) { m_nWriteIndex++; if ( m_nWriteIndex == STATS_WINDOW_SIZE ) { m_nWriteIndex = 0; m_bBufferFull = true; } }
void LevelInitPreEntity() { m_flTimeLevelStart = gpGlobals->curtime; }
void LevelShutdownPreEntity() { float flLevelTime = gpGlobals->curtime - m_flTimeLevelStart; m_flTotalTimeInLevels += flLevelTime; m_iNumLevels ++; UploadPerfData( PERFDATA_LEVEL ); }
void Shutdown() { UploadPerfData( PERFDATA_SHUTDOWN ); }
public: int m_nWriteIndex; float m_flTimeLevelStart; float m_flTotalTimeInLevels; int m_iNumLevels; CStatsRecorder( void ) { m_bBufferFull = false; m_nWriteIndex = 0; m_flLastRealTime = -1; m_flLastSampleTime = -1; m_flTimeLevelStart = 0; m_flTotalTimeInLevels = 0; m_iNumLevels = 0; }
char const *GetPerfStatsString( int iType ); void UploadPerfData( int iType ); void UpdatePerfStats( void ); };
char s_cPerfString[2048];
static inline char const *SafeString( char const *pStr ) { return ( pStr ) ? pStr : "?"; }
// get the string record for sending to the server. Contains perf data and hardware/software
// info. Returns NULL if there isn't a good record to send (i.e. not enough data yet).
// A successful Get() resets the stats
char const *CStatsRecorder::GetPerfStatsString( int iType ) { switch ( iType ) { case PERFDATA_LEVEL: { if ( ! m_bBufferFull ) return NULL;
float flAverageFrameRate = AverageStat( &StatsBufferRecord_t::m_flFrameRate ); float flMinFrameRate = MinStat( &StatsBufferRecord_t::m_flFrameRate ); float flMaxFrameRate = MaxStat( &StatsBufferRecord_t::m_flFrameRate );
const CPUInformation &cpu = GetCPUInformation(); MaterialAdapterInfo_t gpu; materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), gpu );
CMatRenderContextPtr pRenderContext( materials ); int dest_width,dest_height; pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
char szMap[MAX_PATH+1]=""; Q_FileBase( engine->GetLevelName(), szMap, ARRAYSIZE( szMap ) );
V_snprintf( s_cPerfString, sizeof( s_cPerfString ), "PERFDATA:AvgFps=%4.2f MinFps=%4.2f MaxFps=%4.2f CPUID=\"%s\" CPUGhz=%2.2f " "NumCores=%d GPUDrv=\"%s\" " "GPUVendor=%d GPUDeviceID=%d " "GPUDriverVersion=\"%d.%d\" DxLvl=%d " "Width=%d Height=%d MapName=%s", flAverageFrameRate, flMinFrameRate, flMaxFrameRate, cpu.m_szProcessorID, cpu.m_Speed * ( 1.0 / 1.0e9 ), cpu.m_nPhysicalProcessors, SafeString( gpu.m_pDriverName ), gpu.m_VendorID, gpu.m_DeviceID, gpu.m_nDriverVersionHigh, gpu.m_nDriverVersionLow, g_pMaterialSystemHardwareConfig->GetDXSupportLevel(), dest_width, dest_height, szMap ); // get rid of chars that we hate in vendor strings
for( char *i = s_cPerfString; *i; i++ ) { if ( ( i[0]=='\n' ) || ( i[0]=='\r' ) || ( i[0]==';' ) ) i[0]=' '; }
// clear buffer
m_nWriteIndex = 0; m_bBufferFull = false;
return s_cPerfString; } case PERFDATA_SHUTDOWN: V_snprintf( s_cPerfString, sizeof( s_cPerfString ), "PERFDATA:TotalLevelTime=%d NumLevels=%d", (int) m_flTotalTimeInLevels, m_iNumLevels ); return s_cPerfString;
default: Assert( false ); return NULL; } }
void CStatsRecorder::UpdatePerfStats( void ) { float flCurTime = Plat_FloatTime(); if ( ( m_flLastSampleTime == -1 ) || ( flCurTime - m_flLastSampleTime >= STATS_RECORD_INTERVAL ) ) { if ( ( m_flLastRealTime > 0 ) && ( flCurTime > m_flLastRealTime ) ) { float flFrameRate = 1.0 / ( flCurTime - m_flLastRealTime ); StatsBufferRecord_t &stat = m_StatsBuffer[m_nWriteIndex]; stat.m_flFrameRate = flFrameRate; AdvanceIndex(); m_flLastSampleTime = flCurTime; } } m_flLastRealTime = flCurTime; }
static CStatsRecorder s_StatsRecorder;
void UpdatePerfStats( void ) { s_StatsRecorder.UpdatePerfStats(); }
static void ShowPerfStats( void ) { char const *pStr = s_StatsRecorder.GetPerfStatsString( PERFDATA_LEVEL ); if ( pStr ) Warning( "%s\n", pStr ); else Warning( "%d records stored. buffer not full.\n", s_StatsRecorder.m_nWriteIndex ); }
static ConCommand perfstats( "cl_perfstats", ShowPerfStats, "Dump the perf monitoring string" );
// upload performance to steam, if we have any. This is a blocking call.
void CStatsRecorder::UploadPerfData( int iType ) { if( g_pClientGameStatsUploader ) { char const *pPerfData = GetPerfStatsString( iType );
if ( pPerfData ) { g_pClientGameStatsUploader->UploadGameStats( "", 1, 1 + strlen( pPerfData ), pPerfData ); } } }
|