bug 422296: tools needed for debug+leak tests on mozilla-central - bloatdiff.pl + leaktest.py.in runner script. r=tmielczarek, patch=me
authorBen Hearsum <bhearsum@mozilla.com>
Tue, 13 May 2008 17:17:33 -0400
changeset 15117 c70da85c1383eea305909530f64f726b93e3d1dc
parent 15116 5195009caaf1d4caeed040c4c592651f5e314a00
child 15119 0fa5cdc6e062d50761298822f2d66a4ade6cc7b6
push idunknown
push userunknown
push dateunknown
reviewerstmielczarek
bugs422296
milestone2.0a1pre
bug 422296: tools needed for debug+leak tests on mozilla-central - bloatdiff.pl + leaktest.py.in runner script. r=tmielczarek, patch=me
build/Makefile.in
build/leaktest.py.in
tools/rb/bloatdiff.pl
--- a/build/Makefile.in
+++ b/build/Makefile.in
@@ -53,16 +53,56 @@ endif
 ifeq (WINNT,$(OS_ARCH))
 DIRS = win32
 endif
 
 DIRS += pgo
 
 include $(topsrcdir)/config/rules.mk
 
+AUTOMATION_PPARGS = 	\
+			-DBROWSER_PATH=$(browser_path) \
+			-DXPC_BIN_PATH=\"$(DIST)/bin\" \
+			$(NULL)
+
+ifeq ($(OS_ARCH),Darwin)
+AUTOMATION_PPARGS += -DIS_MAC=1
+else
+AUTOMATION_PPARGS += -DIS_MAC=0
+endif
+
+ifeq ($(MOZ_BUILD_APP),camino)
+AUTOMATION_PPARGS += -DIS_CAMINO=1
+else
+AUTOMATION_PPARGS += -DIS_CAMINO=0
+endif
+
+ifeq ($(host_os), cygwin)
+AUTOMATION_PPARGS += -DIS_CYGWIN=1
+endif
+
+_LEAKTEST_DIR = $(DEPTH)/_leaktest
+
+_LEAKTEST_FILES =    \
+		automation.py \
+		leaktest.py \
+		bloatcycle.html \
+		$(NULL)
+
+automation.py: $(topsrcdir)/build/pgo/automation.py.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
+	$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
+
+leaktest.py: leaktest.py.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $^ > $@
+	chmod +x $@
+
+libs:: $(_LEAKTEST_FILES)
+	$(INSTALL) $^ $(_LEAKTEST_DIR)
+
 ifdef ENABLE_TESTS
 # Install bloaturls.txt file for tinderbox bloat test.
 libs:: bloaturls.txt
 	$(INSTALL) $< $(DIST)/bin
 
 # Install bloatcycle.html into dist/bin/res, for auto-cycling
 # of bloaturls.txt.  This is for browsers that can't do -f
 # autocycling of URLs.
new file mode 100644
--- /dev/null
+++ b/build/leaktest.py.in
@@ -0,0 +1,85 @@
+#literal #!/usr/bin/python
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Robert Sayre <sayrer@gmail.com>
+#   Jeff Walden <jwalden+bmo@mit.edu>
+#   Ben Hearsum <bhearsum@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import SimpleHTTPServer
+import SocketServer
+import threading
+import os
+import sys
+import logging
+from getopt import getopt
+import automation
+
+PORT = 8888
+SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
+PROFILE_DIRECTORY = os.path.abspath(os.path.join(SCRIPT_DIR, "./leakprofile"))
+DIST_BIN = os.path.join(SCRIPT_DIR, automation.DIST_BIN)
+os.chdir(SCRIPT_DIR)
+
+class EasyServer(SocketServer.TCPServer):
+    allow_reuse_address = True
+
+if __name__ == '__main__':
+    opts, extraArgs = getopt(sys.argv[1:], 'l:')
+    if len(opts) > 0:
+        try:
+            automation.log.addHandler(logging.FileHandler(opts[0][1], "w"))
+        except:
+            automation.log.info("Unable to open logfile " + opts[0][1] + \
+                                "ONLY logging to stdout.")
+
+    httpd = EasyServer(("", PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
+    t = threading.Thread(target=httpd.serve_forever)
+    t.setDaemon(True)
+    t.start()
+
+    automation.initializeProfile(PROFILE_DIRECTORY)
+    browserEnv = dict(os.environ)
+
+    browserEnv["NO_EM_RESTART"] = "1"
+    browserEnv["XPCOM_DEBUG_BREAK"] = "warn"
+    if automation.UNIXISH:
+        browserEnv["LD_LIBRARY_PATH"] = os.path.join(SCRIPT_DIR, DIST_BIN)
+        browserEnv["MOZILLA_FIVE_HOME"] = os.path.join(SCRIPT_DIR, DIST_BIN)
+
+    automation.runApp("http://localhost:%d/bloatcycle.html" % PORT, browserEnv,
+                      os.path.join(SCRIPT_DIR, automation.DEFAULT_APP),
+                      PROFILE_DIRECTORY, extraArgs)
new file mode 100755
--- /dev/null
+++ b/tools/rb/bloatdiff.pl
@@ -0,0 +1,366 @@
+#!/usr/bin/perl -w
+
+################################################################################
+
+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} or 
+	! $newMap{"TOTAL"}{bloatPercent}) {
+        # it's OK if leaked or leakPercent are 0 (in fact, that would be good)
+	print "\nERROR - unable to calculate bloat/leak data.\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";