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