Source code of Windows XP (NT5)
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.

679 lines
21 KiB

  1. /*
  2. * Title: analog.c - main file for log analyzer
  3. *
  4. * Description: This file is a tool to analyze sorted memsnap and poolsnap log
  5. * files. It reads in the log files and records each of the
  6. * fields for each process or tag. It then does a trend analysis
  7. * of each field. If any field increases every period, it reports
  8. * a definite leak. If the difference of increase count and
  9. * decrease count for any field is more than half the periods, it
  10. * reports a probable leak.
  11. *
  12. * Functions:
  13. *
  14. * Usage Prints usage message
  15. * DetermineFileType Determines type of log file (mem/pool) & longest entry
  16. * AnalyzeMemLog Reads and analyzes sorted memsnap log
  17. * AnalyzePoolLog Reads and analyzes sorted poolsnap log
  18. * AnalyzeFile Opens file, determines type and calls analysis function
  19. * main Loops on each command arg and calls AnalyzeFile
  20. *
  21. * Copyright (c) 1998-1999 Microsoft Corporation
  22. *
  23. * ToDo:
  24. * 1. Way to ignore some of the periods at the beginning.
  25. * 2. Exceptions file to ignore tags or processes.
  26. * 3. Pick up comments from file and print them as notes.
  27. * *4. switch to just show definites.
  28. * 5. Output computername, build number,checked/free, arch. etc
  29. * 6. option to ignore process that weren't around the whole time
  30. *
  31. * Revision history: LarsOp 12/8/1998 - Created
  32. * ChrisW 3/22/1999 - HTML, Calculate rates
  33. *
  34. */
  35. #include <windows.h>
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <string.h>
  39. #include "analog.h"
  40. #include "htmprint.c" // all the HTML procs and variables
  41. INT g_iMaxPeriods=0; // Global for max periods
  42. BOOL g_fVerbose=FALSE; // Global verbosity for deltas on memlogs
  43. BOOL g_fShowExtraInfo=FALSE; // If true, show computer names, and comments
  44. DWORD g_dwElapseTickCount=0; // Total elapse time for these logs
  45. CHAR* g_pszComputerName=NULL; // name of computer the log file came from
  46. CHAR* g_pszBuildNumber=NULL; // build number
  47. CHAR* g_pszBuildType=NULL; // build type (retail/debug)
  48. CHAR* g_pszSystemTime=NULL; // last time
  49. CHAR* g_pszComments=NULL;
  50. INT g_ReportLevel=9; // 0= only definite, 9=all inclusive
  51. #define TAGCHAR '!' /* character that starts tag line */
  52. /*
  53. * Usage prints the usage message.
  54. */
  55. void Usage()
  56. {
  57. printf("Usage: AnaLog [-v] [-h] [-t] [-d] <file1> [<file2>] [<file3>] [...]\n");
  58. printf(" **no wild card support yet**\n\n");
  59. printf("AnaLog will analyze SortLog output of MemSnap or PoolSnap files.\n\n");
  60. printf("-v Print deltas>%d%% for all processes to be written to stderr\n", PERCENT_TO_PRINT);
  61. printf("-h Produce HTML tables\n");
  62. printf("-t Show Extra info like computer name, and comments\n");
  63. printf("-d Show only definite leaks\n");
  64. printf("\n");
  65. printf("Definite leak means that the value increased every period.\n");
  66. printf("Probable leak means that it increased more than half the periods.\n" );
  67. }
  68. DWORD Trick( LONG amount, DWORD ticks )
  69. {
  70. _int64 temp;
  71. temp= amount;
  72. temp= temp * 3600;
  73. temp= temp * 1000;
  74. temp= temp/(ticks);
  75. return( (DWORD) temp );
  76. }
  77. // GetLocalString
  78. //
  79. // Allocate a heap block and copy string into it.
  80. //
  81. // return: pointer to heap block
  82. //
  83. CHAR* GetLocalString( CHAR* pszString )
  84. {
  85. INT len;
  86. CHAR* pszTemp;
  87. len= strlen( pszString ) + 1;
  88. pszTemp= (CHAR*) LocalAlloc( LPTR, len );
  89. if( !pszTemp ) return NULL;
  90. strcpy( pszTemp, pszString );
  91. return( pszTemp );
  92. }
  93. /*
  94. * ProcessTag
  95. *
  96. * Args: char* - pointer to something like 'tag=value'
  97. *
  98. * return: nothing (but may set global variables)
  99. *
  100. */
  101. #define BREAKSYM "<BR>"
  102. VOID ProcessTag( CHAR* pBuffer )
  103. {
  104. CHAR* pszTagName;
  105. CHAR* pszEqual;
  106. CHAR* pszValue;
  107. INT len;
  108. // eliminate trailing newline
  109. len= strlen( pBuffer );
  110. if( len ) {
  111. if( pBuffer[len-1] == '\n' ) {
  112. pBuffer[len-1]= 0;
  113. }
  114. }
  115. pszTagName= pBuffer;
  116. pszEqual= pBuffer;
  117. while( *pszEqual && (*pszEqual != '=' ) ) {
  118. pszEqual++;
  119. }
  120. if( !*pszEqual ) {
  121. return;
  122. }
  123. *pszEqual= 0; // zero terminate the tag name
  124. pszValue= pszEqual+1;
  125. if( _stricmp( pszTagName, "elapsetickcount" ) == 0 ) {
  126. g_dwElapseTickCount= atol( pszValue );
  127. }
  128. else if( _stricmp( pszTagName, "computername" ) == 0 ) {
  129. g_pszComputerName= GetLocalString( pszValue );
  130. }
  131. else if( _stricmp( pszTagName, "buildnumber" ) == 0 ) {
  132. g_pszBuildNumber= GetLocalString( pszValue );
  133. }
  134. else if( _stricmp( pszTagName, "buildtype" ) == 0 ) {
  135. g_pszBuildType= GetLocalString( pszValue );
  136. }
  137. else if( _stricmp( pszTagName, "systemtime" ) == 0 ) {
  138. g_pszSystemTime= GetLocalString( pszValue );
  139. }
  140. else if( _stricmp( pszTagName, "logtype" ) == 0 ) {
  141. // just ignore
  142. }
  143. else {
  144. INT len;
  145. CHAR* pBuf;
  146. BOOL bIgnoreTag= FALSE;
  147. if( _stricmp(pszTagName,"comment")==0 ) {
  148. bIgnoreTag=TRUE;
  149. }
  150. if( g_pszComments == NULL ) {
  151. len= strlen(pszTagName) + 1 + strlen(pszValue) + 1 +1;
  152. pBuf= (CHAR*) LocalAlloc( LPTR, len );
  153. if( pBuf ) {
  154. if( bIgnoreTag ) {
  155. sprintf(pBuf,"%s\n",pszValue);
  156. }
  157. else {
  158. sprintf(pBuf,"%s %s\n",pszTagName,pszValue);
  159. }
  160. g_pszComments= pBuf;
  161. }
  162. }
  163. else {
  164. len= strlen(g_pszComments)+strlen(pszTagName)+1+strlen(pszValue)+sizeof(BREAKSYM)+1 +1;
  165. pBuf= (CHAR*) LocalAlloc( LPTR, len );
  166. if( pBuf ) {
  167. if( bIgnoreTag ) {
  168. sprintf(pBuf,"%s%s%s\n",g_pszComments,BREAKSYM,pszValue);
  169. }
  170. else {
  171. sprintf(pBuf,"%s%s%s=%s\n",g_pszComments,BREAKSYM,pszTagName,pszValue);
  172. }
  173. LocalFree( g_pszComments );
  174. g_pszComments= pBuf;
  175. }
  176. }
  177. }
  178. }
  179. /*
  180. * DetermineFileType
  181. *
  182. * Args: pFile - File pointer to check
  183. *
  184. * Returns: The type of log of given file. UNKNOWN_LOG_TYPE is the error return.
  185. *
  186. * This function scans the file to determine the log type (based on the first
  187. * word) and the maximum number of lines for any process or tag.
  188. *
  189. */
  190. LogType DetermineFileType(FILE *pFile)
  191. {
  192. char buffer[BUF_LEN]; // buffer for reading lines
  193. char idstring[BUF_LEN]; // ident string (1st word of 1st line)
  194. LogType retval=UNKNOWN_LOG_TYPE;// return value (default to error case)
  195. fpos_t savedFilePosition; // file pos to reset after computing max
  196. int iTemp; // temporary used for computing max entries
  197. int iStatus;
  198. //
  199. // Read the first string of the first line to identify the type
  200. //
  201. if (fgets(buffer, BUF_LEN, pFile)) {
  202. iStatus= sscanf(buffer, "%s", idstring);
  203. if( iStatus == 0 ) {
  204. return UNKNOWN_LOG_TYPE;
  205. }
  206. if (0==_strcmpi(idstring, "Tag")) {
  207. retval=POOL_LOG;
  208. } else if (0==_strcmpi(idstring, "Process")) {
  209. retval=MEM_LOG;
  210. } else {
  211. return UNKNOWN_LOG_TYPE;
  212. }
  213. } else {
  214. return UNKNOWN_LOG_TYPE;
  215. }
  216. //
  217. // Save the position to reset after counting the number of polling periods
  218. //
  219. fgetpos(pFile, &savedFilePosition);
  220. //
  221. // Loop until you get a blank line or end of file
  222. //
  223. g_iMaxPeriods=0;
  224. while (TRUE) {
  225. iTemp=0;
  226. while (TRUE) {
  227. //
  228. // Blank line actually has length 1 for LF character.
  229. //
  230. if( (NULL==fgets(buffer, BUF_LEN, pFile)) ||
  231. (*buffer == TAGCHAR ) ||
  232. (strlen(buffer)<2)) {
  233. break;
  234. }
  235. iTemp++;
  236. }
  237. g_iMaxPeriods=MAX(g_iMaxPeriods, iTemp);
  238. if( *buffer == TAGCHAR ) {
  239. ProcessTag( buffer+1 );
  240. }
  241. if (feof(pFile)) {
  242. break;
  243. }
  244. }
  245. //
  246. // Reset position to first record for reading/analyzing data
  247. //
  248. (void) fsetpos(pFile, &savedFilePosition);
  249. return retval;
  250. }
  251. /*
  252. * AnalyzeMemLog
  253. *
  254. * Args: pointer to sorted memsnap log file
  255. *
  256. * Returns: nothing
  257. *
  258. * This function reads a sorted memsnap logfile. For each process in the file,
  259. * it records each column for every period and then analyzes the memory trends
  260. * for leaks.
  261. *
  262. * If any column increases for each period, that is flagged as a definite leak.
  263. * If any column increases significatnly more often than decrease, it is a
  264. * flagged as a probable leak.
  265. *
  266. */
  267. void AnalyzeMemLog(FILE *pFile)
  268. {
  269. int iPeriod; // index for which period being read
  270. MemLogRec Delta; // Record to track increase from first to last entry
  271. MemLogRec TrendInfo; // Record to track period increases
  272. MemLogRec* pLogArray; // Array of records for each process
  273. char buffer[BUF_LEN]; // Buffer for reading each line from pFile
  274. //
  275. // Allocate enough space for the largest set
  276. //
  277. pLogArray=malloc(g_iMaxPeriods*sizeof(MemLogRec));
  278. if (NULL==pLogArray) {
  279. fprintf(stderr,"Out of memory, aborting file.\n");
  280. return;
  281. }
  282. PRINT_HEADER();
  283. //
  284. // Read the entire file
  285. //
  286. while( !feof(pFile) ) {
  287. //
  288. // Reset trend and period info for each new process
  289. //
  290. memset(&TrendInfo, 0, sizeof(TrendInfo));
  291. iPeriod=0;
  292. //
  293. // Loop until you've read all the entries for this process or tag.
  294. //
  295. // Note: Empty line includes LF character that fgets doesn't eat.
  296. //
  297. while (TRUE) {
  298. if( iPeriod >= g_iMaxPeriods ) break; // done
  299. if ((NULL==fgets(buffer, BUF_LEN, pFile)) ||
  300. (strlen(buffer)<2) ||
  301. (*buffer == TAGCHAR) ||
  302. (0==sscanf(buffer,
  303. "%lx %s %ld %ld %ld %ld %ld %ld %ld",
  304. &pLogArray[iPeriod].Pid,
  305. pLogArray[iPeriod].Name,
  306. &pLogArray[iPeriod].WorkingSet,
  307. &pLogArray[iPeriod].PagedPool,
  308. &pLogArray[iPeriod].NonPagedPool,
  309. &pLogArray[iPeriod].PageFile,
  310. &pLogArray[iPeriod].Commit,
  311. &pLogArray[iPeriod].Handles,
  312. &pLogArray[iPeriod].Threads))) {
  313. break;
  314. }
  315. //
  316. // Calculate TrendInfo:
  317. //
  318. // TrendInfo is a running tally of the periods a value went up vs.
  319. // the periods it went down. See macro in analog.h
  320. //
  321. // if (curval>oldval) {
  322. // trend++;
  323. // } else if (curval<oldval) {
  324. // trend--;
  325. // } else {
  326. // trend=trend; // stay same
  327. // }
  328. //
  329. if (iPeriod>0) {
  330. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, WorkingSet);
  331. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, PagedPool);
  332. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, NonPagedPool);
  333. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, PageFile);
  334. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Commit);
  335. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Handles);
  336. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Threads);
  337. }
  338. iPeriod++;
  339. }
  340. if (iPeriod>1) {
  341. //
  342. // GET_DELTA simply records the difference (end-begin) for each field
  343. //
  344. // Macro in analog.h
  345. //
  346. GET_DELTA(Delta, pLogArray, iPeriod, WorkingSet);
  347. GET_DELTA(Delta, pLogArray, iPeriod, PagedPool);
  348. GET_DELTA(Delta, pLogArray, iPeriod, NonPagedPool);
  349. GET_DELTA(Delta, pLogArray, iPeriod, PageFile);
  350. GET_DELTA(Delta, pLogArray, iPeriod, Commit);
  351. GET_DELTA(Delta, pLogArray, iPeriod, Handles);
  352. GET_DELTA(Delta, pLogArray, iPeriod, Threads);
  353. //
  354. // PRINT_IF_TREND reports probable or definite leaks for any field.
  355. //
  356. // Definite leak is where the value goes up every period
  357. // Probable leak is where the value goes up most of the time
  358. //
  359. // Macro in analog.h
  360. //
  361. // if (trend==numperiods-1) {
  362. // definite_leak;
  363. // } else if (trend>=numperiods/2) {
  364. // probable_leak;
  365. // }
  366. //
  367. // PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, WorkingSet);
  368. PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, PagedPool);
  369. PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, NonPagedPool);
  370. // PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, PageFile);
  371. PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Commit);
  372. PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Handles);
  373. PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Threads);
  374. if (g_fVerbose && ANY_PERCENT_GREATER(Delta, pLogArray)) {
  375. printf("%-12s:WS=%4ld%% PP=%4ld%% NP=%4ld%% "
  376. "PF=%4ld%% C=%4ld%% H=%4ld%% T=%4ld%%\n",
  377. pLogArray[0].Name,
  378. PERCENT(Delta.WorkingSet , pLogArray[0].WorkingSet ),
  379. PERCENT(Delta.PagedPool , pLogArray[0].PagedPool ),
  380. PERCENT(Delta.NonPagedPool, pLogArray[0].NonPagedPool),
  381. PERCENT(Delta.PageFile , pLogArray[0].PageFile ),
  382. PERCENT(Delta.Commit , pLogArray[0].Commit ),
  383. PERCENT(Delta.Handles , pLogArray[0].Handles ),
  384. PERCENT(Delta.Threads , pLogArray[0].Threads ));
  385. }
  386. }
  387. }
  388. PRINT_TRAILER();
  389. }
  390. /*
  391. * AnalyzePoolLog
  392. *
  393. * Args: pointer to sorted poolsnap log file
  394. *
  395. * Returns: nothing
  396. *
  397. * This function reads a sorted poolsnap logfile. For each pool tag in the file,
  398. * it records each column for every period and then analyzes the memory trends
  399. * for leaks.
  400. *
  401. * If any column increases for each period, that is flagged as a definite leak.
  402. * If any column increases significatnly more often than decrease, it is a
  403. * flagged as a probable leak.
  404. *
  405. */
  406. void AnalyzePoolLog(FILE *pFile)
  407. {
  408. int iPeriod; // index for which period being read
  409. PoolLogRec Delta, // Record to track increase from first to last entry
  410. TrendInfo, // Record to track period increases
  411. *pLogArray;// Array of records for each pool tag
  412. char buffer[BUF_LEN]; // Buffer for reading each line from pFile
  413. //
  414. // Allocate enough space for the largest set
  415. //
  416. pLogArray=malloc(g_iMaxPeriods*sizeof(PoolLogRec));
  417. if (NULL==pLogArray) {
  418. fprintf(stderr,"Out of memory, aborting file.\n");
  419. return;
  420. }
  421. PRINT_HEADER();
  422. //
  423. // Read the entire file
  424. //
  425. while( !feof(pFile) ) {
  426. //
  427. // Reset trend and period info for each new pool tag
  428. //
  429. memset(&TrendInfo, 0, sizeof(TrendInfo));
  430. iPeriod=0;
  431. //
  432. // Loop until you've read all the entries for this process or tag.
  433. //
  434. // Note: Empty line includes LF character that fgets doesn't eat.
  435. //
  436. while( TRUE ) {
  437. if( iPeriod >= g_iMaxPeriods ) break; // done
  438. if ((NULL==fgets(buffer, BUF_LEN, pFile)) ||
  439. (strlen(buffer)<2) ||
  440. (*buffer == TAGCHAR ) ||
  441. (0==sscanf(buffer,
  442. " %4c %s %ld %ld %ld %ld %ld",
  443. pLogArray[iPeriod].Name,
  444. pLogArray[iPeriod].Type,
  445. &pLogArray[iPeriod].Allocs,
  446. &pLogArray[iPeriod].Frees,
  447. &pLogArray[iPeriod].Diff,
  448. &pLogArray[iPeriod].Bytes,
  449. &pLogArray[iPeriod].PerAlloc))) {
  450. break;
  451. }
  452. pLogArray[iPeriod].Name[4]='\0'; // Terminate the tag
  453. //
  454. // Calculate TrendInfo:
  455. //
  456. // TrendInfo is a running tally of the periods a value went up vs.
  457. // the periods it went down. See macro in analog.h
  458. //
  459. // if (curval>oldval) {
  460. // trend++;
  461. // } else if (curval<oldval) {
  462. // trend--;
  463. // } else {
  464. // trend=trend; // stay same
  465. // }
  466. //
  467. if (iPeriod>0) {
  468. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Allocs);
  469. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Frees);
  470. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Diff);
  471. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Bytes);
  472. GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, PerAlloc);
  473. }
  474. iPeriod++;
  475. }
  476. //
  477. // skip rest of loop if a blank line or useless line
  478. //
  479. if( iPeriod == 0 ) continue;
  480. strcpy(TrendInfo.Name,pLogArray[0].Name);
  481. //
  482. // GET_DELTA simply records the difference (end-begin) for each field
  483. //
  484. // Macro in analog.h
  485. //
  486. GET_DELTA(Delta, pLogArray, iPeriod, Allocs);
  487. GET_DELTA(Delta, pLogArray, iPeriod, Frees);
  488. GET_DELTA(Delta, pLogArray, iPeriod, Diff);
  489. GET_DELTA(Delta, pLogArray, iPeriod, Bytes);
  490. GET_DELTA(Delta, pLogArray, iPeriod, PerAlloc);
  491. //
  492. // PRINT_IF_TREND reports probable or definite leaks for any field.
  493. //
  494. // Definite leak is where the value goes up every period
  495. // Probable leak is where the value goes up most of the time
  496. //
  497. // Macro in analog.h
  498. //
  499. // if (trend==numperiods-1) {
  500. // definite_leak;
  501. // } else if (trend>=numperiods/2) {
  502. // probable_leak;
  503. // }
  504. //
  505. // Note: Allocs, Frees and PerAlloc don't make sense to report trends.
  506. //
  507. // PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Allocs);
  508. // PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Frees);
  509. // PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, PerAlloc);
  510. // PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Diff);
  511. PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Bytes);
  512. }
  513. PRINT_TRAILER();
  514. }
  515. /*
  516. * AnalyzeFile
  517. *
  518. * Args: pFileName - filename to analyze
  519. *
  520. * Returns: nothing
  521. *
  522. * This function opens the specified file, determines the file type and calls
  523. * the appropriate analyze function.
  524. *
  525. */
  526. void AnalyzeFile(char *pFileName)
  527. {
  528. FILE *pFile; // using fopen for fgets functionality
  529. LogType WhichType=UNKNOWN_LOG_TYPE; // which type of log (pool/mem)
  530. pFile=fopen(pFileName, "r");
  531. if (NULL==pFile) {
  532. fprintf(stderr,"Unable to open %s, Error=%d\n", pFileName, GetLastError());
  533. return;
  534. }
  535. WhichType=DetermineFileType(pFile);
  536. switch (WhichType)
  537. {
  538. case MEM_LOG:
  539. AnalyzeMemLog(pFile);
  540. break;
  541. case POOL_LOG:
  542. AnalyzePoolLog(pFile);
  543. break;
  544. default:
  545. ;
  546. }
  547. fclose(pFile);
  548. }
  549. /*
  550. * main
  551. *
  552. * Args: argc - count of command line args
  553. * argv - array of command line args
  554. *
  555. * Returns: 0 if called correctly, 1 if not.
  556. *
  557. * This is the entry point for analog. It simply parses the command line args
  558. * and then calls AnalyzeFile on each file.
  559. *
  560. */
  561. int _cdecl main(int argc, char *argv[])
  562. {
  563. int ArgIndex;
  564. if (argc<2) {
  565. Usage();
  566. return 1;
  567. }
  568. for( ArgIndex=1; ArgIndex<argc; ArgIndex++) {
  569. if( (*argv[ArgIndex] == '/') || (*argv[ArgIndex]=='-') ) {
  570. CHAR chr;
  571. chr= argv[ArgIndex][1];
  572. switch( chr ) {
  573. case 'v': case 'V': // verbose
  574. g_fVerbose= TRUE;
  575. break;
  576. case 'h': case 'H': // output HTML
  577. bHtmlStyle= TRUE;
  578. break;
  579. case 't': case 'T': // show all the extra info
  580. g_fShowExtraInfo=TRUE;
  581. break;
  582. case 'd': case 'D': // print definite only
  583. g_ReportLevel= 0;
  584. break;
  585. default:
  586. Usage();
  587. break;
  588. }
  589. }
  590. else {
  591. AnalyzeFile(argv[ArgIndex]);
  592. }
  593. }
  594. return 0;
  595. }