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