summaryrefslogtreecommitdiffstats
path: root/tools/leak-gauge
diff options
context:
space:
mode:
Diffstat (limited to 'tools/leak-gauge')
-rw-r--r--tools/leak-gauge/leak-gauge.html302
-rwxr-xr-xtools/leak-gauge/leak-gauge.pl239
2 files changed, 541 insertions, 0 deletions
diff --git a/tools/leak-gauge/leak-gauge.html b/tools/leak-gauge/leak-gauge.html
new file mode 100644
index 000000000..74f24fd40
--- /dev/null
+++ b/tools/leak-gauge/leak-gauge.html
@@ -0,0 +1,302 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<!--
+ vim:sw=4:ts=4:et:
+ 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/.
+-->
+<html lang="en-US">
+<head>
+<meta charset="UTF-8" />
+<title>Leak Gauge</title>
+
+<style type="text/css">
+pre { margin: 0; }
+pre.output { border: medium solid; padding: 1em; margin: 1em; }
+</style>
+<script type="text/javascript">
+
+function runfile(file) {
+ var result = "Results of processing log " + file.fileName + " :\n";
+
+ var fileReader = new FileReader();
+ fileReader.onload = function(e)
+ {
+ runContents(result, e.target.result);
+ }
+ fileReader.readAsText(file, "iso-8859-1");
+}
+
+function runContents(result, contents) {
+ // A hash of objects (keyed by the first word of the line in the log)
+ // that have two public methods, handle_line and dump (to be called using
+ // call, above), along with any private data they need.
+ var handlers = {
+ "DOMWINDOW": {
+ count: 0,
+ windows: {},
+ handle_line: function(line) {
+ var match = line.match(/^([0-9a-f]*) (\S*)(.*)/);
+ if (match) {
+ var addr = match[1];
+ var verb = match[2];
+ var rest = match[3];
+ if (verb == "created") {
+ var m = rest.match(/ outer=([0-9a-f]*)$/);
+ if (!m)
+ throw "outer expected";
+ this.windows[addr] = { outer: m[1] };
+ ++this.count;
+ } else if (verb == "destroyed") {
+ delete this.windows[addr];
+ } else if (verb == "SetNewDocument") {
+ var m = rest.match(/^ (.*)$/);
+ if (!m)
+ throw "URI expected";
+ this.windows[addr][m[1]] = true;
+ }
+ }
+ },
+ dump: function() {
+ for (var addr in this.windows) {
+ var winobj = this.windows[addr];
+ var outer = winobj.outer;
+ delete winobj.outer;
+ result += "Leaked " + (outer == "0" ? "outer" : "inner") +
+ " window " + addr + " " +
+ (outer == "0" ? "" : "(outer " + outer + ") ") +
+ "at address " + addr + ".\n";
+ for (var uri in winobj) {
+ result += " ... with URI \"" + uri + "\".\n";
+ }
+ }
+ },
+ summary: function() {
+ var len = 0;
+ for (var w in this.windows)
+ ++len;
+ result += 'Leaked ' + len + ' out of ' +
+ this.count + " DOM Windows\n";
+ }
+ },
+ "DOCUMENT": {
+ count: 0,
+ docs: {},
+ handle_line: function(line) {
+ var match = line.match(/^([0-9a-f]*) (\S*)(.*)/);
+ if (match) {
+ var addr = match[1];
+ var verb = match[2];
+ var rest = match[3];
+ if (verb == "created") {
+ this.docs[addr] = {};
+ ++this.count;
+ } else if (verb == "destroyed") {
+ delete this.docs[addr];
+ } else if (verb == "ResetToURI" ||
+ verb == "StartDocumentLoad") {
+ var m = rest.match(/^ (.*)$/);
+ if (!m)
+ throw "URI expected";
+ var uri = m[1];
+ var doc_info = this.docs[addr];
+ doc_info[uri] = true;
+ if ("nim" in doc_info) {
+ doc_info["nim"][uri] = true;
+ }
+ }
+ }
+ },
+ dump: function() {
+ for (var addr in this.docs) {
+ var doc = this.docs[addr];
+ result += "Leaked document at address " + addr + ".\n";
+ for (var uri in doc) {
+ if (uri != "nim") {
+ result += " ... with URI \"" + uri + "\".\n";
+ }
+ }
+ }
+ },
+ summary: function() {
+ var len = 0;
+ for (var w in this.docs)
+ ++len;
+ result += 'Leaked ' + len + ' out of ' +
+ this.count + " documents\n";
+ }
+ },
+ "DOCSHELL": {
+ count: 0,
+ shells: {},
+ handle_line: function(line) {
+ var match = line.match(/^([0-9a-f]*) (\S*)(.*)/);
+ if (match) {
+ var addr = match[1];
+ var verb = match[2];
+ var rest = match[3];
+ if (verb == "created") {
+ this.shells[addr] = {};
+ ++this.count;
+ } else if (verb == "destroyed") {
+ delete this.shells[addr];
+ } else if (verb == "InternalLoad" ||
+ verb == "SetCurrentURI") {
+ var m = rest.match(/^ (.*)$/);
+ if (!m)
+ throw "URI expected";
+ this.shells[addr][m[1]] = true;
+ }
+ }
+ },
+ dump: function() {
+ for (var addr in this.shells) {
+ var doc = this.shells[addr];
+ result += "Leaked docshell at address " + addr + ".\n";
+ for (var uri in doc) {
+ result += " ... which loaded URI \"" + uri + "\".\n";
+ }
+ }
+ },
+ summary: function() {
+ var len = 0;
+ for (var w in this.shells)
+ ++len;
+ result += 'Leaked ' + len + ' out of ' +
+ this.count + " docshells\n";
+ }
+ },
+ "NODEINFOMANAGER": {
+ count: 0,
+ nims: {},
+ handle_line: function(line) {
+ var match = line.match(/^([0-9a-f]*) (\S*)(.*)/);
+ if (match) {
+ var addr = match[1];
+ var verb = match[2];
+ var rest = match[3];
+ if (verb == "created") {
+ this.nims[addr] = {};
+ ++this.count;
+ } else if (verb == "destroyed") {
+ delete this.nims[addr];
+ } else if (verb == "Init") {
+ var m = rest.match(/^ document=(.*)$/);
+ if (!m)
+ throw "document pointer expected";
+ var nim_info = this.nims[addr];
+ var doc = m[1];
+ if (doc != "0") {
+ var doc_info = handlers["DOCUMENT"].docs[doc];
+ for (var uri in doc_info) {
+ nim_info[uri] = true;
+ }
+ doc_info["nim"] = nim_info;
+ }
+ }
+ }
+ },
+ dump: function() {
+ for (var addr in this.nims) {
+ var nim = this.nims[addr];
+ result += "Leaked content nodes associated with node info manager at address " + addr + ".\n";
+ for (var uri in nim) {
+ result += " ... with document URI \"" + uri + "\".\n";
+ }
+ }
+ },
+ summary: function() {
+ var len = 0;
+ for (var w in this.nims)
+ ++len;
+ result += 'Leaked content nodes in ' + len + ' out of ' +
+ this.count + " documents\n";
+ }
+ }
+ };
+
+ var lines = contents.split(/[\r\n]+/);
+ for (var j in lines) {
+ var line = lines[j];
+ // strip off initial "-", thread id, and thread pointer; separate
+ // first word and rest
+ var matches = line.match(/^\-?[0-9]*\[[0-9a-f]*\]: (\S*) (.*)$/);
+ if (matches) {
+ var handler = matches[1];
+ var data = matches[2];
+ if (typeof(handlers[handler]) != "undefined") {
+ handlers[handler].handle_line(data);
+ }
+ }
+ }
+
+ for (var handler in handlers)
+ handlers[handler].dump();
+ if (result.length)
+ result += "\n";
+ result += "Summary:\n";
+ for (var handler in handlers)
+ handlers[handler].summary();
+ result += "\n";
+
+ var out = document.createElement("pre");
+ out.className = "output";
+ out.appendChild(document.createTextNode(result));
+ document.body.appendChild(out);
+}
+
+function run() {
+ var input = document.getElementById("fileinput");
+ var files = input.files;
+ for (var i = 0; i < files.length; ++i)
+ runfile(files[i]);
+ // So the user can process the same filename again (after
+ // overwriting the log), clear the value on the form input so we
+ // will always get an onchange event.
+ input.value = "";
+}
+
+</script>
+</head>
+<body>
+
+<h1>Leak Gauge</h1>
+
+<pre>$Id: leak-gauge.html,v 1.8 2008/02/08 19:55:34 dbaron%dbaron.org Exp $</pre>
+
+<p>This script is designed to help testers isolate and simplify testcases
+for many classes of leaks (those that involve large graphs of core
+data structures) in Mozilla-based browsers. It is designed to print
+information about what has leaked by processing a log taken while
+running the browser. Such a log can be taken over a long session of
+normal browsing and then the log can be processed to find sites that
+leak. Once a site is known to leak, the logging can then be repeated
+to figure out under what conditions the leak occurs.</p>
+
+<p>The way to create this log is to set the environment variables:</p>
+<pre> MOZ_LOG=DOMLeak:5,DocumentLeak:5,nsDocShellLeak:5,NodeInfoManagerLeak:5
+ MOZ_LOG_FILE=nspr.log <i>(or any other filename of your choice)</i></pre>
+<p>in your shell and then run the program.</p>
+<ul>
+<li>In a Windows command prompt, set environment variables with
+<pre> set VAR=value</pre></li>
+<li> In an sh-based shell such as bash, set environment variables with
+<pre> export VAR=value</pre></li>
+<li>In a csh-based shell such as tcsh, set environment variables with
+<pre> setenv VAR value</pre></li>
+</ul>
+
+<p>Once you have this log from a complete run of the browser (you have
+to exit; otherwise it will look like everything leaked), you can load
+this page (be careful not to overwrite the log when starting the browser
+to load this page) and enter the filename of the log:</p>
+
+<p><input type="file" id="fileinput" onchange="run()"></p>
+
+<p>Then you'll see the output below, which will tell you which of
+certain core objects leaked and the URLs associated with those
+objects.</p>
+
+</body>
+</html>
diff --git a/tools/leak-gauge/leak-gauge.pl b/tools/leak-gauge/leak-gauge.pl
new file mode 100755
index 000000000..76ac597df
--- /dev/null
+++ b/tools/leak-gauge/leak-gauge.pl
@@ -0,0 +1,239 @@
+#!/usr/bin/perl -w
+# vim:sw=4:ts=4:et:
+# 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/.
+
+# $Id: leak-gauge.pl,v 1.8 2008/02/08 19:55:03 dbaron%dbaron.org Exp $
+# This script is designed to help testers isolate and simplify testcases
+# for many classes of leaks (those that involve large graphs of core
+# data structures) in Mozilla-based browsers. It is designed to print
+# information about what has leaked by processing a log taken while
+# running the browser. Such a log can be taken over a long session of
+# normal browsing and then the log can be processed to find sites that
+# leak. Once a site is known to leak, the logging can then be repeated
+# to figure out under what conditions the leak occurs.
+#
+# The way to create this log is to set the environment variables:
+# MOZ_LOG=DOMLeak:5,DocumentLeak:5,nsDocShellLeak:5,NodeInfoManagerLeak:5
+# MOZ_LOG_FILE=nspr.log (or any other filename of your choice)
+# in your shell and then run the program.
+# * In a Windows command prompt, set environment variables with
+# set VAR=value
+# * In an sh-based shell such as bash, set environment variables with
+# export VAR=value
+# * In a csh-based shell such as tcsh, set environment variables with
+# setenv VAR value
+#
+# Then, after you have exited the browser, run this perl script over the
+# log. Either of the following commands should work:
+# perl ./path/to/leak-gauge.pl nspr.log
+# cat nspr.log | perl ./path/to/leak-gauge.pl
+# and it will tell you which of certain core objects leaked and the URLs
+# associated with those objects.
+
+
+# Nobody said I'm not allowed to write my own object system in perl. No
+# classes here. Just objects and methods.
+sub call {
+ my $func = shift;
+ my $obj = shift;
+ my $funcref = ${$obj}{$func};
+ &$funcref($obj, @_);
+}
+
+# A hash of objects (keyed by the first word of the line in the log)
+# that have two public methods, handle_line and dump (to be called using
+# call, above), along with any private data they need.
+my $handlers = {
+ "DOMWINDOW" => {
+ count => 0,
+ windows => {},
+ handle_line => sub($$) {
+ my ($self, $line) = @_;
+ my $windows = ${$self}{windows};
+ if ($line =~ /^([0-9a-f]*) (\S*)/) {
+ my ($addr, $verb, $rest) = ($1, $2, $');
+ if ($verb eq "created") {
+ $rest =~ / outer=([0-9a-f]*)$/ || die "outer expected";
+ my $outer = $1;
+ ${$windows}{$addr} = { outer => $1 };
+ ++${$self}{count};
+ } elsif ($verb eq "destroyed") {
+ delete ${$windows}{$addr};
+ } elsif ($verb eq "SetNewDocument") {
+ $rest =~ /^ (.*)$/ || die "URI expected";
+ my $uri = ($1);
+ ${${$windows}{$addr}}{$uri} = 1;
+ }
+ }
+ },
+ dump => sub ($) {
+ my ($self) = @_;
+ my $windows = ${$self}{windows};
+ foreach my $addr (keys(%{$windows})) {
+ my $winobj = ${$windows}{$addr};
+ my $outer = delete ${$winobj}{outer};
+ print "Leaked " . ($outer eq "0" ? "outer" : "inner") .
+ " window $addr " .
+ ($outer eq "0" ? "" : "(outer $outer) ") .
+ "at address $addr.\n";
+ foreach my $uri (keys(%{$winobj})) {
+ print " ... with URI \"$uri\".\n";
+ }
+ }
+ },
+ summary => sub($) {
+ my ($self) = @_;
+ my $windows = ${$self}{windows};
+ print 'Leaked ' . keys(%{$windows}) . ' out of ' .
+ ${$self}{count} . " DOM Windows\n";
+ }
+ },
+ "DOCUMENT" => {
+ count => 0,
+ docs => {},
+ handle_line => sub($$) {
+ my ($self, $line) = @_;
+ # This doesn't work; I don't have time to figure out why not.
+ # my $docs = ${$self}{docs};
+ my $docs = ${$handlers}{"DOCUMENT"}{docs};
+ if ($line =~ /^([0-9a-f]*) (\S*)/) {
+ my ($addr, $verb, $rest) = ($1, $2, $');
+ if ($verb eq "created") {
+ ${$docs}{$addr} = {};
+ ++${$self}{count};
+ } elsif ($verb eq "destroyed") {
+ delete ${$docs}{$addr};
+ } elsif ($verb eq "ResetToURI" ||
+ $verb eq "StartDocumentLoad") {
+ $rest =~ /^ (.*)$/ || die "URI expected";
+ my $uri = $1;
+ my $doc_info = ${$docs}{$addr};
+ ${$doc_info}{$uri} = 1;
+ if (exists(${$doc_info}{"nim"})) {
+ ${$doc_info}{"nim"}{$uri} = 1;
+ }
+ }
+ }
+ },
+ dump => sub ($) {
+ my ($self) = @_;
+ my $docs = ${$self}{docs};
+ foreach my $addr (keys(%{$docs})) {
+ print "Leaked document at address $addr.\n";
+ foreach my $uri (keys(%{${$docs}{$addr}})) {
+ print " ... with URI \"$uri\".\n" unless $uri eq "nim";
+ }
+ }
+ },
+ summary => sub($) {
+ my ($self) = @_;
+ my $docs = ${$self}{docs};
+ print 'Leaked ' . keys(%{$docs}) . ' out of ' .
+ ${$self}{count} . " documents\n";
+ }
+ },
+ "DOCSHELL" => {
+ count => 0,
+ shells => {},
+ handle_line => sub($$) {
+ my ($self, $line) = @_;
+ my $shells = ${$self}{shells};
+ if ($line =~ /^([0-9a-f]*) (\S*)/) {
+ my ($addr, $verb, $rest) = ($1, $2, $');
+ if ($verb eq "created") {
+ ${$shells}{$addr} = {};
+ ++${$self}{count};
+ } elsif ($verb eq "destroyed") {
+ delete ${$shells}{$addr};
+ } elsif ($verb eq "InternalLoad" ||
+ $verb eq "SetCurrentURI") {
+ $rest =~ /^ (.*)$/ || die "URI expected";
+ my $uri = $1;
+ ${${$shells}{$addr}}{$uri} = 1;
+ }
+ }
+ },
+ dump => sub ($) {
+ my ($self) = @_;
+ my $shells = ${$self}{shells};
+ foreach my $addr (keys(%{$shells})) {
+ print "Leaked docshell at address $addr.\n";
+ foreach my $uri (keys(%{${$shells}{$addr}})) {
+ print " ... which loaded URI \"$uri\".\n";
+ }
+ }
+ },
+ summary => sub($) {
+ my ($self) = @_;
+ my $shells = ${$self}{shells};
+ print 'Leaked ' . keys(%{$shells}) . ' out of ' .
+ ${$self}{count} . " docshells\n";
+ }
+ },
+ "NODEINFOMANAGER" => {
+ count => 0,
+ nims => {},
+ handle_line => sub($$) {
+ my ($self, $line) = @_;
+ my $nims = ${$self}{nims};
+ if ($line =~ /^([0-9a-f]*) (\S*)/) {
+ my ($addr, $verb, $rest) = ($1, $2, $');
+ if ($verb eq "created") {
+ ${$nims}{$addr} = {};
+ ++${$self}{count};
+ } elsif ($verb eq "destroyed") {
+ delete ${$nims}{$addr};
+ } elsif ($verb eq "Init") {
+ $rest =~ /^ document=(.*)$/ ||
+ die "document pointer expected";
+ my $doc = $1;
+ if ($doc ne "0") {
+ my $nim_info = ${$nims}{$addr};
+ my $doc_info = ${$handlers}{"DOCUMENT"}{docs}{$doc};
+ foreach my $uri (keys(%{$doc_info})) {
+ ${$nim_info}{$uri} = 1;
+ }
+ ${$doc_info}{"nim"} = $nim_info;
+ }
+ }
+ }
+ },
+ dump => sub ($) {
+ my ($self) = @_;
+ my $nims = ${$self}{nims};
+ foreach my $addr (keys(%{$nims})) {
+ print "Leaked content nodes associated with node info manager at address $addr.\n";
+ foreach my $uri (keys(%{${$nims}{$addr}})) {
+ print " ... with document URI \"$uri\".\n";
+ }
+ }
+ },
+ summary => sub($) {
+ my ($self) = @_;
+ my $nims = ${$self}{nims};
+ print 'Leaked content nodes within ' . keys(%{$nims}) . ' out of ' .
+ ${$self}{count} . " documents\n";
+ }
+ }
+};
+
+while (<>) {
+ # strip off initial "-", thread id, and thread pointer; separate
+ # first word and rest
+ if (/^\-?[0-9]*\[[0-9a-f]*\]: (\S*) ([^\n\r]*)[\n\r]*$/) {
+ my ($handler, $data) = ($1, $2);
+ if (defined(${$handlers}{$handler})) {
+ call("handle_line", ${$handlers}{$handler}, $data);
+ }
+ }
+}
+
+foreach my $key (keys(%{$handlers})) {
+ call("dump", ${$handlers}{$key});
+}
+print "Summary:\n";
+foreach my $key (keys(%{$handlers})) {
+ call("summary", ${$handlers}{$key});
+}