tools/trace-malloc/tmstats.c
author Benoit Jacob <bjacob@mozilla.com>
Sun, 04 Dec 2011 14:15:43 -0500
changeset 81418 432d88a73914212f73ebf3fef49925e00aea1b78
parent 1 9b2a99adc05e53cd4010de512f50118594756650
child 94475 f4157e8c410708d76703f19e4dfb61859bfe32d8
permissions -rw-r--r--
Bug 707033 - Kill the hashtables of WebGLUniformLocation's held by WebGLProgram's - r=jgilbert Currently, WebGLProgram holds a hashtable of refptrs to all WebGLUniformLocation's created from it. It's used for one thing: to ensure that multiple getUniformLocation() calls on same uniform return the same WebGLUniformLocation object. However, in a discussion on the public_webgl mailing list this week, we agreed that this was not mandated by the spec and that on the contrary, to ensure consistent behavior across browsers, we're going to require that NOT to happen. So this becomes a conformance issue. Removing this hashtable also simplifies code and ownership patterns.

/* -*- 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>

#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           Whether 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 = " <value>";
        }
        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;
}