425 lines
13 KiB
C++
425 lines
13 KiB
C++
//====== Copyright © 1996-2005, 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();
|
|
}
|
|
}
|
|
|
|
void EndBenchmark()
|
|
{
|
|
// 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;
|
|
}
|