1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is spacecategory.c code, released
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2001
22 * the Initial Developer. All Rights Reserved.
25 * Suresh Duddi <dp@netscape.com>, 12-April-2002
27 * Alternatively, the contents of this file may be used under the terms of
28 * either the GNU General Public License Version 2 or later (the "GPL"), or
29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
44 ** Cagtegorizes each allocation using a predefined set of rules
45 ** and presents a tree of categories for browsing.
49 ** Required include files.
51 #include "spacetrace.h"
56 ** Ugh, MSVC6's qsort is too slow...
58 #include "nsQuickSort.h"
60 #if defined(HAVE_BOUTELL_GD)
62 ** See http://www.boutell.com/gd for the GD graphics library.
63 ** Ports for many platorms exist.
64 ** Your box may already have the lib (mine did, redhat 7.1 workstation).
70 #endif /* HAVE_BOUTELL_GD */
75 ** Add a rule into the list of rules maintainted in global
78 AddRule(STGlobals
* g
, STCategoryRule
* rule
)
80 if (g
->mNRules
% ST_ALLOC_STEP
== 0) {
82 STCategoryRule
**newrules
;
84 newrules
= (STCategoryRule
**) realloc(g
->mCategoryRules
,
87 sizeof(STCategoryRule
*));
89 REPORT_ERROR(__LINE__
, AddRule_No_Memory
);
92 g
->mCategoryRules
= newrules
;
94 g
->mCategoryRules
[g
->mNRules
++] = rule
;
101 ** Add the node as a child of the parent node
104 AddChild(STCategoryNode
* parent
, STCategoryNode
* child
)
106 if (parent
->nchildren
% ST_ALLOC_STEP
== 0) {
107 /* need more space */
108 STCategoryNode
**newnodes
;
110 newnodes
= (STCategoryNode
**) realloc(parent
->children
,
113 sizeof(STCategoryNode
*));
115 REPORT_ERROR(__LINE__
, AddChild_No_Memory
);
118 parent
->children
= newnodes
;
120 parent
->children
[parent
->nchildren
++] = child
;
125 ReParent(STCategoryNode
* parent
, STCategoryNode
* child
)
129 if (child
->parent
== parent
)
132 /* Remove child from old parent */
134 for (i
= 0; i
< child
->parent
->nchildren
; i
++) {
135 if (child
->parent
->children
[i
] == child
) {
136 /* Remove child from list */
137 if (i
+ 1 < child
->parent
->nchildren
)
138 memmove(&child
->parent
->children
[i
],
139 &child
->parent
->children
[i
+ 1],
140 (child
->parent
->nchildren
- i
-
141 1) * sizeof(STCategoryNode
*));
142 child
->parent
->nchildren
--;
148 /* Add child into new parent */
149 AddChild(parent
, child
);
157 ** Given a category name, finds the Node corresponding to the category
160 findCategoryNode(const char *catName
, STGlobals
* g
)
164 for (i
= 0; i
< g
->mNCategoryMap
; i
++) {
165 if (!strcmp(g
->mCategoryMap
[i
]->categoryName
, catName
))
166 return g
->mCategoryMap
[i
]->node
;
169 /* Check if we are looking for the root node */
170 if (!strcmp(catName
, ST_ROOT_CATEGORY_NAME
))
171 return &g
->mCategoryRoot
;
179 ** Adds a mapping between a category and its Node into the categoryMap
182 AddCategoryNode(STCategoryNode
* node
, STGlobals
* g
)
184 if (g
->mNCategoryMap
% ST_ALLOC_STEP
== 0) {
185 /* Need more space */
186 STCategoryMapEntry
**newmap
=
187 (STCategoryMapEntry
**) realloc(g
->mCategoryMap
,
190 sizeof(STCategoryMapEntry
*));
192 REPORT_ERROR(__LINE__
, AddCategoryNode_No_Memory
);
195 g
->mCategoryMap
= newmap
;
198 g
->mCategoryMap
[g
->mNCategoryMap
] =
199 (STCategoryMapEntry
*) calloc(1, sizeof(STCategoryMapEntry
));
200 if (!g
->mCategoryMap
[g
->mNCategoryMap
]) {
201 REPORT_ERROR(__LINE__
, AddCategoryNode_No_Memory
);
204 g
->mCategoryMap
[g
->mNCategoryMap
]->categoryName
= node
->categoryName
;
205 g
->mCategoryMap
[g
->mNCategoryMap
]->node
= node
;
213 ** Creates a new category node for category name 'catname' and makes
214 ** 'parent' its parent.
217 NewCategoryNode(const char *catName
, STCategoryNode
* parent
, STGlobals
* g
)
219 STCategoryNode
*node
;
221 node
= (STCategoryNode
*) calloc(1, sizeof(STCategoryNode
));
226 (STRun
**) calloc(g
->mCommandLineOptions
.mContexts
, sizeof(STRun
*));
227 if (NULL
== node
->runs
) {
232 node
->categoryName
= catName
;
234 /* Set parent of child */
235 node
->parent
= parent
;
237 /* Set child in parent */
238 AddChild(parent
, node
);
240 /* Add node into mapping table */
241 AddCategoryNode(node
, g
);
247 ** ProcessCategoryLeafRule
249 ** Add this into the tree as a leaf node. It doesn't know who its parent is. For now we make
250 ** root as its parent
253 ProcessCategoryLeafRule(STCategoryRule
* leafRule
, STCategoryNode
* root
,
256 STCategoryRule
*rule
;
257 STCategoryNode
*node
;
259 rule
= (STCategoryRule
*) calloc(1, sizeof(STCategoryRule
));
263 /* Take ownership of all elements of rule */
266 /* Find/Make a STCategoryNode and add it into the tree */
267 node
= findCategoryNode(rule
->categoryName
, g
);
269 node
= NewCategoryNode(rule
->categoryName
, root
, g
);
271 /* Make sure rule knows which node to access */
274 /* Add rule into rulelist */
281 ** ProcessCategoryParentRule
283 ** Rule has all the children of category as patterns. Sets up the tree so that
284 ** the parent child relationship is honored.
287 ProcessCategoryParentRule(STCategoryRule
* parentRule
, STCategoryNode
* root
,
290 STCategoryNode
*node
;
291 STCategoryNode
*child
;
294 /* Find the parent node in the tree. If not make one and add it into the tree */
295 node
= findCategoryNode(parentRule
->categoryName
, g
);
297 node
= NewCategoryNode(parentRule
->categoryName
, root
, g
);
302 /* For every child node, Find/Create it and make it the child of this node */
303 for (i
= 0; i
< parentRule
->npats
; i
++) {
304 child
= findCategoryNode(parentRule
->pats
[i
], g
);
306 child
= NewCategoryNode(parentRule
->pats
[i
], node
, g
);
311 /* Reparent child to node. This is because when we created the node
312 ** we would have created it as the child of root. Now we need to
313 ** remove it from root's child list and add it into this node
315 ReParent(node
, child
);
325 ** Initialize all categories. This reads in a file that says how to categorize
326 ** each callsite, creates a tree of these categories and makes a list of these
327 ** patterns in order for matching
330 initCategories(STGlobals
* g
)
338 fp
= fopen(g
->mCommandLineOptions
.mCategoryFile
, "r");
340 /* It isn't an error to not have a categories file */
341 REPORT_INFO("No categories file.");
348 memset(&rule
, 0, sizeof(rule
));
350 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
353 if (buf
[n
- 1] == '\n')
361 /* skip empty lines. If we are in a rule, end the rule. */
362 while (*in
&& isspace(*in
))
366 /* End the rule : leaf or non-leaf */
368 ProcessCategoryLeafRule(&rule
, &g
->mCategoryRoot
, g
);
371 ProcessCategoryParentRule(&rule
, &g
->mCategoryRoot
, g
);
373 memset(&rule
, 0, sizeof(rule
));
378 /* if we are in a rule acculumate */
380 rule
.pats
[rule
.npats
] = strdup(in
);
381 rule
.patlen
[rule
.npats
++] = strlen(in
);
383 else if (*in
== '<') {
384 /* Start a category */
388 /* Get the category name */
391 if (in
[n
- 1] == '>')
393 rule
.categoryName
= strdup(in
);
396 /* this is a non-leaf category. Should be of the form CategoryName
397 ** followed by list of child category names one per line
401 rule
.categoryName
= strdup(in
);
405 /* If we were in a rule when processing the last line, end the rule */
407 /* End the rule : leaf or non-leaf */
409 ProcessCategoryLeafRule(&rule
, &g
->mCategoryRoot
, g
);
412 ProcessCategoryParentRule(&rule
, &g
->mCategoryRoot
, g
);
415 /* Add the final "uncategorized" category. We make new memory locations
416 ** for all these to conform to the general principle of all strings are allocated
417 ** so it makes release logic very simple.
419 memset(&rule
, 0, sizeof(rule
));
420 rule
.categoryName
= strdup("uncategorized");
421 rule
.pats
[0] = strdup("");
424 ProcessCategoryLeafRule(&rule
, &g
->mCategoryRoot
, g
);
430 ** callsiteMatchesRule
432 ** Returns the corresponding node if callsite matches the rule. Rule is a sequence
433 ** of patterns that must match contiguously the callsite.
436 callsiteMatchesRule(tmcallsite
* aCallsite
, STCategoryRule
* aRule
)
439 const char *methodName
= NULL
;
441 while (patnum
< aRule
->npats
&& aCallsite
&& aCallsite
->method
) {
442 methodName
= tmmethodnode_name(aCallsite
->method
);
445 if (!*aRule
->pats
[patnum
]
446 || !strncmp(methodName
, aRule
->pats
[patnum
],
447 aRule
->patlen
[patnum
])) {
448 /* We have matched so far. Proceed up the stack and to the next pattern */
450 aCallsite
= aCallsite
->parent
;
453 /* Deal with mismatch */
455 /* contiguous mismatch. Stop */
458 /* We still haven't matched the first pattern. Proceed up the stack without
459 ** moving to the next pattern.
461 aCallsite
= aCallsite
->parent
;
465 if (patnum
== aRule
->npats
) {
466 /* all patterns matched. We have a winner. */
467 #if defined(DEBUG_dp) && 0
468 fprintf(stderr
, "[%s] match\n", aRule
->categoryName
);
477 PRIntervalTime _gMatchTime
= 0;
478 PRUint32 _gMatchCount
= 0;
479 PRUint32 _gMatchRules
= 0;
485 ** Runs through all rules and returns the node corresponding to
486 ** a match of the allocation.
489 matchAllocation(STGlobals
* g
, STAllocation
* aAllocation
)
492 PRIntervalTime start
= PR_IntervalNow();
495 STCategoryNode
*node
= NULL
;
496 STCategoryRule
*rule
;
498 for (rulenum
= 0; rulenum
< g
->mNRules
; rulenum
++) {
502 rule
= g
->mCategoryRules
[rulenum
];
503 if (callsiteMatchesRule(aAllocation
->mEvents
[0].mCallsite
, rule
)) {
510 _gMatchTime
+= PR_IntervalNow() - start
;
516 ** categorizeAllocation
518 ** Given an allocation, it adds it into the category tree at the right spot
519 ** by comparing the allocation to the rules and adding into the right node.
520 ** Also, does propogation of cost upwards in the tree.
521 ** The root of the tree is in the globls as the tree is dependent on the
522 ** category file (options) rather than the run.
525 categorizeAllocation(STOptions
* inOptions
, STContext
* inContext
,
526 STAllocation
* aAllocation
, STGlobals
* g
)
528 /* Run through the rules in order to see if this allcation matches
531 STCategoryNode
*node
;
533 node
= matchAllocation(g
, aAllocation
);
535 /* ugh! it should atleast go into the "uncategorized" node. wierd!
537 REPORT_ERROR(__LINE__
, categorizeAllocation
);
541 /* Create run for node if not already */
542 if (!node
->runs
[inContext
->mIndex
]) {
544 ** Create run with positive timestamp as we can harvest it later
545 ** for callsite details summarization
547 node
->runs
[inContext
->mIndex
] =
548 createRun(inContext
, PR_IntervalNow());
549 if (!node
->runs
[inContext
->mIndex
]) {
550 REPORT_ERROR(__LINE__
, categorizeAllocation_No_Memory
);
555 /* Add allocation into node. We expand the table of allocations in steps */
556 if (node
->runs
[inContext
->mIndex
]->mAllocationCount
% ST_ALLOC_STEP
== 0) {
557 /* Need more space */
558 STAllocation
**allocs
;
561 (STAllocation
**) realloc(node
->runs
[inContext
->mIndex
]->
563 (node
->runs
[inContext
->mIndex
]->
566 sizeof(STAllocation
*));
568 REPORT_ERROR(__LINE__
, categorizeAllocation_No_Memory
);
571 node
->runs
[inContext
->mIndex
]->mAllocations
= allocs
;
573 node
->runs
[inContext
->mIndex
]->mAllocations
[node
->
574 runs
[inContext
->mIndex
]->
575 mAllocationCount
++] =
579 ** Make sure run's stats are calculated. We don't go update the parents of allocation
580 ** at this time. That will happen when we focus on this category. This updating of
581 ** stats will provide us fast categoryreports.
583 recalculateAllocationCost(inOptions
, inContext
,
584 node
->runs
[inContext
->mIndex
], aAllocation
,
587 /* Propagate upwards the statistics */
589 #if defined(DEBUG_dp) && 0
590 fprintf(stderr
, "DEBUG: [%s] match\n", node
->categoryName
);
595 typedef PRBool
STCategoryNodeProcessor(STRequest
* inRequest
,
596 STOptions
* inOptions
,
597 STContext
* inContext
,
599 STCategoryNode
* node
);
602 freeNodeRunProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
603 STContext
* inContext
, void *clientData
,
604 STCategoryNode
* node
)
606 if (node
->runs
&& node
->runs
[inContext
->mIndex
]) {
607 freeRun(node
->runs
[inContext
->mIndex
]);
608 node
->runs
[inContext
->mIndex
] = NULL
;
614 freeNodeRunsProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
615 STContext
* inContext
, void *clientData
,
616 STCategoryNode
* node
)
621 for (loop
= 0; loop
< globals
.mCommandLineOptions
.mContexts
; loop
++) {
622 if (node
->runs
[loop
]) {
623 freeRun(node
->runs
[loop
]);
624 node
->runs
[loop
] = NULL
;
635 #if defined(DEBUG_dp)
637 printNodeProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
638 STContext
* inContext
, void *clientData
,
639 STCategoryNode
* node
)
641 STCategoryNode
*root
= (STCategoryNode
*) clientData
;
643 fprintf(stderr
, "%-25s [ %9s size", node
->categoryName
,
644 FormatNumber(node
->run
? node
->run
->mStats
[inContext
->mIndex
].
646 fprintf(stderr
, ", %5.1f%%",
647 node
->run
? ((double) node
->run
->mStats
[inContext
->mIndex
].mSize
/
648 root
->run
->mStats
[inContext
->mIndex
].mSize
*
650 fprintf(stderr
, ", %7s allocations ]\n",
651 FormatNumber(node
->run
? node
->run
->mStats
[inContext
->mIndex
].
652 mCompositeCount
: 0));
658 typedef struct __struct_optcon
669 ** Compare the nodes as specified by the options.
672 compareNode(const void *aNode1
, const void *aNode2
, void *aContext
)
675 STCategoryNode
*node1
, *node2
;
677 optcon
*oc
= (optcon
*) aContext
;
679 if (!aNode1
|| !aNode2
|| !oc
->mOptions
|| !oc
->mContext
)
682 node1
= *((STCategoryNode
**) aNode1
);
683 node2
= *((STCategoryNode
**) aNode2
);
685 if (node1
&& node2
) {
686 if (oc
->mOptions
->mOrderBy
== ST_COUNT
) {
687 a
= (node1
->runs
[oc
->mContext
->mIndex
]) ? node1
->runs
[oc
->
690 mStats
[oc
->mContext
->mIndex
].mCompositeCount
: 0;
691 b
= (node2
->runs
[oc
->mContext
->mIndex
]) ? node2
->runs
[oc
->
694 mStats
[oc
->mContext
->mIndex
].mCompositeCount
: 0;
697 /* Default is by size */
698 a
= (node1
->runs
[oc
->mContext
->mIndex
]) ? node1
->runs
[oc
->
701 mStats
[oc
->mContext
->mIndex
].mSize
: 0;
702 b
= (node2
->runs
[oc
->mContext
->mIndex
]) ? node2
->runs
[oc
->
705 mStats
[oc
->mContext
->mIndex
].mSize
: 0;
716 sortNodeProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
717 STContext
* inContext
, void *clientData
,
718 STCategoryNode
* node
)
720 if (node
->nchildren
) {
723 context
.mOptions
= inOptions
;
724 context
.mContext
= inContext
;
726 NS_QuickSort(node
->children
, node
->nchildren
,
727 sizeof(STCategoryNode
*), compareNode
, &context
);
737 ** General purpose tree walker. Issues callback for each node.
738 ** If 'maxdepth' > 0, then stops after processing that depth. Root is
739 ** depth 0, the nodes below it are depth 1 etc...
741 #define MODINC(n, mod) ((n+1) % mod)
744 walkTree(STCategoryNode
* root
, STCategoryNodeProcessor func
,
745 STRequest
* inRequest
, STOptions
* inOptions
, STContext
* inContext
,
746 void *clientData
, int maxdepth
)
748 STCategoryNode
*nodes
[1024], *node
;
749 PRUint32 begin
, end
, i
;
751 int curdepth
= 0, ncurdepth
= 0;
757 while (begin
!= end
) {
759 ret
= (*func
) (inRequest
, inOptions
, inContext
, clientData
, node
);
760 if (ret
== PR_FALSE
) {
764 begin
= MODINC(begin
, 1024);
765 for (i
= 0; i
< node
->nchildren
; i
++) {
766 nodes
[end
] = node
->children
[i
];
767 end
= MODINC(end
, 1024);
769 /* Depth tracking. Do it only if walkTree is contrained by a maxdepth */
770 if (maxdepth
> 0 && --ncurdepth
== 0) {
772 ** No more children in current depth. The rest of the nodes
773 ** we have in our list should be nodes in the depth below us.
775 ncurdepth
= (begin
< end
) ? (end
- begin
) : (1024 - begin
+ end
);
776 if (++curdepth
> maxdepth
) {
778 ** Gone too deep. Stop.
788 freeRule(STCategoryRule
* rule
)
791 char *p
= (char *) rule
->categoryName
;
795 for (i
= 0; i
< rule
->npats
; i
++)
803 freeNodeRuns(STCategoryNode
* root
)
805 walkTree(root
, freeNodeRunsProcessor
, NULL
, NULL
, NULL
, NULL
, 0);
809 freeNodeMap(STGlobals
* g
)
813 /* all nodes are in the map table. Just delete all of those. */
814 for (i
= 0; i
< g
->mNCategoryMap
; i
++) {
815 free(g
->mCategoryMap
[i
]->node
);
816 free(g
->mCategoryMap
[i
]);
818 free(g
->mCategoryMap
);
822 freeCategories(STGlobals
* g
)
827 ** walk the tree and free runs held in nodes
829 freeNodeRuns(&g
->mCategoryRoot
);
832 ** delete nodemap. This is the where nodes get deleted.
839 for (i
= 0; i
< g
->mNRules
; i
++) {
840 freeRule(g
->mCategoryRules
[i
]);
842 free(g
->mCategoryRules
);
851 ** categorize all the allocations of the run using the rules into
852 ** a tree rooted at globls.mCategoryRoot
855 categorizeRun(STOptions
* inOptions
, STContext
* inContext
,
856 const STRun
* aRun
, STGlobals
* g
)
860 #if defined(DEBUG_dp)
861 PRIntervalTime start
= PR_IntervalNow();
863 fprintf(stderr
, "DEBUG: categorizing run...\n");
867 ** First, cleanup our tree
869 walkTree(&g
->mCategoryRoot
, freeNodeRunProcessor
, NULL
, inOptions
,
872 if (g
->mNCategoryMap
> 0) {
873 for (i
= 0; i
< aRun
->mAllocationCount
; i
++) {
874 categorizeAllocation(inOptions
, inContext
, aRun
->mAllocations
[i
],
880 ** the run is always going to be the one corresponding to the root node
882 g
->mCategoryRoot
.runs
[inContext
->mIndex
] = (STRun
*) aRun
;
883 g
->mCategoryRoot
.categoryName
= ST_ROOT_CATEGORY_NAME
;
885 #if defined(DEBUG_dp)
887 "DEBUG: categorizing ends: %dms [%d rules, %d allocations]\n",
888 PR_IntervalToMilliseconds(PR_IntervalNow() - start
), g
->mNRules
,
889 aRun
->mAllocationCount
);
890 fprintf(stderr
, "DEBUG: match : %dms [%d calls, %d rule-compares]\n",
891 PR_IntervalToMilliseconds(_gMatchTime
), _gMatchCount
,
896 ** sort the tree based on our sort criterion
898 walkTree(&g
->mCategoryRoot
, sortNodeProcessor
, NULL
, inOptions
, inContext
,
901 #if defined(DEBUG_dp)
902 walkTree(&g
->mCategoryRoot
, printNodeProcessor
, NULL
, inOptions
,
903 inContext
, &g
->mCategoryRoot
, 0);
911 ** displayCategoryReport
913 ** Generate the category report - a list of all categories and details about each
914 ** depth parameter controls how deep we traverse the category tree.
917 displayCategoryNodeProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
918 STContext
* inContext
, void *clientData
,
919 STCategoryNode
* node
)
921 STCategoryNode
*root
= (STCategoryNode
*) clientData
;
922 PRUint32 byteSize
= 0, heapCost
= 0, count
= 0;
926 if (node
->runs
[inContext
->mIndex
]) {
931 node
->runs
[inContext
->mIndex
]->mStats
[inContext
->mIndex
].mSize
;
937 node
->runs
[inContext
->mIndex
]->mStats
[inContext
->mIndex
].
941 ** Heap operation cost
944 node
->runs
[inContext
->mIndex
]->mStats
[inContext
->mIndex
].
950 if (root
->runs
[inContext
->mIndex
]) {
952 ((double) byteSize
) /
953 root
->runs
[inContext
->mIndex
]->mStats
[inContext
->mIndex
].
958 PR_fprintf(inRequest
->mFD
, " <tr>\n" " <td>");
960 /* a link to topcallsites report with focus on category */
961 memcpy(&customOps
, inOptions
, sizeof(customOps
));
962 PR_snprintf(customOps
.mCategoryName
, sizeof(customOps
.mCategoryName
),
963 "%s", node
->categoryName
);
965 htmlAnchor(inRequest
, "top_callsites.html", node
->categoryName
, NULL
,
966 "category-callsites", &customOps
);
967 PR_fprintf(inRequest
->mFD
,
968 "</td>\n" " <td align=right>%u</td>\n"
969 " <td align=right>%4.1f%%</td>\n"
970 " <td align=right>%u</td>\n" " <td align=right>"
971 ST_MICROVAL_FORMAT
"</td>\n" " </tr>\n", byteSize
, percent
,
972 count
, ST_MICROVAL_PRINTABLE(heapCost
));
979 displayCategoryReport(STRequest
* inRequest
, STCategoryNode
* root
, int depth
)
981 PR_fprintf(inRequest
->mFD
,
982 "<table class=\"category-list data\">\n"
983 " <tr class=\"row-header\">\n"
984 " <th>Category</th>\n"
985 " <th>Composite Byte Size</th>\n"
986 " <th>%% of Total Size</th>\n"
987 " <th>Heap Object Count</th>\n"
988 " <th>Composite Heap Operations Seconds</th>\n" " </tr>\n");
990 walkTree(root
, displayCategoryNodeProcessor
, inRequest
,
991 &inRequest
->mOptions
, inRequest
->mContext
, root
, depth
);
993 PR_fprintf(inRequest
->mFD
, "</table>\n");