diff options
Diffstat (limited to 'tools/leak-gauge')
-rw-r--r-- | tools/leak-gauge/leak-gauge.html | 302 | ||||
-rwxr-xr-x | tools/leak-gauge/leak-gauge.pl | 239 |
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}); +} |