Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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

//========= 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 );
}
}
}