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