CLOSED TREE: TraceMonkey merge head. (a=blockers)
[mozilla-central.git] / tools / trace-malloc / spacetrace.c
blob82ad7af4da55f1ebac2c7a9b9d203ee6cb6fdc6a
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is spacetrace.h/spacetrace.c code, released
17 * Nov 6, 2001.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2001
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
25 * Garrett Arch Blythe, 31-October-2001
27 * Alternatively, the contents of this file may be used under the terms of
28 * either the GNU General Public License Version 2 or later (the "GPL"), or
29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
42 ** spacetrace.c
44 ** SpaceTrace is meant to take the output of trace-malloc and present
45 ** a picture of allocations over the run of the application.
49 ** Required include files.
51 #include "spacetrace.h"
53 #include <ctype.h>
54 #include <math.h>
55 #include <string.h>
56 #include <time.h>
57 #if defined(XP_WIN32)
58 #include <malloc.h> /* _heapMin */
59 #endif
61 #if defined(HAVE_BOUTELL_GD)
63 ** See http://www.boutell.com/gd for the GD graphics library.
64 ** Ports for many platorms exist.
65 ** Your box may already have the lib (mine did, redhat 7.1 workstation).
67 #include <gd.h>
68 #include <gdfontt.h>
69 #include <gdfonts.h>
70 #include <gdfontmb.h>
71 #endif /* HAVE_BOUTELL_GD */
74 ** Ugh, MSVC6's qsort is too slow...
76 #include "nsQuickSort.h"
77 #include "prlong.h"
79 ** strcasecmp API please.
81 #if defined(_MSC_VER)
82 #define strcasecmp _stricmp
83 #define strncasecmp _strnicmp
84 #endif
87 ** the globals variables. happy joy.
89 STGlobals globals;
92 ** have the heap cleanup at opportune times, if possible.
94 void
95 heapCompact(void)
97 #if defined(XP_WIN32)
98 _heapmin();
99 #endif
102 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \
103 PR_fprintf(PR_STDOUT, "--%s\nDisabled by default.\n%s\n", #option_name, option_help);
104 #define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \
105 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is \"%s\".\n%s\n", #option_name, default_value, option_help);
106 #define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
107 PR_fprintf(PR_STDOUT, "--%s=<value>\nUp to %u occurrences allowed.\n%s\n", #option_name, array_size, option_help);
108 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \
109 PR_fprintf(PR_STDOUT, "--%s=<value>\nUnlimited occurrences allowed.\n%s\n", #option_name, option_help);
110 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
111 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is %u.\n%s\n", #option_name, default_value, option_help);
112 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
113 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is %llu.\n%s\n", #option_name, default_value, option_help);
116 ** showHelp
118 ** Give simple command line help.
119 ** Returns !0 if the help was showed.
122 showHelp(void)
124 int retval = 0;
126 if (PR_FALSE != globals.mCommandLineOptions.mHelp) {
127 PR_fprintf(PR_STDOUT, "Usage:\t%s [OPTION]... [-|filename]\n\n",
128 globals.mProgramName);
131 #include "stoptions.h"
134 ** Showed something.
136 retval = __LINE__;
139 return retval;
143 ** ticks2xsec
145 ** Convert platform specific ticks to second units
146 ** Returns 0 on success.
148 PRUint32
149 ticks2xsec(tmreader * aReader, PRUint32 aTicks, PRUint32 aResolution)
151 PRUint32 retval = 0;
152 PRUint64 bigone;
153 PRUint64 tmp64;
155 LL_UI2L(bigone, aResolution);
156 LL_UI2L(tmp64, aTicks);
157 LL_MUL(bigone, bigone, tmp64);
158 LL_UI2L(tmp64, aReader->ticksPerSec);
159 LL_DIV(bigone, bigone, tmp64);
160 LL_L2UI(retval, bigone);
161 return retval;
164 #define ticks2msec(reader, ticks) ticks2xsec((reader), (ticks), 1000)
165 #define ticks2usec(reader, ticks) ticks2xsec((reader), (ticks), 1000000)
168 ** initOptions
170 ** Determine global settings for the application.
171 ** Returns 0 on success.
174 initOptions(int aArgCount, char **aArgArray)
176 int retval = 0;
177 int traverse = 0;
180 ** Set the initial global default options.
182 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) globals.mCommandLineOptions.m##option_name = PR_FALSE;
183 #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);
184 #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'; } }
185 #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;
186 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) globals.mCommandLineOptions.m##option_name = default_value * multiplier;
187 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) { PRUint64 def64 = default_value; PRUint64 mul64 = multiplier; LL_MUL(globals.mCommandLineOptions.m##option_name##64, def64, mul64); }
189 #include "stoptions.h"
192 ** Go through all arguments.
193 ** Two dashes lead off an option.
194 ** Any single dash leads off help, unless it is a lone dash (stdin).
195 ** Anything else will be attempted as a file to be processed.
197 for (traverse = 1; traverse < aArgCount; traverse++) {
198 if ('-' == aArgArray[traverse][0] && '-' == aArgArray[traverse][1]) {
199 const char *option = &aArgArray[traverse][2];
202 ** Initial if(0) needed to make "else if"s valid.
204 if (0) {
207 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \
208 else if(0 == strcasecmp(option, #option_name)) \
210 globals.mCommandLineOptions.m##option_name = PR_TRUE; \
212 #define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \
213 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
215 PR_snprintf(globals.mCommandLineOptions.m##option_name, sizeof(globals.mCommandLineOptions.m##option_name), "%s", option + strlen(#option_name "=")); \
217 #define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
218 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
220 int arrLoop = 0; \
222 for(arrLoop = 0; arrLoop < array_size; arrLoop++) \
224 if('\0' == globals.mCommandLineOptions.m##option_name[arrLoop][0]) \
226 break; \
230 if(arrLoop != array_size) \
232 PR_snprintf(globals.mCommandLineOptions.m##option_name[arrLoop], sizeof(globals.mCommandLineOptions.m##option_name[arrLoop]), "%s", option + strlen(#option_name "=")); \
234 else \
236 REPORT_ERROR_MSG(__LINE__, option); \
237 retval = __LINE__; \
238 globals.mCommandLineOptions.mHelp = PR_TRUE; \
241 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \
242 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
244 const char** expand = NULL; \
246 expand = (const char**)realloc((void*)globals.mCommandLineOptions.m##option_name, sizeof(const char*) * (globals.mCommandLineOptions.m##option_name##Count + 1)); \
247 if(NULL != expand) \
249 globals.mCommandLineOptions.m##option_name = expand; \
250 globals.mCommandLineOptions.m##option_name[globals.mCommandLineOptions.m##option_name##Count] = option + strlen(#option_name "="); \
251 globals.mCommandLineOptions.m##option_name##Count++; \
253 else \
255 retval = __LINE__; \
256 globals.mCommandLineOptions.mHelp = PR_TRUE; \
259 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
260 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
262 PRInt32 scanRes = 0; \
264 scanRes = PR_sscanf(option + strlen(#option_name "="), "%u", &globals.mCommandLineOptions.m##option_name); \
265 if(1 != scanRes) \
267 REPORT_ERROR_MSG(__LINE__, option); \
268 retval = __LINE__; \
269 globals.mCommandLineOptions.mHelp = PR_TRUE; \
272 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
273 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
275 PRInt32 scanRes = 0; \
277 scanRes = PR_sscanf(option + strlen(#option_name "="), "%llu", &globals.mCommandLineOptions.m##option_name##64); \
278 if(1 != scanRes) \
280 REPORT_ERROR_MSG(__LINE__, option); \
281 retval = __LINE__; \
282 globals.mCommandLineOptions.mHelp = PR_TRUE; \
286 #include "stoptions.h"
289 ** If no match on options, this else will get hit.
291 else {
292 REPORT_ERROR_MSG(__LINE__, option);
293 retval = __LINE__;
294 globals.mCommandLineOptions.mHelp = PR_TRUE;
297 else if ('-' == aArgArray[traverse][0]
298 && '\0' != aArgArray[traverse][1]) {
300 ** Show help, bad/legacy option.
302 REPORT_ERROR_MSG(__LINE__, aArgArray[traverse]);
303 retval = __LINE__;
304 globals.mCommandLineOptions.mHelp = PR_TRUE;
306 else {
308 ** Default is same as FileName option, the file to process.
310 PR_snprintf(globals.mCommandLineOptions.mFileName,
311 sizeof(globals.mCommandLineOptions.mFileName), "%s",
312 aArgArray[traverse]);
317 ** initialize the categories
319 initCategories(&globals);
321 return retval;
324 #if ST_WANT_GRAPHS
326 ** createGraph
328 ** Create a GD image with the common properties of a graph.
329 ** Upon return, you normally allocate legend colors,
330 ** draw your graph inside the region
331 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGH-STGD_MARGIN,
332 ** and then call drawGraph to format the surrounding information.
334 ** You should use the normal GD image release function, gdImageDestroy
335 ** when done with it.
337 ** Image attributes:
338 ** STGD_WIDTHxSTGD_HEIGHT
339 ** trasparent (white) background
340 ** incremental display
342 gdImagePtr
343 createGraph(int *aTransparencyColor)
345 gdImagePtr retval = NULL;
347 if (NULL != aTransparencyColor) {
348 *aTransparencyColor = -1;
350 retval = gdImageCreate(STGD_WIDTH, STGD_HEIGHT);
351 if (NULL != retval) {
353 ** Background color (first one).
355 *aTransparencyColor = gdImageColorAllocate(retval, 255, 255, 255);
356 if (-1 != *aTransparencyColor) {
358 ** As transparency.
360 gdImageColorTransparent(retval, *aTransparencyColor);
364 ** And to set interlacing.
366 gdImageInterlace(retval, 1);
368 else {
369 REPORT_ERROR(__LINE__, gdImageCreate);
372 else {
373 REPORT_ERROR(__LINE__, createGraph);
376 return retval;
378 #endif /* ST_WANT_GRAPHS */
380 #if ST_WANT_GRAPHS
382 ** drawGraph
384 ** This function mainly exists to simplify putitng all the pretty lace
385 ** around a home made graph.
387 void
388 drawGraph(gdImagePtr aImage, int aColor,
389 const char *aGraphTitle,
390 const char *aXAxisTitle,
391 const char *aYAxisTitle,
392 PRUint32 aXMarkCount,
393 PRUint32 * aXMarkPercents,
394 const char **aXMarkTexts,
395 PRUint32 aYMarkCount,
396 PRUint32 * aYMarkPercents,
397 const char **aYMarkTexts,
398 PRUint32 aLegendCount,
399 int *aLegendColors, const char **aLegendTexts)
401 if (NULL != aImage && NULL != aGraphTitle &&
402 NULL != aXAxisTitle && NULL != aYAxisTitle &&
403 (0 == aXMarkCount || (NULL != aXMarkPercents && NULL != aXMarkTexts))
404 && (0 == aYMarkCount
405 || (NULL != aYMarkPercents && NULL != aYMarkTexts))
406 && (0 == aLegendCount
407 || (NULL != aLegendColors && NULL != aLegendTexts))) {
408 int margin = 1;
409 PRUint32 traverse = 0;
410 PRUint32 target = 0;
411 const int markSize = 2;
412 int x1 = 0;
413 int y1 = 0;
414 int x2 = 0;
415 int y2 = 0;
416 time_t theTimeT = time(NULL);
417 char *theTime = ctime(&theTimeT);
418 const char *logo = "SpaceTrace";
419 gdFontPtr titleFont = gdFontMediumBold;
420 gdFontPtr markFont = gdFontTiny;
421 gdFontPtr dateFont = gdFontTiny;
422 gdFontPtr axisFont = gdFontSmall;
423 gdFontPtr legendFont = gdFontTiny;
424 gdFontPtr logoFont = gdFontTiny;
427 ** Fixup the color.
428 ** Black by default.
430 if (-1 == aColor) {
431 aColor = gdImageColorAllocate(aImage, 0, 0, 0);
433 if (-1 == aColor) {
434 aColor = gdImageColorClosest(aImage, 0, 0, 0);
438 ** Output the box.
440 x1 = STGD_MARGIN - margin;
441 y1 = STGD_MARGIN - margin;
442 x2 = STGD_WIDTH - x1;
443 y2 = STGD_HEIGHT - y1;
444 gdImageRectangle(aImage, x1, y1, x2, y2, aColor);
445 margin++;
448 ** Need to make small markings on the graph to indicate where the
449 ** labels line up exactly.
450 ** While we're at it, draw the label text.
452 for (traverse = 0; traverse < aXMarkCount; traverse++) {
453 target =
454 ((STGD_WIDTH -
455 (STGD_MARGIN * 2)) * aXMarkPercents[traverse]) / 100;
457 x1 = STGD_MARGIN + target;
458 y1 = STGD_MARGIN - margin;
459 x2 = x1;
460 y2 = y1 - markSize;
461 gdImageLine(aImage, x1, y1, x2, y2, aColor);
463 y1 = STGD_HEIGHT - y1;
464 y2 = STGD_HEIGHT - y2;
465 gdImageLine(aImage, x1, y1, x2, y2, aColor);
467 if (NULL != aXMarkTexts[traverse]) {
468 x1 = STGD_MARGIN + target - (markFont->h / 2);
469 y1 = STGD_HEIGHT - STGD_MARGIN + margin + markSize +
470 (strlen(aXMarkTexts[traverse]) * markFont->w);
471 gdImageStringUp(aImage, markFont, x1, y1,
472 (unsigned char *) aXMarkTexts[traverse],
473 aColor);
476 for (traverse = 0; traverse < aYMarkCount; traverse++) {
477 target =
478 ((STGD_HEIGHT - (STGD_MARGIN * 2)) * (100 -
479 aYMarkPercents
480 [traverse])) / 100;
482 x1 = STGD_MARGIN - margin;
483 y1 = STGD_MARGIN + target;
484 x2 = x1 - markSize;
485 y2 = y1;
486 gdImageLine(aImage, x1, y1, x2, y2, aColor);
488 x1 = STGD_WIDTH - x1;
489 x2 = STGD_WIDTH - x2;
490 gdImageLine(aImage, x1, y1, x2, y2, aColor);
492 if (NULL != aYMarkTexts[traverse]) {
493 x1 = STGD_MARGIN - margin - markSize -
494 (strlen(aYMarkTexts[traverse]) * markFont->w);
495 y1 = STGD_MARGIN + target - (markFont->h / 2);
496 gdImageString(aImage, markFont, x1, y1,
497 (unsigned char *) aYMarkTexts[traverse],
498 aColor);
501 margin += markSize;
504 ** Title will be centered above the image.
506 x1 = (STGD_WIDTH / 2) - ((strlen(aGraphTitle) * titleFont->w) / 2);
507 y1 = ((STGD_MARGIN - margin) / 2) - (titleFont->h / 2);
508 gdImageString(aImage, titleFont, x1, y1,
509 (unsigned char *) aGraphTitle, aColor);
512 ** Upper left will be the date.
514 x1 = 0;
515 y1 = 0;
516 traverse = strlen(theTime) - 1;
517 if (isspace(theTime[traverse])) {
518 theTime[traverse] = '\0';
520 gdImageString(aImage, dateFont, x1, y1, (unsigned char *) theTime,
521 aColor);
524 ** Lower right will be the logo.
526 x1 = STGD_WIDTH - (strlen(logo) * logoFont->w);
527 y1 = STGD_HEIGHT - logoFont->h;
528 gdImageString(aImage, logoFont, x1, y1, (unsigned char *) logo,
529 aColor);
532 ** X and Y axis titles
534 x1 = (STGD_WIDTH / 2) - ((strlen(aXAxisTitle) * axisFont->w) / 2);
535 y1 = STGD_HEIGHT - axisFont->h;
536 gdImageString(aImage, axisFont, x1, y1, (unsigned char *) aXAxisTitle,
537 aColor);
538 x1 = 0;
539 y1 = (STGD_HEIGHT / 2) + ((strlen(aYAxisTitle) * axisFont->w) / 2);
540 gdImageStringUp(aImage, axisFont, x1, y1,
541 (unsigned char *) aYAxisTitle, aColor);
544 ** The legend.
545 ** Centered on the right hand side, going up.
547 x1 = STGD_WIDTH - STGD_MARGIN + margin +
548 (aLegendCount * legendFont->h) / 2;
549 x2 = STGD_WIDTH - (aLegendCount * legendFont->h);
550 if (x1 > x2) {
551 x1 = x2;
554 y1 = 0;
555 for (traverse = 0; traverse < aLegendCount; traverse++) {
556 y2 = (STGD_HEIGHT / 2) +
557 ((strlen(aLegendTexts[traverse]) * legendFont->w) / 2);
558 if (y2 > y1) {
559 y1 = y2;
562 for (traverse = 0; traverse < aLegendCount; traverse++) {
563 gdImageStringUp(aImage, legendFont, x1, y1,
564 (unsigned char *) aLegendTexts[traverse],
565 aLegendColors[traverse]);
566 x1 += legendFont->h;
571 #endif /* ST_WANT_GRAPHS */
573 #if defined(HAVE_BOUTELL_GD)
575 ** pngSink
577 ** GD callback, used to write out the png.
580 pngSink(void *aContext, const char *aBuffer, int aLen)
582 return PR_Write((PRFileDesc *) aContext, aBuffer, aLen);
584 #endif /* HAVE_BOUTELL_GD */
587 ** FormatNumber
589 ** Formats a number with thousands separator. Don't free the result. Returns
590 ** static data.
592 char *
593 FormatNumber(PRInt32 num)
595 static char buf[64];
596 char tmpbuf[64];
597 int len = 0;
598 int bufindex = sizeof(buf) - 1;
599 int mod3;
601 PR_snprintf(tmpbuf, sizeof(tmpbuf), "%d", num);
603 /* now insert the thousands separator */
604 mod3 = 0;
605 len = strlen(tmpbuf);
606 while (len >= 0) {
607 if (tmpbuf[len] >= '0' && tmpbuf[len] <= '9') {
608 if (mod3 == 3) {
609 buf[bufindex--] = ',';
610 mod3 = 0;
612 mod3++;
614 buf[bufindex--] = tmpbuf[len--];
616 return buf + bufindex + 1;
620 ** actualByteSize
622 ** Apply alignment and overhead to size to figure out actual byte size
624 PRUint32
625 actualByteSize(STOptions * inOptions, PRUint32 retval)
628 ** Need to bump the result by our alignment and overhead.
629 ** The idea here is that an allocation actually costs you more than you
630 ** thought.
632 ** The msvcrt malloc has an alignment of 16 with an overhead of 8.
633 ** The win32 HeapAlloc has an alignment of 8 with an overhead of 8.
635 if (0 != retval) {
636 PRUint32 eval = 0;
637 PRUint32 over = 0;
639 eval = retval - 1;
640 if (0 != inOptions->mAlignBy) {
641 over = eval % inOptions->mAlignBy;
643 retval = eval + inOptions->mOverhead + inOptions->mAlignBy - over;
646 return retval;
650 ** byteSize
652 ** Figuring the byte size of an allocation.
653 ** Might expand in the future to report size at a given time.
654 ** For now, just use last relevant event.
656 PRUint32
657 byteSize(STOptions * inOptions, STAllocation * aAlloc)
659 PRUint32 retval = 0;
661 if (NULL != aAlloc && 0 != aAlloc->mEventCount) {
662 PRUint32 index = aAlloc->mEventCount;
665 ** Generally, the size is the last event's size.
667 do {
668 index--;
669 retval = aAlloc->mEvents[index].mHeapSize;
671 while (0 == retval && 0 != index);
673 return actualByteSize(inOptions, retval);
678 ** recalculateAllocationCost
680 ** Given an allocation, does a recalculation of Cost - weight, heapcount etc.
681 ** and does the right thing to propagate the cost upwards.
684 recalculateAllocationCost(STOptions * inOptions, STContext * inContext,
685 STRun * aRun, STAllocation * aAllocation,
686 PRBool updateParent)
689 ** Now, see if they desire a callsite update.
690 ** As mentioned previously, we decide if the run desires us to
691 ** manipulate the callsite data only if its stamp is set.
692 ** We change all callsites and parent callsites to have that
693 ** stamp as well, so as to mark them as being relevant to
694 ** the current run in question.
696 if (NULL != inContext && 0 != aRun->mStats[inContext->mIndex].mStamp) {
697 PRUint32 timeval =
698 aAllocation->mMaxTimeval - aAllocation->mMinTimeval;
699 PRUint32 size = byteSize(inOptions, aAllocation);
700 PRUint64 weight64 = LL_INIT(0, 0);
701 PRUint32 heapCost = aAllocation->mHeapRuntimeCost;
702 PRUint64 timeval64 = LL_INIT(0, 0);
703 PRUint64 size64 = LL_INIT(0, 0);
705 LL_UI2L(timeval64, timeval);
706 LL_UI2L(size64, size);
707 LL_MUL(weight64, timeval64, size64);
710 ** First, update this run.
712 aRun->mStats[inContext->mIndex].mCompositeCount++;
713 aRun->mStats[inContext->mIndex].mHeapRuntimeCost += heapCost;
714 aRun->mStats[inContext->mIndex].mSize += size;
715 LL_ADD(aRun->mStats[inContext->mIndex].mTimeval64,
716 aRun->mStats[inContext->mIndex].mTimeval64, timeval64);
717 LL_ADD(aRun->mStats[inContext->mIndex].mWeight64,
718 aRun->mStats[inContext->mIndex].mWeight64, weight64);
721 ** Use the first event of the allocation to update the parent
722 ** callsites.
723 ** This has positive effect of not updating realloc callsites
724 ** with the same data over and over again.
726 if (updateParent && 0 < aAllocation->mEventCount) {
727 tmcallsite *callsite = aAllocation->mEvents[0].mCallsite;
728 STRun *callsiteRun = NULL;
731 ** Go up parents till we drop.
733 while (NULL != callsite && NULL != callsite->method) {
734 callsiteRun = CALLSITE_RUN(callsite);
735 if (NULL != callsiteRun) {
737 ** Do we init it?
739 if (callsiteRun->mStats[inContext->mIndex].mStamp !=
740 aRun->mStats[inContext->mIndex].mStamp) {
741 memset(&callsiteRun->mStats[inContext->mIndex], 0,
742 sizeof(STCallsiteStats));
743 callsiteRun->mStats[inContext->mIndex].mStamp =
744 aRun->mStats[inContext->mIndex].mStamp;
748 ** Add the values.
749 ** Note that if the allocation was ever realloced,
750 ** we are actually recording the final size.
751 ** Also, the composite count does not include
752 ** calls to realloc (or free for that matter),
753 ** but rather is simply a count of actual heap
754 ** allocation objects, from which someone will
755 ** draw conclusions regarding number of malloc
756 ** and free calls.
757 ** It is possible to generate the exact number
758 ** of calls to free/malloc/realloc should the
759 ** absolute need arise to count them individually,
760 ** but I fear it will take mucho memory and this
761 ** is perhaps good enough for now.
763 callsiteRun->mStats[inContext->mIndex].mCompositeCount++;
764 callsiteRun->mStats[inContext->mIndex].mHeapRuntimeCost +=
765 heapCost;
766 callsiteRun->mStats[inContext->mIndex].mSize += size;
767 LL_ADD(callsiteRun->mStats[inContext->mIndex].mTimeval64,
768 callsiteRun->mStats[inContext->mIndex].mTimeval64,
769 timeval64);
770 LL_ADD(callsiteRun->mStats[inContext->mIndex].mWeight64,
771 callsiteRun->mStats[inContext->mIndex].mWeight64,
772 weight64);
775 callsite = callsite->parent;
780 return 0;
785 ** appendAllocation
787 ** Given a run, append the allocation to it.
788 ** No DUP checks are done.
789 ** Also, we might want to update the parent callsites with stats.
790 ** We decide to do this heavy duty work only if the run we are appending
791 ** to has a non ZERO mStats[].mStamp, meaning that it is asking to track
792 ** such information when it was created.
793 ** Returns !0 on success.
796 appendAllocation(STOptions * inOptions, STContext * inContext,
797 STRun * aRun, STAllocation * aAllocation)
799 int retval = 0;
801 if (NULL != aRun && NULL != aAllocation && NULL != inOptions) {
802 STAllocation **expand = NULL;
805 ** Expand the size of the array if needed.
807 expand = (STAllocation **) realloc(aRun->mAllocations,
808 sizeof(STAllocation *) *
809 (aRun->mAllocationCount + 1));
810 if (NULL != expand) {
812 ** Reassign in case of pointer move.
814 aRun->mAllocations = expand;
817 ** Stick the allocation in.
819 aRun->mAllocations[aRun->mAllocationCount] = aAllocation;
822 ** If this is the global run, we need to let the allocation
823 ** track the index back to us.
825 if (&globals.mRun == aRun) {
826 aAllocation->mRunIndex = aRun->mAllocationCount;
830 ** Increase the count.
832 aRun->mAllocationCount++;
835 ** We're good.
837 retval = __LINE__;
840 ** update allocation cost
842 recalculateAllocationCost(inOptions, inContext, aRun, aAllocation,
843 PR_TRUE);
845 else {
846 REPORT_ERROR(__LINE__, appendAllocation);
849 else {
850 REPORT_ERROR(__LINE__, appendAllocation);
853 return retval;
857 ** hasCallsiteMatch
859 ** Determine if the callsite or the other callsites has the matching text.
861 ** Returns 0 if there is no match.
864 hasCallsiteMatch(tmcallsite * aCallsite, const char *aMatch, int aDirection)
866 int retval = 0;
868 if (NULL != aCallsite && NULL != aCallsite->method &&
869 NULL != aMatch && '\0' != *aMatch) {
870 const char *methodName = NULL;
872 do {
873 methodName = tmmethodnode_name(aCallsite->method);
874 if (NULL != methodName && NULL != strstr(methodName, aMatch)) {
876 ** Contains the text.
878 retval = __LINE__;
879 break;
881 else {
882 switch (aDirection) {
883 case ST_FOLLOW_SIBLINGS:
884 aCallsite = aCallsite->siblings;
885 break;
886 case ST_FOLLOW_PARENTS:
887 aCallsite = aCallsite->parent;
888 break;
889 default:
890 aCallsite = NULL;
891 REPORT_ERROR(__LINE__, hasCallsiteMatch);
892 break;
896 while (NULL != aCallsite && NULL != aCallsite->method);
898 else {
899 REPORT_ERROR(__LINE__, hasCallsiteMatch);
902 return retval;
906 ** harvestRun
908 ** Provide a simply way to go over a run, and yield the relevant allocations.
909 ** The restrictions are easily set via the options page or the command
910 ** line switches.
912 ** On any match, add the allocation to the provided run.
914 ** This makes it much easier for all the code to respect the options in
915 ** force.
917 ** Returns !0 on error, though aOutRun may contain a partial data set.
920 harvestRun(const STRun * aInRun, STRun * aOutRun,
921 STOptions * aOptions, STContext * inContext)
923 int retval = 0;
925 #if defined(DEBUG_dp)
926 PRIntervalTime start = PR_IntervalNow();
928 fprintf(stderr, "DEBUG: harvesting run...\n");
929 #endif
931 if (NULL != aInRun && NULL != aOutRun && aInRun != aOutRun
932 && NULL != aOptions && NULL != inContext) {
933 PRUint32 traverse = 0;
934 STAllocation *current = NULL;
936 for (traverse = 0;
937 0 == retval && traverse < aInRun->mAllocationCount; traverse++) {
938 current = aInRun->mAllocations[traverse];
939 if (NULL != current) {
940 PRUint32 lifetime = 0;
941 PRUint32 bytesize = 0;
942 PRUint64 weight64 = LL_INIT(0, 0);
943 PRUint64 bytesize64 = LL_INIT(0, 0);
944 PRUint64 lifetime64 = LL_INIT(0, 0);
945 int appendRes = 0;
946 int looper = 0;
947 PRBool matched = PR_FALSE;
950 ** Use this as an opportune time to fixup a memory
951 ** leaked timeval, so as to not completely skew
952 ** the weights.
954 if (ST_TIMEVAL_MAX == current->mMaxTimeval) {
955 current->mMaxTimeval = globals.mMaxTimeval;
959 ** Check allocation timeval restrictions.
960 ** We have to slide the recorded timevals to be zero
961 ** based, so that the comparisons make sense.
963 if ((aOptions->mAllocationTimevalMin >
964 (current->mMinTimeval - globals.mMinTimeval)) ||
965 (aOptions->mAllocationTimevalMax <
966 (current->mMinTimeval - globals.mMinTimeval))) {
967 continue;
971 ** Check timeval restrictions.
972 ** We have to slide the recorded timevals to be zero
973 ** based, so that the comparisons make sense.
975 if ((aOptions->mTimevalMin >
976 (current->mMinTimeval - globals.mMinTimeval)) ||
977 (aOptions->mTimevalMax <
978 (current->mMinTimeval - globals.mMinTimeval))) {
979 continue;
983 ** Check lifetime restrictions.
985 lifetime = current->mMaxTimeval - current->mMinTimeval;
986 if ((lifetime < aOptions->mLifetimeMin) ||
987 (lifetime > aOptions->mLifetimeMax)) {
988 continue;
992 ** Check byte size restrictions.
994 bytesize = byteSize(aOptions, current);
995 if ((bytesize < aOptions->mSizeMin) ||
996 (bytesize > aOptions->mSizeMax)) {
997 continue;
1001 ** Check weight restrictions.
1003 LL_UI2L(bytesize64, bytesize);
1004 LL_UI2L(lifetime64, lifetime);
1005 LL_MUL(weight64, bytesize64, lifetime64);
1006 if (LL_UCMP(weight64, <, aOptions->mWeightMin64) ||
1007 LL_UCMP(weight64, >, aOptions->mWeightMax64)) {
1008 continue;
1012 ** Possibly restrict the callsite by text.
1013 ** Do this last, as it is a heavier check.
1015 ** One day, we may need to expand the logic to check for
1016 ** events beyond the initial allocation event.
1018 for (looper = 0; ST_SUBSTRING_MATCH_MAX > looper; looper++) {
1019 if ('\0' != aOptions->mRestrictText[looper][0]) {
1020 if (0 ==
1021 hasCallsiteMatch(current->mEvents[0].mCallsite,
1022 aOptions->mRestrictText[looper],
1023 ST_FOLLOW_PARENTS)) {
1024 break;
1027 else {
1028 matched = PR_TRUE;
1029 break;
1032 if (ST_SUBSTRING_MATCH_MAX == looper) {
1033 matched = PR_TRUE;
1035 if (PR_FALSE == matched) {
1036 continue;
1040 ** You get here, we add to the run.
1042 appendRes =
1043 appendAllocation(aOptions, inContext, aOutRun, current);
1044 if (0 == appendRes) {
1045 retval = __LINE__;
1046 REPORT_ERROR(__LINE__, appendAllocation);
1052 #if defined(DEBUG_dp)
1053 fprintf(stderr, "DEBUG: harvesting ends: %dms [%d allocations]\n",
1054 PR_IntervalToMilliseconds(PR_IntervalNow() - start),
1055 aInRun->mAllocationCount);
1056 #endif
1057 return retval;
1061 ** recalculateRunCost
1063 ** Goes over all allocations of a run and recalculates and propagates
1064 ** the allocation costs - weight, heapcount, size
1067 recalculateRunCost(STOptions * inOptions, STContext * inContext, STRun * aRun)
1069 PRUint32 traverse = 0;
1070 STAllocation *current = NULL;
1072 #if defined(DEBUG_dp)
1073 PRIntervalTime start = PR_IntervalNow();
1075 fprintf(stderr, "DEBUG: recalculateRunCost...\n");
1076 #endif
1078 if (NULL == aRun)
1079 return -1;
1081 /* reset stats of this run to 0 to begin recalculation */
1082 memset(&aRun->mStats[inContext->mIndex], 0, sizeof(STCallsiteStats));
1084 /* reset timestamp to force propogation of cost */
1085 aRun->mStats[inContext->mIndex].mStamp = PR_IntervalNow();
1087 for (traverse = 0; traverse < aRun->mAllocationCount; traverse++) {
1088 current = aRun->mAllocations[traverse];
1089 if (NULL != current) {
1090 recalculateAllocationCost(inOptions, inContext, aRun, current,
1091 PR_TRUE);
1095 #if defined(DEBUG_dp)
1096 fprintf(stderr, "DEBUG: recalculateRunCost ends: %dms [%d allocations]\n",
1097 PR_IntervalToMilliseconds(PR_IntervalNow() - start),
1098 aRun->mAllocationCount);
1099 #endif
1101 return 0;
1106 ** compareAllocations
1108 ** qsort callback.
1109 ** Compare the allocations as specified by the options.
1112 compareAllocations(const void *aAlloc1, const void *aAlloc2, void *aContext)
1114 int retval = 0;
1115 STOptions *inOptions = (STOptions *) aContext;
1117 if (NULL != aAlloc1 && NULL != aAlloc2 && NULL != inOptions) {
1118 STAllocation *alloc1 = *((STAllocation **) aAlloc1);
1119 STAllocation *alloc2 = *((STAllocation **) aAlloc2);
1121 if (NULL != alloc1 && NULL != alloc2) {
1123 ** Logic determined by pref/option.
1125 switch (inOptions->mOrderBy) {
1126 case ST_COUNT:
1128 ** "By count" on a single allocation means nothing,
1129 ** fall through to weight.
1131 case ST_WEIGHT:
1133 PRUint64 weight164 = LL_INIT(0, 0);
1134 PRUint64 weight264 = LL_INIT(0, 0);
1135 PRUint64 bytesize164 = LL_INIT(0, 0);
1136 PRUint64 bytesize264 = LL_INIT(0, 0);
1137 PRUint64 timeval164 = LL_INIT(0, 0);
1138 PRUint64 timeval264 = LL_INIT(0, 0);
1140 LL_UI2L(bytesize164, byteSize(inOptions, alloc1));
1141 LL_UI2L(timeval164,
1142 (alloc1->mMaxTimeval - alloc1->mMinTimeval));
1143 LL_MUL(weight164, bytesize164, timeval164);
1144 LL_UI2L(bytesize264, byteSize(inOptions, alloc2));
1145 LL_UI2L(timeval264,
1146 (alloc2->mMaxTimeval - alloc2->mMinTimeval));
1147 LL_MUL(weight264, bytesize264, timeval264);
1149 if (LL_UCMP(weight164, <, weight264)) {
1150 retval = __LINE__;
1152 else if (LL_UCMP(weight164, >, weight264)) {
1153 retval = -__LINE__;
1156 break;
1158 case ST_SIZE:
1160 PRUint32 size1 = byteSize(inOptions, alloc1);
1161 PRUint32 size2 = byteSize(inOptions, alloc2);
1163 if (size1 < size2) {
1164 retval = __LINE__;
1166 else if (size1 > size2) {
1167 retval = -__LINE__;
1170 break;
1172 case ST_TIMEVAL:
1174 PRUint32 timeval1 =
1175 (alloc1->mMaxTimeval - alloc1->mMinTimeval);
1176 PRUint32 timeval2 =
1177 (alloc2->mMaxTimeval - alloc2->mMinTimeval);
1179 if (timeval1 < timeval2) {
1180 retval = __LINE__;
1182 else if (timeval1 > timeval2) {
1183 retval = -__LINE__;
1186 break;
1188 case ST_HEAPCOST:
1190 PRUint32 cost1 = alloc1->mHeapRuntimeCost;
1191 PRUint32 cost2 = alloc2->mHeapRuntimeCost;
1193 if (cost1 < cost2) {
1194 retval = __LINE__;
1196 else if (cost1 > cost2) {
1197 retval = -__LINE__;
1200 break;
1202 default:
1204 REPORT_ERROR(__LINE__, compareAllocations);
1206 break;
1211 return retval;
1215 ** sortRun
1217 ** Given a run, sort it in the manner specified by the options.
1218 ** Returns !0 on failure.
1221 sortRun(STOptions * inOptions, STRun * aRun)
1223 int retval = 0;
1225 if (NULL != aRun && NULL != inOptions) {
1226 if (NULL != aRun->mAllocations && 0 < aRun->mAllocationCount) {
1227 NS_QuickSort(aRun->mAllocations, aRun->mAllocationCount,
1228 sizeof(STAllocation *), compareAllocations,
1229 inOptions);
1232 else {
1233 retval = __LINE__;
1234 REPORT_ERROR(__LINE__, sortRun);
1237 return retval;
1241 ** createRun
1243 ** Returns a newly allocated run, properly initialized.
1244 ** Must call freeRun() with the new STRun.
1246 ** ONLY PASS IN A NON_ZERO STAMP IF YOU KNOW WHAT YOU ARE DOING!!!
1247 ** A non zero stamp in a run has side effects all over the
1248 ** callsites of the allocations added to the run and their
1249 ** parents.
1251 ** Returns NULL on failure.
1253 STRun *
1254 createRun(STContext * inContext, PRUint32 aStamp)
1256 STRun *retval = NULL;
1258 retval = (STRun *) calloc(1, sizeof(STRun));
1259 if (NULL != retval) {
1260 retval->mStats =
1261 (STCallsiteStats *) calloc(globals.mCommandLineOptions.mContexts,
1262 sizeof(STCallsiteStats));
1263 if (NULL != retval->mStats) {
1264 if (NULL != inContext) {
1265 retval->mStats[inContext->mIndex].mStamp = aStamp;
1268 else {
1269 free(retval);
1270 retval = NULL;
1274 return retval;
1278 ** freeRun
1280 ** Free off the run and the associated data.
1282 void
1283 freeRun(STRun * aRun)
1285 if (NULL != aRun) {
1286 if (NULL != aRun->mAllocations) {
1288 ** We do not free the allocations themselves.
1289 ** They are likely pointed to by at least 2 other existing
1290 ** runs.
1292 free(aRun->mAllocations);
1293 aRun->mAllocations = NULL;
1296 if (NULL != aRun->mStats) {
1297 free(aRun->mStats);
1298 aRun->mStats = NULL;
1301 free(aRun);
1302 aRun = NULL;
1307 ** createRunFromGlobal
1309 ** Harvest the global run, then sort it.
1310 ** Returns NULL on failure.
1311 ** Must call freeRun() with the new STRun.
1313 STRun *
1314 createRunFromGlobal(STOptions * inOptions, STContext * inContext)
1316 STRun *retval = NULL;
1318 if (NULL != inOptions && NULL != inContext) {
1320 ** We stamp the run.
1321 ** As things are appended to it, it realizes that it should stamp the
1322 ** callsite backtrace with the information as well.
1323 ** In this manner, we can provide meaningful callsite data.
1325 retval = createRun(inContext, PR_IntervalNow());
1327 if (NULL != retval) {
1328 STCategoryNode *node = NULL;
1329 int failure = 0;
1330 int harvestRes =
1331 harvestRun(&globals.mRun, retval, inOptions, inContext);
1332 if (0 == harvestRes) {
1333 int sortRes = sortRun(inOptions, retval);
1335 if (0 != sortRes) {
1336 failure = __LINE__;
1339 else {
1340 failure = __LINE__;
1344 if (0 != failure) {
1345 freeRun(retval);
1346 retval = NULL;
1348 REPORT_ERROR(failure, createRunFromGlobal);
1352 ** Categorize the run.
1354 failure = categorizeRun(inOptions, inContext, retval, &globals);
1355 if (0 != failure) {
1356 REPORT_ERROR(__LINE__, categorizeRun);
1360 ** if we are focussing on a category, return that run instead of
1361 ** the harvested run. Make sure to recalculate cost.
1363 node = findCategoryNode(inOptions->mCategoryName, &globals);
1364 if (node) {
1365 /* Recalculate cost of run */
1366 recalculateRunCost(inOptions, inContext,
1367 node->runs[inContext->mIndex]);
1369 retval = node->runs[inContext->mIndex];
1373 else {
1374 REPORT_ERROR(__LINE__, createRunFromGlobal);
1377 return retval;
1381 ** getLiveAllocationByHeapID
1383 ** Go through a run and find the right heap ID.
1384 ** At the time of the call to this function, the allocation must be LIVE,
1385 ** meaning that it can not be freed.
1386 ** Go through the run backwards, in hopes of finding it near the end.
1388 ** Returns the allocation on success, otherwise NULL.
1390 STAllocation *
1391 getLiveAllocationByHeapID(STRun * aRun, PRUint32 aHeapID)
1393 STAllocation *retval = NULL;
1395 if (NULL != aRun && 0 != aHeapID) {
1396 PRUint32 traverse = aRun->mAllocationCount;
1397 STAllocation *eval = NULL;
1400 ** Go through in reverse order.
1401 ** Stop when we have a return value.
1403 while (0 < traverse && NULL == retval) {
1405 ** Back up one to align with zero based index.
1407 traverse--;
1410 ** Take the pointer math out of further operations.
1412 eval = aRun->mAllocations[traverse];
1415 ** Take a look at the events in reverse order.
1416 ** Basically the last event must NOT be a free.
1417 ** The last event must NOT be a realloc of size zero (free).
1418 ** Otherwise, try to match up the heapID of the event.
1420 if (0 != eval->mEventCount) {
1421 STAllocEvent *event = eval->mEvents + (eval->mEventCount - 1);
1423 switch (event->mEventType) {
1424 case TM_EVENT_FREE:
1427 ** No freed allocation can match.
1430 break;
1432 case TM_EVENT_REALLOC:
1433 case TM_EVENT_CALLOC:
1434 case TM_EVENT_MALLOC:
1437 ** Heap IDs must match.
1439 if (aHeapID == event->mHeapID) {
1440 retval = eval;
1443 break;
1445 default:
1447 REPORT_ERROR(__LINE__, getAllocationByHeapID);
1449 break;
1454 else {
1455 REPORT_ERROR(__LINE__, getAllocationByHeapID);
1458 return retval;
1462 ** appendEvent
1464 ** Given an allocation, append a new event to its lifetime.
1465 ** Returns the new event on success, otherwise NULL.
1467 STAllocEvent *
1468 appendEvent(STAllocation * aAllocation, PRUint32 aTimeval, char aEventType,
1469 PRUint32 aHeapID, PRUint32 aHeapSize, tmcallsite * aCallsite)
1471 STAllocEvent *retval = NULL;
1473 if (NULL != aAllocation && NULL != aCallsite) {
1474 STAllocEvent *expand = NULL;
1477 ** Expand the allocation's event array.
1479 expand =
1480 (STAllocEvent *) realloc(aAllocation->mEvents,
1481 sizeof(STAllocEvent) *
1482 (aAllocation->mEventCount + 1));
1483 if (NULL != expand) {
1485 ** Reassign in case of pointer move.
1487 aAllocation->mEvents = expand;
1490 ** Remove the pointer math from rest of code.
1492 retval = aAllocation->mEvents + aAllocation->mEventCount;
1495 ** Increase event array count.
1497 aAllocation->mEventCount++;
1500 ** Fill in the event.
1502 retval->mTimeval = aTimeval;
1503 retval->mEventType = aEventType;
1504 retval->mHeapID = aHeapID;
1505 retval->mHeapSize = aHeapSize;
1506 retval->mCallsite = aCallsite;
1509 ** Allocation may need to update idea of lifetime.
1510 ** See allocationTracker to see mMinTimeval inited to ST_TIMEVAL_MAX.
1512 if (aAllocation->mMinTimeval > aTimeval) {
1513 aAllocation->mMinTimeval = aTimeval;
1517 ** This a free event?
1518 ** Can only set max timeval on a free.
1519 ** Otherwise, mMaxTimeval remains ST_TIMEVAL_MAX.
1520 ** Set in allocationTracker.
1522 if (TM_EVENT_FREE == aEventType) {
1523 aAllocation->mMaxTimeval = aTimeval;
1526 else {
1527 REPORT_ERROR(__LINE__, appendEvent);
1530 else {
1531 REPORT_ERROR(__LINE__, appendEvent);
1534 return retval;
1538 ** hasAllocation
1540 ** Determine if a given run has an allocation.
1541 ** This is really nothing more than a pointer comparison loop.
1542 ** Returns !0 if the run has the allocation.
1545 hasAllocation(STRun * aRun, STAllocation * aTestFor)
1547 int retval = 0;
1549 if (NULL != aRun && NULL != aTestFor) {
1550 PRUint32 traverse = aRun->mAllocationCount;
1553 ** Go through reverse, in the hopes it exists nearer the end.
1555 while (0 < traverse) {
1557 ** Back up.
1559 traverse--;
1561 if (aTestFor == aRun->mAllocations[traverse]) {
1562 retval = __LINE__;
1563 break;
1567 else {
1568 REPORT_ERROR(__LINE__, hasAllocation);
1571 return retval;
1575 ** allocationTracker
1577 ** Important to keep track of all allocations unique so as to determine
1578 ** their lifetimes.
1580 ** Returns a pointer to the allocation on success.
1581 ** Return NULL on failure.
1583 STAllocation *
1584 allocationTracker(PRUint32 aTimeval, char aType, PRUint32 aHeapRuntimeCost,
1585 tmcallsite * aCallsite, PRUint32 aHeapID, PRUint32 aSize,
1586 tmcallsite * aOldCallsite, PRUint32 aOldHeapID,
1587 PRUint32 aOldSize)
1589 STAllocation *retval = NULL;
1590 static int compactor = 1;
1591 const int frequency = 10000;
1592 PRUint32 actualSize, actualOldSize = 0;
1594 actualSize = actualByteSize(&globals.mCommandLineOptions, aSize);
1595 if (aOldSize)
1596 actualOldSize =
1597 actualByteSize(&globals.mCommandLineOptions, aOldSize);
1599 if (NULL != aCallsite) {
1600 int newAllocation = 0;
1601 tmcallsite *searchCallsite = NULL;
1602 PRUint32 searchHeapID = 0;
1603 STAllocation *allocation = NULL;
1606 ** Global operation ID increases.
1608 globals.mOperationCount++;
1611 ** Fix up the timevals if needed.
1613 if (aTimeval < globals.mMinTimeval) {
1614 globals.mMinTimeval = aTimeval;
1616 if (aTimeval > globals.mMaxTimeval) {
1617 globals.mMaxTimeval = aTimeval;
1620 switch (aType) {
1621 case TM_EVENT_FREE:
1624 ** Update the global counter.
1626 globals.mFreeCount++;
1629 ** Update our peak memory used counter
1631 globals.mMemoryUsed -= actualSize;
1634 ** Not a new allocation, will need to search passed in site
1635 ** for the original allocation.
1637 searchCallsite = aCallsite;
1638 searchHeapID = aHeapID;
1640 break;
1642 case TM_EVENT_MALLOC:
1645 ** Update the global counter.
1647 globals.mMallocCount++;
1650 ** Update our peak memory used counter
1652 globals.mMemoryUsed += actualSize;
1653 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) {
1654 globals.mPeakMemoryUsed = globals.mMemoryUsed;
1658 ** This will be a new allocation.
1660 newAllocation = __LINE__;
1662 break;
1664 case TM_EVENT_CALLOC:
1667 ** Update the global counter.
1669 globals.mCallocCount++;
1672 ** Update our peak memory used counter
1674 globals.mMemoryUsed += actualSize;
1675 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) {
1676 globals.mPeakMemoryUsed = globals.mMemoryUsed;
1680 ** This will be a new allocation.
1682 newAllocation = __LINE__;
1684 break;
1686 case TM_EVENT_REALLOC:
1689 ** Update the global counter.
1691 globals.mReallocCount++;
1694 ** Update our peak memory used counter
1696 globals.mMemoryUsed += actualSize - actualOldSize;
1697 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) {
1698 globals.mPeakMemoryUsed = globals.mMemoryUsed;
1702 ** This might be a new allocation.
1704 if (NULL == aOldCallsite) {
1705 newAllocation = __LINE__;
1707 else {
1709 ** Need to search for the original callsite for the
1710 ** index to the allocation.
1712 searchCallsite = aOldCallsite;
1713 searchHeapID = aOldHeapID;
1716 break;
1718 default:
1720 REPORT_ERROR(__LINE__, allocationTracker);
1722 break;
1726 ** We are either modifying an existing allocation or we are creating
1727 ** a new one.
1729 if (0 != newAllocation) {
1730 allocation = (STAllocation *) calloc(1, sizeof(STAllocation));
1731 if (NULL != allocation) {
1733 ** Fixup the min timeval so if logic later will just work.
1735 allocation->mMinTimeval = ST_TIMEVAL_MAX;
1736 allocation->mMaxTimeval = ST_TIMEVAL_MAX;
1739 else if (NULL != searchCallsite
1740 && NULL != CALLSITE_RUN(searchCallsite)
1741 && 0 != searchHeapID) {
1743 ** We know what to search for, and we reduce what we search
1744 ** by only looking for those allocations at a known callsite.
1746 allocation =
1747 getLiveAllocationByHeapID(CALLSITE_RUN(searchCallsite),
1748 searchHeapID);
1750 else {
1751 REPORT_ERROR(__LINE__, allocationTracker);
1754 if (NULL != allocation) {
1755 STAllocEvent *appendResult = NULL;
1758 ** Record the amount of time this allocation event took.
1760 allocation->mHeapRuntimeCost += aHeapRuntimeCost;
1763 ** Now that we have an allocation, we need to make sure it has
1764 ** the proper event.
1766 appendResult =
1767 appendEvent(allocation, aTimeval, aType, aHeapID, aSize,
1768 aCallsite);
1769 if (NULL != appendResult) {
1770 if (0 != newAllocation) {
1771 int runAppendResult = 0;
1772 int callsiteAppendResult = 0;
1775 ** A new allocation needs to be added to the global run.
1776 ** A new allocation needs to be added to the callsite.
1778 runAppendResult =
1779 appendAllocation(&globals.mCommandLineOptions, NULL,
1780 &globals.mRun, allocation);
1781 callsiteAppendResult =
1782 appendAllocation(&globals.mCommandLineOptions, NULL,
1783 CALLSITE_RUN(aCallsite), allocation);
1784 if (0 != runAppendResult && 0 != callsiteAppendResult) {
1786 ** Success.
1788 retval = allocation;
1790 else {
1791 REPORT_ERROR(__LINE__, appendAllocation);
1794 else {
1796 ** An existing allocation, if a realloc situation,
1797 ** may need to be added to the new callsite.
1798 ** This can only occur if the new and old callsites
1799 ** differ.
1800 ** Even then, a brute force check will need to be made
1801 ** to ensure the allocation was not added twice;
1802 ** consider a realloc scenario where two different
1803 ** call stacks bump the allocation back and forth.
1805 if (aCallsite != searchCallsite) {
1806 int found = 0;
1808 found =
1809 hasAllocation(CALLSITE_RUN(aCallsite),
1810 allocation);
1811 if (0 == found) {
1812 int appendResult = 0;
1814 appendResult =
1815 appendAllocation(&globals.mCommandLineOptions,
1816 NULL,
1817 CALLSITE_RUN(aCallsite),
1818 allocation);
1819 if (0 != appendResult) {
1821 ** Success.
1823 retval = allocation;
1825 else {
1826 REPORT_ERROR(__LINE__, appendAllocation);
1829 else {
1831 ** Already there.
1833 retval = allocation;
1836 else {
1838 ** Success.
1840 retval = allocation;
1844 else {
1845 REPORT_ERROR(__LINE__, appendEvent);
1848 else {
1849 REPORT_ERROR(__LINE__, allocationTracker);
1852 else {
1853 REPORT_ERROR(__LINE__, allocationTracker);
1857 ** Compact the heap a bit if you can.
1859 compactor++;
1860 if (0 == (compactor % frequency)) {
1861 heapCompact();
1864 return retval;
1868 ** trackEvent
1870 ** An allocation event has dropped in on us.
1871 ** We need to do the right thing and track it.
1873 void
1874 trackEvent(PRUint32 aTimeval, char aType, PRUint32 aHeapRuntimeCost,
1875 tmcallsite * aCallsite, PRUint32 aHeapID, PRUint32 aSize,
1876 tmcallsite * aOldCallsite, PRUint32 aOldHeapID, PRUint32 aOldSize)
1878 if (NULL != aCallsite) {
1880 ** Verify the old callsite just in case.
1882 if (NULL != CALLSITE_RUN(aCallsite)
1883 && (NULL == aOldCallsite || NULL != CALLSITE_RUN(aOldCallsite))) {
1884 STAllocation *allocation = NULL;
1887 ** Add to the allocation tracking code.
1889 allocation =
1890 allocationTracker(aTimeval, aType, aHeapRuntimeCost,
1891 aCallsite, aHeapID, aSize, aOldCallsite,
1892 aOldHeapID, aOldSize);
1894 if (NULL == allocation) {
1895 REPORT_ERROR(__LINE__, allocationTracker);
1898 else {
1899 REPORT_ERROR(__LINE__, trackEvent);
1902 else {
1903 REPORT_ERROR(__LINE__, trackEvent);
1908 ** tmEventHandler
1910 ** Callback from the tmreader_eventloop function.
1911 ** Simply tries to sort out what we desire to know.
1914 static const char spinner_chars[] = { '/', '-', '\\', '|' };
1916 #define SPINNER_UPDATE_FREQUENCY 4096
1917 #define SPINNER_CHAR_COUNT (sizeof(spinner_chars) / sizeof(spinner_chars[0]))
1918 #define SPINNER_CHAR(_x) spinner_chars[(_x / SPINNER_UPDATE_FREQUENCY) % SPINNER_CHAR_COUNT]
1920 void
1921 tmEventHandler(tmreader * aReader, tmevent * aEvent)
1923 static event_count = 0; /* for spinner */
1924 if ((event_count++ % SPINNER_UPDATE_FREQUENCY) == 0)
1925 printf("\rReading... %c", SPINNER_CHAR(event_count));
1927 if (NULL != aReader && NULL != aEvent) {
1928 switch (aEvent->type) {
1930 ** Events we ignore.
1932 case TM_EVENT_LIBRARY:
1933 case TM_EVENT_METHOD:
1934 case TM_EVENT_STATS:
1935 case TM_EVENT_TIMESTAMP:
1936 case TM_EVENT_FILENAME:
1937 break;
1940 ** Allocation events need to be tracked.
1942 case TM_EVENT_MALLOC:
1943 case TM_EVENT_CALLOC:
1944 case TM_EVENT_REALLOC:
1945 case TM_EVENT_FREE:
1947 PRUint32 oldptr = 0;
1948 PRUint32 oldsize = 0;
1949 tmcallsite *callsite = NULL;
1950 tmcallsite *oldcallsite = NULL;
1952 if (TM_EVENT_REALLOC == aEvent->type) {
1954 ** Only care about old arguments if there were any.
1956 if (0 != aEvent->u.alloc.oldserial) {
1957 oldptr = aEvent->u.alloc.oldptr;
1958 oldsize = aEvent->u.alloc.oldsize;
1959 oldcallsite =
1960 tmreader_callsite(aReader,
1961 aEvent->u.alloc.oldserial);
1962 if (NULL == oldcallsite) {
1963 REPORT_ERROR(__LINE__, tmreader_callsite);
1968 callsite = tmreader_callsite(aReader, aEvent->serial);
1969 if (NULL != callsite) {
1971 ** Verify a callsite run is there.
1972 ** If not, we are ignoring this callsite.
1974 if (NULL != CALLSITE_RUN(callsite)) {
1975 char eventType = aEvent->type;
1976 PRUint32 eventSize = aEvent->u.alloc.size;
1979 ** Play a nasty trick on reallocs of size zero.
1980 ** They are to become free events, adjust the size accordingly.
1981 ** This allows me to avoid all types of special case code.
1983 if (0 == aEvent->u.alloc.size
1984 && TM_EVENT_REALLOC == aEvent->type) {
1985 eventType = TM_EVENT_FREE;
1986 if (0 != aEvent->u.alloc.oldserial) {
1987 eventSize = aEvent->u.alloc.oldsize;
1990 trackEvent(ticks2msec
1991 (aReader, aEvent->u.alloc.interval),
1992 eventType, ticks2usec(aReader,
1993 aEvent->u.alloc.
1994 cost), callsite,
1995 aEvent->u.alloc.ptr, eventSize,
1996 oldcallsite, oldptr, oldsize);
1999 else {
2000 REPORT_ERROR(__LINE__, tmreader_callsite);
2003 break;
2006 ** Callsite, set up the callsite run if it does not exist.
2008 case TM_EVENT_CALLSITE:
2010 tmcallsite *callsite =
2011 tmreader_callsite(aReader, aEvent->serial);
2013 if (NULL != callsite) {
2014 if (NULL == CALLSITE_RUN(callsite)) {
2015 int createrun = __LINE__;
2017 #if defined(MOZILLA_CLIENT)
2019 ** For a mozilla spacetrace, ignore this particular
2020 ** callsite as it is just noise, and causes us to
2021 ** use a lot of memory.
2023 ** This callsite is present on the linux build,
2024 ** not sure if the other platforms have it.
2026 if (0 !=
2027 hasCallsiteMatch(callsite, "g_main_is_running",
2028 ST_FOLLOW_PARENTS)) {
2029 createrun = 0;
2031 #endif /* MOZILLA_CLIENT */
2033 if (0 != createrun) {
2034 callsite->data = createRun(NULL, 0);
2038 else {
2039 REPORT_ERROR(__LINE__, tmreader_callsite);
2042 break;
2045 ** Unhandled events should not be allowed.
2047 default:
2049 REPORT_ERROR(__LINE__, tmEventHandler);
2051 break;
2057 ** optionGetDataOut
2059 ** Output option get data.
2061 void
2062 optionGetDataOut(PRFileDesc * inFD, STOptions * inOptions)
2064 if (NULL != inFD && NULL != inOptions) {
2065 int mark = 0;
2067 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
2068 PR_fprintf(inFD, "%s%s=%d", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name);
2069 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
2070 PR_fprintf(inFD, "%s%s=%s", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name);
2071 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
2073 PRUint32 loop = 0; \
2075 for(loop = 0; loop < array_size; loop++) \
2077 PR_fprintf(inFD, "%s%s=%s", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name[loop]); \
2080 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */
2081 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
2082 PR_fprintf(inFD, "%s%s=%u", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name / multiplier);
2083 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
2085 PRUint64 def64 = default_value; \
2086 PRUint64 mul64 = multiplier; \
2087 PRUint64 div64; \
2089 LL_DIV(div64, inOptions->m##option_name##64, mul64); \
2090 PR_fprintf(inFD, "%s%s=%llu", (0 == mark++) ? "?" : "&", #option_name, div64); \
2093 #include "stoptions.h"
2098 ** htmlAnchor
2100 ** Output an HTML anchor, or just the text depending on the mode.
2102 void
2103 htmlAnchor(STRequest * inRequest,
2104 const char *aHref,
2105 const char *aText,
2106 const char *aTarget, const char *aClass, STOptions * inOptions)
2108 if (NULL != aHref && '\0' != *aHref && NULL != aText && '\0' != *aText) {
2109 int anchorLive = 1;
2112 ** In batch mode, we need to verify the anchor is live.
2114 if (0 != inRequest->mOptions.mBatchRequestCount) {
2115 PRUint32 loop = 0;
2116 int comparison = 1;
2118 for (loop = 0; loop < inRequest->mOptions.mBatchRequestCount;
2119 loop++) {
2120 comparison =
2121 strcmp(aHref, inRequest->mOptions.mBatchRequest[loop]);
2122 if (0 == comparison) {
2123 break;
2128 ** Did we find it?
2130 if (0 == comparison) {
2131 anchorLive = 0;
2136 ** In any mode, don't make an href to the current page.
2138 if (0 != anchorLive && NULL != inRequest->mGetFileName) {
2139 if (0 == strcmp(aHref, inRequest->mGetFileName)) {
2140 anchorLive = 0;
2145 ** Do the right thing.
2147 if (0 != anchorLive) {
2148 PR_fprintf(inRequest->mFD, "<a class=\"%s\" ", aClass);
2149 if (NULL != aTarget && '\0' != *aTarget) {
2150 PR_fprintf(inRequest->mFD, "target=\"%s\" ", aTarget);
2152 PR_fprintf(inRequest->mFD, "href=\"./%s", aHref);
2155 ** The options, if desired, get appended as form data.
2157 optionGetDataOut(inRequest->mFD, inOptions);
2159 PR_fprintf(inRequest->mFD, "\">%s</a>\n", aText);
2161 else {
2162 PR_fprintf(inRequest->mFD, "<span class=\"%s\">%s</span>\n",
2163 aClass, aText);
2166 else {
2167 REPORT_ERROR(__LINE__, htmlAnchor);
2172 ** htmlAllocationAnchor
2174 ** Output an html achor that will resolve to the allocation in question.
2176 void
2177 htmlAllocationAnchor(STRequest * inRequest, STAllocation * aAllocation,
2178 const char *aText)
2180 if (NULL != aAllocation && NULL != aText && '\0' != *aText) {
2181 char buffer[128];
2184 ** This is a total hack.
2185 ** The filename contains the index of the allocation in globals.mRun.
2186 ** Safer than using the raw pointer value....
2188 PR_snprintf(buffer, sizeof(buffer), "allocation_%u.html",
2189 aAllocation->mRunIndex);
2191 htmlAnchor(inRequest, buffer, aText, NULL, "allocation",
2192 &inRequest->mOptions);
2194 else {
2195 REPORT_ERROR(__LINE__, htmlAllocationAnchor);
2200 ** resolveSourceFile
2202 ** Easy way to get a readable/short name.
2203 ** NULL if not present, not resolvable.
2205 const char *
2206 resolveSourceFile(tmmethodnode * aMethod)
2208 const char *retval = NULL;
2210 if (NULL != aMethod) {
2211 const char *methodSays = NULL;
2213 methodSays = aMethod->sourcefile;
2215 if (NULL != methodSays && '\0' != methodSays[0]
2216 && 0 != strcmp("noname", methodSays)) {
2217 retval = strrchr(methodSays, '/');
2218 if (NULL != retval) {
2219 retval++;
2221 else {
2222 retval = methodSays;
2227 return retval;
2231 ** htmlCallsiteAnchor
2233 ** Output an html anchor that will resolve to the callsite in question.
2234 ** If no text is provided, we provide our own.
2236 ** RealName determines whether or not we crawl our parents until the point
2237 ** we no longer match stats.
2239 void
2240 htmlCallsiteAnchor(STRequest * inRequest, tmcallsite * aCallsite,
2241 const char *aText, int aRealName)
2243 if (NULL != aCallsite) {
2244 char textBuf[512];
2245 char hrefBuf[128];
2246 tmcallsite *namesite = aCallsite;
2249 ** Should we use a different name?
2251 if (0 == aRealName && NULL != namesite->parent
2252 && NULL != namesite->parent->method) {
2253 STRun *myRun = NULL;
2254 STRun *upRun = NULL;
2256 do {
2257 myRun = CALLSITE_RUN(namesite);
2258 upRun = CALLSITE_RUN(namesite->parent);
2260 if (0 !=
2261 memcmp(&myRun->mStats[inRequest->mContext->mIndex],
2262 &upRun->mStats[inRequest->mContext->mIndex],
2263 sizeof(STCallsiteStats))) {
2265 ** Doesn't match, stop.
2267 break;
2269 else {
2271 ** Matches, keep going up.
2273 namesite = namesite->parent;
2276 while (NULL != namesite->parent
2277 && NULL != namesite->parent->method);
2281 ** If no text, provide our own.
2283 if (NULL == aText || '\0' == *aText) {
2284 const char *methodName = NULL;
2285 const char *sourceFile = NULL;
2287 if (NULL != namesite->method) {
2288 methodName = tmmethodnode_name(namesite->method);
2290 else {
2291 methodName = "==NONAME==";
2295 ** Decide which format to use to identify the callsite.
2296 ** If we can detect availability, hook up the filename with lxr information.
2298 sourceFile = resolveSourceFile(namesite->method);
2299 if (NULL != sourceFile
2300 && 0 == strncmp("mozilla/", namesite->method->sourcefile,
2301 8)) {
2302 char lxrHREFBuf[512];
2304 PR_snprintf(lxrHREFBuf, sizeof(lxrHREFBuf),
2305 " [<a href=\"http://lxr.mozilla.org/mozilla/source/%s#%u\" class=\"lxr\" target=\"_st_lxr\">%s:%u</a>]",
2306 namesite->method->sourcefile + 8,
2307 namesite->method->linenumber, sourceFile,
2308 namesite->method->linenumber);
2309 PR_snprintf(textBuf, sizeof(textBuf),
2310 "<span class=\"source mozilla-source\">%s</span>%s",
2311 methodName, lxrHREFBuf);
2313 else if (NULL != sourceFile) {
2314 PR_snprintf(textBuf, sizeof(textBuf),
2315 "<span class=\"source external-source\">%s [<span class=\"source-extra\">%s:%u</span>]</span>",
2316 methodName, sourceFile,
2317 namesite->method->linenumber);
2319 else {
2320 PR_snprintf(textBuf, sizeof(textBuf),
2321 "<span class=\"source binary-source\">%s [<span class=\"source-extra\">+%u(%u)</span>]</span>",
2322 methodName, namesite->offset,
2323 (PRUint32) namesite->entry.key);
2326 aText = textBuf;
2329 PR_snprintf(hrefBuf, sizeof(hrefBuf), "callsite_%u.html",
2330 (PRUint32) aCallsite->entry.key);
2332 htmlAnchor(inRequest, hrefBuf, aText, NULL, "callsite",
2333 &inRequest->mOptions);
2335 else {
2336 REPORT_ERROR(__LINE__, htmlCallsiteAnchor);
2341 ** htmlHeader
2343 ** Output a standard header in the report files.
2345 void
2346 htmlHeader(STRequest * inRequest, const char *aTitle)
2348 PR_fprintf(inRequest->mFD,
2349 "<html>\n"
2350 "<head>\n"
2351 "<title>%s</title>\n"
2352 "<link rel=\"stylesheet\" href=\"spacetrace.css\" type=\"text/css\""
2353 "</head>\n"
2354 "<body>\n"
2355 "<div class=spacetrace-header>\n"
2356 "<span class=spacetrace-title>Spacetrace</span>"
2357 "<span class=navigate>\n"
2358 "<span class=\"category-title header-text\">Category:</span>\n"
2359 "<span class=\"current-category\">%s</span>\n",
2360 aTitle, inRequest->mOptions.mCategoryName);
2362 PR_fprintf(inRequest->mFD, "<span class=\"header-item\">");
2363 htmlAnchor(inRequest, "index.html", "Index", NULL, "header-menuitem",
2364 &inRequest->mOptions);
2365 PR_fprintf(inRequest->mFD, "</span>\n");
2367 PR_fprintf(inRequest->mFD, "<span class=\"header-item\">");
2368 htmlAnchor(inRequest, "options.html", "Options", NULL, "header-menuitem",
2369 &inRequest->mOptions);
2370 PR_fprintf(inRequest->mFD, "</span>\n");
2372 PR_fprintf(inRequest->mFD, "</span>\n"); /* class=navigate */
2374 PR_fprintf(inRequest->mFD,
2375 "</div>\n\n<div class=\"header-separator\"></div>\n\n");
2379 ** htmlFooter
2381 ** Output a standard footer in the report file.
2383 void
2384 htmlFooter(STRequest * inRequest)
2386 PR_fprintf(inRequest->mFD,
2387 "<div class=\"footer-separator\"></div>\n\n"
2388 "<div class=\"footer\">\n"
2389 "<span class=\"footer-text\">SpaceTrace</span>\n"
2390 "</div>\n\n" "</body>\n" "</html>\n");
2394 ** htmlNotFound
2396 ** Not found message.
2398 void
2399 htmlNotFound(STRequest * inRequest)
2401 htmlHeader(inRequest, "File Not Found");
2402 PR_fprintf(inRequest->mFD, "File Not Found\n");
2403 htmlFooter(inRequest);
2406 void
2407 htmlStartTable(STRequest* inRequest,
2408 const char* table_class,
2409 const char* id,
2410 const char* caption,
2411 const char * const headers[], PRUint32 header_length)
2413 PRUint32 i;
2415 PR_fprintf(inRequest->mFD,
2416 "<div id=\"%s\"><table class=\"data %s\">\n"
2417 " <caption>%s</caption>"
2418 " <thead>\n"
2419 " <tr class=\"row-header\">\n", id,
2420 table_class ? table_class : "",
2421 caption);
2423 for (i=0; i< header_length; i++)
2424 PR_fprintf(inRequest->mFD,
2425 " <th>%s</th>\n", headers[i]);
2427 PR_fprintf(inRequest->mFD, " </tr> </thead> <tbody>\n");
2431 ** callsiteArrayFromCallsite
2433 ** Simply return an array of the callsites divulged from the site passed in,
2434 ** including the site passed in.
2435 ** Do not worry about dups, or the order of the items.
2437 ** Returns the number of items in the array.
2438 ** If the same as aExistingCount, then nothing happened.
2440 PRUint32
2441 callsiteArrayFromCallsite(tmcallsite *** aArray, PRUint32 aExistingCount,
2442 tmcallsite * aSite, int aFollow)
2444 PRUint32 retval = 0;
2446 if (NULL != aArray && NULL != aSite) {
2447 tmcallsite **expand = NULL;
2450 ** If we have an existing count, we just keep expanding this.
2452 retval = aExistingCount;
2455 ** Go through every allocation.
2457 do {
2459 ** expand the array.
2461 expand =
2462 (tmcallsite **) realloc(*aArray,
2463 sizeof(tmcallsite *) * (retval + 1));
2464 if (NULL != expand) {
2466 ** Set the callsite in case of pointer move.
2468 *aArray = expand;
2471 ** Assign the value.
2473 (*aArray)[retval] = aSite;
2474 retval++;
2476 else {
2477 REPORT_ERROR(__LINE__, realloc);
2478 break;
2483 ** What do we follow?
2485 switch (aFollow) {
2486 case ST_FOLLOW_SIBLINGS:
2487 aSite = aSite->siblings;
2488 break;
2489 case ST_FOLLOW_PARENTS:
2490 aSite = aSite->parent;
2491 break;
2492 default:
2493 aSite = NULL;
2494 REPORT_ERROR(__LINE__, callsiteArrayFromCallsite);
2495 break;
2498 while (NULL != aSite && NULL != aSite->method);
2501 return retval;
2505 ** callsiteArrayFromRun
2507 ** Simply return an array of the callsites from the run allocations.
2508 ** We only pay attention to callsites that were not free callsites.
2509 ** Do not worry about dups, or the order of the items.
2511 ** Returns the number of items in the array.
2512 ** If the same as aExistingCount, then nothing happened.
2514 PRUint32
2515 callsiteArrayFromRun(tmcallsite *** aArray, PRUint32 aExistingCount,
2516 STRun * aRun)
2518 PRUint32 retval = 0;
2520 if (NULL != aArray && NULL != aRun && 0 < aRun->mAllocationCount) {
2521 PRUint32 allocLoop = 0;
2522 PRUint32 eventLoop = 0;
2523 int stopLoops = 0;
2526 ** If we have an existing count, we just keep expanding this.
2528 retval = aExistingCount;
2531 ** Go through every allocation.
2533 for (allocLoop = 0;
2534 0 == stopLoops && allocLoop < aRun->mAllocationCount;
2535 allocLoop++) {
2537 ** Go through every event.
2539 for (eventLoop = 0;
2540 0 == stopLoops
2541 && eventLoop < aRun->mAllocations[allocLoop]->mEventCount;
2542 eventLoop++) {
2544 ** Skip the free events.
2546 if (TM_EVENT_FREE !=
2547 aRun->mAllocations[allocLoop]->mEvents[eventLoop].
2548 mEventType) {
2549 tmcallsite **expand = NULL;
2552 ** expand the array.
2554 expand =
2555 (tmcallsite **) realloc(*aArray,
2556 sizeof(tmcallsite *) *
2557 (retval + 1));
2558 if (NULL != expand) {
2560 ** Set the callsite in case of pointer move.
2562 *aArray = expand;
2565 ** Assign the value.
2567 (*aArray)[retval] =
2568 aRun->mAllocations[allocLoop]->mEvents[eventLoop].
2569 mCallsite;
2570 retval++;
2572 else {
2573 REPORT_ERROR(__LINE__, realloc);
2574 stopLoops = __LINE__;
2581 return retval;
2585 ** getDataPRUint*
2587 ** Helper to avoid cut and paste code.
2588 ** Failure to find aCheckFor does not mean failure.
2589 ** In case of dups, specify an index on non "1" to get others.
2590 ** Do not touch storage space unless a find is made.
2591 ** Returns !0 on failure.
2594 getDataPRUint32Base(const FormData * aGetData, const char *aCheckFor,
2595 int inIndex, void *aStoreResult, PRUint32 aBits)
2597 int retval = 0;
2599 if (NULL != aGetData && NULL != aCheckFor && 0 != inIndex
2600 && NULL != aStoreResult) {
2601 unsigned finder = 0;
2604 ** Loop over the names, looking for an exact string match.
2605 ** Skip over initial finds, decrementing inIndex, until "1".
2607 for (finder = 0; finder < aGetData->mNVCount; finder++) {
2608 if (0 == strcmp(aCheckFor, aGetData->mNArray[finder])) {
2609 inIndex--;
2611 if (0 == inIndex) {
2612 PRInt32 scanRes = 0;
2614 if (64 == aBits) {
2615 scanRes =
2616 PR_sscanf(aGetData->mVArray[finder], "%llu",
2617 aStoreResult);
2619 else {
2620 scanRes =
2621 PR_sscanf(aGetData->mVArray[finder], "%u",
2622 aStoreResult);
2624 if (1 != scanRes) {
2625 retval = __LINE__;
2626 REPORT_ERROR(__LINE__, PR_sscanf);
2628 break;
2633 else {
2634 retval = __LINE__;
2635 REPORT_ERROR(__LINE__, getDataPRUint32Base);
2638 return retval;
2642 getDataPRUint32(const FormData * aGetData, const char *aCheckFor, int inIndex,
2643 PRUint32 * aStoreResult, PRUint32 aConversion)
2645 int retval = 0;
2647 retval =
2648 getDataPRUint32Base(aGetData, aCheckFor, inIndex, aStoreResult, 32);
2649 *aStoreResult *= aConversion;
2651 return retval;
2655 getDataPRUint64(const FormData * aGetData, const char *aCheckFor, int inIndex,
2656 PRUint64 * aStoreResult64, PRUint64 aConversion64)
2658 int retval = 0;
2659 PRUint64 value64 = LL_INIT(0, 0);
2661 retval = getDataPRUint32Base(aGetData, aCheckFor, inIndex, &value64, 64);
2662 LL_MUL(*aStoreResult64, value64, aConversion64);
2664 return retval;
2668 ** getDataString
2670 ** Pull out the string data, if specified.
2671 ** In case of dups, specify an index on non "1" to get others.
2672 ** Do not touch storage space unless a find is made.
2673 ** Return !0 on failure.
2676 getDataString(const FormData * aGetData, const char *aCheckFor, int inIndex,
2677 char *aStoreResult, int inStoreResultLength)
2679 int retval = 0;
2681 if (NULL != aGetData && NULL != aCheckFor && 0 != inIndex
2682 && NULL != aStoreResult && 0 != inStoreResultLength) {
2683 unsigned finder = 0;
2686 ** Loop over the names, looking for an exact string match.
2687 ** Skip over initial finds, decrementing inIndex, until "1".
2689 for (finder = 0; finder < aGetData->mNVCount; finder++) {
2690 if (0 == strcmp(aCheckFor, aGetData->mNArray[finder])) {
2691 inIndex--;
2693 if (0 == inIndex) {
2694 PR_snprintf(aStoreResult, inStoreResultLength, "%s",
2695 aGetData->mVArray[finder]);
2696 break;
2701 else {
2702 retval = __LINE__;
2703 REPORT_ERROR(__LINE__, getDataPRUint32);
2706 return retval;
2710 ** displayTopAllocations
2712 ** Present the top allocations.
2713 ** The run must be passed in, and it must be pre-sorted.
2715 ** Returns !0 on failure.
2718 displayTopAllocations(STRequest * inRequest, STRun * aRun,
2719 const char* id,
2720 const char* caption,
2721 int aWantCallsite)
2723 int retval = 0;
2725 if (NULL != aRun) {
2726 if (0 < aRun->mAllocationCount) {
2727 PRUint32 loop = 0;
2728 STAllocation *current = NULL;
2730 static const char* const headers[] = {
2731 "Rank", "Index", "Byte Size", "Lifespan (sec)",
2732 "Weight", "Heap Op (sec)"
2735 static const char* const headers_callsite[] = {
2736 "Rank", "Index", "Byte Size", "Lifespan (sec)",
2737 "Weight", "Heap Op (sec)", "Origin Callsite"
2740 if (aWantCallsite)
2741 htmlStartTable(inRequest, NULL, id,
2742 caption,
2743 headers_callsite,
2744 sizeof(headers_callsite) / sizeof(headers_callsite[0]));
2745 else
2746 htmlStartTable(inRequest, NULL, id, caption,
2747 headers,
2748 sizeof(headers) / sizeof(headers[0]));
2750 ** Loop over the items, up to some limit or until the end.
2752 for (loop = 0;
2753 loop < inRequest->mOptions.mListItemMax
2754 && loop < aRun->mAllocationCount; loop++) {
2755 current = aRun->mAllocations[loop];
2756 if (NULL != current) {
2757 PRUint32 lifespan =
2758 current->mMaxTimeval - current->mMinTimeval;
2759 PRUint32 size = byteSize(&inRequest->mOptions, current);
2760 PRUint32 heapCost = current->mHeapRuntimeCost;
2761 PRUint64 weight64 = LL_INIT(0, 0);
2762 PRUint64 size64 = LL_INIT(0, 0);
2763 PRUint64 lifespan64 = LL_INIT(0, 0);
2764 char buffer[32];
2766 LL_UI2L(size64, size);
2767 LL_UI2L(lifespan64, lifespan);
2768 LL_MUL(weight64, size64, lifespan64);
2770 PR_fprintf(inRequest->mFD, "<tr>\n");
2773 ** Rank.
2775 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n",
2776 loop + 1);
2779 ** Index.
2781 PR_snprintf(buffer, sizeof(buffer), "%u",
2782 current->mRunIndex);
2783 PR_fprintf(inRequest->mFD, "<td align=right>\n");
2784 htmlAllocationAnchor(inRequest, current, buffer);
2785 PR_fprintf(inRequest->mFD, "</td>\n");
2788 ** Byte Size.
2790 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n",
2791 size);
2794 ** Lifespan.
2796 PR_fprintf(inRequest->mFD,
2797 "<td align=right>" ST_TIMEVAL_FORMAT "</td>\n",
2798 ST_TIMEVAL_PRINTABLE(lifespan));
2801 ** Weight.
2803 PR_fprintf(inRequest->mFD, "<td align=right>%llu</td>\n",
2804 weight64);
2807 ** Heap operation cost.
2809 PR_fprintf(inRequest->mFD,
2810 "<td align=right>" ST_MICROVAL_FORMAT
2811 "</td>\n", ST_MICROVAL_PRINTABLE(heapCost));
2813 if (0 != aWantCallsite) {
2815 ** Callsite.
2817 PR_fprintf(inRequest->mFD, "<td>");
2818 htmlCallsiteAnchor(inRequest,
2819 current->mEvents[0].mCallsite,
2820 NULL, 0);
2821 PR_fprintf(inRequest->mFD, "</td>\n");
2824 PR_fprintf(inRequest->mFD, "</tr>\n");
2828 PR_fprintf(inRequest->mFD, "</tbody>\n</table></div>\n\n");
2831 else {
2832 retval = __LINE__;
2833 REPORT_ERROR(__LINE__, displayTopAllocations);
2836 return retval;
2840 ** displayMemoryLeaks
2842 ** Present the top memory leaks.
2843 ** The run must be passed in, and it must be pre-sorted.
2845 ** Returns !0 on failure.
2848 displayMemoryLeaks(STRequest * inRequest, STRun * aRun)
2850 int retval = 0;
2852 if (NULL != aRun) {
2853 PRUint32 loop = 0;
2854 PRUint32 displayed = 0;
2855 STAllocation *current = NULL;
2857 static const char * headers[] = {
2858 "Rank", "Index", "Byte Size", "Lifespan (sec)",
2859 "Weight", "Heap Op (sec)", "Origin Callsite"
2862 htmlStartTable(inRequest, NULL, "memory-leaks", "Memory Leaks", headers,
2863 sizeof(headers) / sizeof(headers[0]));
2866 ** Loop over all of the items, or until we've displayed enough.
2868 for (loop = 0;
2869 displayed < inRequest->mOptions.mListItemMax
2870 && loop < aRun->mAllocationCount; loop++) {
2871 current = aRun->mAllocations[loop];
2872 if (NULL != current && 0 != current->mEventCount) {
2874 ** In order to be a leak, the last event of its life must
2875 ** NOT be a free operation.
2877 ** A free operation is just that, a free.
2879 if (TM_EVENT_FREE !=
2880 current->mEvents[current->mEventCount - 1].mEventType) {
2881 PRUint32 lifespan =
2882 current->mMaxTimeval - current->mMinTimeval;
2883 PRUint32 size = byteSize(&inRequest->mOptions, current);
2884 PRUint32 heapCost = current->mHeapRuntimeCost;
2885 PRUint64 weight64 = LL_INIT(0, 0);
2886 PRUint64 size64 = LL_INIT(0, 0);
2887 PRUint64 lifespan64 = LL_INIT(0, 0);
2888 char buffer[32];
2890 LL_UI2L(size64, size);
2891 LL_UI2L(lifespan64, lifespan);
2892 LL_MUL(weight64, size64, lifespan64);
2895 ** One more shown.
2897 displayed++;
2899 PR_fprintf(inRequest->mFD, "<tr>\n");
2902 ** Rank.
2904 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n",
2905 displayed);
2908 ** Index.
2910 PR_snprintf(buffer, sizeof(buffer), "%u",
2911 current->mRunIndex);
2912 PR_fprintf(inRequest->mFD, "<td align=right>\n");
2913 htmlAllocationAnchor(inRequest, current, buffer);
2914 PR_fprintf(inRequest->mFD, "</td>\n");
2917 ** Byte Size.
2919 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n",
2920 size);
2923 ** Lifespan.
2925 PR_fprintf(inRequest->mFD,
2926 "<td align=right>" ST_TIMEVAL_FORMAT "</td>\n",
2927 ST_TIMEVAL_PRINTABLE(lifespan));
2930 ** Weight.
2932 PR_fprintf(inRequest->mFD, "<td align=right>%llu</td>\n",
2933 weight64);
2936 ** Heap Operation Seconds.
2938 PR_fprintf(inRequest->mFD,
2939 "<td align=right>" ST_MICROVAL_FORMAT
2940 "</td>\n", ST_MICROVAL_PRINTABLE(heapCost));
2943 ** Callsite.
2945 PR_fprintf(inRequest->mFD, "<td>");
2946 htmlCallsiteAnchor(inRequest,
2947 current->mEvents[0].mCallsite, NULL,
2949 PR_fprintf(inRequest->mFD, "</td>\n");
2951 PR_fprintf(inRequest->mFD, "</tr>\n");
2956 PR_fprintf(inRequest->mFD, "</tbody></table></div>\n\n");
2958 else {
2959 retval = __LINE__;
2960 REPORT_ERROR(__LINE__, displayMemoryLeaks);
2963 return retval;
2967 ** displayCallsites
2969 ** Display a table of callsites.
2970 ** If the stamp is non zero, then must match that stamp.
2971 ** If the stamp is zero, then must match the global sorted run stamp.
2972 ** Return !0 on error.
2975 displayCallsites(STRequest * inRequest, tmcallsite * aCallsite, int aFollow,
2976 PRUint32 aStamp,
2977 const char* id,
2978 const char* caption,
2979 int aRealNames)
2981 int retval = 0;
2983 if (NULL != aCallsite && NULL != aCallsite->method) {
2984 int headerDisplayed = 0;
2985 STRun *run = NULL;
2988 ** Correct the stamp if need be.
2990 if (0 == aStamp && NULL != inRequest->mContext->mSortedRun) {
2991 aStamp =
2992 inRequest->mContext->mSortedRun->mStats[inRequest->mContext->
2993 mIndex].mStamp;
2997 ** Loop over the callsites looking for a stamp match.
2998 ** A stamp guarantees there is something interesting to look at too.
2999 ** If found, output it.
3001 while (NULL != aCallsite && NULL != aCallsite->method) {
3002 run = CALLSITE_RUN(aCallsite);
3003 if (NULL != run) {
3004 if (aStamp == run->mStats[inRequest->mContext->mIndex].mStamp) {
3006 ** We got a header?
3008 if (0 == headerDisplayed) {
3010 static const char* const headers[] = {
3011 "Callsite",
3012 "<abbr title=\"Composite Size\">C. Size</abbr>",
3013 "<abbr title=\"Composite Seconds\">C. Seconds</abbr>",
3014 "<abbr title=\"Composite Weight\">C. Weight</abbr>",
3015 "<abbr title=\"Heap Object Count\">H.O. Count</abbr>",
3016 "<abbr title=\"Composite Heap Operation Seconds\">C.H. Operation (sec)</abbr>"
3018 headerDisplayed = __LINE__;
3019 htmlStartTable(inRequest, NULL, id, caption, headers,
3020 sizeof(headers)/sizeof(headers[0]));
3024 ** Output the information.
3026 PR_fprintf(inRequest->mFD, "<tr>\n");
3029 ** Method name.
3031 PR_fprintf(inRequest->mFD, "<td>");
3032 htmlCallsiteAnchor(inRequest, aCallsite, NULL,
3033 aRealNames);
3034 PR_fprintf(inRequest->mFD, "</td>");
3037 ** Byte Size.
3039 PR_fprintf(inRequest->mFD,
3040 "<td valign=top align=right>%u</td>\n",
3041 run->mStats[inRequest->mContext->mIndex].
3042 mSize);
3045 ** Seconds.
3047 PR_fprintf(inRequest->mFD,
3048 "<td valign=top align=right>" ST_TIMEVAL_FORMAT
3049 "</td>\n",
3050 ST_TIMEVAL_PRINTABLE64(run->
3051 mStats[inRequest->
3052 mContext->
3053 mIndex].
3054 mTimeval64));
3057 ** Weight.
3059 PR_fprintf(inRequest->mFD,
3060 "<td valign=top align=right>%llu</td>\n",
3061 run->mStats[inRequest->mContext->mIndex].
3062 mWeight64);
3065 ** Allocation object count.
3067 PR_fprintf(inRequest->mFD,
3068 "<td valign=top align=right>%u</td>\n",
3069 run->mStats[inRequest->mContext->mIndex].
3070 mCompositeCount);
3073 ** Heap Operation Seconds.
3075 PR_fprintf(inRequest->mFD,
3076 "<td valign=top align=right>"
3077 ST_MICROVAL_FORMAT "</td>\n",
3078 ST_MICROVAL_PRINTABLE(run->
3079 mStats[inRequest->
3080 mContext->mIndex].
3081 mHeapRuntimeCost));
3083 PR_fprintf(inRequest->mFD, "</tr>\n");
3086 else {
3087 retval = __LINE__;
3088 REPORT_ERROR(__LINE__, displayCallsites);
3089 break;
3093 ** What do we follow?
3095 switch (aFollow) {
3096 case ST_FOLLOW_SIBLINGS:
3097 aCallsite = aCallsite->siblings;
3098 break;
3099 case ST_FOLLOW_PARENTS:
3100 aCallsite = aCallsite->parent;
3101 break;
3102 default:
3103 aCallsite = NULL;
3104 retval = __LINE__;
3105 REPORT_ERROR(__LINE__, displayCallsites);
3106 break;
3111 ** Terminate the table if we should.
3113 if (0 != headerDisplayed) {
3114 PR_fprintf(inRequest->mFD, "</tbody></table></div>\n\n");
3117 else {
3118 retval = __LINE__;
3119 REPORT_ERROR(__LINE__, displayCallsites);
3122 return retval;
3126 ** displayAllocationDetails
3128 ** Report what we know about the allocation.
3130 ** Returns !0 on error.
3133 displayAllocationDetails(STRequest * inRequest, STAllocation * aAllocation)
3135 int retval = 0;
3137 if (NULL != aAllocation) {
3138 PRUint32 traverse = 0;
3139 PRUint32 bytesize = byteSize(&inRequest->mOptions, aAllocation);
3140 PRUint32 timeval =
3141 aAllocation->mMaxTimeval - aAllocation->mMinTimeval;
3142 PRUint32 heapCost = aAllocation->mHeapRuntimeCost;
3143 PRUint64 weight64 = LL_INIT(0, 0);
3144 PRUint64 bytesize64 = LL_INIT(0, 0);
3145 PRUint64 timeval64 = LL_INIT(0, 0);
3146 PRUint32 cacheval = 0;
3147 int displayRes = 0;
3149 LL_UI2L(bytesize64, bytesize);
3150 LL_UI2L(timeval64, timeval);
3151 LL_MUL(weight64, bytesize64, timeval64);
3153 PR_fprintf(inRequest->mFD, "<p>Allocation %u Details:</p>\n",
3154 aAllocation->mRunIndex);
3156 PR_fprintf(inRequest->mFD, "<div id=\"allocation-details\"><table class=\"data summary\">\n");
3157 PR_fprintf(inRequest->mFD,
3158 "<tr><td align=left>Final Size:</td><td align=right>%u</td></tr>\n",
3159 bytesize);
3160 PR_fprintf(inRequest->mFD,
3161 "<tr><td align=left>Lifespan Seconds:</td><td align=right>"
3162 ST_TIMEVAL_FORMAT "</td></tr>\n",
3163 ST_TIMEVAL_PRINTABLE(timeval));
3164 PR_fprintf(inRequest->mFD,
3165 "<tr><td align=left>Weight:</td><td align=right>%llu</td></tr>\n",
3166 weight64);
3167 PR_fprintf(inRequest->mFD,
3168 "<tr><td align=left>Heap Operation Seconds:</td><td align=right>"
3169 ST_MICROVAL_FORMAT "</td></tr>\n",
3170 ST_MICROVAL_PRINTABLE(heapCost));
3171 PR_fprintf(inRequest->mFD, "</table></div>\n");
3174 ** The events.
3178 static const char* const headers[] = {
3179 "Operation", "Size", "Seconds", ""
3182 char caption[100];
3183 PR_snprintf(caption, sizeof(caption), "%u Life Event(s)",
3184 aAllocation->mEventCount);
3185 htmlStartTable(inRequest, NULL, "allocation-details", caption, headers,
3186 sizeof(headers) / sizeof(headers[0]));
3189 for (traverse = 0;
3190 traverse < aAllocation->mEventCount
3191 && traverse < inRequest->mOptions.mListItemMax; traverse++) {
3192 PR_fprintf(inRequest->mFD, "<tr>\n");
3195 ** count.
3197 PR_fprintf(inRequest->mFD,
3198 "<td valign=top align=right>%u.</td>\n", traverse + 1);
3201 ** Operation.
3203 PR_fprintf(inRequest->mFD, "<td valign=top>");
3204 switch (aAllocation->mEvents[traverse].mEventType) {
3205 case TM_EVENT_CALLOC:
3206 PR_fprintf(inRequest->mFD, "calloc");
3207 break;
3208 case TM_EVENT_FREE:
3209 PR_fprintf(inRequest->mFD, "free");
3210 break;
3211 case TM_EVENT_MALLOC:
3212 PR_fprintf(inRequest->mFD, "malloc");
3213 break;
3214 case TM_EVENT_REALLOC:
3215 PR_fprintf(inRequest->mFD, "realloc");
3216 break;
3217 default:
3218 retval = __LINE__;
3219 REPORT_ERROR(__LINE__, displayAllocationDetails);
3220 break;
3222 PR_fprintf(inRequest->mFD, "</td>");
3225 ** Size.
3227 PR_fprintf(inRequest->mFD, "<td valign=top align=right>%u</td>\n",
3228 aAllocation->mEvents[traverse].mHeapSize);
3231 ** Timeval.
3233 cacheval =
3234 aAllocation->mEvents[traverse].mTimeval - globals.mMinTimeval;
3235 PR_fprintf(inRequest->mFD,
3236 "<td valign=top align=right>" ST_TIMEVAL_FORMAT
3237 "</td>\n", ST_TIMEVAL_PRINTABLE(cacheval));
3240 ** Callsite backtrace.
3241 ** Only relevant backtrace is for event 0 for now until
3242 ** trace-malloc outputs proper callsites for all others.
3244 PR_fprintf(inRequest->mFD, "<td valign=top>\n");
3245 if (0 == traverse) {
3246 displayRes =
3247 displayCallsites(inRequest,
3248 aAllocation->mEvents[traverse].mCallsite,
3249 ST_FOLLOW_PARENTS, 0, "event-stack", "", __LINE__);
3250 if (0 != displayRes) {
3251 retval = __LINE__;
3252 REPORT_ERROR(__LINE__, displayCallsite);
3255 PR_fprintf(inRequest->mFD, "</td>\n");
3257 PR_fprintf(inRequest->mFD, "</tr>\n");
3259 PR_fprintf(inRequest->mFD, "</table></div>\n");
3261 else {
3262 retval = __LINE__;
3263 REPORT_ERROR(__LINE__, displayAllocationDetails);
3266 return retval;
3270 ** compareCallsites
3272 ** qsort callback.
3273 ** Compare the callsites as specified by the options.
3274 ** There must be NO equal callsites, unless they really are duplicates,
3275 ** this is so that a duplicate detector loop can
3276 ** simply skip sorted items until the callsite is different.
3279 compareCallsites(const void *aSite1, const void *aSite2, void *aContext)
3281 int retval = 0;
3282 STRequest *inRequest = (STRequest *) aContext;
3284 if (NULL != aSite1 && NULL != aSite2) {
3285 tmcallsite *site1 = *((tmcallsite **) aSite1);
3286 tmcallsite *site2 = *((tmcallsite **) aSite2);
3288 if (NULL != site1 && NULL != site2) {
3289 STRun *run1 = CALLSITE_RUN(site1);
3290 STRun *run2 = CALLSITE_RUN(site2);
3292 if (NULL != run1 && NULL != run2) {
3293 STCallsiteStats *stats1 =
3294 &(run1->mStats[inRequest->mContext->mIndex]);
3295 STCallsiteStats *stats2 =
3296 &(run2->mStats[inRequest->mContext->mIndex]);
3299 ** Logic determined by pref/option.
3301 switch (inRequest->mOptions.mOrderBy) {
3302 case ST_WEIGHT:
3304 PRUint64 weight164 = stats1->mWeight64;
3305 PRUint64 weight264 = stats2->mWeight64;
3307 if (LL_UCMP(weight164, <, weight264)) {
3308 retval = __LINE__;
3310 else if (LL_UCMP(weight164, >, weight264)) {
3311 retval = -__LINE__;
3314 break;
3316 case ST_SIZE:
3318 PRUint32 size1 = stats1->mSize;
3319 PRUint32 size2 = stats2->mSize;
3321 if (size1 < size2) {
3322 retval = __LINE__;
3324 else if (size1 > size2) {
3325 retval = -__LINE__;
3328 break;
3330 case ST_TIMEVAL:
3332 PRUint64 timeval164 = stats1->mTimeval64;
3333 PRUint64 timeval264 = stats2->mTimeval64;
3335 if (LL_UCMP(timeval164, <, timeval264)) {
3336 retval = __LINE__;
3338 else if (LL_UCMP(timeval164, >, timeval264)) {
3339 retval = -__LINE__;
3342 break;
3344 case ST_COUNT:
3346 PRUint32 count1 = stats1->mCompositeCount;
3347 PRUint32 count2 = stats2->mCompositeCount;
3349 if (count1 < count2) {
3350 retval = __LINE__;
3352 else if (count1 > count2) {
3353 retval = -__LINE__;
3356 break;
3358 case ST_HEAPCOST:
3360 PRUint32 cost1 = stats1->mHeapRuntimeCost;
3361 PRUint32 cost2 = stats2->mHeapRuntimeCost;
3363 if (cost1 < cost2) {
3364 retval = __LINE__;
3366 else if (cost1 > cost2) {
3367 retval = -__LINE__;
3370 break;
3372 default:
3374 REPORT_ERROR(__LINE__, compareAllocations);
3376 break;
3380 ** If the return value is still zero, do a pointer compare.
3381 ** This makes sure we return zero, only iff the same object.
3383 if (0 == retval) {
3384 if (stats1 < stats2) {
3385 retval = __LINE__;
3387 else if (stats1 > stats2) {
3388 retval = -__LINE__;
3395 return retval;
3399 ** displayTopCallsites
3401 ** Given a list of callsites, sort it, and output skipping dups.
3402 ** The passed in callsite array is side effected, as in that it will come
3403 ** back sorted. This function will not release the array.
3405 ** Note: If the stamp passed in is non zero, then all callsites must match.
3406 ** If the stamp is zero, all callsites must match global sorted run stamp.
3408 ** Returns !0 on error.
3411 displayTopCallsites(STRequest * inRequest, tmcallsite ** aCallsites,
3412 PRUint32 aCallsiteCount, PRUint32 aStamp,
3413 const char* id,
3414 const char* caption,
3415 int aRealName)
3417 int retval = 0;
3419 if (NULL != aCallsites && 0 < aCallsiteCount) {
3420 PRUint32 traverse = 0;
3421 STRun *run = NULL;
3422 tmcallsite *site = NULL;
3423 int headerDisplayed = 0;
3424 PRUint32 displayed = 0;
3427 ** Fixup the stamp.
3429 if (0 == aStamp && NULL != inRequest->mContext->mSortedRun) {
3430 aStamp =
3431 inRequest->mContext->mSortedRun->mStats[inRequest->mContext->
3432 mIndex].mStamp;
3436 ** Sort the things.
3438 NS_QuickSort(aCallsites, aCallsiteCount, sizeof(tmcallsite *),
3439 compareCallsites, inRequest);
3442 ** Time for output.
3444 for (traverse = 0;
3445 traverse < aCallsiteCount
3446 && inRequest->mOptions.mListItemMax > displayed; traverse++) {
3447 site = aCallsites[traverse];
3448 run = CALLSITE_RUN(site);
3451 ** Only if the same stamp....
3453 if (aStamp == run->mStats[inRequest->mContext->mIndex].mStamp) {
3455 ** We got a header yet?
3457 if (0 == headerDisplayed) {
3458 static const char* const headers[] = {
3459 "Rank",
3460 "Callsite",
3461 "<abbr title=\"Composite Size\">Size</abbr>",
3462 "<abbr title=\"Composite Seconds\">Seconds</abbr>",
3463 "<abbr title=\"Composite Weight\">Weight</abbr>",
3464 "<abbr title=\"Heap Object Count\">Object Count</abbr>",
3465 "<abbr title=\"Composite Heap Operation Seconds\">C.H. Operation (sec)</abbr>"
3467 headerDisplayed = __LINE__;
3469 htmlStartTable(inRequest, NULL, id, caption, headers,
3470 sizeof(headers) / sizeof(headers[0]));
3473 displayed++;
3475 PR_fprintf(inRequest->mFD, "<tr>\n");
3478 ** Rank.
3480 PR_fprintf(inRequest->mFD,
3481 "<td align=right valign=top>%u</td>\n", displayed);
3484 ** Method.
3486 PR_fprintf(inRequest->mFD, "<td>");
3487 htmlCallsiteAnchor(inRequest, site, NULL, aRealName);
3488 PR_fprintf(inRequest->mFD, "</td>\n");
3491 ** Size.
3493 PR_fprintf(inRequest->mFD,
3494 "<td align=right valign=top>%u</td>\n",
3495 run->mStats[inRequest->mContext->mIndex].mSize);
3498 ** Timeval.
3500 PR_fprintf(inRequest->mFD,
3501 "<td align=right valign=top>" ST_TIMEVAL_FORMAT
3502 "</td>\n",
3503 ST_TIMEVAL_PRINTABLE64(run->
3504 mStats[inRequest->mContext->
3505 mIndex].mTimeval64));
3508 ** Weight.
3510 PR_fprintf(inRequest->mFD,
3511 "<td align=right valign=top>%llu</td>\n",
3512 run->mStats[inRequest->mContext->mIndex].
3513 mWeight64);
3516 ** Allocation object count.
3518 PR_fprintf(inRequest->mFD,
3519 "<td align=right valign=top>%u</td>\n",
3520 run->mStats[inRequest->mContext->mIndex].
3521 mCompositeCount);
3524 ** Heap operation seconds.
3526 PR_fprintf(inRequest->mFD,
3527 "<td align=right valign=top>" ST_MICROVAL_FORMAT
3528 "</td>\n",
3529 ST_MICROVAL_PRINTABLE(run->
3530 mStats[inRequest->mContext->
3531 mIndex].
3532 mHeapRuntimeCost));
3534 PR_fprintf(inRequest->mFD, "</tr>\n");
3537 if (inRequest->mOptions.mListItemMax > displayed) {
3539 ** Skip any dups.
3541 while (((traverse + 1) < aCallsiteCount)
3542 && (site == aCallsites[traverse + 1])) {
3543 traverse++;
3550 ** We need to terminate anything?
3552 if (0 != headerDisplayed) {
3553 PR_fprintf(inRequest->mFD, "</table></div>\n");
3556 else {
3557 retval = __LINE__;
3558 REPORT_ERROR(__LINE__, displayTopCallsites);
3561 return retval;
3565 ** displayCallsiteDetails
3567 ** The callsite specific report.
3568 ** Try to report what we know.
3569 ** This one hits a little harder than the rest.
3571 ** Returns !0 on error.
3574 displayCallsiteDetails(STRequest * inRequest, tmcallsite * aCallsite)
3576 int retval = 0;
3578 if (NULL != aCallsite && NULL != aCallsite->method) {
3579 STRun *sortedRun = NULL;
3580 STRun *thisRun = CALLSITE_RUN(aCallsite);
3581 const char *sourceFile = NULL;
3583 sourceFile = resolveSourceFile(aCallsite->method);
3585 PR_fprintf(inRequest->mFD, "<div class=\"callsite-header\">\n");
3586 if (sourceFile) {
3587 PR_fprintf(inRequest->mFD, "<b>%s</b>",
3588 tmmethodnode_name(aCallsite->method));
3589 PR_fprintf(inRequest->mFD,
3590 " [<a href=\"http://lxr.mozilla.org/mozilla/source/%s#%u\" class=\"lxr\" target=\"_st_lxr\">%s:%u</a>]",
3591 aCallsite->method->sourcefile,
3592 aCallsite->method->linenumber, sourceFile,
3593 aCallsite->method->linenumber);
3595 else {
3596 PR_fprintf(inRequest->mFD,
3597 "<p><b>%s</b>+%u(%u) Callsite Details:</p>\n",
3598 tmmethodnode_name(aCallsite->method),
3599 aCallsite->offset, (PRUint32) aCallsite->entry.key);
3602 PR_fprintf(inRequest->mFD, "</div>\n\n");
3603 PR_fprintf(inRequest->mFD, "<div id=\"callsite-details\"><table class=\"data summary\">\n");
3604 PR_fprintf(inRequest->mFD,
3605 "<tr><td>Composite Byte Size:</td><td align=right>%u</td></tr>\n",
3606 thisRun->mStats[inRequest->mContext->mIndex].mSize);
3607 PR_fprintf(inRequest->mFD,
3608 "<tr><td>Composite Seconds:</td><td align=right>"
3609 ST_TIMEVAL_FORMAT "</td></tr>\n",
3610 ST_TIMEVAL_PRINTABLE64(thisRun->
3611 mStats[inRequest->mContext->mIndex].
3612 mTimeval64));
3613 PR_fprintf(inRequest->mFD,
3614 "<tr><td>Composite Weight:</td><td align=right>%llu</td></tr>\n",
3615 thisRun->mStats[inRequest->mContext->mIndex].mWeight64);
3616 PR_fprintf(inRequest->mFD,
3617 "<tr><td>Heap Object Count:</td><td align=right>%u</td></tr>\n",
3618 thisRun->mStats[inRequest->mContext->mIndex].
3619 mCompositeCount);
3620 PR_fprintf(inRequest->mFD,
3621 "<tr><td>Heap Operation Seconds:</td><td align=right>"
3622 ST_MICROVAL_FORMAT "</td></tr>\n",
3623 ST_MICROVAL_PRINTABLE(thisRun->
3624 mStats[inRequest->mContext->mIndex].
3625 mHeapRuntimeCost));
3626 PR_fprintf(inRequest->mFD, "</table></div>\n\n");
3629 ** Kids (callsites we call):
3631 if (NULL != aCallsite->kids && NULL != aCallsite->kids->method) {
3632 int displayRes = 0;
3633 PRUint32 siteCount = 0;
3634 tmcallsite **sites = NULL;
3637 ** Collect the kid sibling callsites.
3638 ** Doing it this way sorts them for relevance.
3640 siteCount =
3641 callsiteArrayFromCallsite(&sites, 0, aCallsite->kids,
3642 ST_FOLLOW_SIBLINGS);
3643 if (0 != siteCount && NULL != sites) {
3645 ** Got something to show.
3647 displayRes =
3648 displayTopCallsites(inRequest, sites, siteCount, 0,
3649 "callsites",
3650 "Children Callsites",
3651 __LINE__);
3652 if (0 != displayRes) {
3653 retval = __LINE__;
3654 REPORT_ERROR(__LINE__, displayTopCallsites);
3658 ** Done with array.
3660 free(sites);
3661 sites = NULL;
3666 ** Parents (those who call us):
3668 if (NULL != aCallsite->parent && NULL != aCallsite->parent->method) {
3669 int displayRes = 0;
3671 displayRes =
3672 displayCallsites(inRequest, aCallsite->parent,
3673 ST_FOLLOW_PARENTS, 0, "caller-stack", "Caller stack",
3674 __LINE__);
3675 if (0 != displayRes) {
3676 retval = __LINE__;
3677 REPORT_ERROR(__LINE__, displayCallsites);
3682 ** Allocations we did.
3683 ** Simply harvest our own run.
3685 sortedRun = createRun(inRequest->mContext, 0);
3686 if (NULL != sortedRun) {
3687 int harvestRes = 0;
3689 harvestRes =
3690 harvestRun(CALLSITE_RUN(aCallsite), sortedRun,
3691 &inRequest->mOptions, inRequest->mContext);
3692 if (0 == harvestRes) {
3693 if (0 != sortedRun->mAllocationCount) {
3694 int sortRes = 0;
3696 sortRes = sortRun(&inRequest->mOptions, sortedRun);
3697 if (0 == sortRes) {
3698 int displayRes = 0;
3700 displayRes =
3701 displayTopAllocations(inRequest, sortedRun,
3702 "allocations",
3703 "Allocations",
3705 if (0 != displayRes) {
3706 retval = __LINE__;
3707 REPORT_ERROR(__LINE__, displayTopAllocations);
3710 else {
3711 retval = __LINE__;
3712 REPORT_ERROR(__LINE__, sortRun);
3716 else {
3717 retval = __LINE__;
3718 REPORT_ERROR(__LINE__, harvestRun);
3722 ** Done with the run.
3724 freeRun(sortedRun);
3725 sortedRun = NULL;
3727 else {
3728 retval = __LINE__;
3729 REPORT_ERROR(__LINE__, createRun);
3732 else {
3733 retval = __LINE__;
3734 REPORT_ERROR(__LINE__, displayCallsiteDetails);
3737 return retval;
3740 #if ST_WANT_GRAPHS
3742 ** graphFootprint
3744 ** Output a PNG graph of the memory usage of the run.
3746 ** Draw the graph within these boundaries.
3747 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN
3749 ** Returns !0 on failure.
3752 graphFootprint(STRequest * inRequest, STRun * aRun)
3754 int retval = 0;
3756 if (NULL != aRun) {
3757 PRUint32 *YData = NULL;
3758 PRUint32 YDataArray[STGD_SPACE_X];
3759 PRUint32 traverse = 0;
3760 PRUint32 timeval = 0;
3761 PRUint32 loop = 0;
3762 PRBool underLock = PR_FALSE;
3765 ** Decide if this is custom or we should use the cache.
3767 if (aRun == inRequest->mContext->mSortedRun) {
3768 YData = inRequest->mContext->mFootprintYData;
3769 underLock = PR_TRUE;
3771 else {
3772 YData = YDataArray;
3776 ** Protect the shared data so that only one client has access to it
3777 ** at any given time.
3779 if (PR_FALSE != underLock) {
3780 PR_Lock(inRequest->mContext->mImageLock);
3784 ** Only do the computations if we aren't cached already.
3786 if (YData != inRequest->mContext->mFootprintYData
3787 || PR_FALSE == inRequest->mContext->mFootprintCached) {
3788 memset(YData, 0, sizeof(PRUint32) * STGD_SPACE_X);
3791 ** Initialize our Y data.
3792 ** Pretty brutal loop here....
3794 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X;
3795 traverse++) {
3797 ** Compute what timeval this Y data lands in.
3799 timeval =
3800 ((traverse *
3801 (globals.mMaxTimeval -
3802 globals.mMinTimeval)) / STGD_SPACE_X) +
3803 globals.mMinTimeval;
3806 ** Loop over the run.
3807 ** Should an allocation contain said Timeval, we're good.
3809 for (loop = 0; loop < aRun->mAllocationCount; loop++) {
3810 if (timeval >= aRun->mAllocations[loop]->mMinTimeval
3811 && timeval <= aRun->mAllocations[loop]->mMaxTimeval) {
3812 YData[traverse] +=
3813 byteSize(&inRequest->mOptions,
3814 aRun->mAllocations[loop]);
3820 ** Did we cache this?
3822 if (YData == inRequest->mContext->mFootprintYData) {
3823 inRequest->mContext->mFootprintCached = PR_TRUE;
3828 ** Done with the lock.
3830 if (PR_FALSE != underLock) {
3831 PR_Unlock(inRequest->mContext->mImageLock);
3834 if (0 == retval) {
3835 PRUint32 minMemory = (PRUint32) - 1;
3836 PRUint32 maxMemory = 0;
3837 int transparent = 0;
3838 gdImagePtr graph = NULL;
3841 ** Go through and find the minimum and maximum sizes.
3843 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
3844 if (YData[traverse] < minMemory) {
3845 minMemory = YData[traverse];
3847 if (YData[traverse] > maxMemory) {
3848 maxMemory = YData[traverse];
3853 ** We can now draw the graph.
3855 graph = createGraph(&transparent);
3856 if (NULL != graph) {
3857 gdSink theSink;
3858 int red = 0;
3859 int x1 = 0;
3860 int y1 = 0;
3861 int x2 = 0;
3862 int y2 = 0;
3863 PRUint32 percents[11] =
3864 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
3865 char *timevals[11];
3866 char *bytes[11];
3867 char timevalSpace[11][32];
3868 char byteSpace[11][32];
3869 int legendColors[1];
3870 const char *legends[1] = { "Memory in Use" };
3871 PRUint32 cached = 0;
3874 ** Figure out what the labels will say.
3876 for (traverse = 0; traverse < 11; traverse++) {
3877 timevals[traverse] = timevalSpace[traverse];
3878 bytes[traverse] = byteSpace[traverse];
3880 cached =
3881 ((globals.mMaxTimeval -
3882 globals.mMinTimeval) * percents[traverse]) / 100;
3883 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT,
3884 ST_TIMEVAL_PRINTABLE(cached));
3885 PR_snprintf(bytes[traverse], 32, "%u",
3886 ((maxMemory -
3887 minMemory) * percents[traverse]) / 100);
3890 red = gdImageColorAllocate(graph, 255, 0, 0);
3891 legendColors[0] = red;
3893 drawGraph(graph, -1, "Memory Footprint Over Time", "Seconds",
3894 "Bytes", 11, percents, (const char **) timevals, 11,
3895 percents, (const char **) bytes, 1, legendColors,
3896 legends);
3898 if (maxMemory != minMemory) {
3899 PRInt64 in64 = LL_INIT(0, 0);
3900 PRInt64 ydata64 = LL_INIT(0, 0);
3901 PRInt64 spacey64 = LL_INIT(0, 0);
3902 PRInt64 mem64 = LL_INIT(0, 0);
3903 PRInt32 in32 = 0;
3906 ** Go through our Y data and mark it up.
3908 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
3909 x1 = traverse + STGD_MARGIN;
3910 y1 = STGD_HEIGHT - STGD_MARGIN;
3913 ** Need to do this math in 64 bits.
3915 LL_I2L(ydata64, YData[traverse]);
3916 LL_I2L(spacey64, STGD_SPACE_Y);
3917 LL_I2L(mem64, (maxMemory - minMemory));
3919 LL_MUL(in64, ydata64, spacey64);
3920 LL_DIV(in64, in64, mem64);
3921 LL_L2I(in32, in64);
3923 x2 = x1;
3924 y2 = y1 - in32;
3926 gdImageLine(graph, x1, y1, x2, y2, red);
3931 theSink.context = inRequest->mFD;
3932 theSink.sink = pngSink;
3933 gdImagePngToSink(graph, &theSink);
3935 gdImageDestroy(graph);
3937 else {
3938 retval = __LINE__;
3939 REPORT_ERROR(__LINE__, createGraph);
3943 else {
3944 retval = __LINE__;
3945 REPORT_ERROR(__LINE__, graphFootprint);
3948 return retval;
3950 #endif /* ST_WANT_GRAPHS */
3952 #if ST_WANT_GRAPHS
3954 ** graphTimeval
3956 ** Output a PNG graph of when the memory is allocated.
3958 ** Draw the graph within these boundaries.
3959 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN
3961 ** Returns !0 on failure.
3964 graphTimeval(STRequest * inRequest, STRun * aRun)
3966 int retval = 0;
3968 if (NULL != aRun) {
3969 PRUint32 *YData = NULL;
3970 PRUint32 YDataArray[STGD_SPACE_X];
3971 PRUint32 traverse = 0;
3972 PRUint32 timeval = globals.mMinTimeval;
3973 PRUint32 loop = 0;
3974 PRBool underLock = PR_FALSE;
3977 ** Decide if this is custom or we should use the global cache.
3979 if (aRun == inRequest->mContext->mSortedRun) {
3980 YData = inRequest->mContext->mTimevalYData;
3981 underLock = PR_TRUE;
3983 else {
3984 YData = YDataArray;
3988 ** Protect the shared data so that only one client has access to it
3989 ** at any given time.
3991 if (PR_FALSE != underLock) {
3992 PR_Lock(inRequest->mContext->mImageLock);
3996 ** Only do the computations if we aren't cached already.
3998 if (YData != inRequest->mContext->mTimevalYData
3999 || PR_FALSE == inRequest->mContext->mTimevalCached) {
4000 PRUint32 prevTimeval = 0;
4002 memset(YData, 0, sizeof(PRUint32) * STGD_SPACE_X);
4005 ** Initialize our Y data.
4006 ** Pretty brutal loop here....
4008 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X;
4009 traverse++) {
4011 ** Compute what timeval this Y data lands in.
4013 prevTimeval = timeval;
4014 timeval =
4015 ((traverse *
4016 (globals.mMaxTimeval -
4017 globals.mMinTimeval)) / STGD_SPACE_X) +
4018 globals.mMinTimeval;
4021 ** Loop over the run.
4022 ** Should an allocation have been allocated between
4023 ** prevTimeval and timeval....
4025 for (loop = 0; loop < aRun->mAllocationCount; loop++) {
4026 if (prevTimeval < aRun->mAllocations[loop]->mMinTimeval
4027 && timeval >= aRun->mAllocations[loop]->mMinTimeval) {
4028 YData[traverse] +=
4029 byteSize(&inRequest->mOptions,
4030 aRun->mAllocations[loop]);
4036 ** Did we cache this?
4038 if (YData == inRequest->mContext->mTimevalYData) {
4039 inRequest->mContext->mTimevalCached = PR_TRUE;
4044 ** Done with the lock.
4046 if (PR_FALSE != underLock) {
4047 PR_Unlock(inRequest->mContext->mImageLock);
4050 if (0 == retval) {
4051 PRUint32 minMemory = (PRUint32) - 1;
4052 PRUint32 maxMemory = 0;
4053 int transparent = 0;
4054 gdImagePtr graph = NULL;
4057 ** Go through and find the minimum and maximum sizes.
4059 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4060 if (YData[traverse] < minMemory) {
4061 minMemory = YData[traverse];
4063 if (YData[traverse] > maxMemory) {
4064 maxMemory = YData[traverse];
4069 ** We can now draw the graph.
4071 graph = createGraph(&transparent);
4072 if (NULL != graph) {
4073 gdSink theSink;
4074 int red = 0;
4075 int x1 = 0;
4076 int y1 = 0;
4077 int x2 = 0;
4078 int y2 = 0;
4079 PRUint32 percents[11] =
4080 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
4081 char *timevals[11];
4082 char *bytes[11];
4083 char timevalSpace[11][32];
4084 char byteSpace[11][32];
4085 int legendColors[1];
4086 const char *legends[1] = { "Memory Allocated" };
4087 PRUint32 cached = 0;
4090 ** Figure out what the labels will say.
4092 for (traverse = 0; traverse < 11; traverse++) {
4093 timevals[traverse] = timevalSpace[traverse];
4094 bytes[traverse] = byteSpace[traverse];
4096 cached =
4097 ((globals.mMaxTimeval -
4098 globals.mMinTimeval) * percents[traverse]) / 100;
4099 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT,
4100 ST_TIMEVAL_PRINTABLE(cached));
4101 PR_snprintf(bytes[traverse], 32, "%u",
4102 ((maxMemory -
4103 minMemory) * percents[traverse]) / 100);
4106 red = gdImageColorAllocate(graph, 255, 0, 0);
4107 legendColors[0] = red;
4109 drawGraph(graph, -1, "Allocation Times", "Seconds", "Bytes",
4110 11, percents, (const char **) timevals, 11,
4111 percents, (const char **) bytes, 1, legendColors,
4112 legends);
4114 if (maxMemory != minMemory) {
4115 PRInt64 in64 = LL_INIT(0, 0);
4116 PRInt64 ydata64 = LL_INIT(0, 0);
4117 PRInt64 spacey64 = LL_INIT(0, 0);
4118 PRInt64 mem64 = LL_INIT(0, 0);
4119 PRInt32 in32 = 0;
4122 ** Go through our Y data and mark it up.
4124 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4125 x1 = traverse + STGD_MARGIN;
4126 y1 = STGD_HEIGHT - STGD_MARGIN;
4129 ** Need to do this math in 64 bits.
4131 LL_I2L(ydata64, YData[traverse]);
4132 LL_I2L(spacey64, STGD_SPACE_Y);
4133 LL_I2L(mem64, (maxMemory - minMemory));
4135 LL_MUL(in64, ydata64, spacey64);
4136 LL_DIV(in64, in64, mem64);
4137 LL_L2I(in32, in64);
4139 x2 = x1;
4140 y2 = y1 - in32;
4142 gdImageLine(graph, x1, y1, x2, y2, red);
4147 theSink.context = inRequest->mFD;
4148 theSink.sink = pngSink;
4149 gdImagePngToSink(graph, &theSink);
4151 gdImageDestroy(graph);
4153 else {
4154 retval = __LINE__;
4155 REPORT_ERROR(__LINE__, createGraph);
4159 else {
4160 retval = __LINE__;
4161 REPORT_ERROR(__LINE__, graphTimeval);
4164 return retval;
4166 #endif /* ST_WANT_GRAPHS */
4168 #if ST_WANT_GRAPHS
4170 ** graphLifespan
4172 ** Output a PNG graph of how long memory lived.
4174 ** Draw the graph within these boundaries.
4175 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN
4177 ** Returns !0 on failure.
4180 graphLifespan(STRequest * inRequest, STRun * aRun)
4182 int retval = 0;
4184 if (NULL != aRun) {
4185 PRUint32 *YData = NULL;
4186 PRUint32 YDataArray[STGD_SPACE_X];
4187 PRUint32 traverse = 0;
4188 PRUint32 timeval = 0;
4189 PRUint32 loop = 0;
4190 PRBool underLock = PR_FALSE;
4193 ** Decide if this is custom or we should use the global cache.
4195 if (aRun == inRequest->mContext->mSortedRun) {
4196 YData = inRequest->mContext->mLifespanYData;
4197 underLock = PR_TRUE;
4199 else {
4200 YData = YDataArray;
4204 ** Protect the shared data so that only one client has access to it
4205 ** at any given time.
4207 if (PR_FALSE != underLock) {
4208 PR_Lock(inRequest->mContext->mImageLock);
4212 ** Only do the computations if we aren't cached already.
4214 if (YData != inRequest->mContext->mLifespanYData
4215 || PR_FALSE == inRequest->mContext->mLifespanCached) {
4216 PRUint32 prevTimeval = 0;
4217 PRUint32 lifespan = 0;
4219 memset(YData, 0, sizeof(PRUint32) * STGD_SPACE_X);
4222 ** Initialize our Y data.
4223 ** Pretty brutal loop here....
4225 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X;
4226 traverse++) {
4228 ** Compute what timeval this Y data lands in.
4230 prevTimeval = timeval;
4231 timeval =
4232 (traverse * (globals.mMaxTimeval - globals.mMinTimeval)) /
4233 STGD_SPACE_X;
4236 ** Loop over the run.
4237 ** Should an allocation have lived between
4238 ** prevTimeval and timeval....
4240 for (loop = 0; loop < aRun->mAllocationCount; loop++) {
4241 lifespan =
4242 aRun->mAllocations[loop]->mMaxTimeval -
4243 aRun->mAllocations[loop]->mMinTimeval;
4245 if (prevTimeval < lifespan && timeval >= lifespan) {
4246 YData[traverse] +=
4247 byteSize(&inRequest->mOptions,
4248 aRun->mAllocations[loop]);
4254 ** Did we cache this?
4256 if (YData == inRequest->mContext->mLifespanYData) {
4257 inRequest->mContext->mLifespanCached = PR_TRUE;
4262 ** Done with the lock.
4264 if (PR_FALSE != underLock) {
4265 PR_Unlock(inRequest->mContext->mImageLock);
4268 if (0 == retval) {
4269 PRUint32 minMemory = (PRUint32) - 1;
4270 PRUint32 maxMemory = 0;
4271 int transparent = 0;
4272 gdImagePtr graph = NULL;
4275 ** Go through and find the minimum and maximum sizes.
4277 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4278 if (YData[traverse] < minMemory) {
4279 minMemory = YData[traverse];
4281 if (YData[traverse] > maxMemory) {
4282 maxMemory = YData[traverse];
4287 ** We can now draw the graph.
4289 graph = createGraph(&transparent);
4290 if (NULL != graph) {
4291 gdSink theSink;
4292 int red = 0;
4293 int x1 = 0;
4294 int y1 = 0;
4295 int x2 = 0;
4296 int y2 = 0;
4297 PRUint32 percents[11] =
4298 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
4299 char *timevals[11];
4300 char *bytes[11];
4301 char timevalSpace[11][32];
4302 char byteSpace[11][32];
4303 int legendColors[1];
4304 const char *legends[1] = { "Live Memory" };
4305 PRUint32 cached = 0;
4308 ** Figure out what the labels will say.
4310 for (traverse = 0; traverse < 11; traverse++) {
4311 timevals[traverse] = timevalSpace[traverse];
4312 bytes[traverse] = byteSpace[traverse];
4314 cached =
4315 ((globals.mMaxTimeval -
4316 globals.mMinTimeval) * percents[traverse]) / 100;
4317 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT,
4318 ST_TIMEVAL_PRINTABLE(cached));
4319 PR_snprintf(bytes[traverse], 32, "%u",
4320 ((maxMemory -
4321 minMemory) * percents[traverse]) / 100);
4324 red = gdImageColorAllocate(graph, 255, 0, 0);
4325 legendColors[0] = red;
4327 drawGraph(graph, -1, "Allocation Lifespans", "Lifespan",
4328 "Bytes", 11, percents, (const char **) timevals, 11,
4329 percents, (const char **) bytes, 1, legendColors,
4330 legends);
4332 if (maxMemory != minMemory) {
4333 PRInt64 in64 = LL_INIT(0, 0);
4334 PRInt64 ydata64 = LL_INIT(0, 0);
4335 PRInt64 spacey64 = LL_INIT(0, 0);
4336 PRInt64 mem64 = LL_INIT(0, 0);
4337 PRInt32 in32 = 0;
4340 ** Go through our Y data and mark it up.
4342 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4343 x1 = traverse + STGD_MARGIN;
4344 y1 = STGD_HEIGHT - STGD_MARGIN;
4347 ** Need to do this math in 64 bits.
4349 LL_I2L(ydata64, YData[traverse]);
4350 LL_I2L(spacey64, STGD_SPACE_Y);
4351 LL_I2L(mem64, (maxMemory - minMemory));
4353 LL_MUL(in64, ydata64, spacey64);
4354 LL_DIV(in64, in64, mem64);
4355 LL_L2I(in32, in64);
4357 x2 = x1;
4358 y2 = y1 - in32;
4360 gdImageLine(graph, x1, y1, x2, y2, red);
4365 theSink.context = inRequest->mFD;
4366 theSink.sink = pngSink;
4367 gdImagePngToSink(graph, &theSink);
4369 gdImageDestroy(graph);
4371 else {
4372 retval = __LINE__;
4373 REPORT_ERROR(__LINE__, createGraph);
4377 else {
4378 retval = __LINE__;
4379 REPORT_ERROR(__LINE__, graphLifespan);
4382 return retval;
4384 #endif /* ST_WANT_GRAPHS */
4386 #if ST_WANT_GRAPHS
4388 ** graphWeight
4390 ** Output a PNG graph of Allocations by Weight
4392 ** Draw the graph within these boundaries.
4393 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN
4395 ** Returns !0 on failure.
4398 graphWeight(STRequest * inRequest, STRun * aRun)
4400 int retval = 0;
4402 if (NULL != aRun) {
4403 PRUint64 *YData64 = NULL;
4404 PRUint64 YDataArray64[STGD_SPACE_X];
4405 PRUint32 traverse = 0;
4406 PRUint32 timeval = globals.mMinTimeval;
4407 PRUint32 loop = 0;
4408 PRBool underLock = PR_FALSE;
4411 ** Decide if this is custom or we should use the global cache.
4413 if (aRun == inRequest->mContext->mSortedRun) {
4414 YData64 = inRequest->mContext->mWeightYData64;
4415 underLock = PR_TRUE;
4417 else {
4418 YData64 = YDataArray64;
4422 ** Protect the shared data so that only one client has access to it
4423 ** at any given time.
4425 if (PR_FALSE != underLock) {
4426 PR_Lock(inRequest->mContext->mImageLock);
4430 ** Only do the computations if we aren't cached already.
4432 if (YData64 != inRequest->mContext->mWeightYData64
4433 || PR_FALSE == inRequest->mContext->mWeightCached) {
4434 PRUint32 prevTimeval = 0;
4436 memset(YData64, 0, sizeof(PRUint64) * STGD_SPACE_X);
4439 ** Initialize our Y data.
4440 ** Pretty brutal loop here....
4442 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X;
4443 traverse++) {
4445 ** Compute what timeval this Y data lands in.
4447 prevTimeval = timeval;
4448 timeval =
4449 ((traverse *
4450 (globals.mMaxTimeval -
4451 globals.mMinTimeval)) / STGD_SPACE_X) +
4452 globals.mMinTimeval;
4455 ** Loop over the run.
4456 ** Should an allocation have been allocated between
4457 ** prevTimeval and timeval....
4459 for (loop = 0; loop < aRun->mAllocationCount; loop++) {
4460 if (prevTimeval < aRun->mAllocations[loop]->mMinTimeval
4461 && timeval >= aRun->mAllocations[loop]->mMinTimeval) {
4462 PRUint64 size64 = LL_INIT(0, 0);
4463 PRUint64 lifespan64 = LL_INIT(0, 0);
4464 PRUint64 weight64 = LL_INIT(0, 0);
4466 LL_UI2L(size64,
4467 byteSize(&inRequest->mOptions,
4468 aRun->mAllocations[loop]));
4469 LL_UI2L(lifespan64,
4470 (aRun->mAllocations[loop]->mMaxTimeval -
4471 aRun->mAllocations[loop]->mMinTimeval));
4472 LL_MUL(weight64, size64, lifespan64);
4474 LL_ADD(YData64[traverse], YData64[traverse],
4475 weight64);
4481 ** Did we cache this?
4483 if (YData64 == inRequest->mContext->mWeightYData64) {
4484 inRequest->mContext->mWeightCached = PR_TRUE;
4489 ** Done with the lock.
4491 if (PR_FALSE != underLock) {
4492 PR_Unlock(inRequest->mContext->mImageLock);
4495 if (0 == retval) {
4496 PRUint64 minWeight64 = LL_INIT(0xFFFFFFFF, 0xFFFFFFFF);
4497 PRUint64 maxWeight64 = LL_INIT(0, 0);
4498 int transparent = 0;
4499 gdImagePtr graph = NULL;
4502 ** Go through and find the minimum and maximum weights.
4504 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4505 if (LL_UCMP(YData64[traverse], <, minWeight64)) {
4506 minWeight64 = YData64[traverse];
4508 if (LL_UCMP(YData64[traverse], >, maxWeight64)) {
4509 maxWeight64 = YData64[traverse];
4514 ** We can now draw the graph.
4516 graph = createGraph(&transparent);
4517 if (NULL != graph) {
4518 gdSink theSink;
4519 int red = 0;
4520 int x1 = 0;
4521 int y1 = 0;
4522 int x2 = 0;
4523 int y2 = 0;
4524 PRUint32 percents[11] =
4525 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
4526 char *timevals[11];
4527 char *bytes[11];
4528 char timevalSpace[11][32];
4529 char byteSpace[11][32];
4530 int legendColors[1];
4531 const char *legends[1] = { "Memory Weight" };
4532 PRUint64 percent64 = LL_INIT(0, 0);
4533 PRUint64 result64 = LL_INIT(0, 0);
4534 PRUint64 hundred64 = LL_INIT(0, 0);
4535 PRUint32 cached = 0;
4537 LL_UI2L(hundred64, 100);
4540 ** Figure out what the labels will say.
4542 for (traverse = 0; traverse < 11; traverse++) {
4543 timevals[traverse] = timevalSpace[traverse];
4544 bytes[traverse] = byteSpace[traverse];
4546 cached =
4547 ((globals.mMaxTimeval -
4548 globals.mMinTimeval) * percents[traverse]) / 100;
4549 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT,
4550 ST_TIMEVAL_PRINTABLE(cached));
4552 LL_UI2L(percent64, percents[traverse]);
4553 LL_SUB(result64, maxWeight64, minWeight64);
4554 LL_MUL(result64, result64, percent64);
4555 LL_DIV(result64, result64, hundred64);
4556 PR_snprintf(bytes[traverse], 32, "%llu", result64);
4559 red = gdImageColorAllocate(graph, 255, 0, 0);
4560 legendColors[0] = red;
4562 drawGraph(graph, -1, "Allocation Weights", "Seconds",
4563 "Weight", 11, percents, (const char **) timevals,
4564 11, percents, (const char **) bytes, 1,
4565 legendColors, legends);
4567 if (LL_NE(maxWeight64, minWeight64)) {
4568 PRInt64 in64 = LL_INIT(0, 0);
4569 PRInt64 spacey64 = LL_INIT(0, 0);
4570 PRInt64 weight64 = LL_INIT(0, 0);
4571 PRInt32 in32 = 0;
4574 ** Go through our Y data and mark it up.
4576 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4577 x1 = traverse + STGD_MARGIN;
4578 y1 = STGD_HEIGHT - STGD_MARGIN;
4581 ** Need to do this math in 64 bits.
4583 LL_I2L(spacey64, STGD_SPACE_Y);
4584 LL_SUB(weight64, maxWeight64, minWeight64);
4586 LL_MUL(in64, YData64[traverse], spacey64);
4587 LL_DIV(in64, in64, weight64);
4588 LL_L2I(in32, in64);
4590 x2 = x1;
4591 y2 = y1 - in32;
4593 gdImageLine(graph, x1, y1, x2, y2, red);
4598 theSink.context = inRequest->mFD;
4599 theSink.sink = pngSink;
4600 gdImagePngToSink(graph, &theSink);
4602 gdImageDestroy(graph);
4604 else {
4605 retval = __LINE__;
4606 REPORT_ERROR(__LINE__, createGraph);
4610 else {
4611 retval = __LINE__;
4612 REPORT_ERROR(__LINE__, graphWeight);
4615 return retval;
4617 #endif /* ST_WANT_GRAPHS */
4619 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
4621 PRUint32 convert = (PRUint32)outOptions->m##option_name; \
4623 getDataPRUint32(inFormData, #option_name, 1, &convert, 1); \
4624 outOptions->m##option_name = (PRBool)convert; \
4626 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
4627 getDataString(inFormData, #option_name, 1, outOptions->m##option_name, sizeof(outOptions->m##option_name));
4628 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
4630 PRUint32 loop = 0; \
4631 PRUint32 found = 0; \
4632 char buffer[ST_OPTION_STRING_MAX]; \
4634 for(loop = 0; loop < array_size; loop++) \
4636 buffer[0] = '\0'; \
4637 getDataString(inFormData, #option_name, (loop + 1), buffer, sizeof(buffer)); \
4639 if('\0' != buffer[0]) \
4641 PR_snprintf(outOptions->m##option_name[found], sizeof(outOptions->m##option_name[found]), "%s", buffer); \
4642 found++; \
4646 for(; found < array_size; found++) \
4648 outOptions->m##option_name[found][0] = '\0'; \
4651 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */
4652 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
4653 getDataPRUint32(inFormData, #option_name, 1, &outOptions->m##option_name, multiplier);
4654 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
4656 PRUint64 mul64 = multiplier; \
4658 getDataPRUint64(inFormData, #option_name, 1, &outOptions->m##option_name##64, mul64); \
4661 ** fillOptions
4663 ** Given an appropriate hexcaped string, distill the option values
4664 ** and fill the given STOption struct.
4666 ** Note that the options passed in are not touched UNLESS there is
4667 ** a replacement found in the form data.
4669 void
4670 fillOptions(STOptions * outOptions, const FormData * inFormData)
4672 if (NULL != outOptions && NULL != inFormData) {
4674 #include "stoptions.h"
4677 ** Special sanity check here for some options that need data validation.
4679 if (!outOptions->mCategoryName[0]
4680 || !findCategoryNode(outOptions->mCategoryName, &globals)) {
4681 PR_snprintf(outOptions->mCategoryName,
4682 sizeof(outOptions->mCategoryName), "%s",
4683 ST_ROOT_CATEGORY_NAME);
4689 void
4690 displayOptionString(STRequest * inRequest,
4691 const char *option_name,
4692 const char *option_genre,
4693 const char *default_value,
4694 const char *option_help, const char *value)
4696 #if 0
4697 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name);
4698 #endif
4699 PR_fprintf(inRequest->mFD, "<div class=option-box>\n");
4700 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name);
4701 PR_fprintf(inRequest->mFD,
4702 "<input type=text name=\"%s\" value=\"%s\">\n",
4703 option_name, value);
4704 PR_fprintf(inRequest->mFD,
4705 "<p class=option-default>Default value is \"%s\".</p>\n<p class=option-help>%s</p>\n",
4706 default_value, option_help);
4707 PR_fprintf(inRequest->mFD, "</div>\n");
4710 static void
4711 displayOptionStringArray(STRequest * inRequest,
4712 const char *option_name,
4713 const char *option_genre,
4714 PRUint32 array_size,
4715 const char *option_help, const char values[5]
4716 [ST_OPTION_STRING_MAX])
4718 /* values should not be a fixed length! */
4719 PR_ASSERT(array_size == 5);
4720 #if 0
4721 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name);
4722 #endif
4723 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n");
4724 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name); {
4725 PRUint32 loop = 0;
4727 for (loop = 0; loop < array_size; loop++) {
4728 PR_fprintf(inRequest->mFD,
4729 "<input type=text name=\"%s\" value=\"%s\"><br>\n",
4730 option_name, values[loop]);
4733 PR_fprintf(inRequest->mFD,
4734 "<p class=option-default>Up to %u occurrences allowed.</p>\n<p class=option-help>%s</p>\n",
4735 array_size, option_help);
4736 PR_fprintf(inRequest->mFD, "</div>\n");
4739 static void
4740 displayOptionInt(STRequest * inRequest,
4741 const char *option_name,
4742 const char *option_genre,
4743 PRUint32 default_value,
4744 PRUint32 multiplier, const char *option_help, PRUint32 value)
4746 #if 0
4747 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name);
4748 #endif
4749 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n");
4750 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name);
4751 PR_fprintf(inRequest->mFD,
4752 "<input type=text name=%s value=%u>\n", option_name,
4753 value / multiplier);
4754 PR_fprintf(inRequest->mFD,
4755 "<p class=option-default>Default value is %u.</p>\n<p class=option-help>%s</p>\n",
4756 default_value, option_help);
4757 PR_fprintf(inRequest->mFD, "</div>\n");
4760 static void
4761 displayOptionInt64(STRequest * inRequest,
4762 const char *option_name,
4763 const char *option_genre,
4764 PRUint64 default_value,
4765 PRUint64 multiplier,
4766 const char *option_help, PRUint64 value)
4768 #if 0
4769 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name);
4770 #endif
4771 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n");
4772 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name); {
4773 PRUint64 def64 = default_value;
4774 PRUint64 mul64 = multiplier;
4775 PRUint64 div64;
4777 LL_DIV(div64, value, mul64);
4778 PR_fprintf(inRequest->mFD,
4779 "<input type=text name=%s value=%llu>\n",
4780 option_name, div64);
4781 PR_fprintf(inRequest->mFD,
4782 "<p class=option-default>Default value is %llu.</p>\n<p class=option-help>%s</p>\n",
4783 def64, option_help);
4785 PR_fprintf(inRequest->mFD, "</div>\n");
4789 ** displaySettings
4791 ** Present the settings for change during execution.
4793 void
4794 displaySettings(STRequest * inRequest)
4796 int applyRes = 0;
4799 ** We've got a form to create.
4801 PR_fprintf(inRequest->mFD, "<form method=get action=\"./index.html\">\n");
4803 ** Respect newlines in help text.
4805 #if 0
4806 PR_fprintf(inRequest->mFD, "<pre>\n");
4807 #endif
4808 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
4809 displayOptionBool(option_name, option_genre, option_help)
4810 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
4811 displayOptionString(inRequest, #option_name, #option_genre, default_value, option_help, inRequest->mOptions.m##option_name);
4812 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
4813 displayOptionStringArray(inRequest, #option_name, #option_genre, array_size, option_help, inRequest->mOptions.m##option_name);
4814 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */
4815 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
4816 displayOptionInt(inRequest, #option_name, #option_genre, default_value, multiplier, option_help, inRequest->mOptions.m##option_name);
4817 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
4818 displayOptionInt64(inRequest, #option_name, #option_genre, default_value, multiplier, option_help, inRequest->mOptions.m##option_name##64);
4819 #include "stoptions.h"
4821 ** Give a submit/reset button, obligatory.
4822 ** Done respecting newlines in help text.
4824 PR_fprintf(inRequest->mFD,
4825 "<input type=submit value=\"Save Options\"> <input type=reset>\n");
4826 #if 0
4827 PR_fprintf(inRequest->mFD, "</pre>\n");
4828 #endif
4830 ** Done with form.
4832 PR_fprintf(inRequest->mFD, "</form>\n");
4836 handleLocalFile(STRequest * inRequest, const char *aFilename)
4838 static const char *const local_files[] = {
4839 "spacetrace.css",
4841 static const size_t local_file_count =
4842 sizeof(local_files) / sizeof(local_files[0]);
4843 size_t i;
4845 for (i = 0; i < local_file_count; i++) {
4846 if (0 == strcmp(local_files[i], aFilename))
4847 return 1;
4849 return 0;
4853 ** displayFile
4855 ** reads a file from disk, and streams it to the request
4858 displayFile(STRequest * inRequest, const char *aFilename)
4860 PRFileDesc *inFd;
4861 const char *filepath =
4862 PR_smprintf("res%c%s", PR_GetDirectorySeparator(), aFilename);
4863 char buffer[2048];
4864 PRInt32 readRes;
4866 inFd = PR_Open(filepath, PR_RDONLY, PR_IRUSR);
4867 if (!inFd)
4868 return -1;
4869 while ((readRes = PR_Read(inFd, buffer, sizeof(buffer))) > 0) {
4870 PR_Write(inRequest->mFD, buffer, readRes);
4872 if (readRes != 0)
4873 return -1;
4874 PR_Close(inFd);
4875 return 0;
4879 ** displayIndex
4881 ** Present a list of the reports you can drill down into.
4882 ** Returns !0 on failure.
4885 displayIndex(STRequest * inRequest)
4887 int retval = 0;
4888 STOptions *options = &inRequest->mOptions;
4891 ** Present reports in a list format.
4893 PR_fprintf(inRequest->mFD, "<ul>");
4894 PR_fprintf(inRequest->mFD, "\n<li>");
4895 htmlAnchor(inRequest, "root_callsites.html", "Root Callsites",
4896 NULL, "mainmenu", options);
4897 PR_fprintf(inRequest->mFD, "\n<li>");
4898 htmlAnchor(inRequest, "categories_summary.html",
4899 "Categories Report", NULL, "mainmenu", options);
4900 PR_fprintf(inRequest->mFD, "\n<li>");
4901 htmlAnchor(inRequest, "top_callsites.html",
4902 "Top Callsites Report", NULL, "mainmenu", options);
4903 PR_fprintf(inRequest->mFD, "\n<li>");
4904 htmlAnchor(inRequest, "top_allocations.html",
4905 "Top Allocations Report", NULL, "mainmenu", options);
4906 PR_fprintf(inRequest->mFD, "\n<li>");
4907 htmlAnchor(inRequest, "memory_leaks.html",
4908 "Memory Leak Report", NULL, "mainmenu", options);
4909 #if ST_WANT_GRAPHS
4910 PR_fprintf(inRequest->mFD, "\n<li>Graphs");
4911 PR_fprintf(inRequest->mFD, "<ul>");
4912 PR_fprintf(inRequest->mFD, "\n<li>");
4913 htmlAnchor(inRequest, "footprint_graph.html", "Footprint",
4914 NULL, "mainmenu graph", options);
4915 PR_fprintf(inRequest->mFD, "\n<li>");
4916 htmlAnchor(inRequest, "lifespan_graph.html",
4917 "Allocation Lifespans", NULL, "mainmenu graph", options);
4918 PR_fprintf(inRequest->mFD, "\n<li>");
4919 htmlAnchor(inRequest, "times_graph.html", "Allocation Times",
4920 NULL, "mainmenu graph", options);
4921 PR_fprintf(inRequest->mFD, "\n<li>");
4922 htmlAnchor(inRequest, "weight_graph.html",
4923 "Allocation Weights", NULL, "mainmenu graph", options);
4924 PR_fprintf(inRequest->mFD, "\n</ul>\n");
4925 #endif /* ST_WANT_GRAPHS */
4926 PR_fprintf(inRequest->mFD, "\n</ul>\n");
4927 return retval;
4931 ** initRequestOptions
4933 ** Given the request, set the options that are specific to the request.
4934 ** These can generally be determined in the following manner:
4935 ** Copy over global options.
4936 ** If getData present, attempt to use options therein.
4938 void
4939 initRequestOptions(STRequest * inRequest)
4941 if (NULL != inRequest) {
4943 ** Copy of global options.
4945 memcpy(&inRequest->mOptions, &globals.mCommandLineOptions,
4946 sizeof(globals.mCommandLineOptions));
4948 ** Decide what will override global options if anything.
4950 if (NULL != inRequest->mGetData) {
4951 fillOptions(&inRequest->mOptions, inRequest->mGetData);
4956 STContext *
4957 contextLookup(STOptions * inOptions)
4959 ** Lookup a context that matches the options.
4960 ** The lookup may block, especially if the context needs to be created.
4961 ** Callers of this API must eventually call contextRelease with the
4962 ** return value; failure to do so will cause this applications
4963 ** to eventually not work as advertised.
4965 ** inOptions The options determine which context is relevant.
4966 ** returns The fully completed context on success.
4967 ** The context is read only in practice, so please do not
4968 ** write to it or anything it points to.
4969 ** NULL on failure.
4972 STContext *retval = NULL;
4973 STContextCache *inCache = &globals.mContextCache;
4975 if (NULL != inOptions && NULL != inCache) {
4976 PRUint32 loop = 0;
4977 STContext *categoryException = NULL;
4978 PRBool newContext = PR_FALSE;
4979 PRBool evictContext = PR_FALSE;
4980 PRBool changeCategoryContext = PR_FALSE;
4983 ** Own the context cache while we are in here.
4985 PR_Lock(inCache->mLock);
4987 ** Loop until successful.
4988 ** Waiting on the condition variable makes sure we don't hog the
4989 ** lock below.
4991 while (1) {
4993 ** Go over the cache items.
4994 ** At this point we are looking for a cache hit, with multiple
4995 ** readers.
4997 for (loop = 0; loop < inCache->mItemCount; loop++) {
4999 ** Must be in use.
5001 if (PR_FALSE != inCache->mItems[loop].mInUse) {
5002 int delta[(STOptionGenre) MaxGenres];
5005 ** Compare the relevant options, figure out if different
5006 ** in any genre that we care about.
5008 memset(&delta, 0, sizeof(delta));
5009 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
5010 if(inOptions->m##option_name != inCache->mItems[loop].mOptions.m##option_name) \
5012 delta[(STOptionGenre)option_genre]++; \
5014 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
5015 if(0 != strcmp(inOptions->m##option_name, inCache->mItems[loop].mOptions.m##option_name)) \
5017 delta[(STOptionGenre)option_genre]++; \
5019 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
5021 PRUint32 macro_loop = 0; \
5023 for(macro_loop = 0; macro_loop < array_size; macro_loop++) \
5025 if(0 != strcmp(inOptions->m##option_name[macro_loop], inCache->mItems[loop].mOptions.m##option_name[macro_loop])) \
5027 break; \
5031 if(macro_loop != array_size) \
5033 delta[(STOptionGenre)option_genre]++; \
5036 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */
5037 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
5038 if(inOptions->m##option_name != inCache->mItems[loop].mOptions.m##option_name) \
5040 delta[(STOptionGenre)option_genre]++; \
5042 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
5043 if(LL_NE(inOptions->m##option_name##64, inCache->mItems[loop].mOptions.m##option_name##64)) \
5045 delta[(STOptionGenre)option_genre]++; \
5047 #include "stoptions.h"
5049 ** If there is no genre out of alignment, we accept this as the context.
5051 if (0 == delta[CategoryGenre] &&
5052 0 == delta[DataSortGenre] &&
5053 0 == delta[DataSetGenre] && 0 == delta[DataSizeGenre]
5055 retval = &inCache->mItems[loop].mContext;
5056 break;
5060 ** A special exception to the rule here.
5061 ** If all that is different is the category genre, and there
5062 ** is no one looking at the context (zero ref count),
5063 ** then there is some magic we can perform.
5065 if (NULL == retval &&
5066 0 == inCache->mItems[loop].mReferenceCount &&
5067 0 != delta[CategoryGenre] &&
5068 0 == delta[DataSortGenre] &&
5069 0 == delta[DataSetGenre] && 0 == delta[DataSizeGenre]
5071 categoryException = &inCache->mItems[loop].mContext;
5077 ** Pick up our category exception if relevant.
5079 if (NULL == retval && NULL != categoryException) {
5080 retval = categoryException;
5081 categoryException = NULL;
5082 changeCategoryContext = PR_TRUE;
5086 ** If we don't have a cache hit, then we need to check for an empty
5087 ** spot to take over.
5089 if (NULL == retval) {
5090 for (loop = 0; loop < inCache->mItemCount; loop++) {
5092 ** Must NOT be in use, then it will be the context.
5094 if (PR_FALSE == inCache->mItems[loop].mInUse) {
5095 retval = &inCache->mItems[loop].mContext;
5096 newContext = PR_TRUE;
5097 break;
5103 ** If we still don't have a return value, then we need to see if
5104 ** there are any old items with zero ref counts that we
5105 ** can take over.
5107 if (NULL == retval) {
5108 for (loop = 0; loop < inCache->mItemCount; loop++) {
5110 ** Must be in use.
5112 if (PR_FALSE != inCache->mItems[loop].mInUse) {
5114 ** Must have a ref count of zero.
5116 if (0 == inCache->mItems[loop].mReferenceCount) {
5118 ** Must be older than any other we know of.
5120 if (NULL != retval) {
5121 if (inCache->mItems[loop].mLastAccessed <
5122 inCache->mItems[retval->mIndex].
5123 mLastAccessed) {
5124 retval = &inCache->mItems[loop].mContext;
5127 else {
5128 retval = &inCache->mItems[loop].mContext;
5134 if (NULL != retval) {
5135 evictContext = PR_TRUE;
5140 ** If we still don't have a return value, then we can not avoid
5141 ** waiting around until someone gives us the chance.
5142 ** The chance, in specific, comes when a cache item reference
5143 ** count returns to zero, upon which we can try to take
5144 ** it over again.
5146 if (NULL == retval) {
5148 ** This has the side effect of release the context lock.
5149 ** This is a good thing so that other clients can continue
5150 ** to connect and hopefully have cache hits.
5151 ** If they do not have cache hits, then we will end up
5152 ** with a bunch of waiters here....
5154 PR_WaitCondVar(inCache->mCacheMiss, PR_INTERVAL_NO_TIMEOUT);
5158 ** If we have a return value, improve the reference count here.
5160 if (NULL != retval) {
5162 ** Decide if there are any changes to be made.
5163 ** Do as little as possible, then fall through the context
5164 ** cache lock to finish up.
5165 ** This way, lengthy init operations will not block
5166 ** other clients, only matches to this context.
5168 if (PR_FALSE != newContext ||
5169 PR_FALSE != evictContext ||
5170 PR_FALSE != changeCategoryContext) {
5172 ** Overwrite the prefs for this context.
5173 ** They are changing.
5175 memcpy(&inCache->mItems[retval->mIndex].mOptions,
5176 inOptions,
5177 sizeof(inCache->mItems[retval->mIndex].mOptions));
5179 ** As we are going to be changing the context, we need to write lock it.
5180 ** This makes sure no readers are allowed while we are making our changes.
5182 PR_RWLock_Wlock(retval->mRWLock);
5186 ** NOTE, ref count gets incremented here, inside content
5187 ** cache lock so it can not be flushed once lock
5188 ** released.
5190 inCache->mItems[retval->mIndex].mInUse = PR_TRUE;
5191 inCache->mItems[retval->mIndex].mReferenceCount++;
5193 ** That's all folks.
5195 break;
5198 } /* while(1), try again */
5201 ** Done with context cache.
5203 PR_Unlock(inCache->mLock);
5205 ** Now that the context cached is free to continue accepting other
5206 ** requests, we have a little more work to do.
5208 if (NULL != retval) {
5209 PRBool unlock = PR_FALSE;
5212 ** If evicting, we need to free off the old stuff.
5214 if (PR_FALSE != evictContext) {
5215 unlock = PR_TRUE;
5217 ** We do not free the sorted run.
5218 ** The category code takes care of this.
5220 retval->mSortedRun = NULL;
5221 #if ST_WANT_GRAPHS
5223 ** There is no need to
5224 ** PR_Lock(retval->mImageLock)
5225 ** We are already under write lock for the entire structure.
5227 retval->mFootprintCached = PR_FALSE;
5228 retval->mTimevalCached = PR_FALSE;
5229 retval->mLifespanCached = PR_FALSE;
5230 retval->mWeightCached = PR_FALSE;
5231 #endif
5235 ** If new or recently evicted, we need to fully init.
5237 if (PR_FALSE != newContext || PR_FALSE != evictContext) {
5238 unlock = PR_TRUE;
5239 retval->mSortedRun =
5240 createRunFromGlobal(&inCache->mItems[retval->mIndex].
5241 mOptions,
5242 &inCache->mItems[retval->mIndex].
5243 mContext);
5247 ** If changing category, we need to do some sneaky stuff.
5249 if (PR_FALSE != changeCategoryContext) {
5250 STCategoryNode *node = NULL;
5252 unlock = PR_TRUE;
5254 ** Just a category change. We don't need to harvest. Just find the
5255 ** right node and set the cache.mSortedRun. We need to recompute
5256 ** cost though. But that is cheap.
5258 node =
5259 findCategoryNode(inCache->mItems[retval->mIndex].mOptions.
5260 mCategoryName, &globals);
5261 if (node) {
5262 /* Recalculate cost of run */
5263 recalculateRunCost(&inCache->mItems[retval->mIndex].
5264 mOptions, retval,
5265 node->runs[retval->mIndex]);
5266 retval->mSortedRun = node->runs[retval->mIndex];
5269 #if ST_WANT_GRAPHS
5271 ** There is no need to
5272 ** PR_Lock(retval->mImageLock)
5273 ** We are already under write lock for the entire structure.
5275 retval->mFootprintCached = PR_FALSE;
5276 retval->mTimevalCached = PR_FALSE;
5277 retval->mLifespanCached = PR_FALSE;
5278 retval->mWeightCached = PR_FALSE;
5279 #endif
5283 ** Release the write lock if we took one to make changes.
5285 if (PR_FALSE != unlock) {
5286 PR_RWLock_Unlock(retval->mRWLock);
5290 ** Last thing possible, take a read lock on our return value.
5291 ** This will cause us to block if the context is not fully
5292 ** initialized in another thread holding the write lock.
5294 PR_RWLock_Rlock(retval->mRWLock);
5298 return retval;
5301 void
5302 contextRelease(STContext * inContext)
5304 ** After a successful call to contextLookup, one should call this API when
5305 ** done with the context.
5306 ** This effectively removes the usage of the client on a cached item.
5309 STContextCache *inCache = &globals.mContextCache;
5311 if (NULL != inContext && NULL != inCache) {
5313 ** Own the context cache while in here.
5315 PR_Lock(inCache->mLock);
5317 ** Give up the read lock on the context.
5319 PR_RWLock_Unlock(inContext->mRWLock);
5321 ** Decrement the reference count on the context.
5322 ** If it was the last reference, notify that a new item is
5323 ** available for eviction.
5324 ** A waiting thread will wake up and eat it.
5325 ** Also set when it was last accessed so the oldest unused item
5326 ** can be targeted for eviction.
5328 inCache->mItems[inContext->mIndex].mReferenceCount--;
5329 if (0 == inCache->mItems[inContext->mIndex].mReferenceCount) {
5330 PR_NotifyCondVar(inCache->mCacheMiss);
5331 inCache->mItems[inContext->mIndex].mLastAccessed =
5332 PR_IntervalNow();
5336 ** Done with context cache.
5338 PR_Unlock(inCache->mLock);
5344 ** handleRequest
5346 ** Based on what file they are asking for, perform some processing.
5347 ** Output the results to aFD.
5349 ** Returns !0 on error.
5352 handleRequest(tmreader * aTMR, PRFileDesc * aFD,
5353 const char *aFileName, const FormData * aGetData)
5355 int retval = 0;
5357 if (NULL != aTMR && NULL != aFD && NULL != aFileName
5358 && '\0' != *aFileName) {
5359 STRequest request;
5362 ** Init the request.
5364 memset(&request, 0, sizeof(request));
5365 request.mFD = aFD;
5366 request.mGetFileName = aFileName;
5367 request.mGetData = aGetData;
5369 ** Set local options for this request.
5371 initRequestOptions(&request);
5373 ** Get our cached context for this client.
5374 ** Simply based on the options.
5376 request.mContext = contextLookup(&request.mOptions);
5377 if (NULL != request.mContext) {
5379 ** Attempt to find the file of interest.
5381 if (handleLocalFile(&request, aFileName)) {
5382 displayFile(&request, aFileName);
5384 else if (0 == strcmp("index.html", aFileName)) {
5385 int displayRes = 0;
5387 htmlHeader(&request, "SpaceTrace Index");
5388 displayRes = displayIndex(&request);
5389 if (0 != displayRes) {
5390 retval = __LINE__;
5391 REPORT_ERROR(__LINE__, displayIndex);
5394 htmlFooter(&request);
5396 else if (0 == strcmp("settings.html", aFileName) ||
5397 0 == strcmp("options.html", aFileName)) {
5398 htmlHeader(&request, "SpaceTrace Options");
5399 displaySettings(&request);
5400 htmlFooter(&request);
5402 else if (0 == strcmp("top_allocations.html", aFileName)) {
5403 int displayRes = 0;
5405 htmlHeader(&request, "SpaceTrace Top Allocations Report");
5406 displayRes =
5407 displayTopAllocations(&request,
5408 request.mContext->mSortedRun,
5409 "top-allocations",
5410 "SpaceTrace Top Allocations Report",
5412 if (0 != displayRes) {
5413 retval = __LINE__;
5414 REPORT_ERROR(__LINE__, displayTopAllocations);
5417 htmlFooter(&request);
5419 else if (0 == strcmp("top_callsites.html", aFileName)) {
5420 int displayRes = 0;
5421 tmcallsite **array = NULL;
5422 PRUint32 arrayCount = 0;
5425 ** Display header after we figure out if we are going to focus
5426 ** on a category.
5428 htmlHeader(&request, "SpaceTrace Top Callsites Report");
5429 if (NULL != request.mContext->mSortedRun
5430 && 0 < request.mContext->mSortedRun->mAllocationCount) {
5431 arrayCount =
5432 callsiteArrayFromRun(&array, 0,
5433 request.mContext->mSortedRun);
5434 if (0 != arrayCount && NULL != array) {
5435 displayRes =
5436 displayTopCallsites(&request, array, arrayCount,
5438 "top-callsites",
5439 "Top Callsites Report",
5441 if (0 != displayRes) {
5442 retval = __LINE__;
5443 REPORT_ERROR(__LINE__, displayTopCallsites);
5447 ** Done with the array.
5449 free(array);
5450 array = NULL;
5453 else {
5454 retval = __LINE__;
5455 REPORT_ERROR(__LINE__, handleRequest);
5458 htmlFooter(&request);
5460 else if (0 == strcmp("memory_leaks.html", aFileName)) {
5461 int displayRes = 0;
5463 htmlHeader(&request, "SpaceTrace Memory Leaks Report");
5464 displayRes =
5465 displayMemoryLeaks(&request,
5466 request.mContext->mSortedRun);
5467 if (0 != displayRes) {
5468 retval = __LINE__;
5469 REPORT_ERROR(__LINE__, displayMemoryLeaks);
5472 htmlFooter(&request);
5474 else if (0 == strncmp("allocation_", aFileName, 11)) {
5475 int scanRes = 0;
5476 PRUint32 allocationIndex = 0;
5479 ** Oh, what a hack....
5480 ** The index to the allocation structure in the global run
5481 ** is in the filename. Better than the pointer value....
5483 scanRes = PR_sscanf(aFileName + 11, "%u", &allocationIndex);
5484 if (1 == scanRes
5485 && globals.mRun.mAllocationCount > allocationIndex
5486 && NULL != globals.mRun.mAllocations[allocationIndex]) {
5487 STAllocation *allocation =
5488 globals.mRun.mAllocations[allocationIndex];
5489 char buffer[128];
5490 int displayRes = 0;
5492 PR_snprintf(buffer, sizeof(buffer),
5493 "SpaceTrace Allocation %u Details Report",
5494 allocationIndex);
5495 htmlHeader(&request, buffer);
5496 displayRes =
5497 displayAllocationDetails(&request, allocation);
5498 if (0 != displayRes) {
5499 retval = __LINE__;
5500 REPORT_ERROR(__LINE__, displayAllocationDetails);
5503 htmlFooter(&request);
5505 else {
5506 htmlNotFound(&request);
5509 else if (0 == strncmp("callsite_", aFileName, 9)) {
5510 int scanRes = 0;
5511 PRUint32 callsiteSerial = 0;
5512 tmcallsite *resolved = NULL;
5515 ** Oh, what a hack....
5516 ** The serial(key) to the callsite structure in the hash table
5517 ** is in the filename. Better than the pointer value....
5519 scanRes = PR_sscanf(aFileName + 9, "%u", &callsiteSerial);
5520 if (1 == scanRes && 0 != callsiteSerial
5521 && NULL != (resolved =
5522 tmreader_callsite(aTMR, callsiteSerial))) {
5523 char buffer[128];
5524 int displayRes = 0;
5526 PR_snprintf(buffer, sizeof(buffer),
5527 "SpaceTrace Callsite %u Details Report",
5528 callsiteSerial);
5529 htmlHeader(&request, buffer);
5530 displayRes = displayCallsiteDetails(&request, resolved);
5531 if (0 != displayRes) {
5532 retval = __LINE__;
5533 REPORT_ERROR(__LINE__, displayAllocationDetails);
5536 htmlFooter(&request);
5538 else {
5539 htmlNotFound(&request);
5542 else if (0 == strcmp("root_callsites.html", aFileName)) {
5543 int displayRes = 0;
5545 htmlHeader(&request, "SpaceTrace Root Callsites");
5546 displayRes =
5547 displayCallsites(&request, aTMR->calltree_root.kids,
5548 ST_FOLLOW_SIBLINGS, 0,
5549 "callsites-root",
5550 "SpaceTrace Root Callsites",
5551 __LINE__);
5552 if (0 != displayRes) {
5553 retval = __LINE__;
5554 REPORT_ERROR(__LINE__, displayCallsites);
5557 htmlFooter(&request);
5559 #if ST_WANT_GRAPHS
5560 else if (0 == strcmp("footprint_graph.html", aFileName)) {
5561 int displayRes = 0;
5563 htmlHeader(&request, "SpaceTrace Memory Footprint Report");
5564 PR_fprintf(request.mFD, "<div align=center>\n");
5565 PR_fprintf(request.mFD, "<img src=\"./footprint.png");
5566 optionGetDataOut(request.mFD, &request.mOptions);
5567 PR_fprintf(request.mFD, "\">\n");
5568 PR_fprintf(request.mFD, "</div>\n");
5569 htmlFooter(&request);
5571 #endif /* ST_WANT_GRAPHS */
5572 #if ST_WANT_GRAPHS
5573 else if (0 == strcmp("times_graph.html", aFileName)) {
5574 int displayRes = 0;
5576 htmlHeader(&request, "SpaceTrace Allocation Times Report");
5577 PR_fprintf(request.mFD, "<div align=center>\n");
5578 PR_fprintf(request.mFD, "<img src=\"./times.png");
5579 optionGetDataOut(request.mFD, &request.mOptions);
5580 PR_fprintf(request.mFD, "\">\n");
5581 PR_fprintf(request.mFD, "</div>\n");
5582 htmlFooter(&request);
5584 #endif /* ST_WANT_GRAPHS */
5585 #if ST_WANT_GRAPHS
5586 else if (0 == strcmp("lifespan_graph.html", aFileName)) {
5587 int displayRes = 0;
5589 htmlHeader(&request,
5590 "SpaceTrace Allocation Lifespans Report");
5591 PR_fprintf(request.mFD, "<div align=center>\n");
5592 PR_fprintf(request.mFD, "<img src=\"./lifespan.png");
5593 optionGetDataOut(request.mFD, &request.mOptions);
5594 PR_fprintf(request.mFD, "\">\n");
5595 PR_fprintf(request.mFD, "</div>\n");
5596 htmlFooter(&request);
5598 #endif /* ST_WANT_GRAPHS */
5599 #if ST_WANT_GRAPHS
5600 else if (0 == strcmp("weight_graph.html", aFileName)) {
5601 int displayRes = 0;
5603 htmlHeader(&request, "SpaceTrace Allocation Weights Report");
5604 PR_fprintf(request.mFD, "<div align=center>\n");
5605 PR_fprintf(request.mFD, "<img src=\"./weight.png");
5606 optionGetDataOut(request.mFD, &request.mOptions);
5607 PR_fprintf(request.mFD, "\">\n");
5608 PR_fprintf(request.mFD, "</div>\n");
5609 htmlFooter(&request);
5611 #endif /* ST_WANT_GRAPHS */
5612 #if ST_WANT_GRAPHS
5613 else if (0 == strcmp("footprint.png", aFileName)) {
5614 int graphRes = 0;
5616 graphRes =
5617 graphFootprint(&request, request.mContext->mSortedRun);
5618 if (0 != graphRes) {
5619 retval = __LINE__;
5620 REPORT_ERROR(__LINE__, graphFootprint);
5623 #endif /* ST_WANT_GRAPHS */
5624 #if ST_WANT_GRAPHS
5625 else if (0 == strcmp("times.png", aFileName)) {
5626 int graphRes = 0;
5628 graphRes =
5629 graphTimeval(&request, request.mContext->mSortedRun);
5630 if (0 != graphRes) {
5631 retval = __LINE__;
5632 REPORT_ERROR(__LINE__, graphTimeval);
5635 #endif /* ST_WANT_GRAPHS */
5636 #if ST_WANT_GRAPHS
5637 else if (0 == strcmp("lifespan.png", aFileName)) {
5638 int graphRes = 0;
5640 graphRes =
5641 graphLifespan(&request, request.mContext->mSortedRun);
5642 if (0 != graphRes) {
5643 retval = __LINE__;
5644 REPORT_ERROR(__LINE__, graphLifespan);
5647 #endif /* ST_WANT_GRAPHS */
5648 #if ST_WANT_GRAPHS
5649 else if (0 == strcmp("weight.png", aFileName)) {
5650 int graphRes = 0;
5652 graphRes =
5653 graphWeight(&request, request.mContext->mSortedRun);
5654 if (0 != graphRes) {
5655 retval = __LINE__;
5656 REPORT_ERROR(__LINE__, graphWeight);
5659 #endif /* ST_WANT_GRAPHS */
5660 else if (0 == strcmp("categories_summary.html", aFileName)) {
5661 int displayRes = 0;
5663 htmlHeader(&request, "Category Report");
5664 displayRes =
5665 displayCategoryReport(&request, &globals.mCategoryRoot,
5667 if (0 != displayRes) {
5668 retval = __LINE__;
5669 REPORT_ERROR(__LINE__, displayMemoryLeaks);
5672 htmlFooter(&request);
5674 else {
5675 htmlNotFound(&request);
5679 ** Release the context we obtained earlier.
5681 contextRelease(request.mContext);
5682 request.mContext = NULL;
5684 else {
5685 retval = __LINE__;
5686 REPORT_ERROR(__LINE__, contextObtain);
5689 else {
5690 retval = __LINE__;
5691 REPORT_ERROR(__LINE__, handleRequest);
5695 ** Compact a little if you can after each request.
5697 heapCompact();
5698 return retval;
5702 ** handleClient
5704 ** main() of the new client thread.
5705 ** Read the fd for the request.
5706 ** Output the results.
5708 void
5709 handleClient(void *inArg)
5711 PRFileDesc *aFD = NULL;
5713 aFD = (PRFileDesc *) inArg;
5714 if (NULL != aFD) {
5715 PRStatus closeRes = PR_SUCCESS;
5716 char aBuffer[2048];
5717 PRInt32 readRes = 0;
5719 readRes = PR_Read(aFD, aBuffer, sizeof(aBuffer));
5720 if (0 <= readRes) {
5721 const char *sanityCheck = "GET /";
5723 if (0 == strncmp(sanityCheck, aBuffer, 5)) {
5724 char *eourl = NULL;
5725 char *start = &aBuffer[5];
5726 char *getData = NULL;
5727 int realFun = 0;
5728 const char *crlf = "\015\012";
5729 char *eoline = NULL;
5730 FormData *fdGet = NULL;
5733 ** Truncate the line if possible.
5734 ** Only want first one.
5736 eoline = strstr(aBuffer, crlf);
5737 if (NULL != eoline) {
5738 *eoline = '\0';
5742 ** Find the whitespace.
5743 ** That is either end of line or the " HTTP/1.x" suffix.
5744 ** We do not care.
5746 for (eourl = start; 0 == isspace(*eourl) && '\0' != *eourl;
5747 eourl++) {
5749 ** No body.
5754 ** Cap it off.
5755 ** Convert empty '/' to index.html.
5757 *eourl = '\0';
5758 if ('\0' == *start) {
5759 strcpy(start, "index.html");
5763 ** Have we got any GET form data?
5765 getData = strchr(start, '?');
5766 if (NULL != getData) {
5768 ** Whack it off.
5770 *getData = '\0';
5771 getData++;
5775 ** Convert get data into a more useful format.
5777 fdGet = FormData_Create(getData);
5779 ** This is totally a hack, but oh well....
5781 ** Send that the request was OK, regardless.
5783 ** If we have any get data, then it is a set of options
5784 ** we attempt to apply.
5786 ** Other code will tell the user they were wrong or if
5787 ** there was an error.
5788 ** If the filename contains a ".png", then send the image
5789 ** mime type, otherwise, say it is text/html.
5791 PR_fprintf(aFD, "HTTP/1.1 200 OK%s", crlf);
5792 PR_fprintf(aFD, "Server: %s%s",
5793 "$Id: spacetrace.c,v 1.54 2006/11/01 23:02:17 timeless%mozdev.org Exp $",
5794 crlf);
5795 PR_fprintf(aFD, "Content-type: ");
5796 if (NULL != strstr(start, ".png")) {
5797 PR_fprintf(aFD, "image/png");
5799 else if (NULL != strstr(start, ".jpg")) {
5800 PR_fprintf(aFD, "image/jpeg");
5802 else if (NULL != strstr(start, ".txt")) {
5803 PR_fprintf(aFD, "text/plain");
5805 else if (NULL != strstr(start, ".css")) {
5806 PR_fprintf(aFD, "text/css");
5808 else {
5809 PR_fprintf(aFD, "text/html");
5811 PR_fprintf(aFD, crlf);
5813 ** One more to separate headers from content.
5815 PR_fprintf(aFD, crlf);
5817 ** Ready for the real fun.
5819 realFun = handleRequest(globals.mTMR, aFD, start, fdGet);
5820 if (0 != realFun) {
5821 REPORT_ERROR(__LINE__, handleRequest);
5825 ** Free off get data if around.
5827 FormData_Destroy(fdGet);
5828 fdGet = NULL;
5830 else {
5831 REPORT_ERROR(__LINE__, handleClient);
5834 else {
5835 REPORT_ERROR(__LINE__, lineReader);
5839 ** Done with the connection.
5841 closeRes = PR_Close(aFD);
5842 if (PR_SUCCESS != closeRes) {
5843 REPORT_ERROR(__LINE__, PR_Close);
5846 else {
5847 REPORT_ERROR(__LINE__, handleClient);
5852 ** serverMode
5854 ** List on a port as a httpd.
5855 ** Output results interactively on demand.
5857 ** Returns !0 on error.
5860 serverMode(void)
5862 int retval = 0;
5863 PRFileDesc *socket = NULL;
5866 ** Create a socket.
5868 socket = PR_NewTCPSocket();
5869 if (NULL != socket) {
5870 PRStatus closeRes = PR_SUCCESS;
5871 PRNetAddr bindAddr;
5872 PRStatus bindRes = PR_SUCCESS;
5875 ** Bind it to an interface/port.
5876 ** Any interface.
5878 bindAddr.inet.family = PR_AF_INET;
5879 bindAddr.inet.port =
5880 PR_htons((PRUint16) globals.mCommandLineOptions.mHttpdPort);
5881 bindAddr.inet.ip = PR_htonl(PR_INADDR_ANY);
5882 bindRes = PR_Bind(socket, &bindAddr);
5883 if (PR_SUCCESS == bindRes) {
5884 PRStatus listenRes = PR_SUCCESS;
5885 const int backlog = 0x20;
5888 ** Start listening for clients.
5889 ** Give a decent backlog, some of our processing will take
5890 ** a bit.
5892 listenRes = PR_Listen(socket, backlog);
5893 if (PR_SUCCESS == listenRes) {
5894 PRFileDesc *connection = NULL;
5895 int failureSum = 0;
5896 char message[80];
5899 ** Output a little message saying we are receiving.
5901 PR_snprintf(message, sizeof(message),
5902 "server accepting connections at http://localhost:%u/",
5903 globals.mCommandLineOptions.mHttpdPort);
5904 REPORT_INFO(message);
5905 PR_fprintf(PR_STDOUT, "Peak memory used: %s bytes\n",
5906 FormatNumber(globals.mPeakMemoryUsed));
5907 PR_fprintf(PR_STDOUT, "Allocations : %s total\n",
5908 FormatNumber(globals.mMallocCount +
5909 globals.mCallocCount +
5910 globals.mReallocCount),
5911 FormatNumber(globals.mFreeCount));
5912 PR_fprintf(PR_STDOUT, "Breakdown : %s malloc\n",
5913 FormatNumber(globals.mMallocCount));
5914 PR_fprintf(PR_STDOUT, " %s calloc\n",
5915 FormatNumber(globals.mCallocCount));
5916 PR_fprintf(PR_STDOUT, " %s realloc\n",
5917 FormatNumber(globals.mReallocCount));
5918 PR_fprintf(PR_STDOUT, " %s free\n",
5919 FormatNumber(globals.mFreeCount));
5920 PR_fprintf(PR_STDOUT, "Leaks : %s\n",
5921 FormatNumber((globals.mMallocCount +
5922 globals.mCallocCount +
5923 globals.mReallocCount) -
5924 globals.mFreeCount));
5926 ** Keep accepting until we know otherwise.
5928 ** We do a thread per connection.
5929 ** Up to the thread to close the connection when done.
5931 ** This is known by me to be suboptimal, and I would rather
5932 ** do a thread pool if it ever becomes a resource issue.
5933 ** Any issues would simply point to a need to get
5934 ** more machines or a beefier machine to handle the
5935 ** requests, as well as a need to do thread pooling and
5936 ** avoid thread creation overhead.
5937 ** The threads are not tracked, except possibly by NSPR
5938 ** itself and PR_Cleanup will wait on them all to exit as
5939 ** user threads so our shared data is valid.
5941 while (0 == retval) {
5942 connection =
5943 PR_Accept(socket, NULL, PR_INTERVAL_NO_TIMEOUT);
5944 if (NULL != connection) {
5945 PRThread *clientThread = NULL;
5948 ** Thread per connection.
5950 clientThread = PR_CreateThread(PR_USER_THREAD, /* PR_Cleanup sync */
5951 handleClient, (void *) connection, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, /* IO enabled */
5952 PR_UNJOINABLE_THREAD,
5954 if (NULL == clientThread) {
5955 PRStatus closeRes = PR_SUCCESS;
5957 failureSum += __LINE__;
5958 REPORT_ERROR(__LINE__, PR_Accept);
5960 ** Close the connection as well, no service
5962 closeRes = PR_Close(connection);
5963 if (PR_FAILURE == closeRes) {
5964 REPORT_ERROR(__LINE__, PR_Close);
5968 else {
5969 failureSum += __LINE__;
5970 REPORT_ERROR(__LINE__, PR_Accept);
5974 if (0 != failureSum) {
5975 retval = __LINE__;
5979 ** Output a little message saying it is all over.
5981 REPORT_INFO("server no longer accepting connections....");
5983 else {
5984 retval = __LINE__;
5985 REPORT_ERROR(__LINE__, PR_Listen);
5988 else {
5989 retval = __LINE__;
5990 REPORT_ERROR(__LINE__, PR_Bind);
5994 ** Done with socket.
5996 closeRes = PR_Close(socket);
5997 if (PR_SUCCESS != closeRes) {
5998 retval = __LINE__;
5999 REPORT_ERROR(__LINE__, PR_Close);
6001 socket = NULL;
6003 else {
6004 retval = __LINE__;
6005 REPORT_ERROR(__LINE__, PR_NewTCPSocket);
6008 return retval;
6012 ** batchMode
6014 ** Perform whatever batch requests we were asked to do.
6017 batchMode(void)
6019 int retval = 0;
6021 if (0 != globals.mCommandLineOptions.mBatchRequestCount) {
6022 PRUint32 loop = 0;
6023 int failureSum = 0;
6024 int handleRes = 0;
6025 char aFileName[1024];
6026 PRUint32 sprintfRes = 0;
6029 ** Go through and process the various files requested.
6030 ** We do not stop on failure, as it is too costly to rerun the
6031 ** batch job.
6033 for (loop = 0;
6034 loop < globals.mCommandLineOptions.mBatchRequestCount; loop++) {
6035 sprintfRes =
6036 PR_snprintf(aFileName, sizeof(aFileName), "%s%c%s",
6037 globals.mCommandLineOptions.mOutputDir,
6038 PR_GetDirectorySeparator(),
6039 globals.mCommandLineOptions.mBatchRequest[loop]);
6040 if ((PRUint32) - 1 != sprintfRes) {
6041 PRFileDesc *outFile = NULL;
6043 outFile = PR_Open(aFileName, ST_FLAGS, ST_PERMS);
6044 if (NULL != outFile) {
6045 PRStatus closeRes = PR_SUCCESS;
6047 handleRes =
6048 handleRequest(globals.mTMR, outFile,
6049 globals.mCommandLineOptions.
6050 mBatchRequest[loop], NULL);
6051 if (0 != handleRes) {
6052 failureSum += __LINE__;
6053 REPORT_ERROR(__LINE__, handleRequest);
6056 closeRes = PR_Close(outFile);
6057 if (PR_SUCCESS != closeRes) {
6058 failureSum += __LINE__;
6059 REPORT_ERROR(__LINE__, PR_Close);
6062 else {
6063 failureSum += __LINE__;
6064 REPORT_ERROR(__LINE__, PR_Open);
6067 else {
6068 failureSum += __LINE__;
6069 REPORT_ERROR(__LINE__, PR_snprintf);
6073 if (0 != failureSum) {
6074 retval = __LINE__;
6077 else {
6078 retval = __LINE__;
6079 REPORT_ERROR(__LINE__, outputReports);
6082 return retval;
6086 ** doRun
6088 ** Perform the actual processing this program requires.
6089 ** Returns !0 on failure.
6092 doRun(void)
6094 int retval = 0;
6097 ** Create the new trace-malloc reader.
6099 globals.mTMR = tmreader_new(globals.mProgramName, NULL);
6100 if (NULL != globals.mTMR) {
6101 int tmResult = 0;
6102 int outputResult = 0;
6104 #if defined(DEBUG_dp)
6105 PRIntervalTime start = PR_IntervalNow();
6107 fprintf(stderr, "DEBUG: reading tracemalloc data...\n");
6108 #endif
6109 tmResult =
6110 tmreader_eventloop(globals.mTMR,
6111 globals.mCommandLineOptions.mFileName,
6112 tmEventHandler);
6113 printf("\rReading... Done.\n");
6114 #if defined(DEBUG_dp)
6115 fprintf(stderr,
6116 "DEBUG: reading tracemalloc data ends: %dms [%d allocations]\n",
6117 PR_IntervalToMilliseconds(PR_IntervalNow() - start),
6118 globals.mRun.mAllocationCount);
6119 #endif
6120 if (0 == tmResult) {
6121 REPORT_ERROR(__LINE__, tmreader_eventloop);
6122 retval = __LINE__;
6125 if (0 == retval) {
6127 ** Decide if we're going into batch mode or server mode.
6129 if (0 != globals.mCommandLineOptions.mBatchRequestCount) {
6131 ** Output in one big step while everything still exists.
6133 outputResult = batchMode();
6134 if (0 != outputResult) {
6135 REPORT_ERROR(__LINE__, batchMode);
6136 retval = __LINE__;
6139 else {
6140 int serverRes = 0;
6143 ** httpd time.
6145 serverRes = serverMode();
6146 if (0 != serverRes) {
6147 REPORT_ERROR(__LINE__, serverMode);
6148 retval = __LINE__;
6153 ** Clear our categorization tree
6155 freeCategories(&globals);
6158 else {
6159 REPORT_ERROR(__LINE__, tmreader_new);
6160 retval = __LINE__;
6163 return retval;
6167 initCaches(void)
6169 ** Initialize the global caches.
6170 ** More involved since we have to allocated/create some objects.
6172 ** returns Zero if all is well.
6173 ** Non-zero on error.
6176 int retval = 0;
6177 STContextCache *inCache = &globals.mContextCache;
6179 if (NULL != inCache && 0 != globals.mCommandLineOptions.mContexts) {
6180 inCache->mLock = PR_NewLock();
6181 if (NULL != inCache->mLock) {
6182 inCache->mCacheMiss = PR_NewCondVar(inCache->mLock);
6183 if (NULL != inCache->mCacheMiss) {
6184 inCache->mItems =
6185 (STContextCacheItem *) calloc(globals.mCommandLineOptions.
6186 mContexts,
6187 sizeof(STContextCacheItem));
6188 if (NULL != inCache->mItems) {
6189 PRUint32 loop = 0;
6190 char buffer[64];
6192 inCache->mItemCount =
6193 globals.mCommandLineOptions.mContexts;
6195 ** Init each item as needed.
6197 for (loop = 0; loop < inCache->mItemCount; loop++) {
6198 inCache->mItems[loop].mContext.mIndex = loop;
6199 PR_snprintf(buffer, sizeof(buffer),
6200 "Context Item %d RW Lock", loop);
6201 inCache->mItems[loop].mContext.mRWLock =
6202 PR_NewRWLock(PR_RWLOCK_RANK_NONE, buffer);
6203 if (NULL == inCache->mItems[loop].mContext.mRWLock) {
6204 break;
6206 #if ST_WANT_GRAPHS
6207 inCache->mItems[loop].mContext.mImageLock =
6208 PR_NewLock();
6209 if (NULL == inCache->mItems[loop].mContext.mImageLock) {
6210 break;
6212 #endif
6215 if (loop != inCache->mItemCount) {
6216 retval = __LINE__;
6217 REPORT_ERROR(__LINE__, initCaches);
6220 else {
6221 retval = __LINE__;
6222 REPORT_ERROR(__LINE__, calloc);
6225 else {
6226 retval = __LINE__;
6227 REPORT_ERROR(__LINE__, PR_NewCondVar);
6230 else {
6231 retval = __LINE__;
6232 REPORT_ERROR(__LINE__, PR_NewLock);
6235 else {
6236 retval = __LINE__;
6237 REPORT_ERROR(__LINE__, initCaches);
6240 return retval;
6244 destroyCaches(void)
6246 ** Clean up any global caches we have laying around.
6248 ** returns Zero if all is well.
6249 ** Non-zero on error.
6252 int retval = 0;
6253 STContextCache *inCache = &globals.mContextCache;
6255 if (NULL != inCache) {
6256 PRUint32 loop = 0;
6259 ** Uninit item data one by one.
6261 for (loop = 0; loop < inCache->mItemCount; loop++) {
6262 if (NULL != inCache->mItems[loop].mContext.mRWLock) {
6263 PR_DestroyRWLock(inCache->mItems[loop].mContext.mRWLock);
6264 inCache->mItems[loop].mContext.mRWLock = NULL;
6266 #if ST_WANT_GRAPHS
6267 if (NULL != inCache->mItems[loop].mContext.mImageLock) {
6268 PR_DestroyLock(inCache->mItems[loop].mContext.mImageLock);
6269 inCache->mItems[loop].mContext.mImageLock = NULL;
6271 #endif
6274 inCache->mItemCount = 0;
6275 if (NULL != inCache->mItems) {
6276 free(inCache->mItems);
6277 inCache->mItems = NULL;
6280 if (NULL != inCache->mCacheMiss) {
6281 PR_DestroyCondVar(inCache->mCacheMiss);
6282 inCache->mCacheMiss = NULL;
6285 if (NULL != inCache->mLock) {
6286 PR_DestroyLock(inCache->mLock);
6287 inCache->mLock = NULL;
6290 else {
6291 retval = __LINE__;
6292 REPORT_ERROR(__LINE__, destroyCaches);
6295 return retval;
6299 ** main
6301 ** Process entry and exit.
6304 main(int aArgCount, char **aArgArray)
6306 int retval = 0;
6307 int optionsResult = 0;
6308 PRStatus prResult = PR_SUCCESS;
6309 int showedHelp = 0;
6310 int looper = 0;
6311 int cacheResult = 0;
6314 ** NSPR init.
6316 PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
6318 ** Initialize globals
6320 memset(&globals, 0, sizeof(globals));
6322 ** Set the program name.
6324 globals.mProgramName = aArgArray[0];
6326 ** Set the minimum timeval really high so other code
6327 ** that checks the timeval will get it right.
6329 globals.mMinTimeval = ST_TIMEVAL_MAX;
6331 ** Handle initializing options.
6333 optionsResult = initOptions(aArgCount, aArgArray);
6334 if (0 != optionsResult) {
6335 REPORT_ERROR(optionsResult, initOptions);
6336 retval = __LINE__;
6340 ** Initialize our caches.
6342 cacheResult = initCaches();
6343 if (0 != cacheResult) {
6344 retval = __LINE__;
6345 REPORT_ERROR(__LINE__, initCaches);
6349 ** Small alloc code init.
6351 globals.mCategoryRoot.runs =
6352 (STRun **) calloc(globals.mCommandLineOptions.mContexts,
6353 sizeof(STRun *));
6354 if (NULL == globals.mCategoryRoot.runs) {
6355 retval = __LINE__;
6356 REPORT_ERROR(__LINE__, calloc);
6360 ** Show help on usage if need be.
6362 showedHelp = showHelp();
6364 ** Only perform the run if everything is checking out.
6366 if (0 == showedHelp && 0 == retval) {
6367 int runResult = 0;
6369 runResult = doRun();
6370 if (0 != runResult) {
6371 REPORT_ERROR(runResult, doRun);
6372 retval = __LINE__;
6376 if (0 != retval) {
6377 REPORT_ERROR(retval, main);
6381 ** Have NSPR join all client threads we started.
6383 prResult = PR_Cleanup();
6384 if (PR_SUCCESS != prResult) {
6385 REPORT_ERROR(retval, PR_Cleanup);
6386 retval = __LINE__;
6389 ** All threads are joined/done by this line.
6393 ** Options allocated a little.
6395 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \
6396 if(NULL != globals.mCommandLineOptions.m##option_name) \
6398 free((void*)globals.mCommandLineOptions.m##option_name); \
6399 globals.mCommandLineOptions.m##option_name = NULL; \
6400 globals.mCommandLineOptions.m##option_name##Count = 0; \
6402 #include "stoptions.h"
6405 ** globals has a small modification to clear up.
6407 if (NULL != globals.mCategoryRoot.runs) {
6408 free(globals.mCategoryRoot.runs);
6409 globals.mCategoryRoot.runs = NULL;
6413 ** Blow away our caches.
6415 cacheResult = destroyCaches();
6416 if (0 != cacheResult) {
6417 retval = __LINE__;
6418 REPORT_ERROR(__LINE__, initCaches);
6422 ** We are safe to kill our tmreader data.
6424 if (NULL != globals.mTMR) {
6425 tmreader_destroy(globals.mTMR);
6426 globals.mTMR = NULL;
6429 return retval;