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.
241 lines
6.0 KiB
241 lines
6.0 KiB
//========= Copyright 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 );
|
|
}
|
|
}
|
|
}
|