diff -r 000000000000 -r 6474c204b198 tools/trace-malloc/spacetrace.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/trace-malloc/spacetrace.c Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,6352 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* +** spacetrace.c +** +** SpaceTrace is meant to take the output of trace-malloc and present +** a picture of allocations over the run of the application. +*/ + +/* +** Required include files. +*/ +#include "spacetrace.h" + +#include +#include +#include +#include +#if defined(XP_WIN32) +#include /* _heapMin */ +#endif + +#if defined(HAVE_BOUTELL_GD) +/* +** See http://www.boutell.com/gd for the GD graphics library. +** Ports for many platorms exist. +** Your box may already have the lib (mine did, redhat 7.1 workstation). +*/ +#include +#include +#include +#include +#endif /* HAVE_BOUTELL_GD */ + +#include "nsQuickSort.h" +/* +** strcasecmp API please. +*/ +#if defined(_MSC_VER) +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#endif + +/* +** the globals variables. happy joy. +*/ +STGlobals globals; + +/* +** have the heap cleanup at opportune times, if possible. +*/ +void +heapCompact(void) +{ +#if defined(XP_WIN32) + _heapmin(); +#endif +} + +#define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \ + PR_fprintf(PR_STDOUT, "--%s\nDisabled by default.\n%s\n", #option_name, option_help); +#define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \ + PR_fprintf(PR_STDOUT, "--%s=\nDefault value is \"%s\".\n%s\n", #option_name, default_value, option_help); +#define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ + PR_fprintf(PR_STDOUT, "--%s=\nUp to %u occurrences allowed.\n%s\n", #option_name, array_size, option_help); +#define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \ + PR_fprintf(PR_STDOUT, "--%s=\nUnlimited occurrences allowed.\n%s\n", #option_name, option_help); +#define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ + PR_fprintf(PR_STDOUT, "--%s=\nDefault value is %u.\n%s\n", #option_name, default_value, option_help); +#define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ + PR_fprintf(PR_STDOUT, "--%s=\nDefault value is %llu.\n%s\n", #option_name, default_value, option_help); + +/* +** showHelp +** +** Give simple command line help. +** Returns !0 if the help was showed. +*/ +int +showHelp(void) +{ + int retval = 0; + + if (PR_FALSE != globals.mCommandLineOptions.mHelp) { + PR_fprintf(PR_STDOUT, "Usage:\t%s [OPTION]... [-|filename]\n\n", + globals.mProgramName); + + +#include "stoptions.h" + + /* + ** Showed something. + */ + retval = __LINE__; + } + + return retval; +} + +/* +** ticks2xsec +** +** Convert platform specific ticks to second units +** Returns 0 on success. +*/ +uint32_t +ticks2xsec(tmreader * aReader, uint32_t aTicks, uint32_t aResolution) +{ + return (uint32_t)((aResolution * aTicks)/aReader->ticksPerSec); +} + +#define ticks2msec(reader, ticks) ticks2xsec((reader), (ticks), 1000) +#define ticks2usec(reader, ticks) ticks2xsec((reader), (ticks), 1000000) + +/* +** initOptions +** +** Determine global settings for the application. +** Returns 0 on success. +*/ +int +initOptions(int aArgCount, char **aArgArray) +{ + int retval = 0; + int traverse = 0; + + /* + ** Set the initial global default options. + */ +#define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) globals.mCommandLineOptions.m##option_name = PR_FALSE; +#define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) PR_snprintf(globals.mCommandLineOptions.m##option_name, sizeof(globals.mCommandLineOptions.m##option_name), "%s", default_value); +#define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) { int loop; for(loop = 0; loop < array_size; loop++) { globals.mCommandLineOptions.m##option_name[loop][0] = '\0'; } } +#define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) globals.mCommandLineOptions.m##option_name = NULL; globals.mCommandLineOptions.m##option_name##Count = 0; +#define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) globals.mCommandLineOptions.m##option_name = default_value * multiplier; +#define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) { uint64_t def64 = default_value; uint64_t mul64 = multiplier; globals.mCommandLineOptions.m##option_name##64 = def64 * mul64; } + +#include "stoptions.h" + + /* + ** Go through all arguments. + ** Two dashes lead off an option. + ** Any single dash leads off help, unless it is a lone dash (stdin). + ** Anything else will be attempted as a file to be processed. + */ + for (traverse = 1; traverse < aArgCount; traverse++) { + if ('-' == aArgArray[traverse][0] && '-' == aArgArray[traverse][1]) { + const char *option = &aArgArray[traverse][2]; + + /* + ** Initial if(0) needed to make "else if"s valid. + */ + if (0) { + } + +#define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \ + else if(0 == strcasecmp(option, #option_name)) \ + { \ + globals.mCommandLineOptions.m##option_name = PR_TRUE; \ + } +#define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \ + else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ + { \ + PR_snprintf(globals.mCommandLineOptions.m##option_name, sizeof(globals.mCommandLineOptions.m##option_name), "%s", option + strlen(#option_name "=")); \ + } +#define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ + else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ + { \ + int arrLoop = 0; \ + \ + for(arrLoop = 0; arrLoop < array_size; arrLoop++) \ + { \ + if('\0' == globals.mCommandLineOptions.m##option_name[arrLoop][0]) \ + { \ + break; \ + } \ + }\ + \ + if(arrLoop != array_size) \ + { \ + PR_snprintf(globals.mCommandLineOptions.m##option_name[arrLoop], sizeof(globals.mCommandLineOptions.m##option_name[arrLoop]), "%s", option + strlen(#option_name "=")); \ + } \ + else \ + { \ + REPORT_ERROR_MSG(__LINE__, option); \ + retval = __LINE__; \ + globals.mCommandLineOptions.mHelp = PR_TRUE; \ + } \ + } +#define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \ + else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ + { \ + const char** expand = NULL; \ + \ + expand = (const char**)realloc((void*)globals.mCommandLineOptions.m##option_name, sizeof(const char*) * (globals.mCommandLineOptions.m##option_name##Count + 1)); \ + if(NULL != expand) \ + { \ + globals.mCommandLineOptions.m##option_name = expand; \ + globals.mCommandLineOptions.m##option_name[globals.mCommandLineOptions.m##option_name##Count] = option + strlen(#option_name "="); \ + globals.mCommandLineOptions.m##option_name##Count++; \ + } \ + else \ + { \ + retval = __LINE__; \ + globals.mCommandLineOptions.mHelp = PR_TRUE; \ + } \ + } +#define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ + else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ + { \ + int32_t scanRes = 0; \ + \ + scanRes = PR_sscanf(option + strlen(#option_name "="), "%u", &globals.mCommandLineOptions.m##option_name); \ + if(1 != scanRes) \ + { \ + REPORT_ERROR_MSG(__LINE__, option); \ + retval = __LINE__; \ + globals.mCommandLineOptions.mHelp = PR_TRUE; \ + } \ + } +#define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ + else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ + { \ + int32_t scanRes = 0; \ + \ + scanRes = PR_sscanf(option + strlen(#option_name "="), "%llu", &globals.mCommandLineOptions.m##option_name##64); \ + if(1 != scanRes) \ + { \ + REPORT_ERROR_MSG(__LINE__, option); \ + retval = __LINE__; \ + globals.mCommandLineOptions.mHelp = PR_TRUE; \ + } \ + } + +#include "stoptions.h" + + /* + ** If no match on options, this else will get hit. + */ + else { + REPORT_ERROR_MSG(__LINE__, option); + retval = __LINE__; + globals.mCommandLineOptions.mHelp = PR_TRUE; + } + } + else if ('-' == aArgArray[traverse][0] + && '\0' != aArgArray[traverse][1]) { + /* + ** Show help, bad/legacy option. + */ + REPORT_ERROR_MSG(__LINE__, aArgArray[traverse]); + retval = __LINE__; + globals.mCommandLineOptions.mHelp = PR_TRUE; + } + else { + /* + ** Default is same as FileName option, the file to process. + */ + PR_snprintf(globals.mCommandLineOptions.mFileName, + sizeof(globals.mCommandLineOptions.mFileName), "%s", + aArgArray[traverse]); + } + } + + /* + ** initialize the categories + */ + initCategories(&globals); + + return retval; +} + +#if ST_WANT_GRAPHS +/* +** createGraph +** +** Create a GD image with the common properties of a graph. +** Upon return, you normally allocate legend colors, +** draw your graph inside the region +** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGH-STGD_MARGIN, +** and then call drawGraph to format the surrounding information. +** +** You should use the normal GD image release function, gdImageDestroy +** when done with it. +** +** Image attributes: +** STGD_WIDTHxSTGD_HEIGHT +** trasparent (white) background +** incremental display +*/ +gdImagePtr +createGraph(int *aTransparencyColor) +{ + gdImagePtr retval = NULL; + + if (NULL != aTransparencyColor) { + *aTransparencyColor = -1; + + retval = gdImageCreate(STGD_WIDTH, STGD_HEIGHT); + if (NULL != retval) { + /* + ** Background color (first one). + */ + *aTransparencyColor = gdImageColorAllocate(retval, 255, 255, 255); + if (-1 != *aTransparencyColor) { + /* + ** As transparency. + */ + gdImageColorTransparent(retval, *aTransparencyColor); + } + + /* + ** And to set interlacing. + */ + gdImageInterlace(retval, 1); + } + else { + REPORT_ERROR(__LINE__, gdImageCreate); + } + } + else { + REPORT_ERROR(__LINE__, createGraph); + } + + return retval; +} +#endif /* ST_WANT_GRAPHS */ + +#if ST_WANT_GRAPHS +/* +** drawGraph +** +** This function mainly exists to simplify putitng all the pretty lace +** around a home made graph. +*/ +void +drawGraph(gdImagePtr aImage, int aColor, + const char *aGraphTitle, + const char *aXAxisTitle, + const char *aYAxisTitle, + uint32_t aXMarkCount, + uint32_t * aXMarkPercents, + const char **aXMarkTexts, + uint32_t aYMarkCount, + uint32_t * aYMarkPercents, + const char **aYMarkTexts, + uint32_t aLegendCount, + int *aLegendColors, const char **aLegendTexts) +{ + if (NULL != aImage && NULL != aGraphTitle && + NULL != aXAxisTitle && NULL != aYAxisTitle && + (0 == aXMarkCount || (NULL != aXMarkPercents && NULL != aXMarkTexts)) + && (0 == aYMarkCount + || (NULL != aYMarkPercents && NULL != aYMarkTexts)) + && (0 == aLegendCount + || (NULL != aLegendColors && NULL != aLegendTexts))) { + int margin = 1; + uint32_t traverse = 0; + uint32_t target = 0; + const int markSize = 2; + int x1 = 0; + int y1 = 0; + int x2 = 0; + int y2 = 0; + time_t theTimeT = time(NULL); + char *theTime = ctime(&theTimeT); + const char *logo = "SpaceTrace"; + gdFontPtr titleFont = gdFontMediumBold; + gdFontPtr markFont = gdFontTiny; + gdFontPtr dateFont = gdFontTiny; + gdFontPtr axisFont = gdFontSmall; + gdFontPtr legendFont = gdFontTiny; + gdFontPtr logoFont = gdFontTiny; + + /* + ** Fixup the color. + ** Black by default. + */ + if (-1 == aColor) { + aColor = gdImageColorAllocate(aImage, 0, 0, 0); + } + if (-1 == aColor) { + aColor = gdImageColorClosest(aImage, 0, 0, 0); + } + + /* + ** Output the box. + */ + x1 = STGD_MARGIN - margin; + y1 = STGD_MARGIN - margin; + x2 = STGD_WIDTH - x1; + y2 = STGD_HEIGHT - y1; + gdImageRectangle(aImage, x1, y1, x2, y2, aColor); + margin++; + + /* + ** Need to make small markings on the graph to indicate where the + ** labels line up exactly. + ** While we're at it, draw the label text. + */ + for (traverse = 0; traverse < aXMarkCount; traverse++) { + target = + ((STGD_WIDTH - + (STGD_MARGIN * 2)) * aXMarkPercents[traverse]) / 100; + + x1 = STGD_MARGIN + target; + y1 = STGD_MARGIN - margin; + x2 = x1; + y2 = y1 - markSize; + gdImageLine(aImage, x1, y1, x2, y2, aColor); + + y1 = STGD_HEIGHT - y1; + y2 = STGD_HEIGHT - y2; + gdImageLine(aImage, x1, y1, x2, y2, aColor); + + if (NULL != aXMarkTexts[traverse]) { + x1 = STGD_MARGIN + target - (markFont->h / 2); + y1 = STGD_HEIGHT - STGD_MARGIN + margin + markSize + + (strlen(aXMarkTexts[traverse]) * markFont->w); + gdImageStringUp(aImage, markFont, x1, y1, + (unsigned char *) aXMarkTexts[traverse], + aColor); + } + } + for (traverse = 0; traverse < aYMarkCount; traverse++) { + target = + ((STGD_HEIGHT - (STGD_MARGIN * 2)) * (100 - + aYMarkPercents + [traverse])) / 100; + + x1 = STGD_MARGIN - margin; + y1 = STGD_MARGIN + target; + x2 = x1 - markSize; + y2 = y1; + gdImageLine(aImage, x1, y1, x2, y2, aColor); + + x1 = STGD_WIDTH - x1; + x2 = STGD_WIDTH - x2; + gdImageLine(aImage, x1, y1, x2, y2, aColor); + + if (NULL != aYMarkTexts[traverse]) { + x1 = STGD_MARGIN - margin - markSize - + (strlen(aYMarkTexts[traverse]) * markFont->w); + y1 = STGD_MARGIN + target - (markFont->h / 2); + gdImageString(aImage, markFont, x1, y1, + (unsigned char *) aYMarkTexts[traverse], + aColor); + } + } + margin += markSize; + + /* + ** Title will be centered above the image. + */ + x1 = (STGD_WIDTH / 2) - ((strlen(aGraphTitle) * titleFont->w) / 2); + y1 = ((STGD_MARGIN - margin) / 2) - (titleFont->h / 2); + gdImageString(aImage, titleFont, x1, y1, + (unsigned char *) aGraphTitle, aColor); + + /* + ** Upper left will be the date. + */ + x1 = 0; + y1 = 0; + traverse = strlen(theTime) - 1; + if (isspace(theTime[traverse])) { + theTime[traverse] = '\0'; + } + gdImageString(aImage, dateFont, x1, y1, (unsigned char *) theTime, + aColor); + + /* + ** Lower right will be the logo. + */ + x1 = STGD_WIDTH - (strlen(logo) * logoFont->w); + y1 = STGD_HEIGHT - logoFont->h; + gdImageString(aImage, logoFont, x1, y1, (unsigned char *) logo, + aColor); + + /* + ** X and Y axis titles + */ + x1 = (STGD_WIDTH / 2) - ((strlen(aXAxisTitle) * axisFont->w) / 2); + y1 = STGD_HEIGHT - axisFont->h; + gdImageString(aImage, axisFont, x1, y1, (unsigned char *) aXAxisTitle, + aColor); + x1 = 0; + y1 = (STGD_HEIGHT / 2) + ((strlen(aYAxisTitle) * axisFont->w) / 2); + gdImageStringUp(aImage, axisFont, x1, y1, + (unsigned char *) aYAxisTitle, aColor); + + /* + ** The legend. + ** Centered on the right hand side, going up. + */ + x1 = STGD_WIDTH - STGD_MARGIN + margin + + (aLegendCount * legendFont->h) / 2; + x2 = STGD_WIDTH - (aLegendCount * legendFont->h); + if (x1 > x2) { + x1 = x2; + } + + y1 = 0; + for (traverse = 0; traverse < aLegendCount; traverse++) { + y2 = (STGD_HEIGHT / 2) + + ((strlen(aLegendTexts[traverse]) * legendFont->w) / 2); + if (y2 > y1) { + y1 = y2; + } + } + for (traverse = 0; traverse < aLegendCount; traverse++) { + gdImageStringUp(aImage, legendFont, x1, y1, + (unsigned char *) aLegendTexts[traverse], + aLegendColors[traverse]); + x1 += legendFont->h; + } + } +} + +#endif /* ST_WANT_GRAPHS */ + +#if defined(HAVE_BOUTELL_GD) +/* +** pngSink +** +** GD callback, used to write out the png. +*/ +int +pngSink(void *aContext, const char *aBuffer, int aLen) +{ + return PR_Write((PRFileDesc *) aContext, aBuffer, aLen); +} +#endif /* HAVE_BOUTELL_GD */ + +/* +** FormatNumber +** +** Formats a number with thousands separator. Don't free the result. Returns +** static data. +*/ +char * +FormatNumber(int32_t num) +{ + static char buf[64]; + char tmpbuf[64]; + int len = 0; + int bufindex = sizeof(buf) - 1; + int mod3; + + PR_snprintf(tmpbuf, sizeof(tmpbuf), "%d", num); + + /* now insert the thousands separator */ + mod3 = 0; + len = strlen(tmpbuf); + while (len >= 0) { + if (tmpbuf[len] >= '0' && tmpbuf[len] <= '9') { + if (mod3 == 3) { + buf[bufindex--] = ','; + mod3 = 0; + } + mod3++; + } + buf[bufindex--] = tmpbuf[len--]; + } + return buf + bufindex + 1; +} + +/* +** actualByteSize +** +** Apply alignment and overhead to size to figure out actual byte size +*/ +uint32_t +actualByteSize(STOptions * inOptions, uint32_t retval) +{ + /* + ** Need to bump the result by our alignment and overhead. + ** The idea here is that an allocation actually costs you more than you + ** thought. + ** + ** The msvcrt malloc has an alignment of 16 with an overhead of 8. + ** The win32 HeapAlloc has an alignment of 8 with an overhead of 8. + */ + if (0 != retval) { + uint32_t eval = 0; + uint32_t over = 0; + + eval = retval - 1; + if (0 != inOptions->mAlignBy) { + over = eval % inOptions->mAlignBy; + } + retval = eval + inOptions->mOverhead + inOptions->mAlignBy - over; + } + + return retval; +} + +/* +** byteSize +** +** Figuring the byte size of an allocation. +** Might expand in the future to report size at a given time. +** For now, just use last relevant event. +*/ +uint32_t +byteSize(STOptions * inOptions, STAllocation * aAlloc) +{ + uint32_t retval = 0; + + if (NULL != aAlloc && 0 != aAlloc->mEventCount) { + uint32_t index = aAlloc->mEventCount; + + /* + ** Generally, the size is the last event's size. + */ + do { + index--; + retval = aAlloc->mEvents[index].mHeapSize; + } + while (0 == retval && 0 != index); + } + return actualByteSize(inOptions, retval); +} + + +/* +** recalculateAllocationCost +** +** Given an allocation, does a recalculation of Cost - weight, heapcount etc. +** and does the right thing to propagate the cost upwards. +*/ +int +recalculateAllocationCost(STOptions * inOptions, STContext * inContext, + STRun * aRun, STAllocation * aAllocation, + PRBool updateParent) +{ + /* + ** Now, see if they desire a callsite update. + ** As mentioned previously, we decide if the run desires us to + ** manipulate the callsite data only if its stamp is set. + ** We change all callsites and parent callsites to have that + ** stamp as well, so as to mark them as being relevant to + ** the current run in question. + */ + if (NULL != inContext && 0 != aRun->mStats[inContext->mIndex].mStamp) { + uint32_t timeval = + aAllocation->mMaxTimeval - aAllocation->mMinTimeval; + uint32_t size = byteSize(inOptions, aAllocation); + uint32_t heapCost = aAllocation->mHeapRuntimeCost; + uint64_t timeval64 = timeval; + uint64_t size64 = size; + uint64_t weight64 = timeval64 * size64; + + /* + ** First, update this run. + */ + aRun->mStats[inContext->mIndex].mCompositeCount++; + aRun->mStats[inContext->mIndex].mHeapRuntimeCost += heapCost; + aRun->mStats[inContext->mIndex].mSize += size; + aRun->mStats[inContext->mIndex].mTimeval64 += timeval64; + aRun->mStats[inContext->mIndex].mWeight64 += weight64; + + /* + ** Use the first event of the allocation to update the parent + ** callsites. + ** This has positive effect of not updating realloc callsites + ** with the same data over and over again. + */ + if (updateParent && 0 < aAllocation->mEventCount) { + tmcallsite *callsite = aAllocation->mEvents[0].mCallsite; + STRun *callsiteRun = NULL; + + /* + ** Go up parents till we drop. + */ + while (NULL != callsite && NULL != callsite->method) { + callsiteRun = CALLSITE_RUN(callsite); + if (NULL != callsiteRun) { + /* + ** Do we init it? + */ + if (callsiteRun->mStats[inContext->mIndex].mStamp != + aRun->mStats[inContext->mIndex].mStamp) { + memset(&callsiteRun->mStats[inContext->mIndex], 0, + sizeof(STCallsiteStats)); + callsiteRun->mStats[inContext->mIndex].mStamp = + aRun->mStats[inContext->mIndex].mStamp; + } + + /* + ** Add the values. + ** Note that if the allocation was ever realloced, + ** we are actually recording the final size. + ** Also, the composite count does not include + ** calls to realloc (or free for that matter), + ** but rather is simply a count of actual heap + ** allocation objects, from which someone will + ** draw conclusions regarding number of malloc + ** and free calls. + ** It is possible to generate the exact number + ** of calls to free/malloc/realloc should the + ** absolute need arise to count them individually, + ** but I fear it will take mucho memory and this + ** is perhaps good enough for now. + */ + callsiteRun->mStats[inContext->mIndex].mCompositeCount++; + callsiteRun->mStats[inContext->mIndex].mHeapRuntimeCost += + heapCost; + callsiteRun->mStats[inContext->mIndex].mSize += size; + callsiteRun->mStats[inContext->mIndex].mTimeval64 += + timeval64; + callsiteRun->mStats[inContext->mIndex].mWeight64 += + weight64; + } + + callsite = callsite->parent; + } + } + } + + return 0; +} + + +/* +** appendAllocation +** +** Given a run, append the allocation to it. +** No DUP checks are done. +** Also, we might want to update the parent callsites with stats. +** We decide to do this heavy duty work only if the run we are appending +** to has a non ZERO mStats[].mStamp, meaning that it is asking to track +** such information when it was created. +** Returns !0 on success. +*/ +int +appendAllocation(STOptions * inOptions, STContext * inContext, + STRun * aRun, STAllocation * aAllocation) +{ + int retval = 0; + + if (NULL != aRun && NULL != aAllocation && NULL != inOptions) { + STAllocation **expand = NULL; + + /* + ** Expand the size of the array if needed. + */ + expand = (STAllocation **) realloc(aRun->mAllocations, + sizeof(STAllocation *) * + (aRun->mAllocationCount + 1)); + if (NULL != expand) { + /* + ** Reassign in case of pointer move. + */ + aRun->mAllocations = expand; + + /* + ** Stick the allocation in. + */ + aRun->mAllocations[aRun->mAllocationCount] = aAllocation; + + /* + ** If this is the global run, we need to let the allocation + ** track the index back to us. + */ + if (&globals.mRun == aRun) { + aAllocation->mRunIndex = aRun->mAllocationCount; + } + + /* + ** Increase the count. + */ + aRun->mAllocationCount++; + + /* + ** We're good. + */ + retval = __LINE__; + + /* + ** update allocation cost + */ + recalculateAllocationCost(inOptions, inContext, aRun, aAllocation, + PR_TRUE); + } + else { + REPORT_ERROR(__LINE__, appendAllocation); + } + } + else { + REPORT_ERROR(__LINE__, appendAllocation); + } + + return retval; +} + +/* +** hasCallsiteMatch +** +** Determine if the callsite or the other callsites has the matching text. +** +** Returns 0 if there is no match. +*/ +int +hasCallsiteMatch(tmcallsite * aCallsite, const char *aMatch, int aDirection) +{ + int retval = 0; + + if (NULL != aCallsite && NULL != aCallsite->method && + NULL != aMatch && '\0' != *aMatch) { + const char *methodName = NULL; + + do { + methodName = tmmethodnode_name(aCallsite->method); + if (NULL != methodName && NULL != strstr(methodName, aMatch)) { + /* + ** Contains the text. + */ + retval = __LINE__; + break; + } + else { + switch (aDirection) { + case ST_FOLLOW_SIBLINGS: + aCallsite = aCallsite->siblings; + break; + case ST_FOLLOW_PARENTS: + aCallsite = aCallsite->parent; + break; + default: + aCallsite = NULL; + REPORT_ERROR(__LINE__, hasCallsiteMatch); + break; + } + } + } + while (NULL != aCallsite && NULL != aCallsite->method); + } + else { + REPORT_ERROR(__LINE__, hasCallsiteMatch); + } + + return retval; +} + +/* +** harvestRun +** +** Provide a simply way to go over a run, and yield the relevant allocations. +** The restrictions are easily set via the options page or the command +** line switches. +** +** On any match, add the allocation to the provided run. +** +** This makes it much easier for all the code to respect the options in +** force. +** +** Returns !0 on error, though aOutRun may contain a partial data set. +*/ +int +harvestRun(const STRun * aInRun, STRun * aOutRun, + STOptions * aOptions, STContext * inContext) +{ + int retval = 0; + +#if defined(DEBUG_dp) + PRIntervalTime start = PR_IntervalNow(); + + fprintf(stderr, "DEBUG: harvesting run...\n"); +#endif + + if (NULL != aInRun && NULL != aOutRun && aInRun != aOutRun + && NULL != aOptions && NULL != inContext) { + uint32_t traverse = 0; + STAllocation *current = NULL; + + for (traverse = 0; + 0 == retval && traverse < aInRun->mAllocationCount; traverse++) { + current = aInRun->mAllocations[traverse]; + if (NULL != current) { + uint32_t lifetime = 0; + uint32_t bytesize = 0; + uint64_t weight64 = 0; + uint64_t bytesize64 = 0; + uint64_t lifetime64 = 0; + int appendRes = 0; + int looper = 0; + PRBool matched = PR_FALSE; + + /* + ** Use this as an opportune time to fixup a memory + ** leaked timeval, so as to not completely skew + ** the weights. + */ + if (ST_TIMEVAL_MAX == current->mMaxTimeval) { + current->mMaxTimeval = globals.mMaxTimeval; + } + + /* + ** Check allocation timeval restrictions. + ** We have to slide the recorded timevals to be zero + ** based, so that the comparisons make sense. + */ + if ((aOptions->mAllocationTimevalMin > + (current->mMinTimeval - globals.mMinTimeval)) || + (aOptions->mAllocationTimevalMax < + (current->mMinTimeval - globals.mMinTimeval))) { + continue; + } + + /* + ** Check timeval restrictions. + ** We have to slide the recorded timevals to be zero + ** based, so that the comparisons make sense. + */ + if ((aOptions->mTimevalMin > + (current->mMinTimeval - globals.mMinTimeval)) || + (aOptions->mTimevalMax < + (current->mMinTimeval - globals.mMinTimeval))) { + continue; + } + + /* + ** Check lifetime restrictions. + */ + lifetime = current->mMaxTimeval - current->mMinTimeval; + if ((lifetime < aOptions->mLifetimeMin) || + (lifetime > aOptions->mLifetimeMax)) { + continue; + } + + /* + ** Check byte size restrictions. + */ + bytesize = byteSize(aOptions, current); + if ((bytesize < aOptions->mSizeMin) || + (bytesize > aOptions->mSizeMax)) { + continue; + } + + /* + ** Check weight restrictions. + */ + weight64 = (uint64_t)(bytesize * lifetime); + if (weight64 < aOptions->mWeightMin64 || + weight64 > aOptions->mWeightMax64) { + continue; + } + + /* + ** Possibly restrict the callsite by text. + ** Do this last, as it is a heavier check. + ** + ** One day, we may need to expand the logic to check for + ** events beyond the initial allocation event. + */ + for (looper = 0; ST_SUBSTRING_MATCH_MAX > looper; looper++) { + if ('\0' != aOptions->mRestrictText[looper][0]) { + if (0 == + hasCallsiteMatch(current->mEvents[0].mCallsite, + aOptions->mRestrictText[looper], + ST_FOLLOW_PARENTS)) { + break; + } + } + else { + matched = PR_TRUE; + break; + } + } + if (ST_SUBSTRING_MATCH_MAX == looper) { + matched = PR_TRUE; + } + if (PR_FALSE == matched) { + continue; + } + + /* + ** You get here, we add to the run. + */ + appendRes = + appendAllocation(aOptions, inContext, aOutRun, current); + if (0 == appendRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, appendAllocation); + } + } + } + } + +#if defined(DEBUG_dp) + fprintf(stderr, "DEBUG: harvesting ends: %dms [%d allocations]\n", + PR_IntervalToMilliseconds(PR_IntervalNow() - start), + aInRun->mAllocationCount); +#endif + return retval; +} + +/* +** recalculateRunCost +** +** Goes over all allocations of a run and recalculates and propagates +** the allocation costs - weight, heapcount, size +*/ +int +recalculateRunCost(STOptions * inOptions, STContext * inContext, STRun * aRun) +{ + uint32_t traverse = 0; + STAllocation *current = NULL; + +#if defined(DEBUG_dp) + PRIntervalTime start = PR_IntervalNow(); + + fprintf(stderr, "DEBUG: recalculateRunCost...\n"); +#endif + + if (NULL == aRun) + return -1; + + /* reset stats of this run to 0 to begin recalculation */ + memset(&aRun->mStats[inContext->mIndex], 0, sizeof(STCallsiteStats)); + + /* reset timestamp to force propogation of cost */ + aRun->mStats[inContext->mIndex].mStamp = PR_IntervalNow(); + + for (traverse = 0; traverse < aRun->mAllocationCount; traverse++) { + current = aRun->mAllocations[traverse]; + if (NULL != current) { + recalculateAllocationCost(inOptions, inContext, aRun, current, + PR_TRUE); + } + } + +#if defined(DEBUG_dp) + fprintf(stderr, "DEBUG: recalculateRunCost ends: %dms [%d allocations]\n", + PR_IntervalToMilliseconds(PR_IntervalNow() - start), + aRun->mAllocationCount); +#endif + + return 0; +} + + +/* +** compareAllocations +** +** qsort callback. +** Compare the allocations as specified by the options. +*/ +int +compareAllocations(const void *aAlloc1, const void *aAlloc2, void *aContext) +{ + int retval = 0; + STOptions *inOptions = (STOptions *) aContext; + + if (NULL != aAlloc1 && NULL != aAlloc2 && NULL != inOptions) { + STAllocation *alloc1 = *((STAllocation **) aAlloc1); + STAllocation *alloc2 = *((STAllocation **) aAlloc2); + + if (NULL != alloc1 && NULL != alloc2) { + /* + ** Logic determined by pref/option. + */ + switch (inOptions->mOrderBy) { + case ST_COUNT: + /* + ** "By count" on a single allocation means nothing, + ** fall through to weight. + */ + case ST_WEIGHT: + { + uint64_t weight164 = 0; + uint64_t weight264 = 0; + uint64_t bytesize164 = 0; + uint64_t bytesize264 = 0; + uint64_t timeval164 = 0; + uint64_t timeval264 = 0; + + bytesize164 = byteSize(inOptions, alloc1); + timeval164 = alloc1->mMaxTimeval - alloc1->mMinTimeval; + weight164 = bytesize164 * timeval164; + bytesize264 = byteSize(inOptions, alloc2); + timeval264 = alloc2->mMaxTimeval - alloc2->mMinTimeval; + weight264 = bytesize264 * timeval264; + + if (weight164 < weight264) { + retval = __LINE__; + } + else if (weight164 > weight264) { + retval = -__LINE__; + } + } + break; + + case ST_SIZE: + { + uint32_t size1 = byteSize(inOptions, alloc1); + uint32_t size2 = byteSize(inOptions, alloc2); + + if (size1 < size2) { + retval = __LINE__; + } + else if (size1 > size2) { + retval = -__LINE__; + } + } + break; + + case ST_TIMEVAL: + { + uint32_t timeval1 = + (alloc1->mMaxTimeval - alloc1->mMinTimeval); + uint32_t timeval2 = + (alloc2->mMaxTimeval - alloc2->mMinTimeval); + + if (timeval1 < timeval2) { + retval = __LINE__; + } + else if (timeval1 > timeval2) { + retval = -__LINE__; + } + } + break; + + case ST_HEAPCOST: + { + uint32_t cost1 = alloc1->mHeapRuntimeCost; + uint32_t cost2 = alloc2->mHeapRuntimeCost; + + if (cost1 < cost2) { + retval = __LINE__; + } + else if (cost1 > cost2) { + retval = -__LINE__; + } + } + break; + + default: + { + REPORT_ERROR(__LINE__, compareAllocations); + } + break; + } + } + } + + return retval; +} + +/* +** sortRun +** +** Given a run, sort it in the manner specified by the options. +** Returns !0 on failure. +*/ +int +sortRun(STOptions * inOptions, STRun * aRun) +{ + int retval = 0; + + if (NULL != aRun && NULL != inOptions) { + if (NULL != aRun->mAllocations && 0 < aRun->mAllocationCount) { + NS_QuickSort(aRun->mAllocations, aRun->mAllocationCount, + sizeof(STAllocation *), compareAllocations, + inOptions); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, sortRun); + } + + return retval; +} + +/* +** createRun +** +** Returns a newly allocated run, properly initialized. +** Must call freeRun() with the new STRun. +** +** ONLY PASS IN A NON_ZERO STAMP IF YOU KNOW WHAT YOU ARE DOING!!! +** A non zero stamp in a run has side effects all over the +** callsites of the allocations added to the run and their +** parents. +** +** Returns NULL on failure. +*/ +STRun * +createRun(STContext * inContext, uint32_t aStamp) +{ + STRun *retval = NULL; + + retval = (STRun *) calloc(1, sizeof(STRun)); + if (NULL != retval) { + retval->mStats = + (STCallsiteStats *) calloc(globals.mCommandLineOptions.mContexts, + sizeof(STCallsiteStats)); + if (NULL != retval->mStats) { + if (NULL != inContext) { + retval->mStats[inContext->mIndex].mStamp = aStamp; + } + } + else { + free(retval); + retval = NULL; + } + } + + return retval; +} + +/* +** freeRun +** +** Free off the run and the associated data. +*/ +void +freeRun(STRun * aRun) +{ + if (NULL != aRun) { + if (NULL != aRun->mAllocations) { + /* + ** We do not free the allocations themselves. + ** They are likely pointed to by at least 2 other existing + ** runs. + */ + free(aRun->mAllocations); + aRun->mAllocations = NULL; + } + + if (NULL != aRun->mStats) { + free(aRun->mStats); + aRun->mStats = NULL; + } + + free(aRun); + aRun = NULL; + } +} + +/* +** createRunFromGlobal +** +** Harvest the global run, then sort it. +** Returns NULL on failure. +** Must call freeRun() with the new STRun. +*/ +STRun * +createRunFromGlobal(STOptions * inOptions, STContext * inContext) +{ + STRun *retval = NULL; + + if (NULL != inOptions && NULL != inContext) { + /* + ** We stamp the run. + ** As things are appended to it, it realizes that it should stamp the + ** callsite backtrace with the information as well. + ** In this manner, we can provide meaningful callsite data. + */ + retval = createRun(inContext, PR_IntervalNow()); + + if (NULL != retval) { + STCategoryNode *node = NULL; + int failure = 0; + int harvestRes = + harvestRun(&globals.mRun, retval, inOptions, inContext); + if (0 == harvestRes) { + int sortRes = sortRun(inOptions, retval); + + if (0 != sortRes) { + failure = __LINE__; + } + } + else { + failure = __LINE__; + } + + + if (0 != failure) { + freeRun(retval); + retval = NULL; + + REPORT_ERROR(failure, createRunFromGlobal); + } + + /* + ** Categorize the run. + */ + failure = categorizeRun(inOptions, inContext, retval, &globals); + if (0 != failure) { + REPORT_ERROR(__LINE__, categorizeRun); + } + + /* + ** if we are focussing on a category, return that run instead of + ** the harvested run. Make sure to recalculate cost. + */ + node = findCategoryNode(inOptions->mCategoryName, &globals); + if (node) { + /* Recalculate cost of run */ + recalculateRunCost(inOptions, inContext, + node->runs[inContext->mIndex]); + + retval = node->runs[inContext->mIndex]; + } + } + } + else { + REPORT_ERROR(__LINE__, createRunFromGlobal); + } + + return retval; +} + +/* +** getLiveAllocationByHeapID +** +** Go through a run and find the right heap ID. +** At the time of the call to this function, the allocation must be LIVE, +** meaning that it can not be freed. +** Go through the run backwards, in hopes of finding it near the end. +** +** Returns the allocation on success, otherwise NULL. +*/ +STAllocation * +getLiveAllocationByHeapID(STRun * aRun, uint32_t aHeapID) +{ + STAllocation *retval = NULL; + + if (NULL != aRun && 0 != aHeapID) { + uint32_t traverse = aRun->mAllocationCount; + STAllocation *eval = NULL; + + /* + ** Go through in reverse order. + ** Stop when we have a return value. + */ + while (0 < traverse && NULL == retval) { + /* + ** Back up one to align with zero based index. + */ + traverse--; + + /* + ** Take the pointer math out of further operations. + */ + eval = aRun->mAllocations[traverse]; + + /* + ** Take a look at the events in reverse order. + ** Basically the last event must NOT be a free. + ** The last event must NOT be a realloc of size zero (free). + ** Otherwise, try to match up the heapID of the event. + */ + if (0 != eval->mEventCount) { + STAllocEvent *event = eval->mEvents + (eval->mEventCount - 1); + + switch (event->mEventType) { + case TM_EVENT_FREE: + { + /* + ** No freed allocation can match. + */ + } + break; + + case TM_EVENT_REALLOC: + case TM_EVENT_CALLOC: + case TM_EVENT_MALLOC: + { + /* + ** Heap IDs must match. + */ + if (aHeapID == event->mHeapID) { + retval = eval; + } + } + break; + + default: + { + REPORT_ERROR(__LINE__, getAllocationByHeapID); + } + break; + } + } + } + } + else { + REPORT_ERROR(__LINE__, getAllocationByHeapID); + } + + return retval; +} + +/* +** appendEvent +** +** Given an allocation, append a new event to its lifetime. +** Returns the new event on success, otherwise NULL. +*/ +STAllocEvent * +appendEvent(STAllocation * aAllocation, uint32_t aTimeval, char aEventType, + uint32_t aHeapID, uint32_t aHeapSize, tmcallsite * aCallsite) +{ + STAllocEvent *retval = NULL; + + if (NULL != aAllocation && NULL != aCallsite) { + STAllocEvent *expand = NULL; + + /* + ** Expand the allocation's event array. + */ + expand = + (STAllocEvent *) realloc(aAllocation->mEvents, + sizeof(STAllocEvent) * + (aAllocation->mEventCount + 1)); + if (NULL != expand) { + /* + ** Reassign in case of pointer move. + */ + aAllocation->mEvents = expand; + + /* + ** Remove the pointer math from rest of code. + */ + retval = aAllocation->mEvents + aAllocation->mEventCount; + + /* + ** Increase event array count. + */ + aAllocation->mEventCount++; + + /* + ** Fill in the event. + */ + retval->mTimeval = aTimeval; + retval->mEventType = aEventType; + retval->mHeapID = aHeapID; + retval->mHeapSize = aHeapSize; + retval->mCallsite = aCallsite; + + /* + ** Allocation may need to update idea of lifetime. + ** See allocationTracker to see mMinTimeval inited to ST_TIMEVAL_MAX. + */ + if (aAllocation->mMinTimeval > aTimeval) { + aAllocation->mMinTimeval = aTimeval; + } + + /* + ** This a free event? + ** Can only set max timeval on a free. + ** Otherwise, mMaxTimeval remains ST_TIMEVAL_MAX. + ** Set in allocationTracker. + */ + if (TM_EVENT_FREE == aEventType) { + aAllocation->mMaxTimeval = aTimeval; + } + } + else { + REPORT_ERROR(__LINE__, appendEvent); + } + } + else { + REPORT_ERROR(__LINE__, appendEvent); + } + + return retval; +} + +/* +** hasAllocation +** +** Determine if a given run has an allocation. +** This is really nothing more than a pointer comparison loop. +** Returns !0 if the run has the allocation. +*/ +int +hasAllocation(STRun * aRun, STAllocation * aTestFor) +{ + int retval = 0; + + if (NULL != aRun && NULL != aTestFor) { + uint32_t traverse = aRun->mAllocationCount; + + /* + ** Go through reverse, in the hopes it exists nearer the end. + */ + while (0 < traverse) { + /* + ** Back up. + */ + traverse--; + + if (aTestFor == aRun->mAllocations[traverse]) { + retval = __LINE__; + break; + } + } + } + else { + REPORT_ERROR(__LINE__, hasAllocation); + } + + return retval; +} + +/* +** allocationTracker +** +** Important to keep track of all allocations unique so as to determine +** their lifetimes. +** +** Returns a pointer to the allocation on success. +** Return NULL on failure. +*/ +STAllocation * +allocationTracker(uint32_t aTimeval, char aType, uint32_t aHeapRuntimeCost, + tmcallsite * aCallsite, uint32_t aHeapID, uint32_t aSize, + tmcallsite * aOldCallsite, uint32_t aOldHeapID, + uint32_t aOldSize) +{ + STAllocation *retval = NULL; + static int compactor = 1; + const int frequency = 10000; + uint32_t actualSize, actualOldSize = 0; + + actualSize = actualByteSize(&globals.mCommandLineOptions, aSize); + if (aOldSize) + actualOldSize = + actualByteSize(&globals.mCommandLineOptions, aOldSize); + + if (NULL != aCallsite) { + int newAllocation = 0; + tmcallsite *searchCallsite = NULL; + uint32_t searchHeapID = 0; + STAllocation *allocation = NULL; + + /* + ** Global operation ID increases. + */ + globals.mOperationCount++; + + /* + ** Fix up the timevals if needed. + */ + if (aTimeval < globals.mMinTimeval) { + globals.mMinTimeval = aTimeval; + } + if (aTimeval > globals.mMaxTimeval) { + globals.mMaxTimeval = aTimeval; + } + + switch (aType) { + case TM_EVENT_FREE: + { + /* + ** Update the global counter. + */ + globals.mFreeCount++; + + /* + ** Update our peak memory used counter + */ + globals.mMemoryUsed -= actualSize; + + /* + ** Not a new allocation, will need to search passed in site + ** for the original allocation. + */ + searchCallsite = aCallsite; + searchHeapID = aHeapID; + } + break; + + case TM_EVENT_MALLOC: + { + /* + ** Update the global counter. + */ + globals.mMallocCount++; + + /* + ** Update our peak memory used counter + */ + globals.mMemoryUsed += actualSize; + if (globals.mMemoryUsed > globals.mPeakMemoryUsed) { + globals.mPeakMemoryUsed = globals.mMemoryUsed; + } + + /* + ** This will be a new allocation. + */ + newAllocation = __LINE__; + } + break; + + case TM_EVENT_CALLOC: + { + /* + ** Update the global counter. + */ + globals.mCallocCount++; + + /* + ** Update our peak memory used counter + */ + globals.mMemoryUsed += actualSize; + if (globals.mMemoryUsed > globals.mPeakMemoryUsed) { + globals.mPeakMemoryUsed = globals.mMemoryUsed; + } + + /* + ** This will be a new allocation. + */ + newAllocation = __LINE__; + } + break; + + case TM_EVENT_REALLOC: + { + /* + ** Update the global counter. + */ + globals.mReallocCount++; + + /* + ** Update our peak memory used counter + */ + globals.mMemoryUsed += actualSize - actualOldSize; + if (globals.mMemoryUsed > globals.mPeakMemoryUsed) { + globals.mPeakMemoryUsed = globals.mMemoryUsed; + } + + /* + ** This might be a new allocation. + */ + if (NULL == aOldCallsite) { + newAllocation = __LINE__; + } + else { + /* + ** Need to search for the original callsite for the + ** index to the allocation. + */ + searchCallsite = aOldCallsite; + searchHeapID = aOldHeapID; + } + } + break; + + default: + { + REPORT_ERROR(__LINE__, allocationTracker); + } + break; + } + + /* + ** We are either modifying an existing allocation or we are creating + ** a new one. + */ + if (0 != newAllocation) { + allocation = (STAllocation *) calloc(1, sizeof(STAllocation)); + if (NULL != allocation) { + /* + ** Fixup the min timeval so if logic later will just work. + */ + allocation->mMinTimeval = ST_TIMEVAL_MAX; + allocation->mMaxTimeval = ST_TIMEVAL_MAX; + } + } + else if (NULL != searchCallsite + && NULL != CALLSITE_RUN(searchCallsite) + && 0 != searchHeapID) { + /* + ** We know what to search for, and we reduce what we search + ** by only looking for those allocations at a known callsite. + */ + allocation = + getLiveAllocationByHeapID(CALLSITE_RUN(searchCallsite), + searchHeapID); + } + else { + REPORT_ERROR(__LINE__, allocationTracker); + } + + if (NULL != allocation) { + STAllocEvent *appendResult = NULL; + + /* + ** Record the amount of time this allocation event took. + */ + allocation->mHeapRuntimeCost += aHeapRuntimeCost; + + /* + ** Now that we have an allocation, we need to make sure it has + ** the proper event. + */ + appendResult = + appendEvent(allocation, aTimeval, aType, aHeapID, aSize, + aCallsite); + if (NULL != appendResult) { + if (0 != newAllocation) { + int runAppendResult = 0; + int callsiteAppendResult = 0; + + /* + ** A new allocation needs to be added to the global run. + ** A new allocation needs to be added to the callsite. + */ + runAppendResult = + appendAllocation(&globals.mCommandLineOptions, NULL, + &globals.mRun, allocation); + callsiteAppendResult = + appendAllocation(&globals.mCommandLineOptions, NULL, + CALLSITE_RUN(aCallsite), allocation); + if (0 != runAppendResult && 0 != callsiteAppendResult) { + /* + ** Success. + */ + retval = allocation; + } + else { + REPORT_ERROR(__LINE__, appendAllocation); + } + } + else { + /* + ** An existing allocation, if a realloc situation, + ** may need to be added to the new callsite. + ** This can only occur if the new and old callsites + ** differ. + ** Even then, a brute force check will need to be made + ** to ensure the allocation was not added twice; + ** consider a realloc scenario where two different + ** call stacks bump the allocation back and forth. + */ + if (aCallsite != searchCallsite) { + int found = 0; + + found = + hasAllocation(CALLSITE_RUN(aCallsite), + allocation); + if (0 == found) { + int appendResult = 0; + + appendResult = + appendAllocation(&globals.mCommandLineOptions, + NULL, + CALLSITE_RUN(aCallsite), + allocation); + if (0 != appendResult) { + /* + ** Success. + */ + retval = allocation; + } + else { + REPORT_ERROR(__LINE__, appendAllocation); + } + } + else { + /* + ** Already there. + */ + retval = allocation; + } + } + else { + /* + ** Success. + */ + retval = allocation; + } + } + } + else { + REPORT_ERROR(__LINE__, appendEvent); + } + } + else { + REPORT_ERROR(__LINE__, allocationTracker); + } + } + else { + REPORT_ERROR(__LINE__, allocationTracker); + } + + /* + ** Compact the heap a bit if you can. + */ + compactor++; + if (0 == (compactor % frequency)) { + heapCompact(); + } + + return retval; +} + +/* +** trackEvent +** +** An allocation event has dropped in on us. +** We need to do the right thing and track it. +*/ +void +trackEvent(uint32_t aTimeval, char aType, uint32_t aHeapRuntimeCost, + tmcallsite * aCallsite, uint32_t aHeapID, uint32_t aSize, + tmcallsite * aOldCallsite, uint32_t aOldHeapID, uint32_t aOldSize) +{ + if (NULL != aCallsite) { + /* + ** Verify the old callsite just in case. + */ + if (NULL != CALLSITE_RUN(aCallsite) + && (NULL == aOldCallsite || NULL != CALLSITE_RUN(aOldCallsite))) { + STAllocation *allocation = NULL; + + /* + ** Add to the allocation tracking code. + */ + allocation = + allocationTracker(aTimeval, aType, aHeapRuntimeCost, + aCallsite, aHeapID, aSize, aOldCallsite, + aOldHeapID, aOldSize); + + if (NULL == allocation) { + REPORT_ERROR(__LINE__, allocationTracker); + } + } + else { + REPORT_ERROR(__LINE__, trackEvent); + } + } + else { + REPORT_ERROR(__LINE__, trackEvent); + } +} + +/* +** tmEventHandler +** +** Callback from the tmreader_eventloop function. +** Simply tries to sort out what we desire to know. +*/ + +static const char spinner_chars[] = { '/', '-', '\\', '|' }; + +#define SPINNER_UPDATE_FREQUENCY 4096 +#define SPINNER_CHAR_COUNT (sizeof(spinner_chars) / sizeof(spinner_chars[0])) +#define SPINNER_CHAR(_x) spinner_chars[(_x / SPINNER_UPDATE_FREQUENCY) % SPINNER_CHAR_COUNT] + +void +tmEventHandler(tmreader * aReader, tmevent * aEvent) +{ + static int event_count = 0; /* for spinner */ + if ((event_count++ % SPINNER_UPDATE_FREQUENCY) == 0) + printf("\rReading... %c", SPINNER_CHAR(event_count)); + + if (NULL != aReader && NULL != aEvent) { + switch (aEvent->type) { + /* + ** Events we ignore. + */ + case TM_EVENT_LIBRARY: + case TM_EVENT_METHOD: + case TM_EVENT_STATS: + case TM_EVENT_TIMESTAMP: + case TM_EVENT_FILENAME: + break; + + /* + ** Allocation events need to be tracked. + */ + case TM_EVENT_MALLOC: + case TM_EVENT_CALLOC: + case TM_EVENT_REALLOC: + case TM_EVENT_FREE: + { + uint32_t oldptr = 0; + uint32_t oldsize = 0; + tmcallsite *callsite = NULL; + tmcallsite *oldcallsite = NULL; + + if (TM_EVENT_REALLOC == aEvent->type) { + /* + ** Only care about old arguments if there were any. + */ + if (0 != aEvent->u.alloc.oldserial) { + oldptr = aEvent->u.alloc.oldptr; + oldsize = aEvent->u.alloc.oldsize; + oldcallsite = + tmreader_callsite(aReader, + aEvent->u.alloc.oldserial); + if (NULL == oldcallsite) { + REPORT_ERROR(__LINE__, tmreader_callsite); + } + } + } + + callsite = tmreader_callsite(aReader, aEvent->serial); + if (NULL != callsite) { + /* + ** Verify a callsite run is there. + ** If not, we are ignoring this callsite. + */ + if (NULL != CALLSITE_RUN(callsite)) { + char eventType = aEvent->type; + uint32_t eventSize = aEvent->u.alloc.size; + + /* + ** Play a nasty trick on reallocs of size zero. + ** They are to become free events, adjust the size accordingly. + ** This allows me to avoid all types of special case code. + */ + if (0 == aEvent->u.alloc.size + && TM_EVENT_REALLOC == aEvent->type) { + eventType = TM_EVENT_FREE; + if (0 != aEvent->u.alloc.oldserial) { + eventSize = aEvent->u.alloc.oldsize; + } + } + trackEvent(ticks2msec + (aReader, aEvent->u.alloc.interval), + eventType, ticks2usec(aReader, + aEvent->u.alloc. + cost), callsite, + aEvent->u.alloc.ptr, eventSize, + oldcallsite, oldptr, oldsize); + } + } + else { + REPORT_ERROR(__LINE__, tmreader_callsite); + } + } + break; + + /* + ** Callsite, set up the callsite run if it does not exist. + */ + case TM_EVENT_CALLSITE: + { + tmcallsite *callsite = + tmreader_callsite(aReader, aEvent->serial); + + if (NULL != callsite) { + if (NULL == CALLSITE_RUN(callsite)) { + int createrun = __LINE__; + +#if defined(MOZILLA_CLIENT) + /* + ** For a mozilla spacetrace, ignore this particular + ** callsite as it is just noise, and causes us to + ** use a lot of memory. + ** + ** This callsite is present on the linux build, + ** not sure if the other platforms have it. + */ + if (0 != + hasCallsiteMatch(callsite, "g_main_is_running", + ST_FOLLOW_PARENTS)) { + createrun = 0; + } +#endif /* MOZILLA_CLIENT */ + + if (0 != createrun) { + callsite->data = createRun(NULL, 0); + } + } + } + else { + REPORT_ERROR(__LINE__, tmreader_callsite); + } + } + break; + + /* + ** Unhandled events should not be allowed. + */ + default: + { + REPORT_ERROR(__LINE__, tmEventHandler); + } + break; + } + } +} + +/* +** optionGetDataOut +** +** Output option get data. +*/ +void +optionGetDataOut(PRFileDesc * inFD, STOptions * inOptions) +{ + if (NULL != inFD && NULL != inOptions) { + int mark = 0; + +#define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \ + PR_fprintf(inFD, "%s%s=%d", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name); +#define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \ + PR_fprintf(inFD, "%s%s=%s", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name); +#define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ + { \ + uint32_t loop = 0; \ + \ + for(loop = 0; loop < array_size; loop++) \ + { \ + PR_fprintf(inFD, "%s%s=%s", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name[loop]); \ + } \ + } +#define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */ +#define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ + PR_fprintf(inFD, "%s%s=%u", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name / multiplier); +#define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ + { \ + uint64_t def64 = default_value; \ + uint64_t mul64 = multiplier; \ + uint64_t div64; \ + \ + div64 = inOptions->m##option_name##64 / mul64; \ + PR_fprintf(inFD, "%s%s=%llu", (0 == mark++) ? "?" : "&", #option_name, div64); \ + } + +#include "stoptions.h" + } +} + +/* +** htmlAnchor +** +** Output an HTML anchor, or just the text depending on the mode. +*/ +void +htmlAnchor(STRequest * inRequest, + const char *aHref, + const char *aText, + const char *aTarget, const char *aClass, STOptions * inOptions) +{ + if (NULL != aHref && '\0' != *aHref && NULL != aText && '\0' != *aText) { + int anchorLive = 1; + + /* + ** In batch mode, we need to verify the anchor is live. + */ + if (0 != inRequest->mOptions.mBatchRequestCount) { + uint32_t loop = 0; + int comparison = 1; + + for (loop = 0; loop < inRequest->mOptions.mBatchRequestCount; + loop++) { + comparison = + strcmp(aHref, inRequest->mOptions.mBatchRequest[loop]); + if (0 == comparison) { + break; + } + } + + /* + ** Did we find it? + */ + if (0 == comparison) { + anchorLive = 0; + } + } + + /* + ** In any mode, don't make an href to the current page. + */ + if (0 != anchorLive && NULL != inRequest->mGetFileName) { + if (0 == strcmp(aHref, inRequest->mGetFileName)) { + anchorLive = 0; + } + } + + /* + ** Do the right thing. + */ + if (0 != anchorLive) { + PR_fprintf(inRequest->mFD, "mFD, "target=\"%s\" ", aTarget); + } + PR_fprintf(inRequest->mFD, "href=\"./%s", aHref); + + /* + ** The options, if desired, get appended as form data. + */ + optionGetDataOut(inRequest->mFD, inOptions); + + PR_fprintf(inRequest->mFD, "\">%s\n", aText); + } + else { + PR_fprintf(inRequest->mFD, "%s\n", + aClass, aText); + } + } + else { + REPORT_ERROR(__LINE__, htmlAnchor); + } +} + +/* +** htmlAllocationAnchor +** +** Output an html achor that will resolve to the allocation in question. +*/ +void +htmlAllocationAnchor(STRequest * inRequest, STAllocation * aAllocation, + const char *aText) +{ + if (NULL != aAllocation && NULL != aText && '\0' != *aText) { + char buffer[128]; + + /* + ** This is a total hack. + ** The filename contains the index of the allocation in globals.mRun. + ** Safer than using the raw pointer value.... + */ + PR_snprintf(buffer, sizeof(buffer), "allocation_%u.html", + aAllocation->mRunIndex); + + htmlAnchor(inRequest, buffer, aText, NULL, "allocation", + &inRequest->mOptions); + } + else { + REPORT_ERROR(__LINE__, htmlAllocationAnchor); + } +} + +/* +** resolveSourceFile +** +** Easy way to get a readable/short name. +** NULL if not present, not resolvable. +*/ +const char * +resolveSourceFile(tmmethodnode * aMethod) +{ + const char *retval = NULL; + + if (NULL != aMethod) { + const char *methodSays = NULL; + + methodSays = aMethod->sourcefile; + + if (NULL != methodSays && '\0' != methodSays[0] + && 0 != strcmp("noname", methodSays)) { + retval = strrchr(methodSays, '/'); + if (NULL != retval) { + retval++; + } + else { + retval = methodSays; + } + } + } + + return retval; +} + +/* +** htmlCallsiteAnchor +** +** Output an html anchor that will resolve to the callsite in question. +** If no text is provided, we provide our own. +** +** RealName determines whether or not we crawl our parents until the point +** we no longer match stats. +*/ +void +htmlCallsiteAnchor(STRequest * inRequest, tmcallsite * aCallsite, + const char *aText, int aRealName) +{ + if (NULL != aCallsite) { + char textBuf[512]; + char hrefBuf[128]; + tmcallsite *namesite = aCallsite; + + /* + ** Should we use a different name? + */ + if (0 == aRealName && NULL != namesite->parent + && NULL != namesite->parent->method) { + STRun *myRun = NULL; + STRun *upRun = NULL; + + do { + myRun = CALLSITE_RUN(namesite); + upRun = CALLSITE_RUN(namesite->parent); + + if (0 != + memcmp(&myRun->mStats[inRequest->mContext->mIndex], + &upRun->mStats[inRequest->mContext->mIndex], + sizeof(STCallsiteStats))) { + /* + ** Doesn't match, stop. + */ + break; + } + else { + /* + ** Matches, keep going up. + */ + namesite = namesite->parent; + } + } + while (NULL != namesite->parent + && NULL != namesite->parent->method); + } + + /* + ** If no text, provide our own. + */ + if (NULL == aText || '\0' == *aText) { + const char *methodName = NULL; + const char *sourceFile = NULL; + + if (NULL != namesite->method) { + methodName = tmmethodnode_name(namesite->method); + } + else { + methodName = "==NONAME=="; + } + + /* + ** Decide which format to use to identify the callsite. + ** If we can detect availability, hook up the filename with lxr information. + */ + sourceFile = resolveSourceFile(namesite->method); + if (NULL != sourceFile + && 0 == strncmp("mozilla/", namesite->method->sourcefile, + 8)) { + char lxrHREFBuf[512]; + + PR_snprintf(lxrHREFBuf, sizeof(lxrHREFBuf), + " [%s:%u]", + namesite->method->sourcefile + 8, + namesite->method->linenumber, sourceFile, + namesite->method->linenumber); + PR_snprintf(textBuf, sizeof(textBuf), + "%s%s", + methodName, lxrHREFBuf); + } + else if (NULL != sourceFile) { + PR_snprintf(textBuf, sizeof(textBuf), + "%s [%s:%u]", + methodName, sourceFile, + namesite->method->linenumber); + } + else { + PR_snprintf(textBuf, sizeof(textBuf), + "%s [+%u(%u)]", + methodName, namesite->offset, + (uint32_t) namesite->entry.key); + } + + aText = textBuf; + } + + PR_snprintf(hrefBuf, sizeof(hrefBuf), "callsite_%u.html", + (uint32_t) aCallsite->entry.key); + + htmlAnchor(inRequest, hrefBuf, aText, NULL, "callsite", + &inRequest->mOptions); + } + else { + REPORT_ERROR(__LINE__, htmlCallsiteAnchor); + } +} + +/* +** htmlHeader +** +** Output a standard header in the report files. +*/ +void +htmlHeader(STRequest * inRequest, const char *aTitle) +{ + PR_fprintf(inRequest->mFD, + "\n" + "\n" + "%s\n" + "\n" + "\n" + "
\n" + "Spacetrace" + "\n" + "Category:\n" + "%s\n", + aTitle, inRequest->mOptions.mCategoryName); + + PR_fprintf(inRequest->mFD, ""); + htmlAnchor(inRequest, "index.html", "Index", NULL, "header-menuitem", + &inRequest->mOptions); + PR_fprintf(inRequest->mFD, "\n"); + + PR_fprintf(inRequest->mFD, ""); + htmlAnchor(inRequest, "options.html", "Options", NULL, "header-menuitem", + &inRequest->mOptions); + PR_fprintf(inRequest->mFD, "\n"); + + PR_fprintf(inRequest->mFD, "\n"); /* class=navigate */ + + PR_fprintf(inRequest->mFD, + "
\n\n
\n\n"); +} + +/* +** htmlFooter +** +** Output a standard footer in the report file. +*/ +void +htmlFooter(STRequest * inRequest) +{ + PR_fprintf(inRequest->mFD, + "
\n\n" + "
\n" + "SpaceTrace\n" + "
\n\n" "\n" "\n"); +} + +/* +** htmlNotFound +** +** Not found message. +*/ +void +htmlNotFound(STRequest * inRequest) +{ + htmlHeader(inRequest, "File Not Found"); + PR_fprintf(inRequest->mFD, "File Not Found\n"); + htmlFooter(inRequest); +} + +void +htmlStartTable(STRequest* inRequest, + const char* table_class, + const char* id, + const char* caption, + const char * const headers[], uint32_t header_length) +{ + uint32_t i; + + PR_fprintf(inRequest->mFD, + "
\n" + " " + " \n" + " \n", id, + table_class ? table_class : "", + caption); + + for (i=0; i< header_length; i++) + PR_fprintf(inRequest->mFD, + " \n", headers[i]); + + PR_fprintf(inRequest->mFD, " \n"); +} + +/* +** callsiteArrayFromCallsite +** +** Simply return an array of the callsites divulged from the site passed in, +** including the site passed in. +** Do not worry about dups, or the order of the items. +** +** Returns the number of items in the array. +** If the same as aExistingCount, then nothing happened. +*/ +uint32_t +callsiteArrayFromCallsite(tmcallsite *** aArray, uint32_t aExistingCount, + tmcallsite * aSite, int aFollow) +{ + uint32_t retval = 0; + + if (NULL != aArray && NULL != aSite) { + tmcallsite **expand = NULL; + + /* + ** If we have an existing count, we just keep expanding this. + */ + retval = aExistingCount; + + /* + ** Go through every allocation. + */ + do { + /* + ** expand the array. + */ + expand = + (tmcallsite **) realloc(*aArray, + sizeof(tmcallsite *) * (retval + 1)); + if (NULL != expand) { + /* + ** Set the callsite in case of pointer move. + */ + *aArray = expand; + + /* + ** Assign the value. + */ + (*aArray)[retval] = aSite; + retval++; + } + else { + REPORT_ERROR(__LINE__, realloc); + break; + } + + + /* + ** What do we follow? + */ + switch (aFollow) { + case ST_FOLLOW_SIBLINGS: + aSite = aSite->siblings; + break; + case ST_FOLLOW_PARENTS: + aSite = aSite->parent; + break; + default: + aSite = NULL; + REPORT_ERROR(__LINE__, callsiteArrayFromCallsite); + break; + } + } + while (NULL != aSite && NULL != aSite->method); + } + + return retval; +} + +/* +** callsiteArrayFromRun +** +** Simply return an array of the callsites from the run allocations. +** We only pay attention to callsites that were not free callsites. +** Do not worry about dups, or the order of the items. +** +** Returns the number of items in the array. +** If the same as aExistingCount, then nothing happened. +*/ +uint32_t +callsiteArrayFromRun(tmcallsite *** aArray, uint32_t aExistingCount, + STRun * aRun) +{ + uint32_t retval = 0; + + if (NULL != aArray && NULL != aRun && 0 < aRun->mAllocationCount) { + uint32_t allocLoop = 0; + uint32_t eventLoop = 0; + int stopLoops = 0; + + /* + ** If we have an existing count, we just keep expanding this. + */ + retval = aExistingCount; + + /* + ** Go through every allocation. + */ + for (allocLoop = 0; + 0 == stopLoops && allocLoop < aRun->mAllocationCount; + allocLoop++) { + /* + ** Go through every event. + */ + for (eventLoop = 0; + 0 == stopLoops + && eventLoop < aRun->mAllocations[allocLoop]->mEventCount; + eventLoop++) { + /* + ** Skip the free events. + */ + if (TM_EVENT_FREE != + aRun->mAllocations[allocLoop]->mEvents[eventLoop]. + mEventType) { + tmcallsite **expand = NULL; + + /* + ** expand the array. + */ + expand = + (tmcallsite **) realloc(*aArray, + sizeof(tmcallsite *) * + (retval + 1)); + if (NULL != expand) { + /* + ** Set the callsite in case of pointer move. + */ + *aArray = expand; + + /* + ** Assign the value. + */ + (*aArray)[retval] = + aRun->mAllocations[allocLoop]->mEvents[eventLoop]. + mCallsite; + retval++; + } + else { + REPORT_ERROR(__LINE__, realloc); + stopLoops = __LINE__; + } + } + } + } + } + + return retval; +} + +/* +** getDataPRUint* +** +** Helper to avoid cut and paste code. +** Failure to find aCheckFor does not mean failure. +** In case of dups, specify an index on non "1" to get others. +** Do not touch storage space unless a find is made. +** Returns !0 on failure. +*/ +int +getDataPRUint32Base(const FormData * aGetData, const char *aCheckFor, + int inIndex, void *aStoreResult, uint32_t aBits) +{ + int retval = 0; + + if (NULL != aGetData && NULL != aCheckFor && 0 != inIndex + && NULL != aStoreResult) { + unsigned finder = 0; + + /* + ** Loop over the names, looking for an exact string match. + ** Skip over initial finds, decrementing inIndex, until "1". + */ + for (finder = 0; finder < aGetData->mNVCount; finder++) { + if (0 == strcmp(aCheckFor, aGetData->mNArray[finder])) { + inIndex--; + + if (0 == inIndex) { + int32_t scanRes = 0; + + if (64 == aBits) { + scanRes = + PR_sscanf(aGetData->mVArray[finder], "%llu", + aStoreResult); + } + else { + scanRes = + PR_sscanf(aGetData->mVArray[finder], "%u", + aStoreResult); + } + if (1 != scanRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, PR_sscanf); + } + break; + } + } + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, getDataPRUint32Base); + } + + return retval; +} + +int +getDataPRUint32(const FormData * aGetData, const char *aCheckFor, int inIndex, + uint32_t * aStoreResult, uint32_t aConversion) +{ + int retval = 0; + + retval = + getDataPRUint32Base(aGetData, aCheckFor, inIndex, aStoreResult, 32); + *aStoreResult *= aConversion; + + return retval; +} + +int +getDataPRUint64(const FormData * aGetData, const char *aCheckFor, int inIndex, + uint64_t * aStoreResult64, uint64_t aConversion64) +{ + int retval = 0; + uint64_t value64 = 0; + + retval = getDataPRUint32Base(aGetData, aCheckFor, inIndex, &value64, 64); + *aStoreResult64 = value64 * aConversion64; + + return retval; +} + +/* +** getDataString +** +** Pull out the string data, if specified. +** In case of dups, specify an index on non "1" to get others. +** Do not touch storage space unless a find is made. +** Return !0 on failure. +*/ +int +getDataString(const FormData * aGetData, const char *aCheckFor, int inIndex, + char *aStoreResult, int inStoreResultLength) +{ + int retval = 0; + + if (NULL != aGetData && NULL != aCheckFor && 0 != inIndex + && NULL != aStoreResult && 0 != inStoreResultLength) { + unsigned finder = 0; + + /* + ** Loop over the names, looking for an exact string match. + ** Skip over initial finds, decrementing inIndex, until "1". + */ + for (finder = 0; finder < aGetData->mNVCount; finder++) { + if (0 == strcmp(aCheckFor, aGetData->mNArray[finder])) { + inIndex--; + + if (0 == inIndex) { + PR_snprintf(aStoreResult, inStoreResultLength, "%s", + aGetData->mVArray[finder]); + break; + } + } + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, getDataPRUint32); + } + + return retval; +} + +/* +** displayTopAllocations +** +** Present the top allocations. +** The run must be passed in, and it must be pre-sorted. +** +** Returns !0 on failure. +*/ +int +displayTopAllocations(STRequest * inRequest, STRun * aRun, + const char* id, + const char* caption, + int aWantCallsite) +{ + int retval = 0; + + if (NULL != aRun) { + if (0 < aRun->mAllocationCount) { + uint32_t loop = 0; + STAllocation *current = NULL; + + static const char* const headers[] = { + "Rank", "Index", "Byte Size", "Lifespan (sec)", + "Weight", "Heap Op (sec)" + }; + + static const char* const headers_callsite[] = { + "Rank", "Index", "Byte Size", "Lifespan (sec)", + "Weight", "Heap Op (sec)", "Origin Callsite" + }; + + if (aWantCallsite) + htmlStartTable(inRequest, NULL, id, + caption, + headers_callsite, + sizeof(headers_callsite) / sizeof(headers_callsite[0])); + else + htmlStartTable(inRequest, NULL, id, caption, + headers, + sizeof(headers) / sizeof(headers[0])); + /* + ** Loop over the items, up to some limit or until the end. + */ + for (loop = 0; + loop < inRequest->mOptions.mListItemMax + && loop < aRun->mAllocationCount; loop++) { + current = aRun->mAllocations[loop]; + if (NULL != current) { + uint32_t lifespan = + current->mMaxTimeval - current->mMinTimeval; + uint32_t size = byteSize(&inRequest->mOptions, current); + uint32_t heapCost = current->mHeapRuntimeCost; + uint64_t weight64 = 0; + char buffer[32]; + + weight64 =(uint64_t)(size * lifespan); + + PR_fprintf(inRequest->mFD, "\n"); + + /* + ** Rank. + */ + PR_fprintf(inRequest->mFD, "\n", + loop + 1); + + /* + ** Index. + */ + PR_snprintf(buffer, sizeof(buffer), "%u", + current->mRunIndex); + PR_fprintf(inRequest->mFD, "\n"); + + /* + ** Byte Size. + */ + PR_fprintf(inRequest->mFD, "\n", + size); + + /* + ** Lifespan. + */ + PR_fprintf(inRequest->mFD, + "\n", + ST_TIMEVAL_PRINTABLE(lifespan)); + + /* + ** Weight. + */ + PR_fprintf(inRequest->mFD, "\n", + weight64); + + /* + ** Heap operation cost. + */ + PR_fprintf(inRequest->mFD, + "\n", ST_MICROVAL_PRINTABLE(heapCost)); + + if (0 != aWantCallsite) { + /* + ** Callsite. + */ + PR_fprintf(inRequest->mFD, "\n"); + } + + PR_fprintf(inRequest->mFD, "\n"); + } + } + + PR_fprintf(inRequest->mFD, "\n
%s
%s
%u\n"); + htmlAllocationAnchor(inRequest, current, buffer); + PR_fprintf(inRequest->mFD, "%u" ST_TIMEVAL_FORMAT "%llu" ST_MICROVAL_FORMAT + ""); + htmlCallsiteAnchor(inRequest, + current->mEvents[0].mCallsite, + NULL, 0); + PR_fprintf(inRequest->mFD, "
\n\n"); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayTopAllocations); + } + + return retval; +} + +/* +** displayMemoryLeaks +** +** Present the top memory leaks. +** The run must be passed in, and it must be pre-sorted. +** +** Returns !0 on failure. +*/ +int +displayMemoryLeaks(STRequest * inRequest, STRun * aRun) +{ + int retval = 0; + + if (NULL != aRun) { + uint32_t loop = 0; + uint32_t displayed = 0; + STAllocation *current = NULL; + + static const char * headers[] = { + "Rank", "Index", "Byte Size", "Lifespan (sec)", + "Weight", "Heap Op (sec)", "Origin Callsite" + }; + + htmlStartTable(inRequest, NULL, "memory-leaks", "Memory Leaks", headers, + sizeof(headers) / sizeof(headers[0])); + + /* + ** Loop over all of the items, or until we've displayed enough. + */ + for (loop = 0; + displayed < inRequest->mOptions.mListItemMax + && loop < aRun->mAllocationCount; loop++) { + current = aRun->mAllocations[loop]; + if (NULL != current && 0 != current->mEventCount) { + /* + ** In order to be a leak, the last event of its life must + ** NOT be a free operation. + ** + ** A free operation is just that, a free. + */ + if (TM_EVENT_FREE != + current->mEvents[current->mEventCount - 1].mEventType) { + uint32_t lifespan = + current->mMaxTimeval - current->mMinTimeval; + uint32_t size = byteSize(&inRequest->mOptions, current); + uint32_t heapCost = current->mHeapRuntimeCost; + uint64_t weight64 = 0; + char buffer[32]; + + weight64 =(uint64_t)(size * lifespan); + + /* + ** One more shown. + */ + displayed++; + + PR_fprintf(inRequest->mFD, "\n"); + + /* + ** Rank. + */ + PR_fprintf(inRequest->mFD, "%u\n", + displayed); + + /* + ** Index. + */ + PR_snprintf(buffer, sizeof(buffer), "%u", + current->mRunIndex); + PR_fprintf(inRequest->mFD, "\n"); + htmlAllocationAnchor(inRequest, current, buffer); + PR_fprintf(inRequest->mFD, "\n"); + + /* + ** Byte Size. + */ + PR_fprintf(inRequest->mFD, "%u\n", + size); + + /* + ** Lifespan. + */ + PR_fprintf(inRequest->mFD, + "" ST_TIMEVAL_FORMAT "\n", + ST_TIMEVAL_PRINTABLE(lifespan)); + + /* + ** Weight. + */ + PR_fprintf(inRequest->mFD, "%llu\n", + weight64); + + /* + ** Heap Operation Seconds. + */ + PR_fprintf(inRequest->mFD, + "" ST_MICROVAL_FORMAT + "\n", ST_MICROVAL_PRINTABLE(heapCost)); + + /* + ** Callsite. + */ + PR_fprintf(inRequest->mFD, ""); + htmlCallsiteAnchor(inRequest, + current->mEvents[0].mCallsite, NULL, + 0); + PR_fprintf(inRequest->mFD, "\n"); + + PR_fprintf(inRequest->mFD, "\n"); + } + } + } + + PR_fprintf(inRequest->mFD, "\n\n"); + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayMemoryLeaks); + } + + return retval; +} + +/* +** displayCallsites +** +** Display a table of callsites. +** If the stamp is non zero, then must match that stamp. +** If the stamp is zero, then must match the global sorted run stamp. +** Return !0 on error. +*/ +int +displayCallsites(STRequest * inRequest, tmcallsite * aCallsite, int aFollow, + uint32_t aStamp, + const char* id, + const char* caption, + int aRealNames) +{ + int retval = 0; + + if (NULL != aCallsite && NULL != aCallsite->method) { + int headerDisplayed = 0; + STRun *run = NULL; + + /* + ** Correct the stamp if need be. + */ + if (0 == aStamp && NULL != inRequest->mContext->mSortedRun) { + aStamp = + inRequest->mContext->mSortedRun->mStats[inRequest->mContext-> + mIndex].mStamp; + } + + /* + ** Loop over the callsites looking for a stamp match. + ** A stamp guarantees there is something interesting to look at too. + ** If found, output it. + */ + while (NULL != aCallsite && NULL != aCallsite->method) { + run = CALLSITE_RUN(aCallsite); + if (NULL != run) { + if (aStamp == run->mStats[inRequest->mContext->mIndex].mStamp) { + /* + ** We got a header? + */ + if (0 == headerDisplayed) { + + static const char* const headers[] = { + "Callsite", + "C. Size", + "C. Seconds", + "C. Weight", + "H.O. Count", + "C.H. Operation (sec)" + }; + headerDisplayed = __LINE__; + htmlStartTable(inRequest, NULL, id, caption, headers, + sizeof(headers)/sizeof(headers[0])); + } + + /* + ** Output the information. + */ + PR_fprintf(inRequest->mFD, "\n"); + + /* + ** Method name. + */ + PR_fprintf(inRequest->mFD, ""); + htmlCallsiteAnchor(inRequest, aCallsite, NULL, + aRealNames); + PR_fprintf(inRequest->mFD, ""); + + /* + ** Byte Size. + */ + PR_fprintf(inRequest->mFD, + "%u\n", + run->mStats[inRequest->mContext->mIndex]. + mSize); + + /* + ** Seconds. + */ + PR_fprintf(inRequest->mFD, + "" ST_TIMEVAL_FORMAT + "\n", + ST_TIMEVAL_PRINTABLE64(run-> + mStats[inRequest-> + mContext-> + mIndex]. + mTimeval64)); + + /* + ** Weight. + */ + PR_fprintf(inRequest->mFD, + "%llu\n", + run->mStats[inRequest->mContext->mIndex]. + mWeight64); + + /* + ** Allocation object count. + */ + PR_fprintf(inRequest->mFD, + "%u\n", + run->mStats[inRequest->mContext->mIndex]. + mCompositeCount); + + /* + ** Heap Operation Seconds. + */ + PR_fprintf(inRequest->mFD, + "" + ST_MICROVAL_FORMAT "\n", + ST_MICROVAL_PRINTABLE(run-> + mStats[inRequest-> + mContext->mIndex]. + mHeapRuntimeCost)); + + PR_fprintf(inRequest->mFD, "\n"); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayCallsites); + break; + } + + /* + ** What do we follow? + */ + switch (aFollow) { + case ST_FOLLOW_SIBLINGS: + aCallsite = aCallsite->siblings; + break; + case ST_FOLLOW_PARENTS: + aCallsite = aCallsite->parent; + break; + default: + aCallsite = NULL; + retval = __LINE__; + REPORT_ERROR(__LINE__, displayCallsites); + break; + } + } + + /* + ** Terminate the table if we should. + */ + if (0 != headerDisplayed) { + PR_fprintf(inRequest->mFD, "\n\n"); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayCallsites); + } + + return retval; +} + +/* +** displayAllocationDetails +** +** Report what we know about the allocation. +** +** Returns !0 on error. +*/ +int +displayAllocationDetails(STRequest * inRequest, STAllocation * aAllocation) +{ + int retval = 0; + + if (NULL != aAllocation) { + uint32_t traverse = 0; + uint32_t bytesize = byteSize(&inRequest->mOptions, aAllocation); + uint32_t timeval = + aAllocation->mMaxTimeval - aAllocation->mMinTimeval; + uint32_t heapCost = aAllocation->mHeapRuntimeCost; + uint64_t weight64 = 0; + uint32_t cacheval = 0; + int displayRes = 0; + + weight64 = (uint64_t)(bytesize * timeval); + + PR_fprintf(inRequest->mFD, "

