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/.
7 # bloattable [-debug] [-source] [-byte n|-obj n|-ref n] <file1> <file2> ... <filen> > <html-file>
9 # file1, file2, ... filen should be successive BloatView files generated from the same run.
10 # Summarize them in an HTML table. Output the HTML to the standard output.
12 # If -debug is set, create a slightly larger html file which is more suitable for debugging this script.
13 # If -source is set, create an html file that prints the html source as the output
14 # If -byte n, -obj n, or -ref n is given, make the page default to showing byte, object, or reference statistics,
15 # respectively, and sort by the nth column (n is zero-based, so the first column has n==0).
17 # See http://lxr.mozilla.org/mozilla/source/xpcom/doc/MemoryTools.html
25 # The generated HTML is almost entirely generated by a script. Only the <HTML>, <HEAD>, and <BODY> elements are explicit
26 # because a <SCRIPT> element cannot officially be a direct descendant of an <HTML> element.
27 # The script itself is almost all generated by an eval of a large string. This allows the script to reproduce itself
28 # when making a new page using document.write's. Re-sorting the page causes it to regenerate itself in this way.
32 # Return the file's modification date.
35 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) =
36 stat $pathName or die "Can't stat '$pathName'";
43 my $fileName = basename
($pathName, "");
44 $fileName =~ s/\..*//;
49 # Convert a raw string into a single-quoted JavaScript string.
50 sub singleQuoteString
($) {
60 # Convert a raw string into a double-quoted JavaScript string.
61 sub doubleQuoteString
($) {
71 # Quote special HTML characters in the string.
83 # Write the generated page to the standard output.
84 # The script source code is read from this file past the __END__ marker
85 # @$scriptData is the JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
86 # @$persistentScriptData is the same as @scriptData, but persists when the page reloads itself.
87 # If $debug is true, generate the script directly instead of having it eval itself.
88 # If $source is true, generate a script that displays the page's source instead of the page itself.
89 sub generate
(\@\@
$$$$) {
90 my ($scriptData, $persistentScriptData, $debug, $source, $showMode, $sortColumn) = @_;
92 my @scriptSource = <DATA
>;
95 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
98 <SCRIPT type="text/javascript">
101 foreach (@
$scriptData) {print "$_\n";}
104 print "var srcArray = [\n";
105 my @quotedScriptSource = map {
108 # $line =~ s/^\/\/SOURCE\s+//g if $source;
109 $line =~ s/^\/\/.*//g;
111 $line eq "" ?
() : $line
112 } @
$persistentScriptData, @scriptSource;
113 my $lastQuotedLine = pop @quotedScriptSource;
114 foreach (@quotedScriptSource) {print doubleQuoteString
($_), ",\n";}
115 print doubleQuoteString
($lastQuotedLine), "];\n\n";
118 push @quotedScriptSource, $lastQuotedLine;
119 foreach (@quotedScriptSource) {
120 s/<\//<\\\
//g; # This fails if a regexp ends with a '<'. Oh well....
125 print "eval(srcArray.join(\"\\n\"));\n\n";
127 print "showMode = $showMode;\n";
128 print "sortColumn = $sortColumn;\n";
131 function writeQuotedHTML(s) {
132 document.write(quoteHTML(s.toString()).replace(/\n/g, '<BR>\n'));
135 var quotingDocument = {
137 for (var i = 0; i < arguments.length; i++)
138 writeQuotedHTML(arguments[i]);
140 writeln: function () {
141 for (var i = 0; i < arguments.length; i++)
142 writeQuotedHTML(arguments[i]);
143 document.writeln('<BR>');
148 print "showHead(document);\n";
155 print quoteHTML
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n";
156 print quoteHTML
"<HTML>\n";
157 print quoteHTML
"<HEAD>\n";
158 print "<SCRIPT type=\"text/javascript\">showHead(quotingDocument);</SCRIPT>\n";
159 print quoteHTML
"</HEAD>\n\n";
160 print quoteHTML
"<BODY>\n";
161 print "<SCRIPT type=\"text/javascript\">showBody(quotingDocument);</SCRIPT>\n";
162 print quoteHTML
"</BODY>\n";
163 print quoteHTML
"</HTML>\n";
166 print "<SCRIPT type=\"text/javascript\">showBody(document);</SCRIPT>\n";
174 # Read the bloat file into hash table $h. The hash table is indexed by class names;
175 # each entry is a list with the following elements:
176 # bytesAlloc Total number of bytes allocated
177 # bytesNet Total number of bytes allocated but not deallocated
178 # objectsAlloc Total number of objects allocated
179 # objectsNet Total number of objects allocated but not deallocated
180 # refsAlloc Total number of references AddRef'd
181 # refsNet Total number of references AddRef'd but not Released
182 # Except for TOTAL, all hash table entries refer to mutually exclusive data.
183 # $sizes is a hash table indexed by class names. Each entry of that table contains the class's instance size.
184 sub readBloatFile
($\
%\
%) {
185 my ($file, $h, $sizes) = @_;
186 local $_; # Needed for 'while (<FILE>)' below.
188 my $readSomething = 0;
191 if (my ($name, $size, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet) =
192 /^\s*(?:\d+)\s+([\w:]+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(-?\d+)\s*\([^()]*\)\s*(\d+)\s+(-?\d+)\s*\([^()]*\)\s*$/) {
194 if ($name eq "TOTAL") {
196 $bytesAlloc = "undefined";
198 $bytesAlloc = $objectsAlloc * $size;
199 if ($bytesNet != $objectsNet * $size) {
200 print STDERR
"In '$file', class $name bytesNet != objectsNet * size: $bytesNet != $objectsNet * $size\n";
203 print STDERR
"Duplicate entry $name in '$file'\n" if $$h{$name};
204 $$h{$name} = [$bytesAlloc, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet];
206 my $oldSize = $$sizes{$name};
207 print STDERR
"Mismatch of sizes of class $name: $oldSize and $size\n" if defined($oldSize) && $size ne $oldSize;
208 $$sizes{$name} = $size;
210 } elsif (/^\s*(?:\d+)\s+([\w:]+)\s/) {
211 print STDERR
"Unable to parse '$file' line: $_";
215 print STDERR
"No data in '$file'\n" unless $readSomething;
220 my %sizes; # <class-name> => <instance-size>
221 my %tables; # <file-name> => <bloat-table>; see readBloatFile for format of <bloat-table>
223 # Generate the JavaScript source code for the row named $c. $l can contain the initial entries of the row.
224 sub genTableRowSource
($$) {
228 my $e = $tables{$_}{$c};
229 if (defined($lastE) && !defined($e)) {
231 print STDERR
"Class $c is defined in an earlier file but not in '$_'\n";
234 if (defined $lastE) {
235 for (my $i = 0; $i <= $#$e; $i++) {
237 $l .= ($n eq "undefined" ?
"undefined" : $n - $$lastE[$i]) . ",";
241 $l .= join(",", @
$e) . ", ";
245 $l .= "0,0,0,0,0,0, ";
248 $l .= join(",", @
$lastE);
260 GetOptions
("debug" => \
$debug, "source" => \
$source, "byte=i" => \
$modeOptions[0], "obj=i" => \
$modeOptions[1], "ref=i" => \
$modeOptions[2]);
261 for (my $i = 0; $i != 3; $i++) {
262 my $modeOption = $modeOptions[$i];
264 die "Only one of -byte, -obj, or -ref may be given" if defined $showMode;
265 my $nFileColumns = scalar(@ARGV) + 1;
266 die "-byte, -obj, or -ref column number out of range" if $modeOption < 0 || $modeOption >= 2 + 2*$nFileColumns;
268 if ($modeOption >= 2) {
270 $sortColumn = 2 + $showMode*2;
271 if ($modeOption >= $nFileColumns) {
273 $modeOption -= $nFileColumns;
275 $sortColumn += $modeOption*6;
277 $sortColumn = $modeOption;
281 unless (defined $showMode) {
286 # Read all of the bloat files.
288 unless ($tables{$_}) {
292 readBloatFile
$_, %table, %sizes;
293 $tables{$_} = \
%table;
296 die "No input" unless %sizes;
298 my @scriptData; # JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
299 my @persistentScriptData; # Same as @scriptData, but persists the page reloads itself.
301 # Print a list of bloat file names.
302 push @persistentScriptData, "var nFiles = " . scalar(@ARGV) . ";";
303 push @persistentScriptData, "var fileTags = [" . join(", ", map {singleQuoteString
substr(fileCoreName
($_), -10)} @ARGV) . "];";
304 push @persistentScriptData, "var fileNames = [" . join(", ", map {singleQuoteString
$_} @ARGV) . "];";
305 push @persistentScriptData, "var fileDates = [" . join(", ", map {singleQuoteString
localtime fileModDate
$_} @ARGV) . "];";
307 # Print the bloat tables.
308 push @persistentScriptData, "var totals = " . genTableRowSource
('"TOTAL", undefined, ', "TOTAL") . ";";
309 push @scriptData, "var classTables = [";
310 delete $sizes{"TOTAL"};
311 my @classes = sort(keys %sizes);
312 for (my $i = 0; $i <= $#classes; $i++) {
313 my $c = $classes[$i];
314 push @scriptData, genTableRowSource
(doubleQuoteString
($c).", ".$sizes{$c}.", ", $c) . ($i == $#classes ?
"];" : ",");
317 generate
(@scriptData, @persistentScriptData, $debug, $source, $showMode, $sortColumn);
321 # The source of the eval'd JavaScript follows.
322 # Comments starting with // that are alone on a line are stripped by the Perl script.
325 // showMode
: 0=bytes
, 1=objects
, 2=references
332 // Sort according to the sortColumn
. Column
0 is sorted alphabetically
in ascending order
.
333 // All other columns are sorted numerically
in descending order
, with column
0 used
for a secondary
sort.
334 // Undefined is always listed
last.
335 function sortCompare
(x
, y
) {
337 var xc
= x
[sortColumn
];
338 var yc
= y
[sortColumn
];
339 if (xc
< yc
|| xc
=== undefined
&& yc
!== undefined
) return 1;
340 if (yc
< xc
|| yc
=== undefined
&& xc
!== undefined
) return -1;
345 if (x0
> y0
|| x0
=== undefined
&& y0
!== undefined
) return 1;
346 if (y0
> x0
|| y0
=== undefined
&& x0
!== undefined
) return -1;
351 // Quote special HTML characters
in the string
.
352 function quoteHTML
(s
) {
353 s
= s
.replace
(/&/g, '&');
354 // Can
't use /</g because HTML interprets '</g
' as ending the script!
355 s = s.replace(/\x3C/g, '<');
356 s = s.replace(/>/g, '>');
357 s = s.replace(/ /g, ' 
;');
362 function writeFileTable(d) {
363 d.writeln('<TABLE border
=1 cellspacing
=1 cellpadding
=0>');
364 d.writeln('<TR
>\n<TH
>Name
</TH>\n<TH>File</TH
>\n<TH
>Date
</TH>\n</TR
>');
365 for (var i = 0; i < nFiles; i++)
366 d.writeln('<TR
>\n<TD
>'+quoteHTML(fileTags[i])+'</TD>\n<TD><TT>'+quoteHTML(fileNames[i])+'</TT
></TD>\n<TD>'+quoteHTML(fileDates[i])+'</TD
>\n</TR
>');
367 d.writeln('</TABLE
>');
371 function writeReloadLink(d, column, s, rowspan) {
372 d.write(rowspan == 1 ? '<TH
>' : '<TH rowspan
='+rowspan+'>');
373 if (column != sortColumn)
374 d.write('<A href
="javascript:reloadSelf('+column+','+showMode+')">');
376 if (column != sortColumn)
381 function writeClassTableRow(d, row, base, modeName) {
383 d.writeln('<TR
>\n<TH
>'+modeName+'</TH
>');
385 d.writeln('<TR
>\n<TD
><A href
="javascript:showRowDetail(\''+row[0]+'\')">'+quoteHTML(row[0])+'</A></TD
>');
387 d.writeln('<TD
class=num
>'+(v === undefined ? '' : v)+'</TD
>');
389 for (var i = 0; i != 2; i++) {
391 for (var j = 0; j <= nFiles; j++) {
400 d.writeln('<TD
class='+style+'>'+(v === undefined ? '' : v)+'</TD
>');
407 function writeClassTable(d) {
408 var base = 2 + showMode*2;
410 // Make a copy because a sort is destructive.
411 var table = classTables.concat();
412 table.sort(sortCompare);
414 d.writeln('<TABLE border
=1 cellspacing
=1 cellpadding
=0>');
417 writeReloadLink(d, 0, 'Class Name
', 2);
418 writeReloadLink(d, 1, 'Instance
<BR
>Size
', 2);
419 d.writeln('<TH colspan
='+(nFiles+1)+'>'+modeNameUpper+'s allocated
</TH
>');
420 d.writeln('<TH colspan
='+(nFiles+1)+'>'+modeNameUpper+'s allocated but
not freed
</TH>\n</TR
>');
422 for (var i = 0; i != 2; i++) {
424 for (var j = 0; j <= nFiles; j++) {
425 writeReloadLink(d, c, j == nFiles ? 'Total
' : quoteHTML(fileTags[j]), 1);
431 writeClassTableRow(d, totals, base, 0);
432 for (var r = 0; r < table.length; r++)
433 writeClassTableRow(d, table[r], base, 0);
435 d.writeln('</TABLE
>');
439 var modeNames = ["byte", "object", "reference"];
440 var modeNamesUpper = ["Byte", "Object", "Reference"];
441 var styleSheet = '<STYLE type
="TEXT/CSS">\n'+
442 'BODY
{background
-color
: #FFFFFF; color: #000000}\n'+
443 '.num {text-align: right}\n'+
444 '.pos {text-align: right; color: #CC0000}\n'+
445 '.neg {text-align: right; color: #009900}\n'+
449 function showHead
(d
) {
450 modeName
= modeNames
[showMode
];
451 modeNameUpper
= modeNamesUpper
[showMode
];
452 d
.writeln
('<TITLE>'+modeNameUpper
+' Bloats</TITLE>');
453 d
.writeln
(styleSheet
);
456 function showBody
(d
) {
457 d
.writeln
('<H1>'+modeNameUpper
+' Bloats</H1>');
460 for (var i
= 0; i
!= 3; i
++)
462 var newSortColumn
= sortColumn
;
464 newSortColumn
= sortColumn
+ (i
-showMode
)*2;
465 d
.write('<INPUT type="button" value="Show '+modeNamesUpper
[i
]+'s" onClick="reloadSelf('+newSortColumn
+','+i
+')">');
467 d
.writeln
('</FORM>');
468 d
.writeln
('<P>The numbers do not include <CODE>malloc</CODE>\'d data such as string contents.</P>');
469 d
.writeln
('<P>Click on a column heading to sort by that column. Click on a class name to see details for that class.</P>');
474 function showRowDetail
(rowName
) {
478 if (rowName
== "TOTAL")
481 for (i
= 0; i
< classTables
.length; i
++)
482 if (rowName
== classTables
[i
][0]) {
483 row
= classTables
[i
];
488 var w
= window
.open("", "ClassTableRowDetails");
491 d
.writeln
('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
492 d
.writeln
('<HTML>\n<HEAD>\n<TITLE>'+quoteHTML
(rowName
)+' bloat details</TITLE>');
493 d
.writeln
(styleSheet
);
494 d
.writeln
('</HEAD>\n\n<BODY>');
495 d
.writeln
('<H2>'+quoteHTML
(rowName
)+'</H2>');
496 if (row
[1] !== undefined
)
497 d
.writeln
('<P>Each instance has '+row
[1]+' bytes.</P>');
499 d
.writeln
('<TABLE border=1 cellspacing=1 cellpadding=0>');
500 d
.writeln
('<TR>\n<TH></TH>\n<TH colspan='+(nFiles
+1)+'>Allocated</TH>');
501 d
.writeln
('<TH colspan='+(nFiles
+1)+'>Allocated but not freed</TH>\n</TR>');
502 d
.writeln
('<TR>\n<TH></TH>');
503 for (i
= 0; i
!= 2; i
++)
504 for (var j
= 0; j
<= nFiles
; j
++)
505 d
.writeln
('<TH>'+(j
== nFiles ?
'Total' : quoteHTML
(fileTags
[j
]))+'</TH>');
508 for (i
= 0; i
!= 3; i
++)
509 writeClassTableRow
(d
, row
, 2+i
*2, modeNamesUpper
[i
]+'s');
511 d
.writeln
('</TABLE>\n</BODY>\n</HTML>');
518 function stringSource
(s
) {
519 s
= s
.replace
(/\\/g, '\\\\');
520 s
= s
.replace
(/"/g, '\\"');
521 s
= s
.replace
(/<\//g, '<\\/');
525 function reloadSelf(n,m) {
526 // Need to cache these because globals go away on document.open().
528 var ss = stringSource;
529 var ct = classTables;
533 // Uncomment this and comment the document.open() line above to see the reloaded page's source
.
534 //var w
= window
.open("", "NewDoc");
535 //var d
= w
.document
;
536 //var document
= new Object
;
537 //document
.write = function
() {
538 // for (var i
= 0; i
< arguments
.length; i
++) {
539 // var s
= arguments
[i
].toString
();
540 // s
= s
.replace
(/&/g, '&');
541 // s
= s
.replace
(/\x3C/g, '<');
542 // s
= s
.replace
(/>/g, '>');
543 // s
= s
.replace
(/ /g, ' ');
547 //document
.writeln
= function
() {
548 // for (var i
= 0; i
< arguments
.length; i
++) {
549 // var s
= arguments
[i
].toString
();
550 // s
= s
.replace
(/&/g, '&');
551 // s
= s
.replace
(/\x3C/g, '<');
552 // s
= s
.replace
(/>/g, '>');
553 // s
= s
.replace
(/ /g, ' ');
556 // d
.writeln
('<BR>');
559 document
.writeln
('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
560 document
.writeln
('<HTML>\n<HEAD>\n<SCRIPT type="text/javascript">');
562 // Manually copy non
-persistent script data
564 document
.writeln
('var classTables = [];');
566 document
.writeln
('var classTables = [');
567 for (i
= 0; i
< ct
.length; i
++) {
569 document
.write('[' + ss
(row
[0]));
570 for (var j
= 1; j
< row
.length; j
++)
571 document
.write(',' + row
[j
]);
572 document
.writeln
(']' + (i
== ct
.length-1 ?
'];' : ','));
576 document
.writeln
('var srcArray = [');
577 for (i
= 0; i
< sa
.length; i
++) {
578 document
.write(ss
(sa
[i
]));
579 if (i
!= sa
.length-1)
580 document
.writeln
(',');
582 document
.writeln
('];');
583 document
.writeln
('eval(srcArray.join("\\n"));');
584 document
.writeln
('showMode = '+m
+';');
585 document
.writeln
('sortColumn = '+n
+';');
586 document
.writeln
('showHead(document);');
587 document
.writeln
('</SCRIPT>\n</HEAD>\n\n<BODY>\n<SCRIPT type="text/javascript">showBody(document);</SCRIPT>\n</BODY>\n</HTML>');