Backed out changeset b0a4653325cc (bug 941565) for Windows bustage.
[gecko.git] / tools / trace-malloc / spacetrace.c
blobf101ce9ce9e66cac97c9b01263968eb53bfe367d
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 ** spacetrace.c
9 **
10 ** SpaceTrace is meant to take the output of trace-malloc and present
11 ** a picture of allocations over the run of the application.
15 ** Required include files.
17 #include "spacetrace.h"
19 #include <ctype.h>
20 #include <math.h>
21 #include <string.h>
22 #include <time.h>
23 #if defined(XP_WIN32)
24 #include <malloc.h> /* _heapMin */
25 #endif
27 #if defined(HAVE_BOUTELL_GD)
29 ** See http://www.boutell.com/gd for the GD graphics library.
30 ** Ports for many platorms exist.
31 ** Your box may already have the lib (mine did, redhat 7.1 workstation).
33 #include <gd.h>
34 #include <gdfontt.h>
35 #include <gdfonts.h>
36 #include <gdfontmb.h>
37 #endif /* HAVE_BOUTELL_GD */
39 #include "nsQuickSort.h"
41 ** strcasecmp API please.
43 #if defined(_MSC_VER)
44 #define strcasecmp _stricmp
45 #define strncasecmp _strnicmp
46 #endif
49 ** the globals variables. happy joy.
51 STGlobals globals;
54 ** have the heap cleanup at opportune times, if possible.
56 void
57 heapCompact(void)
59 #if defined(XP_WIN32)
60 _heapmin();
61 #endif
64 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \
65 PR_fprintf(PR_STDOUT, "--%s\nDisabled by default.\n%s\n", #option_name, option_help);
66 #define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \
67 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is \"%s\".\n%s\n", #option_name, default_value, option_help);
68 #define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
69 PR_fprintf(PR_STDOUT, "--%s=<value>\nUp to %u occurrences allowed.\n%s\n", #option_name, array_size, option_help);
70 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \
71 PR_fprintf(PR_STDOUT, "--%s=<value>\nUnlimited occurrences allowed.\n%s\n", #option_name, option_help);
72 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
73 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is %u.\n%s\n", #option_name, default_value, option_help);
74 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
75 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is %llu.\n%s\n", #option_name, default_value, option_help);
78 ** showHelp
80 ** Give simple command line help.
81 ** Returns !0 if the help was showed.
83 int
84 showHelp(void)
86 int retval = 0;
88 if (PR_FALSE != globals.mCommandLineOptions.mHelp) {
89 PR_fprintf(PR_STDOUT, "Usage:\t%s [OPTION]... [-|filename]\n\n",
90 globals.mProgramName);
93 #include "stoptions.h"
96 ** Showed something.
98 retval = __LINE__;
101 return retval;
105 ** ticks2xsec
107 ** Convert platform specific ticks to second units
108 ** Returns 0 on success.
110 uint32_t
111 ticks2xsec(tmreader * aReader, uint32_t aTicks, uint32_t aResolution)
113 return (uint32_t)((aResolution * aTicks)/aReader->ticksPerSec);
116 #define ticks2msec(reader, ticks) ticks2xsec((reader), (ticks), 1000)
117 #define ticks2usec(reader, ticks) ticks2xsec((reader), (ticks), 1000000)
120 ** initOptions
122 ** Determine global settings for the application.
123 ** Returns 0 on success.
126 initOptions(int aArgCount, char **aArgArray)
128 int retval = 0;
129 int traverse = 0;
132 ** Set the initial global default options.
134 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) globals.mCommandLineOptions.m##option_name = PR_FALSE;
135 #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);
136 #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'; } }
137 #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;
138 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) globals.mCommandLineOptions.m##option_name = default_value * multiplier;
139 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) { uint64_t def64 = default_value; uint64_t mul64 = multiplier; globals.mCommandLineOptions.m##option_name##64 = def64 * mul64; }
141 #include "stoptions.h"
144 ** Go through all arguments.
145 ** Two dashes lead off an option.
146 ** Any single dash leads off help, unless it is a lone dash (stdin).
147 ** Anything else will be attempted as a file to be processed.
149 for (traverse = 1; traverse < aArgCount; traverse++) {
150 if ('-' == aArgArray[traverse][0] && '-' == aArgArray[traverse][1]) {
151 const char *option = &aArgArray[traverse][2];
154 ** Initial if(0) needed to make "else if"s valid.
156 if (0) {
159 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \
160 else if(0 == strcasecmp(option, #option_name)) \
162 globals.mCommandLineOptions.m##option_name = PR_TRUE; \
164 #define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \
165 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
167 PR_snprintf(globals.mCommandLineOptions.m##option_name, sizeof(globals.mCommandLineOptions.m##option_name), "%s", option + strlen(#option_name "=")); \
169 #define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
170 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
172 int arrLoop = 0; \
174 for(arrLoop = 0; arrLoop < array_size; arrLoop++) \
176 if('\0' == globals.mCommandLineOptions.m##option_name[arrLoop][0]) \
178 break; \
182 if(arrLoop != array_size) \
184 PR_snprintf(globals.mCommandLineOptions.m##option_name[arrLoop], sizeof(globals.mCommandLineOptions.m##option_name[arrLoop]), "%s", option + strlen(#option_name "=")); \
186 else \
188 REPORT_ERROR_MSG(__LINE__, option); \
189 retval = __LINE__; \
190 globals.mCommandLineOptions.mHelp = PR_TRUE; \
193 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \
194 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
196 const char** expand = NULL; \
198 expand = (const char**)realloc((void*)globals.mCommandLineOptions.m##option_name, sizeof(const char*) * (globals.mCommandLineOptions.m##option_name##Count + 1)); \
199 if(NULL != expand) \
201 globals.mCommandLineOptions.m##option_name = expand; \
202 globals.mCommandLineOptions.m##option_name[globals.mCommandLineOptions.m##option_name##Count] = option + strlen(#option_name "="); \
203 globals.mCommandLineOptions.m##option_name##Count++; \
205 else \
207 retval = __LINE__; \
208 globals.mCommandLineOptions.mHelp = PR_TRUE; \
211 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
212 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
214 int32_t scanRes = 0; \
216 scanRes = PR_sscanf(option + strlen(#option_name "="), "%u", &globals.mCommandLineOptions.m##option_name); \
217 if(1 != scanRes) \
219 REPORT_ERROR_MSG(__LINE__, option); \
220 retval = __LINE__; \
221 globals.mCommandLineOptions.mHelp = PR_TRUE; \
224 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
225 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \
227 int32_t scanRes = 0; \
229 scanRes = PR_sscanf(option + strlen(#option_name "="), "%llu", &globals.mCommandLineOptions.m##option_name##64); \
230 if(1 != scanRes) \
232 REPORT_ERROR_MSG(__LINE__, option); \
233 retval = __LINE__; \
234 globals.mCommandLineOptions.mHelp = PR_TRUE; \
238 #include "stoptions.h"
241 ** If no match on options, this else will get hit.
243 else {
244 REPORT_ERROR_MSG(__LINE__, option);
245 retval = __LINE__;
246 globals.mCommandLineOptions.mHelp = PR_TRUE;
249 else if ('-' == aArgArray[traverse][0]
250 && '\0' != aArgArray[traverse][1]) {
252 ** Show help, bad/legacy option.
254 REPORT_ERROR_MSG(__LINE__, aArgArray[traverse]);
255 retval = __LINE__;
256 globals.mCommandLineOptions.mHelp = PR_TRUE;
258 else {
260 ** Default is same as FileName option, the file to process.
262 PR_snprintf(globals.mCommandLineOptions.mFileName,
263 sizeof(globals.mCommandLineOptions.mFileName), "%s",
264 aArgArray[traverse]);
269 ** initialize the categories
271 initCategories(&globals);
273 return retval;
276 #if ST_WANT_GRAPHS
278 ** createGraph
280 ** Create a GD image with the common properties of a graph.
281 ** Upon return, you normally allocate legend colors,
282 ** draw your graph inside the region
283 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGH-STGD_MARGIN,
284 ** and then call drawGraph to format the surrounding information.
286 ** You should use the normal GD image release function, gdImageDestroy
287 ** when done with it.
289 ** Image attributes:
290 ** STGD_WIDTHxSTGD_HEIGHT
291 ** trasparent (white) background
292 ** incremental display
294 gdImagePtr
295 createGraph(int *aTransparencyColor)
297 gdImagePtr retval = NULL;
299 if (NULL != aTransparencyColor) {
300 *aTransparencyColor = -1;
302 retval = gdImageCreate(STGD_WIDTH, STGD_HEIGHT);
303 if (NULL != retval) {
305 ** Background color (first one).
307 *aTransparencyColor = gdImageColorAllocate(retval, 255, 255, 255);
308 if (-1 != *aTransparencyColor) {
310 ** As transparency.
312 gdImageColorTransparent(retval, *aTransparencyColor);
316 ** And to set interlacing.
318 gdImageInterlace(retval, 1);
320 else {
321 REPORT_ERROR(__LINE__, gdImageCreate);
324 else {
325 REPORT_ERROR(__LINE__, createGraph);
328 return retval;
330 #endif /* ST_WANT_GRAPHS */
332 #if ST_WANT_GRAPHS
334 ** drawGraph
336 ** This function mainly exists to simplify putitng all the pretty lace
337 ** around a home made graph.
339 void
340 drawGraph(gdImagePtr aImage, int aColor,
341 const char *aGraphTitle,
342 const char *aXAxisTitle,
343 const char *aYAxisTitle,
344 uint32_t aXMarkCount,
345 uint32_t * aXMarkPercents,
346 const char **aXMarkTexts,
347 uint32_t aYMarkCount,
348 uint32_t * aYMarkPercents,
349 const char **aYMarkTexts,
350 uint32_t aLegendCount,
351 int *aLegendColors, const char **aLegendTexts)
353 if (NULL != aImage && NULL != aGraphTitle &&
354 NULL != aXAxisTitle && NULL != aYAxisTitle &&
355 (0 == aXMarkCount || (NULL != aXMarkPercents && NULL != aXMarkTexts))
356 && (0 == aYMarkCount
357 || (NULL != aYMarkPercents && NULL != aYMarkTexts))
358 && (0 == aLegendCount
359 || (NULL != aLegendColors && NULL != aLegendTexts))) {
360 int margin = 1;
361 uint32_t traverse = 0;
362 uint32_t target = 0;
363 const int markSize = 2;
364 int x1 = 0;
365 int y1 = 0;
366 int x2 = 0;
367 int y2 = 0;
368 time_t theTimeT = time(NULL);
369 char *theTime = ctime(&theTimeT);
370 const char *logo = "SpaceTrace";
371 gdFontPtr titleFont = gdFontMediumBold;
372 gdFontPtr markFont = gdFontTiny;
373 gdFontPtr dateFont = gdFontTiny;
374 gdFontPtr axisFont = gdFontSmall;
375 gdFontPtr legendFont = gdFontTiny;
376 gdFontPtr logoFont = gdFontTiny;
379 ** Fixup the color.
380 ** Black by default.
382 if (-1 == aColor) {
383 aColor = gdImageColorAllocate(aImage, 0, 0, 0);
385 if (-1 == aColor) {
386 aColor = gdImageColorClosest(aImage, 0, 0, 0);
390 ** Output the box.
392 x1 = STGD_MARGIN - margin;
393 y1 = STGD_MARGIN - margin;
394 x2 = STGD_WIDTH - x1;
395 y2 = STGD_HEIGHT - y1;
396 gdImageRectangle(aImage, x1, y1, x2, y2, aColor);
397 margin++;
400 ** Need to make small markings on the graph to indicate where the
401 ** labels line up exactly.
402 ** While we're at it, draw the label text.
404 for (traverse = 0; traverse < aXMarkCount; traverse++) {
405 target =
406 ((STGD_WIDTH -
407 (STGD_MARGIN * 2)) * aXMarkPercents[traverse]) / 100;
409 x1 = STGD_MARGIN + target;
410 y1 = STGD_MARGIN - margin;
411 x2 = x1;
412 y2 = y1 - markSize;
413 gdImageLine(aImage, x1, y1, x2, y2, aColor);
415 y1 = STGD_HEIGHT - y1;
416 y2 = STGD_HEIGHT - y2;
417 gdImageLine(aImage, x1, y1, x2, y2, aColor);
419 if (NULL != aXMarkTexts[traverse]) {
420 x1 = STGD_MARGIN + target - (markFont->h / 2);
421 y1 = STGD_HEIGHT - STGD_MARGIN + margin + markSize +
422 (strlen(aXMarkTexts[traverse]) * markFont->w);
423 gdImageStringUp(aImage, markFont, x1, y1,
424 (unsigned char *) aXMarkTexts[traverse],
425 aColor);
428 for (traverse = 0; traverse < aYMarkCount; traverse++) {
429 target =
430 ((STGD_HEIGHT - (STGD_MARGIN * 2)) * (100 -
431 aYMarkPercents
432 [traverse])) / 100;
434 x1 = STGD_MARGIN - margin;
435 y1 = STGD_MARGIN + target;
436 x2 = x1 - markSize;
437 y2 = y1;
438 gdImageLine(aImage, x1, y1, x2, y2, aColor);
440 x1 = STGD_WIDTH - x1;
441 x2 = STGD_WIDTH - x2;
442 gdImageLine(aImage, x1, y1, x2, y2, aColor);
444 if (NULL != aYMarkTexts[traverse]) {
445 x1 = STGD_MARGIN - margin - markSize -
446 (strlen(aYMarkTexts[traverse]) * markFont->w);
447 y1 = STGD_MARGIN + target - (markFont->h / 2);
448 gdImageString(aImage, markFont, x1, y1,
449 (unsigned char *) aYMarkTexts[traverse],
450 aColor);
453 margin += markSize;
456 ** Title will be centered above the image.
458 x1 = (STGD_WIDTH / 2) - ((strlen(aGraphTitle) * titleFont->w) / 2);
459 y1 = ((STGD_MARGIN - margin) / 2) - (titleFont->h / 2);
460 gdImageString(aImage, titleFont, x1, y1,
461 (unsigned char *) aGraphTitle, aColor);
464 ** Upper left will be the date.
466 x1 = 0;
467 y1 = 0;
468 traverse = strlen(theTime) - 1;
469 if (isspace(theTime[traverse])) {
470 theTime[traverse] = '\0';
472 gdImageString(aImage, dateFont, x1, y1, (unsigned char *) theTime,
473 aColor);
476 ** Lower right will be the logo.
478 x1 = STGD_WIDTH - (strlen(logo) * logoFont->w);
479 y1 = STGD_HEIGHT - logoFont->h;
480 gdImageString(aImage, logoFont, x1, y1, (unsigned char *) logo,
481 aColor);
484 ** X and Y axis titles
486 x1 = (STGD_WIDTH / 2) - ((strlen(aXAxisTitle) * axisFont->w) / 2);
487 y1 = STGD_HEIGHT - axisFont->h;
488 gdImageString(aImage, axisFont, x1, y1, (unsigned char *) aXAxisTitle,
489 aColor);
490 x1 = 0;
491 y1 = (STGD_HEIGHT / 2) + ((strlen(aYAxisTitle) * axisFont->w) / 2);
492 gdImageStringUp(aImage, axisFont, x1, y1,
493 (unsigned char *) aYAxisTitle, aColor);
496 ** The legend.
497 ** Centered on the right hand side, going up.
499 x1 = STGD_WIDTH - STGD_MARGIN + margin +
500 (aLegendCount * legendFont->h) / 2;
501 x2 = STGD_WIDTH - (aLegendCount * legendFont->h);
502 if (x1 > x2) {
503 x1 = x2;
506 y1 = 0;
507 for (traverse = 0; traverse < aLegendCount; traverse++) {
508 y2 = (STGD_HEIGHT / 2) +
509 ((strlen(aLegendTexts[traverse]) * legendFont->w) / 2);
510 if (y2 > y1) {
511 y1 = y2;
514 for (traverse = 0; traverse < aLegendCount; traverse++) {
515 gdImageStringUp(aImage, legendFont, x1, y1,
516 (unsigned char *) aLegendTexts[traverse],
517 aLegendColors[traverse]);
518 x1 += legendFont->h;
523 #endif /* ST_WANT_GRAPHS */
525 #if defined(HAVE_BOUTELL_GD)
527 ** pngSink
529 ** GD callback, used to write out the png.
532 pngSink(void *aContext, const char *aBuffer, int aLen)
534 return PR_Write((PRFileDesc *) aContext, aBuffer, aLen);
536 #endif /* HAVE_BOUTELL_GD */
539 ** FormatNumber
541 ** Formats a number with thousands separator. Don't free the result. Returns
542 ** static data.
544 char *
545 FormatNumber(int32_t num)
547 static char buf[64];
548 char tmpbuf[64];
549 int len = 0;
550 int bufindex = sizeof(buf) - 1;
551 int mod3;
553 PR_snprintf(tmpbuf, sizeof(tmpbuf), "%d", num);
555 /* now insert the thousands separator */
556 mod3 = 0;
557 len = strlen(tmpbuf);
558 while (len >= 0) {
559 if (tmpbuf[len] >= '0' && tmpbuf[len] <= '9') {
560 if (mod3 == 3) {
561 buf[bufindex--] = ',';
562 mod3 = 0;
564 mod3++;
566 buf[bufindex--] = tmpbuf[len--];
568 return buf + bufindex + 1;
572 ** actualByteSize
574 ** Apply alignment and overhead to size to figure out actual byte size
576 uint32_t
577 actualByteSize(STOptions * inOptions, uint32_t retval)
580 ** Need to bump the result by our alignment and overhead.
581 ** The idea here is that an allocation actually costs you more than you
582 ** thought.
584 ** The msvcrt malloc has an alignment of 16 with an overhead of 8.
585 ** The win32 HeapAlloc has an alignment of 8 with an overhead of 8.
587 if (0 != retval) {
588 uint32_t eval = 0;
589 uint32_t over = 0;
591 eval = retval - 1;
592 if (0 != inOptions->mAlignBy) {
593 over = eval % inOptions->mAlignBy;
595 retval = eval + inOptions->mOverhead + inOptions->mAlignBy - over;
598 return retval;
602 ** byteSize
604 ** Figuring the byte size of an allocation.
605 ** Might expand in the future to report size at a given time.
606 ** For now, just use last relevant event.
608 uint32_t
609 byteSize(STOptions * inOptions, STAllocation * aAlloc)
611 uint32_t retval = 0;
613 if (NULL != aAlloc && 0 != aAlloc->mEventCount) {
614 uint32_t index = aAlloc->mEventCount;
617 ** Generally, the size is the last event's size.
619 do {
620 index--;
621 retval = aAlloc->mEvents[index].mHeapSize;
623 while (0 == retval && 0 != index);
625 return actualByteSize(inOptions, retval);
630 ** recalculateAllocationCost
632 ** Given an allocation, does a recalculation of Cost - weight, heapcount etc.
633 ** and does the right thing to propagate the cost upwards.
636 recalculateAllocationCost(STOptions * inOptions, STContext * inContext,
637 STRun * aRun, STAllocation * aAllocation,
638 PRBool updateParent)
641 ** Now, see if they desire a callsite update.
642 ** As mentioned previously, we decide if the run desires us to
643 ** manipulate the callsite data only if its stamp is set.
644 ** We change all callsites and parent callsites to have that
645 ** stamp as well, so as to mark them as being relevant to
646 ** the current run in question.
648 if (NULL != inContext && 0 != aRun->mStats[inContext->mIndex].mStamp) {
649 uint32_t timeval =
650 aAllocation->mMaxTimeval - aAllocation->mMinTimeval;
651 uint32_t size = byteSize(inOptions, aAllocation);
652 uint32_t heapCost = aAllocation->mHeapRuntimeCost;
653 uint64_t timeval64 = timeval;
654 uint64_t size64 = size;
655 uint64_t weight64 = timeval64 * size64;
658 ** First, update this run.
660 aRun->mStats[inContext->mIndex].mCompositeCount++;
661 aRun->mStats[inContext->mIndex].mHeapRuntimeCost += heapCost;
662 aRun->mStats[inContext->mIndex].mSize += size;
663 aRun->mStats[inContext->mIndex].mTimeval64 += timeval64;
664 aRun->mStats[inContext->mIndex].mWeight64 += weight64;
667 ** Use the first event of the allocation to update the parent
668 ** callsites.
669 ** This has positive effect of not updating realloc callsites
670 ** with the same data over and over again.
672 if (updateParent && 0 < aAllocation->mEventCount) {
673 tmcallsite *callsite = aAllocation->mEvents[0].mCallsite;
674 STRun *callsiteRun = NULL;
677 ** Go up parents till we drop.
679 while (NULL != callsite && NULL != callsite->method) {
680 callsiteRun = CALLSITE_RUN(callsite);
681 if (NULL != callsiteRun) {
683 ** Do we init it?
685 if (callsiteRun->mStats[inContext->mIndex].mStamp !=
686 aRun->mStats[inContext->mIndex].mStamp) {
687 memset(&callsiteRun->mStats[inContext->mIndex], 0,
688 sizeof(STCallsiteStats));
689 callsiteRun->mStats[inContext->mIndex].mStamp =
690 aRun->mStats[inContext->mIndex].mStamp;
694 ** Add the values.
695 ** Note that if the allocation was ever realloced,
696 ** we are actually recording the final size.
697 ** Also, the composite count does not include
698 ** calls to realloc (or free for that matter),
699 ** but rather is simply a count of actual heap
700 ** allocation objects, from which someone will
701 ** draw conclusions regarding number of malloc
702 ** and free calls.
703 ** It is possible to generate the exact number
704 ** of calls to free/malloc/realloc should the
705 ** absolute need arise to count them individually,
706 ** but I fear it will take mucho memory and this
707 ** is perhaps good enough for now.
709 callsiteRun->mStats[inContext->mIndex].mCompositeCount++;
710 callsiteRun->mStats[inContext->mIndex].mHeapRuntimeCost +=
711 heapCost;
712 callsiteRun->mStats[inContext->mIndex].mSize += size;
713 callsiteRun->mStats[inContext->mIndex].mTimeval64 +=
714 timeval64;
715 callsiteRun->mStats[inContext->mIndex].mWeight64 +=
716 weight64;
719 callsite = callsite->parent;
724 return 0;
729 ** appendAllocation
731 ** Given a run, append the allocation to it.
732 ** No DUP checks are done.
733 ** Also, we might want to update the parent callsites with stats.
734 ** We decide to do this heavy duty work only if the run we are appending
735 ** to has a non ZERO mStats[].mStamp, meaning that it is asking to track
736 ** such information when it was created.
737 ** Returns !0 on success.
740 appendAllocation(STOptions * inOptions, STContext * inContext,
741 STRun * aRun, STAllocation * aAllocation)
743 int retval = 0;
745 if (NULL != aRun && NULL != aAllocation && NULL != inOptions) {
746 STAllocation **expand = NULL;
749 ** Expand the size of the array if needed.
751 expand = (STAllocation **) realloc(aRun->mAllocations,
752 sizeof(STAllocation *) *
753 (aRun->mAllocationCount + 1));
754 if (NULL != expand) {
756 ** Reassign in case of pointer move.
758 aRun->mAllocations = expand;
761 ** Stick the allocation in.
763 aRun->mAllocations[aRun->mAllocationCount] = aAllocation;
766 ** If this is the global run, we need to let the allocation
767 ** track the index back to us.
769 if (&globals.mRun == aRun) {
770 aAllocation->mRunIndex = aRun->mAllocationCount;
774 ** Increase the count.
776 aRun->mAllocationCount++;
779 ** We're good.
781 retval = __LINE__;
784 ** update allocation cost
786 recalculateAllocationCost(inOptions, inContext, aRun, aAllocation,
787 PR_TRUE);
789 else {
790 REPORT_ERROR(__LINE__, appendAllocation);
793 else {
794 REPORT_ERROR(__LINE__, appendAllocation);
797 return retval;
801 ** hasCallsiteMatch
803 ** Determine if the callsite or the other callsites has the matching text.
805 ** Returns 0 if there is no match.
808 hasCallsiteMatch(tmcallsite * aCallsite, const char *aMatch, int aDirection)
810 int retval = 0;
812 if (NULL != aCallsite && NULL != aCallsite->method &&
813 NULL != aMatch && '\0' != *aMatch) {
814 const char *methodName = NULL;
816 do {
817 methodName = tmmethodnode_name(aCallsite->method);
818 if (NULL != methodName && NULL != strstr(methodName, aMatch)) {
820 ** Contains the text.
822 retval = __LINE__;
823 break;
825 else {
826 switch (aDirection) {
827 case ST_FOLLOW_SIBLINGS:
828 aCallsite = aCallsite->siblings;
829 break;
830 case ST_FOLLOW_PARENTS:
831 aCallsite = aCallsite->parent;
832 break;
833 default:
834 aCallsite = NULL;
835 REPORT_ERROR(__LINE__, hasCallsiteMatch);
836 break;
840 while (NULL != aCallsite && NULL != aCallsite->method);
842 else {
843 REPORT_ERROR(__LINE__, hasCallsiteMatch);
846 return retval;
850 ** harvestRun
852 ** Provide a simply way to go over a run, and yield the relevant allocations.
853 ** The restrictions are easily set via the options page or the command
854 ** line switches.
856 ** On any match, add the allocation to the provided run.
858 ** This makes it much easier for all the code to respect the options in
859 ** force.
861 ** Returns !0 on error, though aOutRun may contain a partial data set.
864 harvestRun(const STRun * aInRun, STRun * aOutRun,
865 STOptions * aOptions, STContext * inContext)
867 int retval = 0;
869 #if defined(DEBUG_dp)
870 PRIntervalTime start = PR_IntervalNow();
872 fprintf(stderr, "DEBUG: harvesting run...\n");
873 #endif
875 if (NULL != aInRun && NULL != aOutRun && aInRun != aOutRun
876 && NULL != aOptions && NULL != inContext) {
877 uint32_t traverse = 0;
878 STAllocation *current = NULL;
880 for (traverse = 0;
881 0 == retval && traverse < aInRun->mAllocationCount; traverse++) {
882 current = aInRun->mAllocations[traverse];
883 if (NULL != current) {
884 uint32_t lifetime = 0;
885 uint32_t bytesize = 0;
886 uint64_t weight64 = 0;
887 uint64_t bytesize64 = 0;
888 uint64_t lifetime64 = 0;
889 int appendRes = 0;
890 int looper = 0;
891 PRBool matched = PR_FALSE;
894 ** Use this as an opportune time to fixup a memory
895 ** leaked timeval, so as to not completely skew
896 ** the weights.
898 if (ST_TIMEVAL_MAX == current->mMaxTimeval) {
899 current->mMaxTimeval = globals.mMaxTimeval;
903 ** Check allocation timeval restrictions.
904 ** We have to slide the recorded timevals to be zero
905 ** based, so that the comparisons make sense.
907 if ((aOptions->mAllocationTimevalMin >
908 (current->mMinTimeval - globals.mMinTimeval)) ||
909 (aOptions->mAllocationTimevalMax <
910 (current->mMinTimeval - globals.mMinTimeval))) {
911 continue;
915 ** Check timeval restrictions.
916 ** We have to slide the recorded timevals to be zero
917 ** based, so that the comparisons make sense.
919 if ((aOptions->mTimevalMin >
920 (current->mMinTimeval - globals.mMinTimeval)) ||
921 (aOptions->mTimevalMax <
922 (current->mMinTimeval - globals.mMinTimeval))) {
923 continue;
927 ** Check lifetime restrictions.
929 lifetime = current->mMaxTimeval - current->mMinTimeval;
930 if ((lifetime < aOptions->mLifetimeMin) ||
931 (lifetime > aOptions->mLifetimeMax)) {
932 continue;
936 ** Check byte size restrictions.
938 bytesize = byteSize(aOptions, current);
939 if ((bytesize < aOptions->mSizeMin) ||
940 (bytesize > aOptions->mSizeMax)) {
941 continue;
945 ** Check weight restrictions.
947 weight64 = (uint64_t)(bytesize * lifetime);
948 if (weight64 < aOptions->mWeightMin64 ||
949 weight64 > aOptions->mWeightMax64) {
950 continue;
954 ** Possibly restrict the callsite by text.
955 ** Do this last, as it is a heavier check.
957 ** One day, we may need to expand the logic to check for
958 ** events beyond the initial allocation event.
960 for (looper = 0; ST_SUBSTRING_MATCH_MAX > looper; looper++) {
961 if ('\0' != aOptions->mRestrictText[looper][0]) {
962 if (0 ==
963 hasCallsiteMatch(current->mEvents[0].mCallsite,
964 aOptions->mRestrictText[looper],
965 ST_FOLLOW_PARENTS)) {
966 break;
969 else {
970 matched = PR_TRUE;
971 break;
974 if (ST_SUBSTRING_MATCH_MAX == looper) {
975 matched = PR_TRUE;
977 if (PR_FALSE == matched) {
978 continue;
982 ** You get here, we add to the run.
984 appendRes =
985 appendAllocation(aOptions, inContext, aOutRun, current);
986 if (0 == appendRes) {
987 retval = __LINE__;
988 REPORT_ERROR(__LINE__, appendAllocation);
994 #if defined(DEBUG_dp)
995 fprintf(stderr, "DEBUG: harvesting ends: %dms [%d allocations]\n",
996 PR_IntervalToMilliseconds(PR_IntervalNow() - start),
997 aInRun->mAllocationCount);
998 #endif
999 return retval;
1003 ** recalculateRunCost
1005 ** Goes over all allocations of a run and recalculates and propagates
1006 ** the allocation costs - weight, heapcount, size
1009 recalculateRunCost(STOptions * inOptions, STContext * inContext, STRun * aRun)
1011 uint32_t traverse = 0;
1012 STAllocation *current = NULL;
1014 #if defined(DEBUG_dp)
1015 PRIntervalTime start = PR_IntervalNow();
1017 fprintf(stderr, "DEBUG: recalculateRunCost...\n");
1018 #endif
1020 if (NULL == aRun)
1021 return -1;
1023 /* reset stats of this run to 0 to begin recalculation */
1024 memset(&aRun->mStats[inContext->mIndex], 0, sizeof(STCallsiteStats));
1026 /* reset timestamp to force propogation of cost */
1027 aRun->mStats[inContext->mIndex].mStamp = PR_IntervalNow();
1029 for (traverse = 0; traverse < aRun->mAllocationCount; traverse++) {
1030 current = aRun->mAllocations[traverse];
1031 if (NULL != current) {
1032 recalculateAllocationCost(inOptions, inContext, aRun, current,
1033 PR_TRUE);
1037 #if defined(DEBUG_dp)
1038 fprintf(stderr, "DEBUG: recalculateRunCost ends: %dms [%d allocations]\n",
1039 PR_IntervalToMilliseconds(PR_IntervalNow() - start),
1040 aRun->mAllocationCount);
1041 #endif
1043 return 0;
1048 ** compareAllocations
1050 ** qsort callback.
1051 ** Compare the allocations as specified by the options.
1054 compareAllocations(const void *aAlloc1, const void *aAlloc2, void *aContext)
1056 int retval = 0;
1057 STOptions *inOptions = (STOptions *) aContext;
1059 if (NULL != aAlloc1 && NULL != aAlloc2 && NULL != inOptions) {
1060 STAllocation *alloc1 = *((STAllocation **) aAlloc1);
1061 STAllocation *alloc2 = *((STAllocation **) aAlloc2);
1063 if (NULL != alloc1 && NULL != alloc2) {
1065 ** Logic determined by pref/option.
1067 switch (inOptions->mOrderBy) {
1068 case ST_COUNT:
1070 ** "By count" on a single allocation means nothing,
1071 ** fall through to weight.
1073 case ST_WEIGHT:
1075 uint64_t weight164 = 0;
1076 uint64_t weight264 = 0;
1077 uint64_t bytesize164 = 0;
1078 uint64_t bytesize264 = 0;
1079 uint64_t timeval164 = 0;
1080 uint64_t timeval264 = 0;
1082 bytesize164 = byteSize(inOptions, alloc1);
1083 timeval164 = alloc1->mMaxTimeval - alloc1->mMinTimeval;
1084 weight164 = bytesize164 * timeval164;
1085 bytesize264 = byteSize(inOptions, alloc2);
1086 timeval264 = alloc2->mMaxTimeval - alloc2->mMinTimeval;
1087 weight264 = bytesize264 * timeval264;
1089 if (weight164 < weight264) {
1090 retval = __LINE__;
1092 else if (weight164 > weight264) {
1093 retval = -__LINE__;
1096 break;
1098 case ST_SIZE:
1100 uint32_t size1 = byteSize(inOptions, alloc1);
1101 uint32_t size2 = byteSize(inOptions, alloc2);
1103 if (size1 < size2) {
1104 retval = __LINE__;
1106 else if (size1 > size2) {
1107 retval = -__LINE__;
1110 break;
1112 case ST_TIMEVAL:
1114 uint32_t timeval1 =
1115 (alloc1->mMaxTimeval - alloc1->mMinTimeval);
1116 uint32_t timeval2 =
1117 (alloc2->mMaxTimeval - alloc2->mMinTimeval);
1119 if (timeval1 < timeval2) {
1120 retval = __LINE__;
1122 else if (timeval1 > timeval2) {
1123 retval = -__LINE__;
1126 break;
1128 case ST_HEAPCOST:
1130 uint32_t cost1 = alloc1->mHeapRuntimeCost;
1131 uint32_t cost2 = alloc2->mHeapRuntimeCost;
1133 if (cost1 < cost2) {
1134 retval = __LINE__;
1136 else if (cost1 > cost2) {
1137 retval = -__LINE__;
1140 break;
1142 default:
1144 REPORT_ERROR(__LINE__, compareAllocations);
1146 break;
1151 return retval;
1155 ** sortRun
1157 ** Given a run, sort it in the manner specified by the options.
1158 ** Returns !0 on failure.
1161 sortRun(STOptions * inOptions, STRun * aRun)
1163 int retval = 0;
1165 if (NULL != aRun && NULL != inOptions) {
1166 if (NULL != aRun->mAllocations && 0 < aRun->mAllocationCount) {
1167 NS_QuickSort(aRun->mAllocations, aRun->mAllocationCount,
1168 sizeof(STAllocation *), compareAllocations,
1169 inOptions);
1172 else {
1173 retval = __LINE__;
1174 REPORT_ERROR(__LINE__, sortRun);
1177 return retval;
1181 ** createRun
1183 ** Returns a newly allocated run, properly initialized.
1184 ** Must call freeRun() with the new STRun.
1186 ** ONLY PASS IN A NON_ZERO STAMP IF YOU KNOW WHAT YOU ARE DOING!!!
1187 ** A non zero stamp in a run has side effects all over the
1188 ** callsites of the allocations added to the run and their
1189 ** parents.
1191 ** Returns NULL on failure.
1193 STRun *
1194 createRun(STContext * inContext, uint32_t aStamp)
1196 STRun *retval = NULL;
1198 retval = (STRun *) calloc(1, sizeof(STRun));
1199 if (NULL != retval) {
1200 retval->mStats =
1201 (STCallsiteStats *) calloc(globals.mCommandLineOptions.mContexts,
1202 sizeof(STCallsiteStats));
1203 if (NULL != retval->mStats) {
1204 if (NULL != inContext) {
1205 retval->mStats[inContext->mIndex].mStamp = aStamp;
1208 else {
1209 free(retval);
1210 retval = NULL;
1214 return retval;
1218 ** freeRun
1220 ** Free off the run and the associated data.
1222 void
1223 freeRun(STRun * aRun)
1225 if (NULL != aRun) {
1226 if (NULL != aRun->mAllocations) {
1228 ** We do not free the allocations themselves.
1229 ** They are likely pointed to by at least 2 other existing
1230 ** runs.
1232 free(aRun->mAllocations);
1233 aRun->mAllocations = NULL;
1236 if (NULL != aRun->mStats) {
1237 free(aRun->mStats);
1238 aRun->mStats = NULL;
1241 free(aRun);
1242 aRun = NULL;
1247 ** createRunFromGlobal
1249 ** Harvest the global run, then sort it.
1250 ** Returns NULL on failure.
1251 ** Must call freeRun() with the new STRun.
1253 STRun *
1254 createRunFromGlobal(STOptions * inOptions, STContext * inContext)
1256 STRun *retval = NULL;
1258 if (NULL != inOptions && NULL != inContext) {
1260 ** We stamp the run.
1261 ** As things are appended to it, it realizes that it should stamp the
1262 ** callsite backtrace with the information as well.
1263 ** In this manner, we can provide meaningful callsite data.
1265 retval = createRun(inContext, PR_IntervalNow());
1267 if (NULL != retval) {
1268 STCategoryNode *node = NULL;
1269 int failure = 0;
1270 int harvestRes =
1271 harvestRun(&globals.mRun, retval, inOptions, inContext);
1272 if (0 == harvestRes) {
1273 int sortRes = sortRun(inOptions, retval);
1275 if (0 != sortRes) {
1276 failure = __LINE__;
1279 else {
1280 failure = __LINE__;
1284 if (0 != failure) {
1285 freeRun(retval);
1286 retval = NULL;
1288 REPORT_ERROR(failure, createRunFromGlobal);
1292 ** Categorize the run.
1294 failure = categorizeRun(inOptions, inContext, retval, &globals);
1295 if (0 != failure) {
1296 REPORT_ERROR(__LINE__, categorizeRun);
1300 ** if we are focussing on a category, return that run instead of
1301 ** the harvested run. Make sure to recalculate cost.
1303 node = findCategoryNode(inOptions->mCategoryName, &globals);
1304 if (node) {
1305 /* Recalculate cost of run */
1306 recalculateRunCost(inOptions, inContext,
1307 node->runs[inContext->mIndex]);
1309 retval = node->runs[inContext->mIndex];
1313 else {
1314 REPORT_ERROR(__LINE__, createRunFromGlobal);
1317 return retval;
1321 ** getLiveAllocationByHeapID
1323 ** Go through a run and find the right heap ID.
1324 ** At the time of the call to this function, the allocation must be LIVE,
1325 ** meaning that it can not be freed.
1326 ** Go through the run backwards, in hopes of finding it near the end.
1328 ** Returns the allocation on success, otherwise NULL.
1330 STAllocation *
1331 getLiveAllocationByHeapID(STRun * aRun, uint32_t aHeapID)
1333 STAllocation *retval = NULL;
1335 if (NULL != aRun && 0 != aHeapID) {
1336 uint32_t traverse = aRun->mAllocationCount;
1337 STAllocation *eval = NULL;
1340 ** Go through in reverse order.
1341 ** Stop when we have a return value.
1343 while (0 < traverse && NULL == retval) {
1345 ** Back up one to align with zero based index.
1347 traverse--;
1350 ** Take the pointer math out of further operations.
1352 eval = aRun->mAllocations[traverse];
1355 ** Take a look at the events in reverse order.
1356 ** Basically the last event must NOT be a free.
1357 ** The last event must NOT be a realloc of size zero (free).
1358 ** Otherwise, try to match up the heapID of the event.
1360 if (0 != eval->mEventCount) {
1361 STAllocEvent *event = eval->mEvents + (eval->mEventCount - 1);
1363 switch (event->mEventType) {
1364 case TM_EVENT_FREE:
1367 ** No freed allocation can match.
1370 break;
1372 case TM_EVENT_REALLOC:
1373 case TM_EVENT_CALLOC:
1374 case TM_EVENT_MALLOC:
1377 ** Heap IDs must match.
1379 if (aHeapID == event->mHeapID) {
1380 retval = eval;
1383 break;
1385 default:
1387 REPORT_ERROR(__LINE__, getAllocationByHeapID);
1389 break;
1394 else {
1395 REPORT_ERROR(__LINE__, getAllocationByHeapID);
1398 return retval;
1402 ** appendEvent
1404 ** Given an allocation, append a new event to its lifetime.
1405 ** Returns the new event on success, otherwise NULL.
1407 STAllocEvent *
1408 appendEvent(STAllocation * aAllocation, uint32_t aTimeval, char aEventType,
1409 uint32_t aHeapID, uint32_t aHeapSize, tmcallsite * aCallsite)
1411 STAllocEvent *retval = NULL;
1413 if (NULL != aAllocation && NULL != aCallsite) {
1414 STAllocEvent *expand = NULL;
1417 ** Expand the allocation's event array.
1419 expand =
1420 (STAllocEvent *) realloc(aAllocation->mEvents,
1421 sizeof(STAllocEvent) *
1422 (aAllocation->mEventCount + 1));
1423 if (NULL != expand) {
1425 ** Reassign in case of pointer move.
1427 aAllocation->mEvents = expand;
1430 ** Remove the pointer math from rest of code.
1432 retval = aAllocation->mEvents + aAllocation->mEventCount;
1435 ** Increase event array count.
1437 aAllocation->mEventCount++;
1440 ** Fill in the event.
1442 retval->mTimeval = aTimeval;
1443 retval->mEventType = aEventType;
1444 retval->mHeapID = aHeapID;
1445 retval->mHeapSize = aHeapSize;
1446 retval->mCallsite = aCallsite;
1449 ** Allocation may need to update idea of lifetime.
1450 ** See allocationTracker to see mMinTimeval inited to ST_TIMEVAL_MAX.
1452 if (aAllocation->mMinTimeval > aTimeval) {
1453 aAllocation->mMinTimeval = aTimeval;
1457 ** This a free event?
1458 ** Can only set max timeval on a free.
1459 ** Otherwise, mMaxTimeval remains ST_TIMEVAL_MAX.
1460 ** Set in allocationTracker.
1462 if (TM_EVENT_FREE == aEventType) {
1463 aAllocation->mMaxTimeval = aTimeval;
1466 else {
1467 REPORT_ERROR(__LINE__, appendEvent);
1470 else {
1471 REPORT_ERROR(__LINE__, appendEvent);
1474 return retval;
1478 ** hasAllocation
1480 ** Determine if a given run has an allocation.
1481 ** This is really nothing more than a pointer comparison loop.
1482 ** Returns !0 if the run has the allocation.
1485 hasAllocation(STRun * aRun, STAllocation * aTestFor)
1487 int retval = 0;
1489 if (NULL != aRun && NULL != aTestFor) {
1490 uint32_t traverse = aRun->mAllocationCount;
1493 ** Go through reverse, in the hopes it exists nearer the end.
1495 while (0 < traverse) {
1497 ** Back up.
1499 traverse--;
1501 if (aTestFor == aRun->mAllocations[traverse]) {
1502 retval = __LINE__;
1503 break;
1507 else {
1508 REPORT_ERROR(__LINE__, hasAllocation);
1511 return retval;
1515 ** allocationTracker
1517 ** Important to keep track of all allocations unique so as to determine
1518 ** their lifetimes.
1520 ** Returns a pointer to the allocation on success.
1521 ** Return NULL on failure.
1523 STAllocation *
1524 allocationTracker(uint32_t aTimeval, char aType, uint32_t aHeapRuntimeCost,
1525 tmcallsite * aCallsite, uint32_t aHeapID, uint32_t aSize,
1526 tmcallsite * aOldCallsite, uint32_t aOldHeapID,
1527 uint32_t aOldSize)
1529 STAllocation *retval = NULL;
1530 static int compactor = 1;
1531 const int frequency = 10000;
1532 uint32_t actualSize, actualOldSize = 0;
1534 actualSize = actualByteSize(&globals.mCommandLineOptions, aSize);
1535 if (aOldSize)
1536 actualOldSize =
1537 actualByteSize(&globals.mCommandLineOptions, aOldSize);
1539 if (NULL != aCallsite) {
1540 int newAllocation = 0;
1541 tmcallsite *searchCallsite = NULL;
1542 uint32_t searchHeapID = 0;
1543 STAllocation *allocation = NULL;
1546 ** Global operation ID increases.
1548 globals.mOperationCount++;
1551 ** Fix up the timevals if needed.
1553 if (aTimeval < globals.mMinTimeval) {
1554 globals.mMinTimeval = aTimeval;
1556 if (aTimeval > globals.mMaxTimeval) {
1557 globals.mMaxTimeval = aTimeval;
1560 switch (aType) {
1561 case TM_EVENT_FREE:
1564 ** Update the global counter.
1566 globals.mFreeCount++;
1569 ** Update our peak memory used counter
1571 globals.mMemoryUsed -= actualSize;
1574 ** Not a new allocation, will need to search passed in site
1575 ** for the original allocation.
1577 searchCallsite = aCallsite;
1578 searchHeapID = aHeapID;
1580 break;
1582 case TM_EVENT_MALLOC:
1585 ** Update the global counter.
1587 globals.mMallocCount++;
1590 ** Update our peak memory used counter
1592 globals.mMemoryUsed += actualSize;
1593 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) {
1594 globals.mPeakMemoryUsed = globals.mMemoryUsed;
1598 ** This will be a new allocation.
1600 newAllocation = __LINE__;
1602 break;
1604 case TM_EVENT_CALLOC:
1607 ** Update the global counter.
1609 globals.mCallocCount++;
1612 ** Update our peak memory used counter
1614 globals.mMemoryUsed += actualSize;
1615 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) {
1616 globals.mPeakMemoryUsed = globals.mMemoryUsed;
1620 ** This will be a new allocation.
1622 newAllocation = __LINE__;
1624 break;
1626 case TM_EVENT_REALLOC:
1629 ** Update the global counter.
1631 globals.mReallocCount++;
1634 ** Update our peak memory used counter
1636 globals.mMemoryUsed += actualSize - actualOldSize;
1637 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) {
1638 globals.mPeakMemoryUsed = globals.mMemoryUsed;
1642 ** This might be a new allocation.
1644 if (NULL == aOldCallsite) {
1645 newAllocation = __LINE__;
1647 else {
1649 ** Need to search for the original callsite for the
1650 ** index to the allocation.
1652 searchCallsite = aOldCallsite;
1653 searchHeapID = aOldHeapID;
1656 break;
1658 default:
1660 REPORT_ERROR(__LINE__, allocationTracker);
1662 break;
1666 ** We are either modifying an existing allocation or we are creating
1667 ** a new one.
1669 if (0 != newAllocation) {
1670 allocation = (STAllocation *) calloc(1, sizeof(STAllocation));
1671 if (NULL != allocation) {
1673 ** Fixup the min timeval so if logic later will just work.
1675 allocation->mMinTimeval = ST_TIMEVAL_MAX;
1676 allocation->mMaxTimeval = ST_TIMEVAL_MAX;
1679 else if (NULL != searchCallsite
1680 && NULL != CALLSITE_RUN(searchCallsite)
1681 && 0 != searchHeapID) {
1683 ** We know what to search for, and we reduce what we search
1684 ** by only looking for those allocations at a known callsite.
1686 allocation =
1687 getLiveAllocationByHeapID(CALLSITE_RUN(searchCallsite),
1688 searchHeapID);
1690 else {
1691 REPORT_ERROR(__LINE__, allocationTracker);
1694 if (NULL != allocation) {
1695 STAllocEvent *appendResult = NULL;
1698 ** Record the amount of time this allocation event took.
1700 allocation->mHeapRuntimeCost += aHeapRuntimeCost;
1703 ** Now that we have an allocation, we need to make sure it has
1704 ** the proper event.
1706 appendResult =
1707 appendEvent(allocation, aTimeval, aType, aHeapID, aSize,
1708 aCallsite);
1709 if (NULL != appendResult) {
1710 if (0 != newAllocation) {
1711 int runAppendResult = 0;
1712 int callsiteAppendResult = 0;
1715 ** A new allocation needs to be added to the global run.
1716 ** A new allocation needs to be added to the callsite.
1718 runAppendResult =
1719 appendAllocation(&globals.mCommandLineOptions, NULL,
1720 &globals.mRun, allocation);
1721 callsiteAppendResult =
1722 appendAllocation(&globals.mCommandLineOptions, NULL,
1723 CALLSITE_RUN(aCallsite), allocation);
1724 if (0 != runAppendResult && 0 != callsiteAppendResult) {
1726 ** Success.
1728 retval = allocation;
1730 else {
1731 REPORT_ERROR(__LINE__, appendAllocation);
1734 else {
1736 ** An existing allocation, if a realloc situation,
1737 ** may need to be added to the new callsite.
1738 ** This can only occur if the new and old callsites
1739 ** differ.
1740 ** Even then, a brute force check will need to be made
1741 ** to ensure the allocation was not added twice;
1742 ** consider a realloc scenario where two different
1743 ** call stacks bump the allocation back and forth.
1745 if (aCallsite != searchCallsite) {
1746 int found = 0;
1748 found =
1749 hasAllocation(CALLSITE_RUN(aCallsite),
1750 allocation);
1751 if (0 == found) {
1752 int appendResult = 0;
1754 appendResult =
1755 appendAllocation(&globals.mCommandLineOptions,
1756 NULL,
1757 CALLSITE_RUN(aCallsite),
1758 allocation);
1759 if (0 != appendResult) {
1761 ** Success.
1763 retval = allocation;
1765 else {
1766 REPORT_ERROR(__LINE__, appendAllocation);
1769 else {
1771 ** Already there.
1773 retval = allocation;
1776 else {
1778 ** Success.
1780 retval = allocation;
1784 else {
1785 REPORT_ERROR(__LINE__, appendEvent);
1788 else {
1789 REPORT_ERROR(__LINE__, allocationTracker);
1792 else {
1793 REPORT_ERROR(__LINE__, allocationTracker);
1797 ** Compact the heap a bit if you can.
1799 compactor++;
1800 if (0 == (compactor % frequency)) {
1801 heapCompact();
1804 return retval;
1808 ** trackEvent
1810 ** An allocation event has dropped in on us.
1811 ** We need to do the right thing and track it.
1813 void
1814 trackEvent(uint32_t aTimeval, char aType, uint32_t aHeapRuntimeCost,
1815 tmcallsite * aCallsite, uint32_t aHeapID, uint32_t aSize,
1816 tmcallsite * aOldCallsite, uint32_t aOldHeapID, uint32_t aOldSize)
1818 if (NULL != aCallsite) {
1820 ** Verify the old callsite just in case.
1822 if (NULL != CALLSITE_RUN(aCallsite)
1823 && (NULL == aOldCallsite || NULL != CALLSITE_RUN(aOldCallsite))) {
1824 STAllocation *allocation = NULL;
1827 ** Add to the allocation tracking code.
1829 allocation =
1830 allocationTracker(aTimeval, aType, aHeapRuntimeCost,
1831 aCallsite, aHeapID, aSize, aOldCallsite,
1832 aOldHeapID, aOldSize);
1834 if (NULL == allocation) {
1835 REPORT_ERROR(__LINE__, allocationTracker);
1838 else {
1839 REPORT_ERROR(__LINE__, trackEvent);
1842 else {
1843 REPORT_ERROR(__LINE__, trackEvent);
1848 ** tmEventHandler
1850 ** Callback from the tmreader_eventloop function.
1851 ** Simply tries to sort out what we desire to know.
1854 static const char spinner_chars[] = { '/', '-', '\\', '|' };
1856 #define SPINNER_UPDATE_FREQUENCY 4096
1857 #define SPINNER_CHAR_COUNT (sizeof(spinner_chars) / sizeof(spinner_chars[0]))
1858 #define SPINNER_CHAR(_x) spinner_chars[(_x / SPINNER_UPDATE_FREQUENCY) % SPINNER_CHAR_COUNT]
1860 void
1861 tmEventHandler(tmreader * aReader, tmevent * aEvent)
1863 static int event_count = 0; /* for spinner */
1864 if ((event_count++ % SPINNER_UPDATE_FREQUENCY) == 0)
1865 printf("\rReading... %c", SPINNER_CHAR(event_count));
1867 if (NULL != aReader && NULL != aEvent) {
1868 switch (aEvent->type) {
1870 ** Events we ignore.
1872 case TM_EVENT_LIBRARY:
1873 case TM_EVENT_METHOD:
1874 case TM_EVENT_STATS:
1875 case TM_EVENT_TIMESTAMP:
1876 case TM_EVENT_FILENAME:
1877 break;
1880 ** Allocation events need to be tracked.
1882 case TM_EVENT_MALLOC:
1883 case TM_EVENT_CALLOC:
1884 case TM_EVENT_REALLOC:
1885 case TM_EVENT_FREE:
1887 uint32_t oldptr = 0;
1888 uint32_t oldsize = 0;
1889 tmcallsite *callsite = NULL;
1890 tmcallsite *oldcallsite = NULL;
1892 if (TM_EVENT_REALLOC == aEvent->type) {
1894 ** Only care about old arguments if there were any.
1896 if (0 != aEvent->u.alloc.oldserial) {
1897 oldptr = aEvent->u.alloc.oldptr;
1898 oldsize = aEvent->u.alloc.oldsize;
1899 oldcallsite =
1900 tmreader_callsite(aReader,
1901 aEvent->u.alloc.oldserial);
1902 if (NULL == oldcallsite) {
1903 REPORT_ERROR(__LINE__, tmreader_callsite);
1908 callsite = tmreader_callsite(aReader, aEvent->serial);
1909 if (NULL != callsite) {
1911 ** Verify a callsite run is there.
1912 ** If not, we are ignoring this callsite.
1914 if (NULL != CALLSITE_RUN(callsite)) {
1915 char eventType = aEvent->type;
1916 uint32_t eventSize = aEvent->u.alloc.size;
1919 ** Play a nasty trick on reallocs of size zero.
1920 ** They are to become free events, adjust the size accordingly.
1921 ** This allows me to avoid all types of special case code.
1923 if (0 == aEvent->u.alloc.size
1924 && TM_EVENT_REALLOC == aEvent->type) {
1925 eventType = TM_EVENT_FREE;
1926 if (0 != aEvent->u.alloc.oldserial) {
1927 eventSize = aEvent->u.alloc.oldsize;
1930 trackEvent(ticks2msec
1931 (aReader, aEvent->u.alloc.interval),
1932 eventType, ticks2usec(aReader,
1933 aEvent->u.alloc.
1934 cost), callsite,
1935 aEvent->u.alloc.ptr, eventSize,
1936 oldcallsite, oldptr, oldsize);
1939 else {
1940 REPORT_ERROR(__LINE__, tmreader_callsite);
1943 break;
1946 ** Callsite, set up the callsite run if it does not exist.
1948 case TM_EVENT_CALLSITE:
1950 tmcallsite *callsite =
1951 tmreader_callsite(aReader, aEvent->serial);
1953 if (NULL != callsite) {
1954 if (NULL == CALLSITE_RUN(callsite)) {
1955 int createrun = __LINE__;
1957 #if defined(MOZILLA_CLIENT)
1959 ** For a mozilla spacetrace, ignore this particular
1960 ** callsite as it is just noise, and causes us to
1961 ** use a lot of memory.
1963 ** This callsite is present on the linux build,
1964 ** not sure if the other platforms have it.
1966 if (0 !=
1967 hasCallsiteMatch(callsite, "g_main_is_running",
1968 ST_FOLLOW_PARENTS)) {
1969 createrun = 0;
1971 #endif /* MOZILLA_CLIENT */
1973 if (0 != createrun) {
1974 callsite->data = createRun(NULL, 0);
1978 else {
1979 REPORT_ERROR(__LINE__, tmreader_callsite);
1982 break;
1985 ** Unhandled events should not be allowed.
1987 default:
1989 REPORT_ERROR(__LINE__, tmEventHandler);
1991 break;
1997 ** optionGetDataOut
1999 ** Output option get data.
2001 void
2002 optionGetDataOut(PRFileDesc * inFD, STOptions * inOptions)
2004 if (NULL != inFD && NULL != inOptions) {
2005 int mark = 0;
2007 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
2008 PR_fprintf(inFD, "%s%s=%d", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name);
2009 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
2010 PR_fprintf(inFD, "%s%s=%s", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name);
2011 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
2013 uint32_t loop = 0; \
2015 for(loop = 0; loop < array_size; loop++) \
2017 PR_fprintf(inFD, "%s%s=%s", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name[loop]); \
2020 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */
2021 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
2022 PR_fprintf(inFD, "%s%s=%u", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name / multiplier);
2023 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
2025 uint64_t def64 = default_value; \
2026 uint64_t mul64 = multiplier; \
2027 uint64_t div64; \
2029 div64 = inOptions->m##option_name##64 / mul64; \
2030 PR_fprintf(inFD, "%s%s=%llu", (0 == mark++) ? "?" : "&", #option_name, div64); \
2033 #include "stoptions.h"
2038 ** htmlAnchor
2040 ** Output an HTML anchor, or just the text depending on the mode.
2042 void
2043 htmlAnchor(STRequest * inRequest,
2044 const char *aHref,
2045 const char *aText,
2046 const char *aTarget, const char *aClass, STOptions * inOptions)
2048 if (NULL != aHref && '\0' != *aHref && NULL != aText && '\0' != *aText) {
2049 int anchorLive = 1;
2052 ** In batch mode, we need to verify the anchor is live.
2054 if (0 != inRequest->mOptions.mBatchRequestCount) {
2055 uint32_t loop = 0;
2056 int comparison = 1;
2058 for (loop = 0; loop < inRequest->mOptions.mBatchRequestCount;
2059 loop++) {
2060 comparison =
2061 strcmp(aHref, inRequest->mOptions.mBatchRequest[loop]);
2062 if (0 == comparison) {
2063 break;
2068 ** Did we find it?
2070 if (0 == comparison) {
2071 anchorLive = 0;
2076 ** In any mode, don't make an href to the current page.
2078 if (0 != anchorLive && NULL != inRequest->mGetFileName) {
2079 if (0 == strcmp(aHref, inRequest->mGetFileName)) {
2080 anchorLive = 0;
2085 ** Do the right thing.
2087 if (0 != anchorLive) {
2088 PR_fprintf(inRequest->mFD, "<a class=\"%s\" ", aClass);
2089 if (NULL != aTarget && '\0' != *aTarget) {
2090 PR_fprintf(inRequest->mFD, "target=\"%s\" ", aTarget);
2092 PR_fprintf(inRequest->mFD, "href=\"./%s", aHref);
2095 ** The options, if desired, get appended as form data.
2097 optionGetDataOut(inRequest->mFD, inOptions);
2099 PR_fprintf(inRequest->mFD, "\">%s</a>\n", aText);
2101 else {
2102 PR_fprintf(inRequest->mFD, "<span class=\"%s\">%s</span>\n",
2103 aClass, aText);
2106 else {
2107 REPORT_ERROR(__LINE__, htmlAnchor);
2112 ** htmlAllocationAnchor
2114 ** Output an html achor that will resolve to the allocation in question.
2116 void
2117 htmlAllocationAnchor(STRequest * inRequest, STAllocation * aAllocation,
2118 const char *aText)
2120 if (NULL != aAllocation && NULL != aText && '\0' != *aText) {
2121 char buffer[128];
2124 ** This is a total hack.
2125 ** The filename contains the index of the allocation in globals.mRun.
2126 ** Safer than using the raw pointer value....
2128 PR_snprintf(buffer, sizeof(buffer), "allocation_%u.html",
2129 aAllocation->mRunIndex);
2131 htmlAnchor(inRequest, buffer, aText, NULL, "allocation",
2132 &inRequest->mOptions);
2134 else {
2135 REPORT_ERROR(__LINE__, htmlAllocationAnchor);
2140 ** resolveSourceFile
2142 ** Easy way to get a readable/short name.
2143 ** NULL if not present, not resolvable.
2145 const char *
2146 resolveSourceFile(tmmethodnode * aMethod)
2148 const char *retval = NULL;
2150 if (NULL != aMethod) {
2151 const char *methodSays = NULL;
2153 methodSays = aMethod->sourcefile;
2155 if (NULL != methodSays && '\0' != methodSays[0]
2156 && 0 != strcmp("noname", methodSays)) {
2157 retval = strrchr(methodSays, '/');
2158 if (NULL != retval) {
2159 retval++;
2161 else {
2162 retval = methodSays;
2167 return retval;
2171 ** htmlCallsiteAnchor
2173 ** Output an html anchor that will resolve to the callsite in question.
2174 ** If no text is provided, we provide our own.
2176 ** RealName determines whether or not we crawl our parents until the point
2177 ** we no longer match stats.
2179 void
2180 htmlCallsiteAnchor(STRequest * inRequest, tmcallsite * aCallsite,
2181 const char *aText, int aRealName)
2183 if (NULL != aCallsite) {
2184 char textBuf[512];
2185 char hrefBuf[128];
2186 tmcallsite *namesite = aCallsite;
2189 ** Should we use a different name?
2191 if (0 == aRealName && NULL != namesite->parent
2192 && NULL != namesite->parent->method) {
2193 STRun *myRun = NULL;
2194 STRun *upRun = NULL;
2196 do {
2197 myRun = CALLSITE_RUN(namesite);
2198 upRun = CALLSITE_RUN(namesite->parent);
2200 if (0 !=
2201 memcmp(&myRun->mStats[inRequest->mContext->mIndex],
2202 &upRun->mStats[inRequest->mContext->mIndex],
2203 sizeof(STCallsiteStats))) {
2205 ** Doesn't match, stop.
2207 break;
2209 else {
2211 ** Matches, keep going up.
2213 namesite = namesite->parent;
2216 while (NULL != namesite->parent
2217 && NULL != namesite->parent->method);
2221 ** If no text, provide our own.
2223 if (NULL == aText || '\0' == *aText) {
2224 const char *methodName = NULL;
2225 const char *sourceFile = NULL;
2227 if (NULL != namesite->method) {
2228 methodName = tmmethodnode_name(namesite->method);
2230 else {
2231 methodName = "==NONAME==";
2235 ** Decide which format to use to identify the callsite.
2236 ** If we can detect availability, hook up the filename with lxr information.
2238 sourceFile = resolveSourceFile(namesite->method);
2239 if (NULL != sourceFile
2240 && 0 == strncmp("mozilla/", namesite->method->sourcefile,
2241 8)) {
2242 char lxrHREFBuf[512];
2244 PR_snprintf(lxrHREFBuf, sizeof(lxrHREFBuf),
2245 " [<a href=\"http://lxr.mozilla.org/mozilla/source/%s#%u\" class=\"lxr\" target=\"_st_lxr\">%s:%u</a>]",
2246 namesite->method->sourcefile + 8,
2247 namesite->method->linenumber, sourceFile,
2248 namesite->method->linenumber);
2249 PR_snprintf(textBuf, sizeof(textBuf),
2250 "<span class=\"source mozilla-source\">%s</span>%s",
2251 methodName, lxrHREFBuf);
2253 else if (NULL != sourceFile) {
2254 PR_snprintf(textBuf, sizeof(textBuf),
2255 "<span class=\"source external-source\">%s [<span class=\"source-extra\">%s:%u</span>]</span>",
2256 methodName, sourceFile,
2257 namesite->method->linenumber);
2259 else {
2260 PR_snprintf(textBuf, sizeof(textBuf),
2261 "<span class=\"source binary-source\">%s [<span class=\"source-extra\">+%u(%u)</span>]</span>",
2262 methodName, namesite->offset,
2263 (uint32_t) namesite->entry.key);
2266 aText = textBuf;
2269 PR_snprintf(hrefBuf, sizeof(hrefBuf), "callsite_%u.html",
2270 (uint32_t) aCallsite->entry.key);
2272 htmlAnchor(inRequest, hrefBuf, aText, NULL, "callsite",
2273 &inRequest->mOptions);
2275 else {
2276 REPORT_ERROR(__LINE__, htmlCallsiteAnchor);
2281 ** htmlHeader
2283 ** Output a standard header in the report files.
2285 void
2286 htmlHeader(STRequest * inRequest, const char *aTitle)
2288 PR_fprintf(inRequest->mFD,
2289 "<html>\n"
2290 "<head>\n"
2291 "<title>%s</title>\n"
2292 "<link rel=\"stylesheet\" href=\"spacetrace.css\" type=\"text/css\""
2293 "</head>\n"
2294 "<body>\n"
2295 "<div class=spacetrace-header>\n"
2296 "<span class=spacetrace-title>Spacetrace</span>"
2297 "<span class=navigate>\n"
2298 "<span class=\"category-title header-text\">Category:</span>\n"
2299 "<span class=\"current-category\">%s</span>\n",
2300 aTitle, inRequest->mOptions.mCategoryName);
2302 PR_fprintf(inRequest->mFD, "<span class=\"header-item\">");
2303 htmlAnchor(inRequest, "index.html", "Index", NULL, "header-menuitem",
2304 &inRequest->mOptions);
2305 PR_fprintf(inRequest->mFD, "</span>\n");
2307 PR_fprintf(inRequest->mFD, "<span class=\"header-item\">");
2308 htmlAnchor(inRequest, "options.html", "Options", NULL, "header-menuitem",
2309 &inRequest->mOptions);
2310 PR_fprintf(inRequest->mFD, "</span>\n");
2312 PR_fprintf(inRequest->mFD, "</span>\n"); /* class=navigate */
2314 PR_fprintf(inRequest->mFD,
2315 "</div>\n\n<div class=\"header-separator\"></div>\n\n");
2319 ** htmlFooter
2321 ** Output a standard footer in the report file.
2323 void
2324 htmlFooter(STRequest * inRequest)
2326 PR_fprintf(inRequest->mFD,
2327 "<div class=\"footer-separator\"></div>\n\n"
2328 "<div class=\"footer\">\n"
2329 "<span class=\"footer-text\">SpaceTrace</span>\n"
2330 "</div>\n\n" "</body>\n" "</html>\n");
2334 ** htmlNotFound
2336 ** Not found message.
2338 void
2339 htmlNotFound(STRequest * inRequest)
2341 htmlHeader(inRequest, "File Not Found");
2342 PR_fprintf(inRequest->mFD, "File Not Found\n");
2343 htmlFooter(inRequest);
2346 void
2347 htmlStartTable(STRequest* inRequest,
2348 const char* table_class,
2349 const char* id,
2350 const char* caption,
2351 const char * const headers[], uint32_t header_length)
2353 uint32_t i;
2355 PR_fprintf(inRequest->mFD,
2356 "<div id=\"%s\"><table class=\"data %s\">\n"
2357 " <caption>%s</caption>"
2358 " <thead>\n"
2359 " <tr class=\"row-header\">\n", id,
2360 table_class ? table_class : "",
2361 caption);
2363 for (i=0; i< header_length; i++)
2364 PR_fprintf(inRequest->mFD,
2365 " <th>%s</th>\n", headers[i]);
2367 PR_fprintf(inRequest->mFD, " </tr> </thead> <tbody>\n");
2371 ** callsiteArrayFromCallsite
2373 ** Simply return an array of the callsites divulged from the site passed in,
2374 ** including the site passed in.
2375 ** Do not worry about dups, or the order of the items.
2377 ** Returns the number of items in the array.
2378 ** If the same as aExistingCount, then nothing happened.
2380 uint32_t
2381 callsiteArrayFromCallsite(tmcallsite *** aArray, uint32_t aExistingCount,
2382 tmcallsite * aSite, int aFollow)
2384 uint32_t retval = 0;
2386 if (NULL != aArray && NULL != aSite) {
2387 tmcallsite **expand = NULL;
2390 ** If we have an existing count, we just keep expanding this.
2392 retval = aExistingCount;
2395 ** Go through every allocation.
2397 do {
2399 ** expand the array.
2401 expand =
2402 (tmcallsite **) realloc(*aArray,
2403 sizeof(tmcallsite *) * (retval + 1));
2404 if (NULL != expand) {
2406 ** Set the callsite in case of pointer move.
2408 *aArray = expand;
2411 ** Assign the value.
2413 (*aArray)[retval] = aSite;
2414 retval++;
2416 else {
2417 REPORT_ERROR(__LINE__, realloc);
2418 break;
2423 ** What do we follow?
2425 switch (aFollow) {
2426 case ST_FOLLOW_SIBLINGS:
2427 aSite = aSite->siblings;
2428 break;
2429 case ST_FOLLOW_PARENTS:
2430 aSite = aSite->parent;
2431 break;
2432 default:
2433 aSite = NULL;
2434 REPORT_ERROR(__LINE__, callsiteArrayFromCallsite);
2435 break;
2438 while (NULL != aSite && NULL != aSite->method);
2441 return retval;
2445 ** callsiteArrayFromRun
2447 ** Simply return an array of the callsites from the run allocations.
2448 ** We only pay attention to callsites that were not free callsites.
2449 ** Do not worry about dups, or the order of the items.
2451 ** Returns the number of items in the array.
2452 ** If the same as aExistingCount, then nothing happened.
2454 uint32_t
2455 callsiteArrayFromRun(tmcallsite *** aArray, uint32_t aExistingCount,
2456 STRun * aRun)
2458 uint32_t retval = 0;
2460 if (NULL != aArray && NULL != aRun && 0 < aRun->mAllocationCount) {
2461 uint32_t allocLoop = 0;
2462 uint32_t eventLoop = 0;
2463 int stopLoops = 0;
2466 ** If we have an existing count, we just keep expanding this.
2468 retval = aExistingCount;
2471 ** Go through every allocation.
2473 for (allocLoop = 0;
2474 0 == stopLoops && allocLoop < aRun->mAllocationCount;
2475 allocLoop++) {
2477 ** Go through every event.
2479 for (eventLoop = 0;
2480 0 == stopLoops
2481 && eventLoop < aRun->mAllocations[allocLoop]->mEventCount;
2482 eventLoop++) {
2484 ** Skip the free events.
2486 if (TM_EVENT_FREE !=
2487 aRun->mAllocations[allocLoop]->mEvents[eventLoop].
2488 mEventType) {
2489 tmcallsite **expand = NULL;
2492 ** expand the array.
2494 expand =
2495 (tmcallsite **) realloc(*aArray,
2496 sizeof(tmcallsite *) *
2497 (retval + 1));
2498 if (NULL != expand) {
2500 ** Set the callsite in case of pointer move.
2502 *aArray = expand;
2505 ** Assign the value.
2507 (*aArray)[retval] =
2508 aRun->mAllocations[allocLoop]->mEvents[eventLoop].
2509 mCallsite;
2510 retval++;
2512 else {
2513 REPORT_ERROR(__LINE__, realloc);
2514 stopLoops = __LINE__;
2521 return retval;
2525 ** getDataPRUint*
2527 ** Helper to avoid cut and paste code.
2528 ** Failure to find aCheckFor does not mean failure.
2529 ** In case of dups, specify an index on non "1" to get others.
2530 ** Do not touch storage space unless a find is made.
2531 ** Returns !0 on failure.
2534 getDataPRUint32Base(const FormData * aGetData, const char *aCheckFor,
2535 int inIndex, void *aStoreResult, uint32_t aBits)
2537 int retval = 0;
2539 if (NULL != aGetData && NULL != aCheckFor && 0 != inIndex
2540 && NULL != aStoreResult) {
2541 unsigned finder = 0;
2544 ** Loop over the names, looking for an exact string match.
2545 ** Skip over initial finds, decrementing inIndex, until "1".
2547 for (finder = 0; finder < aGetData->mNVCount; finder++) {
2548 if (0 == strcmp(aCheckFor, aGetData->mNArray[finder])) {
2549 inIndex--;
2551 if (0 == inIndex) {
2552 int32_t scanRes = 0;
2554 if (64 == aBits) {
2555 scanRes =
2556 PR_sscanf(aGetData->mVArray[finder], "%llu",
2557 aStoreResult);
2559 else {
2560 scanRes =
2561 PR_sscanf(aGetData->mVArray[finder], "%u",
2562 aStoreResult);
2564 if (1 != scanRes) {
2565 retval = __LINE__;
2566 REPORT_ERROR(__LINE__, PR_sscanf);
2568 break;
2573 else {
2574 retval = __LINE__;
2575 REPORT_ERROR(__LINE__, getDataPRUint32Base);
2578 return retval;
2582 getDataPRUint32(const FormData * aGetData, const char *aCheckFor, int inIndex,
2583 uint32_t * aStoreResult, uint32_t aConversion)
2585 int retval = 0;
2587 retval =
2588 getDataPRUint32Base(aGetData, aCheckFor, inIndex, aStoreResult, 32);
2589 *aStoreResult *= aConversion;
2591 return retval;
2595 getDataPRUint64(const FormData * aGetData, const char *aCheckFor, int inIndex,
2596 uint64_t * aStoreResult64, uint64_t aConversion64)
2598 int retval = 0;
2599 uint64_t value64 = 0;
2601 retval = getDataPRUint32Base(aGetData, aCheckFor, inIndex, &value64, 64);
2602 *aStoreResult64 = value64 * aConversion64;
2604 return retval;
2608 ** getDataString
2610 ** Pull out the string data, if specified.
2611 ** In case of dups, specify an index on non "1" to get others.
2612 ** Do not touch storage space unless a find is made.
2613 ** Return !0 on failure.
2616 getDataString(const FormData * aGetData, const char *aCheckFor, int inIndex,
2617 char *aStoreResult, int inStoreResultLength)
2619 int retval = 0;
2621 if (NULL != aGetData && NULL != aCheckFor && 0 != inIndex
2622 && NULL != aStoreResult && 0 != inStoreResultLength) {
2623 unsigned finder = 0;
2626 ** Loop over the names, looking for an exact string match.
2627 ** Skip over initial finds, decrementing inIndex, until "1".
2629 for (finder = 0; finder < aGetData->mNVCount; finder++) {
2630 if (0 == strcmp(aCheckFor, aGetData->mNArray[finder])) {
2631 inIndex--;
2633 if (0 == inIndex) {
2634 PR_snprintf(aStoreResult, inStoreResultLength, "%s",
2635 aGetData->mVArray[finder]);
2636 break;
2641 else {
2642 retval = __LINE__;
2643 REPORT_ERROR(__LINE__, getDataPRUint32);
2646 return retval;
2650 ** displayTopAllocations
2652 ** Present the top allocations.
2653 ** The run must be passed in, and it must be pre-sorted.
2655 ** Returns !0 on failure.
2658 displayTopAllocations(STRequest * inRequest, STRun * aRun,
2659 const char* id,
2660 const char* caption,
2661 int aWantCallsite)
2663 int retval = 0;
2665 if (NULL != aRun) {
2666 if (0 < aRun->mAllocationCount) {
2667 uint32_t loop = 0;
2668 STAllocation *current = NULL;
2670 static const char* const headers[] = {
2671 "Rank", "Index", "Byte Size", "Lifespan (sec)",
2672 "Weight", "Heap Op (sec)"
2675 static const char* const headers_callsite[] = {
2676 "Rank", "Index", "Byte Size", "Lifespan (sec)",
2677 "Weight", "Heap Op (sec)", "Origin Callsite"
2680 if (aWantCallsite)
2681 htmlStartTable(inRequest, NULL, id,
2682 caption,
2683 headers_callsite,
2684 sizeof(headers_callsite) / sizeof(headers_callsite[0]));
2685 else
2686 htmlStartTable(inRequest, NULL, id, caption,
2687 headers,
2688 sizeof(headers) / sizeof(headers[0]));
2690 ** Loop over the items, up to some limit or until the end.
2692 for (loop = 0;
2693 loop < inRequest->mOptions.mListItemMax
2694 && loop < aRun->mAllocationCount; loop++) {
2695 current = aRun->mAllocations[loop];
2696 if (NULL != current) {
2697 uint32_t lifespan =
2698 current->mMaxTimeval - current->mMinTimeval;
2699 uint32_t size = byteSize(&inRequest->mOptions, current);
2700 uint32_t heapCost = current->mHeapRuntimeCost;
2701 uint64_t weight64 = 0;
2702 char buffer[32];
2704 weight64 =(uint64_t)(size * lifespan);
2706 PR_fprintf(inRequest->mFD, "<tr>\n");
2709 ** Rank.
2711 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n",
2712 loop + 1);
2715 ** Index.
2717 PR_snprintf(buffer, sizeof(buffer), "%u",
2718 current->mRunIndex);
2719 PR_fprintf(inRequest->mFD, "<td align=right>\n");
2720 htmlAllocationAnchor(inRequest, current, buffer);
2721 PR_fprintf(inRequest->mFD, "</td>\n");
2724 ** Byte Size.
2726 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n",
2727 size);
2730 ** Lifespan.
2732 PR_fprintf(inRequest->mFD,
2733 "<td align=right>" ST_TIMEVAL_FORMAT "</td>\n",
2734 ST_TIMEVAL_PRINTABLE(lifespan));
2737 ** Weight.
2739 PR_fprintf(inRequest->mFD, "<td align=right>%llu</td>\n",
2740 weight64);
2743 ** Heap operation cost.
2745 PR_fprintf(inRequest->mFD,
2746 "<td align=right>" ST_MICROVAL_FORMAT
2747 "</td>\n", ST_MICROVAL_PRINTABLE(heapCost));
2749 if (0 != aWantCallsite) {
2751 ** Callsite.
2753 PR_fprintf(inRequest->mFD, "<td>");
2754 htmlCallsiteAnchor(inRequest,
2755 current->mEvents[0].mCallsite,
2756 NULL, 0);
2757 PR_fprintf(inRequest->mFD, "</td>\n");
2760 PR_fprintf(inRequest->mFD, "</tr>\n");
2764 PR_fprintf(inRequest->mFD, "</tbody>\n</table></div>\n\n");
2767 else {
2768 retval = __LINE__;
2769 REPORT_ERROR(__LINE__, displayTopAllocations);
2772 return retval;
2776 ** displayMemoryLeaks
2778 ** Present the top memory leaks.
2779 ** The run must be passed in, and it must be pre-sorted.
2781 ** Returns !0 on failure.
2784 displayMemoryLeaks(STRequest * inRequest, STRun * aRun)
2786 int retval = 0;
2788 if (NULL != aRun) {
2789 uint32_t loop = 0;
2790 uint32_t displayed = 0;
2791 STAllocation *current = NULL;
2793 static const char * headers[] = {
2794 "Rank", "Index", "Byte Size", "Lifespan (sec)",
2795 "Weight", "Heap Op (sec)", "Origin Callsite"
2798 htmlStartTable(inRequest, NULL, "memory-leaks", "Memory Leaks", headers,
2799 sizeof(headers) / sizeof(headers[0]));
2802 ** Loop over all of the items, or until we've displayed enough.
2804 for (loop = 0;
2805 displayed < inRequest->mOptions.mListItemMax
2806 && loop < aRun->mAllocationCount; loop++) {
2807 current = aRun->mAllocations[loop];
2808 if (NULL != current && 0 != current->mEventCount) {
2810 ** In order to be a leak, the last event of its life must
2811 ** NOT be a free operation.
2813 ** A free operation is just that, a free.
2815 if (TM_EVENT_FREE !=
2816 current->mEvents[current->mEventCount - 1].mEventType) {
2817 uint32_t lifespan =
2818 current->mMaxTimeval - current->mMinTimeval;
2819 uint32_t size = byteSize(&inRequest->mOptions, current);
2820 uint32_t heapCost = current->mHeapRuntimeCost;
2821 uint64_t weight64 = 0;
2822 char buffer[32];
2824 weight64 =(uint64_t)(size * lifespan);
2827 ** One more shown.
2829 displayed++;
2831 PR_fprintf(inRequest->mFD, "<tr>\n");
2834 ** Rank.
2836 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n",
2837 displayed);
2840 ** Index.
2842 PR_snprintf(buffer, sizeof(buffer), "%u",
2843 current->mRunIndex);
2844 PR_fprintf(inRequest->mFD, "<td align=right>\n");
2845 htmlAllocationAnchor(inRequest, current, buffer);
2846 PR_fprintf(inRequest->mFD, "</td>\n");
2849 ** Byte Size.
2851 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n",
2852 size);
2855 ** Lifespan.
2857 PR_fprintf(inRequest->mFD,
2858 "<td align=right>" ST_TIMEVAL_FORMAT "</td>\n",
2859 ST_TIMEVAL_PRINTABLE(lifespan));
2862 ** Weight.
2864 PR_fprintf(inRequest->mFD, "<td align=right>%llu</td>\n",
2865 weight64);
2868 ** Heap Operation Seconds.
2870 PR_fprintf(inRequest->mFD,
2871 "<td align=right>" ST_MICROVAL_FORMAT
2872 "</td>\n", ST_MICROVAL_PRINTABLE(heapCost));
2875 ** Callsite.
2877 PR_fprintf(inRequest->mFD, "<td>");
2878 htmlCallsiteAnchor(inRequest,
2879 current->mEvents[0].mCallsite, NULL,
2881 PR_fprintf(inRequest->mFD, "</td>\n");
2883 PR_fprintf(inRequest->mFD, "</tr>\n");
2888 PR_fprintf(inRequest->mFD, "</tbody></table></div>\n\n");
2890 else {
2891 retval = __LINE__;
2892 REPORT_ERROR(__LINE__, displayMemoryLeaks);
2895 return retval;
2899 ** displayCallsites
2901 ** Display a table of callsites.
2902 ** If the stamp is non zero, then must match that stamp.
2903 ** If the stamp is zero, then must match the global sorted run stamp.
2904 ** Return !0 on error.
2907 displayCallsites(STRequest * inRequest, tmcallsite * aCallsite, int aFollow,
2908 uint32_t aStamp,
2909 const char* id,
2910 const char* caption,
2911 int aRealNames)
2913 int retval = 0;
2915 if (NULL != aCallsite && NULL != aCallsite->method) {
2916 int headerDisplayed = 0;
2917 STRun *run = NULL;
2920 ** Correct the stamp if need be.
2922 if (0 == aStamp && NULL != inRequest->mContext->mSortedRun) {
2923 aStamp =
2924 inRequest->mContext->mSortedRun->mStats[inRequest->mContext->
2925 mIndex].mStamp;
2929 ** Loop over the callsites looking for a stamp match.
2930 ** A stamp guarantees there is something interesting to look at too.
2931 ** If found, output it.
2933 while (NULL != aCallsite && NULL != aCallsite->method) {
2934 run = CALLSITE_RUN(aCallsite);
2935 if (NULL != run) {
2936 if (aStamp == run->mStats[inRequest->mContext->mIndex].mStamp) {
2938 ** We got a header?
2940 if (0 == headerDisplayed) {
2942 static const char* const headers[] = {
2943 "Callsite",
2944 "<abbr title=\"Composite Size\">C. Size</abbr>",
2945 "<abbr title=\"Composite Seconds\">C. Seconds</abbr>",
2946 "<abbr title=\"Composite Weight\">C. Weight</abbr>",
2947 "<abbr title=\"Heap Object Count\">H.O. Count</abbr>",
2948 "<abbr title=\"Composite Heap Operation Seconds\">C.H. Operation (sec)</abbr>"
2950 headerDisplayed = __LINE__;
2951 htmlStartTable(inRequest, NULL, id, caption, headers,
2952 sizeof(headers)/sizeof(headers[0]));
2956 ** Output the information.
2958 PR_fprintf(inRequest->mFD, "<tr>\n");
2961 ** Method name.
2963 PR_fprintf(inRequest->mFD, "<td>");
2964 htmlCallsiteAnchor(inRequest, aCallsite, NULL,
2965 aRealNames);
2966 PR_fprintf(inRequest->mFD, "</td>");
2969 ** Byte Size.
2971 PR_fprintf(inRequest->mFD,
2972 "<td valign=top align=right>%u</td>\n",
2973 run->mStats[inRequest->mContext->mIndex].
2974 mSize);
2977 ** Seconds.
2979 PR_fprintf(inRequest->mFD,
2980 "<td valign=top align=right>" ST_TIMEVAL_FORMAT
2981 "</td>\n",
2982 ST_TIMEVAL_PRINTABLE64(run->
2983 mStats[inRequest->
2984 mContext->
2985 mIndex].
2986 mTimeval64));
2989 ** Weight.
2991 PR_fprintf(inRequest->mFD,
2992 "<td valign=top align=right>%llu</td>\n",
2993 run->mStats[inRequest->mContext->mIndex].
2994 mWeight64);
2997 ** Allocation object count.
2999 PR_fprintf(inRequest->mFD,
3000 "<td valign=top align=right>%u</td>\n",
3001 run->mStats[inRequest->mContext->mIndex].
3002 mCompositeCount);
3005 ** Heap Operation Seconds.
3007 PR_fprintf(inRequest->mFD,
3008 "<td valign=top align=right>"
3009 ST_MICROVAL_FORMAT "</td>\n",
3010 ST_MICROVAL_PRINTABLE(run->
3011 mStats[inRequest->
3012 mContext->mIndex].
3013 mHeapRuntimeCost));
3015 PR_fprintf(inRequest->mFD, "</tr>\n");
3018 else {
3019 retval = __LINE__;
3020 REPORT_ERROR(__LINE__, displayCallsites);
3021 break;
3025 ** What do we follow?
3027 switch (aFollow) {
3028 case ST_FOLLOW_SIBLINGS:
3029 aCallsite = aCallsite->siblings;
3030 break;
3031 case ST_FOLLOW_PARENTS:
3032 aCallsite = aCallsite->parent;
3033 break;
3034 default:
3035 aCallsite = NULL;
3036 retval = __LINE__;
3037 REPORT_ERROR(__LINE__, displayCallsites);
3038 break;
3043 ** Terminate the table if we should.
3045 if (0 != headerDisplayed) {
3046 PR_fprintf(inRequest->mFD, "</tbody></table></div>\n\n");
3049 else {
3050 retval = __LINE__;
3051 REPORT_ERROR(__LINE__, displayCallsites);
3054 return retval;
3058 ** displayAllocationDetails
3060 ** Report what we know about the allocation.
3062 ** Returns !0 on error.
3065 displayAllocationDetails(STRequest * inRequest, STAllocation * aAllocation)
3067 int retval = 0;
3069 if (NULL != aAllocation) {
3070 uint32_t traverse = 0;
3071 uint32_t bytesize = byteSize(&inRequest->mOptions, aAllocation);
3072 uint32_t timeval =
3073 aAllocation->mMaxTimeval - aAllocation->mMinTimeval;
3074 uint32_t heapCost = aAllocation->mHeapRuntimeCost;
3075 uint64_t weight64 = 0;
3076 uint32_t cacheval = 0;
3077 int displayRes = 0;
3079 weight64 = (uint64_t)(bytesize * timeval);
3081 PR_fprintf(inRequest->mFD, "<p>Allocation %u Details:</p>\n",
3082 aAllocation->mRunIndex);
3084 PR_fprintf(inRequest->mFD, "<div id=\"allocation-details\"><table class=\"data summary\">\n");
3085 PR_fprintf(inRequest->mFD,
3086 "<tr><td align=left>Final Size:</td><td align=right>%u</td></tr>\n",
3087 bytesize);
3088 PR_fprintf(inRequest->mFD,
3089 "<tr><td align=left>Lifespan Seconds:</td><td align=right>"
3090 ST_TIMEVAL_FORMAT "</td></tr>\n",
3091 ST_TIMEVAL_PRINTABLE(timeval));
3092 PR_fprintf(inRequest->mFD,
3093 "<tr><td align=left>Weight:</td><td align=right>%llu</td></tr>\n",
3094 weight64);
3095 PR_fprintf(inRequest->mFD,
3096 "<tr><td align=left>Heap Operation Seconds:</td><td align=right>"
3097 ST_MICROVAL_FORMAT "</td></tr>\n",
3098 ST_MICROVAL_PRINTABLE(heapCost));
3099 PR_fprintf(inRequest->mFD, "</table></div>\n");
3102 ** The events.
3106 static const char* const headers[] = {
3107 "Operation", "Size", "Seconds", ""
3110 char caption[100];
3111 PR_snprintf(caption, sizeof(caption), "%u Life Event(s)",
3112 aAllocation->mEventCount);
3113 htmlStartTable(inRequest, NULL, "allocation-details", caption, headers,
3114 sizeof(headers) / sizeof(headers[0]));
3117 for (traverse = 0;
3118 traverse < aAllocation->mEventCount
3119 && traverse < inRequest->mOptions.mListItemMax; traverse++) {
3120 PR_fprintf(inRequest->mFD, "<tr>\n");
3123 ** count.
3125 PR_fprintf(inRequest->mFD,
3126 "<td valign=top align=right>%u.</td>\n", traverse + 1);
3129 ** Operation.
3131 PR_fprintf(inRequest->mFD, "<td valign=top>");
3132 switch (aAllocation->mEvents[traverse].mEventType) {
3133 case TM_EVENT_CALLOC:
3134 PR_fprintf(inRequest->mFD, "calloc");
3135 break;
3136 case TM_EVENT_FREE:
3137 PR_fprintf(inRequest->mFD, "free");
3138 break;
3139 case TM_EVENT_MALLOC:
3140 PR_fprintf(inRequest->mFD, "malloc");
3141 break;
3142 case TM_EVENT_REALLOC:
3143 PR_fprintf(inRequest->mFD, "realloc");
3144 break;
3145 default:
3146 retval = __LINE__;
3147 REPORT_ERROR(__LINE__, displayAllocationDetails);
3148 break;
3150 PR_fprintf(inRequest->mFD, "</td>");
3153 ** Size.
3155 PR_fprintf(inRequest->mFD, "<td valign=top align=right>%u</td>\n",
3156 aAllocation->mEvents[traverse].mHeapSize);
3159 ** Timeval.
3161 cacheval =
3162 aAllocation->mEvents[traverse].mTimeval - globals.mMinTimeval;
3163 PR_fprintf(inRequest->mFD,
3164 "<td valign=top align=right>" ST_TIMEVAL_FORMAT
3165 "</td>\n", ST_TIMEVAL_PRINTABLE(cacheval));
3168 ** Callsite backtrace.
3169 ** Only relevant backtrace is for event 0 for now until
3170 ** trace-malloc outputs proper callsites for all others.
3172 PR_fprintf(inRequest->mFD, "<td valign=top>\n");
3173 if (0 == traverse) {
3174 displayRes =
3175 displayCallsites(inRequest,
3176 aAllocation->mEvents[traverse].mCallsite,
3177 ST_FOLLOW_PARENTS, 0, "event-stack", "", __LINE__);
3178 if (0 != displayRes) {
3179 retval = __LINE__;
3180 REPORT_ERROR(__LINE__, displayCallsite);
3183 PR_fprintf(inRequest->mFD, "</td>\n");
3185 PR_fprintf(inRequest->mFD, "</tr>\n");
3187 PR_fprintf(inRequest->mFD, "</table></div>\n");
3189 else {
3190 retval = __LINE__;
3191 REPORT_ERROR(__LINE__, displayAllocationDetails);
3194 return retval;
3198 ** compareCallsites
3200 ** qsort callback.
3201 ** Compare the callsites as specified by the options.
3202 ** There must be NO equal callsites, unless they really are duplicates,
3203 ** this is so that a duplicate detector loop can
3204 ** simply skip sorted items until the callsite is different.
3207 compareCallsites(const void *aSite1, const void *aSite2, void *aContext)
3209 int retval = 0;
3210 STRequest *inRequest = (STRequest *) aContext;
3212 if (NULL != aSite1 && NULL != aSite2) {
3213 tmcallsite *site1 = *((tmcallsite **) aSite1);
3214 tmcallsite *site2 = *((tmcallsite **) aSite2);
3216 if (NULL != site1 && NULL != site2) {
3217 STRun *run1 = CALLSITE_RUN(site1);
3218 STRun *run2 = CALLSITE_RUN(site2);
3220 if (NULL != run1 && NULL != run2) {
3221 STCallsiteStats *stats1 =
3222 &(run1->mStats[inRequest->mContext->mIndex]);
3223 STCallsiteStats *stats2 =
3224 &(run2->mStats[inRequest->mContext->mIndex]);
3227 ** Logic determined by pref/option.
3229 switch (inRequest->mOptions.mOrderBy) {
3230 case ST_WEIGHT:
3232 uint64_t weight164 = stats1->mWeight64;
3233 uint64_t weight264 = stats2->mWeight64;
3235 if (weight164 < weight264) {
3236 retval = __LINE__;
3238 else if (weight164 > weight264) {
3239 retval = -__LINE__;
3242 break;
3244 case ST_SIZE:
3246 uint32_t size1 = stats1->mSize;
3247 uint32_t size2 = stats2->mSize;
3249 if (size1 < size2) {
3250 retval = __LINE__;
3252 else if (size1 > size2) {
3253 retval = -__LINE__;
3256 break;
3258 case ST_TIMEVAL:
3260 uint64_t timeval164 = stats1->mTimeval64;
3261 uint64_t timeval264 = stats2->mTimeval64;
3263 if (timeval164 < timeval264) {
3264 retval = __LINE__;
3266 else if (timeval164 > timeval264) {
3267 retval = -__LINE__;
3270 break;
3272 case ST_COUNT:
3274 uint32_t count1 = stats1->mCompositeCount;
3275 uint32_t count2 = stats2->mCompositeCount;
3277 if (count1 < count2) {
3278 retval = __LINE__;
3280 else if (count1 > count2) {
3281 retval = -__LINE__;
3284 break;
3286 case ST_HEAPCOST:
3288 uint32_t cost1 = stats1->mHeapRuntimeCost;
3289 uint32_t cost2 = stats2->mHeapRuntimeCost;
3291 if (cost1 < cost2) {
3292 retval = __LINE__;
3294 else if (cost1 > cost2) {
3295 retval = -__LINE__;
3298 break;
3300 default:
3302 REPORT_ERROR(__LINE__, compareAllocations);
3304 break;
3308 ** If the return value is still zero, do a pointer compare.
3309 ** This makes sure we return zero, only iff the same object.
3311 if (0 == retval) {
3312 if (stats1 < stats2) {
3313 retval = __LINE__;
3315 else if (stats1 > stats2) {
3316 retval = -__LINE__;
3323 return retval;
3327 ** displayTopCallsites
3329 ** Given a list of callsites, sort it, and output skipping dups.
3330 ** The passed in callsite array is side effected, as in that it will come
3331 ** back sorted. This function will not release the array.
3333 ** Note: If the stamp passed in is non zero, then all callsites must match.
3334 ** If the stamp is zero, all callsites must match global sorted run stamp.
3336 ** Returns !0 on error.
3339 displayTopCallsites(STRequest * inRequest, tmcallsite ** aCallsites,
3340 uint32_t aCallsiteCount, uint32_t aStamp,
3341 const char* id,
3342 const char* caption,
3343 int aRealName)
3345 int retval = 0;
3347 if (NULL != aCallsites && 0 < aCallsiteCount) {
3348 uint32_t traverse = 0;
3349 STRun *run = NULL;
3350 tmcallsite *site = NULL;
3351 int headerDisplayed = 0;
3352 uint32_t displayed = 0;
3355 ** Fixup the stamp.
3357 if (0 == aStamp && NULL != inRequest->mContext->mSortedRun) {
3358 aStamp =
3359 inRequest->mContext->mSortedRun->mStats[inRequest->mContext->
3360 mIndex].mStamp;
3364 ** Sort the things.
3366 NS_QuickSort(aCallsites, aCallsiteCount, sizeof(tmcallsite *),
3367 compareCallsites, inRequest);
3370 ** Time for output.
3372 for (traverse = 0;
3373 traverse < aCallsiteCount
3374 && inRequest->mOptions.mListItemMax > displayed; traverse++) {
3375 site = aCallsites[traverse];
3376 run = CALLSITE_RUN(site);
3379 ** Only if the same stamp....
3381 if (aStamp == run->mStats[inRequest->mContext->mIndex].mStamp) {
3383 ** We got a header yet?
3385 if (0 == headerDisplayed) {
3386 static const char* const headers[] = {
3387 "Rank",
3388 "Callsite",
3389 "<abbr title=\"Composite Size\">Size</abbr>",
3390 "<abbr title=\"Composite Seconds\">Seconds</abbr>",
3391 "<abbr title=\"Composite Weight\">Weight</abbr>",
3392 "<abbr title=\"Heap Object Count\">Object Count</abbr>",
3393 "<abbr title=\"Composite Heap Operation Seconds\">C.H. Operation (sec)</abbr>"
3395 headerDisplayed = __LINE__;
3397 htmlStartTable(inRequest, NULL, id, caption, headers,
3398 sizeof(headers) / sizeof(headers[0]));
3401 displayed++;
3403 PR_fprintf(inRequest->mFD, "<tr>\n");
3406 ** Rank.
3408 PR_fprintf(inRequest->mFD,
3409 "<td align=right valign=top>%u</td>\n", displayed);
3412 ** Method.
3414 PR_fprintf(inRequest->mFD, "<td>");
3415 htmlCallsiteAnchor(inRequest, site, NULL, aRealName);
3416 PR_fprintf(inRequest->mFD, "</td>\n");
3419 ** Size.
3421 PR_fprintf(inRequest->mFD,
3422 "<td align=right valign=top>%u</td>\n",
3423 run->mStats[inRequest->mContext->mIndex].mSize);
3426 ** Timeval.
3428 PR_fprintf(inRequest->mFD,
3429 "<td align=right valign=top>" ST_TIMEVAL_FORMAT
3430 "</td>\n",
3431 ST_TIMEVAL_PRINTABLE64(run->
3432 mStats[inRequest->mContext->
3433 mIndex].mTimeval64));
3436 ** Weight.
3438 PR_fprintf(inRequest->mFD,
3439 "<td align=right valign=top>%llu</td>\n",
3440 run->mStats[inRequest->mContext->mIndex].
3441 mWeight64);
3444 ** Allocation object count.
3446 PR_fprintf(inRequest->mFD,
3447 "<td align=right valign=top>%u</td>\n",
3448 run->mStats[inRequest->mContext->mIndex].
3449 mCompositeCount);
3452 ** Heap operation seconds.
3454 PR_fprintf(inRequest->mFD,
3455 "<td align=right valign=top>" ST_MICROVAL_FORMAT
3456 "</td>\n",
3457 ST_MICROVAL_PRINTABLE(run->
3458 mStats[inRequest->mContext->
3459 mIndex].
3460 mHeapRuntimeCost));
3462 PR_fprintf(inRequest->mFD, "</tr>\n");
3465 if (inRequest->mOptions.mListItemMax > displayed) {
3467 ** Skip any dups.
3469 while (((traverse + 1) < aCallsiteCount)
3470 && (site == aCallsites[traverse + 1])) {
3471 traverse++;
3478 ** We need to terminate anything?
3480 if (0 != headerDisplayed) {
3481 PR_fprintf(inRequest->mFD, "</table></div>\n");
3484 else {
3485 retval = __LINE__;
3486 REPORT_ERROR(__LINE__, displayTopCallsites);
3489 return retval;
3493 ** displayCallsiteDetails
3495 ** The callsite specific report.
3496 ** Try to report what we know.
3497 ** This one hits a little harder than the rest.
3499 ** Returns !0 on error.
3502 displayCallsiteDetails(STRequest * inRequest, tmcallsite * aCallsite)
3504 int retval = 0;
3506 if (NULL != aCallsite && NULL != aCallsite->method) {
3507 STRun *sortedRun = NULL;
3508 STRun *thisRun = CALLSITE_RUN(aCallsite);
3509 const char *sourceFile = NULL;
3511 sourceFile = resolveSourceFile(aCallsite->method);
3513 PR_fprintf(inRequest->mFD, "<div class=\"callsite-header\">\n");
3514 if (sourceFile) {
3515 PR_fprintf(inRequest->mFD, "<b>%s</b>",
3516 tmmethodnode_name(aCallsite->method));
3517 PR_fprintf(inRequest->mFD,
3518 " [<a href=\"http://lxr.mozilla.org/mozilla/source/%s#%u\" class=\"lxr\" target=\"_st_lxr\">%s:%u</a>]",
3519 aCallsite->method->sourcefile,
3520 aCallsite->method->linenumber, sourceFile,
3521 aCallsite->method->linenumber);
3523 else {
3524 PR_fprintf(inRequest->mFD,
3525 "<p><b>%s</b>+%u(%u) Callsite Details:</p>\n",
3526 tmmethodnode_name(aCallsite->method),
3527 aCallsite->offset, (uint32_t) aCallsite->entry.key);
3530 PR_fprintf(inRequest->mFD, "</div>\n\n");
3531 PR_fprintf(inRequest->mFD, "<div id=\"callsite-details\"><table class=\"data summary\">\n");
3532 PR_fprintf(inRequest->mFD,
3533 "<tr><td>Composite Byte Size:</td><td align=right>%u</td></tr>\n",
3534 thisRun->mStats[inRequest->mContext->mIndex].mSize);
3535 PR_fprintf(inRequest->mFD,
3536 "<tr><td>Composite Seconds:</td><td align=right>"
3537 ST_TIMEVAL_FORMAT "</td></tr>\n",
3538 ST_TIMEVAL_PRINTABLE64(thisRun->
3539 mStats[inRequest->mContext->mIndex].
3540 mTimeval64));
3541 PR_fprintf(inRequest->mFD,
3542 "<tr><td>Composite Weight:</td><td align=right>%llu</td></tr>\n",
3543 thisRun->mStats[inRequest->mContext->mIndex].mWeight64);
3544 PR_fprintf(inRequest->mFD,
3545 "<tr><td>Heap Object Count:</td><td align=right>%u</td></tr>\n",
3546 thisRun->mStats[inRequest->mContext->mIndex].
3547 mCompositeCount);
3548 PR_fprintf(inRequest->mFD,
3549 "<tr><td>Heap Operation Seconds:</td><td align=right>"
3550 ST_MICROVAL_FORMAT "</td></tr>\n",
3551 ST_MICROVAL_PRINTABLE(thisRun->
3552 mStats[inRequest->mContext->mIndex].
3553 mHeapRuntimeCost));
3554 PR_fprintf(inRequest->mFD, "</table></div>\n\n");
3557 ** Kids (callsites we call):
3559 if (NULL != aCallsite->kids && NULL != aCallsite->kids->method) {
3560 int displayRes = 0;
3561 uint32_t siteCount = 0;
3562 tmcallsite **sites = NULL;
3565 ** Collect the kid sibling callsites.
3566 ** Doing it this way sorts them for relevance.
3568 siteCount =
3569 callsiteArrayFromCallsite(&sites, 0, aCallsite->kids,
3570 ST_FOLLOW_SIBLINGS);
3571 if (0 != siteCount && NULL != sites) {
3573 ** Got something to show.
3575 displayRes =
3576 displayTopCallsites(inRequest, sites, siteCount, 0,
3577 "callsites",
3578 "Children Callsites",
3579 __LINE__);
3580 if (0 != displayRes) {
3581 retval = __LINE__;
3582 REPORT_ERROR(__LINE__, displayTopCallsites);
3586 ** Done with array.
3588 free(sites);
3589 sites = NULL;
3594 ** Parents (those who call us):
3596 if (NULL != aCallsite->parent && NULL != aCallsite->parent->method) {
3597 int displayRes = 0;
3599 displayRes =
3600 displayCallsites(inRequest, aCallsite->parent,
3601 ST_FOLLOW_PARENTS, 0, "caller-stack", "Caller stack",
3602 __LINE__);
3603 if (0 != displayRes) {
3604 retval = __LINE__;
3605 REPORT_ERROR(__LINE__, displayCallsites);
3610 ** Allocations we did.
3611 ** Simply harvest our own run.
3613 sortedRun = createRun(inRequest->mContext, 0);
3614 if (NULL != sortedRun) {
3615 int harvestRes = 0;
3617 harvestRes =
3618 harvestRun(CALLSITE_RUN(aCallsite), sortedRun,
3619 &inRequest->mOptions, inRequest->mContext);
3620 if (0 == harvestRes) {
3621 if (0 != sortedRun->mAllocationCount) {
3622 int sortRes = 0;
3624 sortRes = sortRun(&inRequest->mOptions, sortedRun);
3625 if (0 == sortRes) {
3626 int displayRes = 0;
3628 displayRes =
3629 displayTopAllocations(inRequest, sortedRun,
3630 "allocations",
3631 "Allocations",
3633 if (0 != displayRes) {
3634 retval = __LINE__;
3635 REPORT_ERROR(__LINE__, displayTopAllocations);
3638 else {
3639 retval = __LINE__;
3640 REPORT_ERROR(__LINE__, sortRun);
3644 else {
3645 retval = __LINE__;
3646 REPORT_ERROR(__LINE__, harvestRun);
3650 ** Done with the run.
3652 freeRun(sortedRun);
3653 sortedRun = NULL;
3655 else {
3656 retval = __LINE__;
3657 REPORT_ERROR(__LINE__, createRun);
3660 else {
3661 retval = __LINE__;
3662 REPORT_ERROR(__LINE__, displayCallsiteDetails);
3665 return retval;
3668 #if ST_WANT_GRAPHS
3670 ** graphFootprint
3672 ** Output a PNG graph of the memory usage of the run.
3674 ** Draw the graph within these boundaries.
3675 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN
3677 ** Returns !0 on failure.
3680 graphFootprint(STRequest * inRequest, STRun * aRun)
3682 int retval = 0;
3684 if (NULL != aRun) {
3685 uint32_t *YData = NULL;
3686 uint32_t YDataArray[STGD_SPACE_X];
3687 uint32_t traverse = 0;
3688 uint32_t timeval = 0;
3689 uint32_t loop = 0;
3690 PRBool underLock = PR_FALSE;
3693 ** Decide if this is custom or we should use the cache.
3695 if (aRun == inRequest->mContext->mSortedRun) {
3696 YData = inRequest->mContext->mFootprintYData;
3697 underLock = PR_TRUE;
3699 else {
3700 YData = YDataArray;
3704 ** Protect the shared data so that only one client has access to it
3705 ** at any given time.
3707 if (PR_FALSE != underLock) {
3708 PR_Lock(inRequest->mContext->mImageLock);
3712 ** Only do the computations if we aren't cached already.
3714 if (YData != inRequest->mContext->mFootprintYData
3715 || PR_FALSE == inRequest->mContext->mFootprintCached) {
3716 memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X);
3719 ** Initialize our Y data.
3720 ** Pretty brutal loop here....
3722 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X;
3723 traverse++) {
3725 ** Compute what timeval this Y data lands in.
3727 timeval =
3728 ((traverse *
3729 (globals.mMaxTimeval -
3730 globals.mMinTimeval)) / STGD_SPACE_X) +
3731 globals.mMinTimeval;
3734 ** Loop over the run.
3735 ** Should an allocation contain said Timeval, we're good.
3737 for (loop = 0; loop < aRun->mAllocationCount; loop++) {
3738 if (timeval >= aRun->mAllocations[loop]->mMinTimeval
3739 && timeval <= aRun->mAllocations[loop]->mMaxTimeval) {
3740 YData[traverse] +=
3741 byteSize(&inRequest->mOptions,
3742 aRun->mAllocations[loop]);
3748 ** Did we cache this?
3750 if (YData == inRequest->mContext->mFootprintYData) {
3751 inRequest->mContext->mFootprintCached = PR_TRUE;
3756 ** Done with the lock.
3758 if (PR_FALSE != underLock) {
3759 PR_Unlock(inRequest->mContext->mImageLock);
3762 if (0 == retval) {
3763 uint32_t minMemory = (uint32_t) - 1;
3764 uint32_t maxMemory = 0;
3765 int transparent = 0;
3766 gdImagePtr graph = NULL;
3769 ** Go through and find the minimum and maximum sizes.
3771 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
3772 if (YData[traverse] < minMemory) {
3773 minMemory = YData[traverse];
3775 if (YData[traverse] > maxMemory) {
3776 maxMemory = YData[traverse];
3781 ** We can now draw the graph.
3783 graph = createGraph(&transparent);
3784 if (NULL != graph) {
3785 gdSink theSink;
3786 int red = 0;
3787 int x1 = 0;
3788 int y1 = 0;
3789 int x2 = 0;
3790 int y2 = 0;
3791 uint32_t percents[11] =
3792 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
3793 char *timevals[11];
3794 char *bytes[11];
3795 char timevalSpace[11][32];
3796 char byteSpace[11][32];
3797 int legendColors[1];
3798 const char *legends[1] = { "Memory in Use" };
3799 uint32_t cached = 0;
3802 ** Figure out what the labels will say.
3804 for (traverse = 0; traverse < 11; traverse++) {
3805 timevals[traverse] = timevalSpace[traverse];
3806 bytes[traverse] = byteSpace[traverse];
3808 cached =
3809 ((globals.mMaxTimeval -
3810 globals.mMinTimeval) * percents[traverse]) / 100;
3811 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT,
3812 ST_TIMEVAL_PRINTABLE(cached));
3813 PR_snprintf(bytes[traverse], 32, "%u",
3814 ((maxMemory -
3815 minMemory) * percents[traverse]) / 100);
3818 red = gdImageColorAllocate(graph, 255, 0, 0);
3819 legendColors[0] = red;
3821 drawGraph(graph, -1, "Memory Footprint Over Time", "Seconds",
3822 "Bytes", 11, percents, (const char **) timevals, 11,
3823 percents, (const char **) bytes, 1, legendColors,
3824 legends);
3826 if (maxMemory != minMemory) {
3827 int64_t in64 = 0;
3828 int64_t ydata64 = 0;
3829 int64_t spacey64 = 0;
3830 int64_t mem64 = 0;
3831 int32_t in32 = 0;
3834 ** Go through our Y data and mark it up.
3836 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
3837 x1 = traverse + STGD_MARGIN;
3838 y1 = STGD_HEIGHT - STGD_MARGIN;
3841 ** Need to do this math in 64 bits.
3843 ydata64 = (int64_t)YData[traverse];
3844 spacey64 = (int64_t)STGD_SPACE_Y;
3845 mem64 = (int64_t)(maxMemory - minMemory);
3847 in64 = ydata64 * spacey64;
3848 in64 /= mem64;
3849 in32 = int32_t(in64);
3851 x2 = x1;
3852 y2 = y1 - in32;
3854 gdImageLine(graph, x1, y1, x2, y2, red);
3859 theSink.context = inRequest->mFD;
3860 theSink.sink = pngSink;
3861 gdImagePngToSink(graph, &theSink);
3863 gdImageDestroy(graph);
3865 else {
3866 retval = __LINE__;
3867 REPORT_ERROR(__LINE__, createGraph);
3871 else {
3872 retval = __LINE__;
3873 REPORT_ERROR(__LINE__, graphFootprint);
3876 return retval;
3878 #endif /* ST_WANT_GRAPHS */
3880 #if ST_WANT_GRAPHS
3882 ** graphTimeval
3884 ** Output a PNG graph of when the memory is allocated.
3886 ** Draw the graph within these boundaries.
3887 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN
3889 ** Returns !0 on failure.
3892 graphTimeval(STRequest * inRequest, STRun * aRun)
3894 int retval = 0;
3896 if (NULL != aRun) {
3897 uint32_t *YData = NULL;
3898 uint32_t YDataArray[STGD_SPACE_X];
3899 uint32_t traverse = 0;
3900 uint32_t timeval = globals.mMinTimeval;
3901 uint32_t loop = 0;
3902 PRBool underLock = PR_FALSE;
3905 ** Decide if this is custom or we should use the global cache.
3907 if (aRun == inRequest->mContext->mSortedRun) {
3908 YData = inRequest->mContext->mTimevalYData;
3909 underLock = PR_TRUE;
3911 else {
3912 YData = YDataArray;
3916 ** Protect the shared data so that only one client has access to it
3917 ** at any given time.
3919 if (PR_FALSE != underLock) {
3920 PR_Lock(inRequest->mContext->mImageLock);
3924 ** Only do the computations if we aren't cached already.
3926 if (YData != inRequest->mContext->mTimevalYData
3927 || PR_FALSE == inRequest->mContext->mTimevalCached) {
3928 uint32_t prevTimeval = 0;
3930 memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X);
3933 ** Initialize our Y data.
3934 ** Pretty brutal loop here....
3936 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X;
3937 traverse++) {
3939 ** Compute what timeval this Y data lands in.
3941 prevTimeval = timeval;
3942 timeval =
3943 ((traverse *
3944 (globals.mMaxTimeval -
3945 globals.mMinTimeval)) / STGD_SPACE_X) +
3946 globals.mMinTimeval;
3949 ** Loop over the run.
3950 ** Should an allocation have been allocated between
3951 ** prevTimeval and timeval....
3953 for (loop = 0; loop < aRun->mAllocationCount; loop++) {
3954 if (prevTimeval < aRun->mAllocations[loop]->mMinTimeval
3955 && timeval >= aRun->mAllocations[loop]->mMinTimeval) {
3956 YData[traverse] +=
3957 byteSize(&inRequest->mOptions,
3958 aRun->mAllocations[loop]);
3964 ** Did we cache this?
3966 if (YData == inRequest->mContext->mTimevalYData) {
3967 inRequest->mContext->mTimevalCached = PR_TRUE;
3972 ** Done with the lock.
3974 if (PR_FALSE != underLock) {
3975 PR_Unlock(inRequest->mContext->mImageLock);
3978 if (0 == retval) {
3979 uint32_t minMemory = (uint32_t) - 1;
3980 uint32_t maxMemory = 0;
3981 int transparent = 0;
3982 gdImagePtr graph = NULL;
3985 ** Go through and find the minimum and maximum sizes.
3987 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
3988 if (YData[traverse] < minMemory) {
3989 minMemory = YData[traverse];
3991 if (YData[traverse] > maxMemory) {
3992 maxMemory = YData[traverse];
3997 ** We can now draw the graph.
3999 graph = createGraph(&transparent);
4000 if (NULL != graph) {
4001 gdSink theSink;
4002 int red = 0;
4003 int x1 = 0;
4004 int y1 = 0;
4005 int x2 = 0;
4006 int y2 = 0;
4007 uint32_t percents[11] =
4008 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
4009 char *timevals[11];
4010 char *bytes[11];
4011 char timevalSpace[11][32];
4012 char byteSpace[11][32];
4013 int legendColors[1];
4014 const char *legends[1] = { "Memory Allocated" };
4015 uint32_t cached = 0;
4018 ** Figure out what the labels will say.
4020 for (traverse = 0; traverse < 11; traverse++) {
4021 timevals[traverse] = timevalSpace[traverse];
4022 bytes[traverse] = byteSpace[traverse];
4024 cached =
4025 ((globals.mMaxTimeval -
4026 globals.mMinTimeval) * percents[traverse]) / 100;
4027 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT,
4028 ST_TIMEVAL_PRINTABLE(cached));
4029 PR_snprintf(bytes[traverse], 32, "%u",
4030 ((maxMemory -
4031 minMemory) * percents[traverse]) / 100);
4034 red = gdImageColorAllocate(graph, 255, 0, 0);
4035 legendColors[0] = red;
4037 drawGraph(graph, -1, "Allocation Times", "Seconds", "Bytes",
4038 11, percents, (const char **) timevals, 11,
4039 percents, (const char **) bytes, 1, legendColors,
4040 legends);
4042 if (maxMemory != minMemory) {
4043 int64_t in64 = 0;
4044 int64_t ydata64 = 0;
4045 int64_t spacey64 = 0;
4046 int64_t mem64 = 0;
4047 int32_t in32 = 0;
4050 ** Go through our Y data and mark it up.
4052 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4053 x1 = traverse + STGD_MARGIN;
4054 y1 = STGD_HEIGHT - STGD_MARGIN;
4057 ** Need to do this math in 64 bits.
4059 ydata64 = (int64_t)YData[traverse];
4060 spacey64 = (int64_t)STGD_SPACE_Y;
4061 mem64 = (int64_t)(maxMemory - minMemory);
4063 in64 = ydata64 * spacey64;
4064 in64 /= mem64;
4065 in32 = int32_t(in64);
4067 x2 = x1;
4068 y2 = y1 - in32;
4070 gdImageLine(graph, x1, y1, x2, y2, red);
4075 theSink.context = inRequest->mFD;
4076 theSink.sink = pngSink;
4077 gdImagePngToSink(graph, &theSink);
4079 gdImageDestroy(graph);
4081 else {
4082 retval = __LINE__;
4083 REPORT_ERROR(__LINE__, createGraph);
4087 else {
4088 retval = __LINE__;
4089 REPORT_ERROR(__LINE__, graphTimeval);
4092 return retval;
4094 #endif /* ST_WANT_GRAPHS */
4096 #if ST_WANT_GRAPHS
4098 ** graphLifespan
4100 ** Output a PNG graph of how long memory lived.
4102 ** Draw the graph within these boundaries.
4103 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN
4105 ** Returns !0 on failure.
4108 graphLifespan(STRequest * inRequest, STRun * aRun)
4110 int retval = 0;
4112 if (NULL != aRun) {
4113 uint32_t *YData = NULL;
4114 uint32_t YDataArray[STGD_SPACE_X];
4115 uint32_t traverse = 0;
4116 uint32_t timeval = 0;
4117 uint32_t loop = 0;
4118 PRBool underLock = PR_FALSE;
4121 ** Decide if this is custom or we should use the global cache.
4123 if (aRun == inRequest->mContext->mSortedRun) {
4124 YData = inRequest->mContext->mLifespanYData;
4125 underLock = PR_TRUE;
4127 else {
4128 YData = YDataArray;
4132 ** Protect the shared data so that only one client has access to it
4133 ** at any given time.
4135 if (PR_FALSE != underLock) {
4136 PR_Lock(inRequest->mContext->mImageLock);
4140 ** Only do the computations if we aren't cached already.
4142 if (YData != inRequest->mContext->mLifespanYData
4143 || PR_FALSE == inRequest->mContext->mLifespanCached) {
4144 uint32_t prevTimeval = 0;
4145 uint32_t lifespan = 0;
4147 memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X);
4150 ** Initialize our Y data.
4151 ** Pretty brutal loop here....
4153 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X;
4154 traverse++) {
4156 ** Compute what timeval this Y data lands in.
4158 prevTimeval = timeval;
4159 timeval =
4160 (traverse * (globals.mMaxTimeval - globals.mMinTimeval)) /
4161 STGD_SPACE_X;
4164 ** Loop over the run.
4165 ** Should an allocation have lived between
4166 ** prevTimeval and timeval....
4168 for (loop = 0; loop < aRun->mAllocationCount; loop++) {
4169 lifespan =
4170 aRun->mAllocations[loop]->mMaxTimeval -
4171 aRun->mAllocations[loop]->mMinTimeval;
4173 if (prevTimeval < lifespan && timeval >= lifespan) {
4174 YData[traverse] +=
4175 byteSize(&inRequest->mOptions,
4176 aRun->mAllocations[loop]);
4182 ** Did we cache this?
4184 if (YData == inRequest->mContext->mLifespanYData) {
4185 inRequest->mContext->mLifespanCached = PR_TRUE;
4190 ** Done with the lock.
4192 if (PR_FALSE != underLock) {
4193 PR_Unlock(inRequest->mContext->mImageLock);
4196 if (0 == retval) {
4197 uint32_t minMemory = (uint32_t) - 1;
4198 uint32_t maxMemory = 0;
4199 int transparent = 0;
4200 gdImagePtr graph = NULL;
4203 ** Go through and find the minimum and maximum sizes.
4205 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4206 if (YData[traverse] < minMemory) {
4207 minMemory = YData[traverse];
4209 if (YData[traverse] > maxMemory) {
4210 maxMemory = YData[traverse];
4215 ** We can now draw the graph.
4217 graph = createGraph(&transparent);
4218 if (NULL != graph) {
4219 gdSink theSink;
4220 int red = 0;
4221 int x1 = 0;
4222 int y1 = 0;
4223 int x2 = 0;
4224 int y2 = 0;
4225 uint32_t percents[11] =
4226 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
4227 char *timevals[11];
4228 char *bytes[11];
4229 char timevalSpace[11][32];
4230 char byteSpace[11][32];
4231 int legendColors[1];
4232 const char *legends[1] = { "Live Memory" };
4233 uint32_t cached = 0;
4236 ** Figure out what the labels will say.
4238 for (traverse = 0; traverse < 11; traverse++) {
4239 timevals[traverse] = timevalSpace[traverse];
4240 bytes[traverse] = byteSpace[traverse];
4242 cached =
4243 ((globals.mMaxTimeval -
4244 globals.mMinTimeval) * percents[traverse]) / 100;
4245 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT,
4246 ST_TIMEVAL_PRINTABLE(cached));
4247 PR_snprintf(bytes[traverse], 32, "%u",
4248 ((maxMemory -
4249 minMemory) * percents[traverse]) / 100);
4252 red = gdImageColorAllocate(graph, 255, 0, 0);
4253 legendColors[0] = red;
4255 drawGraph(graph, -1, "Allocation Lifespans", "Lifespan",
4256 "Bytes", 11, percents, (const char **) timevals, 11,
4257 percents, (const char **) bytes, 1, legendColors,
4258 legends);
4260 if (maxMemory != minMemory) {
4261 int64_t in64 = 0;
4262 int64_t ydata64 = 0;
4263 int64_t spacey64 = 0;
4264 int64_t mem64 = 0;
4265 int32_t in32 = 0;
4268 ** Go through our Y data and mark it up.
4270 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4271 x1 = traverse + STGD_MARGIN;
4272 y1 = STGD_HEIGHT - STGD_MARGIN;
4275 ** Need to do this math in 64 bits.
4277 ydata64 = (int64_t)YData[traverse];
4278 spacey64 = (int64_t)STGD_SPACE_Y;
4279 mem64 = (int64_t)(maxMemory - minMemory);
4281 in64 = ydata64 * spacey64;
4282 in64 /= mem64;
4283 in32 = int32_t(in64);
4285 x2 = x1;
4286 y2 = y1 - in32;
4288 gdImageLine(graph, x1, y1, x2, y2, red);
4293 theSink.context = inRequest->mFD;
4294 theSink.sink = pngSink;
4295 gdImagePngToSink(graph, &theSink);
4297 gdImageDestroy(graph);
4299 else {
4300 retval = __LINE__;
4301 REPORT_ERROR(__LINE__, createGraph);
4305 else {
4306 retval = __LINE__;
4307 REPORT_ERROR(__LINE__, graphLifespan);
4310 return retval;
4312 #endif /* ST_WANT_GRAPHS */
4314 #if ST_WANT_GRAPHS
4316 ** graphWeight
4318 ** Output a PNG graph of Allocations by Weight
4320 ** Draw the graph within these boundaries.
4321 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN
4323 ** Returns !0 on failure.
4326 graphWeight(STRequest * inRequest, STRun * aRun)
4328 int retval = 0;
4330 if (NULL != aRun) {
4331 uint64_t *YData64 = NULL;
4332 uint64_t YDataArray64[STGD_SPACE_X];
4333 uint32_t traverse = 0;
4334 uint32_t timeval = globals.mMinTimeval;
4335 uint32_t loop = 0;
4336 PRBool underLock = PR_FALSE;
4339 ** Decide if this is custom or we should use the global cache.
4341 if (aRun == inRequest->mContext->mSortedRun) {
4342 YData64 = inRequest->mContext->mWeightYData64;
4343 underLock = PR_TRUE;
4345 else {
4346 YData64 = YDataArray64;
4350 ** Protect the shared data so that only one client has access to it
4351 ** at any given time.
4353 if (PR_FALSE != underLock) {
4354 PR_Lock(inRequest->mContext->mImageLock);
4358 ** Only do the computations if we aren't cached already.
4360 if (YData64 != inRequest->mContext->mWeightYData64
4361 || PR_FALSE == inRequest->mContext->mWeightCached) {
4362 uint32_t prevTimeval = 0;
4364 memset(YData64, 0, sizeof(uint64_t) * STGD_SPACE_X);
4367 ** Initialize our Y data.
4368 ** Pretty brutal loop here....
4370 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X;
4371 traverse++) {
4373 ** Compute what timeval this Y data lands in.
4375 prevTimeval = timeval;
4376 timeval =
4377 ((traverse *
4378 (globals.mMaxTimeval -
4379 globals.mMinTimeval)) / STGD_SPACE_X) +
4380 globals.mMinTimeval;
4383 ** Loop over the run.
4384 ** Should an allocation have been allocated between
4385 ** prevTimeval and timeval....
4387 for (loop = 0; loop < aRun->mAllocationCount; loop++) {
4388 if (prevTimeval < aRun->mAllocations[loop]->mMinTimeval
4389 && timeval >= aRun->mAllocations[loop]->mMinTimeval) {
4390 uint64_t size64 = 0;
4391 uint64_t lifespan64 = 0;
4392 uint64_t weight64 = 0;
4394 size64 = byteSize(&inRequest->mOptions,
4395 aRun->mAllocations[loop]);
4396 lifespan64 = aRun->mAllocations[loop]->mMaxTimeval -
4397 aRun->mAllocations[loop]->mMinTimeval;
4398 weight64 = size64 * lifespan64;
4400 YData64[traverse] += weight64;
4406 ** Did we cache this?
4408 if (YData64 == inRequest->mContext->mWeightYData64) {
4409 inRequest->mContext->mWeightCached = PR_TRUE;
4414 ** Done with the lock.
4416 if (PR_FALSE != underLock) {
4417 PR_Unlock(inRequest->mContext->mImageLock);
4420 if (0 == retval) {
4421 uint64_t minWeight64 = (0xFFFFFFFFLL << 32) + 0xFFFFFFFFLL;
4422 uint64_t maxWeight64 = 0;
4423 int transparent = 0;
4424 gdImagePtr graph = NULL;
4427 ** Go through and find the minimum and maximum weights.
4429 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4430 if (YData64[traverse] < minWeight64) {
4431 minWeight64 = YData64[traverse];
4433 if (YData64[traverse] > maxWeight64) {
4434 maxWeight64 = YData64[traverse];
4439 ** We can now draw the graph.
4441 graph = createGraph(&transparent);
4442 if (NULL != graph) {
4443 gdSink theSink;
4444 int red = 0;
4445 int x1 = 0;
4446 int y1 = 0;
4447 int x2 = 0;
4448 int y2 = 0;
4449 uint32_t percents[11] =
4450 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
4451 char *timevals[11];
4452 char *bytes[11];
4453 char timevalSpace[11][32];
4454 char byteSpace[11][32];
4455 int legendColors[1];
4456 const char *legends[1] = { "Memory Weight" };
4457 uint64_t percent64 = 0;
4458 uint64_t result64 = 0;
4460 uint32_t cached = 0;
4461 uint64_t hundred64 = 100;
4464 ** Figure out what the labels will say.
4466 for (traverse = 0; traverse < 11; traverse++) {
4467 timevals[traverse] = timevalSpace[traverse];
4468 bytes[traverse] = byteSpace[traverse];
4470 cached =
4471 ((globals.mMaxTimeval -
4472 globals.mMinTimeval) * percents[traverse]) / 100;
4473 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT,
4474 ST_TIMEVAL_PRINTABLE(cached));
4476 result64 = (maxWeight64 - minWeight64) * percents[traverse];
4477 result64 /= hundred64;
4478 PR_snprintf(bytes[traverse], 32, "%llu", result64);
4481 red = gdImageColorAllocate(graph, 255, 0, 0);
4482 legendColors[0] = red;
4484 drawGraph(graph, -1, "Allocation Weights", "Seconds",
4485 "Weight", 11, percents, (const char **) timevals,
4486 11, percents, (const char **) bytes, 1,
4487 legendColors, legends);
4489 if (maxWeight64 != minWeight64) {
4490 int64_t in64 = 0;
4491 int64_t spacey64 = 0;
4492 int64_t weight64 = 0;
4493 int32_t in32 = 0;
4496 ** Go through our Y data and mark it up.
4498 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) {
4499 x1 = traverse + STGD_MARGIN;
4500 y1 = STGD_HEIGHT - STGD_MARGIN;
4503 ** Need to do this math in 64 bits.
4505 spacey64 = (int64_t)STGD_SPACE_Y;
4506 weight64 = maxWeight64 - minWeight64;
4508 in64 = YData64[traverse] * spacey64;
4509 in64 /= weight64;
4510 in32 = int32_t(in64);
4512 x2 = x1;
4513 y2 = y1 - in32;
4515 gdImageLine(graph, x1, y1, x2, y2, red);
4520 theSink.context = inRequest->mFD;
4521 theSink.sink = pngSink;
4522 gdImagePngToSink(graph, &theSink);
4524 gdImageDestroy(graph);
4526 else {
4527 retval = __LINE__;
4528 REPORT_ERROR(__LINE__, createGraph);
4532 else {
4533 retval = __LINE__;
4534 REPORT_ERROR(__LINE__, graphWeight);
4537 return retval;
4539 #endif /* ST_WANT_GRAPHS */
4541 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
4543 uint32_t convert = (uint32_t)outOptions->m##option_name; \
4545 getDataPRUint32(inFormData, #option_name, 1, &convert, 1); \
4546 outOptions->m##option_name = (PRBool)convert; \
4548 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
4549 getDataString(inFormData, #option_name, 1, outOptions->m##option_name, sizeof(outOptions->m##option_name));
4550 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
4552 uint32_t loop = 0; \
4553 uint32_t found = 0; \
4554 char buffer[ST_OPTION_STRING_MAX]; \
4556 for(loop = 0; loop < array_size; loop++) \
4558 buffer[0] = '\0'; \
4559 getDataString(inFormData, #option_name, (loop + 1), buffer, sizeof(buffer)); \
4561 if('\0' != buffer[0]) \
4563 PR_snprintf(outOptions->m##option_name[found], sizeof(outOptions->m##option_name[found]), "%s", buffer); \
4564 found++; \
4568 for(; found < array_size; found++) \
4570 outOptions->m##option_name[found][0] = '\0'; \
4573 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */
4574 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
4575 getDataPRUint32(inFormData, #option_name, 1, &outOptions->m##option_name, multiplier);
4576 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
4578 uint64_t mul64 = multiplier; \
4580 getDataPRUint64(inFormData, #option_name, 1, &outOptions->m##option_name##64, mul64); \
4583 ** fillOptions
4585 ** Given an appropriate hexcaped string, distill the option values
4586 ** and fill the given STOption struct.
4588 ** Note that the options passed in are not touched UNLESS there is
4589 ** a replacement found in the form data.
4591 void
4592 fillOptions(STOptions * outOptions, const FormData * inFormData)
4594 if (NULL != outOptions && NULL != inFormData) {
4596 #include "stoptions.h"
4599 ** Special sanity check here for some options that need data validation.
4601 if (!outOptions->mCategoryName[0]
4602 || !findCategoryNode(outOptions->mCategoryName, &globals)) {
4603 PR_snprintf(outOptions->mCategoryName,
4604 sizeof(outOptions->mCategoryName), "%s",
4605 ST_ROOT_CATEGORY_NAME);
4611 void
4612 displayOptionString(STRequest * inRequest,
4613 const char *option_name,
4614 const char *option_genre,
4615 const char *default_value,
4616 const char *option_help, const char *value)
4618 #if 0
4619 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name);
4620 #endif
4621 PR_fprintf(inRequest->mFD, "<div class=option-box>\n");
4622 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name);
4623 PR_fprintf(inRequest->mFD,
4624 "<input type=text name=\"%s\" value=\"%s\">\n",
4625 option_name, value);
4626 PR_fprintf(inRequest->mFD,
4627 "<p class=option-default>Default value is \"%s\".</p>\n<p class=option-help>%s</p>\n",
4628 default_value, option_help);
4629 PR_fprintf(inRequest->mFD, "</div>\n");
4632 static void
4633 displayOptionStringArray(STRequest * inRequest,
4634 const char *option_name,
4635 const char *option_genre,
4636 uint32_t array_size,
4637 const char *option_help, const char values[5]
4638 [ST_OPTION_STRING_MAX])
4640 /* values should not be a fixed length! */
4641 PR_ASSERT(array_size == 5);
4642 #if 0
4643 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name);
4644 #endif
4645 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n");
4646 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name); {
4647 uint32_t loop = 0;
4649 for (loop = 0; loop < array_size; loop++) {
4650 PR_fprintf(inRequest->mFD,
4651 "<input type=text name=\"%s\" value=\"%s\"><br>\n",
4652 option_name, values[loop]);
4655 PR_fprintf(inRequest->mFD,
4656 "<p class=option-default>Up to %u occurrences allowed.</p>\n<p class=option-help>%s</p>\n",
4657 array_size, option_help);
4658 PR_fprintf(inRequest->mFD, "</div>\n");
4661 static void
4662 displayOptionInt(STRequest * inRequest,
4663 const char *option_name,
4664 const char *option_genre,
4665 uint32_t default_value,
4666 uint32_t multiplier, const char *option_help, uint32_t value)
4668 #if 0
4669 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name);
4670 #endif
4671 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n");
4672 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name);
4673 PR_fprintf(inRequest->mFD,
4674 "<input type=text name=%s value=%u>\n", option_name,
4675 value / multiplier);
4676 PR_fprintf(inRequest->mFD,
4677 "<p class=option-default>Default value is %u.</p>\n<p class=option-help>%s</p>\n",
4678 default_value, option_help);
4679 PR_fprintf(inRequest->mFD, "</div>\n");
4682 static void
4683 displayOptionInt64(STRequest * inRequest,
4684 const char *option_name,
4685 const char *option_genre,
4686 uint64_t default_value,
4687 uint64_t multiplier,
4688 const char *option_help, uint64_t value)
4690 #if 0
4691 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name);
4692 #endif
4693 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n");
4694 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name); {
4695 uint64_t def64 = default_value;
4696 uint64_t mul64 = multiplier;
4697 uint64_t div64;
4699 div64 = value / mul64;
4700 PR_fprintf(inRequest->mFD,
4701 "<input type=text name=%s value=%llu>\n",
4702 option_name, div64);
4703 PR_fprintf(inRequest->mFD,
4704 "<p class=option-default>Default value is %llu.</p>\n<p class=option-help>%s</p>\n",
4705 def64, option_help);
4707 PR_fprintf(inRequest->mFD, "</div>\n");
4711 ** displaySettings
4713 ** Present the settings for change during execution.
4715 void
4716 displaySettings(STRequest * inRequest)
4718 int applyRes = 0;
4721 ** We've got a form to create.
4723 PR_fprintf(inRequest->mFD, "<form method=get action=\"./index.html\">\n");
4725 ** Respect newlines in help text.
4727 #if 0
4728 PR_fprintf(inRequest->mFD, "<pre>\n");
4729 #endif
4730 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
4731 displayOptionBool(option_name, option_genre, option_help)
4732 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
4733 displayOptionString(inRequest, #option_name, #option_genre, default_value, option_help, inRequest->mOptions.m##option_name);
4734 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
4735 displayOptionStringArray(inRequest, #option_name, #option_genre, array_size, option_help, inRequest->mOptions.m##option_name);
4736 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */
4737 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
4738 displayOptionInt(inRequest, #option_name, #option_genre, default_value, multiplier, option_help, inRequest->mOptions.m##option_name);
4739 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
4740 displayOptionInt64(inRequest, #option_name, #option_genre, default_value, multiplier, option_help, inRequest->mOptions.m##option_name##64);
4741 #include "stoptions.h"
4743 ** Give a submit/reset button, obligatory.
4744 ** Done respecting newlines in help text.
4746 PR_fprintf(inRequest->mFD,
4747 "<input type=submit value=\"Save Options\"> <input type=reset>\n");
4748 #if 0
4749 PR_fprintf(inRequest->mFD, "</pre>\n");
4750 #endif
4752 ** Done with form.
4754 PR_fprintf(inRequest->mFD, "</form>\n");
4758 handleLocalFile(STRequest * inRequest, const char *aFilename)
4760 static const char *const local_files[] = {
4761 "spacetrace.css",
4763 static const size_t local_file_count =
4764 sizeof(local_files) / sizeof(local_files[0]);
4765 size_t i;
4767 for (i = 0; i < local_file_count; i++) {
4768 if (0 == strcmp(local_files[i], aFilename))
4769 return 1;
4771 return 0;
4775 ** displayFile
4777 ** reads a file from disk, and streams it to the request
4780 displayFile(STRequest * inRequest, const char *aFilename)
4782 PRFileDesc *inFd;
4783 const char *filepath =
4784 PR_smprintf("res%c%s", PR_GetDirectorySeparator(), aFilename);
4785 char buffer[2048];
4786 int32_t readRes;
4788 inFd = PR_Open(filepath, PR_RDONLY, PR_IRUSR);
4789 if (!inFd)
4790 return -1;
4791 while ((readRes = PR_Read(inFd, buffer, sizeof(buffer))) > 0) {
4792 PR_Write(inRequest->mFD, buffer, readRes);
4794 if (readRes != 0)
4795 return -1;
4796 PR_Close(inFd);
4797 return 0;
4801 ** displayIndex
4803 ** Present a list of the reports you can drill down into.
4804 ** Returns !0 on failure.
4807 displayIndex(STRequest * inRequest)
4809 int retval = 0;
4810 STOptions *options = &inRequest->mOptions;
4813 ** Present reports in a list format.
4815 PR_fprintf(inRequest->mFD, "<ul>");
4816 PR_fprintf(inRequest->mFD, "\n<li>");
4817 htmlAnchor(inRequest, "root_callsites.html", "Root Callsites",
4818 NULL, "mainmenu", options);
4819 PR_fprintf(inRequest->mFD, "\n<li>");
4820 htmlAnchor(inRequest, "categories_summary.html",
4821 "Categories Report", NULL, "mainmenu", options);
4822 PR_fprintf(inRequest->mFD, "\n<li>");
4823 htmlAnchor(inRequest, "top_callsites.html",
4824 "Top Callsites Report", NULL, "mainmenu", options);
4825 PR_fprintf(inRequest->mFD, "\n<li>");
4826 htmlAnchor(inRequest, "top_allocations.html",
4827 "Top Allocations Report", NULL, "mainmenu", options);
4828 PR_fprintf(inRequest->mFD, "\n<li>");
4829 htmlAnchor(inRequest, "memory_leaks.html",
4830 "Memory Leak Report", NULL, "mainmenu", options);
4831 #if ST_WANT_GRAPHS
4832 PR_fprintf(inRequest->mFD, "\n<li>Graphs");
4833 PR_fprintf(inRequest->mFD, "<ul>");
4834 PR_fprintf(inRequest->mFD, "\n<li>");
4835 htmlAnchor(inRequest, "footprint_graph.html", "Footprint",
4836 NULL, "mainmenu graph", options);
4837 PR_fprintf(inRequest->mFD, "\n<li>");
4838 htmlAnchor(inRequest, "lifespan_graph.html",
4839 "Allocation Lifespans", NULL, "mainmenu graph", options);
4840 PR_fprintf(inRequest->mFD, "\n<li>");
4841 htmlAnchor(inRequest, "times_graph.html", "Allocation Times",
4842 NULL, "mainmenu graph", options);
4843 PR_fprintf(inRequest->mFD, "\n<li>");
4844 htmlAnchor(inRequest, "weight_graph.html",
4845 "Allocation Weights", NULL, "mainmenu graph", options);
4846 PR_fprintf(inRequest->mFD, "\n</ul>\n");
4847 #endif /* ST_WANT_GRAPHS */
4848 PR_fprintf(inRequest->mFD, "\n</ul>\n");
4849 return retval;
4853 ** initRequestOptions
4855 ** Given the request, set the options that are specific to the request.
4856 ** These can generally be determined in the following manner:
4857 ** Copy over global options.
4858 ** If getData present, attempt to use options therein.
4860 void
4861 initRequestOptions(STRequest * inRequest)
4863 if (NULL != inRequest) {
4865 ** Copy of global options.
4867 memcpy(&inRequest->mOptions, &globals.mCommandLineOptions,
4868 sizeof(globals.mCommandLineOptions));
4870 ** Decide what will override global options if anything.
4872 if (NULL != inRequest->mGetData) {
4873 fillOptions(&inRequest->mOptions, inRequest->mGetData);
4878 STContext *
4879 contextLookup(STOptions * inOptions)
4881 ** Lookup a context that matches the options.
4882 ** The lookup may block, especially if the context needs to be created.
4883 ** Callers of this API must eventually call contextRelease with the
4884 ** return value; failure to do so will cause this applications
4885 ** to eventually not work as advertised.
4887 ** inOptions The options determine which context is relevant.
4888 ** returns The fully completed context on success.
4889 ** The context is read only in practice, so please do not
4890 ** write to it or anything it points to.
4891 ** NULL on failure.
4894 STContext *retval = NULL;
4895 STContextCache *inCache = &globals.mContextCache;
4897 if (NULL != inOptions && NULL != inCache) {
4898 uint32_t loop = 0;
4899 STContext *categoryException = NULL;
4900 PRBool newContext = PR_FALSE;
4901 PRBool evictContext = PR_FALSE;
4902 PRBool changeCategoryContext = PR_FALSE;
4905 ** Own the context cache while we are in here.
4907 PR_Lock(inCache->mLock);
4909 ** Loop until successful.
4910 ** Waiting on the condition variable makes sure we don't hog the
4911 ** lock below.
4913 while (1) {
4915 ** Go over the cache items.
4916 ** At this point we are looking for a cache hit, with multiple
4917 ** readers.
4919 for (loop = 0; loop < inCache->mItemCount; loop++) {
4921 ** Must be in use.
4923 if (PR_FALSE != inCache->mItems[loop].mInUse) {
4924 int delta[(STOptionGenre) MaxGenres];
4927 ** Compare the relevant options, figure out if different
4928 ** in any genre that we care about.
4930 memset(&delta, 0, sizeof(delta));
4931 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \
4932 if(inOptions->m##option_name != inCache->mItems[loop].mOptions.m##option_name) \
4934 delta[(STOptionGenre)option_genre]++; \
4936 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \
4937 if(0 != strcmp(inOptions->m##option_name, inCache->mItems[loop].mOptions.m##option_name)) \
4939 delta[(STOptionGenre)option_genre]++; \
4941 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \
4943 uint32_t macro_loop = 0; \
4945 for(macro_loop = 0; macro_loop < array_size; macro_loop++) \
4947 if(0 != strcmp(inOptions->m##option_name[macro_loop], inCache->mItems[loop].mOptions.m##option_name[macro_loop])) \
4949 break; \
4953 if(macro_loop != array_size) \
4955 delta[(STOptionGenre)option_genre]++; \
4958 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */
4959 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \
4960 if(inOptions->m##option_name != inCache->mItems[loop].mOptions.m##option_name) \
4962 delta[(STOptionGenre)option_genre]++; \
4964 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \
4965 if(inOptions->m##option_name##64 != inCache->mItems[loop].mOptions.m##option_name##64) \
4967 delta[(STOptionGenre)option_genre]++; \
4969 #include "stoptions.h"
4971 ** If there is no genre out of alignment, we accept this as the context.
4973 if (0 == delta[CategoryGenre] &&
4974 0 == delta[DataSortGenre] &&
4975 0 == delta[DataSetGenre] && 0 == delta[DataSizeGenre]
4977 retval = &inCache->mItems[loop].mContext;
4978 break;
4982 ** A special exception to the rule here.
4983 ** If all that is different is the category genre, and there
4984 ** is no one looking at the context (zero ref count),
4985 ** then there is some magic we can perform.
4987 if (NULL == retval &&
4988 0 == inCache->mItems[loop].mReferenceCount &&
4989 0 != delta[CategoryGenre] &&
4990 0 == delta[DataSortGenre] &&
4991 0 == delta[DataSetGenre] && 0 == delta[DataSizeGenre]
4993 categoryException = &inCache->mItems[loop].mContext;
4999 ** Pick up our category exception if relevant.
5001 if (NULL == retval && NULL != categoryException) {
5002 retval = categoryException;
5003 categoryException = NULL;
5004 changeCategoryContext = PR_TRUE;
5008 ** If we don't have a cache hit, then we need to check for an empty
5009 ** spot to take over.
5011 if (NULL == retval) {
5012 for (loop = 0; loop < inCache->mItemCount; loop++) {
5014 ** Must NOT be in use, then it will be the context.
5016 if (PR_FALSE == inCache->mItems[loop].mInUse) {
5017 retval = &inCache->mItems[loop].mContext;
5018 newContext = PR_TRUE;
5019 break;
5025 ** If we still don't have a return value, then we need to see if
5026 ** there are any old items with zero ref counts that we
5027 ** can take over.
5029 if (NULL == retval) {
5030 for (loop = 0; loop < inCache->mItemCount; loop++) {
5032 ** Must be in use.
5034 if (PR_FALSE != inCache->mItems[loop].mInUse) {
5036 ** Must have a ref count of zero.
5038 if (0 == inCache->mItems[loop].mReferenceCount) {
5040 ** Must be older than any other we know of.
5042 if (NULL != retval) {
5043 if (inCache->mItems[loop].mLastAccessed <
5044 inCache->mItems[retval->mIndex].
5045 mLastAccessed) {
5046 retval = &inCache->mItems[loop].mContext;
5049 else {
5050 retval = &inCache->mItems[loop].mContext;
5056 if (NULL != retval) {
5057 evictContext = PR_TRUE;
5062 ** If we still don't have a return value, then we can not avoid
5063 ** waiting around until someone gives us the chance.
5064 ** The chance, in specific, comes when a cache item reference
5065 ** count returns to zero, upon which we can try to take
5066 ** it over again.
5068 if (NULL == retval) {
5070 ** This has the side effect of release the context lock.
5071 ** This is a good thing so that other clients can continue
5072 ** to connect and hopefully have cache hits.
5073 ** If they do not have cache hits, then we will end up
5074 ** with a bunch of waiters here....
5076 PR_WaitCondVar(inCache->mCacheMiss, PR_INTERVAL_NO_TIMEOUT);
5080 ** If we have a return value, improve the reference count here.
5082 if (NULL != retval) {
5084 ** Decide if there are any changes to be made.
5085 ** Do as little as possible, then fall through the context
5086 ** cache lock to finish up.
5087 ** This way, lengthy init operations will not block
5088 ** other clients, only matches to this context.
5090 if (PR_FALSE != newContext ||
5091 PR_FALSE != evictContext ||
5092 PR_FALSE != changeCategoryContext) {
5094 ** Overwrite the prefs for this context.
5095 ** They are changing.
5097 memcpy(&inCache->mItems[retval->mIndex].mOptions,
5098 inOptions,
5099 sizeof(inCache->mItems[retval->mIndex].mOptions));
5101 ** As we are going to be changing the context, we need to write lock it.
5102 ** This makes sure no readers are allowed while we are making our changes.
5104 PR_RWLock_Wlock(retval->mRWLock);
5108 ** NOTE, ref count gets incremented here, inside content
5109 ** cache lock so it can not be flushed once lock
5110 ** released.
5112 inCache->mItems[retval->mIndex].mInUse = PR_TRUE;
5113 inCache->mItems[retval->mIndex].mReferenceCount++;
5115 ** That's all folks.
5117 break;
5120 } /* while(1), try again */
5123 ** Done with context cache.
5125 PR_Unlock(inCache->mLock);
5127 ** Now that the context cached is free to continue accepting other
5128 ** requests, we have a little more work to do.
5130 if (NULL != retval) {
5131 PRBool unlock = PR_FALSE;
5134 ** If evicting, we need to free off the old stuff.
5136 if (PR_FALSE != evictContext) {
5137 unlock = PR_TRUE;
5139 ** We do not free the sorted run.
5140 ** The category code takes care of this.
5142 retval->mSortedRun = NULL;
5143 #if ST_WANT_GRAPHS
5145 ** There is no need to
5146 ** PR_Lock(retval->mImageLock)
5147 ** We are already under write lock for the entire structure.
5149 retval->mFootprintCached = PR_FALSE;
5150 retval->mTimevalCached = PR_FALSE;
5151 retval->mLifespanCached = PR_FALSE;
5152 retval->mWeightCached = PR_FALSE;
5153 #endif
5157 ** If new or recently evicted, we need to fully init.
5159 if (PR_FALSE != newContext || PR_FALSE != evictContext) {
5160 unlock = PR_TRUE;
5161 retval->mSortedRun =
5162 createRunFromGlobal(&inCache->mItems[retval->mIndex].
5163 mOptions,
5164 &inCache->mItems[retval->mIndex].
5165 mContext);
5169 ** If changing category, we need to do some sneaky stuff.
5171 if (PR_FALSE != changeCategoryContext) {
5172 STCategoryNode *node = NULL;
5174 unlock = PR_TRUE;
5176 ** Just a category change. We don't need to harvest. Just find the
5177 ** right node and set the cache.mSortedRun. We need to recompute
5178 ** cost though. But that is cheap.
5180 node =
5181 findCategoryNode(inCache->mItems[retval->mIndex].mOptions.
5182 mCategoryName, &globals);
5183 if (node) {
5184 /* Recalculate cost of run */
5185 recalculateRunCost(&inCache->mItems[retval->mIndex].
5186 mOptions, retval,
5187 node->runs[retval->mIndex]);
5188 retval->mSortedRun = node->runs[retval->mIndex];
5191 #if ST_WANT_GRAPHS
5193 ** There is no need to
5194 ** PR_Lock(retval->mImageLock)
5195 ** We are already under write lock for the entire structure.
5197 retval->mFootprintCached = PR_FALSE;
5198 retval->mTimevalCached = PR_FALSE;
5199 retval->mLifespanCached = PR_FALSE;
5200 retval->mWeightCached = PR_FALSE;
5201 #endif
5205 ** Release the write lock if we took one to make changes.
5207 if (PR_FALSE != unlock) {
5208 PR_RWLock_Unlock(retval->mRWLock);
5212 ** Last thing possible, take a read lock on our return value.
5213 ** This will cause us to block if the context is not fully
5214 ** initialized in another thread holding the write lock.
5216 PR_RWLock_Rlock(retval->mRWLock);
5220 return retval;
5223 void
5224 contextRelease(STContext * inContext)
5226 ** After a successful call to contextLookup, one should call this API when
5227 ** done with the context.
5228 ** This effectively removes the usage of the client on a cached item.
5231 STContextCache *inCache = &globals.mContextCache;
5233 if (NULL != inContext && NULL != inCache) {
5235 ** Own the context cache while in here.
5237 PR_Lock(inCache->mLock);
5239 ** Give up the read lock on the context.
5241 PR_RWLock_Unlock(inContext->mRWLock);
5243 ** Decrement the reference count on the context.
5244 ** If it was the last reference, notify that a new item is
5245 ** available for eviction.
5246 ** A waiting thread will wake up and eat it.
5247 ** Also set when it was last accessed so the oldest unused item
5248 ** can be targeted for eviction.
5250 inCache->mItems[inContext->mIndex].mReferenceCount--;
5251 if (0 == inCache->mItems[inContext->mIndex].mReferenceCount) {
5252 PR_NotifyCondVar(inCache->mCacheMiss);
5253 inCache->mItems[inContext->mIndex].mLastAccessed =
5254 PR_IntervalNow();
5258 ** Done with context cache.
5260 PR_Unlock(inCache->mLock);
5266 ** handleRequest
5268 ** Based on what file they are asking for, perform some processing.
5269 ** Output the results to aFD.
5271 ** Returns !0 on error.
5274 handleRequest(tmreader * aTMR, PRFileDesc * aFD,
5275 const char *aFileName, const FormData * aGetData)
5277 int retval = 0;
5279 if (NULL != aTMR && NULL != aFD && NULL != aFileName
5280 && '\0' != *aFileName) {
5281 STRequest request;
5284 ** Init the request.
5286 memset(&request, 0, sizeof(request));
5287 request.mFD = aFD;
5288 request.mGetFileName = aFileName;
5289 request.mGetData = aGetData;
5291 ** Set local options for this request.
5293 initRequestOptions(&request);
5295 ** Get our cached context for this client.
5296 ** Simply based on the options.
5298 request.mContext = contextLookup(&request.mOptions);
5299 if (NULL != request.mContext) {
5301 ** Attempt to find the file of interest.
5303 if (handleLocalFile(&request, aFileName)) {
5304 displayFile(&request, aFileName);
5306 else if (0 == strcmp("index.html", aFileName)) {
5307 int displayRes = 0;
5309 htmlHeader(&request, "SpaceTrace Index");
5310 displayRes = displayIndex(&request);
5311 if (0 != displayRes) {
5312 retval = __LINE__;
5313 REPORT_ERROR(__LINE__, displayIndex);
5316 htmlFooter(&request);
5318 else if (0 == strcmp("settings.html", aFileName) ||
5319 0 == strcmp("options.html", aFileName)) {
5320 htmlHeader(&request, "SpaceTrace Options");
5321 displaySettings(&request);
5322 htmlFooter(&request);
5324 else if (0 == strcmp("top_allocations.html", aFileName)) {
5325 int displayRes = 0;
5327 htmlHeader(&request, "SpaceTrace Top Allocations Report");
5328 displayRes =
5329 displayTopAllocations(&request,
5330 request.mContext->mSortedRun,
5331 "top-allocations",
5332 "SpaceTrace Top Allocations Report",
5334 if (0 != displayRes) {
5335 retval = __LINE__;
5336 REPORT_ERROR(__LINE__, displayTopAllocations);
5339 htmlFooter(&request);
5341 else if (0 == strcmp("top_callsites.html", aFileName)) {
5342 int displayRes = 0;
5343 tmcallsite **array = NULL;
5344 uint32_t arrayCount = 0;
5347 ** Display header after we figure out if we are going to focus
5348 ** on a category.
5350 htmlHeader(&request, "SpaceTrace Top Callsites Report");
5351 if (NULL != request.mContext->mSortedRun
5352 && 0 < request.mContext->mSortedRun->mAllocationCount) {
5353 arrayCount =
5354 callsiteArrayFromRun(&array, 0,
5355 request.mContext->mSortedRun);
5356 if (0 != arrayCount && NULL != array) {
5357 displayRes =
5358 displayTopCallsites(&request, array, arrayCount,
5360 "top-callsites",
5361 "Top Callsites Report",
5363 if (0 != displayRes) {
5364 retval = __LINE__;
5365 REPORT_ERROR(__LINE__, displayTopCallsites);
5369 ** Done with the array.
5371 free(array);
5372 array = NULL;
5375 else {
5376 retval = __LINE__;
5377 REPORT_ERROR(__LINE__, handleRequest);
5380 htmlFooter(&request);
5382 else if (0 == strcmp("memory_leaks.html", aFileName)) {
5383 int displayRes = 0;
5385 htmlHeader(&request, "SpaceTrace Memory Leaks Report");
5386 displayRes =
5387 displayMemoryLeaks(&request,
5388 request.mContext->mSortedRun);
5389 if (0 != displayRes) {
5390 retval = __LINE__;
5391 REPORT_ERROR(__LINE__, displayMemoryLeaks);
5394 htmlFooter(&request);
5396 else if (0 == strncmp("allocation_", aFileName, 11)) {
5397 int scanRes = 0;
5398 uint32_t allocationIndex = 0;
5401 ** Oh, what a hack....
5402 ** The index to the allocation structure in the global run
5403 ** is in the filename. Better than the pointer value....
5405 scanRes = PR_sscanf(aFileName + 11, "%u", &allocationIndex);
5406 if (1 == scanRes
5407 && globals.mRun.mAllocationCount > allocationIndex
5408 && NULL != globals.mRun.mAllocations[allocationIndex]) {
5409 STAllocation *allocation =
5410 globals.mRun.mAllocations[allocationIndex];
5411 char buffer[128];
5412 int displayRes = 0;
5414 PR_snprintf(buffer, sizeof(buffer),
5415 "SpaceTrace Allocation %u Details Report",
5416 allocationIndex);
5417 htmlHeader(&request, buffer);
5418 displayRes =
5419 displayAllocationDetails(&request, allocation);
5420 if (0 != displayRes) {
5421 retval = __LINE__;
5422 REPORT_ERROR(__LINE__, displayAllocationDetails);
5425 htmlFooter(&request);
5427 else {
5428 htmlNotFound(&request);
5431 else if (0 == strncmp("callsite_", aFileName, 9)) {
5432 int scanRes = 0;
5433 uint32_t callsiteSerial = 0;
5434 tmcallsite *resolved = NULL;
5437 ** Oh, what a hack....
5438 ** The serial(key) to the callsite structure in the hash table
5439 ** is in the filename. Better than the pointer value....
5441 scanRes = PR_sscanf(aFileName + 9, "%u", &callsiteSerial);
5442 if (1 == scanRes && 0 != callsiteSerial
5443 && NULL != (resolved =
5444 tmreader_callsite(aTMR, callsiteSerial))) {
5445 char buffer[128];
5446 int displayRes = 0;
5448 PR_snprintf(buffer, sizeof(buffer),
5449 "SpaceTrace Callsite %u Details Report",
5450 callsiteSerial);
5451 htmlHeader(&request, buffer);
5452 displayRes = displayCallsiteDetails(&request, resolved);
5453 if (0 != displayRes) {
5454 retval = __LINE__;
5455 REPORT_ERROR(__LINE__, displayAllocationDetails);
5458 htmlFooter(&request);
5460 else {
5461 htmlNotFound(&request);
5464 else if (0 == strcmp("root_callsites.html", aFileName)) {
5465 int displayRes = 0;
5467 htmlHeader(&request, "SpaceTrace Root Callsites");
5468 displayRes =
5469 displayCallsites(&request, aTMR->calltree_root.kids,
5470 ST_FOLLOW_SIBLINGS, 0,
5471 "callsites-root",
5472 "SpaceTrace Root Callsites",
5473 __LINE__);
5474 if (0 != displayRes) {
5475 retval = __LINE__;
5476 REPORT_ERROR(__LINE__, displayCallsites);
5479 htmlFooter(&request);
5481 #if ST_WANT_GRAPHS
5482 else if (0 == strcmp("footprint_graph.html", aFileName)) {
5483 int displayRes = 0;
5485 htmlHeader(&request, "SpaceTrace Memory Footprint Report");
5486 PR_fprintf(request.mFD, "<div align=center>\n");
5487 PR_fprintf(request.mFD, "<img src=\"./footprint.png");
5488 optionGetDataOut(request.mFD, &request.mOptions);
5489 PR_fprintf(request.mFD, "\">\n");
5490 PR_fprintf(request.mFD, "</div>\n");
5491 htmlFooter(&request);
5493 #endif /* ST_WANT_GRAPHS */
5494 #if ST_WANT_GRAPHS
5495 else if (0 == strcmp("times_graph.html", aFileName)) {
5496 int displayRes = 0;
5498 htmlHeader(&request, "SpaceTrace Allocation Times Report");
5499 PR_fprintf(request.mFD, "<div align=center>\n");
5500 PR_fprintf(request.mFD, "<img src=\"./times.png");
5501 optionGetDataOut(request.mFD, &request.mOptions);
5502 PR_fprintf(request.mFD, "\">\n");
5503 PR_fprintf(request.mFD, "</div>\n");
5504 htmlFooter(&request);
5506 #endif /* ST_WANT_GRAPHS */
5507 #if ST_WANT_GRAPHS
5508 else if (0 == strcmp("lifespan_graph.html", aFileName)) {
5509 int displayRes = 0;
5511 htmlHeader(&request,
5512 "SpaceTrace Allocation Lifespans Report");
5513 PR_fprintf(request.mFD, "<div align=center>\n");
5514 PR_fprintf(request.mFD, "<img src=\"./lifespan.png");
5515 optionGetDataOut(request.mFD, &request.mOptions);
5516 PR_fprintf(request.mFD, "\">\n");
5517 PR_fprintf(request.mFD, "</div>\n");
5518 htmlFooter(&request);
5520 #endif /* ST_WANT_GRAPHS */
5521 #if ST_WANT_GRAPHS
5522 else if (0 == strcmp("weight_graph.html", aFileName)) {
5523 int displayRes = 0;
5525 htmlHeader(&request, "SpaceTrace Allocation Weights Report");
5526 PR_fprintf(request.mFD, "<div align=center>\n");
5527 PR_fprintf(request.mFD, "<img src=\"./weight.png");
5528 optionGetDataOut(request.mFD, &request.mOptions);
5529 PR_fprintf(request.mFD, "\">\n");
5530 PR_fprintf(request.mFD, "</div>\n");
5531 htmlFooter(&request);
5533 #endif /* ST_WANT_GRAPHS */
5534 #if ST_WANT_GRAPHS
5535 else if (0 == strcmp("footprint.png", aFileName)) {
5536 int graphRes = 0;
5538 graphRes =
5539 graphFootprint(&request, request.mContext->mSortedRun);
5540 if (0 != graphRes) {
5541 retval = __LINE__;
5542 REPORT_ERROR(__LINE__, graphFootprint);
5545 #endif /* ST_WANT_GRAPHS */
5546 #if ST_WANT_GRAPHS
5547 else if (0 == strcmp("times.png", aFileName)) {
5548 int graphRes = 0;
5550 graphRes =
5551 graphTimeval(&request, request.mContext->mSortedRun);
5552 if (0 != graphRes) {
5553 retval = __LINE__;
5554 REPORT_ERROR(__LINE__, graphTimeval);
5557 #endif /* ST_WANT_GRAPHS */
5558 #if ST_WANT_GRAPHS
5559 else if (0 == strcmp("lifespan.png", aFileName)) {
5560 int graphRes = 0;
5562 graphRes =
5563 graphLifespan(&request, request.mContext->mSortedRun);
5564 if (0 != graphRes) {
5565 retval = __LINE__;
5566 REPORT_ERROR(__LINE__, graphLifespan);
5569 #endif /* ST_WANT_GRAPHS */
5570 #if ST_WANT_GRAPHS
5571 else if (0 == strcmp("weight.png", aFileName)) {
5572 int graphRes = 0;
5574 graphRes =
5575 graphWeight(&request, request.mContext->mSortedRun);
5576 if (0 != graphRes) {
5577 retval = __LINE__;
5578 REPORT_ERROR(__LINE__, graphWeight);
5581 #endif /* ST_WANT_GRAPHS */
5582 else if (0 == strcmp("categories_summary.html", aFileName)) {
5583 int displayRes = 0;
5585 htmlHeader(&request, "Category Report");
5586 displayRes =
5587 displayCategoryReport(&request, &globals.mCategoryRoot,
5589 if (0 != displayRes) {
5590 retval = __LINE__;
5591 REPORT_ERROR(__LINE__, displayMemoryLeaks);
5594 htmlFooter(&request);
5596 else {
5597 htmlNotFound(&request);
5601 ** Release the context we obtained earlier.
5603 contextRelease(request.mContext);
5604 request.mContext = NULL;
5606 else {
5607 retval = __LINE__;
5608 REPORT_ERROR(__LINE__, contextObtain);
5611 else {
5612 retval = __LINE__;
5613 REPORT_ERROR(__LINE__, handleRequest);
5617 ** Compact a little if you can after each request.
5619 heapCompact();
5620 return retval;
5624 ** handleClient
5626 ** main() of the new client thread.
5627 ** Read the fd for the request.
5628 ** Output the results.
5630 void
5631 handleClient(void *inArg)
5633 PRFileDesc *aFD = NULL;
5635 aFD = (PRFileDesc *) inArg;
5636 if (NULL != aFD) {
5637 PRStatus closeRes = PR_SUCCESS;
5638 char aBuffer[2048];
5639 int32_t readRes = 0;
5641 readRes = PR_Read(aFD, aBuffer, sizeof(aBuffer));
5642 if (0 <= readRes) {
5643 const char *sanityCheck = "GET /";
5645 if (0 == strncmp(sanityCheck, aBuffer, 5)) {
5646 char *eourl = NULL;
5647 char *start = &aBuffer[5];
5648 char *getData = NULL;
5649 int realFun = 0;
5650 const char *crlf = "\015\012";
5651 char *eoline = NULL;
5652 FormData *fdGet = NULL;
5655 ** Truncate the line if possible.
5656 ** Only want first one.
5658 eoline = strstr(aBuffer, crlf);
5659 if (NULL != eoline) {
5660 *eoline = '\0';
5664 ** Find the whitespace.
5665 ** That is either end of line or the " HTTP/1.x" suffix.
5666 ** We do not care.
5668 for (eourl = start; 0 == isspace(*eourl) && '\0' != *eourl;
5669 eourl++) {
5671 ** No body.
5676 ** Cap it off.
5677 ** Convert empty '/' to index.html.
5679 *eourl = '\0';
5680 if ('\0' == *start) {
5681 strcpy(start, "index.html");
5685 ** Have we got any GET form data?
5687 getData = strchr(start, '?');
5688 if (NULL != getData) {
5690 ** Whack it off.
5692 *getData = '\0';
5693 getData++;
5697 ** Convert get data into a more useful format.
5699 fdGet = FormData_Create(getData);
5701 ** This is totally a hack, but oh well....
5703 ** Send that the request was OK, regardless.
5705 ** If we have any get data, then it is a set of options
5706 ** we attempt to apply.
5708 ** Other code will tell the user they were wrong or if
5709 ** there was an error.
5710 ** If the filename contains a ".png", then send the image
5711 ** mime type, otherwise, say it is text/html.
5713 PR_fprintf(aFD, "HTTP/1.1 200 OK%s", crlf);
5714 PR_fprintf(aFD, "Server: %s%s",
5715 "$Id: spacetrace.c,v 1.54 2006/11/01 23:02:17 timeless%mozdev.org Exp $",
5716 crlf);
5717 PR_fprintf(aFD, "Content-type: ");
5718 if (NULL != strstr(start, ".png")) {
5719 PR_fprintf(aFD, "image/png");
5721 else if (NULL != strstr(start, ".jpg")) {
5722 PR_fprintf(aFD, "image/jpeg");
5724 else if (NULL != strstr(start, ".txt")) {
5725 PR_fprintf(aFD, "text/plain");
5727 else if (NULL != strstr(start, ".css")) {
5728 PR_fprintf(aFD, "text/css");
5730 else {
5731 PR_fprintf(aFD, "text/html");
5733 PR_fprintf(aFD, crlf);
5735 ** One more to separate headers from content.
5737 PR_fprintf(aFD, crlf);
5739 ** Ready for the real fun.
5741 realFun = handleRequest(globals.mTMR, aFD, start, fdGet);
5742 if (0 != realFun) {
5743 REPORT_ERROR(__LINE__, handleRequest);
5747 ** Free off get data if around.
5749 FormData_Destroy(fdGet);
5750 fdGet = NULL;
5752 else {
5753 REPORT_ERROR(__LINE__, handleClient);
5756 else {
5757 REPORT_ERROR(__LINE__, lineReader);
5761 ** Done with the connection.
5763 closeRes = PR_Close(aFD);
5764 if (PR_SUCCESS != closeRes) {
5765 REPORT_ERROR(__LINE__, PR_Close);
5768 else {
5769 REPORT_ERROR(__LINE__, handleClient);
5774 ** serverMode
5776 ** List on a port as a httpd.
5777 ** Output results interactively on demand.
5779 ** Returns !0 on error.
5782 serverMode(void)
5784 int retval = 0;
5785 PRFileDesc *socket = NULL;
5788 ** Create a socket.
5790 socket = PR_NewTCPSocket();
5791 if (NULL != socket) {
5792 PRStatus closeRes = PR_SUCCESS;
5793 PRNetAddr bindAddr;
5794 PRStatus bindRes = PR_SUCCESS;
5797 ** Bind it to an interface/port.
5798 ** Any interface.
5800 bindAddr.inet.family = PR_AF_INET;
5801 bindAddr.inet.port =
5802 PR_htons((uint16_t) globals.mCommandLineOptions.mHttpdPort);
5803 bindAddr.inet.ip = PR_htonl(PR_INADDR_ANY);
5804 bindRes = PR_Bind(socket, &bindAddr);
5805 if (PR_SUCCESS == bindRes) {
5806 PRStatus listenRes = PR_SUCCESS;
5807 const int backlog = 0x20;
5810 ** Start listening for clients.
5811 ** Give a decent backlog, some of our processing will take
5812 ** a bit.
5814 listenRes = PR_Listen(socket, backlog);
5815 if (PR_SUCCESS == listenRes) {
5816 PRFileDesc *connection = NULL;
5817 int failureSum = 0;
5818 char message[80];
5821 ** Output a little message saying we are receiving.
5823 PR_snprintf(message, sizeof(message),
5824 "server accepting connections at http://localhost:%u/",
5825 globals.mCommandLineOptions.mHttpdPort);
5826 REPORT_INFO(message);
5827 PR_fprintf(PR_STDOUT, "Peak memory used: %s bytes\n",
5828 FormatNumber(globals.mPeakMemoryUsed));
5829 PR_fprintf(PR_STDOUT, "Allocations : %s total\n",
5830 FormatNumber(globals.mMallocCount +
5831 globals.mCallocCount +
5832 globals.mReallocCount),
5833 FormatNumber(globals.mFreeCount));
5834 PR_fprintf(PR_STDOUT, "Breakdown : %s malloc\n",
5835 FormatNumber(globals.mMallocCount));
5836 PR_fprintf(PR_STDOUT, " %s calloc\n",
5837 FormatNumber(globals.mCallocCount));
5838 PR_fprintf(PR_STDOUT, " %s realloc\n",
5839 FormatNumber(globals.mReallocCount));
5840 PR_fprintf(PR_STDOUT, " %s free\n",
5841 FormatNumber(globals.mFreeCount));
5842 PR_fprintf(PR_STDOUT, "Leaks : %s\n",
5843 FormatNumber((globals.mMallocCount +
5844 globals.mCallocCount +
5845 globals.mReallocCount) -
5846 globals.mFreeCount));
5848 ** Keep accepting until we know otherwise.
5850 ** We do a thread per connection.
5851 ** Up to the thread to close the connection when done.
5853 ** This is known by me to be suboptimal, and I would rather
5854 ** do a thread pool if it ever becomes a resource issue.
5855 ** Any issues would simply point to a need to get
5856 ** more machines or a beefier machine to handle the
5857 ** requests, as well as a need to do thread pooling and
5858 ** avoid thread creation overhead.
5859 ** The threads are not tracked, except possibly by NSPR
5860 ** itself and PR_Cleanup will wait on them all to exit as
5861 ** user threads so our shared data is valid.
5863 while (0 == retval) {
5864 connection =
5865 PR_Accept(socket, NULL, PR_INTERVAL_NO_TIMEOUT);
5866 if (NULL != connection) {
5867 PRThread *clientThread = NULL;
5870 ** Thread per connection.
5872 clientThread = PR_CreateThread(PR_USER_THREAD, /* PR_Cleanup sync */
5873 handleClient, (void *) connection, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, /* IO enabled */
5874 PR_UNJOINABLE_THREAD,
5876 if (NULL == clientThread) {
5877 PRStatus closeRes = PR_SUCCESS;
5879 failureSum += __LINE__;
5880 REPORT_ERROR(__LINE__, PR_Accept);
5882 ** Close the connection as well, no service
5884 closeRes = PR_Close(connection);
5885 if (PR_FAILURE == closeRes) {
5886 REPORT_ERROR(__LINE__, PR_Close);
5890 else {
5891 failureSum += __LINE__;
5892 REPORT_ERROR(__LINE__, PR_Accept);
5896 if (0 != failureSum) {
5897 retval = __LINE__;
5901 ** Output a little message saying it is all over.
5903 REPORT_INFO("server no longer accepting connections....");
5905 else {
5906 retval = __LINE__;
5907 REPORT_ERROR(__LINE__, PR_Listen);
5910 else {
5911 retval = __LINE__;
5912 REPORT_ERROR(__LINE__, PR_Bind);
5916 ** Done with socket.
5918 closeRes = PR_Close(socket);
5919 if (PR_SUCCESS != closeRes) {
5920 retval = __LINE__;
5921 REPORT_ERROR(__LINE__, PR_Close);
5923 socket = NULL;
5925 else {
5926 retval = __LINE__;
5927 REPORT_ERROR(__LINE__, PR_NewTCPSocket);
5930 return retval;
5934 ** batchMode
5936 ** Perform whatever batch requests we were asked to do.
5939 batchMode(void)
5941 int retval = 0;
5943 if (0 != globals.mCommandLineOptions.mBatchRequestCount) {
5944 uint32_t loop = 0;
5945 int failureSum = 0;
5946 int handleRes = 0;
5947 char aFileName[1024];
5948 uint32_t sprintfRes = 0;
5951 ** Go through and process the various files requested.
5952 ** We do not stop on failure, as it is too costly to rerun the
5953 ** batch job.
5955 for (loop = 0;
5956 loop < globals.mCommandLineOptions.mBatchRequestCount; loop++) {
5957 sprintfRes =
5958 PR_snprintf(aFileName, sizeof(aFileName), "%s%c%s",
5959 globals.mCommandLineOptions.mOutputDir,
5960 PR_GetDirectorySeparator(),
5961 globals.mCommandLineOptions.mBatchRequest[loop]);
5962 if ((uint32_t) - 1 != sprintfRes) {
5963 PRFileDesc *outFile = NULL;
5965 outFile = PR_Open(aFileName, ST_FLAGS, ST_PERMS);
5966 if (NULL != outFile) {
5967 PRStatus closeRes = PR_SUCCESS;
5969 handleRes =
5970 handleRequest(globals.mTMR, outFile,
5971 globals.mCommandLineOptions.
5972 mBatchRequest[loop], NULL);
5973 if (0 != handleRes) {
5974 failureSum += __LINE__;
5975 REPORT_ERROR(__LINE__, handleRequest);
5978 closeRes = PR_Close(outFile);
5979 if (PR_SUCCESS != closeRes) {
5980 failureSum += __LINE__;
5981 REPORT_ERROR(__LINE__, PR_Close);
5984 else {
5985 failureSum += __LINE__;
5986 REPORT_ERROR(__LINE__, PR_Open);
5989 else {
5990 failureSum += __LINE__;
5991 REPORT_ERROR(__LINE__, PR_snprintf);
5995 if (0 != failureSum) {
5996 retval = __LINE__;
5999 else {
6000 retval = __LINE__;
6001 REPORT_ERROR(__LINE__, outputReports);
6004 return retval;
6008 ** doRun
6010 ** Perform the actual processing this program requires.
6011 ** Returns !0 on failure.
6014 doRun(void)
6016 int retval = 0;
6019 ** Create the new trace-malloc reader.
6021 globals.mTMR = tmreader_new(globals.mProgramName, NULL);
6022 if (NULL != globals.mTMR) {
6023 int tmResult = 0;
6024 int outputResult = 0;
6026 #if defined(DEBUG_dp)
6027 PRIntervalTime start = PR_IntervalNow();
6029 fprintf(stderr, "DEBUG: reading tracemalloc data...\n");
6030 #endif
6031 tmResult =
6032 tmreader_eventloop(globals.mTMR,
6033 globals.mCommandLineOptions.mFileName,
6034 tmEventHandler);
6035 printf("\rReading... Done.\n");
6036 #if defined(DEBUG_dp)
6037 fprintf(stderr,
6038 "DEBUG: reading tracemalloc data ends: %dms [%d allocations]\n",
6039 PR_IntervalToMilliseconds(PR_IntervalNow() - start),
6040 globals.mRun.mAllocationCount);
6041 #endif
6042 if (0 == tmResult) {
6043 REPORT_ERROR(__LINE__, tmreader_eventloop);
6044 retval = __LINE__;
6047 if (0 == retval) {
6049 ** Decide if we're going into batch mode or server mode.
6051 if (0 != globals.mCommandLineOptions.mBatchRequestCount) {
6053 ** Output in one big step while everything still exists.
6055 outputResult = batchMode();
6056 if (0 != outputResult) {
6057 REPORT_ERROR(__LINE__, batchMode);
6058 retval = __LINE__;
6061 else {
6062 int serverRes = 0;
6065 ** httpd time.
6067 serverRes = serverMode();
6068 if (0 != serverRes) {
6069 REPORT_ERROR(__LINE__, serverMode);
6070 retval = __LINE__;
6075 ** Clear our categorization tree
6077 freeCategories(&globals);
6080 else {
6081 REPORT_ERROR(__LINE__, tmreader_new);
6082 retval = __LINE__;
6085 return retval;
6089 initCaches(void)
6091 ** Initialize the global caches.
6092 ** More involved since we have to allocated/create some objects.
6094 ** returns Zero if all is well.
6095 ** Non-zero on error.
6098 int retval = 0;
6099 STContextCache *inCache = &globals.mContextCache;
6101 if (NULL != inCache && 0 != globals.mCommandLineOptions.mContexts) {
6102 inCache->mLock = PR_NewLock();
6103 if (NULL != inCache->mLock) {
6104 inCache->mCacheMiss = PR_NewCondVar(inCache->mLock);
6105 if (NULL != inCache->mCacheMiss) {
6106 inCache->mItems =
6107 (STContextCacheItem *) calloc(globals.mCommandLineOptions.
6108 mContexts,
6109 sizeof(STContextCacheItem));
6110 if (NULL != inCache->mItems) {
6111 uint32_t loop = 0;
6112 char buffer[64];
6114 inCache->mItemCount =
6115 globals.mCommandLineOptions.mContexts;
6117 ** Init each item as needed.
6119 for (loop = 0; loop < inCache->mItemCount; loop++) {
6120 inCache->mItems[loop].mContext.mIndex = loop;
6121 PR_snprintf(buffer, sizeof(buffer),
6122 "Context Item %d RW Lock", loop);
6123 inCache->mItems[loop].mContext.mRWLock =
6124 PR_NewRWLock(PR_RWLOCK_RANK_NONE, buffer);
6125 if (NULL == inCache->mItems[loop].mContext.mRWLock) {
6126 break;
6128 #if ST_WANT_GRAPHS
6129 inCache->mItems[loop].mContext.mImageLock =
6130 PR_NewLock();
6131 if (NULL == inCache->mItems[loop].mContext.mImageLock) {
6132 break;
6134 #endif
6137 if (loop != inCache->mItemCount) {
6138 retval = __LINE__;
6139 REPORT_ERROR(__LINE__, initCaches);
6142 else {
6143 retval = __LINE__;
6144 REPORT_ERROR(__LINE__, calloc);
6147 else {
6148 retval = __LINE__;
6149 REPORT_ERROR(__LINE__, PR_NewCondVar);
6152 else {
6153 retval = __LINE__;
6154 REPORT_ERROR(__LINE__, PR_NewLock);
6157 else {
6158 retval = __LINE__;
6159 REPORT_ERROR(__LINE__, initCaches);
6162 return retval;
6166 destroyCaches(void)
6168 ** Clean up any global caches we have laying around.
6170 ** returns Zero if all is well.
6171 ** Non-zero on error.
6174 int retval = 0;
6175 STContextCache *inCache = &globals.mContextCache;
6177 if (NULL != inCache) {
6178 uint32_t loop = 0;
6181 ** Uninit item data one by one.
6183 for (loop = 0; loop < inCache->mItemCount; loop++) {
6184 if (NULL != inCache->mItems[loop].mContext.mRWLock) {
6185 PR_DestroyRWLock(inCache->mItems[loop].mContext.mRWLock);
6186 inCache->mItems[loop].mContext.mRWLock = NULL;
6188 #if ST_WANT_GRAPHS
6189 if (NULL != inCache->mItems[loop].mContext.mImageLock) {
6190 PR_DestroyLock(inCache->mItems[loop].mContext.mImageLock);
6191 inCache->mItems[loop].mContext.mImageLock = NULL;
6193 #endif
6196 inCache->mItemCount = 0;
6197 if (NULL != inCache->mItems) {
6198 free(inCache->mItems);
6199 inCache->mItems = NULL;
6202 if (NULL != inCache->mCacheMiss) {
6203 PR_DestroyCondVar(inCache->mCacheMiss);
6204 inCache->mCacheMiss = NULL;
6207 if (NULL != inCache->mLock) {
6208 PR_DestroyLock(inCache->mLock);
6209 inCache->mLock = NULL;
6212 else {
6213 retval = __LINE__;
6214 REPORT_ERROR(__LINE__, destroyCaches);
6217 return retval;
6221 ** main
6223 ** Process entry and exit.
6226 main(int aArgCount, char **aArgArray)
6228 int retval = 0;
6229 int optionsResult = 0;
6230 PRStatus prResult = PR_SUCCESS;
6231 int showedHelp = 0;
6232 int looper = 0;
6233 int cacheResult = 0;
6236 ** NSPR init.
6238 PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
6240 ** Initialize globals
6242 memset(&globals, 0, sizeof(globals));
6244 ** Set the program name.
6246 globals.mProgramName = aArgArray[0];
6248 ** Set the minimum timeval really high so other code
6249 ** that checks the timeval will get it right.
6251 globals.mMinTimeval = ST_TIMEVAL_MAX;
6253 ** Handle initializing options.
6255 optionsResult = initOptions(aArgCount, aArgArray);
6256 if (0 != optionsResult) {
6257 REPORT_ERROR(optionsResult, initOptions);
6258 retval = __LINE__;
6262 ** Initialize our caches.
6264 cacheResult = initCaches();
6265 if (0 != cacheResult) {
6266 retval = __LINE__;
6267 REPORT_ERROR(__LINE__, initCaches);
6271 ** Small alloc code init.
6273 globals.mCategoryRoot.runs =
6274 (STRun **) calloc(globals.mCommandLineOptions.mContexts,
6275 sizeof(STRun *));
6276 if (NULL == globals.mCategoryRoot.runs) {
6277 retval = __LINE__;
6278 REPORT_ERROR(__LINE__, calloc);
6282 ** Show help on usage if need be.
6284 showedHelp = showHelp();
6286 ** Only perform the run if everything is checking out.
6288 if (0 == showedHelp && 0 == retval) {
6289 int runResult = 0;
6291 runResult = doRun();
6292 if (0 != runResult) {
6293 REPORT_ERROR(runResult, doRun);
6294 retval = __LINE__;
6298 if (0 != retval) {
6299 REPORT_ERROR(retval, main);
6303 ** Have NSPR join all client threads we started.
6305 prResult = PR_Cleanup();
6306 if (PR_SUCCESS != prResult) {
6307 REPORT_ERROR(retval, PR_Cleanup);
6308 retval = __LINE__;
6311 ** All threads are joined/done by this line.
6315 ** Options allocated a little.
6317 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \
6318 if(NULL != globals.mCommandLineOptions.m##option_name) \
6320 free((void*)globals.mCommandLineOptions.m##option_name); \
6321 globals.mCommandLineOptions.m##option_name = NULL; \
6322 globals.mCommandLineOptions.m##option_name##Count = 0; \
6324 #include "stoptions.h"
6327 ** globals has a small modification to clear up.
6329 if (NULL != globals.mCategoryRoot.runs) {
6330 free(globals.mCategoryRoot.runs);
6331 globals.mCategoryRoot.runs = NULL;
6335 ** Blow away our caches.
6337 cacheResult = destroyCaches();
6338 if (0 != cacheResult) {
6339 retval = __LINE__;
6340 REPORT_ERROR(__LINE__, initCaches);
6344 ** We are safe to kill our tmreader data.
6346 if (NULL != globals.mTMR) {
6347 tmreader_destroy(globals.mTMR);
6348 globals.mTMR = NULL;
6351 return retval;