Allocation %u Details:

\n", + aAllocation->mRunIndex); + + PR_fprintf(inRequest->mFD, "
\n"); + PR_fprintf(inRequest->mFD, + "\n", + bytesize); + PR_fprintf(inRequest->mFD, + "\n", + ST_TIMEVAL_PRINTABLE(timeval)); + PR_fprintf(inRequest->mFD, + "\n", + weight64); + PR_fprintf(inRequest->mFD, + "\n", + ST_MICROVAL_PRINTABLE(heapCost)); + PR_fprintf(inRequest->mFD, "
Final Size:%u
Lifespan Seconds:" + ST_TIMEVAL_FORMAT "
Weight:%llu
Heap Operation Seconds:" + ST_MICROVAL_FORMAT "
\n"); + + /* + ** The events. + */ + + { + static const char* const headers[] = { + "Operation", "Size", "Seconds", "" + }; + + char caption[100]; + PR_snprintf(caption, sizeof(caption), "%u Life Event(s)", + aAllocation->mEventCount); + htmlStartTable(inRequest, NULL, "allocation-details", caption, headers, + sizeof(headers) / sizeof(headers[0])); + } + + for (traverse = 0; + traverse < aAllocation->mEventCount + && traverse < inRequest->mOptions.mListItemMax; traverse++) { + PR_fprintf(inRequest->mFD, "\n"); + + /* + ** count. + */ + PR_fprintf(inRequest->mFD, + "%u.\n", traverse + 1); + + /* + ** Operation. + */ + PR_fprintf(inRequest->mFD, ""); + switch (aAllocation->mEvents[traverse].mEventType) { + case TM_EVENT_CALLOC: + PR_fprintf(inRequest->mFD, "calloc"); + break; + case TM_EVENT_FREE: + PR_fprintf(inRequest->mFD, "free"); + break; + case TM_EVENT_MALLOC: + PR_fprintf(inRequest->mFD, "malloc"); + break; + case TM_EVENT_REALLOC: + PR_fprintf(inRequest->mFD, "realloc"); + break; + default: + retval = __LINE__; + REPORT_ERROR(__LINE__, displayAllocationDetails); + break; + } + PR_fprintf(inRequest->mFD, ""); + + /* + ** Size. + */ + PR_fprintf(inRequest->mFD, "%u\n", + aAllocation->mEvents[traverse].mHeapSize); + + /* + ** Timeval. + */ + cacheval = + aAllocation->mEvents[traverse].mTimeval - globals.mMinTimeval; + PR_fprintf(inRequest->mFD, + "" ST_TIMEVAL_FORMAT + "\n", ST_TIMEVAL_PRINTABLE(cacheval)); + + /* + ** Callsite backtrace. + ** Only relevant backtrace is for event 0 for now until + ** trace-malloc outputs proper callsites for all others. + */ + PR_fprintf(inRequest->mFD, "\n"); + if (0 == traverse) { + displayRes = + displayCallsites(inRequest, + aAllocation->mEvents[traverse].mCallsite, + ST_FOLLOW_PARENTS, 0, "event-stack", "", __LINE__); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayCallsite); + } + } + PR_fprintf(inRequest->mFD, "\n"); + + PR_fprintf(inRequest->mFD, "\n"); + } + PR_fprintf(inRequest->mFD, "\n"); + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayAllocationDetails); + } + + return retval; +} + +/* +** compareCallsites +** +** qsort callback. +** Compare the callsites as specified by the options. +** There must be NO equal callsites, unless they really are duplicates, +** this is so that a duplicate detector loop can +** simply skip sorted items until the callsite is different. +*/ +int +compareCallsites(const void *aSite1, const void *aSite2, void *aContext) +{ + int retval = 0; + STRequest *inRequest = (STRequest *) aContext; + + if (NULL != aSite1 && NULL != aSite2) { + tmcallsite *site1 = *((tmcallsite **) aSite1); + tmcallsite *site2 = *((tmcallsite **) aSite2); + + if (NULL != site1 && NULL != site2) { + STRun *run1 = CALLSITE_RUN(site1); + STRun *run2 = CALLSITE_RUN(site2); + + if (NULL != run1 && NULL != run2) { + STCallsiteStats *stats1 = + &(run1->mStats[inRequest->mContext->mIndex]); + STCallsiteStats *stats2 = + &(run2->mStats[inRequest->mContext->mIndex]); + + /* + ** Logic determined by pref/option. + */ + switch (inRequest->mOptions.mOrderBy) { + case ST_WEIGHT: + { + uint64_t weight164 = stats1->mWeight64; + uint64_t weight264 = stats2->mWeight64; + + if (weight164 < weight264) { + retval = __LINE__; + } + else if (weight164 > weight264) { + retval = -__LINE__; + } + } + break; + + case ST_SIZE: + { + uint32_t size1 = stats1->mSize; + uint32_t size2 = stats2->mSize; + + if (size1 < size2) { + retval = __LINE__; + } + else if (size1 > size2) { + retval = -__LINE__; + } + } + break; + + case ST_TIMEVAL: + { + uint64_t timeval164 = stats1->mTimeval64; + uint64_t timeval264 = stats2->mTimeval64; + + if (timeval164 < timeval264) { + retval = __LINE__; + } + else if (timeval164 > timeval264) { + retval = -__LINE__; + } + } + break; + + case ST_COUNT: + { + uint32_t count1 = stats1->mCompositeCount; + uint32_t count2 = stats2->mCompositeCount; + + if (count1 < count2) { + retval = __LINE__; + } + else if (count1 > count2) { + retval = -__LINE__; + } + } + break; + + case ST_HEAPCOST: + { + uint32_t cost1 = stats1->mHeapRuntimeCost; + uint32_t cost2 = stats2->mHeapRuntimeCost; + + if (cost1 < cost2) { + retval = __LINE__; + } + else if (cost1 > cost2) { + retval = -__LINE__; + } + } + break; + + default: + { + REPORT_ERROR(__LINE__, compareAllocations); + } + break; + } + + /* + ** If the return value is still zero, do a pointer compare. + ** This makes sure we return zero, only iff the same object. + */ + if (0 == retval) { + if (stats1 < stats2) { + retval = __LINE__; + } + else if (stats1 > stats2) { + retval = -__LINE__; + } + } + } + } + } + + return retval; +} + +/* +** displayTopCallsites +** +** Given a list of callsites, sort it, and output skipping dups. +** The passed in callsite array is side effected, as in that it will come +** back sorted. This function will not release the array. +** +** Note: If the stamp passed in is non zero, then all callsites must match. +** If the stamp is zero, all callsites must match global sorted run stamp. +** +** Returns !0 on error. +*/ +int +displayTopCallsites(STRequest * inRequest, tmcallsite ** aCallsites, + uint32_t aCallsiteCount, uint32_t aStamp, + const char* id, + const char* caption, + int aRealName) +{ + int retval = 0; + + if (NULL != aCallsites && 0 < aCallsiteCount) { + uint32_t traverse = 0; + STRun *run = NULL; + tmcallsite *site = NULL; + int headerDisplayed = 0; + uint32_t displayed = 0; + + /* + ** Fixup the stamp. + */ + if (0 == aStamp && NULL != inRequest->mContext->mSortedRun) { + aStamp = + inRequest->mContext->mSortedRun->mStats[inRequest->mContext-> + mIndex].mStamp; + } + + /* + ** Sort the things. + */ + NS_QuickSort(aCallsites, aCallsiteCount, sizeof(tmcallsite *), + compareCallsites, inRequest); + + /* + ** Time for output. + */ + for (traverse = 0; + traverse < aCallsiteCount + && inRequest->mOptions.mListItemMax > displayed; traverse++) { + site = aCallsites[traverse]; + run = CALLSITE_RUN(site); + + /* + ** Only if the same stamp.... + */ + if (aStamp == run->mStats[inRequest->mContext->mIndex].mStamp) { + /* + ** We got a header yet? + */ + if (0 == headerDisplayed) { + static const char* const headers[] = { + "Rank", + "Callsite", + "Size", + "Seconds", + "Weight", + "Object Count", + "C.H. Operation (sec)" + }; + headerDisplayed = __LINE__; + + htmlStartTable(inRequest, NULL, id, caption, headers, + sizeof(headers) / sizeof(headers[0])); + } + + displayed++; + + PR_fprintf(inRequest->mFD, "\n"); + + /* + ** Rank. + */ + PR_fprintf(inRequest->mFD, + "%u\n", displayed); + + /* + ** Method. + */ + PR_fprintf(inRequest->mFD, ""); + htmlCallsiteAnchor(inRequest, site, NULL, aRealName); + PR_fprintf(inRequest->mFD, "\n"); + + /* + ** Size. + */ + PR_fprintf(inRequest->mFD, + "%u\n", + run->mStats[inRequest->mContext->mIndex].mSize); + + /* + ** Timeval. + */ + PR_fprintf(inRequest->mFD, + "" ST_TIMEVAL_FORMAT + "\n", + ST_TIMEVAL_PRINTABLE64(run-> + mStats[inRequest->mContext-> + mIndex].mTimeval64)); + + /* + ** Weight. + */ + PR_fprintf(inRequest->mFD, + "%llu\n", + run->mStats[inRequest->mContext->mIndex]. + mWeight64); + + /* + ** Allocation object count. + */ + PR_fprintf(inRequest->mFD, + "%u\n", + run->mStats[inRequest->mContext->mIndex]. + mCompositeCount); + + /* + ** Heap operation seconds. + */ + PR_fprintf(inRequest->mFD, + "" ST_MICROVAL_FORMAT + "\n", + ST_MICROVAL_PRINTABLE(run-> + mStats[inRequest->mContext-> + mIndex]. + mHeapRuntimeCost)); + + PR_fprintf(inRequest->mFD, "\n"); + + + if (inRequest->mOptions.mListItemMax > displayed) { + /* + ** Skip any dups. + */ + while (((traverse + 1) < aCallsiteCount) + && (site == aCallsites[traverse + 1])) { + traverse++; + } + } + } + } + + /* + ** We need to terminate anything? + */ + if (0 != headerDisplayed) { + PR_fprintf(inRequest->mFD, "\n"); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayTopCallsites); + } + + return retval; +} + +/* +** displayCallsiteDetails +** +** The callsite specific report. +** Try to report what we know. +** This one hits a little harder than the rest. +** +** Returns !0 on error. +*/ +int +displayCallsiteDetails(STRequest * inRequest, tmcallsite * aCallsite) +{ + int retval = 0; + + if (NULL != aCallsite && NULL != aCallsite->method) { + STRun *sortedRun = NULL; + STRun *thisRun = CALLSITE_RUN(aCallsite); + const char *sourceFile = NULL; + + sourceFile = resolveSourceFile(aCallsite->method); + + PR_fprintf(inRequest->mFD, "
\n"); + if (sourceFile) { + PR_fprintf(inRequest->mFD, "%s", + tmmethodnode_name(aCallsite->method)); + PR_fprintf(inRequest->mFD, + " [%s:%u]", + aCallsite->method->sourcefile, + aCallsite->method->linenumber, sourceFile, + aCallsite->method->linenumber); + } + else { + PR_fprintf(inRequest->mFD, + "

