PerfTimer.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // FILE: PerfTimer.cpp ///////////////////////////////////////////////////////////////////////////
  24. // Author:
  25. ///////////////////////////////////////////////////////////////////////////////////////////////////
  26. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  27. #include "Common/PerfTimer.h"
  28. #include "Common/GlobalData.h"
  29. #include "GameClient/DebugDisplay.h"
  30. #include "GameClient/Display.h"
  31. #include "GameClient/GraphDraw.h"
  32. #ifdef _INTERNAL
  33. // for occasional debugging...
  34. //#pragma optimize("", off)
  35. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  36. #endif
  37. //-------------------------------------------------------------------------------------------------
  38. //-------------------------------------------------------------------------------------------------
  39. //-------------------------------------------------------------------------------------------------
  40. #if defined(PERF_TIMERS) || defined(DUMP_PERF_STATS)
  41. //-------------------------------------------------------------------------------------------------
  42. static Int64 s_ticksPerSec = 0;
  43. static double s_ticksPerMSec = 0;
  44. static double s_ticksPerUSec = 0;
  45. //-------------------------------------------------------------------------------------------------
  46. void GetPrecisionTimerTicksPerSec(Int64* t)
  47. {
  48. *t = s_ticksPerSec;
  49. }
  50. //-------------------------------------------------------------------------------------------------
  51. void InitPrecisionTimer()
  52. {
  53. #ifdef USE_QPF
  54. QueryPerformanceFrequency((LARGE_INTEGER*)&s_ticksPerSec);
  55. #else
  56. // Init the precision timers
  57. Int64 totalTime = 0;
  58. Int64 TotalTicks = 0;
  59. static int TESTS = 5;
  60. for (int i = 0; i < TESTS; ++i)
  61. {
  62. int TimeStart;
  63. int TimeStop;
  64. Int64 StartTicks;
  65. Int64 EndTicks;
  66. TimeStart = timeGetTime();
  67. GetPrecisionTimer(&StartTicks);
  68. for(;;)
  69. {
  70. TimeStop = timeGetTime();
  71. if ((TimeStop - TimeStart) > 1000)
  72. {
  73. GetPrecisionTimer(&EndTicks);
  74. break;
  75. }
  76. }
  77. TotalTicks += (EndTicks - StartTicks);
  78. totalTime += (TimeStop - TimeStart);
  79. }
  80. s_ticksPerMSec = 1.0 * TotalTicks / totalTime;
  81. s_ticksPerSec = s_ticksPerMSec * 1000.0f;
  82. #endif
  83. s_ticksPerMSec = s_ticksPerSec / 1000.0f;
  84. s_ticksPerUSec = s_ticksPerSec / 1000000.0f;
  85. #ifdef NOT_IN_USE
  86. Int64 bogus[8];
  87. GetPrecisionTimer(&start);
  88. for (Int ii = 0; ii < ITERS; ++ii)
  89. {
  90. GetPrecisionTimer(&bogus[0]);
  91. GetPrecisionTimer(&bogus[1]);
  92. GetPrecisionTimer(&bogus[2]);
  93. GetPrecisionTimer(&bogus[3]);
  94. GetPrecisionTimer(&bogus[4]);
  95. GetPrecisionTimer(&bogus[5]);
  96. GetPrecisionTimer(&bogus[6]);
  97. GetPrecisionTimer(&bogus[7]);
  98. }
  99. TheTicksToGetTicks = (bogus[7] - start) / (ITERS*8);
  100. DEBUG_LOG(("TheTicksToGetTicks is %d (%f usec)\n",(int)TheTicksToGetTicks,TheTicksToGetTicks/s_ticksPerUSec));
  101. #endif
  102. }
  103. #endif // defined(PERF_TIMERS) || defined(DUMP_PERF_STATS)
  104. //-------------------------------------------------------------------------------------------------
  105. //-------------------------------------------------------------------------------------------------
  106. //-------------------------------------------------------------------------------------------------
  107. #ifdef PERF_TIMERS
  108. //-------------------------------------------------------------------------------------------------
  109. //-------------------------------------------------------------------------------------------------
  110. //-------------------------------------------------------------------------------------------------
  111. /*static*/ Bool AutoPerfGatherIgnore::s_ignoring = false;
  112. //-------------------------------------------------------------------------------------------------
  113. typedef std::vector< std::pair< AsciiString, AsciiString > > StringPairVec;
  114. //-------------------------------------------------------------------------------------------------
  115. // PerfMetrics class. Basically, request a handle with your name and it will return. We use a vector
  116. // of pairs of asciistrings for this
  117. class PerfMetricsOutput
  118. {
  119. private:
  120. StringPairVec m_outputStats;
  121. public:
  122. AsciiString& getStatsString(const AsciiString& id)
  123. {
  124. for (int i = 0; i < m_outputStats.size(); ++i)
  125. {
  126. if (m_outputStats[i].first == id)
  127. return m_outputStats[i].second;
  128. }
  129. std::pair<AsciiString, AsciiString> newPair;
  130. newPair.first = id;
  131. m_outputStats.push_back(newPair);
  132. return m_outputStats.back().second;
  133. }
  134. void clearStatsString(const AsciiString& id)
  135. {
  136. for (int i = 0; i < m_outputStats.size(); ++i)
  137. {
  138. if (m_outputStats[i].first == id)
  139. {
  140. m_outputStats.erase(&m_outputStats[i]);
  141. return;
  142. }
  143. }
  144. }
  145. StringPairVec& friend_getAllStatsStrings() { return m_outputStats; }
  146. };
  147. //-------------------------------------------------------------------------------------------------
  148. static PerfMetricsOutput s_output;
  149. static FILE* s_perfStatsFile = NULL;
  150. static Int s_perfDumpOptions = 0;
  151. static UnsignedInt s_lastDumpedFrame = 0;
  152. static char s_buf[256] = "";
  153. PerfGather* PerfGather::m_active[MAX_ACTIVE_STACK] = { 0 };
  154. PerfGather** PerfGather::m_activeHead = &PerfGather::m_active[0];
  155. Int64 PerfGather::s_stopStartOverhead = -1;
  156. //-------------------------------------------------------------------------------------------------
  157. //-------------------------------------------------------------------------------------------------
  158. //-------------------------------------------------------------------------------------------------
  159. //-------------------------------------------------------------------------------------------------
  160. /*static*/ PerfGather*& PerfGather::getHeadPtr()
  161. {
  162. // funky technique for order-of-init problem. trust me. (srj)
  163. static PerfGather* s_head = NULL;
  164. return s_head;
  165. }
  166. //-------------------------------------------------------------------------------------------------
  167. void PerfGather::addToList()
  168. {
  169. PerfGather*& head = getHeadPtr();
  170. this->m_next = head;
  171. if (head)
  172. head->m_prev = this;
  173. head = this;
  174. }
  175. //-------------------------------------------------------------------------------------------------
  176. void PerfGather::removeFromList()
  177. {
  178. PerfGather*& head = getHeadPtr();
  179. if (this->m_next)
  180. this->m_next->m_prev = this->m_prev;
  181. if (this->m_prev)
  182. this->m_prev->m_next = this->m_next;
  183. else
  184. head = this->m_next;
  185. this->m_prev = 0;
  186. this->m_next = 0;
  187. }
  188. //-------------------------------------------------------------------------------------------------
  189. PerfGather::PerfGather(const char *identifier) :
  190. m_identifier(identifier),
  191. m_startTime(0),
  192. m_runningTimeGross(0),
  193. m_runningTimeNet(0),
  194. m_callCount(0),
  195. m_next(0),
  196. m_prev(0)
  197. {
  198. //Added By Sadullah Nader
  199. //Initializations inserted
  200. m_ignore = FALSE;
  201. //
  202. DEBUG_ASSERTCRASH(strchr(m_identifier, ',') == NULL, ("PerfGather names must not contain commas"));
  203. addToList();
  204. }
  205. //-------------------------------------------------------------------------------------------------
  206. PerfGather::~PerfGather()
  207. {
  208. removeFromList();
  209. }
  210. //-------------------------------------------------------------------------------------------------
  211. void PerfGather::reset()
  212. {
  213. m_startTime = 0;
  214. m_runningTimeGross = 0;
  215. m_runningTimeNet = 0;
  216. m_callCount = 0;
  217. }
  218. //-------------------------------------------------------------------------------------------------
  219. /*static*/ void PerfGather::resetAll()
  220. {
  221. for (PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  222. {
  223. head->reset();
  224. }
  225. }
  226. //-------------------------------------------------------------------------------------------------
  227. /*static*/ void PerfGather::initPerfDump(const char* fname, Int options)
  228. {
  229. PerfGather::termPerfDump();
  230. strcpy(s_buf, fname);
  231. char tmp[256];
  232. strcpy(tmp, s_buf);
  233. strcat(tmp, ".csv");
  234. s_perfStatsFile = fopen(tmp, "w");
  235. s_perfDumpOptions = options;
  236. if (s_perfStatsFile == NULL)
  237. {
  238. DEBUG_CRASH(("could not open/create perf file %s -- is it open in another app?",s_buf));
  239. return;
  240. }
  241. if (s_stopStartOverhead == -1)
  242. {
  243. const Int ITERS = 100000;
  244. Int64 start, end;
  245. PerfGather pf("timer");
  246. GetPrecisionTimer(&start);
  247. for (Int ii = 0; ii < ITERS; ++ii)
  248. {
  249. pf.startTimer(); pf.stopTimer();
  250. pf.startTimer(); pf.stopTimer();
  251. pf.startTimer(); pf.stopTimer();
  252. pf.startTimer(); pf.stopTimer();
  253. pf.startTimer(); pf.stopTimer();
  254. pf.startTimer(); pf.stopTimer();
  255. pf.startTimer(); pf.stopTimer();
  256. pf.startTimer(); pf.stopTimer();
  257. }
  258. GetPrecisionTimer(&end);
  259. s_stopStartOverhead = (end - start) / (ITERS*8);
  260. DEBUG_LOG(("s_stopStartOverhead is %d (%f usec)\n",(int)s_stopStartOverhead,s_stopStartOverhead/s_ticksPerUSec));
  261. }
  262. }
  263. //-------------------------------------------------------------------------------------------------
  264. /*static*/ void PerfGather::dumpAll(UnsignedInt frame)
  265. {
  266. if (frame < s_lastDumpedFrame)
  267. {
  268. // must have reset or started a new game.
  269. termPerfDump();
  270. initPerfDump(s_buf, s_perfDumpOptions);
  271. }
  272. if (!s_perfStatsFile)
  273. {
  274. DEBUG_CRASH(("not inited"));
  275. return;
  276. }
  277. if (frame >= 1 && frame <= 30)
  278. {
  279. // always skip the first second or so, since it loads everything and skews the results horribly
  280. }
  281. else
  282. {
  283. if (s_lastDumpedFrame == 0)
  284. {
  285. fprintf(s_perfStatsFile, "Frame");
  286. if (s_perfDumpOptions & PERF_GROSSTIME)
  287. {
  288. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  289. {
  290. fprintf(s_perfStatsFile, ",Gross:%s", head->m_identifier);
  291. }
  292. }
  293. if (s_perfDumpOptions & PERF_NETTIME)
  294. {
  295. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  296. {
  297. fprintf(s_perfStatsFile, ",Net:%s", head->m_identifier);
  298. }
  299. }
  300. if (s_perfDumpOptions & PERF_CALLCOUNT)
  301. {
  302. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  303. {
  304. fprintf(s_perfStatsFile, ",Count:%s", head->m_identifier);
  305. }
  306. }
  307. fprintf(s_perfStatsFile, "\n");
  308. }
  309. // a strange value so we can find it in the dump, if necessary.
  310. // there's nothing magic about this value, it's purely determined from sample dumps...
  311. // const Real CLIP_BIG_SPIKES = 1e10f;
  312. const Real CLIP_BIG_SPIKES = 100000.0f;
  313. // make this a nonnumeric thing so Excel won't try to graph it...
  314. fprintf(s_perfStatsFile, "Frame%08d", frame);
  315. if (s_perfDumpOptions & PERF_GROSSTIME)
  316. {
  317. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  318. {
  319. double t = head->m_runningTimeGross;
  320. t /= s_ticksPerUSec;
  321. if (t > CLIP_BIG_SPIKES)
  322. t = CLIP_BIG_SPIKES;
  323. fprintf(s_perfStatsFile, ",%f", t);
  324. }
  325. }
  326. if (s_perfDumpOptions & PERF_NETTIME)
  327. {
  328. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  329. {
  330. double t = head->m_runningTimeNet;
  331. t /= s_ticksPerUSec;
  332. if (t > CLIP_BIG_SPIKES)
  333. t = CLIP_BIG_SPIKES;
  334. fprintf(s_perfStatsFile, ",%f", t);
  335. }
  336. }
  337. if (s_perfDumpOptions & PERF_CALLCOUNT)
  338. {
  339. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  340. {
  341. fprintf(s_perfStatsFile, ",%d", head->m_callCount);
  342. }
  343. }
  344. fprintf(s_perfStatsFile, "\n");
  345. fflush(s_perfStatsFile);
  346. s_lastDumpedFrame = frame;
  347. }
  348. }
  349. //-------------------------------------------------------------------------------------------------
  350. // This function will queue up stuff to draw on the next frame. We also need to adjust the
  351. // perf timers to not include time spent paused by the script engine.
  352. /*static*/ void PerfGather::displayGraph(UnsignedInt frame)
  353. {
  354. if (!TheGraphDraw) {
  355. return;
  356. }
  357. if (frame >= 1 && frame <= 30)
  358. {
  359. // always skip the first second or so, since it loads everything and skews the results horribly
  360. }
  361. else
  362. {
  363. const Real CLIP_BIG_SPIKES = 100000.0f;
  364. if (s_perfDumpOptions & PERF_GROSSTIME)
  365. {
  366. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  367. {
  368. Real t = head->m_runningTimeGross;
  369. t /= s_ticksPerUSec;
  370. if (t > CLIP_BIG_SPIKES)
  371. t = CLIP_BIG_SPIKES;
  372. TheGraphDraw->addEntry(head->m_identifier, REAL_TO_INT(t));
  373. }
  374. }
  375. if (s_perfDumpOptions & PERF_NETTIME)
  376. {
  377. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  378. {
  379. Real t = head->m_runningTimeNet;
  380. t /= s_ticksPerUSec;
  381. if (t > CLIP_BIG_SPIKES)
  382. t = CLIP_BIG_SPIKES;
  383. TheGraphDraw->addEntry(head->m_identifier, REAL_TO_INT(t));
  384. }
  385. }
  386. if (s_perfDumpOptions & PERF_CALLCOUNT)
  387. {
  388. for (const PerfGather* head = getHeadPtr(); head != NULL; head = head->m_next)
  389. {
  390. Real t = head->m_callCount;
  391. TheGraphDraw->addEntry(head->m_identifier, REAL_TO_INT(t));
  392. }
  393. }
  394. }
  395. }
  396. //-------------------------------------------------------------------------------------------------
  397. /*static*/ void PerfGather::termPerfDump()
  398. {
  399. if (s_perfStatsFile)
  400. {
  401. fflush(s_perfStatsFile);
  402. fclose(s_perfStatsFile);
  403. s_perfStatsFile = NULL;
  404. }
  405. s_lastDumpedFrame = 0;
  406. }
  407. //-------------------------------------------------------------------------------------------------
  408. //-------------------------------------------------------------------------------------------------
  409. //-------------------------------------------------------------------------------------------------
  410. //-------------------------------------------------------------------------------------------------
  411. PerfTimer::PerfTimer( const char *identifier, Bool crashWithInfo, Int startFrame, Int endFrame) :
  412. m_identifier(identifier),
  413. m_crashWithInfo(crashWithInfo),
  414. m_startFrame(startFrame),
  415. m_endFrame(endFrame),
  416. m_callCount(0),
  417. m_runningTime(0),
  418. m_outputInfo(true),
  419. //Added By Sadullah Nader
  420. //Intializations inserted
  421. m_lastFrame(-1)
  422. {
  423. }
  424. //-------------------------------------------------------------------------------------------------
  425. PerfTimer::~PerfTimer( )
  426. {
  427. if (m_endFrame == -1) {
  428. outputInfo();
  429. }
  430. }
  431. //-------------------------------------------------------------------------------------------------
  432. void PerfTimer::outputInfo( void )
  433. {
  434. if (TheGlobalData->m_showMetrics) {
  435. return;
  436. }
  437. if (m_outputInfo && !TheGlobalData->m_showMetrics) {
  438. m_outputInfo = false;
  439. } else {
  440. return;
  441. }
  442. if (!s_ticksPerSec) {
  443. // DEBUG_CRASH here
  444. return;
  445. }
  446. #if defined(_DEBUG) || defined(_INTERNAL)
  447. double totalTimeInMS = 1000.0 * m_runningTime / s_ticksPerSec;
  448. double avgTimePerFrame = totalTimeInMS / (m_lastFrame - m_startFrame + 1);
  449. double avgTimePerCall = totalTimeInMS / m_callCount;
  450. #endif
  451. if (m_crashWithInfo) {
  452. DEBUG_CRASH(("%s\n"
  453. "Average Time (per call): %.4f ms\n"
  454. "Average Time (per frame): %.4f ms\n"
  455. "Average calls per frame: %.2f\n"
  456. "Number of calls: %d\n"
  457. "Max possible FPS: %.4f\n",
  458. m_identifier,
  459. avgTimePerCall,
  460. avgTimePerFrame,
  461. 1.0f * m_callCount / (m_lastFrame - m_startFrame + 1),
  462. m_callCount,
  463. 1000.0f / avgTimePerFrame));
  464. } else {
  465. DEBUG_LOG(("%s\n"
  466. "Average Time (per call): %.4f ms\n"
  467. "Average Time (per frame): %.4f ms\n"
  468. "Average calls per frame: %.2f\n"
  469. "Number of calls: %d\n"
  470. "Max possible FPS: %.4f\n",
  471. m_identifier,
  472. avgTimePerCall,
  473. avgTimePerFrame,
  474. 1.0f * m_callCount / (m_lastFrame - m_startFrame + 1),
  475. m_callCount,
  476. 1000.0f / avgTimePerFrame));
  477. }
  478. }
  479. //-------------------------------------------------------------------------------------------------
  480. void PerfTimer::showMetrics( void )
  481. {
  482. #if defined(_DEBUG) || defined(_INTERNAL)
  483. double totalTimeInMS = 1000.0 * m_runningTime / s_ticksPerSec;
  484. double avgTimePerFrame = totalTimeInMS / (m_lastFrame - m_startFrame + 1);
  485. double avgTimePerCall = totalTimeInMS / m_callCount;
  486. #endif
  487. // we want to work on the thing in the array, so just store a reference.
  488. AsciiString &outputStats = s_output.getStatsString(m_identifier);
  489. outputStats.format("%s: %.2fms / call, %.2fms / frame \n",
  490. m_identifier,
  491. avgTimePerCall,
  492. avgTimePerFrame);
  493. m_callCount = 0;
  494. m_runningTime = 0;
  495. UnsignedInt frm = (TheGameLogic ? TheGameLogic->getFrame() : m_startFrame);
  496. m_startFrame = frm + 1;
  497. m_endFrame = m_startFrame + PERFMETRICS_BETWEEN_METRICS;
  498. }
  499. //-------------------------------------------------------------------------------StatMetricsDisplay
  500. void StatMetricsDisplay( DebugDisplayInterface *dd, void *, FILE *fp )
  501. {
  502. dd->printf("Performance Metrics: \n");
  503. // no copies will take place because we are storing a reference to the thing
  504. StringPairVec &stats = s_output.friend_getAllStatsStrings();
  505. for (int i = 0; i < stats.size(); ++i) {
  506. dd->printf("%s", stats[i].second.str());
  507. }
  508. }
  509. #endif // PERF_TIMERS