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.
424 lines
12 KiB
424 lines
12 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
#include "serverbenchmark_base.h" |
|
#include "props.h" |
|
#include "filesystem.h" |
|
#include "tier0/icommandline.h" |
|
|
|
|
|
// Server benchmark. Only works on specified maps. |
|
// Lasts for N ticks. |
|
// Enable sv_stressbots. |
|
// Create 20 players and move them around and have them shoot. |
|
// At the end, report the # seconds it took to complete the test. |
|
// Don't start measuring for the first N ticks to account for HD load. |
|
|
|
static ConVar sv_benchmark_numticks( "sv_benchmark_numticks", "3300", 0, "If > 0, then it only runs the benchmark for this # of ticks." ); |
|
static ConVar sv_benchmark_autovprofrecord( "sv_benchmark_autovprofrecord", "0", 0, "If running a benchmark and this is set, it will record a vprof file over the duration of the benchmark with filename benchmark.vprof." ); |
|
|
|
static float s_flBenchmarkStartWaitSeconds = 3; // Wait this many seconds after level load before starting the benchmark. |
|
|
|
static int s_nBenchmarkBotsToCreate = 22; // Create this many bots. |
|
static int s_nBenchmarkBotCreateInterval = 50; // Create a bot every N ticks. |
|
|
|
static int s_nBenchmarkPhysicsObjects = 100; // Create this many physics objects. |
|
|
|
|
|
static double Benchmark_ValidTime() |
|
{ |
|
bool bOld = Plat_IsInBenchmarkMode(); |
|
Plat_SetBenchmarkMode( false ); |
|
double flRet = Plat_FloatTime(); |
|
Plat_SetBenchmarkMode( bOld ); |
|
|
|
return flRet; |
|
} |
|
|
|
|
|
// ---------------------------------------------------------------------------------------------- // |
|
// CServerBenchmark implementation. |
|
// ---------------------------------------------------------------------------------------------- // |
|
class CServerBenchmark : public IServerBenchmark |
|
{ |
|
public: |
|
CServerBenchmark() |
|
{ |
|
m_BenchmarkState = BENCHMARKSTATE_NOT_RUNNING; |
|
|
|
// The benchmark should always have the same seed and do exactly the same thing on the same ticks. |
|
m_RandomStream.SetSeed( 1111 ); |
|
} |
|
|
|
virtual bool StartBenchmark() |
|
{ |
|
bool bBenchmark = (CommandLine()->FindParm( "-sv_benchmark" ) != 0); |
|
|
|
return InternalStartBenchmark( bBenchmark, s_flBenchmarkStartWaitSeconds ); |
|
} |
|
|
|
// nBenchmarkMode: 0 = no benchmark |
|
// 1 = benchmark |
|
// 2 = exit out afterwards and write sv_benchmark.txt |
|
bool InternalStartBenchmark( int nBenchmarkMode, float flCountdown ) |
|
{ |
|
bool bWasRunningBenchmark = (m_BenchmarkState != BENCHMARKSTATE_NOT_RUNNING); |
|
|
|
if ( nBenchmarkMode == 0 ) |
|
{ |
|
// Tear down the previous benchmark environment if necessary. |
|
if ( bWasRunningBenchmark ) |
|
EndBenchmark(); |
|
return false; |
|
} |
|
|
|
m_nBenchmarkMode = nBenchmarkMode; |
|
|
|
if ( !CServerBenchmarkHook::s_pBenchmarkHook ) |
|
Error( "This game doesn't support server benchmarks (no CServerBenchmarkHook found)." ); |
|
|
|
m_BenchmarkState = BENCHMARKSTATE_START_WAIT; |
|
m_flBenchmarkStartTime = Plat_FloatTime(); |
|
m_flBenchmarkStartWaitTime = flCountdown; |
|
|
|
m_nBotsCreated = 0; |
|
m_nStartWaitCounter = -1; |
|
|
|
// Setup the benchmark environment. |
|
engine->SetDedicatedServerBenchmarkMode( true ); // Run 1 tick per frame and ignore all timing stuff. |
|
|
|
// Tell the game-specific hook that we're starting. |
|
CServerBenchmarkHook::s_pBenchmarkHook->StartBenchmark(); |
|
CServerBenchmarkHook::s_pBenchmarkHook->GetPhysicsModelNames( m_PhysicsModelNames ); |
|
|
|
return true; |
|
} |
|
|
|
virtual void UpdateBenchmark() |
|
{ |
|
// No benchmark running? |
|
if ( m_BenchmarkState == BENCHMARKSTATE_NOT_RUNNING ) |
|
return; |
|
|
|
// Wait a certain number of ticks to start the benchmark. |
|
if ( m_BenchmarkState == BENCHMARKSTATE_START_WAIT ) |
|
{ |
|
if ( (Plat_FloatTime() - m_flBenchmarkStartTime) < m_flBenchmarkStartWaitTime ) |
|
{ |
|
UpdateStartWaitCounter(); |
|
return; |
|
} |
|
else |
|
{ |
|
// Ok, now we're officially starting it. |
|
Msg( "Starting benchmark!\n" ); |
|
m_flLastBenchmarkCounterUpdate = m_flBenchmarkStartTime = Plat_FloatTime(); |
|
m_fl_ValidTime_BenchmarkStartTime = Benchmark_ValidTime(); |
|
m_nBenchmarkStartTick = gpGlobals->tickcount; |
|
m_nLastPhysicsObjectTick = m_nLastPhysicsForceTick = 0; |
|
m_BenchmarkState = BENCHMARKSTATE_RUNNING; |
|
|
|
StartVProfRecord(); |
|
|
|
RandomSeed( 0 ); |
|
m_RandomStream.SetSeed( 0 ); |
|
} |
|
} |
|
|
|
int nTicksRunSoFar = gpGlobals->tickcount - m_nBenchmarkStartTick; |
|
UpdateBenchmarkCounter(); |
|
|
|
// Are we finished with the benchmark? |
|
if ( nTicksRunSoFar >= sv_benchmark_numticks.GetInt() ) |
|
{ |
|
EndVProfRecord(); |
|
OutputResults(); |
|
EndBenchmark(); |
|
return; |
|
} |
|
|
|
// Ok, update whatever we're doing in the benchmark. |
|
UpdatePlayerCreation(); |
|
UpdateVPhysicsObjects(); |
|
CServerBenchmarkHook::s_pBenchmarkHook->UpdateBenchmark(); |
|
} |
|
|
|
void StartVProfRecord() |
|
{ |
|
if ( sv_benchmark_autovprofrecord.GetInt() ) |
|
{ |
|
engine->ServerCommand( "vprof_record_start benchmark\n" ); |
|
engine->ServerExecute(); |
|
} |
|
} |
|
|
|
void EndVProfRecord() |
|
{ |
|
if ( sv_benchmark_autovprofrecord.GetInt() ) |
|
{ |
|
engine->ServerCommand( "vprof_record_stop\n" ); |
|
engine->ServerExecute(); |
|
} |
|
} |
|
|
|
virtual void EndBenchmark( void ) |
|
{ |
|
// Write out the results if we're running the build scripts. |
|
float flRunTime = Benchmark_ValidTime() - m_fl_ValidTime_BenchmarkStartTime; |
|
if ( m_nBenchmarkMode == 2 ) |
|
{ |
|
FileHandle_t fh = filesystem->Open( "sv_benchmark_results.txt", "wt", "DEFAULT_WRITE_PATH" ); |
|
|
|
// If this file doesn't get written out, then the build script will generate an email that there's a problem somewhere. |
|
if ( fh ) |
|
{ |
|
filesystem->FPrintf( fh, "sv_benchmark := %.2f\n", flRunTime ); |
|
} |
|
filesystem->Close( fh ); |
|
|
|
// Quit out. |
|
engine->ServerCommand( "quit\n" ); |
|
} |
|
|
|
m_BenchmarkState = BENCHMARKSTATE_NOT_RUNNING; |
|
engine->SetDedicatedServerBenchmarkMode( false ); |
|
} |
|
|
|
virtual bool IsLocalBenchmarkPlayer( CBasePlayer *pPlayer ) |
|
{ |
|
if ( m_BenchmarkState != BENCHMARKSTATE_NOT_RUNNING ) |
|
{ |
|
if( !engine->IsDedicatedServer() && pPlayer->entindex() == 1 ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void UpdateVPhysicsObjects() |
|
{ |
|
int nPhysicsObjectInterval = sv_benchmark_numticks.GetInt() / s_nBenchmarkPhysicsObjects; |
|
|
|
int nNextSpawnTick = m_nLastPhysicsObjectTick + nPhysicsObjectInterval; |
|
if ( GetTickOffset() >= nNextSpawnTick ) |
|
{ |
|
m_nLastPhysicsObjectTick = nNextSpawnTick; |
|
|
|
if ( m_PhysicsObjects.Count() < s_nBenchmarkPhysicsObjects ) |
|
{ |
|
// Find a bot to spawn it from. |
|
CUtlVector<CBasePlayer*> curPlayers; |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) ) |
|
{ |
|
curPlayers.AddToTail( pPlayer ); |
|
} |
|
} |
|
|
|
if ( curPlayers.Count() > 0 && m_PhysicsModelNames.Count() > 0 ) |
|
{ |
|
int iModelName = this->RandomInt( 0, m_PhysicsModelNames.Count() - 1 ); |
|
const char *pModelName = m_PhysicsModelNames[iModelName]; |
|
|
|
int iPlayer = this->RandomInt( 0, curPlayers.Count() - 1 ); |
|
|
|
Vector vSpawnPos = curPlayers[iPlayer]->EyePosition() + Vector( 0, 0, 50 ); |
|
|
|
// We'll try 15 locations around the player to spawn this thing. |
|
for ( int i=0; i < 15; i++ ) |
|
{ |
|
Vector vOffset( this->RandomFloat( -2000, 2000 ), this->RandomFloat( -2000, 2000 ), 0 ); |
|
CPhysicsProp *pProp = CreatePhysicsProp( pModelName, vSpawnPos, vSpawnPos+vOffset, curPlayers[iPlayer], false, "prop_physics_multiplayer" ); |
|
if ( pProp ) |
|
{ |
|
m_PhysicsObjects.AddToTail( pProp ); |
|
pProp->SetAbsVelocity( Vector( this->RandomFloat(-500,500), this->RandomFloat(-500,500), this->RandomFloat(-500,500) ) ); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Give them all a boost periodically. |
|
int nPhysicsForceInterval = sv_benchmark_numticks.GetInt() / 20; |
|
|
|
int nNextForceTick = m_nLastPhysicsForceTick + nPhysicsForceInterval; |
|
if ( GetTickOffset() >= nNextForceTick ) |
|
{ |
|
m_nLastPhysicsForceTick = nNextForceTick; |
|
|
|
for ( int i=0; i < m_PhysicsObjects.Count(); i++ ) |
|
{ |
|
CBaseEntity *pEnt = m_PhysicsObjects[i]; |
|
if ( pEnt ) |
|
{ |
|
IPhysicsObject *pPhysicsObject = pEnt->VPhysicsGetObject(); |
|
if ( pPhysicsObject ) |
|
{ |
|
float flAngImpulse = 300000; |
|
float flForce = 500000; |
|
AngularImpulse vAngularImpulse( this->RandomFloat(-flAngImpulse,flAngImpulse), this->RandomFloat(-flAngImpulse,flAngImpulse), this->RandomFloat(flAngImpulse,flAngImpulse) ); |
|
pPhysicsObject->ApplyForceCenter( Vector( this->RandomFloat(-flForce,flForce), this->RandomFloat(-flForce,flForce), this->RandomFloat(0,flForce) ) ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void UpdateStartWaitCounter() |
|
{ |
|
int nSecondsLeft = (int)ceil( m_flBenchmarkStartWaitTime - (Plat_FloatTime() - m_flBenchmarkStartTime) ); |
|
if ( m_nStartWaitCounter != nSecondsLeft ) |
|
{ |
|
Msg( "Starting benchmark in %d seconds...\n", nSecondsLeft ); |
|
m_nStartWaitCounter = nSecondsLeft; |
|
} |
|
} |
|
|
|
void UpdateBenchmarkCounter() |
|
{ |
|
float flCurTime = Plat_FloatTime(); |
|
if ( (flCurTime - m_flLastBenchmarkCounterUpdate) > 3.0f ) |
|
{ |
|
m_flLastBenchmarkCounterUpdate = flCurTime; |
|
Msg( "Benchmark: %d%% complete.\n", ((gpGlobals->tickcount - m_nBenchmarkStartTick) * 100) / sv_benchmark_numticks.GetInt() ); |
|
} |
|
} |
|
|
|
virtual bool IsBenchmarkRunning() |
|
{ |
|
return (m_BenchmarkState == BENCHMARKSTATE_RUNNING); |
|
} |
|
|
|
virtual int GetTickOffset() |
|
{ |
|
if ( m_BenchmarkState == BENCHMARKSTATE_RUNNING ) |
|
{ |
|
Assert( gpGlobals->tickcount >= m_nBenchmarkStartTick ); |
|
return gpGlobals->tickcount - m_nBenchmarkStartTick; |
|
} |
|
else |
|
{ |
|
return gpGlobals->tickcount; |
|
} |
|
} |
|
|
|
|
|
void UpdatePlayerCreation() |
|
{ |
|
if ( m_nBotsCreated >= s_nBenchmarkBotsToCreate ) |
|
return; |
|
|
|
// Spawn the player. |
|
int nTicksRunSoFar = gpGlobals->tickcount - m_nBenchmarkStartTick; |
|
|
|
if ( (nTicksRunSoFar % s_nBenchmarkBotCreateInterval) == 0 ) |
|
{ |
|
CServerBenchmarkHook::s_pBenchmarkHook->CreateBot(); |
|
++m_nBotsCreated; |
|
} |
|
} |
|
|
|
void OutputResults() |
|
{ |
|
float flRunTime = Benchmark_ValidTime() - m_fl_ValidTime_BenchmarkStartTime; |
|
|
|
Warning( "------------------ SERVER BENCHMARK RESULTS ------------------\n" ); |
|
Warning( "Total time : %.2f seconds\n", flRunTime ); |
|
Warning( "Num ticks simulated : %d\n", sv_benchmark_numticks.GetInt() ); |
|
Warning( "Ticks per second : %.2f\n", sv_benchmark_numticks.GetInt() / flRunTime ); |
|
Warning( "Benchmark CRC : %d\n", CalculateBenchmarkCRC() ); |
|
Warning( "--------------------------------------------------------------\n" ); |
|
} |
|
|
|
int CalculateBenchmarkCRC() |
|
{ |
|
int crc = 0; |
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) ) |
|
{ |
|
crc += pPlayer->GetTeamNumber(); |
|
crc += (int)pPlayer->GetAbsOrigin().x; |
|
crc += (int)pPlayer->GetAbsOrigin().y; |
|
} |
|
} |
|
|
|
return crc; |
|
} |
|
|
|
virtual int RandomInt( int nMin, int nMax ) |
|
{ |
|
return m_RandomStream.RandomInt( nMin, nMax ); |
|
} |
|
|
|
virtual float RandomFloat( float nMin, float nMax ) |
|
{ |
|
return m_RandomStream.RandomInt( nMin, nMax ); |
|
} |
|
|
|
|
|
private: |
|
|
|
enum EBenchmarkState |
|
{ |
|
BENCHMARKSTATE_NOT_RUNNING, |
|
BENCHMARKSTATE_START_WAIT, |
|
BENCHMARKSTATE_RUNNING |
|
}; |
|
EBenchmarkState m_BenchmarkState; |
|
|
|
float m_fl_ValidTime_BenchmarkStartTime; |
|
|
|
float m_flBenchmarkStartTime; |
|
float m_flLastBenchmarkCounterUpdate; |
|
float m_flBenchmarkStartWaitTime; |
|
|
|
int m_nBenchmarkStartTick; |
|
int m_nStartWaitCounter; |
|
int m_nLastPhysicsObjectTick; |
|
int m_nLastPhysicsForceTick; |
|
|
|
int m_nBotsCreated; |
|
CUtlVector< EHANDLE > m_PhysicsObjects; |
|
|
|
CUtlVector<char*> m_PhysicsModelNames; |
|
int m_nBenchmarkMode; |
|
|
|
CUniformRandomStream m_RandomStream; |
|
}; |
|
|
|
static CServerBenchmark g_ServerBenchmark; |
|
IServerBenchmark *g_pServerBenchmark = &g_ServerBenchmark; |
|
|
|
CON_COMMAND( sv_benchmark_force_start, "Force start the benchmark. This is only for debugging. It's better to set sv_benchmark to 1 and restart the level." ) |
|
{ |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
g_ServerBenchmark.InternalStartBenchmark( 1, 1 ); |
|
} |
|
|
|
|
|
// ---------------------------------------------------------------------------------------------- // |
|
// CServerBenchmarkHook implementation. |
|
// ---------------------------------------------------------------------------------------------- // |
|
|
|
CServerBenchmarkHook *CServerBenchmarkHook::s_pBenchmarkHook = NULL; |
|
|
|
CServerBenchmarkHook::CServerBenchmarkHook() |
|
{ |
|
if ( s_pBenchmarkHook ) |
|
Error( "There can only be one CServerBenchmarkHook" ); |
|
|
|
s_pBenchmarkHook = this; |
|
}
|
|
|