%s+%u(%u) Callsite Details:

\n", + tmmethodnode_name(aCallsite->method), + aCallsite->offset, (uint32_t) aCallsite->entry.key); + } + + PR_fprintf(inRequest->mFD, "
\n\n"); + PR_fprintf(inRequest->mFD, "
\n"); + PR_fprintf(inRequest->mFD, + "\n", + thisRun->mStats[inRequest->mContext->mIndex].mSize); + PR_fprintf(inRequest->mFD, + "\n", + ST_TIMEVAL_PRINTABLE64(thisRun-> + mStats[inRequest->mContext->mIndex]. + mTimeval64)); + PR_fprintf(inRequest->mFD, + "\n", + thisRun->mStats[inRequest->mContext->mIndex].mWeight64); + PR_fprintf(inRequest->mFD, + "\n", + thisRun->mStats[inRequest->mContext->mIndex]. + mCompositeCount); + PR_fprintf(inRequest->mFD, + "\n", + ST_MICROVAL_PRINTABLE(thisRun-> + mStats[inRequest->mContext->mIndex]. + mHeapRuntimeCost)); + PR_fprintf(inRequest->mFD, "
Composite Byte Size:%u
Composite Seconds:" + ST_TIMEVAL_FORMAT "
Composite Weight:%llu
Heap Object Count:%u
Heap Operation Seconds:" + ST_MICROVAL_FORMAT "
\n\n"); + + /* + ** Kids (callsites we call): + */ + if (NULL != aCallsite->kids && NULL != aCallsite->kids->method) { + int displayRes = 0; + uint32_t siteCount = 0; + tmcallsite **sites = NULL; + + /* + ** Collect the kid sibling callsites. + ** Doing it this way sorts them for relevance. + */ + siteCount = + callsiteArrayFromCallsite(&sites, 0, aCallsite->kids, + ST_FOLLOW_SIBLINGS); + if (0 != siteCount && NULL != sites) { + /* + ** Got something to show. + */ + displayRes = + displayTopCallsites(inRequest, sites, siteCount, 0, + "callsites", + "Children Callsites", + __LINE__); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayTopCallsites); + } + + /* + ** Done with array. + */ + free(sites); + sites = NULL; + } + } + + /* + ** Parents (those who call us): + */ + if (NULL != aCallsite->parent && NULL != aCallsite->parent->method) { + int displayRes = 0; + + displayRes = + displayCallsites(inRequest, aCallsite->parent, + ST_FOLLOW_PARENTS, 0, "caller-stack", "Caller stack", + __LINE__); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayCallsites); + } + } + + /* + ** Allocations we did. + ** Simply harvest our own run. + */ + sortedRun = createRun(inRequest->mContext, 0); + if (NULL != sortedRun) { + int harvestRes = 0; + + harvestRes = + harvestRun(CALLSITE_RUN(aCallsite), sortedRun, + &inRequest->mOptions, inRequest->mContext); + if (0 == harvestRes) { + if (0 != sortedRun->mAllocationCount) { + int sortRes = 0; + + sortRes = sortRun(&inRequest->mOptions, sortedRun); + if (0 == sortRes) { + int displayRes = 0; + + displayRes = + displayTopAllocations(inRequest, sortedRun, + "allocations", + "Allocations", + 0); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayTopAllocations); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, sortRun); + } + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, harvestRun); + } + + /* + ** Done with the run. + */ + freeRun(sortedRun); + sortedRun = NULL; + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, createRun); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayCallsiteDetails); + } + + return retval; +} + +#if ST_WANT_GRAPHS +/* +** graphFootprint +** +** Output a PNG graph of the memory usage of the run. +** +** Draw the graph within these boundaries. +** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN +** +** Returns !0 on failure. +*/ +int +graphFootprint(STRequest * inRequest, STRun * aRun) +{ + int retval = 0; + + if (NULL != aRun) { + uint32_t *YData = NULL; + uint32_t YDataArray[STGD_SPACE_X]; + uint32_t traverse = 0; + uint32_t timeval = 0; + uint32_t loop = 0; + PRBool underLock = PR_FALSE; + + /* + ** Decide if this is custom or we should use the cache. + */ + if (aRun == inRequest->mContext->mSortedRun) { + YData = inRequest->mContext->mFootprintYData; + underLock = PR_TRUE; + } + else { + YData = YDataArray; + } + + /* + ** Protect the shared data so that only one client has access to it + ** at any given time. + */ + if (PR_FALSE != underLock) { + PR_Lock(inRequest->mContext->mImageLock); + } + + /* + ** Only do the computations if we aren't cached already. + */ + if (YData != inRequest->mContext->mFootprintYData + || PR_FALSE == inRequest->mContext->mFootprintCached) { + memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X); + + /* + ** Initialize our Y data. + ** Pretty brutal loop here.... + */ + for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X; + traverse++) { + /* + ** Compute what timeval this Y data lands in. + */ + timeval = + ((traverse * + (globals.mMaxTimeval - + globals.mMinTimeval)) / STGD_SPACE_X) + + globals.mMinTimeval; + + /* + ** Loop over the run. + ** Should an allocation contain said Timeval, we're good. + */ + for (loop = 0; loop < aRun->mAllocationCount; loop++) { + if (timeval >= aRun->mAllocations[loop]->mMinTimeval + && timeval <= aRun->mAllocations[loop]->mMaxTimeval) { + YData[traverse] += + byteSize(&inRequest->mOptions, + aRun->mAllocations[loop]); + } + } + } + + /* + ** Did we cache this? + */ + if (YData == inRequest->mContext->mFootprintYData) { + inRequest->mContext->mFootprintCached = PR_TRUE; + } + } + + /* + ** Done with the lock. + */ + if (PR_FALSE != underLock) { + PR_Unlock(inRequest->mContext->mImageLock); + } + + if (0 == retval) { + uint32_t minMemory = (uint32_t) - 1; + uint32_t maxMemory = 0; + int transparent = 0; + gdImagePtr graph = NULL; + + /* + ** Go through and find the minimum and maximum sizes. + */ + for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { + if (YData[traverse] < minMemory) { + minMemory = YData[traverse]; + } + if (YData[traverse] > maxMemory) { + maxMemory = YData[traverse]; + } + } + + /* + ** We can now draw the graph. + */ + graph = createGraph(&transparent); + if (NULL != graph) { + gdSink theSink; + int red = 0; + int x1 = 0; + int y1 = 0; + int x2 = 0; + int y2 = 0; + uint32_t percents[11] = + { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; + char *timevals[11]; + char *bytes[11]; + char timevalSpace[11][32]; + char byteSpace[11][32]; + int legendColors[1]; + const char *legends[1] = { "Memory in Use" }; + uint32_t cached = 0; + + /* + ** Figure out what the labels will say. + */ + for (traverse = 0; traverse < 11; traverse++) { + timevals[traverse] = timevalSpace[traverse]; + bytes[traverse] = byteSpace[traverse]; + + cached = + ((globals.mMaxTimeval - + globals.mMinTimeval) * percents[traverse]) / 100; + PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT, + ST_TIMEVAL_PRINTABLE(cached)); + PR_snprintf(bytes[traverse], 32, "%u", + ((maxMemory - + minMemory) * percents[traverse]) / 100); + } + + red = gdImageColorAllocate(graph, 255, 0, 0); + legendColors[0] = red; + + drawGraph(graph, -1, "Memory Footprint Over Time", "Seconds", + "Bytes", 11, percents, (const char **) timevals, 11, + percents, (const char **) bytes, 1, legendColors, + legends); + + if (maxMemory != minMemory) { + int64_t in64 = 0; + int64_t ydata64 = 0; + int64_t spacey64 = 0; + int64_t mem64 = 0; + int32_t in32 = 0; + + /* + ** Go through our Y data and mark it up. + */ + for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { + x1 = traverse + STGD_MARGIN; + y1 = STGD_HEIGHT - STGD_MARGIN; + + /* + ** Need to do this math in 64 bits. + */ + ydata64 = (int64_t)YData[traverse]; + spacey64 = (int64_t)STGD_SPACE_Y; + mem64 = (int64_t)(maxMemory - minMemory); + + in64 = ydata64 * spacey64; + in64 /= mem64; + in32 = int32_t(in64); + + x2 = x1; + y2 = y1 - in32; + + gdImageLine(graph, x1, y1, x2, y2, red); + } + } + + + theSink.context = inRequest->mFD; + theSink.sink = pngSink; + gdImagePngToSink(graph, &theSink); + + gdImageDestroy(graph); + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, createGraph); + } + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, graphFootprint); + } + + return retval; +} +#endif /* ST_WANT_GRAPHS */ + +#if ST_WANT_GRAPHS +/* +** graphTimeval +** +** Output a PNG graph of when the memory is allocated. +** +** Draw the graph within these boundaries. +** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN +** +** Returns !0 on failure. +*/ +int +graphTimeval(STRequest * inRequest, STRun * aRun) +{ + int retval = 0; + + if (NULL != aRun) { + uint32_t *YData = NULL; + uint32_t YDataArray[STGD_SPACE_X]; + uint32_t traverse = 0; + uint32_t timeval = globals.mMinTimeval; + uint32_t loop = 0; + PRBool underLock = PR_FALSE; + + /* + ** Decide if this is custom or we should use the global cache. + */ + if (aRun == inRequest->mContext->mSortedRun) { + YData = inRequest->mContext->mTimevalYData; + underLock = PR_TRUE; + } + else { + YData = YDataArray; + } + + /* + ** Protect the shared data so that only one client has access to it + ** at any given time. + */ + if (PR_FALSE != underLock) { + PR_Lock(inRequest->mContext->mImageLock); + } + + /* + ** Only do the computations if we aren't cached already. + */ + if (YData != inRequest->mContext->mTimevalYData + || PR_FALSE == inRequest->mContext->mTimevalCached) { + uint32_t prevTimeval = 0; + + memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X); + + /* + ** Initialize our Y data. + ** Pretty brutal loop here.... + */ + for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X; + traverse++) { + /* + ** Compute what timeval this Y data lands in. + */ + prevTimeval = timeval; + timeval = + ((traverse * + (globals.mMaxTimeval - + globals.mMinTimeval)) / STGD_SPACE_X) + + globals.mMinTimeval; + + /* + ** Loop over the run. + ** Should an allocation have been allocated between + ** prevTimeval and timeval.... + */ + for (loop = 0; loop < aRun->mAllocationCount; loop++) { + if (prevTimeval < aRun->mAllocations[loop]->mMinTimeval + && timeval >= aRun->mAllocations[loop]->mMinTimeval) { + YData[traverse] += + byteSize(&inRequest->mOptions, + aRun->mAllocations[loop]); + } + } + } + + /* + ** Did we cache this? + */ + if (YData == inRequest->mContext->mTimevalYData) { + inRequest->mContext->mTimevalCached = PR_TRUE; + } + } + + /* + ** Done with the lock. + */ + if (PR_FALSE != underLock) { + PR_Unlock(inRequest->mContext->mImageLock); + } + + if (0 == retval) { + uint32_t minMemory = (uint32_t) - 1; + uint32_t maxMemory = 0; + int transparent = 0; + gdImagePtr graph = NULL; + + /* + ** Go through and find the minimum and maximum sizes. + */ + for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { + if (YData[traverse] < minMemory) { + minMemory = YData[traverse]; + } + if (YData[traverse] > maxMemory) { + maxMemory = YData[traverse]; + } + } + + /* + ** We can now draw the graph. + */ + graph = createGraph(&transparent); + if (NULL != graph) { + gdSink theSink; + int red = 0; + int x1 = 0; + int y1 = 0; + int x2 = 0; + int y2 = 0; + uint32_t percents[11] = + { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; + char *timevals[11]; + char *bytes[11]; + char timevalSpace[11][32]; + char byteSpace[11][32]; + int legendColors[1]; + const char *legends[1] = { "Memory Allocated" }; + uint32_t cached = 0; + + /* + ** Figure out what the labels will say. + */ + for (traverse = 0; traverse < 11; traverse++) { + timevals[traverse] = timevalSpace[traverse]; + bytes[traverse] = byteSpace[traverse]; + + cached = + ((globals.mMaxTimeval - + globals.mMinTimeval) * percents[traverse]) / 100; + PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT, + ST_TIMEVAL_PRINTABLE(cached)); + PR_snprintf(bytes[traverse], 32, "%u", + ((maxMemory - + minMemory) * percents[traverse]) / 100); + } + + red = gdImageColorAllocate(graph, 255, 0, 0); + legendColors[0] = red; + + drawGraph(graph, -1, "Allocation Times", "Seconds", "Bytes", + 11, percents, (const char **) timevals, 11, + percents, (const char **) bytes, 1, legendColors, + legends); + + if (maxMemory != minMemory) { + int64_t in64 = 0; + int64_t ydata64 = 0; + int64_t spacey64 = 0; + int64_t mem64 = 0; + int32_t in32 = 0; + + /* + ** Go through our Y data and mark it up. + */ + for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { + x1 = traverse + STGD_MARGIN; + y1 = STGD_HEIGHT - STGD_MARGIN; + + /* + ** Need to do this math in 64 bits. + */ + ydata64 = (int64_t)YData[traverse]; + spacey64 = (int64_t)STGD_SPACE_Y; + mem64 = (int64_t)(maxMemory - minMemory); + + in64 = ydata64 * spacey64; + in64 /= mem64; + in32 = int32_t(in64); + + x2 = x1; + y2 = y1 - in32; + + gdImageLine(graph, x1, y1, x2, y2, red); + } + } + + + theSink.context = inRequest->mFD; + theSink.sink = pngSink; + gdImagePngToSink(graph, &theSink); + + gdImageDestroy(graph); + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, createGraph); + } + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, graphTimeval); + } + + return retval; +} +#endif /* ST_WANT_GRAPHS */ + +#if ST_WANT_GRAPHS +/* +** graphLifespan +** +** Output a PNG graph of how long memory lived. +** +** Draw the graph within these boundaries. +** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN +** +** Returns !0 on failure. +*/ +int +graphLifespan(STRequest * inRequest, STRun * aRun) +{ + int retval = 0; + + if (NULL != aRun) { + uint32_t *YData = NULL; + uint32_t YDataArray[STGD_SPACE_X]; + uint32_t traverse = 0; + uint32_t timeval = 0; + uint32_t loop = 0; + PRBool underLock = PR_FALSE; + + /* + ** Decide if this is custom or we should use the global cache. + */ + if (aRun == inRequest->mContext->mSortedRun) { + YData = inRequest->mContext->mLifespanYData; + underLock = PR_TRUE; + } + else { + YData = YDataArray; + } + + /* + ** Protect the shared data so that only one client has access to it + ** at any given time. + */ + if (PR_FALSE != underLock) { + PR_Lock(inRequest->mContext->mImageLock); + } + + /* + ** Only do the computations if we aren't cached already. + */ + if (YData != inRequest->mContext->mLifespanYData + || PR_FALSE == inRequest->mContext->mLifespanCached) { + uint32_t prevTimeval = 0; + uint32_t lifespan = 0; + + memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X); + + /* + ** Initialize our Y data. + ** Pretty brutal loop here.... + */ + for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X; + traverse++) { + /* + ** Compute what timeval this Y data lands in. + */ + prevTimeval = timeval; + timeval = + (traverse * (globals.mMaxTimeval - globals.mMinTimeval)) / + STGD_SPACE_X; + + /* + ** Loop over the run. + ** Should an allocation have lived between + ** prevTimeval and timeval.... + */ + for (loop = 0; loop < aRun->mAllocationCount; loop++) { + lifespan = + aRun->mAllocations[loop]->mMaxTimeval - + aRun->mAllocations[loop]->mMinTimeval; + + if (prevTimeval < lifespan && timeval >= lifespan) { + YData[traverse] += + byteSize(&inRequest->mOptions, + aRun->mAllocations[loop]); + } + } + } + + /* + ** Did we cache this? + */ + if (YData == inRequest->mContext->mLifespanYData) { + inRequest->mContext->mLifespanCached = PR_TRUE; + } + } + + /* + ** Done with the lock. + */ + if (PR_FALSE != underLock) { + PR_Unlock(inRequest->mContext->mImageLock); + } + + if (0 == retval) { + uint32_t minMemory = (uint32_t) - 1; + uint32_t maxMemory = 0; + int transparent = 0; + gdImagePtr graph = NULL; + + /* + ** Go through and find the minimum and maximum sizes. + */ + for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { + if (YData[traverse] < minMemory) { + minMemory = YData[traverse]; + } + if (YData[traverse] > maxMemory) { + maxMemory = YData[traverse]; + } + } + + /* + ** We can now draw the graph. + */ + graph = createGraph(&transparent); + if (NULL != graph) { + gdSink theSink; + int red = 0; + int x1 = 0; + int y1 = 0; + int x2 = 0; + int y2 = 0; + uint32_t percents[11] = + { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; + char *timevals[11]; + char *bytes[11]; + char timevalSpace[11][32]; + char byteSpace[11][32]; + int legendColors[1]; + const char *legends[1] = { "Live Memory" }; + uint32_t cached = 0; + + /* + ** Figure out what the labels will say. + */ + for (traverse = 0; traverse < 11; traverse++) { + timevals[traverse] = timevalSpace[traverse]; + bytes[traverse] = byteSpace[traverse]; + + cached = + ((globals.mMaxTimeval - + globals.mMinTimeval) * percents[traverse]) / 100; + PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT, + ST_TIMEVAL_PRINTABLE(cached)); + PR_snprintf(bytes[traverse], 32, "%u", + ((maxMemory - + minMemory) * percents[traverse]) / 100); + } + + red = gdImageColorAllocate(graph, 255, 0, 0); + legendColors[0] = red; + + drawGraph(graph, -1, "Allocation Lifespans", "Lifespan", + "Bytes", 11, percents, (const char **) timevals, 11, + percents, (const char **) bytes, 1, legendColors, + legends); + + if (maxMemory != minMemory) { + int64_t in64 = 0; + int64_t ydata64 = 0; + int64_t spacey64 = 0; + int64_t mem64 = 0; + int32_t in32 = 0; + + /* + ** Go through our Y data and mark it up. + */ + for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { + x1 = traverse + STGD_MARGIN; + y1 = STGD_HEIGHT - STGD_MARGIN; + + /* + ** Need to do this math in 64 bits. + */ + ydata64 = (int64_t)YData[traverse]; + spacey64 = (int64_t)STGD_SPACE_Y; + mem64 = (int64_t)(maxMemory - minMemory); + + in64 = ydata64 * spacey64; + in64 /= mem64; + in32 = int32_t(in64); + + x2 = x1; + y2 = y1 - in32; + + gdImageLine(graph, x1, y1, x2, y2, red); + } + } + + + theSink.context = inRequest->mFD; + theSink.sink = pngSink; + gdImagePngToSink(graph, &theSink); + + gdImageDestroy(graph); + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, createGraph); + } + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, graphLifespan); + } + + return retval; +} +#endif /* ST_WANT_GRAPHS */ + +#if ST_WANT_GRAPHS +/* +** graphWeight +** +** Output a PNG graph of Allocations by Weight +** +** Draw the graph within these boundaries. +** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN +** +** Returns !0 on failure. +*/ +int +graphWeight(STRequest * inRequest, STRun * aRun) +{ + int retval = 0; + + if (NULL != aRun) { + uint64_t *YData64 = NULL; + uint64_t YDataArray64[STGD_SPACE_X]; + uint32_t traverse = 0; + uint32_t timeval = globals.mMinTimeval; + uint32_t loop = 0; + PRBool underLock = PR_FALSE; + + /* + ** Decide if this is custom or we should use the global cache. + */ + if (aRun == inRequest->mContext->mSortedRun) { + YData64 = inRequest->mContext->mWeightYData64; + underLock = PR_TRUE; + } + else { + YData64 = YDataArray64; + } + + /* + ** Protect the shared data so that only one client has access to it + ** at any given time. + */ + if (PR_FALSE != underLock) { + PR_Lock(inRequest->mContext->mImageLock); + } + + /* + ** Only do the computations if we aren't cached already. + */ + if (YData64 != inRequest->mContext->mWeightYData64 + || PR_FALSE == inRequest->mContext->mWeightCached) { + uint32_t prevTimeval = 0; + + memset(YData64, 0, sizeof(uint64_t) * STGD_SPACE_X); + + /* + ** Initialize our Y data. + ** Pretty brutal loop here.... + */ + for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X; + traverse++) { + /* + ** Compute what timeval this Y data lands in. + */ + prevTimeval = timeval; + timeval = + ((traverse * + (globals.mMaxTimeval - + globals.mMinTimeval)) / STGD_SPACE_X) + + globals.mMinTimeval; + + /* + ** Loop over the run. + ** Should an allocation have been allocated between + ** prevTimeval and timeval.... + */ + for (loop = 0; loop < aRun->mAllocationCount; loop++) { + if (prevTimeval < aRun->mAllocations[loop]->mMinTimeval + && timeval >= aRun->mAllocations[loop]->mMinTimeval) { + uint64_t size64 = 0; + uint64_t lifespan64 = 0; + uint64_t weight64 = 0; + + size64 = byteSize(&inRequest->mOptions, + aRun->mAllocations[loop]); + lifespan64 = aRun->mAllocations[loop]->mMaxTimeval - + aRun->mAllocations[loop]->mMinTimeval; + weight64 = size64 * lifespan64; + + YData64[traverse] += weight64; + } + } + } + + /* + ** Did we cache this? + */ + if (YData64 == inRequest->mContext->mWeightYData64) { + inRequest->mContext->mWeightCached = PR_TRUE; + } + } + + /* + ** Done with the lock. + */ + if (PR_FALSE != underLock) { + PR_Unlock(inRequest->mContext->mImageLock); + } + + if (0 == retval) { + uint64_t minWeight64 = (0xFFFFFFFFLL << 32) + 0xFFFFFFFFLL; + uint64_t maxWeight64 = 0; + int transparent = 0; + gdImagePtr graph = NULL; + + /* + ** Go through and find the minimum and maximum weights. + */ + for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { + if (YData64[traverse] < minWeight64) { + minWeight64 = YData64[traverse]; + } + if (YData64[traverse] > maxWeight64) { + maxWeight64 = YData64[traverse]; + } + } + + /* + ** We can now draw the graph. + */ + graph = createGraph(&transparent); + if (NULL != graph) { + gdSink theSink; + int red = 0; + int x1 = 0; + int y1 = 0; + int x2 = 0; + int y2 = 0; + uint32_t percents[11] = + { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; + char *timevals[11]; + char *bytes[11]; + char timevalSpace[11][32]; + char byteSpace[11][32]; + int legendColors[1]; + const char *legends[1] = { "Memory Weight" }; + uint64_t percent64 = 0; + uint64_t result64 = 0; + + uint32_t cached = 0; + uint64_t hundred64 = 100; + + /* + ** Figure out what the labels will say. + */ + for (traverse = 0; traverse < 11; traverse++) { + timevals[traverse] = timevalSpace[traverse]; + bytes[traverse] = byteSpace[traverse]; + + cached = + ((globals.mMaxTimeval - + globals.mMinTimeval) * percents[traverse]) / 100; + PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT, + ST_TIMEVAL_PRINTABLE(cached)); + + result64 = (maxWeight64 - minWeight64) * percents[traverse]; + result64 /= hundred64; + PR_snprintf(bytes[traverse], 32, "%llu", result64); + } + + red = gdImageColorAllocate(graph, 255, 0, 0); + legendColors[0] = red; + + drawGraph(graph, -1, "Allocation Weights", "Seconds", + "Weight", 11, percents, (const char **) timevals, + 11, percents, (const char **) bytes, 1, + legendColors, legends); + + if (maxWeight64 != minWeight64) { + int64_t in64 = 0; + int64_t spacey64 = 0; + int64_t weight64 = 0; + int32_t in32 = 0; + + /* + ** Go through our Y data and mark it up. + */ + for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { + x1 = traverse + STGD_MARGIN; + y1 = STGD_HEIGHT - STGD_MARGIN; + + /* + ** Need to do this math in 64 bits. + */ + spacey64 = (int64_t)STGD_SPACE_Y; + weight64 = maxWeight64 - minWeight64; + + in64 = YData64[traverse] * spacey64; + in64 /= weight64; + in32 = int32_t(in64); + + x2 = x1; + y2 = y1 - in32; + + gdImageLine(graph, x1, y1, x2, y2, red); + } + } + + + theSink.context = inRequest->mFD; + theSink.sink = pngSink; + gdImagePngToSink(graph, &theSink); + + gdImageDestroy(graph); + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, createGraph); + } + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, graphWeight); + } + + return retval; +} +#endif /* ST_WANT_GRAPHS */ + +#define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \ + { \ + uint32_t convert = (uint32_t)outOptions->m##option_name; \ + \ + getDataPRUint32(inFormData, #option_name, 1, &convert, 1); \ + outOptions->m##option_name = (PRBool)convert; \ + } +#define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \ + getDataString(inFormData, #option_name, 1, outOptions->m##option_name, sizeof(outOptions->m##option_name)); +#define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ + { \ + uint32_t loop = 0; \ + uint32_t found = 0; \ + char buffer[ST_OPTION_STRING_MAX]; \ + \ + for(loop = 0; loop < array_size; loop++) \ + { \ + buffer[0] = '\0'; \ + getDataString(inFormData, #option_name, (loop + 1), buffer, sizeof(buffer)); \ + \ + if('\0' != buffer[0]) \ + { \ + PR_snprintf(outOptions->m##option_name[found], sizeof(outOptions->m##option_name[found]), "%s", buffer); \ + found++; \ + } \ + } \ + \ + for(; found < array_size; found++) \ + { \ + outOptions->m##option_name[found][0] = '\0'; \ + } \ + } +#define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */ +#define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ + getDataPRUint32(inFormData, #option_name, 1, &outOptions->m##option_name, multiplier); +#define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ + { \ + uint64_t mul64 = multiplier; \ + \ + getDataPRUint64(inFormData, #option_name, 1, &outOptions->m##option_name##64, mul64); \ + } +/* +** fillOptions +** +** Given an appropriate hexcaped string, distill the option values +** and fill the given STOption struct. +** +** Note that the options passed in are not touched UNLESS there is +** a replacement found in the form data. +*/ +void +fillOptions(STOptions * outOptions, const FormData * inFormData) +{ + if (NULL != outOptions && NULL != inFormData) { + +#include "stoptions.h" + + /* + ** Special sanity check here for some options that need data validation. + */ + if (!outOptions->mCategoryName[0] + || !findCategoryNode(outOptions->mCategoryName, &globals)) { + PR_snprintf(outOptions->mCategoryName, + sizeof(outOptions->mCategoryName), "%s", + ST_ROOT_CATEGORY_NAME); + } + } +} + + +void +displayOptionString(STRequest * inRequest, + const char *option_name, + const char *option_genre, + const char *default_value, + const char *option_help, const char *value) +{ +#if 0 + PR_fprintf(inRequest->mFD, "\n", option_name); +#endif + PR_fprintf(inRequest->mFD, "
\n"); + PR_fprintf(inRequest->mFD, "

