/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is tmstats.c code, released * Oct 25, 2002. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Garrett Arch Blythe, 25-October-2002 * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #include #include #include "nspr.h" #include "tmreader.h" #define ERROR_REPORT(num, val, msg) fprintf(stderr, "error(%d):\t\"%s\"\t%s\n", (num), (val), (msg)); #define CLEANUP(ptr) do { if(NULL != ptr) { free(ptr); ptr = NULL; } } while(0) #define COST_RESOLUTION 1000 #define COST_PRINTABLE(cost) ((PRFloat64)(cost) / (PRFloat64)COST_RESOLUTION) typedef struct __struct_Options /* ** Options to control how we perform. ** ** mProgramName Used in help text. ** mInputName Name of the file. ** mOutput Output file, append. ** Default is stdout. ** mOutputName Name of the file. ** mHelp Wether or not help should be shown. ** mOverhead How much overhead an allocation will have. ** mAlignment What boundry will the end of an allocation line up on. ** mAverages Whether or not to display averages. ** mDeviances Whether or not to display standard deviations. ** mRunLength Whether or not to display run length. */ { const char* mProgramName; char* mInputName; FILE* mOutput; char* mOutputName; int mHelp; unsigned mOverhead; unsigned mAlignment; int mAverages; int mDeviances; int mRunLength; } Options; typedef struct __struct_Switch /* ** Command line options. */ { const char* mLongName; const char* mShortName; int mHasValue; const char* mValue; const char* mDescription; } Switch; #define DESC_NEWLINE "\n\t\t" static Switch gInputSwitch = {"--input", "-i", 1, NULL, "Specify input file." DESC_NEWLINE "stdin is default."}; static Switch gOutputSwitch = {"--output", "-o", 1, NULL, "Specify output file." DESC_NEWLINE "Appends if file exists." DESC_NEWLINE "stdout is default."}; static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."}; static Switch gAlignmentSwitch = {"--alignment", "-al", 1, NULL, "All allocation sizes are made to be a multiple of this number." DESC_NEWLINE "Closer to actual heap conditions; set to 1 for true sizes." DESC_NEWLINE "Default value is 16."}; static Switch gOverheadSwitch = {"--overhead", "-ov", 1, NULL, "After alignment, all allocations are made to increase by this number." DESC_NEWLINE "Closer to actual heap conditions; set to 0 for true sizes." DESC_NEWLINE "Default value is 8."}; static Switch gAveragesSwitch = {"--averages", "-avg", 0, NULL, "Display averages."}; static Switch gDeviationsSwitch = {"--deviations", "-dev", 0, NULL, "Display standard deviations from the average." DESC_NEWLINE "Implies --averages."}; static Switch gRunLengthSwitch = {"--run-length", "-rl", 0, NULL, "Display the run length in seconds."}; static Switch* gSwitches[] = { &gInputSwitch, &gOutputSwitch, &gAlignmentSwitch, &gOverheadSwitch, &gAveragesSwitch, &gDeviationsSwitch, &gRunLengthSwitch, &gHelpSwitch }; typedef struct _struct_VarianceState /* ** State for a single pass variance calculation. */ { unsigned mCount; PRUint64 mSum; PRUint64 mSquaredSum; } VarianceState; typedef struct __struct_TMStats /* ** Stats we are trying to calculate. ** ** mOptions Obilgatory options pointer. ** uMemoryInUse Current tally of memory in use. ** uPeakMemory Heap topped out at this byte level. ** uObjectsInUse Different allocations outstanding. ** uPeakObjects Highest object count. ** uMallocs Number of malloc calls. ** uCallocs Number of calloc calls. ** uReallocs Number of realloc calls. ** uFrees Number of free calls. ** uMallocSize Bytes from malloc. ** uCallocSize Bytes from calloc. ** uReallocSize Bytes from realloc. ** uFreeSize Bytes from free. ** mMallocSizeVar Variance of bytes. ** mCallocSizeVar Variance of bytes. ** mReallocSizeVar Variance of bytes. ** mFreeSizeVar Variance of bytes. ** uMallocCost Time of mallocs. ** uCallocCost Time of callocs. ** uReallocCost Time of reallocs. ** uFreeCost Time of frees. ** mMallocCostVar Variance of cost. ** mCallocCostVar Variance of cost. ** mReallocCostVar Variance of cost. ** mFreeCostVar Variance of cost. ** uMinTicks Start of run. ** uMaxTicks End of run. */ { Options* mOptions; unsigned uMemoryInUse; unsigned uPeakMemory; unsigned uObjectsInUse; unsigned uPeakObjects; unsigned uMallocs; unsigned uCallocs; unsigned uReallocs; unsigned uFrees; unsigned uMallocSize; unsigned uCallocSize; unsigned uReallocSize; unsigned uFreeSize; VarianceState mMallocSizeVar; VarianceState mCallocSizeVar; VarianceState mReallocSizeVar; VarianceState mFreeSizeVar; unsigned uMallocCost; unsigned uCallocCost; unsigned uReallocCost; unsigned uFreeCost; VarianceState mMallocCostVar; VarianceState mCallocCostVar; VarianceState mReallocCostVar; VarianceState mFreeCostVar; unsigned uMinTicks; unsigned uMaxTicks; } TMStats; int initOptions(Options* outOptions, int inArgc, char** inArgv) /* ** returns int 0 if successful. */ { int retval = 0; int loop = 0; int switchLoop = 0; int match = 0; const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]); Switch* current = NULL; /* ** Set any defaults. */ memset(outOptions, 0, sizeof(Options)); outOptions->mProgramName = inArgv[0]; outOptions->mInputName = strdup("-"); outOptions->mOutput = stdout; outOptions->mOutputName = strdup("stdout"); outOptions->mAlignment = 16; outOptions->mOverhead = 8; if(NULL == outOptions->mOutputName || NULL == outOptions->mInputName) { retval = __LINE__; ERROR_REPORT(retval, "stdin/stdout", "Unable to strdup."); } /* ** Go through and attempt to do the right thing. */ for(loop = 1; loop < inArgc && 0 == retval; loop++) { match = 0; current = NULL; for(switchLoop = 0; switchLoop < switchCount && 0 == retval; switchLoop++) { if(0 == strcmp(gSwitches[switchLoop]->mLongName, inArgv[loop])) { match = __LINE__; } else if(0 == strcmp(gSwitches[switchLoop]->mShortName, inArgv[loop])) { match = __LINE__; } if(match) { if(gSwitches[switchLoop]->mHasValue) { /* ** Attempt to absorb next option to fullfill value. */ if(loop + 1 < inArgc) { loop++; current = gSwitches[switchLoop]; current->mValue = inArgv[loop]; } } else { current = gSwitches[switchLoop]; } break; } } if(0 == match) { outOptions->mHelp = __LINE__; retval = __LINE__; ERROR_REPORT(retval, inArgv[loop], "Unknown command line switch."); } else if(NULL == current) { outOptions->mHelp = __LINE__; retval = __LINE__; ERROR_REPORT(retval, inArgv[loop], "Command line switch requires a value."); } else { /* ** Do something based on address/swtich. */ if(current == &gInputSwitch) { CLEANUP(outOptions->mInputName); outOptions->mInputName = strdup(current->mValue); if(NULL == outOptions->mInputName) { retval = __LINE__; ERROR_REPORT(retval, current->mValue, "Unable to strdup."); } } else if(current == &gOutputSwitch) { CLEANUP(outOptions->mOutputName); if(NULL != outOptions->mOutput && stdout != outOptions->mOutput) { fclose(outOptions->mOutput); outOptions->mOutput = NULL; } outOptions->mOutput = fopen(current->mValue, "a"); if(NULL == outOptions->mOutput) { retval = __LINE__; ERROR_REPORT(retval, current->mValue, "Unable to open output file."); } else { outOptions->mOutputName = strdup(current->mValue); if(NULL == outOptions->mOutputName) { retval = __LINE__; ERROR_REPORT(retval, current->mValue, "Unable to strdup."); } } } else if(current == &gHelpSwitch) { outOptions->mHelp = __LINE__; } else if(current == &gAlignmentSwitch) { unsigned arg = 0; char* endScan = NULL; errno = 0; arg = strtoul(current->mValue, &endScan, 0); if(0 == errno && endScan != current->mValue) { outOptions->mAlignment = arg; } else { retval = __LINE__; ERROR_REPORT(retval, current->mValue, "Unable to convert to a number."); } } else if(current == &gOverheadSwitch) { unsigned arg = 0; char* endScan = NULL; errno = 0; arg = strtoul(current->mValue, &endScan, 0); if(0 == errno && endScan != current->mValue) { outOptions->mOverhead = arg; } else { retval = __LINE__; ERROR_REPORT(retval, current->mValue, "Unable to convert to a number."); } } else if(current == &gAveragesSwitch) { outOptions->mAverages = __LINE__; } else if(current == &gDeviationsSwitch) { outOptions->mAverages = __LINE__; outOptions->mDeviances = __LINE__; } else if(current == &gRunLengthSwitch) { outOptions->mRunLength = __LINE__; } else { retval = __LINE__; ERROR_REPORT(retval, current->mLongName, "No handler for command line switch."); } } } return retval; } void cleanOptions(Options* inOptions) /* ** Clean up any open handles. */ { unsigned loop = 0; CLEANUP(inOptions->mInputName); CLEANUP(inOptions->mOutputName); if(NULL != inOptions->mOutput && stdout != inOptions->mOutput) { fclose(inOptions->mOutput); } memset(inOptions, 0, sizeof(Options)); } void showHelp(Options* inOptions) /* ** Show some simple help text on usage. */ { int loop = 0; const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]); const char* valueText = NULL; printf("usage:\t%s [arguments]\n", inOptions->mProgramName); printf("\n"); printf("arguments:\n"); for(loop = 0; loop < switchCount; loop++) { if(gSwitches[loop]->mHasValue) { valueText = " "; } else { valueText = ""; } printf("\t%s%s\n", gSwitches[loop]->mLongName, valueText); printf("\t %s%s", gSwitches[loop]->mShortName, valueText); printf(DESC_NEWLINE "%s\n\n", gSwitches[loop]->mDescription); } printf("This tool reports simple heap usage and allocation call counts.\n"); printf("Useful for eyeballing trace-malloc numbers quickly.\n"); } void addVariance(VarianceState* inVariance, unsigned inValue) /* ** Add a value to a variance state. */ { PRUint64 squared; PRUint64 bigValue; LL_UI2L(bigValue, inValue); LL_ADD(inVariance->mSum, inVariance->mSum, bigValue); LL_MUL(squared, bigValue, bigValue); LL_ADD(inVariance->mSquaredSum, inVariance->mSquaredSum, squared); inVariance->mCount++; } PRFloat64 getAverage(VarianceState* inVariance) /* ** Determine the mean/average based on the given state. */ { PRFloat64 retval = 0.0; if(NULL != inVariance && 0 < inVariance->mCount) { PRFloat64 count; PRFloat64 sum; PRInt64 isum; /* ** Avoids a compiler error (not impl) under MSVC. */ isum = inVariance->mSum; count = (PRFloat64)inVariance->mCount; LL_L2F(sum, isum); retval = sum / count; } return retval; } PRFloat64 getVariance(VarianceState* inVariance) /* ** Determine the variance based on the given state. */ { PRFloat64 retval = 0.0; if(NULL != inVariance && 1 < inVariance->mCount) { PRFloat64 count; PRFloat64 squaredSum; PRFloat64 avg; PRFloat64 squaredAvg; PRInt64 isquaredSum; /* ** Avoids a compiler error (not impl) under MSVC. */ isquaredSum = inVariance->mSquaredSum; count = (PRFloat64)inVariance->mCount; LL_L2F(squaredSum, isquaredSum); avg = getAverage(inVariance); squaredAvg = avg * avg; retval = (squaredSum - (count * squaredAvg)) / (count - 1.0); } return retval; } PRFloat64 getStdDev(VarianceState* inVariance) /* ** Determine the standard deviation based on the given state. */ { PRFloat64 retval = 0.0; PRFloat64 variance; variance = getVariance(inVariance); retval = sqrt(variance); return retval; } unsigned actualByteSize(Options* inOptions, unsigned retval) /* ** Apply alignment and overhead to size to figure out actual byte size. ** This by default mimics spacetrace with default options (msvc crt heap). */ { if(0 != retval) { unsigned eval = 0; unsigned over = 0; eval = retval - 1; if(0 != inOptions->mAlignment) { over = eval % inOptions->mAlignment; } retval = eval + inOptions->mOverhead + inOptions->mAlignment - over; } return retval; } PRUint32 ticks2xsec(tmreader* aReader, PRUint32 aTicks, PRUint32 aResolution) /* ** Convert platform specific ticks to second units ** Returns 0 on success. */ { PRUint32 retval = 0; PRUint64 bigone; PRUint64 tmp64; LL_UI2L(bigone, aResolution); LL_UI2L(tmp64, aTicks); LL_MUL(bigone, bigone, tmp64); LL_UI2L(tmp64, aReader->ticksPerSec); LL_DIV(bigone, bigone, tmp64); LL_L2UI(retval, bigone); return retval; } #define ticks2msec(reader, ticks) ticks2xsec((reader), (ticks), 1000) void tmEventHandler(tmreader* inReader, tmevent* inEvent) /* ** Callback from the tmreader_eventloop. ** Keep it simple in here, this is where we'll spend the most time. ** The goal is to be fast. */ { TMStats* stats = (TMStats*)inReader->data; Options* options = (Options*)stats->mOptions; char type = inEvent->type; unsigned size = inEvent->u.alloc.size; unsigned actualSize = 0; unsigned actualOldSize = 0; PRUint32 interval = 0; /* ** To match spacetrace stats, reallocs of size zero are frees. ** Adjust the size to match what free expects. */ if(TM_EVENT_REALLOC == type && 0 == size) { type = TM_EVENT_FREE; if(0 != inEvent->u.alloc.oldserial) { size = inEvent->u.alloc.oldsize; } } /* ** Adjust the size due to the options. */ actualSize = actualByteSize(options, size); if(TM_EVENT_REALLOC == type && 0 != inEvent->u.alloc.oldserial) { actualOldSize = actualByteSize(options, inEvent->u.alloc.oldsize); } /* ** Modify event specific data. */ switch(type) { case TM_EVENT_MALLOC: stats->uMallocs++; stats->uMallocSize += actualSize; stats->uMallocCost += ticks2msec(inReader, inEvent->u.alloc.cost); stats->uMemoryInUse += actualSize; stats->uObjectsInUse++; addVariance(&stats->mMallocSizeVar, actualSize); addVariance(&stats->mMallocCostVar, inEvent->u.alloc.cost); break; case TM_EVENT_CALLOC: stats->uCallocs++; stats->uCallocSize += actualSize; stats->uCallocCost += ticks2msec(inReader, inEvent->u.alloc.cost); stats->uMemoryInUse += actualSize; stats->uObjectsInUse++; addVariance(&stats->mCallocSizeVar, actualSize); addVariance(&stats->mCallocCostVar, inEvent->u.alloc.cost); break; case TM_EVENT_REALLOC: stats->uReallocs++; stats->uReallocSize -= actualOldSize; stats->uReallocSize += actualSize; stats->uReallocCost += ticks2msec(inReader, inEvent->u.alloc.cost); stats->uMemoryInUse -= actualOldSize; stats->uMemoryInUse += actualSize; if(0 == inEvent->u.alloc.oldserial) { stats->uObjectsInUse++; } if(actualSize > actualOldSize) { addVariance(&stats->mReallocSizeVar, actualSize - actualOldSize); } else { addVariance(&stats->mReallocSizeVar, actualOldSize - actualSize); } addVariance(&stats->mReallocCostVar, inEvent->u.alloc.cost); break; case TM_EVENT_FREE: stats->uFrees++; stats->uFreeSize += actualSize; stats->uFreeCost += ticks2msec(inReader, inEvent->u.alloc.cost); stats->uMemoryInUse -= actualSize; stats->uObjectsInUse--; addVariance(&stats->mFreeSizeVar, actualSize); addVariance(&stats->mFreeCostVar, inEvent->u.alloc.cost); break; default: /* ** Don't care. */ break; } switch(type) { case TM_EVENT_MALLOC: case TM_EVENT_CALLOC: case TM_EVENT_REALLOC: /* ** Check the peaks. */ if(stats->uMemoryInUse > stats->uPeakMemory) { stats->uPeakMemory = stats->uMemoryInUse; } if(stats->uObjectsInUse > stats->uPeakObjects) { stats->uPeakObjects = stats->uObjectsInUse; } /* ** Falling through. */ case TM_EVENT_FREE: /* ** Check the overall time. */ interval = ticks2msec(inReader, inEvent->u.alloc.interval); if(stats->uMinTicks > interval) { stats->uMinTicks = interval; } if(stats->uMaxTicks < interval) { stats->uMaxTicks = interval; } break; default: /* ** Don't care. */ break; } } int report_stats(Options* inOptions, TMStats* inStats) { int retval = 0; fprintf(inOptions->mOutput, "Peak Memory Usage: %11d\n", inStats->uPeakMemory); fprintf(inOptions->mOutput, "Memory Leaked: %11d\n", inStats->uMemoryInUse); fprintf(inOptions->mOutput, "\n"); fprintf(inOptions->mOutput, "Peak Object Count: %11d\n", inStats->uPeakObjects); fprintf(inOptions->mOutput, "Objects Leaked: %11d\n", inStats->uObjectsInUse); if(0 != inOptions->mAverages && 0 != inStats->uObjectsInUse) { fprintf(inOptions->mOutput, "Average Leaked Object Size: %11.4f\n", (PRFloat64)inStats->uMemoryInUse / (PRFloat64)inStats->uObjectsInUse); } fprintf(inOptions->mOutput, "\n"); fprintf(inOptions->mOutput, "Call Total: %11d\n", inStats->uMallocs + inStats->uCallocs + inStats->uReallocs + inStats->uFrees); fprintf(inOptions->mOutput, " malloc: %11d\n", inStats->uMallocs); fprintf(inOptions->mOutput, " calloc: %11d\n", inStats->uCallocs); fprintf(inOptions->mOutput, " realloc: %11d\n", inStats->uReallocs); fprintf(inOptions->mOutput, " free: %11d\n", inStats->uFrees); fprintf(inOptions->mOutput, "\n"); fprintf(inOptions->mOutput, "Byte Total (sans free): %11d\n", inStats->uMallocSize + inStats->uCallocSize + inStats->uReallocSize); fprintf(inOptions->mOutput, " malloc: %11d\n", inStats->uMallocSize); fprintf(inOptions->mOutput, " calloc: %11d\n", inStats->uCallocSize); fprintf(inOptions->mOutput, " realloc: %11d\n", inStats->uReallocSize); fprintf(inOptions->mOutput, " free: %11d\n", inStats->uFreeSize); if(0 != inOptions->mAverages) { fprintf(inOptions->mOutput, "Byte Averages:\n"); fprintf(inOptions->mOutput, " malloc: %11.4f\n", getAverage(&inStats->mMallocSizeVar)); fprintf(inOptions->mOutput, " calloc: %11.4f\n", getAverage(&inStats->mCallocSizeVar)); fprintf(inOptions->mOutput, " realloc: %11.4f\n", getAverage(&inStats->mReallocSizeVar)); fprintf(inOptions->mOutput, " free: %11.4f\n", getAverage(&inStats->mFreeSizeVar)); } if(0 != inOptions->mDeviances) { fprintf(inOptions->mOutput, "Byte Standard Deviations:\n"); fprintf(inOptions->mOutput, " malloc: %11.4f\n", getStdDev(&inStats->mMallocSizeVar)); fprintf(inOptions->mOutput, " calloc: %11.4f\n", getStdDev(&inStats->mCallocSizeVar)); fprintf(inOptions->mOutput, " realloc: %11.4f\n", getStdDev(&inStats->mReallocSizeVar)); fprintf(inOptions->mOutput, " free: %11.4f\n", getStdDev(&inStats->mFreeSizeVar)); } fprintf(inOptions->mOutput, "\n"); fprintf(inOptions->mOutput, "Overhead Total: %11.4f\n", COST_PRINTABLE(inStats->uMallocCost) + COST_PRINTABLE(inStats->uCallocCost) + COST_PRINTABLE(inStats->uReallocCost) + COST_PRINTABLE(inStats->uFreeCost)); fprintf(inOptions->mOutput, " malloc: %11.4f\n", COST_PRINTABLE(inStats->uMallocCost)); fprintf(inOptions->mOutput, " calloc: %11.4f\n", COST_PRINTABLE(inStats->uCallocCost)); fprintf(inOptions->mOutput, " realloc: %11.4f\n", COST_PRINTABLE(inStats->uReallocCost)); fprintf(inOptions->mOutput, " free: %11.4f\n", COST_PRINTABLE(inStats->uFreeCost)); if(0 != inOptions->mAverages) { fprintf(inOptions->mOutput, "Overhead Averages:\n"); fprintf(inOptions->mOutput, " malloc: %11.4f\n", COST_PRINTABLE(getAverage(&inStats->mMallocCostVar))); fprintf(inOptions->mOutput, " calloc: %11.4f\n", COST_PRINTABLE(getAverage(&inStats->mCallocCostVar))); fprintf(inOptions->mOutput, " realloc: %11.4f\n", COST_PRINTABLE(getAverage(&inStats->mReallocCostVar))); fprintf(inOptions->mOutput, " free: %11.4f\n", COST_PRINTABLE(getAverage(&inStats->mFreeCostVar))); } if(0 != inOptions->mDeviances) { fprintf(inOptions->mOutput, "Overhead Standard Deviations:\n"); fprintf(inOptions->mOutput, " malloc: %11.4f\n", COST_PRINTABLE(getStdDev(&inStats->mMallocCostVar))); fprintf(inOptions->mOutput, " calloc: %11.4f\n", COST_PRINTABLE(getStdDev(&inStats->mCallocCostVar))); fprintf(inOptions->mOutput, " realloc: %11.4f\n", COST_PRINTABLE(getStdDev(&inStats->mReallocCostVar))); fprintf(inOptions->mOutput, " free: %11.4f\n", COST_PRINTABLE(getStdDev(&inStats->mFreeCostVar))); } fprintf(inOptions->mOutput, "\n"); if(0 != inOptions->mRunLength) { unsigned length = inStats->uMaxTicks - inStats->uMinTicks; fprintf(inOptions->mOutput, "Run Length: %11.4f\n", COST_PRINTABLE(length)); fprintf(inOptions->mOutput, "\n"); } return retval; } int tmstats(Options* inOptions) /* ** As quick as possible, load the input file and report stats. */ { int retval = 0; tmreader* tmr = NULL; TMStats stats; memset(&stats, 0, sizeof(stats)); stats.mOptions = inOptions; stats.uMinTicks = 0xFFFFFFFFU; /* ** Need a tmreader. */ tmr = tmreader_new(inOptions->mProgramName, &stats); if(NULL != tmr) { int tmResult = 0; tmResult = tmreader_eventloop(tmr, inOptions->mInputName, tmEventHandler); if(0 == tmResult) { retval = __LINE__; ERROR_REPORT(retval, inOptions->mInputName, "Problem reading trace-malloc data."); } tmreader_destroy(tmr); tmr = NULL; if(0 == retval) { retval = report_stats(inOptions, &stats); } } else { retval = __LINE__; ERROR_REPORT(retval, inOptions->mProgramName, "Unable to obtain tmreader."); } return retval; } int main(int inArgc, char** inArgv) { int retval = 0; Options options; retval = initOptions(&options, inArgc, inArgv); if(options.mHelp) { showHelp(&options); } else if(0 == retval) { retval = tmstats(&options); } cleanOptions(&options); return retval; }