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 nsTraceMalloc.c/bloatblame.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) 2000
22 * the Initial Developer. All Rights Reserved.
25 * Brendan Eich, 14-April-2000
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 ***** */
51 extern int getopt(int argc
, char *const *argv
, const char *shortopts
);
64 #include "nsTraceMalloc.h"
68 static int sort_by_direct
= 0;
69 static int js_mode
= 0;
70 static int do_tree_dump
= 0;
71 static int unified_output
= 0;
72 static char *function_dump
= NULL
;
73 static uint32 min_subtotal
= 0;
75 static void compute_callsite_totals(tmcallsite
*site
)
79 site
->allocs
.bytes
.total
+= site
->allocs
.bytes
.direct
;
80 site
->allocs
.calls
.total
+= site
->allocs
.calls
.direct
;
81 for (kid
= site
->kids
; kid
; kid
= kid
->siblings
) {
82 compute_callsite_totals(kid
);
83 site
->allocs
.bytes
.total
+= kid
->allocs
.bytes
.total
;
84 site
->allocs
.calls
.total
+= kid
->allocs
.calls
.total
;
88 static void walk_callsite_tree(tmcallsite
*site
, int level
, int kidnum
, FILE *fp
)
91 tmgraphnode
*comp
, *pcomp
, *lib
, *plib
;
92 tmmethodnode
*meth
, *pmeth
;
93 int old_meth_low
, old_comp_low
, old_lib_low
, nkids
;
96 parent
= site
->parent
;
102 pmeth
= parent
->method
;
103 if (pmeth
&& pmeth
!= meth
) {
104 if (!meth
->graphnode
.low
) {
105 meth
->graphnode
.allocs
.bytes
.total
+= site
->allocs
.bytes
.total
;
106 meth
->graphnode
.allocs
.calls
.total
+= site
->allocs
.calls
.total
;
108 if (!tmgraphnode_connect(&(pmeth
->graphnode
), &(meth
->graphnode
), site
))
111 comp
= meth
->graphnode
.up
;
113 pcomp
= pmeth
->graphnode
.up
;
114 if (pcomp
&& pcomp
!= comp
) {
116 comp
->allocs
.bytes
.total
117 += site
->allocs
.bytes
.total
;
118 comp
->allocs
.calls
.total
119 += site
->allocs
.calls
.total
;
121 if (!tmgraphnode_connect(pcomp
, comp
, site
))
127 if (plib
&& plib
!= lib
) {
129 lib
->allocs
.bytes
.total
130 += site
->allocs
.bytes
.total
;
131 lib
->allocs
.calls
.total
132 += site
->allocs
.calls
.total
;
134 if (!tmgraphnode_connect(plib
, lib
, site
))
137 old_lib_low
= lib
->low
;
142 old_comp_low
= comp
->low
;
147 old_meth_low
= meth
->graphnode
.low
;
149 meth
->graphnode
.low
= level
;
154 fprintf(fp
, "%c%*s%3d %3d %s %lu %ld\n",
155 site
->kids
? '+' : '-', level
, "", level
, kidnum
,
156 meth
? tmmethodnode_name(meth
) : "???",
157 (unsigned long)site
->allocs
.bytes
.direct
,
158 (long)site
->allocs
.bytes
.total
);
162 for (kid
= site
->kids
; kid
; kid
= kid
->siblings
) {
163 walk_callsite_tree(kid
, level
, nkids
, fp
);
169 meth
->graphnode
.low
= 0;
187 * Linked list bubble-sort (waterson and brendan went bald hacking this).
189 * Sort the list in non-increasing order, using the expression passed as the
190 * 'lessthan' formal macro parameter. This expression should use 'curr' as
191 * the pointer to the current node (of type nodetype) and 'next' as the next
192 * node pointer. It should return true if curr is less than next, and false
195 #define BUBBLE_SORT_LINKED_LIST(listp, nodetype, lessthan) \
197 nodetype *curr, **currp, *next, **nextp, *tmp; \
200 while ((curr = *currp) != NULL && curr->next) { \
201 nextp = &curr->next; \
202 while ((next = *nextp) != NULL) { \
207 PR_ASSERT(nextp == &curr->next); \
208 curr->next = next->next; \
211 *nextp = next->next; \
212 curr->next = next->next; \
216 nextp = &curr->next; \
221 nextp = &next->next; \
223 currp = &curr->next; \
227 static PRIntn
tabulate_node(PLHashEntry
*he
, PRIntn i
, void *arg
)
229 tmgraphnode
*node
= (tmgraphnode
*) he
;
230 tmgraphnode
**table
= (tmgraphnode
**) arg
;
233 BUBBLE_SORT_LINKED_LIST(&node
->down
, tmgraphnode
,
234 (curr
->allocs
.bytes
.total
< next
->allocs
.bytes
.total
));
235 return HT_ENUMERATE_NEXT
;
238 /* Sort in reverse size order, so biggest node comes first. */
239 static int node_table_compare(const void *p1
, const void *p2
)
241 const tmgraphnode
*node1
, *node2
;
244 node1
= *(const tmgraphnode
**) p1
;
245 node2
= *(const tmgraphnode
**) p2
;
246 if (sort_by_direct
) {
247 key1
= node1
->allocs
.bytes
.direct
;
248 key2
= node2
->allocs
.bytes
.direct
;
250 key1
= node1
->allocs
.bytes
.total
;
251 key2
= node2
->allocs
.bytes
.total
;
253 return (key2
< key1
) ? -1 : (key2
> key1
) ? 1 : 0;
256 static int mean_size_compare(const void *p1
, const void *p2
)
258 const tmgraphnode
*node1
, *node2
;
259 double div1
, div2
, key1
, key2
;
261 node1
= *(const tmgraphnode
**) p1
;
262 node2
= *(const tmgraphnode
**) p2
;
263 div1
= (double)node1
->allocs
.calls
.direct
;
264 div2
= (double)node2
->allocs
.calls
.direct
;
265 if (div1
== 0 || div2
== 0)
266 return (int)(div2
- div1
);
267 key1
= (double)node1
->allocs
.bytes
.direct
/ div1
;
268 key2
= (double)node2
->allocs
.bytes
.direct
/ div2
;
276 static const char *prettybig(uint32 num
, char *buf
, size_t limit
)
278 if (num
>= 1000000000)
279 PR_snprintf(buf
, limit
, "%1.2fG", (double) num
/ 1e9
);
280 else if (num
>= 1000000)
281 PR_snprintf(buf
, limit
, "%1.2fM", (double) num
/ 1e6
);
282 else if (num
>= 1000)
283 PR_snprintf(buf
, limit
, "%1.2fK", (double) num
/ 1e3
);
285 PR_snprintf(buf
, limit
, "%lu", (unsigned long) num
);
289 static double percent(uint32 num
, uint32 total
)
293 return ((double) num
* 100) / (double) total
;
296 static void sort_graphlink_list(tmgraphlink
**listp
, int which
)
298 BUBBLE_SORT_LINKED_LIST(listp
, tmgraphlink
,
299 (TM_LINK_TO_EDGE(curr
, which
)->allocs
.bytes
.total
300 < TM_LINK_TO_EDGE(next
, which
)->allocs
.bytes
.total
));
303 static void dump_graphlink_list(tmgraphlink
*list
, int which
, const char *name
,
311 bytes
.direct
= bytes
.total
= 0;
312 for (link
= list
; link
; link
= link
->next
) {
313 edge
= TM_LINK_TO_EDGE(link
, which
);
314 bytes
.direct
+= edge
->allocs
.bytes
.direct
;
315 bytes
.total
+= edge
->allocs
.bytes
.total
;
320 " %s:{dbytes:%ld, tbytes:%ld, edges:[\n",
321 name
, (long) bytes
.direct
, (long) bytes
.total
);
322 for (link
= list
; link
; link
= link
->next
) {
323 edge
= TM_LINK_TO_EDGE(link
, which
);
325 " {node:%d, dbytes:%ld, tbytes:%ld},\n",
327 (long) edge
->allocs
.bytes
.direct
,
328 (long) edge
->allocs
.bytes
.total
);
332 fputs("<td valign=top>", fp
);
333 for (link
= list
; link
; link
= link
->next
) {
334 edge
= TM_LINK_TO_EDGE(link
, which
);
336 "<a href='#%s'>%s (%1.2f%%)</a>\n",
337 tmgraphnode_name(link
->node
),
338 prettybig(edge
->allocs
.bytes
.total
, buf
, sizeof buf
),
339 percent(edge
->allocs
.bytes
.total
, bytes
.total
));
345 static void dump_graph(tmreader
*tmr
, PLHashTable
*hashtbl
, const char *varname
,
346 const char *title
, FILE *fp
)
349 tmgraphnode
**table
, *node
;
352 char buf1
[16], buf2
[16], buf3
[16], buf4
[16];
353 static char NA
[] = "N/A";
355 count
= hashtbl
->nentries
;
356 table
= (tmgraphnode
**) malloc(count
* sizeof(tmgraphnode
*));
361 PL_HashTableEnumerateEntries(hashtbl
, tabulate_node
, table
);
362 qsort(table
, count
, sizeof(tmgraphnode
*), node_table_compare
);
363 for (i
= 0; i
< count
; i
++)
368 "var %s = {\n name:'%s', title:'%s', nodes:[\n",
369 varname
, varname
, title
);
377 "<th>Total/Direct (percents)</th>"
378 "<th>Allocations</th>"
385 for (i
= 0; i
< count
; i
++) {
386 /* Don't bother with truly puny nodes. */
388 if (node
->allocs
.bytes
.total
< min_subtotal
)
391 name
= tmgraphnode_name(node
);
394 " {name:'%s', dbytes:%ld, tbytes:%ld,"
395 " dallocs:%ld, tallocs:%ld,\n",
397 (long) node
->allocs
.bytes
.direct
,
398 (long) node
->allocs
.bytes
.total
,
399 (long) node
->allocs
.calls
.direct
,
400 (long) node
->allocs
.calls
.total
);
402 namelen
= strlen(name
);
405 "<td valign=top><a name='%s'>%.*s%s</a></td>",
407 (namelen
> 40) ? 40 : (int)namelen
, name
,
408 (namelen
> 40) ? "<i>...</i>" : "");
411 "<td valign=top><a href='#%s'><i>down</i></a></td>",
412 tmgraphnode_name(node
->down
));
414 fputs("<td></td>", fp
);
418 "<td valign=top><a href='#%s'><i>next</i></a></td>",
419 tmgraphnode_name(node
->next
));
421 fputs("<td></td>", fp
);
424 "<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>"
425 "<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>",
426 prettybig(node
->allocs
.bytes
.total
, buf1
, sizeof buf1
),
427 prettybig(node
->allocs
.bytes
.direct
, buf2
, sizeof buf2
),
428 percent(node
->allocs
.bytes
.total
,
429 tmr
->calltree_root
.allocs
.bytes
.total
),
430 percent(node
->allocs
.bytes
.direct
,
431 tmr
->calltree_root
.allocs
.bytes
.total
),
432 prettybig(node
->allocs
.calls
.total
, buf3
, sizeof buf3
),
433 prettybig(node
->allocs
.calls
.direct
, buf4
, sizeof buf4
),
434 percent(node
->allocs
.calls
.total
,
435 tmr
->calltree_root
.allocs
.calls
.total
),
436 percent(node
->allocs
.calls
.direct
,
437 tmr
->calltree_root
.allocs
.calls
.total
));
440 /* NB: we must use 'fin' because 'in' is a JS keyword! */
441 sort_graphlink_list(&node
->in
, TM_EDGE_IN_LINK
);
442 dump_graphlink_list(node
->in
, TM_EDGE_IN_LINK
, "fin", fp
);
443 sort_graphlink_list(&node
->out
, TM_EDGE_OUT_LINK
);
444 dump_graphlink_list(node
->out
, TM_EDGE_OUT_LINK
, "out", fp
);
449 fputs("</tr>\n", fp
);
455 fputs("</table>\n<hr>\n", fp
);
457 qsort(table
, count
, sizeof(tmgraphnode
*), mean_size_compare
);
461 "<tr><th colspan=4>Direct Allocators</th></tr>\n"
464 "<th>Mean Size</th>"
466 "<th>Allocations<th>"
470 for (i
= 0; i
< count
; i
++) {
471 double allocs
, bytes
, mean
, variance
, sigma
;
474 allocs
= (double)node
->allocs
.calls
.direct
;
478 /* Compute direct-size mean and standard deviation. */
479 bytes
= (double)node
->allocs
.bytes
.direct
;
480 mean
= bytes
/ allocs
;
481 variance
= allocs
* node
->sqsum
- bytes
* bytes
;
482 if (variance
< 0 || allocs
== 1)
485 variance
/= allocs
* (allocs
- 1);
486 sigma
= sqrt(variance
);
488 name
= tmgraphnode_name(node
);
489 namelen
= strlen(name
);
492 "<td valign=top>%.*s%s</td>"
493 "<td valign=top>%s</td>"
494 "<td valign=top>%s</td>"
495 "<td valign=top>%s</td>"
497 (namelen
> 65) ? 45 : (int)namelen
, name
,
498 (namelen
> 65) ? "<i>...</i>" : "",
499 prettybig((uint32
)mean
, buf1
, sizeof buf1
),
500 prettybig((uint32
)sigma
, buf2
, sizeof buf2
),
501 prettybig(node
->allocs
.calls
.direct
, buf3
, sizeof buf3
));
503 fputs("</table>\n", fp
);
509 static void my_tmevent_handler(tmreader
*tmr
, tmevent
*event
)
511 switch (event
->type
) {
516 "<p><table border=1>"
517 "<tr><th>Counter</th><th>Value</th></tr>\n"
518 "<tr><td>maximum actual stack depth</td><td align=right>%lu</td></tr>\n"
519 "<tr><td>maximum callsite tree depth</td><td align=right>%lu</td></tr>\n"
520 "<tr><td>number of parent callsites</td><td align=right>%lu</td></tr>\n"
521 "<tr><td>maximum kids per parent</td><td align=right>%lu</td></tr>\n"
522 "<tr><td>hits looking for a kid</td><td align=right>%lu</td></tr>\n"
523 "<tr><td>misses looking for a kid</td><td align=right>%lu</td></tr>\n"
524 "<tr><td>steps over other kids</td><td align=right>%lu</td></tr>\n"
525 "<tr><td>callsite recurrences</td><td align=right>%lu</td></tr>\n"
526 "<tr><td>number of stack backtraces</td><td align=right>%lu</td></tr>\n"
527 "<tr><td>backtrace failures</td><td align=right>%lu</td></tr>\n"
528 "<tr><td>backtrace malloc failures</td><td align=right>%lu</td></tr>\n"
529 "<tr><td>backtrace dladdr failures</td><td align=right>%lu</td></tr>\n"
530 "<tr><td>malloc calls</td><td align=right>%lu</td></tr>\n"
531 "<tr><td>malloc failures</td><td align=right>%lu</td></tr>\n"
532 "<tr><td>calloc calls</td><td align=right>%lu</td></tr>\n"
533 "<tr><td>calloc failures</td><td align=right>%lu</td></tr>\n"
534 "<tr><td>realloc calls</td><td align=right>%lu</td></tr>\n"
535 "<tr><td>realloc failures</td><td align=right>%lu</td></tr>\n"
536 "<tr><td>free calls</td><td align=right>%lu</td></tr>\n"
537 "<tr><td>free(null) calls</td><td align=right>%lu</td></tr>\n"
539 (unsigned long) event
->u
.stats
.tmstats
.calltree_maxstack
,
540 (unsigned long) event
->u
.stats
.tmstats
.calltree_maxdepth
,
541 (unsigned long) event
->u
.stats
.tmstats
.calltree_parents
,
542 (unsigned long) event
->u
.stats
.tmstats
.calltree_maxkids
,
543 (unsigned long) event
->u
.stats
.tmstats
.calltree_kidhits
,
544 (unsigned long) event
->u
.stats
.tmstats
.calltree_kidmisses
,
545 (unsigned long) event
->u
.stats
.tmstats
.calltree_kidsteps
,
546 (unsigned long) event
->u
.stats
.tmstats
.callsite_recurrences
,
547 (unsigned long) event
->u
.stats
.tmstats
.backtrace_calls
,
548 (unsigned long) event
->u
.stats
.tmstats
.backtrace_failures
,
549 (unsigned long) event
->u
.stats
.tmstats
.btmalloc_failures
,
550 (unsigned long) event
->u
.stats
.tmstats
.dladdr_failures
,
551 (unsigned long) event
->u
.stats
.tmstats
.malloc_calls
,
552 (unsigned long) event
->u
.stats
.tmstats
.malloc_failures
,
553 (unsigned long) event
->u
.stats
.tmstats
.calloc_calls
,
554 (unsigned long) event
->u
.stats
.tmstats
.calloc_failures
,
555 (unsigned long) event
->u
.stats
.tmstats
.realloc_calls
,
556 (unsigned long) event
->u
.stats
.tmstats
.realloc_failures
,
557 (unsigned long) event
->u
.stats
.tmstats
.free_calls
,
558 (unsigned long) event
->u
.stats
.tmstats
.null_free_calls
);
560 if (event
->u
.stats
.calltree_maxkids_parent
) {
562 tmreader_callsite(tmr
, event
->u
.stats
.calltree_maxkids_parent
);
563 if (site
&& site
->method
) {
564 fprintf(stdout
, "<p>callsite with the most kids: %s</p>",
565 tmmethodnode_name(site
->method
));
569 if (event
->u
.stats
.calltree_maxstack_top
) {
571 tmreader_callsite(tmr
, event
->u
.stats
.calltree_maxstack_top
);
572 fputs("<p>deepest callsite tree path:\n"
574 "<tr><th>Method</th><th>Offset</th></tr>\n",
578 "<tr><td>%s</td><td>0x%08lX</td></tr>\n",
579 site
->method
? tmmethodnode_name(site
->method
) : "???",
580 (unsigned long) site
->offset
);
583 fputs("</table>\n<hr>\n", stdout
);
589 int main(int argc
, char **argv
)
596 tmr
= tmreader_new(program
, NULL
);
602 while ((c
= getopt(argc
, argv
, "djtuf:m:")) != EOF
) {
617 function_dump
= optarg
;
620 min_subtotal
= atoi(optarg
);
624 "usage: %s [-dtu] [-f function-dump-filename] [-m min] [output.html]\n",
631 time_t start
= time(NULL
);
634 "<script language=\"JavaScript\">\n"
635 "function onload() {\n"
636 " document.links[0].__proto__.onmouseover = new Function("
638 " this.href.substring(this.href.lastIndexOf('#') + 1)\");\n"
641 fprintf(stdout
, "%s starting at %s", program
, ctime(&start
));
648 if (tmreader_eventloop(tmr
, "-", my_tmevent_handler
) <= 0)
651 for (i
= j
= 0; i
< argc
; i
++) {
652 fp
= fopen(argv
[i
], "r");
654 fprintf(stderr
, "%s: can't open %s: %s\n",
655 program
, argv
[i
], strerror(errno
));
658 rv
= tmreader_eventloop(tmr
, argv
[i
], my_tmevent_handler
);
669 compute_callsite_totals(&tmr
->calltree_root
);
670 walk_callsite_tree(&tmr
->calltree_root
, 0, 0, stdout
);
674 "<script language='javascript'>\n"
675 "// direct and total byte and allocator-call counts\n"
676 "var dbytes = %ld, tbytes = %ld,"
677 " dallocs = %ld, tallocs = %ld;\n",
678 (long) tmr
->calltree_root
.allocs
.bytes
.direct
,
679 (long) tmr
->calltree_root
.allocs
.bytes
.total
,
680 (long) tmr
->calltree_root
.allocs
.calls
.direct
,
681 (long) tmr
->calltree_root
.allocs
.calls
.total
);
684 dump_graph(tmr
, tmr
->libraries
, "libraries", "Library", stdout
);
686 fputs("<hr>\n", stdout
);
688 dump_graph(tmr
, tmr
->components
, "classes", "Class or Component", stdout
);
689 if (js_mode
|| unified_output
|| function_dump
) {
690 if (js_mode
|| unified_output
|| strcmp(function_dump
, "-") == 0) {
697 fstat(fileno(stdout
), &sb
);
698 if (stat(function_dump
, &fsb
) == 0 &&
699 fsb
.st_dev
== sb
.st_dev
&& fsb
.st_ino
== sb
.st_ino
) {
703 fp
= fopen(function_dump
, "w");
705 fprintf(stderr
, "%s: can't open %s: %s\n",
706 program
, function_dump
, strerror(errno
));
712 dump_graph(tmr
, tmr
->methods
, "methods", "Function or Method", fp
);
717 fputs("function viewnode(graph, index) {\n"
718 " view.location = viewsrc();\n"
720 "function viewnodelink(graph, index) {\n"
721 " var node = graph.nodes[index];\n"
722 " return '<a href=\"javascript:viewnode('"
723 " + graph.name.quote() + ', ' + node.sort"
724 " + ')\" onmouseover=' + node.name.quote() + '>'"
725 " + node.name + '</a>';\n"
727 "function search(expr) {\n"
728 " var re = new RegExp(expr);\n"
730 " var graphs = [libraries, classes, methods]\n"
732 " for (var n = 0; n < (nodes = graphs[n].nodes).length; n++) {\n"
733 " for (var i = 0; i < nodes.length; i++) {\n"
734 " if (re.test(nodes[i].name))\n"
735 " src += viewnodelink(graph, i) + '\\n';\n"
738 " view.location = viewsrc();\n"
740 "function ctrlsrc() {\n"
741 " return \"<form>\\n"
742 "search: <input size=40 onchange='search(this.value)'>\\n"
745 "function viewsrc() {\n"
749 "<frameset rows='10%,*'>\n"
750 " <frame name='ctrl' src='javascript:top.ctrlsrc()'>\n"
751 " <frame name='view' src='javascript:top.viewsrc()'>\n"