%s

\n", option_name); + PR_fprintf(inRequest->mFD, + "\n", + option_name, value); + PR_fprintf(inRequest->mFD, + "

Default value is \"%s\".

\n

%s

\n", + default_value, option_help); + PR_fprintf(inRequest->mFD, "
\n"); +} + +static void +displayOptionStringArray(STRequest * inRequest, + const char *option_name, + const char *option_genre, + uint32_t array_size, + const char *option_help, const char values[5] + [ST_OPTION_STRING_MAX]) +{ + /* values should not be a fixed length! */ + PR_ASSERT(array_size == 5); +#if 0 + PR_fprintf(inRequest->mFD, "\n", option_name); +#endif + PR_fprintf(inRequest->mFD, "
\n"); + PR_fprintf(inRequest->mFD, "

%s

\n", option_name); { + uint32_t loop = 0; + + for (loop = 0; loop < array_size; loop++) { + PR_fprintf(inRequest->mFD, + "
\n", + option_name, values[loop]); + } + } + PR_fprintf(inRequest->mFD, + "

Up to %u occurrences allowed.

\n

%s

\n", + array_size, option_help); + PR_fprintf(inRequest->mFD, "
\n"); +} + +static void +displayOptionInt(STRequest * inRequest, + const char *option_name, + const char *option_genre, + uint32_t default_value, + uint32_t multiplier, const char *option_help, uint32_t value) +{ +#if 0 + PR_fprintf(inRequest->mFD, "\n", option_name); +#endif + PR_fprintf(inRequest->mFD, "
\n"); + PR_fprintf(inRequest->mFD, "

