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.

2517 lines
79 KiB

  1. // memlog.cpp : mines memory data from vxconsole logs (see game/client/c_memorylog.cpp)
  2. #include "utlbuffer.h"
  3. #include <conio.h>
  4. #include <math.h>
  5. #include <iostream>
  6. #include <tchar.h>
  7. #include <assert.h>
  8. #include <fstream>
  9. #include <sstream>
  10. #include <vector>
  11. #include <map>
  12. #define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
  13. #include <hash_map>
  14. #include <algorithm>
  15. #include "windows.h"
  16. using namespace std;
  17. using namespace stdext;
  18. // We write this to output files on conversion, and test it on read to see if files need re-converting:
  19. static const int MEMLOG_VERSION = 4;
  20. enum DVDHosted_t
  21. {
  22. // Is the game running FULLY off the DVD? (i.e. it reads no data from the HDD - no texture streaming)
  23. // If so, it will use more memory for audio/anim/texture data.
  24. DVDHOSTED_UNKNOWN = 0,
  25. DVDHOSTED_YES,
  26. DVDHOSTED_NO
  27. };
  28. enum Platform_t
  29. {
  30. PLATFORM_UNKNOWN = 0,
  31. PLATFORM_360 = 1,
  32. PLATFORM_PS3 = 2,
  33. PLATFORM_PC = 3
  34. };
  35. const char *gPlatformNames[] = { "unknown", "360", "PS3", "PC" };
  36. enum TrackStat_t
  37. {
  38. TRACKSTAT_UNKNOWN = 0,
  39. TRACKSTAT_MINFREE_CPU = 1,
  40. TRACKSTAT_MINFREE_GPU = 2
  41. };
  42. // TODO: these will eventually vary by platform (certainly, these numbers are not useful on PC)
  43. #define CPU_MEM_SIZE 512
  44. #define GPU_MEM_SIZE 256
  45. static const int MAX_LANG_LEN = 64;
  46. struct CHeaderInfo
  47. {
  48. CHeaderInfo() : memlogVersion( 0 ), dvdHosted( DVDHOSTED_UNKNOWN ), buildNumber(-1), platform( PLATFORM_UNKNOWN ) { language[0] = 0; }
  49. int memlogVersion;
  50. DVDHosted_t dvdHosted;
  51. int buildNumber;
  52. Platform_t platform;
  53. char language[MAX_LANG_LEN];
  54. };
  55. // Bitfield type used to filter logs w.r.t subsets of maps/players
  56. class FilterBitfield
  57. {
  58. public:
  59. FilterBitfield( void ) { for ( int i = 0; i < NUM_INT64S; i++ ) bits[i] = 0; }
  60. void SetAll( void ) { for ( int i = 0; i < NUM_INT64S; i++ ) bits[i] = -1; }
  61. void Set( int bit ) { bits[bit/64] |= ((__int64)1) << (bit&63); }
  62. bool IsSet( int bit ) { return !!( bits[bit/64] & ((__int64)1) << (bit&63) ); }
  63. bool Intersects( const FilterBitfield &other ) { __int64 result = 0; for ( int i = 0; i < NUM_INT64S; i++ ) result |= ( bits[i] & other.bits[i] ); return !!result; }
  64. bool operator ! ( void ) const { __int64 result = 0; for ( int i = 0; i < NUM_INT64S; i++ ) result |= bits[i]; return !result; }
  65. static const int NUM_INT64S = 4;
  66. __int64 bits[NUM_INT64S];
  67. };
  68. class CStringList
  69. {
  70. public:
  71. static const int MAX_STRINGLIST_ENTRIES = 64*FilterBitfield::NUM_INT64S;
  72. ~CStringList( void )
  73. {
  74. for ( int i = 0; i < m_Strings.Count(); i++ ) delete m_Strings[ i ];
  75. }
  76. int AddString( const char *string )
  77. {
  78. int existingIndex = StringToInt( string );
  79. if ( existingIndex != -1 )
  80. return existingIndex;
  81. if ( m_Strings.Count() == MAX_STRINGLIST_ENTRIES )
  82. {
  83. Assert( 0 );
  84. printf( "----ERROR: more than %d strings added to stringlist (e.g. '%s'), have to increase the size of FilterBitfield.bits!\n\n", MAX_STRINGLIST_ENTRIES, m_Strings[0] );
  85. return -1;
  86. }
  87. return m_Strings.AddToTail( strdup( string ) );
  88. }
  89. int StringToInt( const char *string )
  90. {
  91. // TODO: make this search faster (BUT we still need to be able to index into m_Strings and FilterBitfields... storing CUtlSymbolTable indices in m_String could work)
  92. for ( int i = 0; i < m_Strings.Count(); i++ )
  93. {
  94. if ( !stricmp( string, m_Strings[ i ] ) )
  95. return i;
  96. }
  97. return -1;
  98. }
  99. FilterBitfield SubstringToBitfield( const char *subString )
  100. {
  101. FilterBitfield bitField;
  102. if ( !subString || !subString[0] )
  103. {
  104. bitField.SetAll();
  105. }
  106. else
  107. {
  108. for ( int i = 0; i < m_Strings.Count(); i++ )
  109. {
  110. if ( V_stristr( m_Strings[ i ], subString ) )
  111. bitField.Set( i );
  112. }
  113. }
  114. return bitField;
  115. }
  116. private:
  117. CUtlVector< const char *>m_Strings;
  118. };
  119. struct MapMin_t // See CLogFile::badMaps
  120. {
  121. int map; // Map number
  122. float minMem; // Minimum memory observed in this map
  123. };
  124. struct CItem
  125. {
  126. int time;
  127. float freeMem;
  128. float gpuFree;
  129. int globalMapIndex;
  130. int logMapIndex;
  131. int numBots;
  132. int numPlayers;
  133. int numLocalPlayers;
  134. int numSpecators;
  135. bool isServer;
  136. FilterBitfield playerBitfield; // Bits represent items in CLogFile::playerList
  137. };
  138. struct CLogFile
  139. {
  140. public:
  141. CLogFile( void ) {}
  142. CUtlVector<CItem> entries;
  143. string memoryLog; // Path of the source memorylog file
  144. string consoleLog; // Path of the source vxconsole file
  145. ULONGLONG modifyTime; // Last modification time of the source vxconsole file
  146. bool isActive; // Last time we checked, the vxconsole file was still being written
  147. float minFreeMem; // Minimum free memory in any entry in this log
  148. float minGPUFreeMem; // Minimum free GPU memory in any entry in this log
  149. bool isServer; // True if the local box is the Server for any entry in this log
  150. bool isSplitscreen; // True if the local box is running splitscreen (>= 2 local players) for any entry in this log
  151. int maxPlayers; // Maximum players in any entry in this log
  152. CStringList mapList; // The maps present in this log
  153. CStringList playerList; // The players present in this log
  154. float memorylogTick; // Seconds between entries in this log
  155. CHeaderInfo headerInfo; // Various global data about the log (stuff that can't be computed from 'entries')
  156. CUtlVector<MapMin_t>badMaps; // A temp list that accumulates maps in this log which pass the current filters
  157. private:
  158. CLogFile( const CLogFile &other ) {}
  159. };
  160. struct CLogStats
  161. {
  162. CLogStats( void )
  163. {
  164. for ( int i = 0; i < CStringList::MAX_STRINGLIST_ENTRIES; i++ )
  165. {
  166. mapMin[ i ] = CPU_MEM_SIZE;
  167. mapGPUMin[ i ] = GPU_MEM_SIZE;
  168. mapAverage[ i ] = 0.0f;
  169. mapSeconds[ i ] = 0;
  170. mapSamples[ i ] = 0;
  171. }
  172. }
  173. float mapMin[ CStringList::MAX_STRINGLIST_ENTRIES ];
  174. float mapGPUMin[ CStringList::MAX_STRINGLIST_ENTRIES ];
  175. float mapAverage[ CStringList::MAX_STRINGLIST_ENTRIES ];
  176. float mapSeconds[ CStringList::MAX_STRINGLIST_ENTRIES ];
  177. unsigned int mapSamples[ CStringList::MAX_STRINGLIST_ENTRIES ];
  178. };
  179. static const char *gKnownMaps[] = { "none",
  180. /*"credits", */
  181. /* L4D1
  182. "l4d_hospital01_apartment",
  183. "l4d_hospital02_subway",
  184. "l4d_hospital03_sewers",
  185. "l4d_hospital04_interior",
  186. "l4d_hospital05_rooftop",
  187. "l4d_airport01_greenhouse",
  188. "l4d_airport02_offices",
  189. "l4d_airport03_garage",
  190. "l4d_airport04_terminal",
  191. "l4d_airport05_runway",
  192. "l4d_farm01_hilltop",
  193. "l4d_farm02_traintunnel",
  194. "l4d_farm03_bridge",
  195. "l4d_farm04_barn",
  196. "l4d_farm05_cornfield",
  197. "l4d_smalltown01_caves",
  198. "l4d_smalltown02_drainage",
  199. "l4d_smalltown03_ranchhouse",
  200. "l4d_smalltown04_mainstreet",
  201. "l4d_smalltown05_houseboat",
  202. "l4d_vs_hospital01_apartment",
  203. "l4d_vs_hospital02_subway",
  204. "l4d_vs_hospital03_sewers",
  205. "l4d_vs_hospital04_interior",
  206. "l4d_vs_hospital05_rooftop",
  207. "l4d_vs_airport01_greenhouse",
  208. "l4d_vs_airport02_offices",
  209. "l4d_vs_airport03_garage",
  210. "l4d_vs_airport04_terminal",
  211. "l4d_vs_airport05_runway",
  212. "l4d_vs_farm01_hilltop",
  213. "l4d_vs_farm02_traintunnel",
  214. "l4d_vs_farm03_bridge",
  215. "l4d_vs_farm04_barn",
  216. "l4d_vs_farm05_cornfield",
  217. "l4d_vs_smalltown01_caves",
  218. "l4d_vs_smalltown02_drainage",
  219. "l4d_vs_smalltown03_ranchhouse",
  220. "l4d_vs_smalltown04_mainstreet",
  221. "l4d_vs_smalltown05_houseboat",
  222. "backgroundstreet", */
  223. /* L4D2
  224. "c1m1_hotel",
  225. "c1m2_streets",
  226. "c1m3_mall",
  227. "c1m4_atrium",
  228. "c2m1_highway",
  229. "c2m2_fairgrounds",
  230. "c2m3_coaster",
  231. "c2m4_barns",
  232. "c2m5_concert",
  233. "c3m1_plankcountry",
  234. "c3m2_swamp",
  235. "c3m3_shantytown",
  236. "c3m4_plantation",
  237. "c4m1_milltown_a",
  238. "c4m2_sugarmill_a",
  239. "c4m3_sugarmill_b",
  240. "c4m4_milltown_b",
  241. "c4m5_milltown_escape",
  242. "c5m1_waterfront",
  243. "c5m2_park",
  244. "c5m3_cemetery",
  245. "c5m4_quarter",
  246. "c5m5_bridge", */
  247. /* // Portal 2 SP
  248. "sp_a1_intro1",
  249. "sp_a1_intro2",
  250. "sp_a1_intro3",
  251. "sp_a1_intro4",
  252. "sp_a1_intro5",
  253. "sp_a1_intro6",
  254. "sp_a1_intro7",
  255. "sp_a1_wakeup",
  256. "sp_a2_intro",
  257. "sp_a2_laser_intro",
  258. "sp_a2_laser_stairs",
  259. "sp_a2_dual_lasers",
  260. "sp_a2_laser_over_goo",
  261. "sp_a2_catapult_intro",
  262. "sp_a2_trust_fling",
  263. "sp_a2_pit_flings",
  264. "sp_a2_fizzler_intro",
  265. "sp_a2_sphere_peek",
  266. "sp_a2_ricochet",
  267. "sp_a2_bridge_intro",
  268. "sp_a2_bridge_the_gap",
  269. "sp_a2_turret_intro",
  270. "sp_a2_laser_relays",
  271. "sp_a2_turret_blocker",
  272. "sp_a2_laser_vs_turret",
  273. "sp_a2_pull_the_rug",
  274. "sp_a2_column_blocker",
  275. "sp_a2_laser_chaining",
  276. "sp_a2_turret_tower",
  277. "sp_a2_triple_laser",
  278. "sp_a2_bts1",
  279. "sp_a2_bts2",
  280. "sp_a2_bts3",
  281. "sp_a2_bts4",
  282. "sp_a2_bts5",
  283. "sp_a2_bts6",
  284. "sp_a2_core",
  285. "sp_a3_00",
  286. "sp_a3_01",
  287. "sp_a3_03",
  288. "sp_a3_jump_intro",
  289. "sp_a3_bomb_flings",
  290. "sp_a3_crazy_box",
  291. "sp_a3_transition01",
  292. "sp_a3_speed_ramp",
  293. "sp_a3_speed_flings",
  294. "sp_a3_portal_intro",
  295. "sp_a3_end",
  296. "sp_a4_intro",
  297. "sp_a4_tb_intro",
  298. "sp_a4_tb_trust_drop",
  299. "sp_a4_tb_wall_button",
  300. "sp_a4_tb_polarity",
  301. "sp_a4_tb_catch",
  302. "sp_a4_stop_the_box",
  303. "sp_a4_laser_catapult",
  304. "sp_a4_laser_platform",
  305. "sp_a4_speed_tb_catch",
  306. "sp_a4_jump_polarity",
  307. "sp_a4_finale1",
  308. "sp_a4_finale2",
  309. "sp_a4_finale3",
  310. "sp_a4_finale4",
  311. // Portal 2 Co-op
  312. "mp_coop_start",
  313. "mp_coop_lobby_2",
  314. "mp_coop_doors",
  315. "mp_coop_race_2",
  316. "mp_coop_laser_2",
  317. "mp_coop_rat_maze",
  318. "mp_coop_laser_crusher",
  319. "mp_coop_teambts",
  320. "mp_coop_fling_3",
  321. "mp_coop_infinifling_train",
  322. "mp_coop_come_along",
  323. "mp_coop_fling_1",
  324. "mp_coop_catapult_1",
  325. "mp_coop_multifling_1",
  326. "mp_coop_fling_crushers",
  327. "mp_coop_fan",
  328. "mp_coop_wall_intro",
  329. "mp_coop_wall_2",
  330. "mp_coop_catapult_wall_intro",
  331. "mp_coop_wall_block",
  332. "mp_coop_catapult_2",
  333. "mp_coop_turret_walls",
  334. "mp_coop_turret_ball",
  335. "mp_coop_wall_5",
  336. "mp_coop_tbeam_redirect",
  337. "mp_coop_tbeam_drill",
  338. "mp_coop_tbeam_catch_grind_1",
  339. "mp_coop_tbeam_laser_1",
  340. "mp_coop_tbeam_polarity",
  341. "mp_coop_tbeam_polarity2",
  342. "mp_coop_tbeam_polarity3",
  343. "mp_coop_tbeam_maze",
  344. "mp_coop_tbeam_end",
  345. "mp_coop_paint_come_along",
  346. "mp_coop_paint_redirect",
  347. "mp_coop_paint_bridge",
  348. "mp_coop_paint_walljumps",
  349. "mp_coop_paint_speed_fling",
  350. "mp_coop_paint_red_racer",
  351. "mp_coop_paint_speed_catch",
  352. "mp_coop_paint_longjump_intro",
  353. "mp_coop_separation_1",
  354. "mp_coop_rocket_block",
  355. "mp_coop_race_3",
  356. "mp_coop_laser_1",
  357. "mp_coop_wall_1",
  358. "mp_coop_2guns_longjump_intro",
  359. "mp_coop_paint_crazy_box",
  360. "mp_coop_wall_6",
  361. "mp_coop_button_tower",
  362. "mp_coop_tbeam_fling_float_1",
  363. // Portal 2 demo
  364. "demo_intro",
  365. "demo_paint",
  366. "demo_underground", */
  367. // CSGO
  368. "cs_italy",
  369. "cs_office",
  370. "de_aztec",
  371. "de_dust2",
  372. "de_dust",
  373. "de_inferno",
  374. "de_nuke",
  375. "de_lake",
  376. "de_safehouse",
  377. "de_shorttrain"
  378. "de_sugarcane",
  379. "de_stmarc",
  380. "de_bank",
  381. "ar_shoots",
  382. "ar_baggage",
  383. "de_train",
  384. "training1",
  385. };
  386. const int gNumKnownMaps = ARRAYSIZE( gKnownMaps );
  387. static const char *gIgnoreMaps[] = {"devtest",
  388. /*"test_box",
  389. "test_box2",
  390. "nav_test",
  391. "transition_test01",
  392. "transition_test02",
  393. "c2m4_concert",
  394. "c5m2_cemetery",
  395. "c5m3_quarter",
  396. "c5m4_bridge"*/ };
  397. const int gNumIgnoreMaps = ARRAYSIZE( gIgnoreMaps );
  398. CUtlVector< const char * > gMapNames; // List of encountered map names
  399. typedef hash_map<string, int> CMapHash; // For quickly determining is a string is in gMapNames
  400. CMapHash gMapHash;
  401. typedef hash_map<string, CLogFile*> CLogFiles;
  402. const int FILTER_SIZE = 64;
  403. struct Config
  404. {
  405. char sourcePath[ _MAX_PATH ]; // Where we're getting logs from
  406. char prevCommandLine[ _MAX_PATH ]; // Previously entered command
  407. bool recurse; // Recurse the tree under 'sourcePath'
  408. bool update; // Re-convert vxconsole logs, if they're newer than their corresponding memorylogs
  409. bool updateActive; // Re-convert vxconsole logs,
  410. bool forceUpdate; // Re-convert vxconsole logs, even if they've been converted before
  411. // Console mode:
  412. bool consoleMode; // Accept commands from the user 'till they quit
  413. bool load; // Load new logs from 'sourcePath'
  414. bool unload; // Unload all logs from 'sourcePath'
  415. bool unloadAll; // Unload all logs
  416. bool quitting; // We're done, exit the app
  417. bool help; // User wants to see the help text
  418. // Memory tracking (CSV files to plot memory stats over time)
  419. char trackFile[MAX_PATH]; // Tracking file to update from the current filter set
  420. char trackColumn[32]; // New column to add to the tracking file
  421. TrackStat_t trackStat; // Stat to track (min free CPU memory, min free GPU memory...)
  422. // Data analysis filters (per-log-entry)
  423. float dangerLimit; // Spew log entries in which memory drops below this limit (in MB)
  424. int minPlayers; // Spew log entries with at least this many concurrent players
  425. int maxPlayers; // Spew log entries with at most this many concurrent players
  426. bool isSplitscreen; // Spew log entries in which the machine has more than one local player
  427. bool isSinglescreen; // Spew log entries in which the machine has AT MOST one local player
  428. char mapFilter[FILTER_SIZE]; // Spew log entries with map names which contain this substring
  429. char playerFilter[FILTER_SIZE]; // Spew log entries with player names which contain this substring
  430. // Data analysis filters (per-log-file)
  431. int dangerTime; // Spew logs in which memory drops below the danger limit within this many seconds
  432. int duration; // Spew logs in which the timer reaches this many seconds
  433. int minAge; // Spew logs updated at least this many seconds ago
  434. int maxAge; // Spew logs updated at most this many seconds ago
  435. bool isServer; // Spew logs in which the machine is a listen server at least once
  436. bool isClient; // Spew logs in which the machine is NEVER a listen server
  437. bool isActive; // Spew logs currently being written
  438. DVDHosted_t dvdHosted; // Spew logs matching this DVD hosted state (UNKNOWN means accept all)
  439. char languageFilter[FILTER_SIZE]; // Spew logs with a language which contain this substring
  440. Platform_t platform; // Spew logs generated from the specified platform (UNKNOWN means accept all)
  441. };
  442. Config gConfig;
  443. // 10 million 100-nanosecond intervals per second in FILETIME
  444. const ULONGLONG ONE_SECOND = 10000000LL;
  445. int AddNewMapName( const char *name )
  446. {
  447. char *nameCopy = strdup( name ); // Leak
  448. strlwr( nameCopy );
  449. if ( gMapHash.find( string( nameCopy ) ) != gMapHash.end() )
  450. {
  451. printf( "ERROR: duplicate map name in gMapNames!!!! (%s)\n", nameCopy );
  452. printf( "Aborting... press any key to exit\n" );
  453. DebuggerBreakIfDebugging();
  454. getchar();
  455. exit( 1 );
  456. }
  457. int index = gMapNames.AddToTail( nameCopy );
  458. gMapHash[ string( nameCopy ) ] = index;
  459. return index;
  460. }
  461. bool ParseLineForDVDHostedInfo( string &line, CHeaderInfo &headerInfo )
  462. {
  463. // Game spews text like this on startup:
  464. //
  465. // Xbox Launched From DVD.
  466. // Install Status:
  467. // Version: 746360 (english) (Xbox|PS3|PC)
  468. // DVD Hosted: Enabled <---- this is the important line
  469. // Progress: 0/1200 MB
  470. // Active Image: 746360
  471. //
  472. // We use that to determine if we are running SOLELY off the DVD (no HDD access, no texture streaming),
  473. // in which case textures/audio/anims take more memory, so the minimum free memory watermark is lower.
  474. // UPDATE: these memory effects apply to L4D/L4D2 but not PORTAL2 (it doesn't do texture streaming)
  475. if ( headerInfo.dvdHosted == DVDHOSTED_UNKNOWN )
  476. {
  477. const char *cursor = line.c_str();
  478. if ( strstr( cursor, "Device\\Harddisk" ) )
  479. {
  480. // Running from the HDD (this test works for old images without the "DVD Hosted:" spew)
  481. headerInfo.dvdHosted = DVDHOSTED_NO;
  482. return true;
  483. }
  484. else if ( strstr( cursor, "DVD Hosted:" ) )
  485. {
  486. headerInfo.dvdHosted = strstr( cursor, "Enabled" ) ? DVDHOSTED_YES : DVDHOSTED_NO;
  487. return true;
  488. }
  489. }
  490. return false;
  491. }
  492. bool ParseLineForBuildNumberLanguageAndPlatformInfo( string &line, CHeaderInfo &headerInfo )
  493. {
  494. // Game spews text like this on startup:
  495. //
  496. // Xbox Launched From DVD.
  497. // Install Status:
  498. // Version: 746360 (english) (Xbox|PS3|PC) <---- this is the important line
  499. // DVD Hosted: Enabled
  500. // Progress: 0/1200 MB
  501. // Active Image: 746360
  502. //
  503. // We use that to determine current build number and language.
  504. if ( headerInfo.buildNumber == -1 )
  505. {
  506. const char *cursor = line.c_str();
  507. cursor = strstr( cursor, "Version:" );
  508. if ( cursor )
  509. {
  510. static char language[1024], platform[1024];
  511. if ( 3 == sscanf( cursor, "Version: %d (%s (%s", &headerInfo.buildNumber, &(language[0]), &(platform[0]) ) )
  512. {
  513. strncpy( &(headerInfo.language[0]), &(language[0]), MAX_LANG_LEN );
  514. char *closingBrace = strstr( headerInfo.language, ")" );
  515. if ( closingBrace ) *closingBrace = 0;
  516. strlwr( &( headerInfo.language[0] ) );
  517. closingBrace = strstr( platform, ")" );
  518. if ( closingBrace ) *closingBrace = 0;
  519. if ( !stricmp( platform, "Xbox" ) )
  520. headerInfo.platform = PLATFORM_360;
  521. else if ( !stricmp( platform, "PS3" ) )
  522. headerInfo.platform = PLATFORM_PS3;
  523. else if ( !stricmp( platform, "PC" ) )
  524. headerInfo.platform = PLATFORM_PC;
  525. return true;
  526. }
  527. headerInfo.buildNumber = -1;
  528. headerInfo.language[0] = 0;
  529. headerInfo.platform = PLATFORM_UNKNOWN;
  530. }
  531. }
  532. return false;
  533. }
  534. bool ParseLineForHeaderInfo( string &line, CHeaderInfo &headerInfo )
  535. {
  536. if ( ParseLineForDVDHostedInfo( line, headerInfo ) )
  537. return true;
  538. if ( ParseLineForBuildNumberLanguageAndPlatformInfo( line, headerInfo ) )
  539. return true;
  540. return false;
  541. }
  542. int ConvertItem( string &line, CItem &result, const CItem &prevItem, CHeaderInfo *headerInfo, const char *fileName, int lineNum, vector<int> *truncated, CStringList *mapList, CStringList *playerList, bool skipIgnored )
  543. {
  544. // Accumulate header lines as we go through the log
  545. if ( headerInfo && ParseLineForHeaderInfo( line, *headerInfo ) )
  546. return -1;
  547. // Special-case 'out of memory' lines:
  548. const char *start = strstr( line.c_str(), "*** OUT OF MEMORY!" );
  549. if ( start )
  550. {
  551. // Replace this with a fake "zero memory free" line
  552. static char fakeLine[1024];
  553. _snprintf( fakeLine, sizeof( fakeLine ), "[MEMORYLOG] Time:%6d | Free:%6.2f | GPU:%6.2f | %s | Map: %-32s | Bots:%2d | Players: 0",
  554. prevItem.time, 0.0f, 0.0f, prevItem.isServer ? "Server" : "Client",
  555. ( ( prevItem.globalMapIndex < 0 ) ? "none" : gMapNames[ prevItem.globalMapIndex ] ), prevItem.numBots );
  556. printf( "---- OUT OF MEMORY on line %d, in %s\n\n", lineNum, fileName );
  557. line = fakeLine;
  558. }
  559. start = strstr( line.c_str(), "[MEMORYLOG] " );
  560. if ( !start )
  561. return -1;
  562. // Get rid of line endings returned by GetLine()
  563. while ( ( line[ line.size() - 1 ] == '\r' ) || ( line[ line.size() - 1 ] == '\n' ) )
  564. line.resize( line.size() - 1 );
  565. int time;
  566. float freeMem;
  567. float GPUFree;
  568. char hostType[33];
  569. char mapName[33];
  570. int numBots;
  571. int numPlayers;
  572. CMapHash::iterator it;
  573. int numRead = sscanf( start,
  574. "[MEMORYLOG] Time:%d | Free: %f | GPU: %f | %s | Map: %s | Bots: %d | Players: %d",
  575. &time,
  576. &freeMem,
  577. &GPUFree,
  578. hostType,
  579. mapName,
  580. &numBots,
  581. &numPlayers );
  582. if ( numRead != 7 )
  583. goto error;
  584. bool isServer;
  585. if ( !stricmp( hostType, "Server" ) )
  586. isServer = true;
  587. else if ( !stricmp( hostType, "Client" ) )
  588. isServer = false;
  589. else
  590. goto error;
  591. int globalMapIndex = -1;
  592. strlwr( mapName );
  593. it = gMapHash.find( mapName );
  594. if ( it != gMapHash.end() )
  595. {
  596. globalMapIndex = it->second;
  597. if ( skipIgnored && ( globalMapIndex < gNumIgnoreMaps ) )
  598. return -1;
  599. }
  600. else
  601. {
  602. // Add the new name to gMapNames and gMapHash
  603. globalMapIndex = AddNewMapName( mapName );
  604. // This message notifies us when new maps appear on the horizon (e.g. 'credits'!)
  605. printf( "----WARNING: unrecognised map name '%s' on line %d, in %s\n\n", mapName, lineNum, fileName );
  606. }
  607. // Also add the map name to a per-logfile list
  608. int logMapIndex = mapList ? mapList->AddString( mapName ) : -1;
  609. // Iterate over players
  610. int numLocalPlayers = 0, numSpectators = 0, numNamedBots = 0;
  611. const char *playerStart = start;
  612. for ( int i = 0; i < numPlayers; i++ )
  613. {
  614. playerStart = strstr( playerStart, ", " );
  615. if ( !playerStart )
  616. goto playerError;
  617. playerStart += 2;
  618. const char *playerEnd = playerStart;
  619. while ( playerEnd[0] && ( playerEnd[0] != '|' ) && ( playerEnd[0] != ',' ) ) playerEnd++;
  620. if ( playerList )
  621. {
  622. // Build a vector of player name references
  623. string playerName( playerStart, ( playerEnd - playerStart ) );
  624. strlwr( (char*)playerName.c_str() );
  625. int playerIndex = playerList->AddString( playerName.c_str() );
  626. result.playerBitfield.Set( playerIndex );
  627. }
  628. // Track how many local/spectator players there are
  629. while ( playerEnd[0] == '|' )
  630. {
  631. playerEnd++;
  632. if ( !strncmp( playerEnd, "LOCAL", 5 ) )
  633. {
  634. numLocalPlayers++;
  635. playerEnd += 5;
  636. }
  637. else if ( !strncmp( playerEnd, "SPEC", 4 ) )
  638. {
  639. numSpectators++;
  640. playerEnd += 4;
  641. }
  642. else if ( !strncmp( playerEnd, "BOT", 3 ) )
  643. {
  644. numNamedBots++;
  645. playerEnd += 3;
  646. }
  647. else
  648. goto playerError;
  649. if ( playerEnd[0] && ( playerEnd[0] != ',' ) && ( playerEnd[0] != '|' ) && ( playerEnd[0] != ' ' ) )
  650. goto playerError;
  651. }
  652. }
  653. Assert( !numNamedBots || ( numNamedBots == numBots ) );
  654. if ( numNamedBots && ( numNamedBots != numBots ) )
  655. goto playerError;
  656. if ( false )
  657. {
  658. playerError:
  659. // Don't quit on errors in the player list, keep the data we managed to get (usually truncated lines in versus games)
  660. if ( strlen( line.c_str() ) == 264 )
  661. {
  662. // TODO: Somewhere, lines are getting capped at 264 chars (don't think it's in VXConsole, it's max command len is 4096)
  663. if ( truncated ) truncated->push_back( lineNum );
  664. }
  665. else
  666. {
  667. printf( "----ERROR: unrecognised [MEMORYLOG] syntax on line %d, in %s\n", lineNum, fileName );
  668. printf( " \"%s\"\n\n", line.c_str() );
  669. }
  670. }
  671. result.freeMem = freeMem;
  672. result.gpuFree = GPUFree;
  673. result.time = time;
  674. result.globalMapIndex = globalMapIndex;
  675. result.logMapIndex = logMapIndex;
  676. result.numBots = numBots;
  677. result.numPlayers = numPlayers;
  678. result.numLocalPlayers = numLocalPlayers;
  679. result.numSpecators = numSpectators;
  680. result.isServer = isServer;
  681. return ( start - line.c_str() );
  682. error:
  683. printf( "----ERROR: unrecognised [MEMORYLOG] syntax on line %d, in %s\n", lineNum, fileName );
  684. printf( " \"%s\"\n\n", line.c_str() );
  685. return -1;
  686. }
  687. bool ReadFile( const string &fileName, CUtlBuffer &buffer )
  688. {
  689. FILE *fp = fopen( fileName.c_str(), "rb" );
  690. if (!fp)
  691. return false;
  692. fseek( fp, 0, SEEK_END );
  693. int nFileLength = ftell( fp );
  694. fseek( fp, 0, SEEK_SET );
  695. buffer.EnsureCapacity( nFileLength );
  696. int nBytesRead = fread( buffer.Base(), 1, nFileLength, fp );
  697. fclose( fp );
  698. buffer.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );
  699. return true;
  700. }
  701. bool WriteFile( const string &fileName, CUtlBuffer &buffer, CUtlBuffer *header )
  702. {
  703. FILE *fp = fopen( fileName.c_str(), "wb" );
  704. if ( !fp )
  705. return false;
  706. if ( header )
  707. fwrite( header->Base(), 1, header->TellPut(), fp );
  708. fwrite( buffer.Base(), 1, buffer.TellPut(), fp );
  709. fclose( fp );
  710. return true;
  711. }
  712. char *SpewHeaderSummary( char *buffer, int bufferSize, const CHeaderInfo &headerInfo )
  713. {
  714. const char *dvdHosted = ( headerInfo.dvdHosted == DVDHOSTED_UNKNOWN ) ? " - " : ( ( headerInfo.dvdHosted == DVDHOSTED_YES ) ? "DVD" : "HDD" );
  715. bool languageKnown = !!strcmp( headerInfo.language, "unknown" );
  716. _snprintf( buffer, bufferSize, "%7d %4s %10s ", headerInfo.buildNumber, dvdHosted, languageKnown ? headerInfo.language : " -- " );
  717. return buffer;
  718. }
  719. void WriteHeaderInfoToBuffer( CUtlBuffer &buffer, CHeaderInfo &headerInfo )
  720. {
  721. buffer.Clear();
  722. buffer.Printf( "[HDR]:version=%d\n", MEMLOG_VERSION );
  723. buffer.Printf( "[HDR]:dvdhosted=%s\n", ( headerInfo.dvdHosted == DVDHOSTED_UNKNOWN ) ? "unknown" : ( ( headerInfo.dvdHosted == DVDHOSTED_YES ) ? "yes" : "no" ) );
  724. buffer.Printf( "[HDR]:build=%d\n", headerInfo.buildNumber );
  725. buffer.Printf( "[HDR]:language=%s\n", ( headerInfo.language && headerInfo.language[0] ) ? headerInfo.language : "unknown" );
  726. buffer.Printf( "[HDR]:platform=%s\n", gPlatformNames[ headerInfo.platform ] );
  727. }
  728. void InitItem( CItem &item )
  729. {
  730. item.time = 0;
  731. item.freeMem = CPU_MEM_SIZE;
  732. item.gpuFree = GPU_MEM_SIZE;
  733. item.globalMapIndex = gMapHash[ string( "backgroundstreet" ) ];
  734. item.logMapIndex = -1;
  735. item.numBots = 0;
  736. item.numPlayers = 0;
  737. item.numLocalPlayers = 0;
  738. item.numSpecators = 0;
  739. item.isServer = false;
  740. }
  741. bool ConvertVXConsoleLog( string &consoleLog, string &memoryLog )
  742. {
  743. CUtlBuffer iBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER|CUtlBuffer::CONTAINS_CRLF );
  744. CUtlBuffer oBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER|CUtlBuffer::CONTAINS_CRLF );
  745. CUtlBuffer ohBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER|CUtlBuffer::CONTAINS_CRLF );
  746. if ( !ReadFile( consoleLog, iBuffer ) )
  747. {
  748. printf( "----ERROR: Failed to read file %s\n\n", consoleLog.c_str() );
  749. return false;
  750. }
  751. bool isEmpty = true;
  752. int lineNum = 1;
  753. vector<int> truncated;
  754. CHeaderInfo headerInfo;
  755. CItem prevItem;
  756. InitItem( prevItem );
  757. while ( iBuffer.IsValid() )
  758. {
  759. static char lineBuf[ 10000 ];
  760. iBuffer.GetLine( lineBuf, sizeof( lineBuf ) );
  761. // CUtlBuffer.GetLine() returns TWO lines for lines ending in '\r\n' :o/
  762. if ( lineBuf[0] == '\n' ) continue;
  763. if ( !lineBuf[0] && ( iBuffer.TellGet() != iBuffer.TellPut() ) )
  764. {
  765. // CUtlBuffer.GetLine() can fail if there are non-ASCII characters in the log. If that happens
  766. // (which it does - vxconsole bug?), we'll get a zero-length line, so unstick the buffer:
  767. iBuffer.GetChar();
  768. continue;
  769. }
  770. CItem item;
  771. string line = lineBuf;
  772. int start = ConvertItem( line, item, prevItem, &headerInfo, consoleLog.c_str(), lineNum, &truncated, NULL, NULL, false );
  773. if ( start >= 0 )
  774. {
  775. // If the line is valid, copy it to the output file
  776. oBuffer.Put( ( line.c_str() + start ), ( line.size() - start ) );
  777. oBuffer.Put( "\n", 1 );
  778. isEmpty = false;
  779. prevItem = item;
  780. }
  781. lineNum++;
  782. }
  783. // Write the headerInfo to a buffer
  784. WriteHeaderInfoToBuffer( ohBuffer, headerInfo );
  785. if ( truncated.size() )
  786. {
  787. printf( "----WARNING: [MEMORYLOG] text truncated in %s\n", consoleLog.c_str() );
  788. printf( "---- Truncated lines: %d", truncated[0] );
  789. for ( unsigned int i = 1; i < truncated.size(); i++ ) printf( ", %d", truncated[i] );
  790. printf( "\n" );
  791. }
  792. // Write the output file, with zero or more items in it
  793. if ( !WriteFile( memoryLog, oBuffer, &ohBuffer ) )
  794. {
  795. printf( "----ERROR: Failed to write file %s\n\n", memoryLog.c_str() );
  796. return false;
  797. }
  798. return !isEmpty;
  799. }
  800. CLogFile &InitLogFile( CLogFiles &results, const string &memoryLog, const string &consoleLog, const ULONGLONG &modifyTime, bool isActive )
  801. {
  802. CLogFile *result = new CLogFile();
  803. pair<CLogFiles::iterator,bool> insertion = results.insert( make_pair( memoryLog, result ) );
  804. if ( !insertion.second || ( insertion.first == results.end() ) )
  805. {
  806. printf( "----ERROR: hash_map insertion failed...?\n\n" );
  807. insertion.first = results.find( memoryLog );
  808. if ( insertion.first == results.end() )
  809. printf( "----ERROR: hash_map totally b0rked, gonna crash...\n\n" );
  810. }
  811. result->memoryLog = memoryLog;
  812. result->consoleLog = consoleLog;
  813. result->modifyTime = modifyTime;
  814. result->isActive = isActive;
  815. result->minFreeMem = CPU_MEM_SIZE;
  816. result->minGPUFreeMem = GPU_MEM_SIZE;
  817. result->isServer = false;
  818. result->isSplitscreen = false;
  819. result->maxPlayers = 0;
  820. return *result;
  821. }
  822. bool StringToPlatformName( const char *name, Platform_t &platform )
  823. {
  824. for ( int i = 0; i < ARRAYSIZE( gPlatformNames ); i++ )
  825. {
  826. if ( !strnicmp( name, gPlatformNames[ i ], strlen( gPlatformNames[ i ] ) ) )
  827. {
  828. platform = (Platform_t)i;
  829. return true;
  830. }
  831. }
  832. return false;
  833. }
  834. bool ReadHeaderLine( const char *lineBuf, CHeaderInfo &headerInfo )
  835. {
  836. static const char HEADER_PREFIX[] = "[HDR]:";
  837. static const int HEADER_PREFIX_LEN = sizeof( HEADER_PREFIX ) - 1;
  838. static const char KEY_VERSION[] = "version=";
  839. static const int KEY_VERSION_LEN = sizeof( KEY_VERSION ) - 1;
  840. static const char KEY_DVD_HOSTED[] = "dvdhosted=";
  841. static const int KEY_DVD_HOSTED_LEN = sizeof( KEY_DVD_HOSTED ) - 1;
  842. static const char KEY_BUILDNUM[] = "build=";
  843. static const int KEY_BUILDNUM_LEN = sizeof( KEY_BUILDNUM ) - 1;
  844. static const char KEY_LANGUAGE[] = "language=";
  845. static const int KEY_LANGUAGE_LEN = sizeof( KEY_LANGUAGE ) - 1;
  846. static const char KEY_PLATFORM[] = "platform=";
  847. static const int KEY_PLATFORM_LEN = sizeof( KEY_PLATFORM ) - 1;
  848. const char *cursor = lineBuf;
  849. if ( strncmp( cursor, HEADER_PREFIX, HEADER_PREFIX_LEN ) )
  850. return false;
  851. cursor += HEADER_PREFIX_LEN;
  852. if ( !strncmp( cursor, KEY_VERSION, KEY_VERSION_LEN ) )
  853. {
  854. cursor += KEY_VERSION_LEN;
  855. headerInfo.memlogVersion = atoi( cursor );
  856. return true;
  857. }
  858. else if ( !strncmp( cursor, KEY_DVD_HOSTED, KEY_DVD_HOSTED_LEN ) )
  859. {
  860. cursor += KEY_DVD_HOSTED_LEN;
  861. if ( !strncmp( cursor, "yes", 3 ) )
  862. {
  863. headerInfo.dvdHosted = DVDHOSTED_YES;
  864. }
  865. else if ( !strncmp( cursor, "no", 2 ) )
  866. {
  867. headerInfo.dvdHosted = DVDHOSTED_NO;
  868. }
  869. return true;
  870. }
  871. else if ( !strncmp( cursor, KEY_BUILDNUM, KEY_BUILDNUM_LEN ) )
  872. {
  873. cursor += KEY_BUILDNUM_LEN;
  874. headerInfo.buildNumber = atoi( cursor );
  875. return true;
  876. }
  877. else if ( !strncmp( cursor, KEY_LANGUAGE, KEY_LANGUAGE_LEN ) )
  878. {
  879. cursor += KEY_LANGUAGE_LEN;
  880. strncpy( headerInfo.language, cursor, MAX_LANG_LEN );
  881. char *eol = strstr( headerInfo.language, "\n" );
  882. if ( eol ) *eol = 0;
  883. return true;
  884. }
  885. else if ( !strncmp( cursor, KEY_PLATFORM, KEY_PLATFORM_LEN ) )
  886. {
  887. cursor += KEY_PLATFORM_LEN;
  888. if ( StringToPlatformName( cursor, headerInfo.platform ) )
  889. return true;
  890. }
  891. printf( "----ERROR: Unrecognised header line: (%s)\n\n", lineBuf );
  892. return true;
  893. }
  894. int ReadMemoryLog( string &memoryLog, string &consoleLog, CLogFiles & results, const ULONGLONG &modifyTime, bool isActive, bool checkVersion )
  895. {
  896. CUtlBuffer iBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER|CUtlBuffer::CONTAINS_CRLF );
  897. if ( !ReadFile( memoryLog.c_str(), iBuffer ) )
  898. {
  899. printf( "----ERROR: Failed to open input file %s\n\n", memoryLog.c_str() );
  900. return 0;
  901. }
  902. CLogFile &result = InitLogFile( results, memoryLog, consoleLog, modifyTime, isActive );
  903. bool bOrderError = false;
  904. bool bReallyEmpty = true;
  905. bool bDoneHeader = false;
  906. int lineNum = 1;
  907. int lastTime = 0;
  908. CItem prevItem;
  909. InitItem( prevItem );
  910. while ( iBuffer.IsValid() )
  911. {
  912. // create+add an item
  913. static char lineBuf[ 10000 ];
  914. iBuffer.GetLine( lineBuf, sizeof( lineBuf ) );
  915. // CUtlBuffer.GetLine() returns TWO lines for lines ending in '\r\n' :o/
  916. if ( lineBuf[0] == '\n' ) continue;
  917. if ( !bDoneHeader )
  918. {
  919. // Read one or more header lines at the top of the memorylog file
  920. if ( ReadHeaderLine( lineBuf, result.headerInfo ) )
  921. {
  922. lineNum++;
  923. continue;
  924. }
  925. bDoneHeader = true;
  926. // Check file version
  927. if ( ( result.headerInfo.memlogVersion < MEMLOG_VERSION ) && checkVersion )
  928. {
  929. printf( "----Log is from an old version, re-converting...\n" );
  930. CLogFile *oldLog = results.find( memoryLog )->second;
  931. if ( oldLog )
  932. {
  933. delete oldLog;
  934. results.erase( memoryLog );
  935. }
  936. return -1;
  937. }
  938. else if ( result.headerInfo.memlogVersion > MEMLOG_VERSION )
  939. {
  940. printf( "----ERROR: memory log file is newer than this version of memlog.exe!\n\n" );
  941. }
  942. }
  943. CItem item;
  944. string line = lineBuf;
  945. if ( ConvertItem( line, item, prevItem, NULL, memoryLog.c_str(), lineNum, NULL, &result.mapList, &result.playerList, true ) >= 0 )
  946. {
  947. bReallyEmpty = false;
  948. if ( item.globalMapIndex >= 0 )
  949. {
  950. result.entries.AddToTail( item );
  951. prevItem = item;
  952. }
  953. bOrderError = bOrderError || ( item.time < lastTime );
  954. lastTime = item.time;
  955. }
  956. lineNum++;
  957. }
  958. // VXConsole may have concatenated logs from multiple runs - DO NOT WANT!
  959. if ( bOrderError )
  960. printf( "----ERROR: '[MEMORYLOG]' timestamps are out-of-order! Log file may contain multiple runs of the game...\n\n" );
  961. // It's ok to load logs with [MEMORYLOG] lines only for invalid maps, but
  962. // logs with no [MEMORYLOG] lines should be zero size and not loaded at all.
  963. // UPDATE: the header info might still be useful, so we load 'em anyway...
  964. if ( bReallyEmpty )
  965. printf( "----Empty memorylog file.\n" );
  966. // Compute a few aggregate stats in order to speed up log filtering:
  967. int noneMap = gMapHash[ string( "none" ) ];
  968. int menuMap = gMapHash[ string( "backgroundstreet" ) ];
  969. int creditsMap = gMapHash[ string( "credits" ) ];
  970. float memorylogTick = 0;
  971. for ( int i = 0; i < result.entries.Count(); i++ )
  972. {
  973. CItem &item = result.entries[ i ];
  974. result.minFreeMem = min( result.minFreeMem, item.freeMem );
  975. result.minGPUFreeMem = min( result.minGPUFreeMem, item.gpuFree );
  976. result.maxPlayers = max( result.maxPlayers, item.numPlayers );
  977. if ( item.numLocalPlayers > 1 )
  978. result.isSplitscreen = true;
  979. if ( item.isServer && ( item.globalMapIndex != noneMap ) && ( item.globalMapIndex != menuMap ) && ( item.globalMapIndex != creditsMap ) )
  980. result.isServer = true;
  981. if ( i > 0 ) // Average the intervals between memorylog entries
  982. memorylogTick += ( item.time - result.entries[ i - 1 ].time );
  983. }
  984. if ( result.entries.Count() > 1 )
  985. memorylogTick /= ( result.entries.Count() - 1 );
  986. result.memorylogTick = memorylogTick;
  987. return 1;
  988. }
  989. ULONGLONG WriteTime( WIN32_FIND_DATA &fileData )
  990. {
  991. return ( ((ULONGLONG)fileData.ftLastWriteTime.dwHighDateTime ) << 32 ) | (ULONGLONG)fileData.ftLastWriteTime.dwLowDateTime;
  992. }
  993. /*ULONGLONG CreateTime( WIN32_FIND_DATA &fileData )
  994. {
  995. return ( ((ULONGLONG)fileData.ftCreationTime.dwHighDateTime ) << 32 ) | (ULONGLONG)fileData.ftCreationTime.dwLowDateTime;
  996. }*/
  997. ULONGLONG SystemTime( void )
  998. {
  999. SYSTEMTIME LocalSysTime;
  1000. FILETIME LocalFileTime;
  1001. FILETIME UTCFileTime;
  1002. BOOL success = TRUE;
  1003. GetLocalTime( &LocalSysTime );
  1004. success = SystemTimeToFileTime( &LocalSysTime, &LocalFileTime );
  1005. success = ( LocalFileTimeToFileTime( &LocalFileTime, &UTCFileTime ) || success );
  1006. if ( !success )
  1007. {
  1008. static int errCount = 0;
  1009. if ( !errCount++ )
  1010. {
  1011. printf( "----ERROR: error generating system time... all logs will be considered 'active'\n\n" );
  1012. DebuggerBreak();
  1013. }
  1014. return 0;
  1015. }
  1016. return ( ((ULONGLONG)UTCFileTime.dwHighDateTime ) << 32 ) | (ULONGLONG)UTCFileTime.dwLowDateTime;
  1017. }
  1018. bool IsActive( ULONGLONG lastWriteTime )
  1019. {
  1020. return ( SystemTime() < ( lastWriteTime + 60*ONE_SECOND ) );
  1021. }
  1022. void Tick( void )
  1023. {
  1024. // Let the user know we're still working (recursing through fileserver is sloooow)
  1025. SYSTEMTIME systemTime;
  1026. GetLocalTime( &systemTime );
  1027. static int lastTime = -10;
  1028. if ( ( ( systemTime.wSecond % 10 ) == 0 ) && ( systemTime.wSecond != lastTime ) )
  1029. {
  1030. printf( "%02d:%02d:%02d--------------------------------------------------------------------------------------------\n", systemTime.wHour, systemTime.wMinute, systemTime.wSecond );
  1031. lastTime = systemTime.wSecond;
  1032. }
  1033. }
  1034. void _ProcessLogFiles( const char *path, CLogFiles &results,
  1035. int &numLoaded, int &numUnloaded, int &numConverted, int &numUpdated, int &numActive )
  1036. {
  1037. WIN32_FIND_DATA fileData;
  1038. HANDLE handle;
  1039. Tick();
  1040. if ( gConfig.load )
  1041. {
  1042. // Convert 'vxconsole_*.log' files
  1043. static char pathBuf[ _MAX_PATH ]; // No recursion in this loop
  1044. _snprintf( pathBuf, sizeof( pathBuf ), "%svxconsole_*.log", path );
  1045. handle = FindFirstFile( pathBuf, &fileData );
  1046. if ( handle != INVALID_HANDLE_VALUE )
  1047. {
  1048. do
  1049. {
  1050. _snprintf( pathBuf, sizeof( pathBuf ) , "%s%s", path, fileData.cFileName );
  1051. string consoleLog = pathBuf;
  1052. // Does this 'vxconsole_*.log' have a corresponding 'memorylog_*.log'?
  1053. static char prefix[] = "vxconsole_";
  1054. char *suffix = fileData.cFileName + sizeof( prefix ) - 1;
  1055. _snprintf( pathBuf, sizeof( pathBuf ) , "%smemorylog_%s", path, suffix );
  1056. string memoryLog = pathBuf;
  1057. WIN32_FIND_DATA fileData2;
  1058. HANDLE handle2 = FindFirstFile( memoryLog.c_str(), &fileData2 );
  1059. // Convert all logs if asked to do a forced update
  1060. bool bNeedsConverting = gConfig.forceUpdate;
  1061. bool isActive = ( handle2 != INVALID_HANDLE_VALUE ) && IsActive( WriteTime( fileData ) );
  1062. if ( !bNeedsConverting )
  1063. {
  1064. if ( isActive )
  1065. {
  1066. // Only convert currently-active logs if that is specifically requested
  1067. bNeedsConverting = gConfig.updateActive;
  1068. if ( !gConfig.updateActive )
  1069. printf( "--Skipping update of ACTIVE log %s\n", consoleLog.c_str() );
  1070. numActive++;
  1071. }
  1072. else if ( handle2 == INVALID_HANDLE_VALUE )
  1073. {
  1074. // Convert logs we haven't converted before
  1075. bNeedsConverting = true;
  1076. }
  1077. else if ( gConfig.update )
  1078. {
  1079. // We have been asked to reconvert logs that have been updated
  1080. bNeedsConverting = WriteTime( fileData ) > WriteTime( fileData2 );
  1081. }
  1082. }
  1083. if ( bNeedsConverting )
  1084. {
  1085. // Create/update the memory log
  1086. bool bUpdating = ( handle2 != INVALID_HANDLE_VALUE );
  1087. printf( "--%s %s\n", ( bUpdating ? "Updating" : "Converting" ), consoleLog.c_str() );
  1088. if ( isActive )
  1089. printf( "----Log is ACTIVE\n" );
  1090. ConvertVXConsoleLog( consoleLog, memoryLog );
  1091. numConverted += bUpdating ? 0 : 1;
  1092. numUpdated += bUpdating ? 1 : 0;
  1093. }
  1094. FindClose( handle2 );
  1095. Tick();
  1096. }
  1097. while( FindNextFile( handle, &fileData ) );
  1098. FindClose( handle );
  1099. }
  1100. }
  1101. // Note that update/updateActive/forceUpdate imply 'load' (it's confusing otherwise)
  1102. if ( gConfig.load || gConfig.unload )
  1103. {
  1104. // Read in 'memorylog_*.log' files
  1105. static char pathBuf[ _MAX_PATH ]; // No recursion in this loop
  1106. _snprintf( pathBuf, sizeof( pathBuf ), "%smemorylog_*.log", path );
  1107. handle = FindFirstFile( pathBuf, &fileData );
  1108. if ( handle != INVALID_HANDLE_VALUE )
  1109. {
  1110. do
  1111. {
  1112. // Compute full memorylog_*/vxconsole_* paths
  1113. string memoryLog = path;
  1114. memoryLog += fileData.cFileName;
  1115. strlwr( (char *)memoryLog.c_str() );
  1116. // Is this memory log already in memory?
  1117. CLogFiles::iterator it = results.find( memoryLog );
  1118. bool bLoaded = ( it != results.end() );
  1119. // Re-load active logs if requested
  1120. if ( bLoaded && it->second->isActive && gConfig.updateActive )
  1121. {
  1122. CLogFile *oldLog = it->second;
  1123. if ( oldLog ) delete oldLog;
  1124. results.erase( it );
  1125. bLoaded = false;
  1126. }
  1127. if ( gConfig.load && !bLoaded )
  1128. {
  1129. // Get the timestamp for the corresponding vxconsole log (if there is one)
  1130. char *suffix = fileData.cFileName + strlen( "memorylog_" );
  1131. _snprintf( pathBuf, sizeof( pathBuf ) , "%svxconsole_%s", path, suffix );
  1132. string consoleLog = pathBuf;
  1133. strlwr( (char *)consoleLog.c_str() );
  1134. WIN32_FIND_DATA fileData2;
  1135. HANDLE handle2 = FindFirstFile( consoleLog.c_str(), &fileData2 );
  1136. ULONGLONG timeStamp = ( handle2 != INVALID_HANDLE_VALUE ) ? WriteTime( fileData2 ) : WriteTime( fileData );
  1137. bool isActive = ( handle2 != INVALID_HANDLE_VALUE ) ? IsActive( WriteTime( fileData2 ) ) : false;
  1138. if ( handle2 == INVALID_HANDLE_VALUE ) consoleLog = "";
  1139. FindClose( handle2 );
  1140. // Load each memorylog file, and add it to the results
  1141. printf( "--Loading %s\n", memoryLog.c_str() );
  1142. if ( isActive )
  1143. printf( "----Log is ACTIVE, %s\n", ( gConfig.updateActive | gConfig.forceUpdate ) ? "loaded up-to-date memorylog data" : "loading old memorylog data" );
  1144. bool checkVersion = true, ignoreVersion = false;
  1145. int retVal = ReadMemoryLog( memoryLog, consoleLog, results, timeStamp, isActive, checkVersion );
  1146. if ( retVal == -1 )
  1147. {
  1148. // Memorylog is out of date, re-convert it
  1149. numUpdated += ConvertVXConsoleLog( consoleLog, memoryLog ) ? 1 : 0;
  1150. printf( "----Loading re-converted log\n", memoryLog.c_str() );
  1151. retVal = ReadMemoryLog( memoryLog, consoleLog, results, timeStamp, isActive, ignoreVersion );
  1152. }
  1153. if ( retVal > 0 )
  1154. numLoaded++;
  1155. }
  1156. else if ( gConfig.unload && bLoaded )
  1157. {
  1158. // Unload this memorylog file
  1159. printf( "--Unloading %s\n", memoryLog.c_str() );
  1160. results.erase( memoryLog );
  1161. numUnloaded++;
  1162. }
  1163. Tick();
  1164. }
  1165. while( FindNextFile( handle, &fileData ) );
  1166. FindClose( handle );
  1167. }
  1168. }
  1169. if ( gConfig.recurse )
  1170. {
  1171. // Recurse to subdirectories
  1172. char *searchPath = new char[ _MAX_PATH ]; // Avoid blowing the stack due to recursion
  1173. _snprintf( searchPath, _MAX_PATH, "%s*.*", path );
  1174. handle = FindFirstFile( searchPath, &fileData );
  1175. if ( handle != INVALID_HANDLE_VALUE )
  1176. {
  1177. do
  1178. {
  1179. if ( ( fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) &&
  1180. ( fileData.cFileName[ 0 ] != '.' ) )
  1181. {
  1182. char *subDir = new char[ _MAX_PATH ]; // Avoid blowing the stack due to recursion
  1183. _snprintf( subDir, _MAX_PATH, "%s%s\\", path, fileData.cFileName );
  1184. _ProcessLogFiles( subDir, results, numLoaded, numUnloaded, numConverted, numUpdated, numActive );
  1185. delete[] subDir;
  1186. }
  1187. }
  1188. while( FindNextFile( handle, &fileData ) );
  1189. }
  1190. FindClose( handle );
  1191. delete[] searchPath;
  1192. }
  1193. }
  1194. void ProcessLogFiles( const char *path, CLogFiles &results )
  1195. {
  1196. if ( gConfig.unloadAll )
  1197. {
  1198. CLogFiles::iterator it;
  1199. for ( it = results.begin(); it != results.end(); it++ )
  1200. {
  1201. printf( "--Unloading %s\n", it->first.c_str() );
  1202. delete it->second;
  1203. }
  1204. printf( "\n" );
  1205. printf( "%d logs unloaded\n", results.size() );
  1206. printf( "\n" );
  1207. results.clear();
  1208. return;
  1209. }
  1210. if ( !path || !path[0] )
  1211. return;
  1212. if ( gConfig.load || gConfig.unload )
  1213. {
  1214. int numLoaded = 0, numUnloaded = 0, numConverted = 0, numUpdated = 0, numEmpty = 0, numActive = 0;
  1215. _ProcessLogFiles( gConfig.sourcePath, results, numLoaded, numUnloaded, numConverted, numUpdated, numActive );
  1216. for ( CLogFiles::iterator it = results.begin(); it != results.end(); it++ )
  1217. {
  1218. numEmpty += it->second->entries.Count() ? 0 : 1;
  1219. }
  1220. printf( "\n" );
  1221. /* TODO:
  1222. * numLoaded is confusing... not sure what it means
  1223. * I updated a bunch of logs from fileserver and saw this:
  1224. * 2 logs loaded
  1225. * 26 new logs converted
  1226. * 12 logs re-converted
  1227. * 7 active logs (converted)
  1228. * (1435 empty log files)
  1229. * in light of that, I have no idea what "logs loaded" means...
  1230. * hmm, it seems to occur for empty logs!
  1231. * then I ran "r u a" a second time (without copying over any more files) and it found another empty log... and listed logs loaded as 1
  1232. * then I did it again and it loaded no logs...
  1233. * weird
  1234. */
  1235. if ( numLoaded ) printf( "%d logs loaded\n", numLoaded );
  1236. if ( numUnloaded ) printf( "%d logs unloaded\n", numUnloaded );
  1237. if ( numConverted ) printf( "%d new logs converted\n", numConverted );
  1238. if ( numUpdated ) printf( "%d logs re-converted\n", numUpdated );
  1239. if ( numActive ) printf( "%d active logs %s\n", numActive, gConfig.updateActive ? "(converted)" : "(not converted)" );
  1240. if ( numEmpty ) printf( "(%d empty log files)\n", numEmpty );
  1241. printf( "\n" );
  1242. }
  1243. }
  1244. int trim( string &s )
  1245. {
  1246. // Remove whitespace from the head+tail of the string
  1247. size_t oldlen = s.length(), head = 0, tail = 0;
  1248. while( ( head < oldlen ) && V_isspace( s[head] ) ) head++;
  1249. while( ( tail < oldlen ) && V_isspace( s[oldlen-1-tail] ) ) tail++;
  1250. size_t numRemoved = head + tail;
  1251. if ( !numRemoved )
  1252. return 0;
  1253. s = s.substr( head, MAX(0,oldlen-numRemoved) ); // The 'MAX' fixes the case where the string is ALL whitespace :)
  1254. return numRemoved;
  1255. }
  1256. struct TrackSort_t
  1257. {
  1258. int line;
  1259. float minFree;
  1260. };
  1261. int TrackSortFunc( const void *a, const void *b )
  1262. {
  1263. TrackSort_t *A = (TrackSort_t *)a, *B = (TrackSort_t *)b;
  1264. return ( A->minFree < B->minFree ) ? -1 : +1;
  1265. }
  1266. void NormalizeCSVRowLengths( vector< vector< string > > &lines )
  1267. {
  1268. unsigned int nMaxLineLength = 0;
  1269. for ( unsigned int i = 0; i < lines.size(); i++ )
  1270. {
  1271. nMaxLineLength = max( nMaxLineLength, lines[i].size() );
  1272. }
  1273. for ( unsigned int i = 0; i < lines.size(); i++ )
  1274. {
  1275. string empty = i ? "" : "unknown";
  1276. vector<string> &line = lines[i];
  1277. while ( line.size() < nMaxLineLength )
  1278. {
  1279. line.push_back( empty );
  1280. }
  1281. }
  1282. }
  1283. bool UpdateTrackFile( const char *trackFile, const char *trackColumn,
  1284. unsigned int *mapSamples, float *mapMin, float *mapGPUMin )
  1285. {
  1286. // Make a backup before we start
  1287. CUtlBuffer fileBuffer;
  1288. string fileName = trackFile;
  1289. if ( ReadFile( fileName, fileBuffer ) )
  1290. {
  1291. int nCharsToExtension = V_stristr( trackFile, ".csv" ) - trackFile;
  1292. Assert( nCharsToExtension > 0 );
  1293. string backupName = fileName.substr( 0, nCharsToExtension ) + "_bak.csv";
  1294. if ( !WriteFile( backupName, fileBuffer, NULL ) )
  1295. {
  1296. printf( "ERROR: could not write backup tracking .CSV file (%s), aborting!\n\n", backupName.c_str() );
  1297. return false;
  1298. }
  1299. }
  1300. // Now read the .CSV into a vector of vectors of strings
  1301. const string MAP_NAME_HEADER = "Map Name";
  1302. unsigned int nMaxRowLen = 0;
  1303. vector<vector<string>> lines;
  1304. CMapHash mapsInCSV;
  1305. ifstream file;
  1306. file.open( trackFile );
  1307. if ( file.is_open() )
  1308. {
  1309. while ( !file.eof() )
  1310. {
  1311. string lineString;
  1312. getline( file, lineString );
  1313. if ( !lineString.size() )
  1314. break;
  1315. // Each line is a vector of strings:
  1316. string item;
  1317. vector<string> line;
  1318. stringstream lineStream( lineString );
  1319. while ( !lineStream.eof() )
  1320. {
  1321. getline( lineStream, item, ',' );
  1322. trim( item ); // avoid duplicates differing only by whitespace
  1323. line.push_back( item );
  1324. }
  1325. nMaxRowLen = max( nMaxRowLen, line.size() );
  1326. lines.push_back( line );
  1327. }
  1328. file.close();
  1329. }
  1330. // Init an empty .CSV file:
  1331. if ( !nMaxRowLen || !lines.size() )
  1332. {
  1333. vector<string> headerLine;
  1334. headerLine.push_back( MAP_NAME_HEADER );
  1335. lines.clear();
  1336. lines.push_back( headerLine );
  1337. }
  1338. // Validate the .CSV file
  1339. for ( unsigned int i = 0; i < lines.size(); i++ )
  1340. {
  1341. string &firstItem = lines[ i ][ 0 ];
  1342. if ( ( ( i == 0 ) && ( firstItem != MAP_NAME_HEADER ) ) || !firstItem.size() )
  1343. {
  1344. printf( "ERROR: first item on line %d in tracking .CSV file (%s) is invalid, aborting!\n\n", i+1, trackFile );
  1345. return false;
  1346. }
  1347. // Make a note of all the maps found in the file
  1348. if ( i > 0 )
  1349. {
  1350. if ( mapsInCSV.find( firstItem ) != mapsInCSV.end() )
  1351. {
  1352. printf( "ERROR: map '%s' occurs more than once in tracking .CSV file (%s), aborting!\n\n", firstItem.c_str(), trackFile );
  1353. return false;
  1354. }
  1355. mapsInCSV[ firstItem ] = i;
  1356. }
  1357. }
  1358. // Normalize row lengths
  1359. NormalizeCSVRowLengths( lines );
  1360. // Add a new column
  1361. vector<string> &headerLine = lines[0];
  1362. if ( trackColumn[0] )
  1363. {
  1364. headerLine.push_back( string( trackColumn ) );
  1365. }
  1366. else
  1367. {
  1368. ostringstream columnName;
  1369. columnName << "column_";
  1370. columnName << headerLine.size();
  1371. headerLine.push_back( columnName.str() );
  1372. }
  1373. // Add data to the .CSV file
  1374. bool trackGPUMem = ( gConfig.trackStat == TRACKSTAT_MINFREE_GPU );
  1375. unsigned int numMaps = gMapNames.Count();
  1376. for ( unsigned int i = gNumIgnoreMaps; i < numMaps; i++ )
  1377. {
  1378. string empty = "", mapName = gMapNames[ i ];
  1379. if ( mapsInCSV.find( mapName ) == mapsInCSV.end() )
  1380. {
  1381. // This map is not present in the .CSV file, so add a new row
  1382. vector<string> newLine;
  1383. newLine.push_back( mapName );
  1384. while ( newLine.size() < nMaxRowLen )
  1385. {
  1386. newLine.push_back( empty );
  1387. }
  1388. lines.push_back( newLine );
  1389. mapsInCSV[ mapName ] = lines.size() - 1;
  1390. }
  1391. // Add the new data sample to the end of this line
  1392. int nMapLine = mapsInCSV[ mapName ];
  1393. vector<string> &line = lines[ nMapLine ];
  1394. if ( mapSamples[ i ] )
  1395. {
  1396. // Add the worst case sample for this map
  1397. ostringstream minFree;
  1398. minFree << ( trackGPUMem ? mapGPUMin[ i ] : mapMin[ i ] );
  1399. line.push_back( minFree.str() );
  1400. }
  1401. else
  1402. {
  1403. // Add an empty entry for this map
  1404. // (it makes diffing/sorting/combining/comparing these CSVs easier if they all contain the same set of maps)
  1405. line.push_back( empty );
  1406. }
  1407. }
  1408. // Normalize line lengths again, in case some maps weren't updated:
  1409. NormalizeCSVRowLengths( lines );
  1410. // Sort the .CSV file so the maps with the 'worst' most recent data point are at the top
  1411. // (maps missing recent data points are sorted to the top, 'older' maps being biased higher)
  1412. vector< TrackSort_t > sortedLines;
  1413. for ( unsigned int i = 1; i < lines.size(); i++ )
  1414. {
  1415. int memSize = trackGPUMem ? GPU_MEM_SIZE : CPU_MEM_SIZE;
  1416. TrackSort_t trackSort = { i, memSize };
  1417. vector<string> &line = lines[ i ];
  1418. for ( unsigned int j = line.size()-1; j > 0; j-- )
  1419. {
  1420. if ( 1 == sscanf( line[j].c_str(), "%f", &trackSort.minFree ) )
  1421. {
  1422. trackSort.minFree -= memSize*(line.size()-(j+1)); // The older the data, the higher up the list it goes
  1423. sortedLines.push_back( trackSort );
  1424. break;
  1425. }
  1426. if ( j == 1 )
  1427. {
  1428. //printf( "ERROR: no valid datapoints for map '%s' in tracking .CSV file (%s)\n\n", line[0].c_str(), trackFile );
  1429. sortedLines.push_back( trackSort );
  1430. }
  1431. }
  1432. }
  1433. qsort( &sortedLines[0], sortedLines.size(), sizeof(TrackSort_t), TrackSortFunc );
  1434. // Write out the updated file
  1435. ofstream output;
  1436. output.open( trackFile );
  1437. if ( !output.is_open() )
  1438. {
  1439. printf( "ERROR: could write to tracking .CSV file (%s), aborting!\n\n", trackFile );
  1440. return false;
  1441. }
  1442. for ( unsigned int i = 0; i < lines.size(); i++ )
  1443. {
  1444. int nLine = ( i == 0 ) ? 0 : sortedLines[i-1].line;
  1445. vector<string> &line = lines[ nLine ];
  1446. for ( unsigned int j = 0; j < line.size(); j++ )
  1447. {
  1448. if ( j > 0 ) output.write( ",", 1 );
  1449. output.write( line[j].c_str(), line[j].size() );
  1450. }
  1451. if ( i < (lines.size()-1) ) output.write( "\n", 1 );
  1452. }
  1453. output.close();
  1454. return true;
  1455. }
  1456. bool FilterLogFile( CLogFile &logFile, CLogStats &filteredLogStats )
  1457. {
  1458. // TODO: these filters are very inconsistent, rework it to allow more arbitrary filter specification
  1459. // like "include:numplayers:1-4" or "exclude:numplayers:5-8"
  1460. // Filter based on aggregate log properties
  1461. if ( gConfig.dangerLimit && ( logFile.minFreeMem > gConfig.dangerLimit ) && ( logFile.minGPUFreeMem > gConfig.dangerLimit ) )
  1462. return false; // Log contains no entries with memory below the danger limit
  1463. if ( gConfig.isServer && !logFile.isServer )
  1464. return false; // Log doesn't contain any entries where the local machine is the Server
  1465. if ( gConfig.isClient && logFile.isServer )
  1466. return false; // Log contains entries where the local machine is the Server
  1467. if ( gConfig.isSplitscreen && !logFile.isSplitscreen )
  1468. return false; // Log doesn't contain any entries where the local machine is running Splitscreen
  1469. if ( gConfig.minPlayers && ( logFile.maxPlayers < gConfig.minPlayers ) )
  1470. return false; // Log contains no entries with the requisite number of players
  1471. ULONGLONG systemTime = SystemTime();
  1472. if ( gConfig.minAge && ( logFile.modifyTime > ( systemTime - gConfig.minAge*ONE_SECOND ) ) )
  1473. return false; // Too recent
  1474. if ( gConfig.maxAge && ( logFile.modifyTime < ( systemTime - gConfig.maxAge*ONE_SECOND ) ) )
  1475. return false; // Too old
  1476. if ( gConfig.isActive && !logFile.isActive )
  1477. return false; // Not active (not currently being written)
  1478. if ( ( gConfig.dvdHosted == DVDHOSTED_YES ) && ( logFile.headerInfo.dvdHosted != DVDHOSTED_YES ) )
  1479. return false; // Using the HDD
  1480. if ( ( gConfig.dvdHosted == DVDHOSTED_NO ) && ( logFile.headerInfo.dvdHosted != DVDHOSTED_NO ) )
  1481. return false; // Only using the DVD
  1482. if ( gConfig.languageFilter[0] )
  1483. {
  1484. if ( !strstr( logFile.headerInfo.language, gConfig.languageFilter ) )
  1485. return false; // Did not play in the specified language
  1486. }
  1487. if ( gConfig.platform != PLATFORM_UNKNOWN )
  1488. {
  1489. if ( logFile.headerInfo.platform != gConfig.platform )
  1490. return false; // Log file not for the specified platform
  1491. }
  1492. FilterBitfield mapFilter = logFile.mapList.SubstringToBitfield( gConfig.mapFilter );
  1493. if ( !mapFilter )
  1494. return false; // Log doesn't contain the specified map(s)
  1495. FilterBitfield playerFilter = logFile.playerList.SubstringToBitfield( gConfig.playerFilter );
  1496. if ( !playerFilter )
  1497. return false; // Log doesn't contain the specified player(s)
  1498. // The log's aggregate properties passed the filters, so now filter individual log entries
  1499. int dangerTime = -1;
  1500. int numPassingEntries = 0;
  1501. int duration = 0;
  1502. for ( int i = 0; i < logFile.entries.Count(); i++ )
  1503. {
  1504. CItem &item = logFile.entries[ i ];
  1505. if ( gConfig.dangerLimit && ( item.freeMem > gConfig.dangerLimit ) )
  1506. continue; // Filter out entries above the danger limit
  1507. if ( dangerTime == -1 )
  1508. dangerTime = item.time;
  1509. if ( gConfig.dangerTime && ( dangerTime > gConfig.dangerTime ) )
  1510. return false; // Log fails if no entries pass the danger limit soon enough
  1511. // NOTE: we don't filter individual entries by isServer, since we want to see the effects of playing maps AFTER being a server
  1512. if ( gConfig.isSplitscreen && ( item.numLocalPlayers <= 1 ) )
  1513. continue; // Filter out single-screen entries
  1514. if ( gConfig.isSinglescreen && ( item.numLocalPlayers >= 2 ) )
  1515. continue; // Filter out splitscreen entries
  1516. if ( gConfig.mapFilter[0] && !( mapFilter.IsSet( item.logMapIndex ) ) )
  1517. continue; // Filter out entries not on the specified map(s)
  1518. if ( gConfig.minPlayers && ( item.numPlayers < gConfig.minPlayers ) )
  1519. continue; // Filter out entries with too few players
  1520. if ( gConfig.maxPlayers && ( item.numPlayers > gConfig.maxPlayers ) )
  1521. continue; // Filter out entries with too many players
  1522. if ( gConfig.playerFilter[0] && !( playerFilter.Intersects( item.playerBitfield ) ) )
  1523. continue; // Filter out entries not containing the specified player(s)
  1524. // Ok, this entry passes all filters, so update the filtered aggregate stats
  1525. filteredLogStats.mapMin[ item.logMapIndex ] = min( item.freeMem, filteredLogStats.mapMin[ item.logMapIndex ] );
  1526. filteredLogStats.mapGPUMin[ item.logMapIndex ] = min( item.gpuFree, filteredLogStats.mapGPUMin[ item.logMapIndex ] );
  1527. filteredLogStats.mapAverage[ item.logMapIndex ] += item.freeMem;
  1528. filteredLogStats.mapSeconds[ item.logMapIndex ] += logFile.memorylogTick;
  1529. filteredLogStats.mapSamples[ item.logMapIndex ] ++;
  1530. duration = max( duration, item.time ); // Only update this for entries passing the filters
  1531. numPassingEntries++;
  1532. }
  1533. if ( numPassingEntries == 0 )
  1534. return false; // Log contains no lines passing the filters
  1535. if ( gConfig.duration && ( duration < gConfig.duration ) )
  1536. return false; // App run time too short (at filter-passing entries)
  1537. for ( int i = 0; i < CStringList::MAX_STRINGLIST_ENTRIES; i++ )
  1538. {
  1539. // Compute final per-map averages (for passing entries)
  1540. if ( filteredLogStats.mapSamples[ i ] > 0 )
  1541. filteredLogStats.mapAverage[ i ] /= filteredLogStats.mapSamples[ i ];
  1542. }
  1543. // We have a winner!
  1544. return true;
  1545. }
  1546. bool HasFilter( void )
  1547. {
  1548. // Are we filtering the logs in any meaningful way?
  1549. return ( gConfig.dangerLimit || gConfig.dangerTime || gConfig.duration || gConfig.minAge || gConfig.maxAge || gConfig.minPlayers || gConfig.maxPlayers ||
  1550. gConfig.isServer || gConfig.isClient || gConfig.isSplitscreen || gConfig.isSinglescreen || gConfig.isActive || ( gConfig.dvdHosted != DVDHOSTED_UNKNOWN ) ||
  1551. gConfig.mapFilter[0] || gConfig.playerFilter[0] || gConfig.languageFilter[0] || ( gConfig.platform != PLATFORM_UNKNOWN ) );
  1552. }
  1553. void PrintStats( CLogFiles &results )
  1554. {
  1555. int numMaps = gMapNames.Count();
  1556. if ( ( results.size() == 0 ) || ( numMaps == 0 ) )
  1557. {
  1558. printf( "Aggregate stats\n" );
  1559. printf( "---------------\n" );
  1560. printf( "No log files loaded\n" );
  1561. return;
  1562. }
  1563. if ( gConfig.isActive )
  1564. {
  1565. // Update which of our loaded logs are still active
  1566. for ( CLogFiles::iterator logIt = results.begin(); logIt != results.end(); logIt++ )
  1567. {
  1568. CLogFile &logFile = *logIt->second;
  1569. if ( logFile.isActive )
  1570. {
  1571. // If it was active when we loaded it, is it still?
  1572. WIN32_FIND_DATA fileData;
  1573. HANDLE handle = FindFirstFile( logFile.consoleLog.c_str(), &fileData );
  1574. if ( ( handle == INVALID_HANDLE_VALUE ) || !IsActive( WriteTime( fileData ) ) )
  1575. {
  1576. logFile.isActive = false;
  1577. }
  1578. }
  1579. }
  1580. }
  1581. typedef multimap<ULONGLONG, const CLogFile *> CFilteredLogs;
  1582. CFilteredLogs filteredLogs;
  1583. float *mapMin = new float[ numMaps ];
  1584. float *mapGPUMin = new float[ numMaps ];
  1585. float *mapAverage = new float[ numMaps ];
  1586. float *mapSeconds = new float[ numMaps ];
  1587. unsigned int *mapSamples = new unsigned int[ numMaps ];
  1588. unsigned int *mapLogs = new unsigned int[ numMaps ];
  1589. for ( int i = 0; i < numMaps; i++ )
  1590. {
  1591. mapMin[i] = CPU_MEM_SIZE;
  1592. mapGPUMin[i] = GPU_MEM_SIZE;
  1593. mapAverage[i] = 0.0f;
  1594. mapSeconds[i] = 0;
  1595. mapSamples[i] = 0;
  1596. mapLogs[i] = 0;
  1597. }
  1598. for ( CLogFiles::iterator logIt = results.begin(); logIt != results.end(); logIt++ )
  1599. {
  1600. CLogFile &logFile = *logIt->second;
  1601. CLogStats filteredLogStats;
  1602. // Run the log file through our filters
  1603. logFile.badMaps.Purge();
  1604. if ( FilterLogFile( logFile, filteredLogStats ) )
  1605. {
  1606. // Score! Use the data from this log to update per-map aggregate stats:
  1607. for ( int i = 0; i < numMaps; i++ )
  1608. {
  1609. int index = logFile.mapList.StringToInt( gMapNames[ i ] ); // Index into this logfile's map list
  1610. if ( ( index >= 0 ) && ( filteredLogStats.mapSamples[ index ] > 0 ) )
  1611. {
  1612. mapMin[ i ] = min( mapMin[ i ], filteredLogStats.mapMin[ index ] );
  1613. mapGPUMin[ i ] = min( mapGPUMin[ i ], filteredLogStats.mapGPUMin[ index ] );
  1614. mapAverage[ i ] += filteredLogStats.mapAverage[ index ]*filteredLogStats.mapSamples[ index ];
  1615. mapSeconds[ i ] += filteredLogStats.mapSeconds[ index ];
  1616. mapSamples[ i ] += filteredLogStats.mapSamples[ index ];
  1617. mapLogs[ i ]++;
  1618. // Build a list of the 'bad' maps in this log file, for spewage below:
  1619. MapMin_t mapMin = { i, filteredLogStats.mapMin[ index ] };
  1620. logFile.badMaps.AddToTail( mapMin );
  1621. }
  1622. }
  1623. // Add this to the list of logs to spew:
  1624. filteredLogs.insert( make_pair( logFile.modifyTime, &logFile ) );
  1625. }
  1626. }
  1627. if ( gConfig.trackFile[0] )
  1628. {
  1629. UpdateTrackFile( gConfig.trackFile, gConfig.trackColumn, mapSamples, mapMin, mapGPUMin );
  1630. }
  1631. // Spew the names of logs passing our filters (multimap-sorted by log last-modified time):
  1632. // TODO: will this spew scroll faster if we print multiple lines at a time?
  1633. if ( HasFilter() )
  1634. {
  1635. printf( "\n\nLog files which pass filters ( command-line \"%s\" )\n", gConfig.prevCommandLine );
  1636. printf( "----------------------------\n" );
  1637. CFilteredLogs::iterator it;
  1638. for ( it = filteredLogs.begin(); it != filteredLogs.end(); it++ )
  1639. {
  1640. static char headerString[1024];
  1641. SpewHeaderSummary( headerString, ARRAYSIZE( headerString ), it->second->headerInfo );
  1642. printf( " [%s] %s\n", headerString, it->second->consoleLog.c_str()[0] ? it->second->consoleLog.c_str() : it->second->memoryLog.c_str() );
  1643. // Spew a little summary of all offending maps in this log file (makes associating map issues w/ logs much faster)
  1644. printf( " " );
  1645. const CUtlVector<MapMin_t> &badMaps = it->second->badMaps;
  1646. for ( int i = 0; i < badMaps.Count(); i++ ) printf( " %4.1fMB:%-28s|", badMaps[i].minMem, gMapNames[ badMaps[i].map ] );
  1647. printf( "\n" );
  1648. }
  1649. printf( "\n" );
  1650. }
  1651. // NOTE: empty log files will always fail filters, unless you set "danger:512" (TODO: not sure this workaround works any more...)
  1652. printf( "Aggregate stats ( command-line \"%s\" )\n", gConfig.prevCommandLine );
  1653. printf( "--------------- (%d logs pass filters, of %d loaded)\n", filteredLogs.size(), results.size() );
  1654. int totalSeconds = 0;
  1655. if ( filteredLogs.size() )
  1656. {
  1657. for ( int i = 0; i < numMaps; i++ )
  1658. {
  1659. if ( mapSamples[ i ] )
  1660. {
  1661. mapAverage[ i ] /= mapSamples[ i ]; // Compute the final per-map average
  1662. totalSeconds += mapSeconds[ i ];
  1663. printf( "Map %-32s %4d logs, %6d samples, min %6.2fMB, average %6.2fMB\n",
  1664. gMapNames[ i ], mapLogs[ i ], mapSamples[ i ], mapMin[ i ], mapAverage[ i ] );
  1665. }
  1666. }
  1667. }
  1668. int days = (int)( totalSeconds / 86400 );
  1669. totalSeconds -= days*86400;
  1670. int hours = (int)( totalSeconds / 3600 );
  1671. totalSeconds -= hours*3600;
  1672. int mins = (int)( totalSeconds / 60 );
  1673. printf( " Total play time represented by these samples: %dd:%2dh:%2dm\n", days, hours, mins );
  1674. delete mapMin;
  1675. delete mapGPUMin;
  1676. delete mapAverage;
  1677. delete mapSamples;
  1678. delete mapSeconds;
  1679. delete mapLogs;
  1680. }
  1681. void InitMapHash( void )
  1682. {
  1683. for ( int i = 0; i < gNumIgnoreMaps; i++ ) AddNewMapName( gIgnoreMaps[ i ] );
  1684. for ( int i = 0; i < gNumKnownMaps; i++ ) AddNewMapName( gKnownMaps[ i ] );
  1685. }
  1686. const char *CleanPath( const char *path )
  1687. {
  1688. strncpy( gConfig.sourcePath, path, sizeof( gConfig.sourcePath ) );
  1689. strlwr( gConfig.sourcePath );
  1690. int pathLen = (int)strlen( gConfig.sourcePath );
  1691. if ( pathLen && ( gConfig.sourcePath[ pathLen - 1 ] != '\\' ) && ( gConfig.sourcePath[ pathLen - 1 ] != '/' ) )
  1692. {
  1693. strncat( gConfig.sourcePath, "\\", sizeof( gConfig.sourcePath ) );
  1694. }
  1695. return gConfig.sourcePath;
  1696. }
  1697. void Usage()
  1698. {
  1699. printf( "\n" );
  1700. printf( "memlog mines vxconsole logs for memory data\n" );
  1701. printf( "\n" );
  1702. printf( " USAGE: memlog [options] <folder>\n" );
  1703. printf( "\n" );
  1704. printf( "Input is a folder. memlog will convert all vxconsole logs in that folder into\n" );
  1705. printf( "memlog files (prefix 'memorylog_'). It will then output aggregate memory data for\n" );
  1706. printf( "those files.\n" );
  1707. printf( "\n" );
  1708. printf( "options:\n" );
  1709. printf( "[-c|console] run in console mode (see below)\n" );
  1710. printf( "[-r|recurse] recurse the input folder tree\n" );
  1711. printf( "[-u|update] reconvert vxconsole logs that have been updated\n" );
  1712. printf( "[-a|updateactive] reconvert vxconsole logs that are still being updated\n" );
  1713. printf( "[-f|force] reconvert all vxconsole logs\n" );
  1714. printf( "\n" );
  1715. printf( "tracking:\n" );
  1716. printf( "[-track:<file>] update a CSV file which tracks memory stats over time\n" );
  1717. printf( " the filename must end with one of these suffices:\n" );
  1718. printf( " '_MinFreeCPU' - track minimium free CPU memory\n" );
  1719. printf( " '_MinFreeGPU' - track minimium free GPU memory\n" );
  1720. printf( "[-trackcol:<name>] 'name' is the new column to add (rows are maps)\n" );
  1721. printf( "\n" );
  1722. printf( "analysis: (spews data passing these filters - all default to off)\n" );
  1723. printf( " ----the following options are applied per log entry----\n" );
  1724. printf( "[-danger:N] pass log entries in which memory is below N kilobytes\n" );
  1725. printf( "[-minplayers:N] pass log entries with at least this many concurrent players\n" );
  1726. printf( "[-maxplayers:N] pass log entries with at most this many concurrent players\n" );
  1727. printf( "[-issinglescreen] pass log entries in which the local box has 1 player\n" );
  1728. printf( "[-issplitscreen] pass log entries in which the local box has 2 players\n" );
  1729. printf( "[-map:<name>] pass log entries with map names containing this substring\n" );
  1730. printf( "[-player:<name>] pass log entries with player names containing this substring\n" );
  1731. printf( " ----the following options are applied per log file----\n" );
  1732. printf( "[-dangertime:N] pass logs dropping below 'danger' within this many minutes\n" );
  1733. printf( "[-duration:X] pass logs in which the timer reaches this many minutes\n" );
  1734. printf( "[-minage:N] pass logs updated at least this many hours ago\n" );
  1735. printf( "[-maxage:N] pass logs updated at most this many hours ago\n" );
  1736. printf( "[-isserver] pass logs in which the local box is a listen server at least once\n" );
  1737. printf( "[-isclient] pass logs in which the local box is NEVER a listen server\n" );
  1738. printf( "[-isactive] pass logs that are still being updated\n" );
  1739. printf( "[-isdvdonly] pass logs in which the local box is fully DVD hosted\n" );
  1740. printf( "[-isusinghdd] pass logs in which the local box is using the HDD\n" );
  1741. printf( "[-language:<name>] pass logs with the language containing this substring\n" );
  1742. printf( "[-platform:<name>] pass logs generated on this platform ('360', 'PS3', 'PC')\n" );
  1743. printf( "\n" );
  1744. printf( "\n" );
  1745. printf( "Console mode:\n" );
  1746. printf( "\n" );
  1747. printf( " USAGE: [options] [folder]\n" );
  1748. printf( "\n" );
  1749. printf( "In console mode, memlog keeps running until told to quit, allowing the user to\n" );
  1750. printf( "perform any number of log mining operations without having to re-load all the logs\n" );
  1751. printf( "each time. Commands in console mode are the same as the above command-line\n" );
  1752. printf( "options (without the leading '-'). Enter a set of commands, in any order (followed\n" );
  1753. printf( "by a folder path if appropriate), and hit enter. If you don't specify a path, the\n" );
  1754. printf( "most recently entered path will be used.\n" );
  1755. printf( "\n" );
  1756. printf( "console-mode-specific commands:\n" );
  1757. printf( "[load] load more memory logs, from the specified folder\n" );
  1758. printf( "[unload] unload memory logs in the specified folder\n" );
  1759. printf( "[unloadall] unload all memory logs\n" );
  1760. printf( "\n" );
  1761. }
  1762. void InitConfig( bool bCommandLine = false )
  1763. {
  1764. if ( bCommandLine )
  1765. {
  1766. // These persist after the first set of commands:
  1767. gConfig.sourcePath[0] = 0;
  1768. gConfig.consoleMode = false;
  1769. }
  1770. gConfig.recurse = false;
  1771. gConfig.update = false;
  1772. gConfig.updateActive = false;
  1773. gConfig.forceUpdate = false;
  1774. gConfig.load = false;
  1775. gConfig.unload = false;
  1776. gConfig.unloadAll = false;
  1777. gConfig.quitting = false;
  1778. gConfig.help = false;
  1779. // 'track' updates a memory tracking file, but only do it when asked
  1780. gConfig.trackFile[0] = 0;
  1781. gConfig.trackColumn[0] = 0;
  1782. gConfig.trackStat = TRACKSTAT_UNKNOWN;
  1783. // Default all filters off (no analysis)
  1784. gConfig.dangerLimit = 0;
  1785. gConfig.dangerTime = 0;
  1786. gConfig.duration = 0;
  1787. gConfig.minAge = 0;
  1788. gConfig.maxAge = 0;
  1789. gConfig.minPlayers = 0;
  1790. gConfig.maxPlayers = 0;
  1791. gConfig.isServer = false;
  1792. gConfig.isClient = false;
  1793. gConfig.isSplitscreen = false;
  1794. gConfig.isSinglescreen = false;
  1795. gConfig.isActive = false;
  1796. gConfig.dvdHosted = DVDHOSTED_UNKNOWN;
  1797. gConfig.mapFilter[0] = 0;
  1798. gConfig.playerFilter[0] = 0;
  1799. gConfig.languageFilter[0] = 0;
  1800. gConfig.platform = PLATFORM_UNKNOWN;
  1801. }
  1802. bool ParseOption( const char *option )
  1803. {
  1804. // Make everything lower-case for simplicity
  1805. strlwr( (char *)option );
  1806. if ( option[0] == '-' )
  1807. option++;
  1808. // Console mode
  1809. {
  1810. if ( !strcmp( option, "c" ) || !strcmp( option, "console" ) )
  1811. {
  1812. gConfig.consoleMode = true;
  1813. return true;
  1814. }
  1815. if ( !strcmp( option, "quit" ) || !strcmp( option, "exit" ) )
  1816. {
  1817. gConfig.quitting = true;
  1818. return true;
  1819. }
  1820. if ( !strcmp( option, "help" ) || !strcmp( option, "h" ) || !strcmp( option, "usage" ) || !strcmp( option, "?" ) )
  1821. {
  1822. gConfig.help = true;
  1823. return true;
  1824. }
  1825. }
  1826. // Data analysis (filters)
  1827. {
  1828. int dangerLimit;
  1829. if ( sscanf( option, "danger:%d", &dangerLimit ) == 1 )
  1830. {
  1831. if ( dangerLimit > 0 )
  1832. {
  1833. gConfig.dangerLimit = dangerLimit / 1024.0f; // KB -> MB
  1834. return true;
  1835. }
  1836. printf( "!'danger' must be > 0!\n" );
  1837. return false;
  1838. }
  1839. int dangerTime;
  1840. if ( sscanf( option, "dangertime:%d", &dangerTime ) == 1 )
  1841. {
  1842. if ( dangerTime > 0 )
  1843. {
  1844. gConfig.dangerTime = dangerTime*60; // Minutes
  1845. return true;
  1846. }
  1847. printf( "!'dangertime' must be > 0!\n" );
  1848. return false;
  1849. }
  1850. int duration;
  1851. if ( sscanf( option, "duration:%d", &duration ) == 1 )
  1852. {
  1853. if ( duration > 0 )
  1854. {
  1855. gConfig.duration = duration*60; // Minutes
  1856. return true;
  1857. }
  1858. printf( "!'duration' must be > 0!\n" );
  1859. return false;
  1860. }
  1861. int minAge;
  1862. if ( sscanf( option, "minage:%d", &minAge ) == 1 )
  1863. {
  1864. if ( minAge >= 0 )
  1865. {
  1866. gConfig.minAge = minAge*3600; // Hours
  1867. return true;
  1868. }
  1869. printf( "!'minage' must be >= 0!\n" );
  1870. return false;
  1871. }
  1872. int maxAge;
  1873. if ( sscanf( option, "maxage:%d", &maxAge ) == 1 )
  1874. {
  1875. if ( maxAge >= 0 )
  1876. {
  1877. gConfig.maxAge = maxAge*3600; // Hours
  1878. return true;
  1879. }
  1880. printf( "!'maxage' must be >= 0!\n" );
  1881. return false;
  1882. }
  1883. int minPlayers;
  1884. if ( sscanf( option, "minplayers:%d", &minPlayers ) == 1 )
  1885. {
  1886. if ( minPlayers >= 1 )
  1887. {
  1888. gConfig.minPlayers = minPlayers;
  1889. return true;
  1890. }
  1891. printf( "!'minplayers' must be >= 1!\n" );
  1892. return false;
  1893. }
  1894. int maxPlayers;
  1895. if ( sscanf( option, "maxplayers:%d", &maxPlayers ) == 1 )
  1896. {
  1897. if ( maxPlayers >= 1 )
  1898. {
  1899. gConfig.maxPlayers = maxPlayers;
  1900. return true;
  1901. }
  1902. printf( "!'maxplayers' must be >= 1!\n" );
  1903. return false;
  1904. }
  1905. if ( !strcmp( option, "isclient" ) )
  1906. {
  1907. if ( gConfig.isServer )
  1908. {
  1909. printf( "!'isclient' and 'isserver' are mutually exclusive!\n" );
  1910. return false;
  1911. }
  1912. gConfig.isClient = true;
  1913. return true;
  1914. }
  1915. if ( !strcmp( option, "isserver" ) )
  1916. {
  1917. if ( gConfig.isClient )
  1918. {
  1919. printf( "!'isclient' and 'isserver' are mutually exclusive!\n" );
  1920. return false;
  1921. }
  1922. gConfig.isServer = true;
  1923. return true;
  1924. }
  1925. if ( !strcmp( option, "issplitscreen" ) )
  1926. {
  1927. if ( gConfig.isSinglescreen )
  1928. {
  1929. printf( "!'issinglescreen' and 'issplitscreen' are mutually exclusive!\n" );
  1930. return false;
  1931. }
  1932. gConfig.isSplitscreen = true;
  1933. return true;
  1934. }
  1935. if ( !strcmp( option, "issinglescreen" ) )
  1936. {
  1937. if ( gConfig.isSplitscreen )
  1938. {
  1939. printf( "!'issinglescreen' and 'issplitscreen' are mutually exclusive!\n" );
  1940. return false;
  1941. }
  1942. gConfig.isSinglescreen = true;
  1943. return true;
  1944. }
  1945. if ( !strcmp( option, "isactive" ) )
  1946. {
  1947. gConfig.isActive = true;
  1948. return true;
  1949. }
  1950. if ( !strcmp( option, "isdvdonly" ) )
  1951. {
  1952. if ( gConfig.dvdHosted == DVDHOSTED_NO )
  1953. {
  1954. printf( "!'isdvdonly' and 'isusinghdd' are mutually exclusive!\n" );
  1955. return false;
  1956. }
  1957. gConfig.dvdHosted = DVDHOSTED_YES;
  1958. return true;
  1959. }
  1960. if ( !strcmp( option, "isusinghdd" ) )
  1961. {
  1962. if ( gConfig.dvdHosted == DVDHOSTED_YES )
  1963. {
  1964. printf( "!'isdvdonly' and 'isusinghdd' are mutually exclusive!\n" );
  1965. return false;
  1966. }
  1967. gConfig.dvdHosted = DVDHOSTED_NO;
  1968. return true;
  1969. }
  1970. char mapFilter[ FILTER_SIZE ] = "";
  1971. if ( sscanf( option, "map:%32s", mapFilter ) == 1 )
  1972. {
  1973. if ( mapFilter[0] )
  1974. {
  1975. strncpy( gConfig.mapFilter, mapFilter, sizeof( gConfig.mapFilter ) );
  1976. return true;
  1977. }
  1978. return false;
  1979. }
  1980. char playerFilter[ FILTER_SIZE ] = "";
  1981. if ( sscanf( option, "player:%32s", playerFilter ) == 1 )
  1982. {
  1983. if ( playerFilter[0] )
  1984. {
  1985. strncpy( gConfig.playerFilter, playerFilter, sizeof( gConfig.playerFilter ) );
  1986. return true;
  1987. }
  1988. return false;
  1989. }
  1990. char languageFilter[ FILTER_SIZE ] = "";
  1991. if ( sscanf( option, "language:%32s", languageFilter ) == 1 )
  1992. {
  1993. if ( languageFilter[0] )
  1994. {
  1995. strncpy( gConfig.languageFilter, languageFilter, sizeof( gConfig.languageFilter ) );
  1996. return true;
  1997. }
  1998. return false;
  1999. }
  2000. char platformFilter[ 16 ] = "";
  2001. if ( sscanf( option, "platform:%16s", platformFilter ) == 1 )
  2002. {
  2003. return StringToPlatformName( platformFilter, gConfig.platform );
  2004. }
  2005. }
  2006. // File processing
  2007. {
  2008. if ( !strcmp( option, "r" ) || !stricmp( option, "recurse" ) )
  2009. {
  2010. gConfig.recurse = true;
  2011. return true;
  2012. }
  2013. if ( !strcmp( option, "u" ) || !stricmp( option, "update" ) )
  2014. {
  2015. if ( gConfig.unload || gConfig.unloadAll )
  2016. {
  2017. printf( "!'update' is mututally exclusive with 'unload'!\n" );
  2018. return false;
  2019. }
  2020. gConfig.update = true;
  2021. gConfig.load = true; // Less confusing if update implies load
  2022. return true;
  2023. }
  2024. if ( !strcmp( option, "a" ) || !stricmp( option, "updateactive" ) )
  2025. {
  2026. if ( gConfig.unload || gConfig.unloadAll )
  2027. {
  2028. printf( "!'updateactive' is mututally exclusive with 'unload'!\n" );
  2029. return false;
  2030. }
  2031. gConfig.updateActive = true;
  2032. gConfig.update = true; // Less confusing if updateActive implies update
  2033. gConfig.load = true; // Less confusing if update implies load
  2034. return true;
  2035. }
  2036. if ( !strcmp( option, "f" ) || !stricmp( option, "force" ) )
  2037. {
  2038. // TODO: these error checks should really be a post-step - this depends on ordering (i.e. we don't get a message if force appears *before* unload)
  2039. if ( gConfig.unload || gConfig.unloadAll )
  2040. {
  2041. printf( "!'force' is mututally exclusive with 'unload'!\n" );
  2042. return false;
  2043. }
  2044. gConfig.load = true; // Less confusing if update implies load
  2045. gConfig.forceUpdate = true;
  2046. return true;
  2047. }
  2048. if ( !strcmp( option, "load" ) )
  2049. {
  2050. if ( gConfig.unload || gConfig.unloadAll )
  2051. {
  2052. printf( "!'load' is mututally exclusive with 'unload'!\n" );
  2053. return false;
  2054. }
  2055. gConfig.load = true;
  2056. return true;
  2057. }
  2058. if ( !strcmp( option, "unload" ) )
  2059. {
  2060. if ( gConfig.load )
  2061. {
  2062. printf( "!'load' is mututally exclusive with 'unload'!\n" );
  2063. return false;
  2064. }
  2065. gConfig.unload = true;
  2066. return true;
  2067. }
  2068. if ( !strcmp( option, "unloadall" ) )
  2069. {
  2070. if ( gConfig.load )
  2071. {
  2072. printf( "!'load' is mututally exclusive with 'unloadall'!\n" );
  2073. return false;
  2074. }
  2075. gConfig.unloadAll = true;
  2076. return true;
  2077. }
  2078. COMPILE_TIME_ASSERT( sizeof( gConfig.trackFile ) == MAX_PATH );
  2079. if ( 1 == sscanf( option, "track:%260s", gConfig.trackFile ) )
  2080. {
  2081. int nStrLen = strlen( gConfig.trackFile );
  2082. const char *ext = V_stristr( gConfig.trackFile, ".csv" );
  2083. if ( !ext || ( ( ext - &gConfig.trackFile[0] ) != ( nStrLen - 4 ) ) || ( nStrLen <= 4 ) )
  2084. {
  2085. printf( "!'track' must specify a .csv file!\n" );
  2086. return false;
  2087. }
  2088. // Filename suffix determines the stat tracked in the file
  2089. if ( V_stristr( gConfig.trackFile, "_MinFreeCPU" ) )
  2090. gConfig.trackStat = TRACKSTAT_MINFREE_CPU;
  2091. else if ( V_stristr( gConfig.trackFile, "_MinFreeGPU" ) )
  2092. gConfig.trackStat = TRACKSTAT_MINFREE_GPU;
  2093. else
  2094. {
  2095. printf( "!'track' .csv file must end with a valid stat type suffix!\n" );
  2096. return false;
  2097. }
  2098. return true;
  2099. }
  2100. COMPILE_TIME_ASSERT( sizeof( gConfig.trackColumn ) == 32 );
  2101. if ( 1 == sscanf( option, "trackcol:%32s", gConfig.trackColumn ) )
  2102. {
  2103. return true;
  2104. }
  2105. }
  2106. return false;
  2107. }
  2108. bool ParseCommandLine( int argc, _TCHAR* argv[], Config &config )
  2109. {
  2110. bool bCommandLine = true;
  2111. InitConfig( bCommandLine );
  2112. // Cache off the command-line:
  2113. gConfig.prevCommandLine[0] = 0;
  2114. for ( int i = 1; i < argc; i++ )
  2115. {
  2116. strcat( gConfig.prevCommandLine, argv[i] );
  2117. strcat( gConfig.prevCommandLine, " " );
  2118. }
  2119. int numOptions = 1;
  2120. while ( argv[numOptions] && argv[numOptions][0] == '-' )
  2121. {
  2122. if ( !ParseOption( argv[numOptions] ) )
  2123. {
  2124. printf( "ERROR: invalid command-line option '%s'!\n\n\n", argv[numOptions] );
  2125. Usage();
  2126. return false;
  2127. }
  2128. numOptions++;
  2129. }
  2130. if ( numOptions == argc )
  2131. {
  2132. if ( !gConfig.consoleMode )
  2133. {
  2134. printf( "ERROR: no folder path specified!\n\n\n" );
  2135. Usage();
  2136. return false;
  2137. }
  2138. CleanPath( "" );
  2139. }
  2140. else
  2141. {
  2142. gConfig.load = true;
  2143. CleanPath( argv[numOptions] );
  2144. }
  2145. return true;
  2146. }
  2147. void ExtractTokens( char *buffer, vector<string> &tokens )
  2148. {
  2149. char *context = NULL;
  2150. char *token = strtok_s( buffer, " ", &context );
  2151. while( token )
  2152. {
  2153. tokens.push_back( string( token ) );
  2154. token = strtok_s( NULL, " ", &context );
  2155. }
  2156. }
  2157. bool ParseConsoleCommand( void )
  2158. {
  2159. if ( !gConfig.consoleMode )
  2160. return false;
  2161. while( true )
  2162. {
  2163. // Loop until the user inputs a command without errors
  2164. bool bError = false;
  2165. // NOTE: this remembers the last folder path used
  2166. InitConfig();
  2167. printf( "\n\n\n\n" );
  2168. printf( "> current folder path: '%s'\n", gConfig.sourcePath[0] ? gConfig.sourcePath : "<none>" );
  2169. printf( "> Enter a command to process:\n" );
  2170. printf( ">\n" );
  2171. printf( "-> " );
  2172. char buffer[4*_MAX_PATH] = "";
  2173. // TODO: (?while debugging?) this interferes with command prompt cut'n'paste (QuickEdit gets around it):
  2174. cin.getline( buffer, sizeof( buffer ) );
  2175. strcpy( gConfig.prevCommandLine, buffer );
  2176. vector<string> commands;
  2177. ExtractTokens( buffer, commands );
  2178. if ( commands.size() < 1 )
  2179. bError = true;
  2180. for ( unsigned int i = 0; i < commands.size(); i++ )
  2181. {
  2182. string & command = commands[ i ];
  2183. if ( !ParseOption( command.c_str() ) )
  2184. {
  2185. // If parsing files, the last item should be the folder path
  2186. if ( ( i == ( commands.size() - 1 ) ) &&
  2187. ( gConfig.load || gConfig.unload ) )
  2188. {
  2189. CleanPath( command.c_str() );
  2190. }
  2191. else
  2192. {
  2193. printf( "\nInvalid command '%s'\n\n", command.c_str() );
  2194. bError = true;
  2195. }
  2196. }
  2197. }
  2198. if ( ( gConfig.load || gConfig.unload ) && !gConfig.sourcePath[0] )
  2199. {
  2200. printf( "\nYou must specify a path in order to use '%s'\n\n", gConfig.load ? "load" : "unload" );
  2201. bError = true;
  2202. }
  2203. if ( gConfig.quitting )
  2204. return false;
  2205. if ( gConfig.help )
  2206. {
  2207. Usage();
  2208. }
  2209. else if ( !bError )
  2210. {
  2211. printf( ">\n" );
  2212. printf( "> current folder path: '%s'\n", gConfig.sourcePath[0] ? gConfig.sourcePath : "<none>" );
  2213. printf( "> Processing command...\n" );
  2214. printf( "\n" );
  2215. return true;
  2216. }
  2217. }
  2218. }
  2219. // NOTE: this app doesn't bother with little things like freeing memory - enjoy!
  2220. int _tmain(int argc, _TCHAR* argv[])
  2221. {
  2222. InitMapHash();
  2223. // Grab command-line options
  2224. if ( !ParseCommandLine( argc, argv, gConfig ) )
  2225. return 1;
  2226. CLogFiles results;
  2227. do
  2228. {
  2229. // Process log files
  2230. // TODO: aggregate error messages and spew them at the end, so it's easier to notice+read them (list of: " 'log' plus all errors for 'log' ", for every 'log' with errors)
  2231. ProcessLogFiles( gConfig.sourcePath, results );
  2232. // Print stats gathered from the log files
  2233. PrintStats( results );
  2234. }
  2235. // Continue processing commands (console mode) if requested
  2236. while( ParseConsoleCommand() );
  2237. // TODO: add a new option to filter on the 'memory dip' during a map - i.e. the biggest reduction in free memory during a map (always ignore the first ?2? entries for a given map - find the biggest dips to see if 2 is right... ideally, we want to synch up with charlie's numbers for this to be useful)
  2238. // TODO: quantize [MEMORYLOG] timestamps (set 'next time' rather than 'prev time' -> :00, :20, :40) so spew aligns for all clients in a game (well, it wouldn't really be synchronized, depending on how long they sat at their respective menus...)
  2239. // TODO: spew aggregate memlog data
  2240. // - worst-cases
  2241. // - av/min/max per map, per machine, globally
  2242. // probably want to ignore early high results, concentrate on longer-term numbers (after X times or minutes on a map)
  2243. // maybe spew results for "at least x minutes", for several different values of x (15, 30, 60, 90, 120...)
  2244. // - correlate mem with: play time, map loads, numplayers, listen Vs dedicated server,
  2245. // player join/quits, team death/restarts, campaign starts/ends, exit to menu...
  2246. // o load entries for a log file
  2247. // o create aggregates for a log file for various criteria
  2248. // o combine aggregates across all files
  2249. // TODO: would also like to parse SBH spew
  2250. // store values in the header (want maxima, per-alloc-size & per-heap)....
  2251. // - detect start of an SBH dump and write a func to process it (take note of Toms recent change to the spew - therell be two types of spew out there)
  2252. // think about detecting the main menu by looking for adjacent 'none' lines...
  2253. // - post-process the log and convert all reasonable 'none's into 'menu's
  2254. // - convert runs with a full minute of 'none' at either end
  2255. // - menu items must also be "client" and have zero players
  2256. if ( IsDebuggerPresent() )
  2257. {
  2258. printf( "\n\nPress any key to exit...\n" );
  2259. _getche();
  2260. }
  2261. return 0;
  2262. }