|
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * |
|
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/. */ |
|
6 #include <stdio.h> |
|
7 #include <stdlib.h> |
|
8 #include <string.h> |
|
9 #include <ctype.h> |
|
10 #include <errno.h> |
|
11 #ifdef HAVE_GETOPT_H |
|
12 #include <getopt.h> |
|
13 #else |
|
14 #ifdef XP_WIN |
|
15 #include "getopt.c" |
|
16 #else |
|
17 extern int getopt(int argc, char *const *argv, const char *shortopts); |
|
18 extern char *optarg; |
|
19 extern int optind; |
|
20 #endif |
|
21 #endif |
|
22 #include <math.h> |
|
23 #include <time.h> |
|
24 #include <sys/stat.h> |
|
25 #include "prlog.h" |
|
26 #include "prprf.h" |
|
27 #include "plhash.h" |
|
28 #include "pldhash.h" |
|
29 #include "nsTraceMalloc.h" |
|
30 #include "tmreader.h" |
|
31 |
|
32 static char *program; |
|
33 static int sort_by_direct = 0; |
|
34 static int js_mode = 0; |
|
35 static int do_tree_dump = 0; |
|
36 static int unified_output = 0; |
|
37 static char *function_dump = NULL; |
|
38 static uint32_t min_subtotal = 0; |
|
39 |
|
40 static void compute_callsite_totals(tmcallsite *site) |
|
41 { |
|
42 tmcallsite *kid; |
|
43 |
|
44 site->allocs.bytes.total += site->allocs.bytes.direct; |
|
45 site->allocs.calls.total += site->allocs.calls.direct; |
|
46 for (kid = site->kids; kid; kid = kid->siblings) { |
|
47 compute_callsite_totals(kid); |
|
48 site->allocs.bytes.total += kid->allocs.bytes.total; |
|
49 site->allocs.calls.total += kid->allocs.calls.total; |
|
50 } |
|
51 } |
|
52 |
|
53 static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp) |
|
54 { |
|
55 tmcallsite *parent; |
|
56 tmgraphnode *comp, *pcomp, *lib, *plib; |
|
57 tmmethodnode *meth, *pmeth; |
|
58 int old_meth_low, old_comp_low, old_lib_low, nkids; |
|
59 tmcallsite *kid; |
|
60 |
|
61 parent = site->parent; |
|
62 comp = lib = NULL; |
|
63 meth = NULL; |
|
64 if (parent) { |
|
65 meth = site->method; |
|
66 if (meth) { |
|
67 pmeth = parent->method; |
|
68 if (pmeth && pmeth != meth) { |
|
69 if (!meth->graphnode.low) { |
|
70 meth->graphnode.allocs.bytes.total += site->allocs.bytes.total; |
|
71 meth->graphnode.allocs.calls.total += site->allocs.calls.total; |
|
72 } |
|
73 if (!tmgraphnode_connect(&(pmeth->graphnode), &(meth->graphnode), site)) |
|
74 goto bad; |
|
75 |
|
76 comp = meth->graphnode.up; |
|
77 if (comp) { |
|
78 pcomp = pmeth->graphnode.up; |
|
79 if (pcomp && pcomp != comp) { |
|
80 if (!comp->low) { |
|
81 comp->allocs.bytes.total |
|
82 += site->allocs.bytes.total; |
|
83 comp->allocs.calls.total |
|
84 += site->allocs.calls.total; |
|
85 } |
|
86 if (!tmgraphnode_connect(pcomp, comp, site)) |
|
87 goto bad; |
|
88 |
|
89 lib = comp->up; |
|
90 if (lib) { |
|
91 plib = pcomp->up; |
|
92 if (plib && plib != lib) { |
|
93 if (!lib->low) { |
|
94 lib->allocs.bytes.total |
|
95 += site->allocs.bytes.total; |
|
96 lib->allocs.calls.total |
|
97 += site->allocs.calls.total; |
|
98 } |
|
99 if (!tmgraphnode_connect(plib, lib, site)) |
|
100 goto bad; |
|
101 } |
|
102 old_lib_low = lib->low; |
|
103 if (!old_lib_low) |
|
104 lib->low = level; |
|
105 } |
|
106 } |
|
107 old_comp_low = comp->low; |
|
108 if (!old_comp_low) |
|
109 comp->low = level; |
|
110 } |
|
111 } |
|
112 old_meth_low = meth->graphnode.low; |
|
113 if (!old_meth_low) |
|
114 meth->graphnode.low = level; |
|
115 } |
|
116 } |
|
117 |
|
118 if (do_tree_dump) { |
|
119 fprintf(fp, "%c%*s%3d %3d %s %lu %ld\n", |
|
120 site->kids ? '+' : '-', level, "", level, kidnum, |
|
121 meth ? tmmethodnode_name(meth) : "???", |
|
122 (unsigned long)site->allocs.bytes.direct, |
|
123 (long)site->allocs.bytes.total); |
|
124 } |
|
125 nkids = 0; |
|
126 level++; |
|
127 for (kid = site->kids; kid; kid = kid->siblings) { |
|
128 walk_callsite_tree(kid, level, nkids, fp); |
|
129 nkids++; |
|
130 } |
|
131 |
|
132 if (meth) { |
|
133 if (!old_meth_low) |
|
134 meth->graphnode.low = 0; |
|
135 if (comp) { |
|
136 if (!old_comp_low) |
|
137 comp->low = 0; |
|
138 if (lib) { |
|
139 if (!old_lib_low) |
|
140 lib->low = 0; |
|
141 } |
|
142 } |
|
143 } |
|
144 return; |
|
145 |
|
146 bad: |
|
147 perror(program); |
|
148 exit(1); |
|
149 } |
|
150 |
|
151 /* |
|
152 * Linked list bubble-sort (waterson and brendan went bald hacking this). |
|
153 * |
|
154 * Sort the list in non-increasing order, using the expression passed as the |
|
155 * 'lessthan' formal macro parameter. This expression should use 'curr' as |
|
156 * the pointer to the current node (of type nodetype) and 'next' as the next |
|
157 * node pointer. It should return true if curr is less than next, and false |
|
158 * otherwise. |
|
159 */ |
|
160 #define BUBBLE_SORT_LINKED_LIST(listp, nodetype, lessthan) \ |
|
161 PR_BEGIN_MACRO \ |
|
162 nodetype *curr, **currp, *next, **nextp, *tmp; \ |
|
163 \ |
|
164 currp = listp; \ |
|
165 while ((curr = *currp) != NULL && curr->next) { \ |
|
166 nextp = &curr->next; \ |
|
167 while ((next = *nextp) != NULL) { \ |
|
168 if (lessthan) { \ |
|
169 tmp = curr->next; \ |
|
170 *currp = tmp; \ |
|
171 if (tmp == next) { \ |
|
172 PR_ASSERT(nextp == &curr->next); \ |
|
173 curr->next = next->next; \ |
|
174 next->next = curr; \ |
|
175 } else { \ |
|
176 *nextp = next->next; \ |
|
177 curr->next = next->next; \ |
|
178 next->next = tmp; \ |
|
179 *currp = next; \ |
|
180 *nextp = curr; \ |
|
181 nextp = &curr->next; \ |
|
182 } \ |
|
183 curr = next; \ |
|
184 continue; \ |
|
185 } \ |
|
186 nextp = &next->next; \ |
|
187 } \ |
|
188 currp = &curr->next; \ |
|
189 } \ |
|
190 PR_END_MACRO |
|
191 |
|
192 static int tabulate_node(PLHashEntry *he, int i, void *arg) |
|
193 { |
|
194 tmgraphnode *node = (tmgraphnode*) he; |
|
195 tmgraphnode **table = (tmgraphnode**) arg; |
|
196 |
|
197 table[i] = node; |
|
198 BUBBLE_SORT_LINKED_LIST(&node->down, tmgraphnode, |
|
199 (curr->allocs.bytes.total < next->allocs.bytes.total)); |
|
200 return HT_ENUMERATE_NEXT; |
|
201 } |
|
202 |
|
203 /* Sort in reverse size order, so biggest node comes first. */ |
|
204 static int node_table_compare(const void *p1, const void *p2) |
|
205 { |
|
206 const tmgraphnode *node1, *node2; |
|
207 uint32_t key1, key2; |
|
208 |
|
209 node1 = *(const tmgraphnode**) p1; |
|
210 node2 = *(const tmgraphnode**) p2; |
|
211 if (sort_by_direct) { |
|
212 key1 = node1->allocs.bytes.direct; |
|
213 key2 = node2->allocs.bytes.direct; |
|
214 } else { |
|
215 key1 = node1->allocs.bytes.total; |
|
216 key2 = node2->allocs.bytes.total; |
|
217 } |
|
218 return (key2 < key1) ? -1 : (key2 > key1) ? 1 : 0; |
|
219 } |
|
220 |
|
221 static int mean_size_compare(const void *p1, const void *p2) |
|
222 { |
|
223 const tmgraphnode *node1, *node2; |
|
224 double div1, div2, key1, key2; |
|
225 |
|
226 node1 = *(const tmgraphnode**) p1; |
|
227 node2 = *(const tmgraphnode**) p2; |
|
228 div1 = (double)node1->allocs.calls.direct; |
|
229 div2 = (double)node2->allocs.calls.direct; |
|
230 if (div1 == 0 || div2 == 0) |
|
231 return (int)(div2 - div1); |
|
232 key1 = (double)node1->allocs.bytes.direct / div1; |
|
233 key2 = (double)node2->allocs.bytes.direct / div2; |
|
234 if (key1 < key2) |
|
235 return 1; |
|
236 if (key1 > key2) |
|
237 return -1; |
|
238 return 0; |
|
239 } |
|
240 |
|
241 static const char *prettybig(uint32_t num, char *buf, size_t limit) |
|
242 { |
|
243 if (num >= 1000000000) |
|
244 PR_snprintf(buf, limit, "%1.2fG", (double) num / 1e9); |
|
245 else if (num >= 1000000) |
|
246 PR_snprintf(buf, limit, "%1.2fM", (double) num / 1e6); |
|
247 else if (num >= 1000) |
|
248 PR_snprintf(buf, limit, "%1.2fK", (double) num / 1e3); |
|
249 else |
|
250 PR_snprintf(buf, limit, "%lu", (unsigned long) num); |
|
251 return buf; |
|
252 } |
|
253 |
|
254 static double percent(uint32_t num, uint32_t total) |
|
255 { |
|
256 if (num == 0) |
|
257 return 0.0; |
|
258 return ((double) num * 100) / (double) total; |
|
259 } |
|
260 |
|
261 static void sort_graphlink_list(tmgraphlink **listp, int which) |
|
262 { |
|
263 BUBBLE_SORT_LINKED_LIST(listp, tmgraphlink, |
|
264 (TM_LINK_TO_EDGE(curr, which)->allocs.bytes.total |
|
265 < TM_LINK_TO_EDGE(next, which)->allocs.bytes.total)); |
|
266 } |
|
267 |
|
268 static void dump_graphlink_list(tmgraphlink *list, int which, const char *name, |
|
269 FILE *fp) |
|
270 { |
|
271 tmcounts bytes; |
|
272 tmgraphlink *link; |
|
273 tmgraphedge *edge; |
|
274 char buf[16]; |
|
275 |
|
276 bytes.direct = bytes.total = 0; |
|
277 for (link = list; link; link = link->next) { |
|
278 edge = TM_LINK_TO_EDGE(link, which); |
|
279 bytes.direct += edge->allocs.bytes.direct; |
|
280 bytes.total += edge->allocs.bytes.total; |
|
281 } |
|
282 |
|
283 if (js_mode) { |
|
284 fprintf(fp, |
|
285 " %s:{dbytes:%ld, tbytes:%ld, edges:[\n", |
|
286 name, (long) bytes.direct, (long) bytes.total); |
|
287 for (link = list; link; link = link->next) { |
|
288 edge = TM_LINK_TO_EDGE(link, which); |
|
289 fprintf(fp, |
|
290 " {node:%d, dbytes:%ld, tbytes:%ld},\n", |
|
291 link->node->sort, |
|
292 (long) edge->allocs.bytes.direct, |
|
293 (long) edge->allocs.bytes.total); |
|
294 } |
|
295 fputs(" ]},\n", fp); |
|
296 } else { |
|
297 fputs("<td valign=top>", fp); |
|
298 for (link = list; link; link = link->next) { |
|
299 edge = TM_LINK_TO_EDGE(link, which); |
|
300 fprintf(fp, |
|
301 "<a href='#%s'>%s (%1.2f%%)</a>\n", |
|
302 tmgraphnode_name(link->node), |
|
303 prettybig(edge->allocs.bytes.total, buf, sizeof buf), |
|
304 percent(edge->allocs.bytes.total, bytes.total)); |
|
305 } |
|
306 fputs("</td>", fp); |
|
307 } |
|
308 } |
|
309 |
|
310 static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname, |
|
311 const char *title, FILE *fp) |
|
312 { |
|
313 uint32_t i, count; |
|
314 tmgraphnode **table, *node; |
|
315 char *name; |
|
316 size_t namelen; |
|
317 char buf1[16], buf2[16], buf3[16], buf4[16]; |
|
318 |
|
319 count = hashtbl->nentries; |
|
320 table = (tmgraphnode**) malloc(count * sizeof(tmgraphnode*)); |
|
321 if (!table) { |
|
322 perror(program); |
|
323 exit(1); |
|
324 } |
|
325 PL_HashTableEnumerateEntries(hashtbl, tabulate_node, table); |
|
326 qsort(table, count, sizeof(tmgraphnode*), node_table_compare); |
|
327 for (i = 0; i < count; i++) |
|
328 table[i]->sort = i; |
|
329 |
|
330 if (js_mode) { |
|
331 fprintf(fp, |
|
332 "var %s = {\n name:'%s', title:'%s', nodes:[\n", |
|
333 varname, varname, title); |
|
334 } else { |
|
335 fprintf(fp, |
|
336 "<table border=1>\n" |
|
337 "<tr>" |
|
338 "<th>%s</th>" |
|
339 "<th>Down</th>" |
|
340 "<th>Next</th>" |
|
341 "<th>Total/Direct (percents)</th>" |
|
342 "<th>Allocations</th>" |
|
343 "<th>Fan-in</th>" |
|
344 "<th>Fan-out</th>" |
|
345 "</tr>\n", |
|
346 title); |
|
347 } |
|
348 |
|
349 for (i = 0; i < count; i++) { |
|
350 /* Don't bother with truly puny nodes. */ |
|
351 node = table[i]; |
|
352 if (node->allocs.bytes.total < min_subtotal) |
|
353 break; |
|
354 |
|
355 name = tmgraphnode_name(node); |
|
356 if (js_mode) { |
|
357 fprintf(fp, |
|
358 " {name:'%s', dbytes:%ld, tbytes:%ld," |
|
359 " dallocs:%ld, tallocs:%ld,\n", |
|
360 name, |
|
361 (long) node->allocs.bytes.direct, |
|
362 (long) node->allocs.bytes.total, |
|
363 (long) node->allocs.calls.direct, |
|
364 (long) node->allocs.calls.total); |
|
365 } else { |
|
366 namelen = strlen(name); |
|
367 fprintf(fp, |
|
368 "<tr>" |
|
369 "<td valign=top><a name='%s'>%.*s%s</a></td>", |
|
370 name, |
|
371 (namelen > 40) ? 40 : (int)namelen, name, |
|
372 (namelen > 40) ? "<i>...</i>" : ""); |
|
373 if (node->down) { |
|
374 fprintf(fp, |
|
375 "<td valign=top><a href='#%s'><i>down</i></a></td>", |
|
376 tmgraphnode_name(node->down)); |
|
377 } else { |
|
378 fputs("<td></td>", fp); |
|
379 } |
|
380 if (node->next) { |
|
381 fprintf(fp, |
|
382 "<td valign=top><a href='#%s'><i>next</i></a></td>", |
|
383 tmgraphnode_name(node->next)); |
|
384 } else { |
|
385 fputs("<td></td>", fp); |
|
386 } |
|
387 fprintf(fp, |
|
388 "<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>" |
|
389 "<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>", |
|
390 prettybig(node->allocs.bytes.total, buf1, sizeof buf1), |
|
391 prettybig(node->allocs.bytes.direct, buf2, sizeof buf2), |
|
392 percent(node->allocs.bytes.total, |
|
393 tmr->calltree_root.allocs.bytes.total), |
|
394 percent(node->allocs.bytes.direct, |
|
395 tmr->calltree_root.allocs.bytes.total), |
|
396 prettybig(node->allocs.calls.total, buf3, sizeof buf3), |
|
397 prettybig(node->allocs.calls.direct, buf4, sizeof buf4), |
|
398 percent(node->allocs.calls.total, |
|
399 tmr->calltree_root.allocs.calls.total), |
|
400 percent(node->allocs.calls.direct, |
|
401 tmr->calltree_root.allocs.calls.total)); |
|
402 } |
|
403 |
|
404 /* NB: we must use 'fin' because 'in' is a JS keyword! */ |
|
405 sort_graphlink_list(&node->in, TM_EDGE_IN_LINK); |
|
406 dump_graphlink_list(node->in, TM_EDGE_IN_LINK, "fin", fp); |
|
407 sort_graphlink_list(&node->out, TM_EDGE_OUT_LINK); |
|
408 dump_graphlink_list(node->out, TM_EDGE_OUT_LINK, "out", fp); |
|
409 |
|
410 if (js_mode) |
|
411 fputs(" },\n", fp); |
|
412 else |
|
413 fputs("</tr>\n", fp); |
|
414 } |
|
415 |
|
416 if (js_mode) { |
|
417 fputs("]};\n", fp); |
|
418 } else { |
|
419 fputs("</table>\n<hr>\n", fp); |
|
420 |
|
421 qsort(table, count, sizeof(tmgraphnode*), mean_size_compare); |
|
422 |
|
423 fprintf(fp, |
|
424 "<table border=1>\n" |
|
425 "<tr><th colspan=4>Direct Allocators</th></tr>\n" |
|
426 "<tr>" |
|
427 "<th>%s</th>" |
|
428 "<th>Mean Size</th>" |
|
429 "<th>StdDev</th>" |
|
430 "<th>Allocations<th>" |
|
431 "</tr>\n", |
|
432 title); |
|
433 |
|
434 for (i = 0; i < count; i++) { |
|
435 double allocs, bytes, mean, variance, sigma; |
|
436 |
|
437 node = table[i]; |
|
438 allocs = (double)node->allocs.calls.direct; |
|
439 if (!allocs) |
|
440 continue; |
|
441 |
|
442 /* Compute direct-size mean and standard deviation. */ |
|
443 bytes = (double)node->allocs.bytes.direct; |
|
444 mean = bytes / allocs; |
|
445 variance = allocs * node->sqsum - bytes * bytes; |
|
446 if (variance < 0 || allocs == 1) |
|
447 variance = 0; |
|
448 else |
|
449 variance /= allocs * (allocs - 1); |
|
450 sigma = sqrt(variance); |
|
451 |
|
452 name = tmgraphnode_name(node); |
|
453 namelen = strlen(name); |
|
454 fprintf(fp, |
|
455 "<tr>" |
|
456 "<td valign=top>%.*s%s</td>" |
|
457 "<td valign=top>%s</td>" |
|
458 "<td valign=top>%s</td>" |
|
459 "<td valign=top>%s</td>" |
|
460 "</tr>\n", |
|
461 (namelen > 65) ? 45 : (int)namelen, name, |
|
462 (namelen > 65) ? "<i>...</i>" : "", |
|
463 prettybig((uint32_t)mean, buf1, sizeof buf1), |
|
464 prettybig((uint32_t)sigma, buf2, sizeof buf2), |
|
465 prettybig(node->allocs.calls.direct, buf3, sizeof buf3)); |
|
466 } |
|
467 fputs("</table>\n", fp); |
|
468 } |
|
469 |
|
470 free((void*) table); |
|
471 } |
|
472 |
|
473 static void my_tmevent_handler(tmreader *tmr, tmevent *event) |
|
474 { |
|
475 switch (event->type) { |
|
476 case TM_EVENT_STATS: |
|
477 if (js_mode) |
|
478 break; |
|
479 fprintf(stdout, |
|
480 "<p><table border=1>" |
|
481 "<tr><th>Counter</th><th>Value</th></tr>\n" |
|
482 "<tr><td>maximum actual stack depth</td><td align=right>%lu</td></tr>\n" |
|
483 "<tr><td>maximum callsite tree depth</td><td align=right>%lu</td></tr>\n" |
|
484 "<tr><td>number of parent callsites</td><td align=right>%lu</td></tr>\n" |
|
485 "<tr><td>maximum kids per parent</td><td align=right>%lu</td></tr>\n" |
|
486 "<tr><td>hits looking for a kid</td><td align=right>%lu</td></tr>\n" |
|
487 "<tr><td>misses looking for a kid</td><td align=right>%lu</td></tr>\n" |
|
488 "<tr><td>steps over other kids</td><td align=right>%lu</td></tr>\n" |
|
489 "<tr><td>callsite recurrences</td><td align=right>%lu</td></tr>\n" |
|
490 "<tr><td>number of stack backtraces</td><td align=right>%lu</td></tr>\n" |
|
491 "<tr><td>backtrace failures</td><td align=right>%lu</td></tr>\n" |
|
492 "<tr><td>backtrace malloc failures</td><td align=right>%lu</td></tr>\n" |
|
493 "<tr><td>backtrace dladdr failures</td><td align=right>%lu</td></tr>\n" |
|
494 "<tr><td>malloc calls</td><td align=right>%lu</td></tr>\n" |
|
495 "<tr><td>malloc failures</td><td align=right>%lu</td></tr>\n" |
|
496 "<tr><td>calloc calls</td><td align=right>%lu</td></tr>\n" |
|
497 "<tr><td>calloc failures</td><td align=right>%lu</td></tr>\n" |
|
498 "<tr><td>realloc calls</td><td align=right>%lu</td></tr>\n" |
|
499 "<tr><td>realloc failures</td><td align=right>%lu</td></tr>\n" |
|
500 "<tr><td>free calls</td><td align=right>%lu</td></tr>\n" |
|
501 "<tr><td>free(null) calls</td><td align=right>%lu</td></tr>\n" |
|
502 "</table>", |
|
503 (unsigned long) event->u.stats.tmstats.calltree_maxstack, |
|
504 (unsigned long) event->u.stats.tmstats.calltree_maxdepth, |
|
505 (unsigned long) event->u.stats.tmstats.calltree_parents, |
|
506 (unsigned long) event->u.stats.tmstats.calltree_maxkids, |
|
507 (unsigned long) event->u.stats.tmstats.calltree_kidhits, |
|
508 (unsigned long) event->u.stats.tmstats.calltree_kidmisses, |
|
509 (unsigned long) event->u.stats.tmstats.calltree_kidsteps, |
|
510 (unsigned long) event->u.stats.tmstats.callsite_recurrences, |
|
511 (unsigned long) event->u.stats.tmstats.backtrace_calls, |
|
512 (unsigned long) event->u.stats.tmstats.backtrace_failures, |
|
513 (unsigned long) event->u.stats.tmstats.btmalloc_failures, |
|
514 (unsigned long) event->u.stats.tmstats.dladdr_failures, |
|
515 (unsigned long) event->u.stats.tmstats.malloc_calls, |
|
516 (unsigned long) event->u.stats.tmstats.malloc_failures, |
|
517 (unsigned long) event->u.stats.tmstats.calloc_calls, |
|
518 (unsigned long) event->u.stats.tmstats.calloc_failures, |
|
519 (unsigned long) event->u.stats.tmstats.realloc_calls, |
|
520 (unsigned long) event->u.stats.tmstats.realloc_failures, |
|
521 (unsigned long) event->u.stats.tmstats.free_calls, |
|
522 (unsigned long) event->u.stats.tmstats.null_free_calls); |
|
523 |
|
524 if (event->u.stats.calltree_maxkids_parent) { |
|
525 tmcallsite *site = |
|
526 tmreader_callsite(tmr, event->u.stats.calltree_maxkids_parent); |
|
527 if (site && site->method) { |
|
528 fprintf(stdout, "<p>callsite with the most kids: %s</p>", |
|
529 tmmethodnode_name(site->method)); |
|
530 } |
|
531 } |
|
532 |
|
533 if (event->u.stats.calltree_maxstack_top) { |
|
534 tmcallsite *site = |
|
535 tmreader_callsite(tmr, event->u.stats.calltree_maxstack_top); |
|
536 fputs("<p>deepest callsite tree path:\n" |
|
537 "<table border=1>\n" |
|
538 "<tr><th>Method</th><th>Offset</th></tr>\n", |
|
539 stdout); |
|
540 while (site) { |
|
541 fprintf(stdout, |
|
542 "<tr><td>%s</td><td>0x%08lX</td></tr>\n", |
|
543 site->method ? tmmethodnode_name(site->method) : "???", |
|
544 (unsigned long) site->offset); |
|
545 site = site->parent; |
|
546 } |
|
547 fputs("</table>\n<hr>\n", stdout); |
|
548 } |
|
549 break; |
|
550 } |
|
551 } |
|
552 |
|
553 int main(int argc, char **argv) |
|
554 { |
|
555 int c, i, j, rv; |
|
556 tmreader *tmr; |
|
557 FILE *fp; |
|
558 |
|
559 program = *argv; |
|
560 tmr = tmreader_new(program, NULL); |
|
561 if (!tmr) { |
|
562 perror(program); |
|
563 exit(1); |
|
564 } |
|
565 |
|
566 while ((c = getopt(argc, argv, "djtuf:m:")) != EOF) { |
|
567 switch (c) { |
|
568 case 'd': |
|
569 sort_by_direct = 1; |
|
570 break; |
|
571 case 'j': |
|
572 js_mode = 1; |
|
573 break; |
|
574 case 't': |
|
575 do_tree_dump = 1; |
|
576 break; |
|
577 case 'u': |
|
578 unified_output = 1; |
|
579 break; |
|
580 case 'f': |
|
581 function_dump = optarg; |
|
582 break; |
|
583 case 'm': |
|
584 min_subtotal = atoi(optarg); |
|
585 break; |
|
586 default: |
|
587 fprintf(stderr, |
|
588 "usage: %s [-dtu] [-f function-dump-filename] [-m min] [output.html]\n", |
|
589 program); |
|
590 exit(2); |
|
591 } |
|
592 } |
|
593 |
|
594 if (!js_mode) { |
|
595 time_t start = time(NULL); |
|
596 |
|
597 fprintf(stdout, |
|
598 "<script language=\"JavaScript\">\n" |
|
599 "function onload() {\n" |
|
600 " document.links[0].__proto__.onmouseover = new Function(" |
|
601 "\"window.status =" |
|
602 " this.href.substring(this.href.lastIndexOf('#') + 1)\");\n" |
|
603 "}\n" |
|
604 "</script>\n"); |
|
605 fprintf(stdout, "%s starting at %s", program, ctime(&start)); |
|
606 fflush(stdout); |
|
607 } |
|
608 |
|
609 argc -= optind; |
|
610 argv += optind; |
|
611 if (argc == 0) { |
|
612 if (tmreader_eventloop(tmr, "-", my_tmevent_handler) <= 0) |
|
613 exit(1); |
|
614 } else { |
|
615 for (i = j = 0; i < argc; i++) { |
|
616 fp = fopen(argv[i], "r"); |
|
617 if (!fp) { |
|
618 fprintf(stderr, "%s: can't open %s: %s\n", |
|
619 program, argv[i], strerror(errno)); |
|
620 exit(1); |
|
621 } |
|
622 rv = tmreader_eventloop(tmr, argv[i], my_tmevent_handler); |
|
623 if (rv < 0) |
|
624 exit(1); |
|
625 if (rv > 0) |
|
626 j++; |
|
627 fclose(fp); |
|
628 } |
|
629 if (j == 0) |
|
630 exit(1); |
|
631 } |
|
632 |
|
633 compute_callsite_totals(&tmr->calltree_root); |
|
634 walk_callsite_tree(&tmr->calltree_root, 0, 0, stdout); |
|
635 |
|
636 if (js_mode) { |
|
637 fprintf(stdout, |
|
638 "<script language='javascript'>\n" |
|
639 "// direct and total byte and allocator-call counts\n" |
|
640 "var dbytes = %ld, tbytes = %ld," |
|
641 " dallocs = %ld, tallocs = %ld;\n", |
|
642 (long) tmr->calltree_root.allocs.bytes.direct, |
|
643 (long) tmr->calltree_root.allocs.bytes.total, |
|
644 (long) tmr->calltree_root.allocs.calls.direct, |
|
645 (long) tmr->calltree_root.allocs.calls.total); |
|
646 } |
|
647 |
|
648 dump_graph(tmr, tmr->libraries, "libraries", "Library", stdout); |
|
649 if (!js_mode) |
|
650 fputs("<hr>\n", stdout); |
|
651 |
|
652 dump_graph(tmr, tmr->components, "classes", "Class or Component", stdout); |
|
653 if (js_mode || unified_output || function_dump) { |
|
654 if (js_mode || unified_output || strcmp(function_dump, "-") == 0) { |
|
655 fp = stdout; |
|
656 if (!js_mode) |
|
657 fputs("<hr>\n", fp); |
|
658 } else { |
|
659 struct stat sb, fsb; |
|
660 |
|
661 fstat(fileno(stdout), &sb); |
|
662 if (stat(function_dump, &fsb) == 0 && |
|
663 fsb.st_dev == sb.st_dev && fsb.st_ino == sb.st_ino) { |
|
664 fp = stdout; |
|
665 fputs("<hr>\n", fp); |
|
666 } else { |
|
667 fp = fopen(function_dump, "w"); |
|
668 if (!fp) { |
|
669 fprintf(stderr, "%s: can't open %s: %s\n", |
|
670 program, function_dump, strerror(errno)); |
|
671 exit(1); |
|
672 } |
|
673 } |
|
674 } |
|
675 |
|
676 dump_graph(tmr, tmr->methods, "methods", "Function or Method", fp); |
|
677 if (fp != stdout) |
|
678 fclose(fp); |
|
679 |
|
680 if (js_mode) { |
|
681 fputs("function viewnode(graph, index) {\n" |
|
682 " view.location = viewsrc();\n" |
|
683 "}\n" |
|
684 "function viewnodelink(graph, index) {\n" |
|
685 " var node = graph.nodes[index];\n" |
|
686 " return '<a href=\"javascript:viewnode('" |
|
687 " + graph.name.quote() + ', ' + node.sort" |
|
688 " + ')\" onmouseover=' + node.name.quote() + '>'" |
|
689 " + node.name + '</a>';\n" |
|
690 "}\n" |
|
691 "function search(expr) {\n" |
|
692 " var re = new RegExp(expr);\n" |
|
693 " var src = '';\n" |
|
694 " var graphs = [libraries, classes, methods]\n" |
|
695 " var nodes;\n" |
|
696 " for (var n = 0; n < (nodes = graphs[n].nodes).length; n++) {\n" |
|
697 " for (var i = 0; i < nodes.length; i++) {\n" |
|
698 " if (re.test(nodes[i].name))\n" |
|
699 " src += viewnodelink(graph, i) + '\\n';\n" |
|
700 " }\n" |
|
701 " }\n" |
|
702 " view.location = viewsrc();\n" |
|
703 "}\n" |
|
704 "function ctrlsrc() {\n" |
|
705 " return \"<form>\\n" |
|
706 "search: <input size=40 onchange='search(this.value)'>\\n" |
|
707 "</form>\\n\";\n" |
|
708 "}\n" |
|
709 "function viewsrc() {\n" |
|
710 " return 'hiiiii'\n" |
|
711 "}\n" |
|
712 "</script>\n" |
|
713 "<frameset rows='10%,*'>\n" |
|
714 " <frame name='ctrl' src='javascript:top.ctrlsrc()'>\n" |
|
715 " <frame name='view' src='javascript:top.viewsrc()'>\n" |
|
716 "</frameset>\n", |
|
717 stdout); |
|
718 } |
|
719 } |
|
720 |
|
721 exit(0); |
|
722 } |