%s

\n", option_name); + PR_fprintf(inRequest->mFD, + "\n", option_name, + value / multiplier); + PR_fprintf(inRequest->mFD, + "

Default value is %u.

\n

%s

\n", + default_value, option_help); + PR_fprintf(inRequest->mFD, "
\n"); +} + +static void +displayOptionInt64(STRequest * inRequest, + const char *option_name, + const char *option_genre, + uint64_t default_value, + uint64_t multiplier, + const char *option_help, uint64_t value) +{ +#if 0 + PR_fprintf(inRequest->mFD, "\n", option_name); +#endif + PR_fprintf(inRequest->mFD, "
\n"); + PR_fprintf(inRequest->mFD, "

%s

\n", option_name); { + uint64_t def64 = default_value; + uint64_t mul64 = multiplier; + uint64_t div64; + + div64 = value / mul64; + PR_fprintf(inRequest->mFD, + "\n", + option_name, div64); + PR_fprintf(inRequest->mFD, + "

Default value is %llu.

\n

%s

\n", + def64, option_help); + } + PR_fprintf(inRequest->mFD, "
\n"); +} + +/* +** displaySettings +** +** Present the settings for change during execution. +*/ +void +displaySettings(STRequest * inRequest) +{ + int applyRes = 0; + + /* + ** We've got a form to create. + */ + PR_fprintf(inRequest->mFD, "
\n"); + /* + ** Respect newlines in help text. + */ +#if 0 + PR_fprintf(inRequest->mFD, "
\n");
+#endif
+#define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
+    displayOptionBool(option_name, option_genre, option_help)
+#define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
+    displayOptionString(inRequest, #option_name, #option_genre, default_value, option_help, inRequest->mOptions.m##option_name);
+#define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
+    displayOptionStringArray(inRequest, #option_name, #option_genre, array_size, option_help, inRequest->mOptions.m##option_name);
+#define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help)  /* no implementation */
+#define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
+    displayOptionInt(inRequest, #option_name, #option_genre, default_value, multiplier, option_help, inRequest->mOptions.m##option_name);
+#define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
+    displayOptionInt64(inRequest, #option_name, #option_genre, default_value, multiplier, option_help, inRequest->mOptions.m##option_name##64);
+#include "stoptions.h"
+    /*
+     **  Give a submit/reset button, obligatory.
+     **  Done respecting newlines in help text.
+     */
+    PR_fprintf(inRequest->mFD,
+               " \n");
+#if 0
+    PR_fprintf(inRequest->mFD, "
\n"); +#endif + /* + ** Done with form. + */ + PR_fprintf(inRequest->mFD, "
\n"); +} + +int +handleLocalFile(STRequest * inRequest, const char *aFilename) +{ + static const char *const local_files[] = { + "spacetrace.css", + }; + static const size_t local_file_count = + sizeof(local_files) / sizeof(local_files[0]); + size_t i; + + for (i = 0; i < local_file_count; i++) { + if (0 == strcmp(local_files[i], aFilename)) + return 1; + } + return 0; +} + +/* +** displayFile +** +** reads a file from disk, and streams it to the request +*/ +int +displayFile(STRequest * inRequest, const char *aFilename) +{ + PRFileDesc *inFd; + const char *filepath = + PR_smprintf("res%c%s", PR_GetDirectorySeparator(), aFilename); + char buffer[2048]; + int32_t readRes; + + inFd = PR_Open(filepath, PR_RDONLY, PR_IRUSR); + if (!inFd) + return -1; + while ((readRes = PR_Read(inFd, buffer, sizeof(buffer))) > 0) { + PR_Write(inRequest->mFD, buffer, readRes); + } + if (readRes != 0) + return -1; + PR_Close(inFd); + return 0; +} + +/* +** displayIndex +** +** Present a list of the reports you can drill down into. +** Returns !0 on failure. +*/ +int +displayIndex(STRequest * inRequest) +{ + int retval = 0; + STOptions *options = &inRequest->mOptions; + + /* + ** Present reports in a list format. + */ + PR_fprintf(inRequest->mFD, "
    "); + PR_fprintf(inRequest->mFD, "\n
  • "); + htmlAnchor(inRequest, "root_callsites.html", "Root Callsites", + NULL, "mainmenu", options); + PR_fprintf(inRequest->mFD, "\n
  • "); + htmlAnchor(inRequest, "categories_summary.html", + "Categories Report", NULL, "mainmenu", options); + PR_fprintf(inRequest->mFD, "\n
  • "); + htmlAnchor(inRequest, "top_callsites.html", + "Top Callsites Report", NULL, "mainmenu", options); + PR_fprintf(inRequest->mFD, "\n
  • "); + htmlAnchor(inRequest, "top_allocations.html", + "Top Allocations Report", NULL, "mainmenu", options); + PR_fprintf(inRequest->mFD, "\n
  • "); + htmlAnchor(inRequest, "memory_leaks.html", + "Memory Leak Report", NULL, "mainmenu", options); +#if ST_WANT_GRAPHS + PR_fprintf(inRequest->mFD, "\n
  • Graphs"); + PR_fprintf(inRequest->mFD, "
      "); + PR_fprintf(inRequest->mFD, "\n
    • "); + htmlAnchor(inRequest, "footprint_graph.html", "Footprint", + NULL, "mainmenu graph", options); + PR_fprintf(inRequest->mFD, "\n
    • "); + htmlAnchor(inRequest, "lifespan_graph.html", + "Allocation Lifespans", NULL, "mainmenu graph", options); + PR_fprintf(inRequest->mFD, "\n
    • "); + htmlAnchor(inRequest, "times_graph.html", "Allocation Times", + NULL, "mainmenu graph", options); + PR_fprintf(inRequest->mFD, "\n
    • "); + htmlAnchor(inRequest, "weight_graph.html", + "Allocation Weights", NULL, "mainmenu graph", options); + PR_fprintf(inRequest->mFD, "\n
    \n"); +#endif /* ST_WANT_GRAPHS */ + PR_fprintf(inRequest->mFD, "\n
\n"); + return retval; +} + +/* +** initRequestOptions +** +** Given the request, set the options that are specific to the request. +** These can generally be determined in the following manner: +** Copy over global options. +** If getData present, attempt to use options therein. +*/ +void +initRequestOptions(STRequest * inRequest) +{ + if (NULL != inRequest) { + /* + ** Copy of global options. + */ + memcpy(&inRequest->mOptions, &globals.mCommandLineOptions, + sizeof(globals.mCommandLineOptions)); + /* + ** Decide what will override global options if anything. + */ + if (NULL != inRequest->mGetData) { + fillOptions(&inRequest->mOptions, inRequest->mGetData); + } + } +} + +STContext * +contextLookup(STOptions * inOptions) +/* +** Lookup a context that matches the options. +** The lookup may block, especially if the context needs to be created. +** Callers of this API must eventually call contextRelease with the +** return value; failure to do so will cause this applications +** to eventually not work as advertised. +** +** inOptions The options determine which context is relevant. +** returns The fully completed context on success. +** The context is read only in practice, so please do not +** write to it or anything it points to. +** NULL on failure. +*/ +{ + STContext *retval = NULL; + STContextCache *inCache = &globals.mContextCache; + + if (NULL != inOptions && NULL != inCache) { + uint32_t loop = 0; + STContext *categoryException = NULL; + PRBool newContext = PR_FALSE; + PRBool evictContext = PR_FALSE; + PRBool changeCategoryContext = PR_FALSE; + + /* + ** Own the context cache while we are in here. + */ + PR_Lock(inCache->mLock); + /* + ** Loop until successful. + ** Waiting on the condition variable makes sure we don't hog the + ** lock below. + */ + while (1) { + /* + ** Go over the cache items. + ** At this point we are looking for a cache hit, with multiple + ** readers. + */ + for (loop = 0; loop < inCache->mItemCount; loop++) { + /* + ** Must be in use. + */ + if (PR_FALSE != inCache->mItems[loop].mInUse) { + int delta[(STOptionGenre) MaxGenres]; + + /* + ** Compare the relevant options, figure out if different + ** in any genre that we care about. + */ + memset(&delta, 0, sizeof(delta)); +#define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \ + if(inOptions->m##option_name != inCache->mItems[loop].mOptions.m##option_name) \ + { \ + delta[(STOptionGenre)option_genre]++; \ + } +#define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \ + if(0 != strcmp(inOptions->m##option_name, inCache->mItems[loop].mOptions.m##option_name)) \ + { \ + delta[(STOptionGenre)option_genre]++; \ + } +#define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ + { \ + uint32_t macro_loop = 0; \ + \ + for(macro_loop = 0; macro_loop < array_size; macro_loop++) \ + { \ + if(0 != strcmp(inOptions->m##option_name[macro_loop], inCache->mItems[loop].mOptions.m##option_name[macro_loop])) \ + { \ + break; \ + } \ + } \ + \ + if(macro_loop != array_size) \ + { \ + delta[(STOptionGenre)option_genre]++; \ + } \ + } +#define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */ +#define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ + if(inOptions->m##option_name != inCache->mItems[loop].mOptions.m##option_name) \ + { \ + delta[(STOptionGenre)option_genre]++; \ + } +#define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ + if(inOptions->m##option_name##64 != inCache->mItems[loop].mOptions.m##option_name##64) \ + { \ + delta[(STOptionGenre)option_genre]++; \ + } +#include "stoptions.h" + /* + ** If there is no genre out of alignment, we accept this as the context. + */ + if (0 == delta[CategoryGenre] && + 0 == delta[DataSortGenre] && + 0 == delta[DataSetGenre] && 0 == delta[DataSizeGenre] + ) { + retval = &inCache->mItems[loop].mContext; + break; + } + + /* + ** A special exception to the rule here. + ** If all that is different is the category genre, and there + ** is no one looking at the context (zero ref count), + ** then there is some magic we can perform. + */ + if (NULL == retval && + 0 == inCache->mItems[loop].mReferenceCount && + 0 != delta[CategoryGenre] && + 0 == delta[DataSortGenre] && + 0 == delta[DataSetGenre] && 0 == delta[DataSizeGenre] + ) { + categoryException = &inCache->mItems[loop].mContext; + } + } + } + + /* + ** Pick up our category exception if relevant. + */ + if (NULL == retval && NULL != categoryException) { + retval = categoryException; + categoryException = NULL; + changeCategoryContext = PR_TRUE; + } + + /* + ** If we don't have a cache hit, then we need to check for an empty + ** spot to take over. + */ + if (NULL == retval) { + for (loop = 0; loop < inCache->mItemCount; loop++) { + /* + ** Must NOT be in use, then it will be the context. + */ + if (PR_FALSE == inCache->mItems[loop].mInUse) { + retval = &inCache->mItems[loop].mContext; + newContext = PR_TRUE; + break; + } + } + } + + /* + ** If we still don't have a return value, then we need to see if + ** there are any old items with zero ref counts that we + ** can take over. + */ + if (NULL == retval) { + for (loop = 0; loop < inCache->mItemCount; loop++) { + /* + ** Must be in use. + */ + if (PR_FALSE != inCache->mItems[loop].mInUse) { + /* + ** Must have a ref count of zero. + */ + if (0 == inCache->mItems[loop].mReferenceCount) { + /* + ** Must be older than any other we know of. + */ + if (NULL != retval) { + if (inCache->mItems[loop].mLastAccessed < + inCache->mItems[retval->mIndex]. + mLastAccessed) { + retval = &inCache->mItems[loop].mContext; + } + } + else { + retval = &inCache->mItems[loop].mContext; + } + } + } + } + + if (NULL != retval) { + evictContext = PR_TRUE; + } + } + + /* + ** If we still don't have a return value, then we can not avoid + ** waiting around until someone gives us the chance. + ** The chance, in specific, comes when a cache item reference + ** count returns to zero, upon which we can try to take + ** it over again. + */ + if (NULL == retval) { + /* + ** This has the side effect of release the context lock. + ** This is a good thing so that other clients can continue + ** to connect and hopefully have cache hits. + ** If they do not have cache hits, then we will end up + ** with a bunch of waiters here.... + */ + PR_WaitCondVar(inCache->mCacheMiss, PR_INTERVAL_NO_TIMEOUT); + } + + /* + ** If we have a return value, improve the reference count here. + */ + if (NULL != retval) { + /* + ** Decide if there are any changes to be made. + ** Do as little as possible, then fall through the context + ** cache lock to finish up. + ** This way, lengthy init operations will not block + ** other clients, only matches to this context. + */ + if (PR_FALSE != newContext || + PR_FALSE != evictContext || + PR_FALSE != changeCategoryContext) { + /* + ** Overwrite the prefs for this context. + ** They are changing. + */ + memcpy(&inCache->mItems[retval->mIndex].mOptions, + inOptions, + sizeof(inCache->mItems[retval->mIndex].mOptions)); + /* + ** As we are going to be changing the context, we need to write lock it. + ** This makes sure no readers are allowed while we are making our changes. + */ + PR_RWLock_Wlock(retval->mRWLock); + } + + /* + ** NOTE, ref count gets incremented here, inside content + ** cache lock so it can not be flushed once lock + ** released. + */ + inCache->mItems[retval->mIndex].mInUse = PR_TRUE; + inCache->mItems[retval->mIndex].mReferenceCount++; + /* + ** That's all folks. + */ + break; + } + + } /* while(1), try again */ + + /* + ** Done with context cache. + */ + PR_Unlock(inCache->mLock); + /* + ** Now that the context cached is free to continue accepting other + ** requests, we have a little more work to do. + */ + if (NULL != retval) { + PRBool unlock = PR_FALSE; + + /* + ** If evicting, we need to free off the old stuff. + */ + if (PR_FALSE != evictContext) { + unlock = PR_TRUE; + /* + ** We do not free the sorted run. + ** The category code takes care of this. + */ + retval->mSortedRun = NULL; +#if ST_WANT_GRAPHS + /* + ** There is no need to + ** PR_Lock(retval->mImageLock) + ** We are already under write lock for the entire structure. + */ + retval->mFootprintCached = PR_FALSE; + retval->mTimevalCached = PR_FALSE; + retval->mLifespanCached = PR_FALSE; + retval->mWeightCached = PR_FALSE; +#endif + } + + /* + ** If new or recently evicted, we need to fully init. + */ + if (PR_FALSE != newContext || PR_FALSE != evictContext) { + unlock = PR_TRUE; + retval->mSortedRun = + createRunFromGlobal(&inCache->mItems[retval->mIndex]. + mOptions, + &inCache->mItems[retval->mIndex]. + mContext); + } + + /* + ** If changing category, we need to do some sneaky stuff. + */ + if (PR_FALSE != changeCategoryContext) { + STCategoryNode *node = NULL; + + unlock = PR_TRUE; + /* + ** Just a category change. We don't need to harvest. Just find the + ** right node and set the cache.mSortedRun. We need to recompute + ** cost though. But that is cheap. + */ + node = + findCategoryNode(inCache->mItems[retval->mIndex].mOptions. + mCategoryName, &globals); + if (node) { + /* Recalculate cost of run */ + recalculateRunCost(&inCache->mItems[retval->mIndex]. + mOptions, retval, + node->runs[retval->mIndex]); + retval->mSortedRun = node->runs[retval->mIndex]; + } + +#if ST_WANT_GRAPHS + /* + ** There is no need to + ** PR_Lock(retval->mImageLock) + ** We are already under write lock for the entire structure. + */ + retval->mFootprintCached = PR_FALSE; + retval->mTimevalCached = PR_FALSE; + retval->mLifespanCached = PR_FALSE; + retval->mWeightCached = PR_FALSE; +#endif + } + + /* + ** Release the write lock if we took one to make changes. + */ + if (PR_FALSE != unlock) { + PR_RWLock_Unlock(retval->mRWLock); + } + + /* + ** Last thing possible, take a read lock on our return value. + ** This will cause us to block if the context is not fully + ** initialized in another thread holding the write lock. + */ + PR_RWLock_Rlock(retval->mRWLock); + } + } + + return retval; +} + +void +contextRelease(STContext * inContext) +/* +** After a successful call to contextLookup, one should call this API when +** done with the context. +** This effectively removes the usage of the client on a cached item. +*/ +{ + STContextCache *inCache = &globals.mContextCache; + + if (NULL != inContext && NULL != inCache) { + /* + ** Own the context cache while in here. + */ + PR_Lock(inCache->mLock); + /* + ** Give up the read lock on the context. + */ + PR_RWLock_Unlock(inContext->mRWLock); + /* + ** Decrement the reference count on the context. + ** If it was the last reference, notify that a new item is + ** available for eviction. + ** A waiting thread will wake up and eat it. + ** Also set when it was last accessed so the oldest unused item + ** can be targeted for eviction. + */ + inCache->mItems[inContext->mIndex].mReferenceCount--; + if (0 == inCache->mItems[inContext->mIndex].mReferenceCount) { + PR_NotifyCondVar(inCache->mCacheMiss); + inCache->mItems[inContext->mIndex].mLastAccessed = + PR_IntervalNow(); + } + + /* + ** Done with context cache. + */ + PR_Unlock(inCache->mLock); + } +} + + +/* +** handleRequest +** +** Based on what file they are asking for, perform some processing. +** Output the results to aFD. +** +** Returns !0 on error. +*/ +int +handleRequest(tmreader * aTMR, PRFileDesc * aFD, + const char *aFileName, const FormData * aGetData) +{ + int retval = 0; + + if (NULL != aTMR && NULL != aFD && NULL != aFileName + && '\0' != *aFileName) { + STRequest request; + + /* + ** Init the request. + */ + memset(&request, 0, sizeof(request)); + request.mFD = aFD; + request.mGetFileName = aFileName; + request.mGetData = aGetData; + /* + ** Set local options for this request. + */ + initRequestOptions(&request); + /* + ** Get our cached context for this client. + ** Simply based on the options. + */ + request.mContext = contextLookup(&request.mOptions); + if (NULL != request.mContext) { + /* + ** Attempt to find the file of interest. + */ + if (handleLocalFile(&request, aFileName)) { + displayFile(&request, aFileName); + } + else if (0 == strcmp("index.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, "SpaceTrace Index"); + displayRes = displayIndex(&request); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayIndex); + } + + htmlFooter(&request); + } + else if (0 == strcmp("settings.html", aFileName) || + 0 == strcmp("options.html", aFileName)) { + htmlHeader(&request, "SpaceTrace Options"); + displaySettings(&request); + htmlFooter(&request); + } + else if (0 == strcmp("top_allocations.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, "SpaceTrace Top Allocations Report"); + displayRes = + displayTopAllocations(&request, + request.mContext->mSortedRun, + "top-allocations", + "SpaceTrace Top Allocations Report", + 1); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayTopAllocations); + } + + htmlFooter(&request); + } + else if (0 == strcmp("top_callsites.html", aFileName)) { + int displayRes = 0; + tmcallsite **array = NULL; + uint32_t arrayCount = 0; + + /* + ** Display header after we figure out if we are going to focus + ** on a category. + */ + htmlHeader(&request, "SpaceTrace Top Callsites Report"); + if (NULL != request.mContext->mSortedRun + && 0 < request.mContext->mSortedRun->mAllocationCount) { + arrayCount = + callsiteArrayFromRun(&array, 0, + request.mContext->mSortedRun); + if (0 != arrayCount && NULL != array) { + displayRes = + displayTopCallsites(&request, array, arrayCount, + 0, + "top-callsites", + "Top Callsites Report", + 0); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayTopCallsites); + } + + /* + ** Done with the array. + */ + free(array); + array = NULL; + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, handleRequest); + } + + htmlFooter(&request); + } + else if (0 == strcmp("memory_leaks.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, "SpaceTrace Memory Leaks Report"); + displayRes = + displayMemoryLeaks(&request, + request.mContext->mSortedRun); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayMemoryLeaks); + } + + htmlFooter(&request); + } + else if (0 == strncmp("allocation_", aFileName, 11)) { + int scanRes = 0; + uint32_t allocationIndex = 0; + + /* + ** Oh, what a hack.... + ** The index to the allocation structure in the global run + ** is in the filename. Better than the pointer value.... + */ + scanRes = PR_sscanf(aFileName + 11, "%u", &allocationIndex); + if (1 == scanRes + && globals.mRun.mAllocationCount > allocationIndex + && NULL != globals.mRun.mAllocations[allocationIndex]) { + STAllocation *allocation = + globals.mRun.mAllocations[allocationIndex]; + char buffer[128]; + int displayRes = 0; + + PR_snprintf(buffer, sizeof(buffer), + "SpaceTrace Allocation %u Details Report", + allocationIndex); + htmlHeader(&request, buffer); + displayRes = + displayAllocationDetails(&request, allocation); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayAllocationDetails); + } + + htmlFooter(&request); + } + else { + htmlNotFound(&request); + } + } + else if (0 == strncmp("callsite_", aFileName, 9)) { + int scanRes = 0; + uint32_t callsiteSerial = 0; + tmcallsite *resolved = NULL; + + /* + ** Oh, what a hack.... + ** The serial(key) to the callsite structure in the hash table + ** is in the filename. Better than the pointer value.... + */ + scanRes = PR_sscanf(aFileName + 9, "%u", &callsiteSerial); + if (1 == scanRes && 0 != callsiteSerial + && NULL != (resolved = + tmreader_callsite(aTMR, callsiteSerial))) { + char buffer[128]; + int displayRes = 0; + + PR_snprintf(buffer, sizeof(buffer), + "SpaceTrace Callsite %u Details Report", + callsiteSerial); + htmlHeader(&request, buffer); + displayRes = displayCallsiteDetails(&request, resolved); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayAllocationDetails); + } + + htmlFooter(&request); + } + else { + htmlNotFound(&request); + } + } + else if (0 == strcmp("root_callsites.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, "SpaceTrace Root Callsites"); + displayRes = + displayCallsites(&request, aTMR->calltree_root.kids, + ST_FOLLOW_SIBLINGS, 0, + "callsites-root", + "SpaceTrace Root Callsites", + __LINE__); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayCallsites); + } + + htmlFooter(&request); + } +#if ST_WANT_GRAPHS + else if (0 == strcmp("footprint_graph.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, "SpaceTrace Memory Footprint Report"); + PR_fprintf(request.mFD, "
\n"); + PR_fprintf(request.mFD, "\n"); + PR_fprintf(request.mFD, "
\n"); + htmlFooter(&request); + } +#endif /* ST_WANT_GRAPHS */ +#if ST_WANT_GRAPHS + else if (0 == strcmp("times_graph.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, "SpaceTrace Allocation Times Report"); + PR_fprintf(request.mFD, "
\n"); + PR_fprintf(request.mFD, "\n"); + PR_fprintf(request.mFD, "
\n"); + htmlFooter(&request); + } +#endif /* ST_WANT_GRAPHS */ +#if ST_WANT_GRAPHS + else if (0 == strcmp("lifespan_graph.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, + "SpaceTrace Allocation Lifespans Report"); + PR_fprintf(request.mFD, "
\n"); + PR_fprintf(request.mFD, "\n"); + PR_fprintf(request.mFD, "
\n"); + htmlFooter(&request); + } +#endif /* ST_WANT_GRAPHS */ +#if ST_WANT_GRAPHS + else if (0 == strcmp("weight_graph.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, "SpaceTrace Allocation Weights Report"); + PR_fprintf(request.mFD, "
\n"); + PR_fprintf(request.mFD, "\n"); + PR_fprintf(request.mFD, "
\n"); + htmlFooter(&request); + } +#endif /* ST_WANT_GRAPHS */ +#if ST_WANT_GRAPHS + else if (0 == strcmp("footprint.png", aFileName)) { + int graphRes = 0; + + graphRes = + graphFootprint(&request, request.mContext->mSortedRun); + if (0 != graphRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, graphFootprint); + } + } +#endif /* ST_WANT_GRAPHS */ +#if ST_WANT_GRAPHS + else if (0 == strcmp("times.png", aFileName)) { + int graphRes = 0; + + graphRes = + graphTimeval(&request, request.mContext->mSortedRun); + if (0 != graphRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, graphTimeval); + } + } +#endif /* ST_WANT_GRAPHS */ +#if ST_WANT_GRAPHS + else if (0 == strcmp("lifespan.png", aFileName)) { + int graphRes = 0; + + graphRes = + graphLifespan(&request, request.mContext->mSortedRun); + if (0 != graphRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, graphLifespan); + } + } +#endif /* ST_WANT_GRAPHS */ +#if ST_WANT_GRAPHS + else if (0 == strcmp("weight.png", aFileName)) { + int graphRes = 0; + + graphRes = + graphWeight(&request, request.mContext->mSortedRun); + if (0 != graphRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, graphWeight); + } + } +#endif /* ST_WANT_GRAPHS */ + else if (0 == strcmp("categories_summary.html", aFileName)) { + int displayRes = 0; + + htmlHeader(&request, "Category Report"); + displayRes = + displayCategoryReport(&request, &globals.mCategoryRoot, + 1); + if (0 != displayRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, displayMemoryLeaks); + } + + htmlFooter(&request); + } + else { + htmlNotFound(&request); + } + + /* + ** Release the context we obtained earlier. + */ + contextRelease(request.mContext); + request.mContext = NULL; + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, contextObtain); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, handleRequest); + } + + /* + ** Compact a little if you can after each request. + */ + heapCompact(); + return retval; +} + +/* +** handleClient +** +** main() of the new client thread. +** Read the fd for the request. +** Output the results. +*/ +void +handleClient(void *inArg) +{ + PRFileDesc *aFD = NULL; + + aFD = (PRFileDesc *) inArg; + if (NULL != aFD) { + PRStatus closeRes = PR_SUCCESS; + char aBuffer[2048]; + int32_t readRes = 0; + + readRes = PR_Read(aFD, aBuffer, sizeof(aBuffer)); + if (0 <= readRes) { + const char *sanityCheck = "GET /"; + + if (0 == strncmp(sanityCheck, aBuffer, 5)) { + char *eourl = NULL; + char *start = &aBuffer[5]; + char *getData = NULL; + int realFun = 0; + const char *crlf = "\015\012"; + char *eoline = NULL; + FormData *fdGet = NULL; + + /* + ** Truncate the line if possible. + ** Only want first one. + */ + eoline = strstr(aBuffer, crlf); + if (NULL != eoline) { + *eoline = '\0'; + } + + /* + ** Find the whitespace. + ** That is either end of line or the " HTTP/1.x" suffix. + ** We do not care. + */ + for (eourl = start; 0 == isspace(*eourl) && '\0' != *eourl; + eourl++) { + /* + ** No body. + */ + } + + /* + ** Cap it off. + ** Convert empty '/' to index.html. + */ + *eourl = '\0'; + if ('\0' == *start) { + strcpy(start, "index.html"); + } + + /* + ** Have we got any GET form data? + */ + getData = strchr(start, '?'); + if (NULL != getData) { + /* + ** Whack it off. + */ + *getData = '\0'; + getData++; + } + + /* + ** Convert get data into a more useful format. + */ + fdGet = FormData_Create(getData); + /* + ** This is totally a hack, but oh well.... + ** + ** Send that the request was OK, regardless. + ** + ** If we have any get data, then it is a set of options + ** we attempt to apply. + ** + ** Other code will tell the user they were wrong or if + ** there was an error. + ** If the filename contains a ".png", then send the image + ** mime type, otherwise, say it is text/html. + */ + PR_fprintf(aFD, "HTTP/1.1 200 OK%s", crlf); + PR_fprintf(aFD, "Server: %s%s", + "$Id: spacetrace.c,v 1.54 2006/11/01 23:02:17 timeless%mozdev.org Exp $", + crlf); + PR_fprintf(aFD, "Content-type: "); + if (NULL != strstr(start, ".png")) { + PR_fprintf(aFD, "image/png"); + } + else if (NULL != strstr(start, ".jpg")) { + PR_fprintf(aFD, "image/jpeg"); + } + else if (NULL != strstr(start, ".txt")) { + PR_fprintf(aFD, "text/plain"); + } + else if (NULL != strstr(start, ".css")) { + PR_fprintf(aFD, "text/css"); + } + else { + PR_fprintf(aFD, "text/html"); + } + PR_fprintf(aFD, crlf); + /* + ** One more to separate headers from content. + */ + PR_fprintf(aFD, crlf); + /* + ** Ready for the real fun. + */ + realFun = handleRequest(globals.mTMR, aFD, start, fdGet); + if (0 != realFun) { + REPORT_ERROR(__LINE__, handleRequest); + } + + /* + ** Free off get data if around. + */ + FormData_Destroy(fdGet); + fdGet = NULL; + } + else { + REPORT_ERROR(__LINE__, handleClient); + } + } + else { + REPORT_ERROR(__LINE__, lineReader); + } + + /* + ** Done with the connection. + */ + closeRes = PR_Close(aFD); + if (PR_SUCCESS != closeRes) { + REPORT_ERROR(__LINE__, PR_Close); + } + } + else { + REPORT_ERROR(__LINE__, handleClient); + } +} + +/* +** serverMode +** +** List on a port as a httpd. +** Output results interactively on demand. +** +** Returns !0 on error. +*/ +int +serverMode(void) +{ + int retval = 0; + PRFileDesc *socket = NULL; + + /* + ** Create a socket. + */ + socket = PR_NewTCPSocket(); + if (NULL != socket) { + PRStatus closeRes = PR_SUCCESS; + PRNetAddr bindAddr; + PRStatus bindRes = PR_SUCCESS; + + /* + ** Bind it to an interface/port. + ** Any interface. + */ + bindAddr.inet.family = PR_AF_INET; + bindAddr.inet.port = + PR_htons((uint16_t) globals.mCommandLineOptions.mHttpdPort); + bindAddr.inet.ip = PR_htonl(PR_INADDR_ANY); + bindRes = PR_Bind(socket, &bindAddr); + if (PR_SUCCESS == bindRes) { + PRStatus listenRes = PR_SUCCESS; + const int backlog = 0x20; + + /* + ** Start listening for clients. + ** Give a decent backlog, some of our processing will take + ** a bit. + */ + listenRes = PR_Listen(socket, backlog); + if (PR_SUCCESS == listenRes) { + PRFileDesc *connection = NULL; + int failureSum = 0; + char message[80]; + + /* + ** Output a little message saying we are receiving. + */ + PR_snprintf(message, sizeof(message), + "server accepting connections at http://localhost:%u/", + globals.mCommandLineOptions.mHttpdPort); + REPORT_INFO(message); + PR_fprintf(PR_STDOUT, "Peak memory used: %s bytes\n", + FormatNumber(globals.mPeakMemoryUsed)); + PR_fprintf(PR_STDOUT, "Allocations : %s total\n", + FormatNumber(globals.mMallocCount + + globals.mCallocCount + + globals.mReallocCount), + FormatNumber(globals.mFreeCount)); + PR_fprintf(PR_STDOUT, "Breakdown : %s malloc\n", + FormatNumber(globals.mMallocCount)); + PR_fprintf(PR_STDOUT, " %s calloc\n", + FormatNumber(globals.mCallocCount)); + PR_fprintf(PR_STDOUT, " %s realloc\n", + FormatNumber(globals.mReallocCount)); + PR_fprintf(PR_STDOUT, " %s free\n", + FormatNumber(globals.mFreeCount)); + PR_fprintf(PR_STDOUT, "Leaks : %s\n", + FormatNumber((globals.mMallocCount + + globals.mCallocCount + + globals.mReallocCount) - + globals.mFreeCount)); + /* + ** Keep accepting until we know otherwise. + ** + ** We do a thread per connection. + ** Up to the thread to close the connection when done. + ** + ** This is known by me to be suboptimal, and I would rather + ** do a thread pool if it ever becomes a resource issue. + ** Any issues would simply point to a need to get + ** more machines or a beefier machine to handle the + ** requests, as well as a need to do thread pooling and + ** avoid thread creation overhead. + ** The threads are not tracked, except possibly by NSPR + ** itself and PR_Cleanup will wait on them all to exit as + ** user threads so our shared data is valid. + */ + while (0 == retval) { + connection = + PR_Accept(socket, NULL, PR_INTERVAL_NO_TIMEOUT); + if (NULL != connection) { + PRThread *clientThread = NULL; + + /* + ** Thread per connection. + */ + clientThread = PR_CreateThread(PR_USER_THREAD, /* PR_Cleanup sync */ + handleClient, (void *) connection, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, /* IO enabled */ + PR_UNJOINABLE_THREAD, + 0); + if (NULL == clientThread) { + PRStatus closeRes = PR_SUCCESS; + + failureSum += __LINE__; + REPORT_ERROR(__LINE__, PR_Accept); + /* + ** Close the connection as well, no service + */ + closeRes = PR_Close(connection); + if (PR_FAILURE == closeRes) { + REPORT_ERROR(__LINE__, PR_Close); + } + } + } + else { + failureSum += __LINE__; + REPORT_ERROR(__LINE__, PR_Accept); + } + } + + if (0 != failureSum) { + retval = __LINE__; + } + + /* + ** Output a little message saying it is all over. + */ + REPORT_INFO("server no longer accepting connections...."); + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, PR_Listen); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, PR_Bind); + } + + /* + ** Done with socket. + */ + closeRes = PR_Close(socket); + if (PR_SUCCESS != closeRes) { + retval = __LINE__; + REPORT_ERROR(__LINE__, PR_Close); + } + socket = NULL; + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, PR_NewTCPSocket); + } + + return retval; +} + +/* +** batchMode +** +** Perform whatever batch requests we were asked to do. +*/ +int +batchMode(void) +{ + int retval = 0; + + if (0 != globals.mCommandLineOptions.mBatchRequestCount) { + uint32_t loop = 0; + int failureSum = 0; + int handleRes = 0; + char aFileName[1024]; + uint32_t sprintfRes = 0; + + /* + ** Go through and process the various files requested. + ** We do not stop on failure, as it is too costly to rerun the + ** batch job. + */ + for (loop = 0; + loop < globals.mCommandLineOptions.mBatchRequestCount; loop++) { + sprintfRes = + PR_snprintf(aFileName, sizeof(aFileName), "%s%c%s", + globals.mCommandLineOptions.mOutputDir, + PR_GetDirectorySeparator(), + globals.mCommandLineOptions.mBatchRequest[loop]); + if ((uint32_t) - 1 != sprintfRes) { + PRFileDesc *outFile = NULL; + + outFile = PR_Open(aFileName, ST_FLAGS, ST_PERMS); + if (NULL != outFile) { + PRStatus closeRes = PR_SUCCESS; + + handleRes = + handleRequest(globals.mTMR, outFile, + globals.mCommandLineOptions. + mBatchRequest[loop], NULL); + if (0 != handleRes) { + failureSum += __LINE__; + REPORT_ERROR(__LINE__, handleRequest); + } + + closeRes = PR_Close(outFile); + if (PR_SUCCESS != closeRes) { + failureSum += __LINE__; + REPORT_ERROR(__LINE__, PR_Close); + } + } + else { + failureSum += __LINE__; + REPORT_ERROR(__LINE__, PR_Open); + } + } + else { + failureSum += __LINE__; + REPORT_ERROR(__LINE__, PR_snprintf); + } + } + + if (0 != failureSum) { + retval = __LINE__; + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, outputReports); + } + + return retval; +} + +/* +** doRun +** +** Perform the actual processing this program requires. +** Returns !0 on failure. +*/ +int +doRun(void) +{ + int retval = 0; + + /* + ** Create the new trace-malloc reader. + */ + globals.mTMR = tmreader_new(globals.mProgramName, NULL); + if (NULL != globals.mTMR) { + int tmResult = 0; + int outputResult = 0; + +#if defined(DEBUG_dp) + PRIntervalTime start = PR_IntervalNow(); + + fprintf(stderr, "DEBUG: reading tracemalloc data...\n"); +#endif + tmResult = + tmreader_eventloop(globals.mTMR, + globals.mCommandLineOptions.mFileName, + tmEventHandler); + printf("\rReading... Done.\n"); +#if defined(DEBUG_dp) + fprintf(stderr, + "DEBUG: reading tracemalloc data ends: %dms [%d allocations]\n", + PR_IntervalToMilliseconds(PR_IntervalNow() - start), + globals.mRun.mAllocationCount); +#endif + if (0 == tmResult) { + REPORT_ERROR(__LINE__, tmreader_eventloop); + retval = __LINE__; + } + + if (0 == retval) { + /* + ** Decide if we're going into batch mode or server mode. + */ + if (0 != globals.mCommandLineOptions.mBatchRequestCount) { + /* + ** Output in one big step while everything still exists. + */ + outputResult = batchMode(); + if (0 != outputResult) { + REPORT_ERROR(__LINE__, batchMode); + retval = __LINE__; + } + } + else { + int serverRes = 0; + + /* + ** httpd time. + */ + serverRes = serverMode(); + if (0 != serverRes) { + REPORT_ERROR(__LINE__, serverMode); + retval = __LINE__; + } + } + + /* + ** Clear our categorization tree + */ + freeCategories(&globals); + } + } + else { + REPORT_ERROR(__LINE__, tmreader_new); + retval = __LINE__; + } + + return retval; +} + +int +initCaches(void) +/* +** Initialize the global caches. +** More involved since we have to allocated/create some objects. +** +** returns Zero if all is well. +** Non-zero on error. +*/ +{ + int retval = 0; + STContextCache *inCache = &globals.mContextCache; + + if (NULL != inCache && 0 != globals.mCommandLineOptions.mContexts) { + inCache->mLock = PR_NewLock(); + if (NULL != inCache->mLock) { + inCache->mCacheMiss = PR_NewCondVar(inCache->mLock); + if (NULL != inCache->mCacheMiss) { + inCache->mItems = + (STContextCacheItem *) calloc(globals.mCommandLineOptions. + mContexts, + sizeof(STContextCacheItem)); + if (NULL != inCache->mItems) { + uint32_t loop = 0; + char buffer[64]; + + inCache->mItemCount = + globals.mCommandLineOptions.mContexts; + /* + ** Init each item as needed. + */ + for (loop = 0; loop < inCache->mItemCount; loop++) { + inCache->mItems[loop].mContext.mIndex = loop; + PR_snprintf(buffer, sizeof(buffer), + "Context Item %d RW Lock", loop); + inCache->mItems[loop].mContext.mRWLock = + PR_NewRWLock(PR_RWLOCK_RANK_NONE, buffer); + if (NULL == inCache->mItems[loop].mContext.mRWLock) { + break; + } +#if ST_WANT_GRAPHS + inCache->mItems[loop].mContext.mImageLock = + PR_NewLock(); + if (NULL == inCache->mItems[loop].mContext.mImageLock) { + break; + } +#endif + } + + if (loop != inCache->mItemCount) { + retval = __LINE__; + REPORT_ERROR(__LINE__, initCaches); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, calloc); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, PR_NewCondVar); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, PR_NewLock); + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, initCaches); + } + + return retval; +} + +int +destroyCaches(void) +/* +** Clean up any global caches we have laying around. +** +** returns Zero if all is well. +** Non-zero on error. +*/ +{ + int retval = 0; + STContextCache *inCache = &globals.mContextCache; + + if (NULL != inCache) { + uint32_t loop = 0; + + /* + ** Uninit item data one by one. + */ + for (loop = 0; loop < inCache->mItemCount; loop++) { + if (NULL != inCache->mItems[loop].mContext.mRWLock) { + PR_DestroyRWLock(inCache->mItems[loop].mContext.mRWLock); + inCache->mItems[loop].mContext.mRWLock = NULL; + } +#if ST_WANT_GRAPHS + if (NULL != inCache->mItems[loop].mContext.mImageLock) { + PR_DestroyLock(inCache->mItems[loop].mContext.mImageLock); + inCache->mItems[loop].mContext.mImageLock = NULL; + } +#endif + } + + inCache->mItemCount = 0; + if (NULL != inCache->mItems) { + free(inCache->mItems); + inCache->mItems = NULL; + } + + if (NULL != inCache->mCacheMiss) { + PR_DestroyCondVar(inCache->mCacheMiss); + inCache->mCacheMiss = NULL; + } + + if (NULL != inCache->mLock) { + PR_DestroyLock(inCache->mLock); + inCache->mLock = NULL; + } + } + else { + retval = __LINE__; + REPORT_ERROR(__LINE__, destroyCaches); + } + + return retval; +} + +/* +** main +** +** Process entry and exit. +*/ +int +main(int aArgCount, char **aArgArray) +{ + int retval = 0; + int optionsResult = 0; + PRStatus prResult = PR_SUCCESS; + int showedHelp = 0; + int looper = 0; + int cacheResult = 0; + + /* + ** NSPR init. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + /* + ** Initialize globals + */ + memset(&globals, 0, sizeof(globals)); + /* + ** Set the program name. + */ + globals.mProgramName = aArgArray[0]; + /* + ** Set the minimum timeval really high so other code + ** that checks the timeval will get it right. + */ + globals.mMinTimeval = ST_TIMEVAL_MAX; + /* + ** Handle initializing options. + */ + optionsResult = initOptions(aArgCount, aArgArray); + if (0 != optionsResult) { + REPORT_ERROR(optionsResult, initOptions); + retval = __LINE__; + } + + /* + ** Initialize our caches. + */ + cacheResult = initCaches(); + if (0 != cacheResult) { + retval = __LINE__; + REPORT_ERROR(__LINE__, initCaches); + } + + /* + ** Small alloc code init. + */ + globals.mCategoryRoot.runs = + (STRun **) calloc(globals.mCommandLineOptions.mContexts, + sizeof(STRun *)); + if (NULL == globals.mCategoryRoot.runs) { + retval = __LINE__; + REPORT_ERROR(__LINE__, calloc); + } + + /* + ** Show help on usage if need be. + */ + showedHelp = showHelp(); + /* + ** Only perform the run if everything is checking out. + */ + if (0 == showedHelp && 0 == retval) { + int runResult = 0; + + runResult = doRun(); + if (0 != runResult) { + REPORT_ERROR(runResult, doRun); + retval = __LINE__; + } + } + + if (0 != retval) { + REPORT_ERROR(retval, main); + } + + /* + ** Have NSPR join all client threads we started. + */ + prResult = PR_Cleanup(); + if (PR_SUCCESS != prResult) { + REPORT_ERROR(retval, PR_Cleanup); + retval = __LINE__; + } + /* + ** All threads are joined/done by this line. + */ + + /* + ** Options allocated a little. + */ +#define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \ + if(NULL != globals.mCommandLineOptions.m##option_name) \ + { \ + free((void*)globals.mCommandLineOptions.m##option_name); \ + globals.mCommandLineOptions.m##option_name = NULL; \ + globals.mCommandLineOptions.m##option_name##Count = 0; \ + } +#include "stoptions.h" + + /* + ** globals has a small modification to clear up. + */ + if (NULL != globals.mCategoryRoot.runs) { + free(globals.mCategoryRoot.runs); + globals.mCategoryRoot.runs = NULL; + } + + /* + ** Blow away our caches. + */ + cacheResult = destroyCaches(); + if (0 != cacheResult) { + retval = __LINE__; + REPORT_ERROR(__LINE__, initCaches); + } + + /* + ** We are safe to kill our tmreader data. + */ + if (NULL != globals.mTMR) { + tmreader_destroy(globals.mTMR); + globals.mTMR = NULL; + } + + return retval; +}