Counter Strike : Global Offensive Source Code
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.

1226 lines
28 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #undef PROTECT_FILEIO_FUNCTIONS
  7. #include "tier0/vprof.h"
  8. #include "utldict.h"
  9. #include "client.h"
  10. #include "cmd.h"
  11. #include "filesystem_engine.h"
  12. #include "vprof_record.h"
  13. #include "tier1/byteswap.h"
  14. #ifdef VPROF_ENABLED
  15. CVProfile *g_pVProfileForDisplay = &g_VProfCurrentProfile;
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. long GetFileSize( FILE *fp )
  19. {
  20. int curPos = ftell( fp );
  21. fseek( fp, 0, SEEK_END );
  22. long ret = ftell( fp );
  23. fseek( fp, curPos, SEEK_SET );
  24. return ret;
  25. }
  26. // ------------------------------------------------------------------------------------------------------------------------------------ //
  27. // VProf record mode. Turn it on to record all the vprof data, then when you're playing back, the engine's budget and vprof panels
  28. // show the data from the recording instead of the real data.
  29. // ------------------------------------------------------------------------------------------------------------------------------------ //
  30. class CVProfRecorder : public CVProfile
  31. {
  32. public:
  33. CVProfRecorder()
  34. {
  35. m_Mode = Mode_None;
  36. m_hFile = NULL;
  37. m_nQueuedStarts = 0;
  38. m_nQueuedStops = 0;
  39. m_iPlaybackTick = -1;
  40. // Set up byte-swapping for this platform so that we can query later if we need to swap on reading and writing or not.
  41. m_Byteswap.SetTargetBigEndian( false );
  42. }
  43. ~CVProfRecorder()
  44. {
  45. Assert( m_Mode == Mode_None );
  46. }
  47. template <typename T> void Write( T *pData )
  48. {
  49. if ( m_Byteswap.IsSwappingBytes() )
  50. {
  51. T swapped;
  52. m_Byteswap.SwapBuffer( &swapped, pData );
  53. g_pFileSystem->Write( &swapped, sizeof( T ), m_hFile );
  54. }
  55. else
  56. {
  57. g_pFileSystem->Write( pData, sizeof( T ), m_hFile );
  58. }
  59. }
  60. template <typename T> int Read( T *pData )
  61. {
  62. int ret;
  63. if ( m_Byteswap.IsSwappingBytes() )
  64. {
  65. T tmp;
  66. ret = g_pFileSystem->Read( &tmp, sizeof( T ), m_hFile );
  67. m_Byteswap.SwapBuffer( pData, &tmp );
  68. }
  69. else
  70. {
  71. ret = g_pFileSystem->Read( pData, sizeof( T ), m_hFile );
  72. }
  73. return ret;
  74. }
  75. void Shutdown()
  76. {
  77. Stop();
  78. }
  79. void Stop()
  80. {
  81. if ( (m_Mode == Mode_Record || m_Mode == Mode_Playback) && m_hFile != NULL )
  82. {
  83. if ( m_Mode == Mode_Record )
  84. ++m_nQueuedStops;
  85. g_pFileSystem->Close( m_hFile );
  86. }
  87. m_Mode = Mode_None;
  88. m_hFile = NULL;
  89. g_pVProfileForDisplay = &g_VProfCurrentProfile; // Stop using us for vprofile displays.
  90. m_iPlaybackTick = -1;
  91. m_bNodesChanged = true;
  92. Term(); // clear the vprof data
  93. }
  94. bool IsPlayingBack()
  95. {
  96. return m_Mode == Mode_Playback;
  97. }
  98. // RECORD FUNCTIONS.
  99. public:
  100. bool Record_Start( const char *pFilename )
  101. {
  102. Stop();
  103. char tempFilename[512];
  104. if ( !strchr( pFilename, '.' ) )
  105. {
  106. Q_snprintf( tempFilename, sizeof( tempFilename ), "%s.vprof", pFilename );
  107. pFilename = tempFilename;
  108. }
  109. m_iLastUniqueNodeID = -1;
  110. m_hFile = g_pFileSystem->Open( pFilename, "wb" );
  111. m_Mode = Mode_Record;
  112. if ( m_hFile == NULL )
  113. {
  114. return false;
  115. }
  116. else
  117. {
  118. // Write the version number.
  119. int version = VPROF_FILE_VERSION;
  120. Write( &version );
  121. // Write the root node ID.
  122. int nodeID = g_VProfCurrentProfile.GetRoot()->GetUniqueNodeID();
  123. Write( &nodeID );
  124. ++m_nQueuedStarts;
  125. // Make sure vprof is recrding.
  126. Cbuf_AddText( Cbuf_GetCurrentPlayer(), "vprof_on\n" );
  127. return true;
  128. }
  129. }
  130. void Record_WriteToken( char val )
  131. {
  132. Write( &val );
  133. }
  134. void Record_MatchTree_R( CVProfNode *pOut, const CVProfNode *pIn, CVProfile *pInProfile )
  135. {
  136. // Add any new nodes at the beginning of the list..
  137. if ( pIn->m_pChild )
  138. {
  139. while ( !pOut->m_pChild || pIn->m_pChild->GetUniqueNodeID() != pOut->m_pChild->GetUniqueNodeID() )
  140. {
  141. // Find the last new node in the list.
  142. const CVProfNode *pToAdd = NULL;
  143. const CVProfNode *pCur = pIn->m_pChild;
  144. while ( pCur )
  145. {
  146. // If the out node has no children then we add the last one in the input node.
  147. if ( pOut->m_pChild && pCur->GetUniqueNodeID() == pOut->m_pChild->GetUniqueNodeID() )
  148. break;
  149. pToAdd = pCur;
  150. pCur = pCur->m_pSibling;
  151. }
  152. Assert( pToAdd );
  153. // Write this to the file.
  154. int budgetGroupID = pToAdd->m_BudgetGroupID;
  155. int parentNodeID = pIn->GetUniqueNodeID();
  156. int nodeID = pToAdd->GetUniqueNodeID();
  157. Record_WriteToken( Token_AddNode );
  158. Write( &parentNodeID ); // Parent node ID.
  159. WriteString( pToAdd->m_pszName ); // Name of the new node.
  160. Write( &budgetGroupID );
  161. Write( &nodeID );
  162. // There's a new one here.
  163. const char *pBudgetGroupName = g_VProfCurrentProfile.GetBudgetGroupName( pToAdd->m_BudgetGroupID );
  164. int budgetGroupFlags = g_VProfCurrentProfile.GetBudgetGroupFlags( pToAdd->m_BudgetGroupID );
  165. CVProfNode *pNewNode = pOut->GetSubNode( pToAdd->m_pszName, 0, pBudgetGroupName, budgetGroupFlags );
  166. pNewNode->SetBudgetGroupID( pToAdd->m_BudgetGroupID );
  167. pNewNode->SetUniqueNodeID( pToAdd->GetUniqueNodeID() );
  168. }
  169. }
  170. // Recurse.
  171. CVProfNode *pOutChild = pOut->m_pChild;
  172. const CVProfNode *pInChild = pIn->m_pChild;
  173. while ( pOutChild && pInChild )
  174. {
  175. Assert( Q_stricmp( pInChild->m_pszName, pOutChild->m_pszName ) == 0 );
  176. Assert( pInChild->GetUniqueNodeID() == pOutChild->GetUniqueNodeID() );
  177. Record_MatchTree_R( pOutChild, pInChild, pInProfile );
  178. pOutChild = pOutChild->m_pSibling;
  179. pInChild = pInChild->m_pSibling;
  180. }
  181. }
  182. void Record_MatchBudgetGroups( CVProfile *pInProfile )
  183. {
  184. Assert( GetNumBudgetGroups() <= pInProfile->GetNumBudgetGroups() );
  185. int nOriginalGroups = GetNumBudgetGroups();
  186. for ( int i=nOriginalGroups; i < pInProfile->GetNumBudgetGroups(); i++ )
  187. {
  188. const char *pName = pInProfile->GetBudgetGroupName( i );
  189. int flags = pInProfile->GetBudgetGroupFlags( i );
  190. Record_WriteToken( Token_AddBudgetGroup );
  191. WriteString( pName );
  192. Write( &flags );
  193. AddBudgetGroupName( pName, flags );
  194. }
  195. }
  196. void Record_WriteTimings_R( const CVProfNode *pIn )
  197. {
  198. unsigned short curCalls = MIN( pIn->m_nCurFrameCalls, 0xFFFF );
  199. if ( curCalls >= 255 )
  200. {
  201. unsigned char token = 255;
  202. Write( &token );
  203. Write( &curCalls );
  204. }
  205. else
  206. {
  207. // Get away with one byte if we can.
  208. unsigned char token = (char)curCalls;
  209. Write( &token );
  210. }
  211. // This allows us to write 2 bytes unless it's > 256 milliseconds (unlikely).
  212. unsigned long nMicroseconds = pIn->m_CurFrameTime.GetMicroseconds() / 4;
  213. if ( nMicroseconds >= 0xFFFF )
  214. {
  215. unsigned short token = 0xFFFF;
  216. Write( &token );
  217. Write( &nMicroseconds );
  218. }
  219. else
  220. {
  221. unsigned short token = (unsigned short)nMicroseconds;
  222. Write( &token );
  223. }
  224. for ( const CVProfNode *pChild = pIn->m_pChild; pChild; pChild = pChild->m_pSibling )
  225. Record_WriteTimings_R( pChild );
  226. }
  227. void Record_Snapshot()
  228. {
  229. CVProfile *pInProfile = &g_VProfCurrentProfile;
  230. // Don't record the overhead of writing in the filesystem here.
  231. pInProfile->Pause();
  232. // Record the tick count and start of frame.
  233. Record_WriteToken( Token_StartFrame );
  234. #ifdef DEDICATED
  235. Write( &host_tickcount );
  236. #else
  237. Write( &g_ClientGlobalVariables.tickcount );
  238. #endif
  239. // Record all the changes to get our tree and budget groups to g_VProfCurrentProfile.
  240. Record_MatchBudgetGroups( pInProfile );
  241. if ( m_iLastUniqueNodeID != CVProfNode::s_iCurrentUniqueNodeID )
  242. {
  243. Record_MatchTree_R( GetRoot(), pInProfile->GetRoot(), pInProfile );
  244. }
  245. // Now that we have a matching tree, write all the timings.
  246. Record_WriteToken( Token_Timings );
  247. Record_WriteTimings_R( pInProfile->GetRoot() );
  248. Record_WriteToken( Token_EndOfFrame );
  249. pInProfile->Resume();
  250. }
  251. // PLAYBACK FUNCTIONS.
  252. public:
  253. #define Playback_Assert( theTest ) Playback_AssertFn( !!(theTest), __LINE__ )
  254. bool Playback_AssertFn( bool bTest, int iLine )
  255. {
  256. if ( bTest )
  257. {
  258. return true;
  259. }
  260. else
  261. {
  262. Stop();
  263. Warning( "VPROF PLAYBACK ASSERT (%s, line %d) - stopping playback.\n", __FILE__, iLine );
  264. Assert( 0 );
  265. return false;
  266. }
  267. }
  268. bool Playback_Start( const char *pFilename )
  269. {
  270. Stop();
  271. char tempFilename[512];
  272. if ( !strchr( pFilename, '.' ) )
  273. {
  274. Q_snprintf( tempFilename, sizeof( tempFilename ), "%s.vprof", pFilename );
  275. pFilename = tempFilename;
  276. }
  277. m_iLastUniqueNodeID = -1;
  278. m_hFile = g_pFileSystem->Open( pFilename, "rb" );
  279. m_Mode = Mode_Playback;
  280. m_bPlaybackPaused = true;
  281. if ( m_hFile == NULL )
  282. {
  283. Warning( "vprof_playback_start: Open( %s ) failed.\n", pFilename );
  284. return false;
  285. }
  286. else
  287. {
  288. int version;
  289. Read( &version );
  290. if ( !Playback_Assert( version == VPROF_FILE_VERSION ) )
  291. return false;
  292. // Read the root node ID.
  293. int nodeID;
  294. Read( &nodeID );
  295. GetRoot()->SetUniqueNodeID( nodeID );
  296. m_iSkipPastHeaderPos = g_pFileSystem->Tell( m_hFile );
  297. m_bPlaybackFinished = false;
  298. m_FileLen = g_pFileSystem->Size( m_hFile );
  299. m_enabled = true; // So IsEnabled() returns true..
  300. Playback_ReadTick();
  301. g_pVProfileForDisplay = this; // Start using this CVProfile for displays.
  302. return true;
  303. }
  304. }
  305. void Playback_Restart()
  306. {
  307. if ( m_Mode != Mode_Playback )
  308. {
  309. Assert( false );
  310. return;
  311. }
  312. // Clear the data and restart playback.
  313. m_iPlaybackTick = -1;
  314. Term(); // clear the vprof data
  315. m_bNodesChanged = true;
  316. g_pFileSystem->Seek( m_hFile, m_iSkipPastHeaderPos, FILESYSTEM_SEEK_HEAD );
  317. Playback_ReadTick(); // Read in one tick's worth of data.
  318. }
  319. char Playback_ReadToken()
  320. {
  321. Assert( m_Mode == Mode_Playback );
  322. char token;
  323. if ( Read( &token ) != 1 )
  324. token = TOKEN_FILE_FINISHED;
  325. return token;
  326. }
  327. void WriteString( const char *pStr )
  328. {
  329. g_pFileSystem->Write( pStr, strlen( pStr ) + 1, m_hFile );
  330. }
  331. bool Playback_ReadString( char *pOut, int maxLen )
  332. {
  333. int i = 0;
  334. while ( 1 )
  335. {
  336. char ch;
  337. if ( g_pFileSystem->Read( &ch, 1, m_hFile ) == 0 )
  338. {
  339. Playback_Assert( false );
  340. return false;
  341. }
  342. if ( ch == 0 )
  343. {
  344. pOut[i] = 0;
  345. break;
  346. }
  347. else
  348. {
  349. if ( i < (maxLen-1) )
  350. {
  351. pOut[i] = ch;
  352. ++i;
  353. }
  354. }
  355. }
  356. return true;
  357. }
  358. bool Playback_ReadAddBudgetGroup()
  359. {
  360. char name[512];
  361. if ( !Playback_ReadString( name, sizeof( name ) ) )
  362. return false;
  363. int flags = 0;
  364. Read( &flags );
  365. AddBudgetGroupName( name, flags );
  366. return true;
  367. }
  368. CVProfNode* FindVProfNodeByID_R( CVProfNode *pNode, int id )
  369. {
  370. if ( pNode->GetUniqueNodeID() == id )
  371. return pNode;
  372. for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
  373. {
  374. CVProfNode *pTest = FindVProfNodeByID_R( pCur, id );
  375. if ( pTest )
  376. return pTest;
  377. }
  378. return NULL;
  379. }
  380. bool Playback_ReadAddNode()
  381. {
  382. int budgetGroupID;
  383. int parentNodeID;
  384. int nodeID;
  385. char nodeName[512];
  386. Read( &parentNodeID ); // Parent node ID.
  387. if ( !Playback_ReadString( nodeName, sizeof( nodeName ) ) )
  388. return false;
  389. Read( &budgetGroupID );
  390. Read( &nodeID );
  391. // Now find the parent node.
  392. CVProfNode *pParentNode = FindVProfNodeByID_R( GetRoot(), parentNodeID );
  393. if ( !Playback_Assert( pParentNode != NULL ) )
  394. return false;
  395. const char *pBudgetGroupName = GetBudgetGroupName( 0 );
  396. int budgetGroupFlags = 0;
  397. CVProfNode *pNewNode = pParentNode->GetSubNode( PoolString( nodeName ), 0, pBudgetGroupName, budgetGroupFlags );
  398. pNewNode->SetBudgetGroupID( budgetGroupID );
  399. pNewNode->SetUniqueNodeID( nodeID );
  400. m_bNodesChanged = true;
  401. return true;
  402. }
  403. bool Playback_ReadTimings_R( CVProfNode *pNode )
  404. {
  405. // Read the timing.
  406. unsigned char token;
  407. if ( Read( &token ) != sizeof( token ) )
  408. return false;
  409. if ( token == 255 )
  410. {
  411. unsigned short curCalls;
  412. if ( Read( &curCalls ) != sizeof( curCalls ) )
  413. return false;
  414. pNode->m_nCurFrameCalls = curCalls;
  415. }
  416. else
  417. {
  418. pNode->m_nCurFrameCalls = token;
  419. }
  420. pNode->m_nPrevFrameCalls = pNode->m_nCurFrameCalls;
  421. // This allows us to write 2 bytes unless it's > 256 milliseconds (unlikely).
  422. unsigned short microsecondsToken;
  423. if ( Read( &microsecondsToken ) != sizeof( microsecondsToken ) )
  424. return false;
  425. if ( microsecondsToken == 0xFFFF )
  426. {
  427. unsigned long nMicroseconds;
  428. if ( Read( &nMicroseconds ) != sizeof( nMicroseconds ) )
  429. return false;
  430. pNode->m_CurFrameTime.SetMicroseconds( nMicroseconds * 4 );
  431. }
  432. else
  433. {
  434. pNode->m_CurFrameTime.SetMicroseconds( (unsigned long)microsecondsToken * 4 );
  435. }
  436. pNode->m_PrevFrameTime = pNode->m_CurFrameTime;
  437. // Recurse.
  438. for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
  439. {
  440. if ( !Playback_ReadTimings_R( pCur ) )
  441. return false;
  442. }
  443. return true;
  444. }
  445. // Read the next tick. If iDontGoPast is set, then it will abort IF the next tick's index
  446. // is greater than iDontGoPast. In that case, sets pWouldHaveGonePast to true,
  447. // stays where it was before the call, and returns true.
  448. bool Playback_ReadTick( int iDontGoPast = -1, bool *pWouldHaveGonePast = NULL )
  449. {
  450. if ( pWouldHaveGonePast )
  451. *pWouldHaveGonePast = false;
  452. if ( m_Mode != Mode_Playback )
  453. return false;
  454. // Read the next tick..
  455. int token = Playback_ReadToken();
  456. if ( token == TOKEN_FILE_FINISHED )
  457. {
  458. Msg( "VPROF playback finished.\n" );
  459. m_bPlaybackFinished = true; // Now we know our last tick.
  460. return true;
  461. }
  462. if ( !Playback_Assert( token == Token_StartFrame ) )
  463. return false;
  464. int iPlaybackTick = m_iPlaybackTick;
  465. Read( &iPlaybackTick );
  466. // First test if this tick would go past the number they don't want us to go past.
  467. if ( iDontGoPast != -1 && iPlaybackTick > iDontGoPast )
  468. {
  469. *pWouldHaveGonePast = true;
  470. g_pFileSystem->Seek( m_hFile, -5, FILESYSTEM_SEEK_CURRENT );
  471. return true;
  472. }
  473. else
  474. {
  475. m_iPlaybackTick = iPlaybackTick;
  476. }
  477. while ( 1 )
  478. {
  479. int token = Playback_ReadToken();
  480. if ( token == Token_EndOfFrame )
  481. break;
  482. if ( token == Token_AddBudgetGroup )
  483. {
  484. if ( !Playback_ReadAddBudgetGroup() )
  485. return false;
  486. }
  487. else if ( token == Token_AddNode )
  488. {
  489. if ( !Playback_ReadAddNode() )
  490. return false;
  491. }
  492. else if ( token == Token_Timings )
  493. {
  494. if ( !Playback_ReadTimings_R( GetRoot() ) )
  495. return false;
  496. }
  497. else
  498. {
  499. Playback_Assert( false );
  500. return false;
  501. }
  502. }
  503. return true;
  504. }
  505. void Playback_Snapshot()
  506. {
  507. if ( m_Mode == Mode_Playback && !m_bPlaybackPaused )
  508. Playback_ReadTick();
  509. }
  510. void Playback_Step()
  511. {
  512. Playback_ReadTick();
  513. }
  514. class CNodeAverage
  515. {
  516. public:
  517. CVProfNode *m_pNode;
  518. CCycleCount m_CurFrameTime_Total;
  519. int m_nCurFrameCalls_Total;
  520. int m_nSamples;
  521. };
  522. CNodeAverage* FindNodeAverage( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
  523. {
  524. for ( int i=0; i < averages.Count(); i++ )
  525. {
  526. if ( averages[i].m_pNode == pNode )
  527. return &averages[i];
  528. }
  529. return NULL;
  530. }
  531. void UpdateAverages_R( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
  532. {
  533. CNodeAverage *pAverage = FindNodeAverage( averages, pNode );
  534. if ( !pAverage )
  535. {
  536. pAverage = &averages[ averages.AddToTail() ];
  537. memset( pAverage, 0, sizeof( *pAverage ) );
  538. pAverage->m_pNode = pNode;
  539. }
  540. pAverage->m_CurFrameTime_Total += pNode->m_CurFrameTime;
  541. pAverage->m_nCurFrameCalls_Total += pNode->m_nCurFrameCalls;
  542. pAverage->m_nSamples++;
  543. // Recurse.
  544. for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
  545. UpdateAverages_R( averages, pCur );
  546. }
  547. void DumpAverages_R( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
  548. {
  549. CNodeAverage *pAverage = FindNodeAverage( averages, pNode );
  550. if ( pAverage )
  551. {
  552. pNode->m_CurFrameTime.m_Int64 = pAverage->m_CurFrameTime_Total.m_Int64 / pAverage->m_nSamples;
  553. pNode->m_nCurFrameCalls = pAverage->m_nCurFrameCalls_Total / pAverage->m_nSamples;
  554. }
  555. pNode->m_PrevFrameTime = pNode->m_CurFrameTime;
  556. pNode->m_nPrevFrameCalls = pNode->m_nCurFrameCalls;
  557. // Recurse.
  558. for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
  559. DumpAverages_R( averages, pCur );
  560. }
  561. void Playback_Average( int nFrames )
  562. {
  563. // Remember where we started.
  564. unsigned long seekPos = g_pFileSystem->Tell( m_hFile );
  565. int iOldPlaybackTick = m_iPlaybackTick;
  566. // Take the average of the next N ticks.
  567. CUtlVector<CNodeAverage> averages;
  568. while ( nFrames > 0 && !m_bPlaybackFinished )
  569. {
  570. Playback_ReadTick();
  571. UpdateAverages_R( averages, GetRoot() );
  572. --nFrames;
  573. }
  574. DumpAverages_R( averages, GetRoot() );
  575. // Now seek back to where we started.
  576. g_pFileSystem->Seek( m_hFile, seekPos, FILESYSTEM_SEEK_HEAD );
  577. m_iPlaybackTick = iOldPlaybackTick;
  578. }
  579. int Playback_SetPlaybackTick( int iTick )
  580. {
  581. if ( m_Mode != Mode_Playback )
  582. return 0;
  583. m_bNodesChanged = false; // We want to pickup changes to this, so reset it here.
  584. if ( iTick == m_iPlaybackTick )
  585. {
  586. return 1;
  587. }
  588. else if ( iTick < m_iPlaybackTick )
  589. {
  590. // Crap.. have to go back. Restart and seek to this tick.
  591. Playback_Restart();
  592. // If this tick has a smaller value than the first tick in the file, then we can't seek forward to it...
  593. if ( iTick <= m_iPlaybackTick )
  594. {
  595. return 1 + m_bNodesChanged; // return 2 if the nodes changed
  596. }
  597. }
  598. // Now seek forward to the tick they want.
  599. while ( m_iPlaybackTick < iTick )
  600. {
  601. bool bWouldHaveGonePast = false;
  602. if ( !Playback_ReadTick( iTick, &bWouldHaveGonePast ) )
  603. return 0; // error
  604. // If reading this tick would have gone past the tick they're asking us to go for,
  605. // stay on the current tick.
  606. if ( bWouldHaveGonePast )
  607. break;
  608. // If we went to the last tick in the file, then stop here.
  609. if ( m_bPlaybackFinished )
  610. return 1 + m_bNodesChanged;
  611. }
  612. return 1 + m_bNodesChanged;
  613. }
  614. // 0-1 value.
  615. float Playback_GetCurrentPercent()
  616. {
  617. return (float)g_pFileSystem->Tell( m_hFile ) / m_FileLen;
  618. }
  619. int Playback_SeekToPercent( float flWantedPercent )
  620. {
  621. if ( m_Mode != Mode_Playback )
  622. return 0; // error
  623. m_bNodesChanged = false; // We want to pickup changes to this, so reset it here.
  624. float flCurPercent = Playback_GetCurrentPercent();
  625. if ( flWantedPercent < flCurPercent )
  626. {
  627. // Crap.. have to go back. Restart and seek to this tick.
  628. Playback_Restart();
  629. // If this tick has a smaller value than the first tick in the file, then we can't seek forward to it...
  630. if ( flWantedPercent <= 0 )
  631. return 1 + m_bNodesChanged; // return 2 if nodes changed
  632. }
  633. // Now seek forward to the tick they want.
  634. while ( Playback_GetCurrentPercent() < flWantedPercent )
  635. {
  636. if ( !Playback_ReadTick() )
  637. return 0; // error
  638. // If we went to the last tick in the file, then stop here.
  639. if ( m_bPlaybackFinished )
  640. return 1 + m_bNodesChanged; // return 2 if nodes changed
  641. }
  642. return 1 + m_bNodesChanged;
  643. }
  644. int Playback_GetCurrentTick()
  645. {
  646. return m_iPlaybackTick;
  647. }
  648. // OTHER FUNCTIONS.
  649. public:
  650. void Snapshot()
  651. {
  652. if ( m_Mode == Mode_Record )
  653. Record_Snapshot();
  654. else if ( m_Mode == Mode_Playback )
  655. Playback_Snapshot();
  656. }
  657. void StartOrStop()
  658. {
  659. while ( m_nQueuedStarts > 0 )
  660. {
  661. --m_nQueuedStarts;
  662. g_VProfCurrentProfile.Start();
  663. }
  664. while ( m_nQueuedStops > 0 )
  665. {
  666. --m_nQueuedStops;
  667. g_VProfCurrentProfile.Stop();
  668. }
  669. }
  670. inline CVProfile* GetActiveProfile()
  671. {
  672. if ( m_Mode == Mode_Playback )
  673. return this;
  674. else
  675. return &g_VProfCurrentProfile;
  676. }
  677. bool IsPlaybackFinished()
  678. {
  679. return m_bPlaybackFinished;
  680. }
  681. private:
  682. const char* PoolString( const char *pStr )
  683. {
  684. int i = m_PooledStrings.Find( pStr );
  685. if ( i == m_PooledStrings.InvalidIndex() )
  686. i = m_PooledStrings.Insert( pStr, 0 );
  687. return m_PooledStrings.GetElementName( i );
  688. }
  689. private:
  690. enum
  691. {
  692. Token_StartFrame=0,
  693. Token_AddNode,
  694. Token_AddBudgetGroup,
  695. Token_Timings,
  696. Token_EndOfFrame,
  697. TOKEN_FILE_FINISHED
  698. };
  699. enum
  700. {
  701. VPROF_FILE_VERSION = 1
  702. };
  703. enum
  704. {
  705. Mode_None,
  706. Mode_Record,
  707. Mode_Playback
  708. };
  709. CUtlDict<int,int> m_PooledStrings;
  710. int m_Mode;
  711. FileHandle_t m_hFile;
  712. int m_iLastUniqueNodeID;
  713. int m_iPlaybackTick; // Our current tick.
  714. int m_iSkipPastHeaderPos;
  715. bool m_bPlaybackFinished;
  716. int m_FileLen;
  717. bool m_bNodesChanged; // Set if the nodes were added or removed.
  718. int m_nQueuedStarts;
  719. int m_nQueuedStops;
  720. bool m_bPlaybackPaused;
  721. CByteswap m_Byteswap;
  722. };
  723. static CVProfRecorder g_VProfRecorder;
  724. void VProf_StartRecording( const char *pFilename )
  725. {
  726. g_VProfRecorder.Record_Start( pFilename );
  727. }
  728. void VProf_StopRecording( void )
  729. {
  730. g_VProfRecorder.Stop();
  731. }
  732. CON_COMMAND( vprof_record_start, "Start recording vprof data for playback later." )
  733. {
  734. if ( args.ArgC() != 2 )
  735. {
  736. Warning( "vprof_record_start requires a filename\n" );
  737. return;
  738. }
  739. g_VProfRecorder.Record_Start( args[1] );
  740. }
  741. CON_COMMAND( vprof_record_stop, "Stop recording vprof data" )
  742. {
  743. Warning( "Stopping vprof recording...\n" );
  744. g_VProfRecorder.Stop();
  745. }
  746. class CVPROFToCSVConverter
  747. {
  748. public:
  749. CVPROFToCSVConverter()
  750. {
  751. m_pTokenMap = NULL;
  752. }
  753. void ConvertVPROJFileToCSVFile( const char *szVPROJName, const char *szCSVName )
  754. {
  755. //
  756. // Open output file and early out if file creation fails
  757. //
  758. m_fileHandle = g_pFileSystem->Open( szCSVName, "w" );
  759. //
  760. // Begin playback and storage of VPROF data into local structures
  761. //
  762. g_VProfRecorder.Playback_Start( szVPROJName );
  763. while ( !g_VProfRecorder.IsPlaybackFinished() )
  764. {
  765. CUtlMap< char *, double > *pTickDataMap = new CUtlMap< char *, double >( DefLessFunc( char * ) );
  766. m_dataVector.AddToTail( pTickDataMap );
  767. WriteNodeDataToDict( g_VProfRecorder.GetRoot(), pTickDataMap );
  768. g_VProfRecorder.Playback_ReadTick();
  769. }
  770. //
  771. // Generate output
  772. //
  773. char szHeaders[1024];
  774. char szData[2048];
  775. WriteHeaders( szHeaders, sizeof( szHeaders ) );
  776. g_pFileSystem->FPrintf( m_fileHandle, "%s", szHeaders );
  777. FOR_EACH_VEC( m_dataVector, i )
  778. {
  779. char szTickNum[16];
  780. V_snprintf( szTickNum, sizeof( szTickNum ), "%d", i+1 );
  781. V_strncpy( szData, szTickNum, sizeof( szData ) );
  782. V_strncat( szData, ",", sizeof( szData ) );
  783. FOR_EACH_VEC( m_labelVector, j )
  784. {
  785. const unsigned short usIndex = m_dataVector[i]->Find( m_labelVector[j] );
  786. if ( usIndex != m_dataVector[i]->InvalidIndex() )
  787. {
  788. char szFloatValue[32];
  789. V_snprintf(szFloatValue, sizeof( szFloatValue ), "%f", m_dataVector[i]->Element( usIndex ) );
  790. V_strncat( szData, szFloatValue, sizeof( szData ) );
  791. }
  792. if ( j != ( m_labelVector.Count() - 1 ) )
  793. {
  794. V_strncat( szData, ",", sizeof( szData ) );
  795. }
  796. }
  797. V_strncat( szData, "\n", sizeof( szData ) );
  798. g_pFileSystem->FPrintf( m_fileHandle, "%s", szData );
  799. }
  800. g_pFileSystem->Close( m_fileHandle );
  801. //
  802. // Cleanup
  803. //
  804. m_labelVector.RemoveAll();
  805. m_dataVector.PurgeAndDeleteElements();
  806. }
  807. void SetNodeFilter( CUtlMap<char*, int> *pTokenMap )
  808. {
  809. m_pTokenMap = pTokenMap;
  810. }
  811. protected:
  812. CUtlVector<char *> m_labelVector;
  813. CUtlVector< CUtlMap< char *, double > *> m_dataVector;
  814. FileHandle_t m_fileHandle;
  815. CUtlMap<char*, int> *m_pTokenMap;
  816. void WriteNodeDataToDict( CVProfNode *pNode, CUtlMap< char *, double > *pTickDataMap )
  817. {
  818. char *pcNodeName = (char*)pNode->GetName();
  819. // Don't care about the Root label or data
  820. if ( 0 != V_strcmp( pcNodeName, "Root" ) )
  821. {
  822. // If there is a token filter and it is populated then use it
  823. if ( NULL == m_pTokenMap || 0 == m_pTokenMap->Count() || ( m_pTokenMap->InvalidIndex() != m_pTokenMap->Find( pcNodeName ) ) )
  824. {
  825. // Store the label in the label vector if we haven't seen it before
  826. if (-1 == m_labelVector.Find( pcNodeName ) )
  827. {
  828. m_labelVector.AddToTail( pcNodeName );
  829. }
  830. // Store the keyname and value for this VPROF node
  831. pTickDataMap->Insert( pcNodeName, pNode->GetCurTime() );
  832. }
  833. }
  834. if( pNode->GetSibling() )
  835. {
  836. WriteNodeDataToDict( pNode->GetSibling(), pTickDataMap );
  837. }
  838. if( pNode->GetChild() )
  839. {
  840. WriteNodeDataToDict( pNode->GetChild(), pTickDataMap );
  841. }
  842. }
  843. void WriteHeaders( char *szBuffer, int nBufferSize )
  844. {
  845. V_strncpy( szBuffer, "Tick Number,", nBufferSize );
  846. // We start at 1 instead of 0 because 'Root' is always the first one
  847. for ( int i = 0; i < m_labelVector.Count(); i++ )
  848. {
  849. V_strncat( szBuffer, m_labelVector[i], nBufferSize );
  850. // Skip the last comma
  851. if ( i != ( m_labelVector.Count() - 1 ) )
  852. {
  853. V_strncat( szBuffer, ",", nBufferSize );
  854. }
  855. }
  856. V_strncat( szBuffer, "\n", nBufferSize );
  857. }
  858. };
  859. CON_COMMAND( vprof_to_csv, "Convert a recorded .vprof file to .csv." )
  860. {
  861. //
  862. // Args
  863. //
  864. if ( args.ArgC() < 2 )
  865. {
  866. Warning( "vprof_to_csv requires an input filename (.VPROJ) and optional VPROF node names\n" );
  867. return;
  868. }
  869. //
  870. // Copy filename and parse filters from console
  871. //
  872. // Console parser treats colons as a break, so join all the tokens together here.
  873. char szVPROFFilename[MAX_PATH];
  874. char szCSVFilename[MAX_PATH];
  875. char szArgs[2*MAX_PATH];
  876. szArgs[0] = NULL;
  877. for ( int i=1; i < args.ArgC(); i++ )
  878. {
  879. Q_strncat( szArgs, args[i], sizeof( szArgs ), COPY_ALL_CHARACTERS );
  880. }
  881. //Separate the filter tokens from the arguments
  882. CUtlVector<char*, CUtlMemory<char*, int> > argsVector;
  883. CUtlMap<char*, int> tokenMap( DefLessFunc( char * ) );
  884. V_SplitString( szArgs, "|", argsVector );
  885. V_strncpy( szVPROFFilename, argsVector[0], MAX_PATH);
  886. delete argsVector[0];
  887. argsVector.RemoveMultiple( 0, 1 );
  888. // Add the remaining arguments to the map of token filters
  889. FOR_EACH_VEC( argsVector, i )
  890. {
  891. tokenMap.Insert(argsVector[i], 0);
  892. }
  893. // Create CSV filename
  894. V_StripExtension( szVPROFFilename, szCSVFilename, sizeof( szCSVFilename ) );
  895. V_strncat( szCSVFilename, ".csv", sizeof( szCSVFilename ) );
  896. //
  897. // Perform conversion
  898. //
  899. CVPROFToCSVConverter converter;
  900. converter.SetNodeFilter( &tokenMap );
  901. converter.ConvertVPROJFileToCSVFile( szVPROFFilename, szCSVFilename );
  902. //
  903. // Cleanup
  904. //
  905. argsVector.PurgeAndDeleteElements();
  906. tokenMap.RemoveAll();
  907. }
  908. CON_COMMAND( vprof_playback_start, "Start playing back a recorded .vprof file." )
  909. {
  910. if ( args.ArgC() < 2 )
  911. {
  912. Warning( "vprof_playback_start requires a filename\n" );
  913. return;
  914. }
  915. // Console parser treats colons as a break, so join all the tokens together here.
  916. char fullFilename[512];
  917. fullFilename[0] = 0;
  918. for ( int i=1; i < args.ArgC(); i++ )
  919. {
  920. Q_strncat( fullFilename, args[i], sizeof( fullFilename ), COPY_ALL_CHARACTERS );
  921. }
  922. g_VProfRecorder.Playback_Start( fullFilename );
  923. }
  924. CON_COMMAND( vprof_playback_stop, "Stop playing back a recorded .vprof file." )
  925. {
  926. Warning( "Stopping vprof playback...\n" );
  927. g_VProfRecorder.Stop();
  928. }
  929. CON_COMMAND( vprof_playback_step, "While playing back a .vprof file, step to the next tick." )
  930. {
  931. VProfPlayback_Step();
  932. }
  933. CON_COMMAND( vprof_playback_stepback, "While playing back a .vprof file, step to the previous tick." )
  934. {
  935. VProfPlayback_StepBack();
  936. }
  937. CON_COMMAND( vprof_playback_average, "Average the next N frames." )
  938. {
  939. if ( args.ArgC() >= 2 )
  940. {
  941. int nFrames = atoi( args[ 1 ] );
  942. if ( nFrames == -1 )
  943. nFrames = 9999999;
  944. g_VProfRecorder.Playback_Average( nFrames );
  945. }
  946. else
  947. {
  948. Warning( "vprof_playback_average [# frames]\n" );
  949. Warning( "If # frames is -1, then it will average all the remaining frames in the vprof file.\n" );
  950. }
  951. }
  952. void VProfRecord_Snapshot()
  953. {
  954. g_VProfRecorder.Snapshot();
  955. }
  956. void VProfRecord_StartOrStop()
  957. {
  958. g_VProfRecorder.StartOrStop();
  959. }
  960. void VProfRecord_Shutdown()
  961. {
  962. g_VProfRecorder.Shutdown();
  963. }
  964. bool VProfRecord_IsPlayingBack()
  965. {
  966. return g_VProfRecorder.IsPlayingBack();
  967. }
  968. int VProfPlayback_GetCurrentTick()
  969. {
  970. return g_VProfRecorder.Playback_GetCurrentTick();
  971. }
  972. float VProfPlayback_GetCurrentPercent()
  973. {
  974. return g_VProfRecorder.Playback_GetCurrentPercent();
  975. }
  976. int VProfPlayback_SetPlaybackTick( int iTick )
  977. {
  978. return g_VProfRecorder.Playback_SetPlaybackTick( iTick );
  979. }
  980. int VProfPlayback_SeekToPercent( float percent )
  981. {
  982. return g_VProfRecorder.Playback_SeekToPercent( percent );
  983. }
  984. void VProfPlayback_Step()
  985. {
  986. g_VProfRecorder.Playback_Step();
  987. }
  988. int VProfPlayback_StepBack()
  989. {
  990. return g_VProfRecorder.Playback_SetPlaybackTick( g_VProfRecorder.Playback_GetCurrentTick() - 1 );
  991. }
  992. #else // VPROF_ENABLED
  993. #define VProf_StartRecording( pFilename ) ((void)(0))
  994. #define VProf_StopRecording() ((void)(0))
  995. #endif