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