Bug 1855360 - Fix the skip-if syntax. a=bustage-fix
[gecko.git] / tools / bloatview / bloattable.pl
blobe8acfabed32eb967b37f98c97d4a0c00b0e3984a
1 #!/usr/bin/perl -w
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
19 use 5.004;
20 use strict;
21 use diagnostics;
22 use File::Basename;
23 use Getopt::Long;
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.
33 sub fileModDate($) {
34 my ($pathName) = @_;
35 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) =
36 stat $pathName or die "Can't stat '$pathName'";
37 return $mtime;
41 sub fileCoreName($) {
42 my ($pathName) = @_;
43 my $fileName = basename($pathName, "");
44 $fileName =~ s/\..*//;
45 return $fileName;
49 # Convert a raw string into a single-quoted JavaScript string.
50 sub singleQuoteString($) {
51 local ($_) = @_;
52 s/\\/\\\\/g;
53 s/'/\\'/g;
54 s/\n/\\n/g;
55 s/<\//<\\\//g;
56 return "'$_'";
60 # Convert a raw string into a double-quoted JavaScript string.
61 sub doubleQuoteString($) {
62 local ($_) = @_;
63 s/\\/\\\\/g;
64 s/"/\\"/g;
65 s/\n/\\n/g;
66 s/<\//<\\\//g;
67 return "\"$_\"";
71 # Quote special HTML characters in the string.
72 sub quoteHTML($) {
73 local ($_) = @_;
74 s/\&/&amp;/g;
75 s/</&lt;/g;
76 s/>/&gt;/g;
77 s/ /&nbsp;/g;
78 s/\n/<BR>\n/g;
79 return $_;
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>;
93 chomp @scriptSource;
94 print <<'EOS';
95 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
96 <HTML>
97 <HEAD>
98 <SCRIPT type="text/javascript">
99 EOS
101 foreach (@$scriptData) {print "$_\n";}
102 print "\n";
104 print "var srcArray = [\n";
105 my @quotedScriptSource = map {
106 my $line = $_;
107 $line =~ s/^\s+//g;
108 # $line =~ s/^\/\/SOURCE\s+//g if $source;
109 $line =~ s/^\/\/.*//g;
110 $line =~ s/\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";
117 if ($debug) {
118 push @quotedScriptSource, $lastQuotedLine;
119 foreach (@quotedScriptSource) {
120 s/<\//<\\\//g; # This fails if a regexp ends with a '<'. Oh well....
121 print "$_\n";
123 print "\n";
124 } else {
125 print "eval(srcArray.join(\"\\n\"));\n\n";
127 print "showMode = $showMode;\n";
128 print "sortColumn = $sortColumn;\n";
129 if ($source) {
130 print <<'EOS';
131 function writeQuotedHTML(s) {
132 document.write(quoteHTML(s.toString()).replace(/\n/g, '<BR>\n'));
135 var quotingDocument = {
136 write: function () {
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>');
147 } else {
148 print "showHead(document);\n";
150 print "</SCRIPT>\n";
151 print "</HEAD>\n\n";
152 print "<BODY>\n";
153 if ($source) {
154 print "<P><TT>";
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";
164 print "</TT></P>\n";
165 } else {
166 print "<SCRIPT type=\"text/javascript\">showBody(document);</SCRIPT>\n";
168 print "</BODY>\n";
169 print "</HTML>\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;
189 open FILE, $file;
190 while (<FILE>) {
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*$/) {
193 my $bytesAlloc;
194 if ($name eq "TOTAL") {
195 $size = "undefined";
196 $bytesAlloc = "undefined";
197 } else {
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;
209 $readSomething = 1;
210 } elsif (/^\s*(?:\d+)\s+([\w:]+)\s/) {
211 print STDERR "Unable to parse '$file' line: $_";
214 close FILE;
215 print STDERR "No data in '$file'\n" unless $readSomething;
216 return $h;
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($$) {
225 my ($l, $c) = @_;
226 my $lastE;
227 foreach (@ARGV) {
228 my $e = $tables{$_}{$c};
229 if (defined($lastE) && !defined($e)) {
230 $e = [0,0,0,0,0,0];
231 print STDERR "Class $c is defined in an earlier file but not in '$_'\n";
233 if (defined $e) {
234 if (defined $lastE) {
235 for (my $i = 0; $i <= $#$e; $i++) {
236 my $n = $$e[$i];
237 $l .= ($n eq "undefined" ? "undefined" : $n - $$lastE[$i]) . ",";
239 $l .= " ";
240 } else {
241 $l .= join(",", @$e) . ", ";
243 $lastE = $e;
244 } else {
245 $l .= "0,0,0,0,0,0, ";
248 $l .= join(",", @$lastE);
249 return "[$l]";
254 my $debug;
255 my $source;
256 my $showMode;
257 my $sortColumn;
258 my @modeOptions;
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];
263 if ($modeOption) {
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;
267 $showMode = $i;
268 if ($modeOption >= 2) {
269 $modeOption -= 2;
270 $sortColumn = 2 + $showMode*2;
271 if ($modeOption >= $nFileColumns) {
272 $sortColumn++;
273 $modeOption -= $nFileColumns;
275 $sortColumn += $modeOption*6;
276 } else {
277 $sortColumn = $modeOption;
281 unless (defined $showMode) {
282 $showMode = 0;
283 $sortColumn = 0;
286 # Read all of the bloat files.
287 foreach (@ARGV) {
288 unless ($tables{$_}) {
289 my $f = $_;
290 my %table;
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.
323 __END__
325 // showMode: 0=bytes, 1=objects, 2=references
326 var showMode;
327 var modeName;
328 var modeNameUpper;
330 var sortColumn;
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) {
336 if (sortColumn) {
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;
343 var x0 = x[0];
344 var y0 = y[0];
345 if (x0 > y0 || x0 === undefined && y0 !== undefined) return 1;
346 if (y0 > x0 || y0 === undefined && x0 !== undefined) return -1;
347 return 0;
351 // Quote special HTML characters in the string.
352 function quoteHTML(s) {
353 s = s.replace(/&/g, '&amp;');
354 // Can't use /</g because HTML interprets '</g' as ending the script!
355 s = s.replace(/\x3C/g, '&lt;');
356 s = s.replace(/>/g, '&gt;');
357 s = s.replace(/ /g, '&nbsp;');
358 return s;
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+')">');
375 d.write(s);
376 if (column != sortColumn)
377 d.write('</A>');
378 d.writeln('</TH>');
381 function writeClassTableRow(d, row, base, modeName) {
382 if (modeName) {
383 d.writeln('<TR>\n<TH>'+modeName+'</TH>');
384 } else {
385 d.writeln('<TR>\n<TD><A href="javascript:showRowDetail(\''+row[0]+'\')">'+quoteHTML(row[0])+'</A></TD>');
386 var v = row[1];
387 d.writeln('<TD class=num>'+(v === undefined ? '' : v)+'</TD>');
389 for (var i = 0; i != 2; i++) {
390 var c = base + i;
391 for (var j = 0; j <= nFiles; j++) {
392 v = row[c];
393 var style = 'num';
394 if (j != nFiles)
395 if (v > 0) {
396 style = 'pos';
397 v = '+'+v;
398 } else
399 style = 'neg';
400 d.writeln('<TD class='+style+'>'+(v === undefined ? '' : v)+'</TD>');
401 c += 6;
404 d.writeln('</TR>');
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>');
416 d.writeln('<TR>');
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>');
421 d.writeln('<TR>');
422 for (var i = 0; i != 2; i++) {
423 var c = base + i;
424 for (var j = 0; j <= nFiles; j++) {
425 writeReloadLink(d, c, j == nFiles ? 'Total' : quoteHTML(fileTags[j]), 1);
426 c += 6;
429 d.writeln('</TR>');
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'+
446 '</STYLE>';
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>');
458 writeFileTable(d);
459 d.write('<FORM>');
460 for (var i = 0; i != 3; i++)
461 if (i != showMode) {
462 var newSortColumn = sortColumn;
463 if (sortColumn >= 2)
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>');
470 writeClassTable(d);
474 function showRowDetail(rowName) {
475 var row;
476 var i;
478 if (rowName == "TOTAL")
479 row = totals;
480 else {
481 for (i = 0; i < classTables.length; i++)
482 if (rowName == classTables[i][0]) {
483 row = classTables[i];
484 break;
487 if (row) {
488 var w = window.open("", "ClassTableRowDetails");
489 var d = w.document;
490 d.open();
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>');
506 d.writeln('</TR>');
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>');
512 d.close();
514 return undefined;
518 function stringSource(s) {
519 s = s.replace(/\\/g, '\\\\');
520 s = s.replace(/"/g, '\\"');
521 s = s.replace(/<\//g, '<\\/');
522 return '"'+s+'"';
525 function reloadSelf(n,m) {
526 // Need to cache these because globals go away on document.open().
527 var sa = srcArray;
528 var ss = stringSource;
529 var ct = classTables;
530 var i;
532 document.open();
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, '&amp;');
541 // s = s.replace(/\x3C/g, '&lt;');
542 // s = s.replace(/>/g, '&gt;');
543 // s = s.replace(/ /g, '&nbsp;');
544 // d.write(s);
545 // }
546 //};
547 //document.writeln = function () {
548 // for (var i = 0; i < arguments.length; i++) {
549 // var s = arguments[i].toString();
550 // s = s.replace(/&/g, '&amp;');
551 // s = s.replace(/\x3C/g, '&lt;');
552 // s = s.replace(/>/g, '&gt;');
553 // s = s.replace(/ /g, '&nbsp;');
554 // d.write(s);
555 // }
556 // d.writeln('<BR>');
557 //};
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
563 if (!ct.length)
564 document.writeln('var classTables = [];');
565 else {
566 document.writeln('var classTables = [');
567 for (i = 0; i < ct.length; i++) {
568 var row = ct[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>');
588 document.close();
589 return undefined;