tools/footprint/leak-gauge.pl

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/tools/footprint/leak-gauge.pl	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,239 @@
     1.4 +#!/usr/bin/perl -w
     1.5 +# vim:sw=4:ts=4:et:
     1.6 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.9 +
    1.10 +# $Id: leak-gauge.pl,v 1.8 2008/02/08 19:55:03 dbaron%dbaron.org Exp $
    1.11 +# This script is designed to help testers isolate and simplify testcases
    1.12 +# for many classes of leaks (those that involve large graphs of core
    1.13 +# data structures) in Mozilla-based browsers.  It is designed to print
    1.14 +# information about what has leaked by processing a log taken while
    1.15 +# running the browser.  Such a log can be taken over a long session of
    1.16 +# normal browsing and then the log can be processed to find sites that
    1.17 +# leak.  Once a site is known to leak, the logging can then be repeated
    1.18 +# to figure out under what conditions the leak occurs.
    1.19 +#
    1.20 +# The way to create this log is to set the environment variables:
    1.21 +#   NSPR_LOG_MODULES=DOMLeak:5,DocumentLeak:5,nsDocShellLeak:5,NodeInfoManagerLeak:5
    1.22 +#   NSPR_LOG_FILE=nspr.log     (or any other filename of your choice)
    1.23 +# in your shell and then run the program.
    1.24 +# * In a Windows command prompt, set environment variables with
    1.25 +#     set VAR=value
    1.26 +# * In an sh-based shell such as bash, set environment variables with
    1.27 +#     export VAR=value
    1.28 +# * In a csh-based shell such as tcsh, set environment variables with
    1.29 +#     setenv VAR value
    1.30 +#
    1.31 +# Then, after you have exited the browser, run this perl script over the
    1.32 +# log.  Either of the following commands should work:
    1.33 +#   perl ./path/to/leak-gauge.pl nspr.log
    1.34 +#   cat nspr.log | perl ./path/to/leak-gauge.pl
    1.35 +# and it will tell you which of certain core objects leaked and the URLs
    1.36 +# associated with those objects.
    1.37 +
    1.38 +
    1.39 +# Nobody said I'm not allowed to write my own object system in perl.  No
    1.40 +# classes here.  Just objects and methods.
    1.41 +sub call {
    1.42 +    my $func = shift;
    1.43 +    my $obj = shift;
    1.44 +    my $funcref = ${$obj}{$func};
    1.45 +    &$funcref($obj, @_);
    1.46 +}
    1.47 +
    1.48 +# A hash of objects (keyed by the first word of the line in the log)
    1.49 +# that have two public methods, handle_line and dump (to be called using
    1.50 +# call, above), along with any private data they need.
    1.51 +my $handlers = {
    1.52 +    "DOMWINDOW" => {
    1.53 +        count => 0,
    1.54 +        windows => {},
    1.55 +        handle_line => sub($$) {
    1.56 +            my ($self, $line) = @_;
    1.57 +            my $windows = ${$self}{windows};
    1.58 +            if ($line =~ /^([0-9a-f]*) (\S*)/) {
    1.59 +                my ($addr, $verb, $rest) = ($1, $2, $');
    1.60 +                if ($verb eq "created") {
    1.61 +                    $rest =~ / outer=([0-9a-f]*)$/ || die "outer expected";
    1.62 +                    my $outer = $1;
    1.63 +                    ${$windows}{$addr} = { outer => $1 };
    1.64 +                    ++${$self}{count};
    1.65 +                } elsif ($verb eq "destroyed") {
    1.66 +                    delete ${$windows}{$addr};
    1.67 +                } elsif ($verb eq "SetNewDocument") {
    1.68 +                    $rest =~ /^ (.*)$/ || die "URI expected";
    1.69 +                    my $uri = ($1);
    1.70 +                    ${${$windows}{$addr}}{$uri} = 1;
    1.71 +                }
    1.72 +            }
    1.73 +        },
    1.74 +        dump => sub ($) {
    1.75 +            my ($self) = @_;
    1.76 +            my $windows = ${$self}{windows};
    1.77 +            foreach my $addr (keys(%{$windows})) {
    1.78 +                my $winobj = ${$windows}{$addr};
    1.79 +                my $outer = delete ${$winobj}{outer};
    1.80 +                print "Leaked " . ($outer eq "0" ? "outer" : "inner") .
    1.81 +                      " window $addr " .
    1.82 +                      ($outer eq "0" ? "" : "(outer $outer) ") .
    1.83 +                      "at address $addr.\n";
    1.84 +                foreach my $uri (keys(%{$winobj})) {
    1.85 +                    print " ... with URI \"$uri\".\n";
    1.86 +                }
    1.87 +            }
    1.88 +        },
    1.89 +        summary => sub($) {
    1.90 +            my ($self) = @_;
    1.91 +            my $windows = ${$self}{windows};
    1.92 +            print 'Leaked ' . keys(%{$windows}) . ' out of ' .
    1.93 +                  ${$self}{count} . " DOM Windows\n";
    1.94 +        }
    1.95 +    },
    1.96 +    "DOCUMENT" => {
    1.97 +        count => 0,
    1.98 +        docs => {},
    1.99 +        handle_line => sub($$) {
   1.100 +            my ($self, $line) = @_;
   1.101 +            # This doesn't work; I don't have time to figure out why not.
   1.102 +            # my $docs = ${$self}{docs};
   1.103 +            my $docs = ${$handlers}{"DOCUMENT"}{docs};
   1.104 +            if ($line =~ /^([0-9a-f]*) (\S*)/) {
   1.105 +                my ($addr, $verb, $rest) = ($1, $2, $');
   1.106 +                if ($verb eq "created") {
   1.107 +                    ${$docs}{$addr} = {};
   1.108 +                    ++${$self}{count};
   1.109 +                } elsif ($verb eq "destroyed") {
   1.110 +                    delete ${$docs}{$addr};
   1.111 +                } elsif ($verb eq "ResetToURI" ||
   1.112 +                         $verb eq "StartDocumentLoad") {
   1.113 +                    $rest =~ /^ (.*)$/ || die "URI expected";
   1.114 +                    my $uri = $1;
   1.115 +                    my $doc_info = ${$docs}{$addr};
   1.116 +                    ${$doc_info}{$uri} = 1;
   1.117 +                    if (exists(${$doc_info}{"nim"})) {
   1.118 +                        ${$doc_info}{"nim"}{$uri} = 1;
   1.119 +                    }
   1.120 +                }
   1.121 +            }
   1.122 +        },
   1.123 +        dump => sub ($) {
   1.124 +            my ($self) = @_;
   1.125 +            my $docs = ${$self}{docs};
   1.126 +            foreach my $addr (keys(%{$docs})) {
   1.127 +                print "Leaked document at address $addr.\n";
   1.128 +                foreach my $uri (keys(%{${$docs}{$addr}})) {
   1.129 +                    print " ... with URI \"$uri\".\n" unless $uri eq "nim";
   1.130 +                }
   1.131 +            }
   1.132 +        },
   1.133 +        summary => sub($) {
   1.134 +            my ($self) = @_;
   1.135 +            my $docs = ${$self}{docs};
   1.136 +            print 'Leaked ' . keys(%{$docs}) . ' out of ' .
   1.137 +                  ${$self}{count} . " documents\n";
   1.138 +        }
   1.139 +    },
   1.140 +    "DOCSHELL" => {
   1.141 +        count => 0,
   1.142 +        shells => {},
   1.143 +        handle_line => sub($$) {
   1.144 +            my ($self, $line) = @_;
   1.145 +            my $shells = ${$self}{shells};
   1.146 +            if ($line =~ /^([0-9a-f]*) (\S*)/) {
   1.147 +                my ($addr, $verb, $rest) = ($1, $2, $');
   1.148 +                if ($verb eq "created") {
   1.149 +                    ${$shells}{$addr} = {};
   1.150 +                    ++${$self}{count};
   1.151 +                } elsif ($verb eq "destroyed") {
   1.152 +                    delete ${$shells}{$addr};
   1.153 +                } elsif ($verb eq "InternalLoad" ||
   1.154 +                         $verb eq "SetCurrentURI") {
   1.155 +                    $rest =~ /^ (.*)$/ || die "URI expected";
   1.156 +                    my $uri = $1;
   1.157 +                    ${${$shells}{$addr}}{$uri} = 1;
   1.158 +                }
   1.159 +            }
   1.160 +        },
   1.161 +        dump => sub ($) {
   1.162 +            my ($self) = @_;
   1.163 +            my $shells = ${$self}{shells};
   1.164 +            foreach my $addr (keys(%{$shells})) {
   1.165 +                print "Leaked docshell at address $addr.\n";
   1.166 +                foreach my $uri (keys(%{${$shells}{$addr}})) {
   1.167 +                    print " ... which loaded URI \"$uri\".\n";
   1.168 +                }
   1.169 +            }
   1.170 +        },
   1.171 +        summary => sub($) {
   1.172 +            my ($self) = @_;
   1.173 +            my $shells = ${$self}{shells};
   1.174 +            print 'Leaked ' . keys(%{$shells}) . ' out of ' .
   1.175 +                  ${$self}{count} . " docshells\n";
   1.176 +        }
   1.177 +    },
   1.178 +    "NODEINFOMANAGER" => {
   1.179 +        count => 0,
   1.180 +        nims => {},
   1.181 +        handle_line => sub($$) {
   1.182 +            my ($self, $line) = @_;
   1.183 +            my $nims = ${$self}{nims};
   1.184 +            if ($line =~ /^([0-9a-f]*) (\S*)/) {
   1.185 +                my ($addr, $verb, $rest) = ($1, $2, $');
   1.186 +                if ($verb eq "created") {
   1.187 +                    ${$nims}{$addr} = {};
   1.188 +                    ++${$self}{count};
   1.189 +                } elsif ($verb eq "destroyed") {
   1.190 +                    delete ${$nims}{$addr};
   1.191 +                } elsif ($verb eq "Init") {
   1.192 +                    $rest =~ /^ document=(.*)$/ ||
   1.193 +                        die "document pointer expected";
   1.194 +                    my $doc = $1;
   1.195 +                    if ($doc ne "0") {
   1.196 +                        my $nim_info = ${$nims}{$addr};
   1.197 +                        my $doc_info = ${$handlers}{"DOCUMENT"}{docs}{$doc};
   1.198 +                        foreach my $uri (keys(%{$doc_info})) {
   1.199 +                            ${$nim_info}{$uri} = 1;
   1.200 +                        }
   1.201 +                        ${$doc_info}{"nim"} = $nim_info;
   1.202 +                    }
   1.203 +                }
   1.204 +            }
   1.205 +        },
   1.206 +        dump => sub ($) {
   1.207 +            my ($self) = @_;
   1.208 +            my $nims = ${$self}{nims};
   1.209 +            foreach my $addr (keys(%{$nims})) {
   1.210 +                print "Leaked content nodes associated with node info manager at address $addr.\n";
   1.211 +                foreach my $uri (keys(%{${$nims}{$addr}})) {
   1.212 +                    print " ... with document URI \"$uri\".\n";
   1.213 +                }
   1.214 +            }
   1.215 +        },
   1.216 +        summary => sub($) {
   1.217 +            my ($self) = @_;
   1.218 +            my $nims = ${$self}{nims};
   1.219 +            print 'Leaked content nodes within ' . keys(%{$nims}) . ' out of ' .
   1.220 +                  ${$self}{count} . " documents\n";
   1.221 +        }
   1.222 +    }
   1.223 +};
   1.224 +
   1.225 +while (<>) {
   1.226 +    # strip off initial "-", thread id, and thread pointer; separate
   1.227 +    # first word and rest
   1.228 +    if (/^\-?[0-9]*\[[0-9a-f]*\]: (\S*) ([^\n\r]*)[\n\r]*$/) {
   1.229 +        my ($handler, $data) = ($1, $2);
   1.230 +        if (defined(${$handlers}{$handler})) {
   1.231 +            call("handle_line", ${$handlers}{$handler}, $data);
   1.232 +        }
   1.233 +    }
   1.234 +}
   1.235 +
   1.236 +foreach my $key (keys(%{$handlers})) {
   1.237 +    call("dump", ${$handlers}{$key});
   1.238 +}
   1.239 +print "Summary:\n";
   1.240 +foreach my $key (keys(%{$handlers})) {
   1.241 +    call("summary", ${$handlers}{$key});
   1.242 +}

mercurial