Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "serverbenchmark_base.h"
  8. #include "props.h"
  9. #include "filesystem.h"
  10. #include "tier0/icommandline.h"
  11. // Server benchmark. Only works on specified maps.
  12. // Lasts for N ticks.
  13. // Enable sv_stressbots.
  14. // Create 20 players and move them around and have them shoot.
  15. // At the end, report the # seconds it took to complete the test.
  16. // Don't start measuring for the first N ticks to account for HD load.
  17. static ConVar sv_benchmark_numticks( "sv_benchmark_numticks", "3300", 0, "If > 0, then it only runs the benchmark for this # of ticks." );
  18. 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." );
  19. static float s_flBenchmarkStartWaitSeconds = 3; // Wait this many seconds after level load before starting the benchmark.
  20. static int s_nBenchmarkBotsToCreate = 22; // Create this many bots.
  21. static int s_nBenchmarkBotCreateInterval = 50; // Create a bot every N ticks.
  22. static int s_nBenchmarkPhysicsObjects = 100; // Create this many physics objects.
  23. static double Benchmark_ValidTime()
  24. {
  25. bool bOld = Plat_IsInBenchmarkMode();
  26. Plat_SetBenchmarkMode( false );
  27. double flRet = Plat_FloatTime();
  28. Plat_SetBenchmarkMode( bOld );
  29. return flRet;
  30. }
  31. // ---------------------------------------------------------------------------------------------- //
  32. // CServerBenchmark implementation.
  33. // ---------------------------------------------------------------------------------------------- //
  34. class CServerBenchmark : public IServerBenchmark
  35. {
  36. public:
  37. CServerBenchmark()
  38. {
  39. m_BenchmarkState = BENCHMARKSTATE_NOT_RUNNING;
  40. // The benchmark should always have the same seed and do exactly the same thing on the same ticks.
  41. m_RandomStream.SetSeed( 1111 );
  42. }
  43. virtual bool StartBenchmark()
  44. {
  45. bool bBenchmark = (CommandLine()->FindParm( "-sv_benchmark" ) != 0);
  46. return InternalStartBenchmark( bBenchmark, s_flBenchmarkStartWaitSeconds );
  47. }
  48. // nBenchmarkMode: 0 = no benchmark
  49. // 1 = benchmark
  50. // 2 = exit out afterwards and write sv_benchmark.txt
  51. bool InternalStartBenchmark( int nBenchmarkMode, float flCountdown )
  52. {
  53. bool bWasRunningBenchmark = (m_BenchmarkState != BENCHMARKSTATE_NOT_RUNNING);
  54. if ( nBenchmarkMode == 0 )
  55. {
  56. // Tear down the previous benchmark environment if necessary.
  57. if ( bWasRunningBenchmark )
  58. EndBenchmark();
  59. return false;
  60. }
  61. m_nBenchmarkMode = nBenchmarkMode;
  62. if ( !CServerBenchmarkHook::s_pBenchmarkHook )
  63. Error( "This game doesn't support server benchmarks (no CServerBenchmarkHook found)." );
  64. m_BenchmarkState = BENCHMARKSTATE_START_WAIT;
  65. m_flBenchmarkStartTime = Plat_FloatTime();
  66. m_flBenchmarkStartWaitTime = flCountdown;
  67. m_nBotsCreated = 0;
  68. m_nStartWaitCounter = -1;
  69. // Setup the benchmark environment.
  70. engine->SetDedicatedServerBenchmarkMode( true ); // Run 1 tick per frame and ignore all timing stuff.
  71. // Tell the game-specific hook that we're starting.
  72. CServerBenchmarkHook::s_pBenchmarkHook->StartBenchmark();
  73. CServerBenchmarkHook::s_pBenchmarkHook->GetPhysicsModelNames( m_PhysicsModelNames );
  74. return true;
  75. }
  76. virtual void UpdateBenchmark()
  77. {
  78. // No benchmark running?
  79. if ( m_BenchmarkState == BENCHMARKSTATE_NOT_RUNNING )
  80. return;
  81. // Wait a certain number of ticks to start the benchmark.
  82. if ( m_BenchmarkState == BENCHMARKSTATE_START_WAIT )
  83. {
  84. if ( (Plat_FloatTime() - m_flBenchmarkStartTime) < m_flBenchmarkStartWaitTime )
  85. {
  86. UpdateStartWaitCounter();
  87. return;
  88. }
  89. else
  90. {
  91. // Ok, now we're officially starting it.
  92. Msg( "Starting benchmark!\n" );
  93. m_flLastBenchmarkCounterUpdate = m_flBenchmarkStartTime = Plat_FloatTime();
  94. m_fl_ValidTime_BenchmarkStartTime = Benchmark_ValidTime();
  95. m_nBenchmarkStartTick = gpGlobals->tickcount;
  96. m_nLastPhysicsObjectTick = m_nLastPhysicsForceTick = 0;
  97. m_BenchmarkState = BENCHMARKSTATE_RUNNING;
  98. StartVProfRecord();
  99. RandomSeed( 0 );
  100. m_RandomStream.SetSeed( 0 );
  101. }
  102. }
  103. int nTicksRunSoFar = gpGlobals->tickcount - m_nBenchmarkStartTick;
  104. UpdateBenchmarkCounter();
  105. // Are we finished with the benchmark?
  106. if ( nTicksRunSoFar >= sv_benchmark_numticks.GetInt() )
  107. {
  108. EndVProfRecord();
  109. OutputResults();
  110. EndBenchmark();
  111. return;
  112. }
  113. // Ok, update whatever we're doing in the benchmark.
  114. UpdatePlayerCreation();
  115. UpdateVPhysicsObjects();
  116. CServerBenchmarkHook::s_pBenchmarkHook->UpdateBenchmark();
  117. }
  118. void StartVProfRecord()
  119. {
  120. if ( sv_benchmark_autovprofrecord.GetInt() )
  121. {
  122. engine->ServerCommand( "vprof_record_start benchmark\n" );
  123. engine->ServerExecute();
  124. }
  125. }
  126. void EndVProfRecord()
  127. {
  128. if ( sv_benchmark_autovprofrecord.GetInt() )
  129. {
  130. engine->ServerCommand( "vprof_record_stop\n" );
  131. engine->ServerExecute();
  132. }
  133. }
  134. virtual void EndBenchmark( void )
  135. {
  136. // Write out the results if we're running the build scripts.
  137. float flRunTime = Benchmark_ValidTime() - m_fl_ValidTime_BenchmarkStartTime;
  138. if ( m_nBenchmarkMode == 2 )
  139. {
  140. FileHandle_t fh = filesystem->Open( "sv_benchmark_results.txt", "wt", "DEFAULT_WRITE_PATH" );
  141. // If this file doesn't get written out, then the build script will generate an email that there's a problem somewhere.
  142. if ( fh )
  143. {
  144. filesystem->FPrintf( fh, "sv_benchmark := %.2f\n", flRunTime );
  145. }
  146. filesystem->Close( fh );
  147. // Quit out.
  148. engine->ServerCommand( "quit\n" );
  149. }
  150. m_BenchmarkState = BENCHMARKSTATE_NOT_RUNNING;
  151. engine->SetDedicatedServerBenchmarkMode( false );
  152. }
  153. virtual bool IsLocalBenchmarkPlayer( CBasePlayer *pPlayer )
  154. {
  155. if ( m_BenchmarkState != BENCHMARKSTATE_NOT_RUNNING )
  156. {
  157. if( !engine->IsDedicatedServer() && pPlayer->entindex() == 1 )
  158. return true;
  159. }
  160. return false;
  161. }
  162. void UpdateVPhysicsObjects()
  163. {
  164. int nPhysicsObjectInterval = sv_benchmark_numticks.GetInt() / s_nBenchmarkPhysicsObjects;
  165. int nNextSpawnTick = m_nLastPhysicsObjectTick + nPhysicsObjectInterval;
  166. if ( GetTickOffset() >= nNextSpawnTick )
  167. {
  168. m_nLastPhysicsObjectTick = nNextSpawnTick;
  169. if ( m_PhysicsObjects.Count() < s_nBenchmarkPhysicsObjects )
  170. {
  171. // Find a bot to spawn it from.
  172. CUtlVector<CBasePlayer*> curPlayers;
  173. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  174. {
  175. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  176. if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
  177. {
  178. curPlayers.AddToTail( pPlayer );
  179. }
  180. }
  181. if ( curPlayers.Count() > 0 && m_PhysicsModelNames.Count() > 0 )
  182. {
  183. int iModelName = this->RandomInt( 0, m_PhysicsModelNames.Count() - 1 );
  184. const char *pModelName = m_PhysicsModelNames[iModelName];
  185. int iPlayer = this->RandomInt( 0, curPlayers.Count() - 1 );
  186. Vector vSpawnPos = curPlayers[iPlayer]->EyePosition() + Vector( 0, 0, 50 );
  187. // We'll try 15 locations around the player to spawn this thing.
  188. for ( int i=0; i < 15; i++ )
  189. {
  190. Vector vOffset( this->RandomFloat( -2000, 2000 ), this->RandomFloat( -2000, 2000 ), 0 );
  191. CPhysicsProp *pProp = CreatePhysicsProp( pModelName, vSpawnPos, vSpawnPos+vOffset, curPlayers[iPlayer], false, "prop_physics_multiplayer" );
  192. if ( pProp )
  193. {
  194. m_PhysicsObjects.AddToTail( pProp );
  195. pProp->SetAbsVelocity( Vector( this->RandomFloat(-500,500), this->RandomFloat(-500,500), this->RandomFloat(-500,500) ) );
  196. break;
  197. }
  198. }
  199. }
  200. }
  201. }
  202. // Give them all a boost periodically.
  203. int nPhysicsForceInterval = sv_benchmark_numticks.GetInt() / 20;
  204. int nNextForceTick = m_nLastPhysicsForceTick + nPhysicsForceInterval;
  205. if ( GetTickOffset() >= nNextForceTick )
  206. {
  207. m_nLastPhysicsForceTick = nNextForceTick;
  208. for ( int i=0; i < m_PhysicsObjects.Count(); i++ )
  209. {
  210. CBaseEntity *pEnt = m_PhysicsObjects[i];
  211. if ( pEnt )
  212. {
  213. IPhysicsObject *pPhysicsObject = pEnt->VPhysicsGetObject();
  214. if ( pPhysicsObject )
  215. {
  216. float flAngImpulse = 300000;
  217. float flForce = 500000;
  218. AngularImpulse vAngularImpulse( this->RandomFloat(-flAngImpulse,flAngImpulse), this->RandomFloat(-flAngImpulse,flAngImpulse), this->RandomFloat(flAngImpulse,flAngImpulse) );
  219. pPhysicsObject->ApplyForceCenter( Vector( this->RandomFloat(-flForce,flForce), this->RandomFloat(-flForce,flForce), this->RandomFloat(0,flForce) ) );
  220. }
  221. }
  222. }
  223. }
  224. }
  225. void UpdateStartWaitCounter()
  226. {
  227. int nSecondsLeft = (int)ceil( m_flBenchmarkStartWaitTime - (Plat_FloatTime() - m_flBenchmarkStartTime) );
  228. if ( m_nStartWaitCounter != nSecondsLeft )
  229. {
  230. Msg( "Starting benchmark in %d seconds...\n", nSecondsLeft );
  231. m_nStartWaitCounter = nSecondsLeft;
  232. }
  233. }
  234. void UpdateBenchmarkCounter()
  235. {
  236. float flCurTime = Plat_FloatTime();
  237. if ( (flCurTime - m_flLastBenchmarkCounterUpdate) > 3.0f )
  238. {
  239. m_flLastBenchmarkCounterUpdate = flCurTime;
  240. Msg( "Benchmark: %d%% complete.\n", ((gpGlobals->tickcount - m_nBenchmarkStartTick) * 100) / sv_benchmark_numticks.GetInt() );
  241. }
  242. }
  243. virtual bool IsBenchmarkRunning()
  244. {
  245. return (m_BenchmarkState == BENCHMARKSTATE_RUNNING);
  246. }
  247. virtual int GetTickOffset()
  248. {
  249. if ( m_BenchmarkState == BENCHMARKSTATE_RUNNING )
  250. {
  251. Assert( gpGlobals->tickcount >= m_nBenchmarkStartTick );
  252. return gpGlobals->tickcount - m_nBenchmarkStartTick;
  253. }
  254. else
  255. {
  256. return gpGlobals->tickcount;
  257. }
  258. }
  259. void UpdatePlayerCreation()
  260. {
  261. if ( m_nBotsCreated >= s_nBenchmarkBotsToCreate )
  262. return;
  263. // Spawn the player.
  264. int nTicksRunSoFar = gpGlobals->tickcount - m_nBenchmarkStartTick;
  265. if ( (nTicksRunSoFar % s_nBenchmarkBotCreateInterval) == 0 )
  266. {
  267. CServerBenchmarkHook::s_pBenchmarkHook->CreateBot();
  268. ++m_nBotsCreated;
  269. }
  270. }
  271. void OutputResults()
  272. {
  273. float flRunTime = Benchmark_ValidTime() - m_fl_ValidTime_BenchmarkStartTime;
  274. Warning( "------------------ SERVER BENCHMARK RESULTS ------------------\n" );
  275. Warning( "Total time : %.2f seconds\n", flRunTime );
  276. Warning( "Num ticks simulated : %d\n", sv_benchmark_numticks.GetInt() );
  277. Warning( "Ticks per second : %.2f\n", sv_benchmark_numticks.GetInt() / flRunTime );
  278. Warning( "Benchmark CRC : %d\n", CalculateBenchmarkCRC() );
  279. Warning( "--------------------------------------------------------------\n" );
  280. }
  281. int CalculateBenchmarkCRC()
  282. {
  283. int crc = 0;
  284. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  285. {
  286. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  287. if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
  288. {
  289. crc += pPlayer->GetTeamNumber();
  290. crc += (int)pPlayer->GetAbsOrigin().x;
  291. crc += (int)pPlayer->GetAbsOrigin().y;
  292. }
  293. }
  294. return crc;
  295. }
  296. virtual int RandomInt( int nMin, int nMax )
  297. {
  298. return m_RandomStream.RandomInt( nMin, nMax );
  299. }
  300. virtual float RandomFloat( float nMin, float nMax )
  301. {
  302. return m_RandomStream.RandomInt( nMin, nMax );
  303. }
  304. private:
  305. enum EBenchmarkState
  306. {
  307. BENCHMARKSTATE_NOT_RUNNING,
  308. BENCHMARKSTATE_START_WAIT,
  309. BENCHMARKSTATE_RUNNING
  310. };
  311. EBenchmarkState m_BenchmarkState;
  312. float m_fl_ValidTime_BenchmarkStartTime;
  313. float m_flBenchmarkStartTime;
  314. float m_flLastBenchmarkCounterUpdate;
  315. float m_flBenchmarkStartWaitTime;
  316. int m_nBenchmarkStartTick;
  317. int m_nStartWaitCounter;
  318. int m_nLastPhysicsObjectTick;
  319. int m_nLastPhysicsForceTick;
  320. int m_nBotsCreated;
  321. CUtlVector< EHANDLE > m_PhysicsObjects;
  322. CUtlVector<char*> m_PhysicsModelNames;
  323. int m_nBenchmarkMode;
  324. CUniformRandomStream m_RandomStream;
  325. };
  326. static CServerBenchmark g_ServerBenchmark;
  327. IServerBenchmark *g_pServerBenchmark = &g_ServerBenchmark;
  328. 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." )
  329. {
  330. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  331. return;
  332. g_ServerBenchmark.InternalStartBenchmark( 1, 1 );
  333. }
  334. // ---------------------------------------------------------------------------------------------- //
  335. // CServerBenchmarkHook implementation.
  336. // ---------------------------------------------------------------------------------------------- //
  337. CServerBenchmarkHook *CServerBenchmarkHook::s_pBenchmarkHook = NULL;
  338. CServerBenchmarkHook::CServerBenchmarkHook()
  339. {
  340. if ( s_pBenchmarkHook )
  341. Error( "There can only be one CServerBenchmarkHook" );
  342. s_pBenchmarkHook = this;
  343. }