summaryrefslogtreecommitdiffstats
path: root/tools/bloatview
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /tools/bloatview
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/bloatview')
-rwxr-xr-xtools/bloatview/bloatdiff.pl372
-rwxr-xr-xtools/bloatview/bloattable.pl590
2 files changed, 962 insertions, 0 deletions
diff --git a/tools/bloatview/bloatdiff.pl b/tools/bloatview/bloatdiff.pl
new file mode 100755
index 0000000000..8c93ad2b00
--- /dev/null
+++ b/tools/bloatview/bloatdiff.pl
@@ -0,0 +1,372 @@
+#!/usr/bin/perl -w
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+################################################################################
+
+sub usage() {
+ print <<EOUSAGE;
+# bloatdiff.pl - munges the output from
+# XPCOM_MEM_BLOAT_LOG=1
+# firefox-bin -P default resource:///res/bloatcycle.html
+# so that it does some summary and stats stuff.
+#
+# To show leak test results for a set of changes, do something like this:
+#
+# XPCOM_MEM_BLOAT_LOG=1
+# firefox-bin -P default resource:///res/bloatcycle.html > a.out
+# **make change**
+# firefox-bin -P default resource:///res/bloatcycle.html > b.out
+# bloatdiff.pl a.out b.out
+
+EOUSAGE
+}
+
+$OLDFILE = $ARGV[0];
+$NEWFILE = $ARGV[1];
+#$LABEL = $ARGV[2];
+
+if (!$OLDFILE or
+ ! -e $OLDFILE or
+ -z $OLDFILE) {
+ print "\nError: Previous log file not specified, does not exist, or is empty.\n\n";
+ &usage();
+ exit 1;
+}
+
+if (!$NEWFILE or
+ ! -e $NEWFILE or
+ -z $NEWFILE) {
+ print "\nError: Current log file not specified, does not exist, or is empty.\n\n";
+ &usage();
+ exit 1;
+}
+
+sub processFile {
+ my ($filename, $map, $prevMap) = @_;
+ open(FH, $filename);
+ while (<FH>) {
+ if (m{
+ ^\s*(\d+)\s # Line number
+ ([\w:]+)\s+ # Name
+ (-?\d+)\s+ # Size
+ (-?\d+)\s+ # Leaked
+ (-?\d+)\s+ # Objects Total
+ (-?\d+)\s+ # Objects Rem
+ \(\s*(-?[\d.]+)\s+ # Objects Mean
+ \+/-\s+
+ ([\w.]+)\)\s+ # Objects StdDev
+ (-?\d+)\s+ # Reference Total
+ (-?\d+)\s+ # Reference Rem
+ \(\s*(-?[\d.]+)\s+ # Reference Mean
+ \+/-\s+
+ ([\w\.]+)\) # Reference StdDev
+ }x) {
+ $$map{$2} = { name => $2,
+ size => $3,
+ leaked => $4,
+ objTotal => $5,
+ objRem => $6,
+ objMean => $7,
+ objStdDev => $8,
+ refTotal => $9,
+ refRem => $10,
+ refMean => $11,
+ refStdDev => $12,
+ bloat => $3 * $5 # size * objTotal
+ };
+ } else {
+# print "failed to parse: $_\n";
+ }
+ }
+ close(FH);
+}
+
+%oldMap = ();
+processFile($OLDFILE, \%oldMap);
+
+%newMap = ();
+processFile($NEWFILE, \%newMap);
+
+################################################################################
+
+$inf = 9999999.99;
+
+sub getLeaksDelta {
+ my ($key) = @_;
+ my $oldLeaks = $oldMap{$key}{leaked} || 0;
+ my $newLeaks = $newMap{$key}{leaked};
+ my $percentLeaks = 0;
+ if ($oldLeaks == 0) {
+ if ($newLeaks != 0) {
+ # there weren't any leaks before, but now there are!
+ $percentLeaks = $inf;
+ }
+ }
+ else {
+ $percentLeaks = ($newLeaks - $oldLeaks) / $oldLeaks * 100;
+ }
+ # else we had no record of this class before
+ return ($newLeaks - $oldLeaks, $percentLeaks);
+}
+
+################################################################################
+
+sub getBloatDelta {
+ my ($key) = @_;
+ my $newBloat = $newMap{$key}{bloat};
+ my $percentBloat = 0;
+ my $oldSize = $oldMap{$key}{size} || 0;
+ my $oldTotal = $oldMap{$key}{objTotal} || 0;
+ my $oldBloat = $oldTotal * $oldSize;
+ if ($oldBloat == 0) {
+ if ($newBloat != 0) {
+ # this class wasn't used before, but now it is
+ $percentBloat = $inf;
+ }
+ }
+ else {
+ $percentBloat = ($newBloat - $oldBloat) / $oldBloat * 100;
+ }
+ # else we had no record of this class before
+ return ($newBloat - $oldBloat, $percentBloat);
+}
+
+################################################################################
+
+foreach $key (keys %newMap) {
+ my ($newLeaks, $percentLeaks) = getLeaksDelta($key);
+ my ($newBloat, $percentBloat) = getBloatDelta($key);
+ $newMap{$key}{leakDelta} = $newLeaks;
+ $newMap{$key}{leakPercent} = $percentLeaks;
+ $newMap{$key}{bloatDelta} = $newBloat;
+ $newMap{$key}{bloatPercent} = $percentBloat;
+}
+
+################################################################################
+
+# Print a value of bytes out in a reasonable
+# KB, MB, or GB form. Copied from build-seamonkey-util.pl, sorry. -mcafee
+sub PrintSize($) {
+
+ # print a number with 3 significant figures
+ sub PrintNum($) {
+ my ($num) = @_;
+ my $rv;
+ if ($num < 1) {
+ $rv = sprintf "%.3f", ($num);
+ } elsif ($num < 10) {
+ $rv = sprintf "%.2f", ($num);
+ } elsif ($num < 100) {
+ $rv = sprintf "%.1f", ($num);
+ } else {
+ $rv = sprintf "%d", ($num);
+ }
+ }
+
+ my ($size) = @_;
+ my $rv;
+ if ($size > 1000000000) {
+ $rv = PrintNum($size / 1000000000.0) . "G";
+ } elsif ($size > 1000000) {
+ $rv = PrintNum($size / 1000000.0) . "M";
+ } elsif ($size > 1000) {
+ $rv = PrintNum($size / 1000.0) . "K";
+ } else {
+ $rv = PrintNum($size);
+ }
+}
+
+
+print "Bloat/Leak Delta Report\n";
+print "--------------------------------------------------------------------------------------\n";
+print "Current file: $NEWFILE\n";
+print "Previous file: $OLDFILE\n";
+print "----------------------------------------------leaks------leaks%------bloat------bloat%\n";
+
+ if (! $newMap{"TOTAL"} or
+ ! $newMap{"TOTAL"}{bloat}) {
+ # It's OK if leaked or leakPercent are 0 (in fact, that would be good).
+ # If bloatPercent is zero, it is also OK, because we may have just had
+ # two runs exactly the same or with no new bloat.
+ print "\nError: unable to calculate bloat/leak data.\n";
+ print "There is no data present.\n\n";
+ print "HINT - Did your test run complete successfully?\n";
+ print "HINT - Are you pointing at the right log files?\n\n";
+ &usage();
+ exit 1;
+ }
+
+printf "%-40s %10s %10.2f%% %10s %10.2f%%\n",
+ ("TOTAL",
+ $newMap{"TOTAL"}{leaked}, $newMap{"TOTAL"}{leakPercent},
+ $newMap{"TOTAL"}{bloat}, $newMap{"TOTAL"}{bloatPercent});
+
+################################################################################
+
+sub percentStr {
+ my ($p) = @_;
+ if ($p == $inf) {
+ return "-";
+ }
+ else {
+ return sprintf "%10.2f%%", $p;
+ }
+}
+
+# NEW LEAKS
+@keys = sort { $newMap{$b}{leakPercent} <=> $newMap{$a}{leakPercent} } keys %newMap;
+my $needsHeading = 1;
+my $total = 0;
+foreach $key (@keys) {
+ my $percentLeaks = $newMap{$key}{leakPercent};
+ my $leaks = $newMap{$key}{leaked};
+ if ($percentLeaks > 0 && $key !~ /TOTAL/) {
+ if ($needsHeading) {
+ printf "--NEW-LEAKS-----------------------------------leaks------leaks%%-----------------------\n";
+ $needsHeading = 0;
+ }
+ printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
+ $total += $leaks;
+ }
+}
+if (!$needsHeading) {
+ printf "%-40s %10s\n", ("TOTAL", $total);
+}
+
+# FIXED LEAKS
+@keys = sort { $newMap{$b}{leakPercent} <=> $newMap{$a}{leakPercent} } keys %newMap;
+$needsHeading = 1;
+$total = 0;
+foreach $key (@keys) {
+ my $percentLeaks = $newMap{$key}{leakPercent};
+ my $leaks = $newMap{$key}{leaked};
+ if ($percentLeaks < 0 && $key !~ /TOTAL/) {
+ if ($needsHeading) {
+ printf "--FIXED-LEAKS---------------------------------leaks------leaks%%-----------------------\n";
+ $needsHeading = 0;
+ }
+ printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
+ $total += $leaks;
+ }
+}
+if (!$needsHeading) {
+ printf "%-40s %10s\n", ("TOTAL", $total);
+}
+
+# NEW BLOAT
+@keys = sort { $newMap{$b}{bloatPercent} <=> $newMap{$a}{bloatPercent} } keys %newMap;
+$needsHeading = 1;
+$total = 0;
+foreach $key (@keys) {
+ my $percentBloat = $newMap{$key}{bloatPercent};
+ my $bloat = $newMap{$key}{bloat};
+ if ($percentBloat > 0 && $key !~ /TOTAL/) {
+ if ($needsHeading) {
+ printf "--NEW-BLOAT-----------------------------------bloat------bloat%%-----------------------\n";
+ $needsHeading = 0;
+ }
+ printf "%-40s %10s %10s\n", ($key, $bloat, percentStr($percentBloat));
+ $total += $bloat;
+ }
+}
+if (!$needsHeading) {
+ printf "%-40s %10s\n", ("TOTAL", $total);
+}
+
+# ALL LEAKS
+@keys = sort { $newMap{$b}{leaked} <=> $newMap{$a}{leaked} } keys %newMap;
+$needsHeading = 1;
+$total = 0;
+foreach $key (@keys) {
+ my $leaks = $newMap{$key}{leaked};
+ my $percentLeaks = $newMap{$key}{leakPercent};
+ if ($leaks > 0) {
+ if ($needsHeading) {
+ printf "--ALL-LEAKS-----------------------------------leaks------leaks%%-----------------------\n";
+ $needsHeading = 0;
+ }
+ printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
+ if ($key !~ /TOTAL/) {
+ $total += $leaks;
+ }
+ }
+}
+if (!$needsHeading) {
+# printf "%-40s %10s\n", ("TOTAL", $total);
+}
+
+# ALL BLOAT
+@keys = sort { $newMap{$b}{bloat} <=> $newMap{$a}{bloat} } keys %newMap;
+$needsHeading = 1;
+$total = 0;
+foreach $key (@keys) {
+ my $bloat = $newMap{$key}{bloat};
+ my $percentBloat = $newMap{$key}{bloatPercent};
+ if ($bloat > 0) {
+ if ($needsHeading) {
+ printf "--ALL-BLOAT-----------------------------------bloat------bloat%%-----------------------\n";
+ $needsHeading = 0;
+ }
+ printf "%-40s %10s %10s\n", ($key, $bloat, percentStr($percentBloat));
+ if ($key !~ /TOTAL/) {
+ $total += $bloat;
+ }
+ }
+}
+if (!$needsHeading) {
+# printf "%-40s %10s\n", ("TOTAL", $total);
+}
+
+# NEW CLASSES
+@keys = sort { $newMap{$b}{bloatDelta} <=> $newMap{$a}{bloatDelta} } keys %newMap;
+$needsHeading = 1;
+my $ltotal = 0;
+my $btotal = 0;
+foreach $key (@keys) {
+ my $leaks = $newMap{$key}{leaked};
+ my $bloat = $newMap{$key}{bloat};
+ my $percentBloat = $newMap{$key}{bloatPercent};
+ if ($percentBloat == $inf && $key !~ /TOTAL/) {
+ if ($needsHeading) {
+ printf "--CLASSES-NOT-REPORTED-LAST-TIME--------------leaks------bloat------------------------\n";
+ $needsHeading = 0;
+ }
+ printf "%-40s %10s %10s\n", ($key, $leaks, $bloat);
+ if ($key !~ /TOTAL/) {
+ $ltotal += $leaks;
+ $btotal += $bloat;
+ }
+ }
+}
+if (!$needsHeading) {
+ printf "%-40s %10s %10s\n", ("TOTAL", $ltotal, $btotal);
+}
+
+# OLD CLASSES
+@keys = sort { ($oldMap{$b}{bloat} || 0) <=> ($oldMap{$a}{bloat} || 0) } keys %oldMap;
+$needsHeading = 1;
+$ltotal = 0;
+$btotal = 0;
+foreach $key (@keys) {
+ if (!defined($newMap{$key})) {
+ my $leaks = $oldMap{$key}{leaked};
+ my $bloat = $oldMap{$key}{bloat};
+ if ($needsHeading) {
+ printf "--CLASSES-THAT-WENT-AWAY----------------------leaks------bloat------------------------\n";
+ $needsHeading = 0;
+ }
+ printf "%-40s %10s %10s\n", ($key, $leaks, $bloat);
+ if ($key !~ /TOTAL/) {
+ $ltotal += $leaks;
+ $btotal += $bloat;
+ }
+ }
+}
+if (!$needsHeading) {
+ printf "%-40s %10s %10s\n", ("TOTAL", $ltotal, $btotal);
+}
+
+print "--------------------------------------------------------------------------------------\n";
diff --git a/tools/bloatview/bloattable.pl b/tools/bloatview/bloattable.pl
new file mode 100755
index 0000000000..e8acfabed3
--- /dev/null
+++ b/tools/bloatview/bloattable.pl
@@ -0,0 +1,590 @@
+#!/usr/bin/perl -w
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# bloattable [-debug] [-source] [-byte n|-obj n|-ref n] <file1> <file2> ... <filen> > <html-file>
+#
+# file1, file2, ... filen should be successive BloatView files generated from the same run.
+# Summarize them in an HTML table. Output the HTML to the standard output.
+#
+# If -debug is set, create a slightly larger html file which is more suitable for debugging this script.
+# If -source is set, create an html file that prints the html source as the output
+# If -byte n, -obj n, or -ref n is given, make the page default to showing byte, object, or reference statistics,
+# respectively, and sort by the nth column (n is zero-based, so the first column has n==0).
+#
+# See http://lxr.mozilla.org/mozilla/source/xpcom/doc/MemoryTools.html
+
+use 5.004;
+use strict;
+use diagnostics;
+use File::Basename;
+use Getopt::Long;
+
+# The generated HTML is almost entirely generated by a script. Only the <HTML>, <HEAD>, and <BODY> elements are explicit
+# because a <SCRIPT> element cannot officially be a direct descendant of an <HTML> element.
+# The script itself is almost all generated by an eval of a large string. This allows the script to reproduce itself
+# when making a new page using document.write's. Re-sorting the page causes it to regenerate itself in this way.
+
+
+
+# Return the file's modification date.
+sub fileModDate($) {
+ my ($pathName) = @_;
+ my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) =
+ stat $pathName or die "Can't stat '$pathName'";
+ return $mtime;
+}
+
+
+sub fileCoreName($) {
+ my ($pathName) = @_;
+ my $fileName = basename($pathName, "");
+ $fileName =~ s/\..*//;
+ return $fileName;
+}
+
+
+# Convert a raw string into a single-quoted JavaScript string.
+sub singleQuoteString($) {
+ local ($_) = @_;
+ s/\\/\\\\/g;
+ s/'/\\'/g;
+ s/\n/\\n/g;
+ s/<\//<\\\//g;
+ return "'$_'";
+}
+
+
+# Convert a raw string into a double-quoted JavaScript string.
+sub doubleQuoteString($) {
+ local ($_) = @_;
+ s/\\/\\\\/g;
+ s/"/\\"/g;
+ s/\n/\\n/g;
+ s/<\//<\\\//g;
+ return "\"$_\"";
+}
+
+
+# Quote special HTML characters in the string.
+sub quoteHTML($) {
+ local ($_) = @_;
+ s/\&/&amp;/g;
+ s/</&lt;/g;
+ s/>/&gt;/g;
+ s/ /&nbsp;/g;
+ s/\n/<BR>\n/g;
+ return $_;
+}
+
+
+# Write the generated page to the standard output.
+# The script source code is read from this file past the __END__ marker
+# @$scriptData is the JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
+# @$persistentScriptData is the same as @scriptData, but persists when the page reloads itself.
+# If $debug is true, generate the script directly instead of having it eval itself.
+# If $source is true, generate a script that displays the page's source instead of the page itself.
+sub generate(\@\@$$$$) {
+ my ($scriptData, $persistentScriptData, $debug, $source, $showMode, $sortColumn) = @_;
+
+ my @scriptSource = <DATA>;
+ chomp @scriptSource;
+ print <<'EOS';
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
+<HTML>
+<HEAD>
+<SCRIPT type="text/javascript">
+EOS
+
+ foreach (@$scriptData) {print "$_\n";}
+ print "\n";
+
+ print "var srcArray = [\n";
+ my @quotedScriptSource = map {
+ my $line = $_;
+ $line =~ s/^\s+//g;
+ # $line =~ s/^\/\/SOURCE\s+//g if $source;
+ $line =~ s/^\/\/.*//g;
+ $line =~ s/\s+$//g;
+ $line eq "" ? () : $line
+ } @$persistentScriptData, @scriptSource;
+ my $lastQuotedLine = pop @quotedScriptSource;
+ foreach (@quotedScriptSource) {print doubleQuoteString($_), ",\n";}
+ print doubleQuoteString($lastQuotedLine), "];\n\n";
+
+ if ($debug) {
+ push @quotedScriptSource, $lastQuotedLine;
+ foreach (@quotedScriptSource) {
+ s/<\//<\\\//g; # This fails if a regexp ends with a '<'. Oh well....
+ print "$_\n";
+ }
+ print "\n";
+ } else {
+ print "eval(srcArray.join(\"\\n\"));\n\n";
+ }
+ print "showMode = $showMode;\n";
+ print "sortColumn = $sortColumn;\n";
+ if ($source) {
+ print <<'EOS';
+function writeQuotedHTML(s) {
+ document.write(quoteHTML(s.toString()).replace(/\n/g, '<BR>\n'));
+}
+
+var quotingDocument = {
+ write: function () {
+ for (var i = 0; i < arguments.length; i++)
+ writeQuotedHTML(arguments[i]);
+ },
+ writeln: function () {
+ for (var i = 0; i < arguments.length; i++)
+ writeQuotedHTML(arguments[i]);
+ document.writeln('<BR>');
+ }
+};
+EOS
+ } else {
+ print "showHead(document);\n";
+ }
+ print "</SCRIPT>\n";
+ print "</HEAD>\n\n";
+ print "<BODY>\n";
+ if ($source) {
+ print "<P><TT>";
+ print quoteHTML "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n";
+ print quoteHTML "<HTML>\n";
+ print quoteHTML "<HEAD>\n";
+ print "<SCRIPT type=\"text/javascript\">showHead(quotingDocument);</SCRIPT>\n";
+ print quoteHTML "</HEAD>\n\n";
+ print quoteHTML "<BODY>\n";
+ print "<SCRIPT type=\"text/javascript\">showBody(quotingDocument);</SCRIPT>\n";
+ print quoteHTML "</BODY>\n";
+ print quoteHTML "</HTML>\n";
+ print "</TT></P>\n";
+ } else {
+ print "<SCRIPT type=\"text/javascript\">showBody(document);</SCRIPT>\n";
+ }
+ print "</BODY>\n";
+ print "</HTML>\n";
+}
+
+
+
+# Read the bloat file into hash table $h. The hash table is indexed by class names;
+# each entry is a list with the following elements:
+# bytesAlloc Total number of bytes allocated
+# bytesNet Total number of bytes allocated but not deallocated
+# objectsAlloc Total number of objects allocated
+# objectsNet Total number of objects allocated but not deallocated
+# refsAlloc Total number of references AddRef'd
+# refsNet Total number of references AddRef'd but not Released
+# Except for TOTAL, all hash table entries refer to mutually exclusive data.
+# $sizes is a hash table indexed by class names. Each entry of that table contains the class's instance size.
+sub readBloatFile($\%\%) {
+ my ($file, $h, $sizes) = @_;
+ local $_; # Needed for 'while (<FILE>)' below.
+
+ my $readSomething = 0;
+ open FILE, $file;
+ while (<FILE>) {
+ if (my ($name, $size, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet) =
+ /^\s*(?:\d+)\s+([\w:]+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(-?\d+)\s*\([^()]*\)\s*(\d+)\s+(-?\d+)\s*\([^()]*\)\s*$/) {
+ my $bytesAlloc;
+ if ($name eq "TOTAL") {
+ $size = "undefined";
+ $bytesAlloc = "undefined";
+ } else {
+ $bytesAlloc = $objectsAlloc * $size;
+ if ($bytesNet != $objectsNet * $size) {
+ print STDERR "In '$file', class $name bytesNet != objectsNet * size: $bytesNet != $objectsNet * $size\n";
+ }
+ }
+ print STDERR "Duplicate entry $name in '$file'\n" if $$h{$name};
+ $$h{$name} = [$bytesAlloc, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet];
+
+ my $oldSize = $$sizes{$name};
+ print STDERR "Mismatch of sizes of class $name: $oldSize and $size\n" if defined($oldSize) && $size ne $oldSize;
+ $$sizes{$name} = $size;
+ $readSomething = 1;
+ } elsif (/^\s*(?:\d+)\s+([\w:]+)\s/) {
+ print STDERR "Unable to parse '$file' line: $_";
+ }
+ }
+ close FILE;
+ print STDERR "No data in '$file'\n" unless $readSomething;
+ return $h;
+}
+
+
+my %sizes; # <class-name> => <instance-size>
+my %tables; # <file-name> => <bloat-table>; see readBloatFile for format of <bloat-table>
+
+# Generate the JavaScript source code for the row named $c. $l can contain the initial entries of the row.
+sub genTableRowSource($$) {
+ my ($l, $c) = @_;
+ my $lastE;
+ foreach (@ARGV) {
+ my $e = $tables{$_}{$c};
+ if (defined($lastE) && !defined($e)) {
+ $e = [0,0,0,0,0,0];
+ print STDERR "Class $c is defined in an earlier file but not in '$_'\n";
+ }
+ if (defined $e) {
+ if (defined $lastE) {
+ for (my $i = 0; $i <= $#$e; $i++) {
+ my $n = $$e[$i];
+ $l .= ($n eq "undefined" ? "undefined" : $n - $$lastE[$i]) . ",";
+ }
+ $l .= " ";
+ } else {
+ $l .= join(",", @$e) . ", ";
+ }
+ $lastE = $e;
+ } else {
+ $l .= "0,0,0,0,0,0, ";
+ }
+ }
+ $l .= join(",", @$lastE);
+ return "[$l]";
+}
+
+
+
+my $debug;
+my $source;
+my $showMode;
+my $sortColumn;
+my @modeOptions;
+
+GetOptions("debug" => \$debug, "source" => \$source, "byte=i" => \$modeOptions[0], "obj=i" => \$modeOptions[1], "ref=i" => \$modeOptions[2]);
+for (my $i = 0; $i != 3; $i++) {
+ my $modeOption = $modeOptions[$i];
+ if ($modeOption) {
+ die "Only one of -byte, -obj, or -ref may be given" if defined $showMode;
+ my $nFileColumns = scalar(@ARGV) + 1;
+ die "-byte, -obj, or -ref column number out of range" if $modeOption < 0 || $modeOption >= 2 + 2*$nFileColumns;
+ $showMode = $i;
+ if ($modeOption >= 2) {
+ $modeOption -= 2;
+ $sortColumn = 2 + $showMode*2;
+ if ($modeOption >= $nFileColumns) {
+ $sortColumn++;
+ $modeOption -= $nFileColumns;
+ }
+ $sortColumn += $modeOption*6;
+ } else {
+ $sortColumn = $modeOption;
+ }
+ }
+}
+unless (defined $showMode) {
+ $showMode = 0;
+ $sortColumn = 0;
+}
+
+# Read all of the bloat files.
+foreach (@ARGV) {
+ unless ($tables{$_}) {
+ my $f = $_;
+ my %table;
+
+ readBloatFile $_, %table, %sizes;
+ $tables{$_} = \%table;
+ }
+}
+die "No input" unless %sizes;
+
+my @scriptData; # JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
+my @persistentScriptData; # Same as @scriptData, but persists the page reloads itself.
+
+# Print a list of bloat file names.
+push @persistentScriptData, "var nFiles = " . scalar(@ARGV) . ";";
+push @persistentScriptData, "var fileTags = [" . join(", ", map {singleQuoteString substr(fileCoreName($_), -10)} @ARGV) . "];";
+push @persistentScriptData, "var fileNames = [" . join(", ", map {singleQuoteString $_} @ARGV) . "];";
+push @persistentScriptData, "var fileDates = [" . join(", ", map {singleQuoteString localtime fileModDate $_} @ARGV) . "];";
+
+# Print the bloat tables.
+push @persistentScriptData, "var totals = " . genTableRowSource('"TOTAL", undefined, ', "TOTAL") . ";";
+push @scriptData, "var classTables = [";
+delete $sizes{"TOTAL"};
+my @classes = sort(keys %sizes);
+for (my $i = 0; $i <= $#classes; $i++) {
+ my $c = $classes[$i];
+ push @scriptData, genTableRowSource(doubleQuoteString($c).", ".$sizes{$c}.", ", $c) . ($i == $#classes ? "];" : ",");
+}
+
+generate(@scriptData, @persistentScriptData, $debug, $source, $showMode, $sortColumn);
+1;
+
+
+# The source of the eval'd JavaScript follows.
+# Comments starting with // that are alone on a line are stripped by the Perl script.
+__END__
+
+// showMode: 0=bytes, 1=objects, 2=references
+var showMode;
+var modeName;
+var modeNameUpper;
+
+var sortColumn;
+
+// Sort according to the sortColumn. Column 0 is sorted alphabetically in ascending order.
+// All other columns are sorted numerically in descending order, with column 0 used for a secondary sort.
+// Undefined is always listed last.
+function sortCompare(x, y) {
+ if (sortColumn) {
+ var xc = x[sortColumn];
+ var yc = y[sortColumn];
+ if (xc < yc || xc === undefined && yc !== undefined) return 1;
+ if (yc < xc || yc === undefined && xc !== undefined) return -1;
+ }
+
+ var x0 = x[0];
+ var y0 = y[0];
+ if (x0 > y0 || x0 === undefined && y0 !== undefined) return 1;
+ if (y0 > x0 || y0 === undefined && x0 !== undefined) return -1;
+ return 0;
+}
+
+
+// Quote special HTML characters in the string.
+function quoteHTML(s) {
+ s = s.replace(/&/g, '&amp;');
+ // Can't use /</g because HTML interprets '</g' as ending the script!
+ s = s.replace(/\x3C/g, '&lt;');
+ s = s.replace(/>/g, '&gt;');
+ s = s.replace(/ /g, '&nbsp;');
+ return s;
+}
+
+
+function writeFileTable(d) {
+ d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
+ d.writeln('<TR>\n<TH>Name</TH>\n<TH>File</TH>\n<TH>Date</TH>\n</TR>');
+ for (var i = 0; i < nFiles; i++)
+ 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>');
+ d.writeln('</TABLE>');
+}
+
+
+function writeReloadLink(d, column, s, rowspan) {
+ d.write(rowspan == 1 ? '<TH>' : '<TH rowspan='+rowspan+'>');
+ if (column != sortColumn)
+ d.write('<A href="javascript:reloadSelf('+column+','+showMode+')">');
+ d.write(s);
+ if (column != sortColumn)
+ d.write('</A>');
+ d.writeln('</TH>');
+}
+
+function writeClassTableRow(d, row, base, modeName) {
+ if (modeName) {
+ d.writeln('<TR>\n<TH>'+modeName+'</TH>');
+ } else {
+ d.writeln('<TR>\n<TD><A href="javascript:showRowDetail(\''+row[0]+'\')">'+quoteHTML(row[0])+'</A></TD>');
+ var v = row[1];
+ d.writeln('<TD class=num>'+(v === undefined ? '' : v)+'</TD>');
+ }
+ for (var i = 0; i != 2; i++) {
+ var c = base + i;
+ for (var j = 0; j <= nFiles; j++) {
+ v = row[c];
+ var style = 'num';
+ if (j != nFiles)
+ if (v > 0) {
+ style = 'pos';
+ v = '+'+v;
+ } else
+ style = 'neg';
+ d.writeln('<TD class='+style+'>'+(v === undefined ? '' : v)+'</TD>');
+ c += 6;
+ }
+ }
+ d.writeln('</TR>');
+}
+
+function writeClassTable(d) {
+ var base = 2 + showMode*2;
+
+ // Make a copy because a sort is destructive.
+ var table = classTables.concat();
+ table.sort(sortCompare);
+
+ d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
+
+ d.writeln('<TR>');
+ writeReloadLink(d, 0, 'Class Name', 2);
+ writeReloadLink(d, 1, 'Instance<BR>Size', 2);
+ d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated</TH>');
+ d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated but not freed</TH>\n</TR>');
+ d.writeln('<TR>');
+ for (var i = 0; i != 2; i++) {
+ var c = base + i;
+ for (var j = 0; j <= nFiles; j++) {
+ writeReloadLink(d, c, j == nFiles ? 'Total' : quoteHTML(fileTags[j]), 1);
+ c += 6;
+ }
+ }
+ d.writeln('</TR>');
+
+ writeClassTableRow(d, totals, base, 0);
+ for (var r = 0; r < table.length; r++)
+ writeClassTableRow(d, table[r], base, 0);
+
+ d.writeln('</TABLE>');
+}
+
+
+var modeNames = ["byte", "object", "reference"];
+var modeNamesUpper = ["Byte", "Object", "Reference"];
+var styleSheet = '<STYLE type="TEXT/CSS">\n'+
+ 'BODY {background-color: #FFFFFF; color: #000000}\n'+
+ '.num {text-align: right}\n'+
+ '.pos {text-align: right; color: #CC0000}\n'+
+ '.neg {text-align: right; color: #009900}\n'+
+ '</STYLE>';
+
+
+function showHead(d) {
+ modeName = modeNames[showMode];
+ modeNameUpper = modeNamesUpper[showMode];
+ d.writeln('<TITLE>'+modeNameUpper+' Bloats</TITLE>');
+ d.writeln(styleSheet);
+}
+
+function showBody(d) {
+ d.writeln('<H1>'+modeNameUpper+' Bloats</H1>');
+ writeFileTable(d);
+ d.write('<FORM>');
+ for (var i = 0; i != 3; i++)
+ if (i != showMode) {
+ var newSortColumn = sortColumn;
+ if (sortColumn >= 2)
+ newSortColumn = sortColumn + (i-showMode)*2;
+ d.write('<INPUT type="button" value="Show '+modeNamesUpper[i]+'s" onClick="reloadSelf('+newSortColumn+','+i+')">');
+ }
+ d.writeln('</FORM>');
+ d.writeln('<P>The numbers do not include <CODE>malloc</CODE>\'d data such as string contents.</P>');
+ 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>');
+ writeClassTable(d);
+}
+
+
+function showRowDetail(rowName) {
+ var row;
+ var i;
+
+ if (rowName == "TOTAL")
+ row = totals;
+ else {
+ for (i = 0; i < classTables.length; i++)
+ if (rowName == classTables[i][0]) {
+ row = classTables[i];
+ break;
+ }
+ }
+ if (row) {
+ var w = window.open("", "ClassTableRowDetails");
+ var d = w.document;
+ d.open();
+ d.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
+ d.writeln('<HTML>\n<HEAD>\n<TITLE>'+quoteHTML(rowName)+' bloat details</TITLE>');
+ d.writeln(styleSheet);
+ d.writeln('</HEAD>\n\n<BODY>');
+ d.writeln('<H2>'+quoteHTML(rowName)+'</H2>');
+ if (row[1] !== undefined)
+ d.writeln('<P>Each instance has '+row[1]+' bytes.</P>');
+
+ d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
+ d.writeln('<TR>\n<TH></TH>\n<TH colspan='+(nFiles+1)+'>Allocated</TH>');
+ d.writeln('<TH colspan='+(nFiles+1)+'>Allocated but not freed</TH>\n</TR>');
+ d.writeln('<TR>\n<TH></TH>');
+ for (i = 0; i != 2; i++)
+ for (var j = 0; j <= nFiles; j++)
+ d.writeln('<TH>'+(j == nFiles ? 'Total' : quoteHTML(fileTags[j]))+'</TH>');
+ d.writeln('</TR>');
+
+ for (i = 0; i != 3; i++)
+ writeClassTableRow(d, row, 2+i*2, modeNamesUpper[i]+'s');
+
+ d.writeln('</TABLE>\n</BODY>\n</HTML>');
+ d.close();
+ }
+ return undefined;
+}
+
+
+function stringSource(s) {
+ s = s.replace(/\\/g, '\\\\');
+ s = s.replace(/"/g, '\\"');
+ s = s.replace(/<\//g, '<\\/');
+ return '"'+s+'"';
+}
+
+function reloadSelf(n,m) {
+ // Need to cache these because globals go away on document.open().
+ var sa = srcArray;
+ var ss = stringSource;
+ var ct = classTables;
+ var i;
+
+ document.open();
+ // Uncomment this and comment the document.open() line above to see the reloaded page's source.
+ //var w = window.open("", "NewDoc");
+ //var d = w.document;
+ //var document = new Object;
+ //document.write = function () {
+ // for (var i = 0; i < arguments.length; i++) {
+ // var s = arguments[i].toString();
+ // s = s.replace(/&/g, '&amp;');
+ // s = s.replace(/\x3C/g, '&lt;');
+ // s = s.replace(/>/g, '&gt;');
+ // s = s.replace(/ /g, '&nbsp;');
+ // d.write(s);
+ // }
+ //};
+ //document.writeln = function () {
+ // for (var i = 0; i < arguments.length; i++) {
+ // var s = arguments[i].toString();
+ // s = s.replace(/&/g, '&amp;');
+ // s = s.replace(/\x3C/g, '&lt;');
+ // s = s.replace(/>/g, '&gt;');
+ // s = s.replace(/ /g, '&nbsp;');
+ // d.write(s);
+ // }
+ // d.writeln('<BR>');
+ //};
+
+ document.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
+ document.writeln('<HTML>\n<HEAD>\n<SCRIPT type="text/javascript">');
+
+ // Manually copy non-persistent script data
+ if (!ct.length)
+ document.writeln('var classTables = [];');
+ else {
+ document.writeln('var classTables = [');
+ for (i = 0; i < ct.length; i++) {
+ var row = ct[i];
+ document.write('[' + ss(row[0]));
+ for (var j = 1; j < row.length; j++)
+ document.write(',' + row[j]);
+ document.writeln(']' + (i == ct.length-1 ? '];' : ','));
+ }
+ }
+
+ document.writeln('var srcArray = [');
+ for (i = 0; i < sa.length; i++) {
+ document.write(ss(sa[i]));
+ if (i != sa.length-1)
+ document.writeln(',');
+ }
+ document.writeln('];');
+ document.writeln('eval(srcArray.join("\\n"));');
+ document.writeln('showMode = '+m+';');
+ document.writeln('sortColumn = '+n+';');
+ document.writeln('showHead(document);');
+ document.writeln('</SCRIPT>\n</HEAD>\n\n<BODY>\n<SCRIPT type="text/javascript">showBody(document);</SCRIPT>\n</BODY>\n</HTML>');
+ document.close();
+ return undefined;
+}