michael@0: #!/usr/bin/perl -w michael@0: # vim:cindent:ts=8:et:sw=4: michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: # This script produces a diff between two files that are the result of michael@0: # calling NS_TraceMallocDumpAllocations. Such files can be created michael@0: # through the command-line option --shutdown-leaks= or through michael@0: # the DOM function window.TraceMallocDumpAllocations(). Both michael@0: # methods will work only if --trace-malloc= is also given on michael@0: # the command line. michael@0: michael@0: use 5.004; michael@0: use strict; michael@0: use Getopt::Long; michael@0: michael@0: $::opt_help = 0; michael@0: $::opt_depth = 6; michael@0: $::opt_include_zero = 0; michael@0: $::opt_allocation_count = 0; michael@0: $::opt_use_address = 0; michael@0: michael@0: # XXX Change --use-address to be the default and remove the option michael@0: # once tinderbox is no longer using it without --use-address. michael@0: michael@0: Getopt::Long::Configure("pass_through"); michael@0: Getopt::Long::GetOptions("help", "allocation-count", "depth=i", michael@0: "include-zero", "use-address"); michael@0: michael@0: if ($::opt_help) { michael@0: die "usage: diffbloatdump.pl [options] michael@0: --help Display this message michael@0: michael@0: --allocation-count Use allocation count rather than size (i.e., treat michael@0: all sizes as 1). michael@0: --depth= Only display frames at top of allocation stack. michael@0: --include-zero Display subtrees totalling zero. michael@0: --use-address Don't ignore the address part of the stack trace michael@0: (can make comparison more accurate when comparing michael@0: results from the same build) michael@0: michael@0: The input files ( and above) are either trace-malloc michael@0: memory dumps OR this script's output. (If this script's output, michael@0: --allocation-count and --use-address are ignored.) If the input files michael@0: have .gz or .bz2 extension, they are uncompressed. michael@0: "; michael@0: } michael@0: michael@0: my $calltree = { count => 0 }; # leave children undefined michael@0: michael@0: sub get_child($$) { michael@0: my ($node, $frame) = @_; michael@0: if (!defined($node->{children})) { michael@0: $node->{children} = {}; michael@0: } michael@0: if (!defined($node->{children}->{$frame})) { michael@0: $node->{children}->{$frame} = { count => 0 }; michael@0: } michael@0: return $node->{children}->{$frame}; michael@0: } michael@0: michael@0: sub add_tree_file($$$) { michael@0: my ($infile, $firstline, $factor) = @_; michael@0: michael@0: my @nodestack; michael@0: $nodestack[1] = $calltree; michael@0: $firstline =~ /^(-?\d+) malloc$/; michael@0: $calltree->{count} += $1 * $factor; michael@0: michael@0: my $lineno = 1; michael@0: while (!eof($infile)) { michael@0: my $line = <$infile>; michael@0: ++$lineno; michael@0: $line =~ /^( *)(-?\d+) (.*)$/ || die "malformed input, line $lineno"; michael@0: my $depth = length($1); michael@0: my $count = $2; michael@0: my $frame = $3; michael@0: die "malformed input, line $lineno" if ($depth % 2 != 0); michael@0: $depth /= 2; michael@0: die "malformed input, line $lineno" if ($depth > $#nodestack); michael@0: $#nodestack = $depth; michael@0: my $node = get_child($nodestack[$depth], $frame); michael@0: push @nodestack, $node; michael@0: $node->{count} += $count * $factor; michael@0: } michael@0: } michael@0: michael@0: sub add_file($$) { michael@0: # Takes (1) a reference to a file descriptor for input and (2) the michael@0: # factor to multiply the stacks by (generally +1 or -1). michael@0: # Returns a reference to an array representing the stack, allocation michael@0: # site in array[0]. michael@0: sub read_stack($) { michael@0: my ($infile) = @_; michael@0: my $line; michael@0: my @stack; michael@0: michael@0: # read the data at the memory location michael@0: while ( defined($infile) && ($line = <$infile>) && substr($line,0,1) eq "\t" ) { michael@0: # do nothing michael@0: } michael@0: michael@0: # read the stack michael@0: do { michael@0: chomp($line); michael@0: if ( ! $::opt_use_address && michael@0: $line =~ /(.*)\[(.*)\]/) { michael@0: $line = $1; michael@0: } michael@0: $stack[$#stack+1] = $line; michael@0: } while ( defined($infile) && ($line = <$infile>) && $line ne "\n" && $line ne "\r\n" ); michael@0: michael@0: return \@stack; michael@0: } michael@0: michael@0: # adds the stack given as a parameter (reference to array, $stack[0] is michael@0: # allocator) to $calltree, with the call count multiplied by $factor michael@0: # (typically +1 or -1). michael@0: sub add_stack($$) { michael@0: my @stack = @{$_[0]}; michael@0: my $factor = $_[1]; michael@0: michael@0: my $i = 0; michael@0: my $node = $calltree; michael@0: while ($i < $#stack && $i < $::opt_depth) { michael@0: $node->{count} += $factor; michael@0: $node = get_child($node, $stack[$i]); michael@0: ++$i; michael@0: } michael@0: $node->{count} += $factor; michael@0: } michael@0: michael@0: my ($infile, $factor) = @_; michael@0: michael@0: if ($infile =~ /\.bz2$/) { michael@0: # XXX This doesn't propagate errors from bzip2. michael@0: open (INFILE, "bzip2 -cd '$infile' |") || die "Can't open input \"$infile\""; michael@0: } elsif ($infile =~ /\.gz$/) { michael@0: # XXX This doesn't propagate errors from gzip. michael@0: open (INFILE, "gzip -cd '$infile' |") || die "Can't open input \"$infile\""; michael@0: } else { michael@0: open (INFILE, "<$infile") || die "Can't open input \"$infile\""; michael@0: } michael@0: my $first = 1; michael@0: while ( ! eof(INFILE) ) { michael@0: # read the type and address michael@0: my $line = ; michael@0: if ($first) { michael@0: $first = 0; michael@0: if ($line =~ /^-?\d+ malloc$/) { michael@0: # We're capable of reading in our own output as well. michael@0: add_tree_file(\*INFILE, $line, $factor); michael@0: close INFILE; michael@0: return; michael@0: } michael@0: } michael@0: unless ($line =~ /.*\((\d*)\)[\r|\n]/) { michael@0: die "badly formed allocation header in $infile"; michael@0: } michael@0: my $size; michael@0: if ($::opt_allocation_count) { michael@0: $size = 1; michael@0: } else { michael@0: $size = $1; michael@0: } michael@0: michael@0: add_stack(read_stack(\*INFILE), $size * $factor); michael@0: } michael@0: close INFILE; michael@0: } michael@0: michael@0: sub print_node_indent($$$); michael@0: michael@0: sub print_calltree() { michael@0: sub print_indent($) { michael@0: my ($i) = @_; michael@0: while (--$i >= 0) { michael@0: print " "; michael@0: } michael@0: } michael@0: michael@0: sub print_node_indent($$$) { michael@0: my ($nodename, $node, $indent) = @_; michael@0: michael@0: if (!$::opt_include_zero && $node->{count} == 0) { michael@0: return; michael@0: } michael@0: michael@0: print_indent($indent); michael@0: print "$node->{count} $nodename\n"; michael@0: if (defined($node->{children})) { michael@0: my %kids = %{$node->{children}}; michael@0: ++$indent; michael@0: foreach my $kid (sort { $kids{$b}->{count} <=> $kids{$a}->{count} } michael@0: keys (%kids)) { michael@0: print_node_indent($kid, $kids{$kid}, $indent); michael@0: } michael@0: } michael@0: } michael@0: michael@0: print_node_indent("malloc", $calltree, 0); michael@0: } michael@0: michael@0: add_file($ARGV[0], -1); michael@0: add_file($ARGV[1], 1); michael@0: print_calltree();