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 ** Cagtegorizes each allocation using a predefined set of rules
11 ** and presents a tree of categories for browsing.
15 ** Required include files.
17 #include "spacetrace.h"
21 #include "nsQuickSort.h"
23 #if defined(HAVE_BOUTELL_GD)
25 ** See http://www.boutell.com/gd for the GD graphics library.
26 ** Ports for many platorms exist.
27 ** Your box may already have the lib (mine did, redhat 7.1 workstation).
33 #endif /* HAVE_BOUTELL_GD */
38 ** Add a rule into the list of rules maintainted in global
41 AddRule(STGlobals
* g
, STCategoryRule
* rule
)
43 if (g
->mNRules
% ST_ALLOC_STEP
== 0) {
45 STCategoryRule
**newrules
;
47 newrules
= (STCategoryRule
**) realloc(g
->mCategoryRules
,
50 sizeof(STCategoryRule
*));
52 REPORT_ERROR(__LINE__
, AddRule_No_Memory
);
55 g
->mCategoryRules
= newrules
;
57 g
->mCategoryRules
[g
->mNRules
++] = rule
;
64 ** Add the node as a child of the parent node
67 AddChild(STCategoryNode
* parent
, STCategoryNode
* child
)
69 if (parent
->nchildren
% ST_ALLOC_STEP
== 0) {
71 STCategoryNode
**newnodes
;
73 newnodes
= (STCategoryNode
**) realloc(parent
->children
,
76 sizeof(STCategoryNode
*));
78 REPORT_ERROR(__LINE__
, AddChild_No_Memory
);
81 parent
->children
= newnodes
;
83 parent
->children
[parent
->nchildren
++] = child
;
88 Reparent(STCategoryNode
* parent
, STCategoryNode
* child
)
92 if (child
->parent
== parent
)
95 /* Remove child from old parent */
97 for (i
= 0; i
< child
->parent
->nchildren
; i
++) {
98 if (child
->parent
->children
[i
] == child
) {
99 /* Remove child from list */
100 if (i
+ 1 < child
->parent
->nchildren
)
101 memmove(&child
->parent
->children
[i
],
102 &child
->parent
->children
[i
+ 1],
103 (child
->parent
->nchildren
- i
-
104 1) * sizeof(STCategoryNode
*));
105 child
->parent
->nchildren
--;
111 /* Add child into new parent */
112 AddChild(parent
, child
);
120 ** Given a category name, finds the Node corresponding to the category
123 findCategoryNode(const char *catName
, STGlobals
* g
)
127 for (i
= 0; i
< g
->mNCategoryMap
; i
++) {
128 if (!strcmp(g
->mCategoryMap
[i
]->categoryName
, catName
))
129 return g
->mCategoryMap
[i
]->node
;
132 /* Check if we are looking for the root node */
133 if (!strcmp(catName
, ST_ROOT_CATEGORY_NAME
))
134 return &g
->mCategoryRoot
;
142 ** Adds a mapping between a category and its Node into the categoryMap
145 AddCategoryNode(STCategoryNode
* node
, STGlobals
* g
)
147 if (g
->mNCategoryMap
% ST_ALLOC_STEP
== 0) {
148 /* Need more space */
149 STCategoryMapEntry
**newmap
=
150 (STCategoryMapEntry
**) realloc(g
->mCategoryMap
,
153 sizeof(STCategoryMapEntry
*));
155 REPORT_ERROR(__LINE__
, AddCategoryNode_No_Memory
);
158 g
->mCategoryMap
= newmap
;
161 g
->mCategoryMap
[g
->mNCategoryMap
] =
162 (STCategoryMapEntry
*) calloc(1, sizeof(STCategoryMapEntry
));
163 if (!g
->mCategoryMap
[g
->mNCategoryMap
]) {
164 REPORT_ERROR(__LINE__
, AddCategoryNode_No_Memory
);
167 g
->mCategoryMap
[g
->mNCategoryMap
]->categoryName
= node
->categoryName
;
168 g
->mCategoryMap
[g
->mNCategoryMap
]->node
= node
;
176 ** Creates a new category node for category name 'catname' and makes
177 ** 'parent' its parent.
180 NewCategoryNode(const char *catName
, STCategoryNode
* parent
, STGlobals
* g
)
182 STCategoryNode
*node
;
184 node
= (STCategoryNode
*) calloc(1, sizeof(STCategoryNode
));
189 (STRun
**) calloc(g
->mCommandLineOptions
.mContexts
, sizeof(STRun
*));
190 if (NULL
== node
->runs
) {
195 node
->categoryName
= catName
;
197 /* Set parent of child */
198 node
->parent
= parent
;
200 /* Set child in parent */
201 AddChild(parent
, node
);
203 /* Add node into mapping table */
204 AddCategoryNode(node
, g
);
210 ** ProcessCategoryLeafRule
212 ** Add this into the tree as a leaf node. It doesn't know who its parent is. For now we make
213 ** root as its parent
216 ProcessCategoryLeafRule(STCategoryRule
* leafRule
, STCategoryNode
* root
,
219 STCategoryRule
*rule
;
220 STCategoryNode
*node
;
222 rule
= (STCategoryRule
*) calloc(1, sizeof(STCategoryRule
));
226 /* Take ownership of all elements of rule */
229 /* Find/Make a STCategoryNode and add it into the tree */
230 node
= findCategoryNode(rule
->categoryName
, g
);
232 node
= NewCategoryNode(rule
->categoryName
, root
, g
);
234 /* Make sure rule knows which node to access */
237 /* Add rule into rulelist */
244 ** ProcessCategoryParentRule
246 ** Rule has all the children of category as patterns. Sets up the tree so that
247 ** the parent child relationship is honored.
250 ProcessCategoryParentRule(STCategoryRule
* parentRule
, STCategoryNode
* root
,
253 STCategoryNode
*node
;
254 STCategoryNode
*child
;
257 /* Find the parent node in the tree. If not make one and add it into the tree */
258 node
= findCategoryNode(parentRule
->categoryName
, g
);
260 node
= NewCategoryNode(parentRule
->categoryName
, root
, g
);
265 /* For every child node, Find/Create it and make it the child of this node */
266 for (i
= 0; i
< parentRule
->npats
; i
++) {
267 child
= findCategoryNode(parentRule
->pats
[i
], g
);
269 child
= NewCategoryNode(parentRule
->pats
[i
], node
, g
);
274 /* Reparent child to node. This is because when we created the node
275 ** we would have created it as the child of root. Now we need to
276 ** remove it from root's child list and add it into this node
278 Reparent(node
, child
);
288 ** Initialize all categories. This reads in a file that says how to categorize
289 ** each callsite, creates a tree of these categories and makes a list of these
290 ** patterns in order for matching
293 initCategories(STGlobals
* g
)
301 fp
= fopen(g
->mCommandLineOptions
.mCategoryFile
, "r");
303 /* It isn't an error to not have a categories file */
304 REPORT_INFO("No categories file.");
311 memset(&rule
, 0, sizeof(rule
));
313 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
316 if (buf
[n
- 1] == '\n')
324 /* skip empty lines. If we are in a rule, end the rule. */
325 while (*in
&& isspace(*in
))
329 /* End the rule : leaf or non-leaf */
331 ProcessCategoryLeafRule(&rule
, &g
->mCategoryRoot
, g
);
334 ProcessCategoryParentRule(&rule
, &g
->mCategoryRoot
, g
);
336 memset(&rule
, 0, sizeof(rule
));
341 /* if we are in a rule acculumate */
343 rule
.pats
[rule
.npats
] = strdup(in
);
344 rule
.patlen
[rule
.npats
++] = strlen(in
);
346 else if (*in
== '<') {
347 /* Start a category */
351 /* Get the category name */
354 if (in
[n
- 1] == '>')
356 rule
.categoryName
= strdup(in
);
359 /* this is a non-leaf category. Should be of the form CategoryName
360 ** followed by list of child category names one per line
364 rule
.categoryName
= strdup(in
);
368 /* If we were in a rule when processing the last line, end the rule */
370 /* End the rule : leaf or non-leaf */
372 ProcessCategoryLeafRule(&rule
, &g
->mCategoryRoot
, g
);
375 ProcessCategoryParentRule(&rule
, &g
->mCategoryRoot
, g
);
378 /* Add the final "uncategorized" category. We make new memory locations
379 ** for all these to conform to the general principle of all strings are allocated
380 ** so it makes release logic very simple.
382 memset(&rule
, 0, sizeof(rule
));
383 rule
.categoryName
= strdup("uncategorized");
384 rule
.pats
[0] = strdup("");
387 ProcessCategoryLeafRule(&rule
, &g
->mCategoryRoot
, g
);
393 ** callsiteMatchesRule
395 ** Returns the corresponding node if callsite matches the rule. Rule is a sequence
396 ** of patterns that must match contiguously the callsite.
399 callsiteMatchesRule(tmcallsite
* aCallsite
, STCategoryRule
* aRule
)
402 const char *methodName
= NULL
;
404 while (patnum
< aRule
->npats
&& aCallsite
&& aCallsite
->method
) {
405 methodName
= tmmethodnode_name(aCallsite
->method
);
408 if (!*aRule
->pats
[patnum
]
409 || !strncmp(methodName
, aRule
->pats
[patnum
],
410 aRule
->patlen
[patnum
])) {
411 /* We have matched so far. Proceed up the stack and to the next pattern */
413 aCallsite
= aCallsite
->parent
;
416 /* Deal with mismatch */
418 /* contiguous mismatch. Stop */
421 /* We still haven't matched the first pattern. Proceed up the stack without
422 ** moving to the next pattern.
424 aCallsite
= aCallsite
->parent
;
428 if (patnum
== aRule
->npats
) {
429 /* all patterns matched. We have a winner. */
430 #if defined(DEBUG_dp) && 0
431 fprintf(stderr
, "[%s] match\n", aRule
->categoryName
);
440 PRIntervalTime _gMatchTime
= 0;
441 uint32_t _gMatchCount
= 0;
442 uint32_t _gMatchRules
= 0;
448 ** Runs through all rules and returns the node corresponding to
449 ** a match of the allocation.
452 matchAllocation(STGlobals
* g
, STAllocation
* aAllocation
)
455 PRIntervalTime start
= PR_IntervalNow();
458 STCategoryNode
*node
= NULL
;
459 STCategoryRule
*rule
;
461 for (rulenum
= 0; rulenum
< g
->mNRules
; rulenum
++) {
465 rule
= g
->mCategoryRules
[rulenum
];
466 if (callsiteMatchesRule(aAllocation
->mEvents
[0].mCallsite
, rule
)) {
473 _gMatchTime
+= PR_IntervalNow() - start
;
479 ** categorizeAllocation
481 ** Given an allocation, it adds it into the category tree at the right spot
482 ** by comparing the allocation to the rules and adding into the right node.
483 ** Also, does propogation of cost upwards in the tree.
484 ** The root of the tree is in the globls as the tree is dependent on the
485 ** category file (options) rather than the run.
488 categorizeAllocation(STOptions
* inOptions
, STContext
* inContext
,
489 STAllocation
* aAllocation
, STGlobals
* g
)
491 /* Run through the rules in order to see if this allcation matches
494 STCategoryNode
*node
;
496 node
= matchAllocation(g
, aAllocation
);
498 /* ugh! it should atleast go into the "uncategorized" node. wierd!
500 REPORT_ERROR(__LINE__
, categorizeAllocation
);
504 /* Create run for node if not already */
505 if (!node
->runs
[inContext
->mIndex
]) {
507 ** Create run with positive timestamp as we can harvest it later
508 ** for callsite details summarization
510 node
->runs
[inContext
->mIndex
] =
511 createRun(inContext
, PR_IntervalNow());
512 if (!node
->runs
[inContext
->mIndex
]) {
513 REPORT_ERROR(__LINE__
, categorizeAllocation_No_Memory
);
518 /* Add allocation into node. We expand the table of allocations in steps */
519 if (node
->runs
[inContext
->mIndex
]->mAllocationCount
% ST_ALLOC_STEP
== 0) {
520 /* Need more space */
521 STAllocation
**allocs
;
524 (STAllocation
**) realloc(node
->runs
[inContext
->mIndex
]->
526 (node
->runs
[inContext
->mIndex
]->
529 sizeof(STAllocation
*));
531 REPORT_ERROR(__LINE__
, categorizeAllocation_No_Memory
);
534 node
->runs
[inContext
->mIndex
]->mAllocations
= allocs
;
536 node
->runs
[inContext
->mIndex
]->mAllocations
[node
->
537 runs
[inContext
->mIndex
]->
538 mAllocationCount
++] =
542 ** Make sure run's stats are calculated. We don't go update the parents of allocation
543 ** at this time. That will happen when we focus on this category. This updating of
544 ** stats will provide us fast categoryreports.
546 recalculateAllocationCost(inOptions
, inContext
,
547 node
->runs
[inContext
->mIndex
], aAllocation
,
550 /* Propagate upwards the statistics */
552 #if defined(DEBUG_dp) && 0
553 fprintf(stderr
, "DEBUG: [%s] match\n", node
->categoryName
);
558 typedef PRBool
STCategoryNodeProcessor(STRequest
* inRequest
,
559 STOptions
* inOptions
,
560 STContext
* inContext
,
562 STCategoryNode
* node
);
565 freeNodeRunProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
566 STContext
* inContext
, void *clientData
,
567 STCategoryNode
* node
)
569 if (node
->runs
&& node
->runs
[inContext
->mIndex
]) {
570 freeRun(node
->runs
[inContext
->mIndex
]);
571 node
->runs
[inContext
->mIndex
] = NULL
;
577 freeNodeRunsProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
578 STContext
* inContext
, void *clientData
,
579 STCategoryNode
* node
)
584 for (loop
= 0; loop
< globals
.mCommandLineOptions
.mContexts
; loop
++) {
585 if (node
->runs
[loop
]) {
586 freeRun(node
->runs
[loop
]);
587 node
->runs
[loop
] = NULL
;
598 #if defined(DEBUG_dp)
600 printNodeProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
601 STContext
* inContext
, void *clientData
,
602 STCategoryNode
* node
)
604 STCategoryNode
*root
= (STCategoryNode
*) clientData
;
606 fprintf(stderr
, "%-25s [ %9s size", node
->categoryName
,
607 FormatNumber(node
->run
? node
->run
->mStats
[inContext
->mIndex
].
609 fprintf(stderr
, ", %5.1f%%",
610 node
->run
? ((double) node
->run
->mStats
[inContext
->mIndex
].mSize
/
611 root
->run
->mStats
[inContext
->mIndex
].mSize
*
613 fprintf(stderr
, ", %7s allocations ]\n",
614 FormatNumber(node
->run
? node
->run
->mStats
[inContext
->mIndex
].
615 mCompositeCount
: 0));
621 typedef struct __struct_optcon
632 ** Compare the nodes as specified by the options.
635 compareNode(const void *aNode1
, const void *aNode2
, void *aContext
)
638 STCategoryNode
*node1
, *node2
;
640 optcon
*oc
= (optcon
*) aContext
;
642 if (!aNode1
|| !aNode2
|| !oc
->mOptions
|| !oc
->mContext
)
645 node1
= *((STCategoryNode
**) aNode1
);
646 node2
= *((STCategoryNode
**) aNode2
);
648 if (node1
&& node2
) {
649 if (oc
->mOptions
->mOrderBy
== ST_COUNT
) {
650 a
= (node1
->runs
[oc
->mContext
->mIndex
]) ? node1
->runs
[oc
->
653 mStats
[oc
->mContext
->mIndex
].mCompositeCount
: 0;
654 b
= (node2
->runs
[oc
->mContext
->mIndex
]) ? node2
->runs
[oc
->
657 mStats
[oc
->mContext
->mIndex
].mCompositeCount
: 0;
660 /* Default is by size */
661 a
= (node1
->runs
[oc
->mContext
->mIndex
]) ? node1
->runs
[oc
->
664 mStats
[oc
->mContext
->mIndex
].mSize
: 0;
665 b
= (node2
->runs
[oc
->mContext
->mIndex
]) ? node2
->runs
[oc
->
668 mStats
[oc
->mContext
->mIndex
].mSize
: 0;
679 sortNodeProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
680 STContext
* inContext
, void *clientData
,
681 STCategoryNode
* node
)
683 if (node
->nchildren
) {
686 context
.mOptions
= inOptions
;
687 context
.mContext
= inContext
;
689 NS_QuickSort(node
->children
, node
->nchildren
,
690 sizeof(STCategoryNode
*), compareNode
, &context
);
700 ** General purpose tree walker. Issues callback for each node.
701 ** If 'maxdepth' > 0, then stops after processing that depth. Root is
702 ** depth 0, the nodes below it are depth 1 etc...
704 #define MODINC(n, mod) ((n+1) % mod)
707 walkTree(STCategoryNode
* root
, STCategoryNodeProcessor func
,
708 STRequest
* inRequest
, STOptions
* inOptions
, STContext
* inContext
,
709 void *clientData
, int maxdepth
)
711 STCategoryNode
*nodes
[1024], *node
;
712 uint32_t begin
, end
, i
;
714 int curdepth
= 0, ncurdepth
= 0;
720 while (begin
!= end
) {
722 ret
= (*func
) (inRequest
, inOptions
, inContext
, clientData
, node
);
727 begin
= MODINC(begin
, 1024);
728 for (i
= 0; i
< node
->nchildren
; i
++) {
729 nodes
[end
] = node
->children
[i
];
730 end
= MODINC(end
, 1024);
732 /* Depth tracking. Do it only if walkTree is contrained by a maxdepth */
733 if (maxdepth
> 0 && --ncurdepth
== 0) {
735 ** No more children in current depth. The rest of the nodes
736 ** we have in our list should be nodes in the depth below us.
738 ncurdepth
= (begin
< end
) ? (end
- begin
) : (1024 - begin
+ end
);
739 if (++curdepth
> maxdepth
) {
741 ** Gone too deep. Stop.
751 freeRule(STCategoryRule
* rule
)
754 char *p
= (char *) rule
->categoryName
;
758 for (i
= 0; i
< rule
->npats
; i
++)
766 freeNodeRuns(STCategoryNode
* root
)
768 walkTree(root
, freeNodeRunsProcessor
, NULL
, NULL
, NULL
, NULL
, 0);
772 freeNodeMap(STGlobals
* g
)
776 /* all nodes are in the map table. Just delete all of those. */
777 for (i
= 0; i
< g
->mNCategoryMap
; i
++) {
778 free(g
->mCategoryMap
[i
]->node
);
779 free(g
->mCategoryMap
[i
]);
781 free(g
->mCategoryMap
);
785 freeCategories(STGlobals
* g
)
790 ** walk the tree and free runs held in nodes
792 freeNodeRuns(&g
->mCategoryRoot
);
795 ** delete nodemap. This is the where nodes get deleted.
802 for (i
= 0; i
< g
->mNRules
; i
++) {
803 freeRule(g
->mCategoryRules
[i
]);
805 free(g
->mCategoryRules
);
814 ** categorize all the allocations of the run using the rules into
815 ** a tree rooted at globls.mCategoryRoot
818 categorizeRun(STOptions
* inOptions
, STContext
* inContext
,
819 const STRun
* aRun
, STGlobals
* g
)
823 #if defined(DEBUG_dp)
824 PRIntervalTime start
= PR_IntervalNow();
826 fprintf(stderr
, "DEBUG: categorizing run...\n");
830 ** First, cleanup our tree
832 walkTree(&g
->mCategoryRoot
, freeNodeRunProcessor
, NULL
, inOptions
,
835 if (g
->mNCategoryMap
> 0) {
836 for (i
= 0; i
< aRun
->mAllocationCount
; i
++) {
837 categorizeAllocation(inOptions
, inContext
, aRun
->mAllocations
[i
],
843 ** the run is always going to be the one corresponding to the root node
845 g
->mCategoryRoot
.runs
[inContext
->mIndex
] = (STRun
*) aRun
;
846 g
->mCategoryRoot
.categoryName
= ST_ROOT_CATEGORY_NAME
;
848 #if defined(DEBUG_dp)
850 "DEBUG: categorizing ends: %dms [%d rules, %d allocations]\n",
851 PR_IntervalToMilliseconds(PR_IntervalNow() - start
), g
->mNRules
,
852 aRun
->mAllocationCount
);
853 fprintf(stderr
, "DEBUG: match : %dms [%d calls, %d rule-compares]\n",
854 PR_IntervalToMilliseconds(_gMatchTime
), _gMatchCount
,
859 ** sort the tree based on our sort criterion
861 walkTree(&g
->mCategoryRoot
, sortNodeProcessor
, NULL
, inOptions
, inContext
,
864 #if defined(DEBUG_dp)
865 walkTree(&g
->mCategoryRoot
, printNodeProcessor
, NULL
, inOptions
,
866 inContext
, &g
->mCategoryRoot
, 0);
874 ** displayCategoryReport
876 ** Generate the category report - a list of all categories and details about each
877 ** depth parameter controls how deep we traverse the category tree.
880 displayCategoryNodeProcessor(STRequest
* inRequest
, STOptions
* inOptions
,
881 STContext
* inContext
, void *clientData
,
882 STCategoryNode
* node
)
884 STCategoryNode
*root
= (STCategoryNode
*) clientData
;
885 uint32_t byteSize
= 0, heapCost
= 0, count
= 0;
889 if (node
->runs
[inContext
->mIndex
]) {
894 node
->runs
[inContext
->mIndex
]->mStats
[inContext
->mIndex
].mSize
;
900 node
->runs
[inContext
->mIndex
]->mStats
[inContext
->mIndex
].
904 ** Heap operation cost
907 node
->runs
[inContext
->mIndex
]->mStats
[inContext
->mIndex
].
913 if (root
->runs
[inContext
->mIndex
]) {
915 ((double) byteSize
) /
916 root
->runs
[inContext
->mIndex
]->mStats
[inContext
->mIndex
].
921 PR_fprintf(inRequest
->mFD
, " <tr>\n" " <td>");
923 /* a link to topcallsites report with focus on category */
924 memcpy(&customOps
, inOptions
, sizeof(customOps
));
925 PR_snprintf(customOps
.mCategoryName
, sizeof(customOps
.mCategoryName
),
926 "%s", node
->categoryName
);
928 htmlAnchor(inRequest
, "top_callsites.html", node
->categoryName
, NULL
,
929 "category-callsites", &customOps
);
930 PR_fprintf(inRequest
->mFD
,
931 "</td>\n" " <td align=right>%u</td>\n"
932 " <td align=right>%4.1f%%</td>\n"
933 " <td align=right>%u</td>\n" " <td align=right>"
934 ST_MICROVAL_FORMAT
"</td>\n" " </tr>\n", byteSize
, percent
,
935 count
, ST_MICROVAL_PRINTABLE(heapCost
));
942 displayCategoryReport(STRequest
* inRequest
, STCategoryNode
* root
, int depth
)
944 PR_fprintf(inRequest
->mFD
,
945 "<table class=\"category-list data\">\n"
946 " <tr class=\"row-header\">\n"
947 " <th>Category</th>\n"
948 " <th>Composite Byte Size</th>\n"
949 " <th>%% of Total Size</th>\n"
950 " <th>Heap Object Count</th>\n"
951 " <th>Composite Heap Operations Seconds</th>\n" " </tr>\n");
953 walkTree(root
, displayCategoryNodeProcessor
, inRequest
,
954 &inRequest
->mOptions
, inRequest
->mContext
, root
, depth
);
956 PR_fprintf(inRequest
->mFD
, "</table>\n");