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.

444 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // DiffMemStats.cpp : processes two "memstats<n>.txt" memory dumps from the game into a 'diff' text file
  3. // which Excel will display in legible form (useful for tracking mem leaks)
  4. #include <math.h>
  5. #include <iostream>
  6. #include <tchar.h>
  7. #include <assert.h>
  8. #include <algorithm>
  9. #include <fstream>
  10. #include <string>
  11. #include <vector>
  12. #include <map>
  13. using namespace std;
  14. typedef map<string,float> CItemMap;
  15. typedef pair<string, float> CDelta;
  16. struct Sequence
  17. {
  18. const char *m_name;
  19. float *m_values;
  20. float m_maxDelta;
  21. float m_endDelta;
  22. };
  23. // The number of chains which will be output (the top N, after sorting and skipping)
  24. int gNumSequencesToOutput = 16;
  25. // The number of chains which will be skipped before output (the top N, after sorting)
  26. int gNumSequencesToSkip = 0;
  27. // If this is true, we sort chains by their maximum delta from the starting value,
  28. // otherwise we sort by the change from the start to the end value
  29. bool gSortByMaxChange = false;
  30. // If this is true, we use the absolute value of deltas for sorting
  31. // (so large negative deltas rank high as well as large position ones)
  32. bool gSortByAbsDeltas = false;
  33. // By default, we expect the input sequence of memstats files to be in chronological
  34. // order and from the same play session - if true, this relaxes that restriction:
  35. bool gAllowArbitraryInputSequence = false;
  36. // Output deltas from the first value in each sequence, rather than the current value
  37. // (outputs N-1 values instead of N)
  38. bool gOutputDeltas = false;
  39. // If this is true, output absolute values (by default, output values are relative to
  40. // those in the first input file - the first value in each sequence is subtracted out)
  41. bool gOutputAbsolute = false;
  42. // Output MB instead of KB
  43. bool gOutputMB = false;
  44. bool GetQuotedString( ifstream & file, string & item )
  45. {
  46. // Skip the opening quote
  47. char ch;
  48. while ( file.get( ch ) && ( ch != '"' ) );
  49. // Get the string
  50. getline( file, item, '"' );
  51. // Skip the comma
  52. file.get( ch );
  53. return ( !file.eof() );
  54. }
  55. string & CleanupName( string & name )
  56. {
  57. // Strip everything before "\src\", to make memstats files from different peoples' machines compatible
  58. const char * pSrc = strstr( name.c_str(), "src\\" );
  59. if ( pSrc && ( pSrc != name.c_str() ) )
  60. {
  61. string strippedName( pSrc );
  62. name = strippedName;
  63. }
  64. return name;
  65. }
  66. bool ParseFile( char *pszFile, CItemMap *pResult )
  67. {
  68. ifstream file;
  69. string item;
  70. float size;
  71. // Is this a CSV file?
  72. bool bIsCSV = !!strstr( pszFile, ".csv" );
  73. pResult->clear();
  74. file.open( pszFile );
  75. if ( !file.is_open() )
  76. {
  77. printf( "Failed to open %s\n", pszFile );
  78. return false;
  79. }
  80. // Skip the header
  81. getline( file, item );
  82. float maxEntrySize = 0;
  83. while ( !file.eof() )
  84. {
  85. if ( bIsCSV )
  86. {
  87. // Comma-separated data
  88. if ( !GetQuotedString( file, item ) )
  89. break;
  90. }
  91. else
  92. {
  93. // Tab-delimited data
  94. getline( file, item, '\t' );
  95. if ( !item.length() )
  96. break;
  97. }
  98. file >> size;
  99. maxEntrySize = max( maxEntrySize, size );
  100. pResult->insert( make_pair( CleanupName( item ), size ) );
  101. getline( file, item ); // skip the end of line
  102. }
  103. // XBox 360 has 512MB of RAM, so we can tell if data is in MB or KB
  104. // (and it's pretty unlikely we have no allocation entries greater than 512KB!)
  105. bool bInputDataInKB = ( maxEntrySize > 512 );
  106. // Convert the output to either KB or MB, as requested
  107. float multiplier = 1.0f;
  108. if ( bInputDataInKB && gOutputMB )
  109. multiplier = 1.0f / 1024.0f;
  110. else if ( !bInputDataInKB && !gOutputMB )
  111. multiplier = 1024.0f;
  112. CItemMap::iterator p1;
  113. for ( p1 = pResult->begin(); p1 != pResult->end(); p1++ )
  114. {
  115. p1->second = p1->second*multiplier;
  116. }
  117. return ( pResult->size() > 0 );
  118. }
  119. bool FillMissingEntries( CItemMap *items, int numItems, int * numAllocations )
  120. {
  121. // First, generate a list of all unique allocations
  122. CItemMap allAllocations;
  123. CItemMap::const_iterator p1, p2;
  124. for ( int i = 0; i < numItems; i++ )
  125. {
  126. for ( p1 = items[i].begin(); p1 != items[i].end(); p1++ )
  127. {
  128. p2 = allAllocations.find( p1->first );
  129. if ( p2 == allAllocations.end() )
  130. allAllocations.insert( make_pair( p1->first, 0.0f ) );
  131. }
  132. }
  133. // Determine how many sequences we have in total
  134. *numAllocations = (int)allAllocations.size();
  135. // Now make sure each allocation is present in every CItemMap. Where absent, assign the
  136. // previous known value, and where there is no known value assign zero.
  137. // 'Validity' requires that a given allocation will always be present in CItemMaps after
  138. // the first one in which it occurs (this is what you would get if the input files represent
  139. // memdumps in chronological order, all from the same play session).
  140. bool isValid = true;
  141. for ( p1 = allAllocations.begin(); p1 != allAllocations.end(); p1++ )
  142. {
  143. float curValue = 0.0f;
  144. bool foundFirstOccurrence = false;
  145. for ( int i = 0;i < numItems; i++ )
  146. {
  147. p2 = items[i].find( p1->first );
  148. if ( p2 != items[i].end() )
  149. {
  150. // Entry already present, update current value
  151. curValue = p2->second;
  152. foundFirstOccurrence = true;
  153. }
  154. else
  155. {
  156. // Entry missing, add it (and check validity)
  157. items[i].insert( make_pair( p1->first, curValue ) );
  158. if ( foundFirstOccurrence )
  159. isValid = false;
  160. }
  161. }
  162. }
  163. return isValid;
  164. }
  165. bool CompareSequence( const Sequence * & lhs, const Sequence * & rhs )
  166. {
  167. if ( gSortByMaxChange )
  168. return ( lhs->m_maxDelta > rhs->m_maxDelta );
  169. else
  170. return ( lhs->m_endDelta > rhs->m_endDelta );
  171. }
  172. vector<const Sequence *> & CreateSequences( CItemMap *items, int numItems )
  173. {
  174. // Create a vector of Sequence objects, each of which holds the
  175. // sequence of 'Allocation Size' values for each allocation
  176. vector<const Sequence *> & sequences = *new vector<const Sequence *>();
  177. CItemMap::const_iterator p1, p2;
  178. for ( p1 = items[0].begin(); p1 != items[0].end(); p1++ )
  179. {
  180. Sequence * seq = new Sequence;
  181. seq->m_name = p1->first.c_str();
  182. seq->m_values = new float[ numItems ];
  183. float startVal = p1->second;
  184. float maxDelta = 0.0f;
  185. float endDelta = 0.0f;
  186. for ( int i = 0; i < numItems; i++ )
  187. {
  188. p2 = items[ i ].find( seq->m_name );
  189. assert( p2 != items[i].end() );
  190. if ( p2 != items[i].end() )
  191. {
  192. seq->m_values[i] = p2->second;
  193. float delta = p2->second - startVal;
  194. if ( gSortByAbsDeltas )
  195. delta = fabs( delta );
  196. if ( delta > maxDelta )
  197. maxDelta = delta;
  198. endDelta = delta;
  199. }
  200. }
  201. seq->m_endDelta = endDelta;
  202. seq->m_maxDelta = maxDelta;
  203. sequences.push_back( seq );
  204. }
  205. // Now sort the sequences vector
  206. sort( sequences.begin(), sequences.end(), CompareSequence );
  207. return sequences;
  208. }
  209. void Usage()
  210. {
  211. printf( "diffmemstats is used for hunting down memory leaks\n" );
  212. printf( "\n" );
  213. printf( " USAGE: diffmemstats [options] <file1> <file2> [<file3>, ...]\n" );
  214. printf( "\n" );
  215. printf( "Input is a sequence of memstats<n>.txt files (saved from game using 'mem_dump')\n" );
  216. printf( "and output is a single tab-separated text file, where each line represents a\n" );
  217. printf( "given allocation's size as it varies over time through the memstats sequence\n" );
  218. printf( "(lines are sorted by maximum change over time - see sortend/sortmax options).\n" );
  219. printf( "This text file can then be graphed in Excel using a 'stacked column' chart.\n" );
  220. printf( "\n" );
  221. printf( "NOTE: input files must be in chronological order, from a SINGLE play session\n" );
  222. printf( " (unless -allowmismatch is specified).\n" );
  223. printf( "\n" );
  224. printf( "options:\n" );
  225. printf( "[-numchains:N] the top N sequences are output (default: 16)\n" );
  226. printf( "[-skipchains:M] skip the top M sequences before output (default: 0)\n" );
  227. printf( "[-delta] output deltas between adjacent values in each sequence\n" );
  228. printf( " (the first delta for each sequence will always be zero)\n" );
  229. printf( "[-absolute] output absolute values (default is to subtract out the\n" );
  230. printf( " first value in each sequence), overridden by '-delta'\n" );
  231. printf( "[-sortend] sort sequences by start-to-end change (default)\n" );
  232. printf( "[-sortmax] sort sequences by start-to-max-value change\n" );
  233. printf( "[-sortabs] sort by absolute change values\n" );
  234. printf( "[-allowmismatch] don't check that the input file sequence is in\n" );
  235. printf( " chronological order and from the same play session\n" );
  236. printf( "[-mb] output values in MB (default is KB)\n" );
  237. }
  238. bool ParseOption( _TCHAR* option )
  239. {
  240. if ( option[0] != '-' )
  241. return false;
  242. option++;
  243. int numChains, numRead = sscanf( option, "numchains:%d", &numChains );
  244. if ( numRead == 1 )
  245. {
  246. if ( numChains >= 0 )
  247. {
  248. gNumSequencesToOutput = numChains;
  249. return true;
  250. }
  251. return false;
  252. }
  253. int skipChains, numRead2 = sscanf( option, "skipchains:%d", &skipChains );
  254. if ( numRead2 == 1 )
  255. {
  256. if ( skipChains >= 0 )
  257. {
  258. gNumSequencesToSkip = skipChains;
  259. return true;
  260. }
  261. return false;
  262. }
  263. if ( !stricmp( option, "delta" ) )
  264. {
  265. gOutputDeltas = true;
  266. return true;
  267. }
  268. if ( !stricmp( option, "absolute" ) )
  269. {
  270. gOutputAbsolute = true;
  271. return true;
  272. }
  273. if ( !stricmp( option, "sortend" ) )
  274. {
  275. gSortByMaxChange = false;
  276. return true;
  277. }
  278. if ( !stricmp( option, "sortmax" ) )
  279. {
  280. gSortByMaxChange = true;
  281. return true;
  282. }
  283. if ( !stricmp( option, "sortabs" ) )
  284. {
  285. gSortByAbsDeltas = true;
  286. return true;
  287. }
  288. if ( !stricmp( option, "allowmismatch" ) )
  289. {
  290. gAllowArbitraryInputSequence = true;
  291. return true;
  292. }
  293. if ( !stricmp( option, "mb" ) )
  294. {
  295. gOutputMB = true;
  296. return true;
  297. }
  298. return false;
  299. }
  300. // NOTE: this app doesn't bother with little things like freeing memory
  301. int _tmain(int argc, _TCHAR* argv[])
  302. {
  303. if ( argc < 3 )
  304. {
  305. Usage();
  306. return 1;
  307. }
  308. // Grab options
  309. int numOptions = 0;
  310. argv++;
  311. while ( argv[0][0] == '-' )
  312. {
  313. if ( !ParseOption( argv[0] ) )
  314. {
  315. Usage();
  316. return 1;
  317. }
  318. numOptions++;
  319. argv++;
  320. }
  321. // TODO: allow the user to pass a starting filename and have the program figure out the sequence of files
  322. // in that folder (using Aaron's naming scheme: <map_name>_<mmdd>_<hhmmss>_<count>.txt)
  323. int numFiles = argc - 1 - numOptions;
  324. CItemMap *items = new CItemMap[ numFiles ];
  325. string *names = new string[ numFiles ];
  326. for ( int i = 0; i < numFiles; i++ )
  327. {
  328. strlwr( argv[0] );
  329. if ( !ParseFile( argv[0], &items[i] ) )
  330. return 1;
  331. // Create a label for each column of output data
  332. string name = argv[0];
  333. if ( ( name.find( ".csv" ) == ( name.length() - 4 ) ) ||
  334. ( name.find( ".txt" ) == ( name.length() - 4 ) ) )
  335. {
  336. name = name.substr( 0, name.length() - 4 );
  337. }
  338. names[ i ] = ( gOutputDeltas ? "[delta] " : "[size] " ) + name;
  339. argv++;
  340. }
  341. // Generate missing entries (i.e. make it so that each allocation
  342. // occurs in every CItemMap, so we have a sequence of 'numFiles' value, duplicating )
  343. int numAllocations = 0;
  344. bool isValidSequence = FillMissingEntries( items, numFiles, &numAllocations );
  345. if ( !isValidSequence && !gAllowArbitraryInputSequence )
  346. {
  347. printf( "ERROR: input files did not all come from the same play session, or are in the wrong order (to allow this, specify -allowmismatch)\n" );
  348. return 1;
  349. }
  350. // Create a vector of Sequence objects, each of which holds the sequence of 'size'
  351. // values for each allocation. The vector is sorted based on max change from the
  352. // start value, or the start-to-end change (gSortByMaxChange).
  353. vector<const Sequence *> & sequences = CreateSequences( items, numFiles );
  354. // Headings
  355. printf( "Allocation type" );
  356. for ( int i = 0; i < numFiles; i++ )
  357. {
  358. printf( "\t%s", names[ i ].c_str() );
  359. }
  360. printf("\n");
  361. for ( int i = gNumSequencesToSkip; (i < (gNumSequencesToSkip + gNumSequencesToOutput)) && (i < numAllocations); i++ )
  362. {
  363. const Sequence & seq = *sequences.at(i);
  364. printf( seq.m_name );
  365. for ( int j = 0; j < numFiles; j++ )
  366. {
  367. // Subtract out either the first (want change since the sequence start)
  368. // or the prior value (want change from one value to the next).
  369. int base = 0;
  370. if ( gOutputDeltas && ( j > 0 ) )
  371. base = j - 1;
  372. float baseVal = seq.m_values[base];
  373. if ( gOutputAbsolute && !gOutputDeltas )
  374. baseVal = 0.0f;
  375. printf( "\t%.2f", (seq.m_values[j] - baseVal) );
  376. }
  377. printf( "\n" );
  378. }
  379. return 0;
  380. }