680 lines
21 KiB
C
680 lines
21 KiB
C
|
/*
|
||
|
* Title: analog.c - main file for log analyzer
|
||
|
*
|
||
|
* Description: This file is a tool to analyze sorted memsnap and poolsnap log
|
||
|
* files. It reads in the log files and records each of the
|
||
|
* fields for each process or tag. It then does a trend analysis
|
||
|
* of each field. If any field increases every period, it reports
|
||
|
* a definite leak. If the difference of increase count and
|
||
|
* decrease count for any field is more than half the periods, it
|
||
|
* reports a probable leak.
|
||
|
*
|
||
|
* Functions:
|
||
|
*
|
||
|
* Usage Prints usage message
|
||
|
* DetermineFileType Determines type of log file (mem/pool) & longest entry
|
||
|
* AnalyzeMemLog Reads and analyzes sorted memsnap log
|
||
|
* AnalyzePoolLog Reads and analyzes sorted poolsnap log
|
||
|
* AnalyzeFile Opens file, determines type and calls analysis function
|
||
|
* main Loops on each command arg and calls AnalyzeFile
|
||
|
*
|
||
|
* Copyright (c) 1998-1999 Microsoft Corporation
|
||
|
*
|
||
|
* ToDo:
|
||
|
* 1. Way to ignore some of the periods at the beginning.
|
||
|
* 2. Exceptions file to ignore tags or processes.
|
||
|
* 3. Pick up comments from file and print them as notes.
|
||
|
* *4. switch to just show definites.
|
||
|
* 5. Output computername, build number,checked/free, arch. etc
|
||
|
* 6. option to ignore process that weren't around the whole time
|
||
|
*
|
||
|
* Revision history: LarsOp 12/8/1998 - Created
|
||
|
* ChrisW 3/22/1999 - HTML, Calculate rates
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <windows.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include "analog.h"
|
||
|
|
||
|
#include "htmprint.c" // all the HTML procs and variables
|
||
|
|
||
|
|
||
|
INT g_iMaxPeriods=0; // Global for max periods
|
||
|
BOOL g_fVerbose=FALSE; // Global verbosity for deltas on memlogs
|
||
|
BOOL g_fShowExtraInfo=FALSE; // If true, show computer names, and comments
|
||
|
DWORD g_dwElapseTickCount=0; // Total elapse time for these logs
|
||
|
CHAR* g_pszComputerName=NULL; // name of computer the log file came from
|
||
|
CHAR* g_pszBuildNumber=NULL; // build number
|
||
|
CHAR* g_pszBuildType=NULL; // build type (retail/debug)
|
||
|
CHAR* g_pszSystemTime=NULL; // last time
|
||
|
CHAR* g_pszComments=NULL;
|
||
|
INT g_ReportLevel=9; // 0= only definite, 9=all inclusive
|
||
|
|
||
|
#define TAGCHAR '!' /* character that starts tag line */
|
||
|
|
||
|
/*
|
||
|
* Usage prints the usage message.
|
||
|
*/
|
||
|
void Usage()
|
||
|
{
|
||
|
printf("Usage: AnaLog [-v] [-h] [-t] [-d] <file1> [<file2>] [<file3>] [...]\n");
|
||
|
printf(" **no wild card support yet**\n\n");
|
||
|
printf("AnaLog will analyze SortLog output of MemSnap or PoolSnap files.\n\n");
|
||
|
printf("-v Print deltas>%d%% for all processes to be written to stderr\n", PERCENT_TO_PRINT);
|
||
|
printf("-h Produce HTML tables\n");
|
||
|
printf("-t Show Extra info like computer name, and comments\n");
|
||
|
printf("-d Show only definite leaks\n");
|
||
|
printf("\n");
|
||
|
printf("Definite leak means that the value increased every period.\n");
|
||
|
printf("Probable leak means that it increased more than half the periods.\n" );
|
||
|
}
|
||
|
|
||
|
DWORD Trick( LONG amount, DWORD ticks )
|
||
|
{
|
||
|
_int64 temp;
|
||
|
|
||
|
|
||
|
temp= amount;
|
||
|
temp= temp * 3600;
|
||
|
|
||
|
temp= temp * 1000;
|
||
|
temp= temp/(ticks);
|
||
|
|
||
|
return( (DWORD) temp );
|
||
|
}
|
||
|
|
||
|
|
||
|
// GetLocalString
|
||
|
//
|
||
|
// Allocate a heap block and copy string into it.
|
||
|
//
|
||
|
// return: pointer to heap block
|
||
|
//
|
||
|
|
||
|
CHAR* GetLocalString( CHAR* pszString )
|
||
|
{
|
||
|
INT len;
|
||
|
CHAR* pszTemp;
|
||
|
|
||
|
len= strlen( pszString ) + 1;
|
||
|
|
||
|
pszTemp= (CHAR*) LocalAlloc( LPTR, len );
|
||
|
|
||
|
if( !pszTemp ) return NULL;
|
||
|
|
||
|
strcpy( pszTemp, pszString );
|
||
|
|
||
|
return( pszTemp );
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* ProcessTag
|
||
|
*
|
||
|
* Args: char* - pointer to something like 'tag=value'
|
||
|
*
|
||
|
* return: nothing (but may set global variables)
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#define BREAKSYM "<BR>"
|
||
|
|
||
|
VOID ProcessTag( CHAR* pBuffer )
|
||
|
{
|
||
|
CHAR* pszTagName;
|
||
|
CHAR* pszEqual;
|
||
|
CHAR* pszValue;
|
||
|
INT len;
|
||
|
|
||
|
// eliminate trailing newline
|
||
|
|
||
|
len= strlen( pBuffer );
|
||
|
|
||
|
if( len ) {
|
||
|
if( pBuffer[len-1] == '\n' ) {
|
||
|
pBuffer[len-1]= 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pszTagName= pBuffer;
|
||
|
|
||
|
pszEqual= pBuffer;
|
||
|
|
||
|
while( *pszEqual && (*pszEqual != '=' ) ) {
|
||
|
pszEqual++;
|
||
|
}
|
||
|
|
||
|
if( !*pszEqual ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
*pszEqual= 0; // zero terminate the tag name
|
||
|
|
||
|
pszValue= pszEqual+1;
|
||
|
|
||
|
if( _stricmp( pszTagName, "elapsetickcount" ) == 0 ) {
|
||
|
g_dwElapseTickCount= atol( pszValue );
|
||
|
}
|
||
|
|
||
|
else if( _stricmp( pszTagName, "computername" ) == 0 ) {
|
||
|
g_pszComputerName= GetLocalString( pszValue );
|
||
|
}
|
||
|
|
||
|
else if( _stricmp( pszTagName, "buildnumber" ) == 0 ) {
|
||
|
g_pszBuildNumber= GetLocalString( pszValue );
|
||
|
}
|
||
|
|
||
|
else if( _stricmp( pszTagName, "buildtype" ) == 0 ) {
|
||
|
g_pszBuildType= GetLocalString( pszValue );
|
||
|
}
|
||
|
|
||
|
else if( _stricmp( pszTagName, "systemtime" ) == 0 ) {
|
||
|
g_pszSystemTime= GetLocalString( pszValue );
|
||
|
}
|
||
|
|
||
|
else if( _stricmp( pszTagName, "logtype" ) == 0 ) {
|
||
|
// just ignore
|
||
|
}
|
||
|
|
||
|
else {
|
||
|
INT len;
|
||
|
CHAR* pBuf;
|
||
|
BOOL bIgnoreTag= FALSE;
|
||
|
|
||
|
if( _stricmp(pszTagName,"comment")==0 ) {
|
||
|
bIgnoreTag=TRUE;
|
||
|
}
|
||
|
|
||
|
if( g_pszComments == NULL ) {
|
||
|
len= strlen(pszTagName) + 1 + strlen(pszValue) + 1 +1;
|
||
|
pBuf= (CHAR*) LocalAlloc( LPTR, len );
|
||
|
if( pBuf ) {
|
||
|
if( bIgnoreTag ) {
|
||
|
sprintf(pBuf,"%s\n",pszValue);
|
||
|
}
|
||
|
else {
|
||
|
sprintf(pBuf,"%s %s\n",pszTagName,pszValue);
|
||
|
}
|
||
|
g_pszComments= pBuf;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
len= strlen(g_pszComments)+strlen(pszTagName)+1+strlen(pszValue)+sizeof(BREAKSYM)+1 +1;
|
||
|
pBuf= (CHAR*) LocalAlloc( LPTR, len );
|
||
|
if( pBuf ) {
|
||
|
if( bIgnoreTag ) {
|
||
|
sprintf(pBuf,"%s%s%s\n",g_pszComments,BREAKSYM,pszValue);
|
||
|
}
|
||
|
else {
|
||
|
sprintf(pBuf,"%s%s%s=%s\n",g_pszComments,BREAKSYM,pszTagName,pszValue);
|
||
|
}
|
||
|
LocalFree( g_pszComments );
|
||
|
g_pszComments= pBuf;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* DetermineFileType
|
||
|
*
|
||
|
* Args: pFile - File pointer to check
|
||
|
*
|
||
|
* Returns: The type of log of given file. UNKNOWN_LOG_TYPE is the error return.
|
||
|
*
|
||
|
* This function scans the file to determine the log type (based on the first
|
||
|
* word) and the maximum number of lines for any process or tag.
|
||
|
*
|
||
|
*/
|
||
|
LogType DetermineFileType(FILE *pFile)
|
||
|
{
|
||
|
char buffer[BUF_LEN]; // buffer for reading lines
|
||
|
char idstring[BUF_LEN]; // ident string (1st word of 1st line)
|
||
|
LogType retval=UNKNOWN_LOG_TYPE;// return value (default to error case)
|
||
|
fpos_t savedFilePosition; // file pos to reset after computing max
|
||
|
int iTemp; // temporary used for computing max entries
|
||
|
int iStatus;
|
||
|
|
||
|
//
|
||
|
// Read the first string of the first line to identify the type
|
||
|
//
|
||
|
if (fgets(buffer, BUF_LEN, pFile)) {
|
||
|
iStatus= sscanf(buffer, "%s", idstring);
|
||
|
if( iStatus == 0 ) {
|
||
|
return UNKNOWN_LOG_TYPE;
|
||
|
}
|
||
|
if (0==_strcmpi(idstring, "Tag")) {
|
||
|
retval=POOL_LOG;
|
||
|
} else if (0==_strcmpi(idstring, "Process")) {
|
||
|
retval=MEM_LOG;
|
||
|
} else {
|
||
|
return UNKNOWN_LOG_TYPE;
|
||
|
}
|
||
|
} else {
|
||
|
return UNKNOWN_LOG_TYPE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Save the position to reset after counting the number of polling periods
|
||
|
//
|
||
|
fgetpos(pFile, &savedFilePosition);
|
||
|
|
||
|
//
|
||
|
// Loop until you get a blank line or end of file
|
||
|
//
|
||
|
g_iMaxPeriods=0;
|
||
|
while (TRUE) {
|
||
|
iTemp=0;
|
||
|
while (TRUE) {
|
||
|
//
|
||
|
// Blank line actually has length 1 for LF character.
|
||
|
//
|
||
|
if( (NULL==fgets(buffer, BUF_LEN, pFile)) ||
|
||
|
(*buffer == TAGCHAR ) ||
|
||
|
(strlen(buffer)<2)) {
|
||
|
break;
|
||
|
}
|
||
|
iTemp++;
|
||
|
}
|
||
|
g_iMaxPeriods=MAX(g_iMaxPeriods, iTemp);
|
||
|
|
||
|
if( *buffer == TAGCHAR ) {
|
||
|
ProcessTag( buffer+1 );
|
||
|
}
|
||
|
if (feof(pFile)) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Reset position to first record for reading/analyzing data
|
||
|
//
|
||
|
(void) fsetpos(pFile, &savedFilePosition);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* AnalyzeMemLog
|
||
|
*
|
||
|
* Args: pointer to sorted memsnap log file
|
||
|
*
|
||
|
* Returns: nothing
|
||
|
*
|
||
|
* This function reads a sorted memsnap logfile. For each process in the file,
|
||
|
* it records each column for every period and then analyzes the memory trends
|
||
|
* for leaks.
|
||
|
*
|
||
|
* If any column increases for each period, that is flagged as a definite leak.
|
||
|
* If any column increases significatnly more often than decrease, it is a
|
||
|
* flagged as a probable leak.
|
||
|
*
|
||
|
*/
|
||
|
void AnalyzeMemLog(FILE *pFile)
|
||
|
{
|
||
|
int iPeriod; // index for which period being read
|
||
|
MemLogRec Delta; // Record to track increase from first to last entry
|
||
|
MemLogRec TrendInfo; // Record to track period increases
|
||
|
MemLogRec* pLogArray; // Array of records for each process
|
||
|
char buffer[BUF_LEN]; // Buffer for reading each line from pFile
|
||
|
|
||
|
//
|
||
|
// Allocate enough space for the largest set
|
||
|
//
|
||
|
pLogArray=malloc(g_iMaxPeriods*sizeof(MemLogRec));
|
||
|
if (NULL==pLogArray) {
|
||
|
fprintf(stderr,"Out of memory, aborting file.\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PRINT_HEADER();
|
||
|
//
|
||
|
// Read the entire file
|
||
|
//
|
||
|
while( !feof(pFile) ) {
|
||
|
|
||
|
//
|
||
|
// Reset trend and period info for each new process
|
||
|
//
|
||
|
memset(&TrendInfo, 0, sizeof(TrendInfo));
|
||
|
iPeriod=0;
|
||
|
|
||
|
//
|
||
|
// Loop until you've read all the entries for this process or tag.
|
||
|
//
|
||
|
// Note: Empty line includes LF character that fgets doesn't eat.
|
||
|
//
|
||
|
while (TRUE) {
|
||
|
|
||
|
if( iPeriod >= g_iMaxPeriods ) break; // done
|
||
|
|
||
|
if ((NULL==fgets(buffer, BUF_LEN, pFile)) ||
|
||
|
(strlen(buffer)<2) ||
|
||
|
(*buffer == TAGCHAR) ||
|
||
|
(0==sscanf(buffer,
|
||
|
"%lx %s %ld %ld %ld %ld %ld %ld %ld",
|
||
|
&pLogArray[iPeriod].Pid,
|
||
|
pLogArray[iPeriod].Name,
|
||
|
&pLogArray[iPeriod].WorkingSet,
|
||
|
&pLogArray[iPeriod].PagedPool,
|
||
|
&pLogArray[iPeriod].NonPagedPool,
|
||
|
&pLogArray[iPeriod].PageFile,
|
||
|
&pLogArray[iPeriod].Commit,
|
||
|
&pLogArray[iPeriod].Handles,
|
||
|
&pLogArray[iPeriod].Threads))) {
|
||
|
break;
|
||
|
}
|
||
|
//
|
||
|
// Calculate TrendInfo:
|
||
|
//
|
||
|
// TrendInfo is a running tally of the periods a value went up vs.
|
||
|
// the periods it went down. See macro in analog.h
|
||
|
//
|
||
|
// if (curval>oldval) {
|
||
|
// trend++;
|
||
|
// } else if (curval<oldval) {
|
||
|
// trend--;
|
||
|
// } else {
|
||
|
// trend=trend; // stay same
|
||
|
// }
|
||
|
//
|
||
|
if (iPeriod>0) {
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, WorkingSet);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, PagedPool);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, NonPagedPool);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, PageFile);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Commit);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Handles);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Threads);
|
||
|
}
|
||
|
iPeriod++;
|
||
|
}
|
||
|
|
||
|
if (iPeriod>1) {
|
||
|
//
|
||
|
// GET_DELTA simply records the difference (end-begin) for each field
|
||
|
//
|
||
|
// Macro in analog.h
|
||
|
//
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, WorkingSet);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, PagedPool);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, NonPagedPool);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, PageFile);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, Commit);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, Handles);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, Threads);
|
||
|
|
||
|
//
|
||
|
// PRINT_IF_TREND reports probable or definite leaks for any field.
|
||
|
//
|
||
|
// Definite leak is where the value goes up every period
|
||
|
// Probable leak is where the value goes up most of the time
|
||
|
//
|
||
|
// Macro in analog.h
|
||
|
//
|
||
|
// if (trend==numperiods-1) {
|
||
|
// definite_leak;
|
||
|
// } else if (trend>=numperiods/2) {
|
||
|
// probable_leak;
|
||
|
// }
|
||
|
//
|
||
|
// PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, WorkingSet);
|
||
|
PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, PagedPool);
|
||
|
PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, NonPagedPool);
|
||
|
// PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, PageFile);
|
||
|
PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Commit);
|
||
|
PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Handles);
|
||
|
PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Threads);
|
||
|
if (g_fVerbose && ANY_PERCENT_GREATER(Delta, pLogArray)) {
|
||
|
printf("%-12s:WS=%4ld%% PP=%4ld%% NP=%4ld%% "
|
||
|
"PF=%4ld%% C=%4ld%% H=%4ld%% T=%4ld%%\n",
|
||
|
pLogArray[0].Name,
|
||
|
PERCENT(Delta.WorkingSet , pLogArray[0].WorkingSet ),
|
||
|
PERCENT(Delta.PagedPool , pLogArray[0].PagedPool ),
|
||
|
PERCENT(Delta.NonPagedPool, pLogArray[0].NonPagedPool),
|
||
|
PERCENT(Delta.PageFile , pLogArray[0].PageFile ),
|
||
|
PERCENT(Delta.Commit , pLogArray[0].Commit ),
|
||
|
PERCENT(Delta.Handles , pLogArray[0].Handles ),
|
||
|
PERCENT(Delta.Threads , pLogArray[0].Threads ));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PRINT_TRAILER();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* AnalyzePoolLog
|
||
|
*
|
||
|
* Args: pointer to sorted poolsnap log file
|
||
|
*
|
||
|
* Returns: nothing
|
||
|
*
|
||
|
* This function reads a sorted poolsnap logfile. For each pool tag in the file,
|
||
|
* it records each column for every period and then analyzes the memory trends
|
||
|
* for leaks.
|
||
|
*
|
||
|
* If any column increases for each period, that is flagged as a definite leak.
|
||
|
* If any column increases significatnly more often than decrease, it is a
|
||
|
* flagged as a probable leak.
|
||
|
*
|
||
|
*/
|
||
|
void AnalyzePoolLog(FILE *pFile)
|
||
|
{
|
||
|
int iPeriod; // index for which period being read
|
||
|
PoolLogRec Delta, // Record to track increase from first to last entry
|
||
|
TrendInfo, // Record to track period increases
|
||
|
*pLogArray;// Array of records for each pool tag
|
||
|
char buffer[BUF_LEN]; // Buffer for reading each line from pFile
|
||
|
|
||
|
//
|
||
|
// Allocate enough space for the largest set
|
||
|
//
|
||
|
pLogArray=malloc(g_iMaxPeriods*sizeof(PoolLogRec));
|
||
|
if (NULL==pLogArray) {
|
||
|
fprintf(stderr,"Out of memory, aborting file.\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PRINT_HEADER();
|
||
|
|
||
|
//
|
||
|
// Read the entire file
|
||
|
//
|
||
|
while( !feof(pFile) ) {
|
||
|
|
||
|
//
|
||
|
// Reset trend and period info for each new pool tag
|
||
|
//
|
||
|
memset(&TrendInfo, 0, sizeof(TrendInfo));
|
||
|
iPeriod=0;
|
||
|
|
||
|
//
|
||
|
// Loop until you've read all the entries for this process or tag.
|
||
|
//
|
||
|
// Note: Empty line includes LF character that fgets doesn't eat.
|
||
|
//
|
||
|
while( TRUE ) {
|
||
|
|
||
|
if( iPeriod >= g_iMaxPeriods ) break; // done
|
||
|
|
||
|
if ((NULL==fgets(buffer, BUF_LEN, pFile)) ||
|
||
|
(strlen(buffer)<2) ||
|
||
|
(*buffer == TAGCHAR ) ||
|
||
|
(0==sscanf(buffer,
|
||
|
" %4c %s %ld %ld %ld %ld %ld",
|
||
|
pLogArray[iPeriod].Name,
|
||
|
pLogArray[iPeriod].Type,
|
||
|
&pLogArray[iPeriod].Allocs,
|
||
|
&pLogArray[iPeriod].Frees,
|
||
|
&pLogArray[iPeriod].Diff,
|
||
|
&pLogArray[iPeriod].Bytes,
|
||
|
&pLogArray[iPeriod].PerAlloc))) {
|
||
|
break;
|
||
|
}
|
||
|
pLogArray[iPeriod].Name[4]='\0'; // Terminate the tag
|
||
|
|
||
|
//
|
||
|
// Calculate TrendInfo:
|
||
|
//
|
||
|
// TrendInfo is a running tally of the periods a value went up vs.
|
||
|
// the periods it went down. See macro in analog.h
|
||
|
//
|
||
|
// if (curval>oldval) {
|
||
|
// trend++;
|
||
|
// } else if (curval<oldval) {
|
||
|
// trend--;
|
||
|
// } else {
|
||
|
// trend=trend; // stay same
|
||
|
// }
|
||
|
//
|
||
|
if (iPeriod>0) {
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Allocs);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Frees);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Diff);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, Bytes);
|
||
|
GREATER_LESS_OR_EQUAL(TrendInfo, pLogArray, iPeriod, PerAlloc);
|
||
|
}
|
||
|
iPeriod++;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// skip rest of loop if a blank line or useless line
|
||
|
//
|
||
|
|
||
|
if( iPeriod == 0 ) continue;
|
||
|
|
||
|
|
||
|
strcpy(TrendInfo.Name,pLogArray[0].Name);
|
||
|
|
||
|
//
|
||
|
// GET_DELTA simply records the difference (end-begin) for each field
|
||
|
//
|
||
|
// Macro in analog.h
|
||
|
//
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, Allocs);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, Frees);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, Diff);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, Bytes);
|
||
|
GET_DELTA(Delta, pLogArray, iPeriod, PerAlloc);
|
||
|
|
||
|
//
|
||
|
// PRINT_IF_TREND reports probable or definite leaks for any field.
|
||
|
//
|
||
|
// Definite leak is where the value goes up every period
|
||
|
// Probable leak is where the value goes up most of the time
|
||
|
//
|
||
|
// Macro in analog.h
|
||
|
//
|
||
|
// if (trend==numperiods-1) {
|
||
|
// definite_leak;
|
||
|
// } else if (trend>=numperiods/2) {
|
||
|
// probable_leak;
|
||
|
// }
|
||
|
//
|
||
|
// Note: Allocs, Frees and PerAlloc don't make sense to report trends.
|
||
|
//
|
||
|
// PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Allocs);
|
||
|
// PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Frees);
|
||
|
// PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, PerAlloc);
|
||
|
// PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Diff);
|
||
|
PRINT_IF_TREND(pLogArray, TrendInfo, Delta, iPeriod, Bytes);
|
||
|
}
|
||
|
|
||
|
|
||
|
PRINT_TRAILER();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* AnalyzeFile
|
||
|
*
|
||
|
* Args: pFileName - filename to analyze
|
||
|
*
|
||
|
* Returns: nothing
|
||
|
*
|
||
|
* This function opens the specified file, determines the file type and calls
|
||
|
* the appropriate analyze function.
|
||
|
*
|
||
|
*/
|
||
|
void AnalyzeFile(char *pFileName)
|
||
|
{
|
||
|
FILE *pFile; // using fopen for fgets functionality
|
||
|
LogType WhichType=UNKNOWN_LOG_TYPE; // which type of log (pool/mem)
|
||
|
|
||
|
pFile=fopen(pFileName, "r");
|
||
|
if (NULL==pFile) {
|
||
|
fprintf(stderr,"Unable to open %s, Error=%d\n", pFileName, GetLastError());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
WhichType=DetermineFileType(pFile);
|
||
|
|
||
|
switch (WhichType)
|
||
|
{
|
||
|
case MEM_LOG:
|
||
|
AnalyzeMemLog(pFile);
|
||
|
break;
|
||
|
case POOL_LOG:
|
||
|
AnalyzePoolLog(pFile);
|
||
|
break;
|
||
|
default:
|
||
|
;
|
||
|
}
|
||
|
|
||
|
fclose(pFile);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* main
|
||
|
*
|
||
|
* Args: argc - count of command line args
|
||
|
* argv - array of command line args
|
||
|
*
|
||
|
* Returns: 0 if called correctly, 1 if not.
|
||
|
*
|
||
|
* This is the entry point for analog. It simply parses the command line args
|
||
|
* and then calls AnalyzeFile on each file.
|
||
|
*
|
||
|
*/
|
||
|
int _cdecl main(int argc, char *argv[])
|
||
|
{
|
||
|
int ArgIndex;
|
||
|
if (argc<2) {
|
||
|
Usage();
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
for( ArgIndex=1; ArgIndex<argc; ArgIndex++) {
|
||
|
if( (*argv[ArgIndex] == '/') || (*argv[ArgIndex]=='-') ) {
|
||
|
CHAR chr;
|
||
|
|
||
|
chr= argv[ArgIndex][1];
|
||
|
switch( chr ) {
|
||
|
case 'v': case 'V': // verbose
|
||
|
g_fVerbose= TRUE;
|
||
|
break;
|
||
|
case 'h': case 'H': // output HTML
|
||
|
bHtmlStyle= TRUE;
|
||
|
break;
|
||
|
case 't': case 'T': // show all the extra info
|
||
|
g_fShowExtraInfo=TRUE;
|
||
|
break;
|
||
|
case 'd': case 'D': // print definite only
|
||
|
g_ReportLevel= 0;
|
||
|
break;
|
||
|
default:
|
||
|
Usage();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
AnalyzeFile(argv[ArgIndex]);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|