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

Allocation %u Details:

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

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

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

%s

\n", option_name); michael@0: PR_fprintf(inRequest->mFD, michael@0: "\n", michael@0: option_name, value); michael@0: PR_fprintf(inRequest->mFD, michael@0: "

Default value is \"%s\".

\n

%s

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

%s

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

Up to %u occurrences allowed.

\n

%s

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

%s

\n", option_name); michael@0: PR_fprintf(inRequest->mFD, michael@0: "\n", option_name, michael@0: value / multiplier); michael@0: PR_fprintf(inRequest->mFD, michael@0: "

Default value is %u.

\n

%s

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

%s

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

Default value is %llu.

\n

%s

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