michael@0: #!/usr/bin/perl -w michael@0: # 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: # A perl version of Patrick Beard's ``Leak Soup'', which processes the michael@0: # stack crawls from the Boehm GC into a graph. michael@0: michael@0: use 5.004; michael@0: use strict; michael@0: use Getopt::Long; michael@0: use FileHandle; michael@0: use IPC::Open2; michael@0: michael@0: # Collect program options michael@0: $::opt_help = 0; michael@0: $::opt_detail = 0; michael@0: $::opt_fragment = 1.0; # Default to no fragment analysis michael@0: $::opt_nostacks = 0; michael@0: $::opt_nochildstacks = 0; michael@0: $::opt_depth = 9999; michael@0: $::opt_noentrained = 0; michael@0: $::opt_noslop = 0; michael@0: $::opt_showtype = -1; # default to listing all types michael@0: $::opt_stackrefine = "C"; michael@0: @::opt_stackretype = (); michael@0: @::opt_stackskipclass = (); michael@0: @::opt_stackskipfunc = (); michael@0: @::opt_typedivide = (); michael@0: michael@0: GetOptions("help", "detail", "format=s", "fragment=f", "nostacks", michael@0: "nochildstacks", "depth=i", "noentrained", "noslop", "showtype=i", michael@0: "stackrefine=s", "stackretype=s@", "stackskipclass=s@", "stackskipfunc=s@", michael@0: "typedivide=s@" michael@0: ); michael@0: michael@0: if ($::opt_help) { michael@0: die "usage: leak-soup.pl [options] michael@0: --help Display this message michael@0: --detail Provide details of memory sweeping from child to parents michael@0: --fragment=ratio Histogram bucket ratio for fragmentation analysis michael@0: # --nostacks Do not compute stack traces michael@0: # --nochildstacks Do not compute stack traces for entrained objects michael@0: # --depth= Only compute stack traces to depth of michael@0: # --noentrained Do not compute amount of memory entrained by root objects michael@0: --noslop Don't ignore low bits when searching for pointers michael@0: --showtype= Show memory usage histogram for most-significant types michael@0: --stackrefine={F|C} During stack based refinement, use 'F'ull name name or just 'C'lass michael@0: --stackretype=type Use allocation stack to refine vague types like void* michael@0: --stackskipclass=class When refining types, ignore stack frames from 'class' michael@0: --stackskipfunc=func When refining types, ignore stack frames for 'func' michael@0: --typedivide=type Subdivide 'type' based on objects pointing to each instance michael@0: "; michael@0: } michael@0: michael@0: # This is the table that keeps a graph of objects. It's indexed by the michael@0: # object's address (as an integer), and refers to a simple hash that michael@0: # has information about the object's type, size, slots, and allocation michael@0: # stack. michael@0: %::Objects = %{0}; michael@0: michael@0: # This will be a list of keys to (addresses in) Objects, that is sorted michael@0: # It gets used to evaluate overlaps, calculate fragmentation, and chase michael@0: # parent->child (interior) pointers. michael@0: @::SortedAddresses = []; michael@0: michael@0: # This is the table that keeps track of memory usage on a per-type basis. michael@0: # It is indexed by the type name (string), and keeps a tally of the michael@0: # total number of such objects, and the memory usage of such objects. michael@0: %::Types = %{0}; michael@0: $::TotalSize = 0; # sum of sizes of all objects included $::Types{} michael@0: michael@0: # This is an array of leaf node addresses. A leaf node has no children michael@0: # with memory allocations. We traverse them sweeping memory michael@0: # tallies into parents. Note that after all children have michael@0: # been swept into a parent, that parent may also become a leaf node. michael@0: @::Leafs = @{0}; michael@0: michael@0: michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Decode arguments to override default values for doing call-stack-based michael@0: # refinement of typename based on contents of the stack at allocation time. michael@0: # michael@0: michael@0: # List the types that we need to refine (if any) based on allocation stack michael@0: $::VagueType = { michael@0: 'void*' => 1, michael@0: }; michael@0: michael@0: # With regard to the stack, ignore stack frames in the following michael@0: # overly vague classes. michael@0: $::VagueClasses = { michael@0: # 'nsStr' => 1, michael@0: 'nsVoidArray' => 1, michael@0: }; michael@0: michael@0: # With regard to stack, ignore stack frames with the following vague michael@0: # function names michael@0: $::VagueFunctions = { michael@0: 'PL_ArenaAllocate' => 1, michael@0: 'PL_HashTableFinalize(PLHashTable *)' => 1, michael@0: 'PL_HashTableInit__FP11PLHashTableUiPFPCv_UiPFPCvPCv_iT3PC14PLHashAllocOpsPv' => 1, michael@0: 'PL_HashTableRawAdd' => 1, michael@0: '__builtin_vec_new' => 1, michael@0: '_init' => 1, michael@0: 'il_get_container(_IL_GroupContext *, ImgCachePolicy, char const *, _NI_IRGB *, IL_DitherMode, int, int, int)' => 1, michael@0: 'nsCStringKey::Clone(void) const' => 1, michael@0: 'nsCppSharedAllocator::allocate(unsigned int, void const *)' => 1, michael@0: 'nsHashtable::Put(nsHashKey *, void *)' => 1, michael@0: 'nsHashtable::nsHashtable(unsigned int, int)' => 1, michael@0: 'nsMemory::Alloc(unsigned int)' => 1, michael@0: 'nsMemoryImpl::Alloc(unsigned int)' => 1, michael@0: }; michael@0: michael@0: sub init_stack_based_type_refinement() { michael@0: # Move across stackretype options, or use default values michael@0: if ($#::opt_stackretype < 0) { michael@0: print "Default --stackretype options will be used (since none were specified)\n"; michael@0: print " use --stackretype='nothing' to disable re-typing activity\n"; michael@0: } else { michael@0: foreach my $type (keys %{$::VagueType}) { michael@0: delete ($::VagueType->{$type}); michael@0: } michael@0: if ($#::opt_stackretype == 0 && $::opt_stackretype[0] eq 'nothing') { michael@0: print "Types will not be refined based on call stack\n"; michael@0: } else { michael@0: foreach my $type (@::opt_stackretype) { michael@0: $::VagueType->{$type} = 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: if (keys %{$::VagueType}) { michael@0: print "The following type(s) will be refined based on call stacks:\n"; michael@0: foreach my $type (sort keys %{$::VagueType}) { michael@0: print " $type\n"; michael@0: } michael@0: print "Equivalent command line argument(s):\n"; michael@0: foreach my $type (sort keys %{$::VagueType}) { michael@0: print " --stackretype='$type'"; michael@0: } michael@0: print "\n\n"; michael@0: michael@0: if ($#::opt_stackskipclass < 0) { michael@0: print "Default --stackskipclass options will be used (since none were specified)\n"; michael@0: print " use --stackskipclass='nothing' to disable skipping stack frames based on class names\n"; michael@0: } else { michael@0: foreach my $type (keys %{$::VagueClasses}) { michael@0: delete ($::VagueClasses->{$type}); michael@0: } michael@0: if ($#::opt_stackskipclass == 0 && $::opt_stackskipclass[0] eq 'nothing') { michael@0: print "Types will not be refined based on call stack\n"; michael@0: } else { michael@0: foreach my $type (@::opt_stackskipclass) { michael@0: $::VagueClasses->{$type} = 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (keys %{$::VagueClasses}) { michael@0: print "Stack frames from the following class(es) will not be used to refine types:\n"; michael@0: foreach my $class (sort keys %{$::VagueClasses}) { michael@0: print " $class\n"; michael@0: } michael@0: print "Equivalent command line argument(s):\n"; michael@0: foreach my $class (sort keys %{$::VagueClasses}) { michael@0: print " --stackskipclass='$class'"; michael@0: } michael@0: print "\n\n"; michael@0: } michael@0: michael@0: michael@0: if ($#::opt_stackskipfunc < 0) { michael@0: print "Default --stackskipfunc options will be used (since none were specified)\n"; michael@0: print " use --stackskipfunc='nothing' to disable skipping stack frames based on function names\n"; michael@0: } else { michael@0: foreach my $type (keys %{$::VagueFunctions}) { michael@0: delete ($::VagueFunctions->{$type}); michael@0: } michael@0: if ($#::opt_stackskipfunc == 0 && $::opt_stackskipfunc[0] eq 'nothing') { michael@0: print "Types will not be refined based on call stack\n"; michael@0: } else { michael@0: foreach my $type (@::opt_stackskipfunc) { michael@0: $::VagueFunctions->{$type} = 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (keys %{$::VagueFunctions}) { michael@0: print "Stack frames from the following function(s) will not be used to refine types:\n"; michael@0: foreach my $func (sort keys %{$::VagueFunctions}) { michael@0: print " $func\n"; michael@0: } michael@0: print "Equivalent command line argument(s):\n"; michael@0: foreach my $func (sort keys %{$::VagueFunctions}) { michael@0: print " --stackskipfunc='$func'"; michael@0: } michael@0: print "\n\n"; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Read in the output from the Boehm GC or Trace-malloc. michael@0: # michael@0: sub read_boehm() { michael@0: OBJECT: while (<>) { michael@0: # e.g., 0x0832FBD0 (80) michael@0: next OBJECT unless /^0x(\S+) <(.*)> \((\d+)\)/; michael@0: my ($addr, $type, $size) = (hex $1, $2, $3); michael@0: michael@0: my $object = $::Objects{$addr}; michael@0: if (! $object) { michael@0: # Found a new object entry. Record its type and size michael@0: $::Objects{$addr} = michael@0: $object = michael@0: { 'type' => $type, 'size' => $size }; michael@0: } else { michael@0: print "Duplicate address $addr contains $object->{'type'} and $type\n"; michael@0: $object->{'dup_addr_count'}++; michael@0: } michael@0: michael@0: # Record the object's slots michael@0: my @slots; michael@0: michael@0: SLOT: while (<>) { michael@0: # e.g., 0x00000000 michael@0: last SLOT unless /^\t0x(\S+)/; michael@0: my $value = hex $1; michael@0: michael@0: # Ignore low bits, unless they've specified --noslop michael@0: $value &= ~0x7 unless $::opt_noslop; michael@0: michael@0: $slots[$#slots + 1] = $value; michael@0: } michael@0: michael@0: $object->{'slots'} = \@slots; michael@0: michael@0: if (@::opt_stackretype && (defined $::VagueType->{$type})) { michael@0: # Change the value of type of the object based on stack michael@0: # if we can find an interesting calling function michael@0: VAGUEFRAME: while (<>) { michael@0: # e.g., _dl_debug_message[/lib/ld-linux.so.2 +0x0000B858] michael@0: last VAGUEFRAMEFRAME unless /^(.*)\[(.*) \+0x(\S+)\]$/; michael@0: my ($func, $lib, $off) = ($1, $2, hex $3); michael@0: chomp; michael@0: michael@0: my ($class,,$fname) = split(/:/, $func); michael@0: next VAGUEFRAME if (defined $::VagueFunctions->{$func} || michael@0: defined $::VagueClasses->{$class}); michael@0: michael@0: # Refine typename and exit stack scan michael@0: $object->{'type'} = $type . ":" . michael@0: (('C' eq $::opt_stackrefine) ? michael@0: $class : michael@0: $func); michael@0: last VAGUEFRAME; michael@0: } michael@0: } else { michael@0: # Save all stack info if requested michael@0: if (! $::opt_nostacks) { michael@0: # Record the stack by which the object was allocated michael@0: my @stack; michael@0: michael@0: FRAME: while (<>) { michael@0: # e.g., _dl_debug_message[/lib/ld-linux.so.2 +0x0000B858] michael@0: last FRAME unless /^(.*)\[(.*) \+0x(\S+)\]$/; michael@0: my ($func, $lib, $off) = ($1, $2, hex $3); michael@0: chomp; michael@0: michael@0: $stack[$#stack + 1] = $_; michael@0: } michael@0: michael@0: $object->{'stack'} = \@stack; michael@0: } michael@0: } michael@0: michael@0: # Gotta check EOF explicitly... michael@0: last OBJECT if eof; michael@0: } michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Read input michael@0: # michael@0: init_stack_based_type_refinement(); michael@0: read_boehm; michael@0: michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Do basic initialization of the type hash table. Accumulate michael@0: # total counts, and basic memory usage (not including children) michael@0: sub load_type_table() { michael@0: # Reset global counter and hash table michael@0: $::TotalSize = 0; michael@0: %::Types = %{0}; michael@0: michael@0: OBJECT: foreach my $addr (keys %::Objects) { michael@0: my $obj = $::Objects{$addr}; michael@0: my ($type, $size, $swept_in, $overlap_count, $dup_addr_count) = michael@0: ($obj->{'type'}, $obj->{'size'}, michael@0: $obj->{'swept_in'}, michael@0: $obj->{'overlap_count'},$obj->{'dup_addr_count'}); michael@0: michael@0: my $type_data = $::Types{$type}; michael@0: if (! defined $type_data) { michael@0: $::Types{$type} = michael@0: $type_data = {'count' => 0, 'size' => 0, michael@0: 'max' => $size, 'min' => $size, michael@0: 'swept_in' => 0, 'swept' => 0, michael@0: 'overlap_count' => 0, michael@0: 'dup_addr_count' => 0}; michael@0: } michael@0: michael@0: if (!$size) { michael@0: $type_data->{'swept'}++; michael@0: next OBJECT; michael@0: } michael@0: $::TotalSize += $size; michael@0: michael@0: $type_data->{'count'}++; michael@0: $type_data->{'size'} += $size; michael@0: if (defined $swept_in) { michael@0: $type_data->{'swept_in'} += $swept_in; michael@0: michael@0: if ($::opt_detail) { michael@0: my $type_detail_sizes = $type_data->{'sweep_details_size'}; michael@0: my $type_detail_counts; michael@0: if (!defined $type_detail_sizes) { michael@0: $type_detail_sizes = $type_data->{'sweep_details_size'} = {}; michael@0: $type_detail_counts = $type_data->{'sweep_details_count'} = {}; michael@0: } else { michael@0: $type_detail_counts = $type_data->{'sweep_details_count'}; michael@0: } michael@0: michael@0: my $sweep_details = $obj->{'sweep_details'}; michael@0: for my $swept_addr (keys (%{$sweep_details})) { michael@0: my $swept_obj = $::Objects{$swept_addr}; michael@0: my $swept_type = $swept_obj->{'type'}; michael@0: $type_detail_sizes->{$swept_type} += $sweep_details->{$swept_addr}; michael@0: $type_detail_counts->{$swept_type}++; michael@0: } michael@0: } michael@0: } michael@0: if (defined $overlap_count) { michael@0: $type_data->{'overlap_count'} += $overlap_count; michael@0: } michael@0: michael@0: if (defined $dup_addr_count) { michael@0: $type_data->{'dup_addr_count'} += $dup_addr_count; michael@0: } michael@0: michael@0: if ($type_data->{'max'} < $size) { michael@0: $type_data->{'max'} = $size; michael@0: } michael@0: # Watch out for case where min is produced by a swept object michael@0: if (!$type_data->{'min'} || $type_data->{'min'} > $size) { michael@0: $type_data->{'min'} = $size; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: sub print_type_table(){ michael@0: if (!$::opt_showtype) { michael@0: return; michael@0: } michael@0: my $line_count = 0; michael@0: my $bytes_printed_tally = 0; michael@0: michael@0: # Display type summary information michael@0: my @sorted_types = keys (%::Types); michael@0: print "There are ", 1 + $#sorted_types, " types containing ", $::TotalSize, " bytes\n"; michael@0: @sorted_types = sort {$::Types{$b}->{'size'} michael@0: <=> $::Types{$a}->{'size'} } @sorted_types; michael@0: michael@0: foreach my $type (@sorted_types) { michael@0: last if ($line_count++ == $::opt_showtype); michael@0: michael@0: my $type_data = $::Types{$type}; michael@0: $bytes_printed_tally += $type_data->{'size'}; michael@0: michael@0: if ($type_data->{'count'}) { michael@0: printf "%.2f%% ", $type_data->{'size'} * 100.0/$::TotalSize; michael@0: print $type_data->{'size'}, michael@0: "\t(", michael@0: $type_data->{'min'}, "/", michael@0: int($type_data->{'size'} / $type_data->{'count'}),"/", michael@0: $type_data->{'max'}, ")"; michael@0: print "\t", $type_data->{'count'}, michael@0: " x "; michael@0: } michael@0: print $type; michael@0: michael@0: if ($type_data->{'swept_in'}) { michael@0: print ", $type_data->{'swept_in'} sub-objs absorbed"; michael@0: } michael@0: if ($type_data->{'swept'}) { michael@0: print ", $type_data->{'swept'} swept away"; michael@0: } michael@0: if ($type_data->{'overlap_count'}) { michael@0: print ", $type_data->{'overlap_count'} range overlaps"; michael@0: } michael@0: if ($type_data->{'dup_addr_count'}) { michael@0: print ", $type_data->{'dup_addr_count'} duplicated addresses"; michael@0: } michael@0: michael@0: print "\n" ; michael@0: if (defined $type_data->{'sweep_details_size'}) { michael@0: my $sizes = $type_data->{'sweep_details_size'}; michael@0: my $counts = $type_data->{'sweep_details_count'}; michael@0: my @swept_types = sort {$sizes->{$b} <=> $sizes->{$a}} keys (%{$sizes}); michael@0: michael@0: for my $type (@swept_types) { michael@0: printf " %.2f%% ", $sizes->{$type} * 100.0/$::TotalSize; michael@0: print "$sizes->{$type} (", int($sizes->{$type}/$counts->{$type}) , ") $counts->{$type} x $type\n"; michael@0: } michael@0: print " ---------------\n"; michael@0: } michael@0: } michael@0: if ($bytes_printed_tally != $::TotalSize) { michael@0: printf "%.2f%% ", ($::TotalSize- $bytes_printed_tally) * 100.0/$::TotalSize; michael@0: print $::TotalSize - $bytes_printed_tally, "\t not shown due to truncation of type list\n"; michael@0: print "Currently only data on $::opt_showtype types are displayed, due to command \n", michael@0: "line argument '--showtype=$::opt_showtype'\n\n"; michael@0: } michael@0: michael@0: } michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Check for duplicate address ranges is Objects table, and michael@0: # create list of sorted addresses for doing pointer-chasing michael@0: michael@0: sub validate_address_ranges() { michael@0: # Build sorted list of address for validating interior pointers michael@0: @::SortedAddresses = sort {$a <=> $b} keys %::Objects; michael@0: michael@0: # Validate non-overlap of memory michael@0: my $prev_addr_end = -1; michael@0: my $prev_addr = -1; michael@0: my $index = 0; michael@0: my $overlap_tally = 0; # overlapping object memory michael@0: my $unused_tally = 0; # unused memory between blocks michael@0: while ($index <= $#::SortedAddresses) { michael@0: my $address = $::SortedAddresses[$index]; michael@0: if ($prev_addr_end > $address) { michael@0: print "Object overlap from $::Objects{$prev_addr}->{'type'}:$prev_addr-$prev_addr_end into"; michael@0: my $test_index = $index; michael@0: my $prev_addr_overlap_tally = 0; michael@0: michael@0: while ($test_index <= $#::SortedAddresses) { michael@0: my $test_address = $::SortedAddresses[$test_index]; michael@0: last if ($prev_addr_end < $test_address); michael@0: print " $::Objects{$test_address}->{'type'}:$test_address"; michael@0: michael@0: $::Objects{$prev_addr}->{'overlap_count'}++; michael@0: $::Objects{$test_address}->{'overlap_count'}++; michael@0: my $overlap = $prev_addr_end - $test_address; michael@0: if ($overlap > $::Objects{$test_address}->{'size'}) { michael@0: $overlap = $::Objects{$test_address}->{'size'}; michael@0: } michael@0: print "($overlap bytes)"; michael@0: $prev_addr_overlap_tally += $overlap; michael@0: michael@0: $test_index++; michael@0: } michael@0: print " [total $prev_addr_overlap_tally bytes]"; michael@0: $overlap_tally += $prev_addr_overlap_tally; michael@0: print "\n"; michael@0: } michael@0: michael@0: $prev_addr = $address; michael@0: $prev_addr_end = $prev_addr + $::Objects{$prev_addr}->{'size'} - 1; michael@0: $index++; michael@0: } #end while michael@0: if ($overlap_tally) { michael@0: print "Total overlap of $overlap_tally bytes\n"; michael@0: } michael@0: } michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Evaluate sizes of interobject spacing (fragmentation loss?) michael@0: # Gather the sizes into histograms for analysis michael@0: # This function assumes a sorted list of addresses is present globally michael@0: michael@0: sub generate_and_print_unused_memory_histogram() { michael@0: print "\nInterobject spacing (fragmentation waste) Statistics\n"; michael@0: if ($::opt_fragment <= 1) { michael@0: print "Statistics are not being gathered. Use '--fragment=10' to get stats\n"; michael@0: return; michael@0: } michael@0: print "Ratio of histogram buckets will be a factor of $::opt_fragment\n"; michael@0: michael@0: my $prev_addr_end = -1; michael@0: my $prev_addr = -1; michael@0: my $index = 0; michael@0: michael@0: my @fragment_count; michael@0: my @fragment_tally; michael@0: my $power; michael@0: my $bucket_size; michael@0: michael@0: my $max_power = 0; michael@0: michael@0: my $tally_sizes = 0; michael@0: michael@0: while ($index <= $#::SortedAddresses) { michael@0: my $address = $::SortedAddresses[$index]; michael@0: michael@0: my $unused = $address - $prev_addr_end; michael@0: michael@0: # handle overlaps gracefully michael@0: if ($unused < 0) { michael@0: $unused = 0; michael@0: } michael@0: michael@0: $power = 0; michael@0: $bucket_size = 1; michael@0: while ($bucket_size < $unused) { michael@0: $bucket_size *= $::opt_fragment; michael@0: $power++; michael@0: } michael@0: $fragment_count[$power]++; michael@0: $fragment_tally[$power] += $unused; michael@0: if ($power > $max_power) { michael@0: $max_power = $power; michael@0: } michael@0: my $size = $::Objects{$address}->{'size'}; michael@0: $tally_sizes += $size; michael@0: $prev_addr_end = $address + $size - 1; michael@0: $index++; michael@0: } michael@0: michael@0: michael@0: $power = 0; michael@0: $bucket_size = 1; michael@0: print "Basic gap histogram is (max_size:count):\n"; michael@0: while ($power <= $max_power) { michael@0: if (! defined $fragment_count[$power]) { michael@0: $fragment_count[$power] = $fragment_tally[$power] = 0; michael@0: } michael@0: printf " %.1f:", $bucket_size; michael@0: print $fragment_count[$power]; michael@0: $power++; michael@0: $bucket_size *= $::opt_fragment; michael@0: } michael@0: print "\n"; michael@0: michael@0: print "Summary gap analysis:\n"; michael@0: michael@0: $power = 0; michael@0: $bucket_size = 1; michael@0: my $tally = 0; michael@0: my $count = 0; michael@0: while ($power <= $max_power) { michael@0: $count += $fragment_count[$power]; michael@0: $tally += $fragment_tally[$power]; michael@0: print "$count gaps, totaling $tally bytes, were under "; michael@0: printf "%.1f bytes each", $bucket_size; michael@0: if ($count) { michael@0: printf ", for an average of %.1f bytes per gap", $tally/$count, ; michael@0: } michael@0: print "\n"; michael@0: $power++; michael@0: $bucket_size *= $::opt_fragment; michael@0: } michael@0: michael@0: print "Total allocation was $tally_sizes bytes, or "; michael@0: printf "%.0f bytes per allocation block\n\n", $tally_sizes/($count+1); michael@0: michael@0: } michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Now thread the parents and children together by looking through the michael@0: # slots for each object. michael@0: # michael@0: sub create_parent_links(){ michael@0: my $min_addr = $::SortedAddresses[0]; michael@0: my $max_addr = $::SortedAddresses[ $#::SortedAddresses]; #allow one beyond each object michael@0: $max_addr += $::Objects{$max_addr}->{'size'}; michael@0: michael@0: print "Viable addresses range from $min_addr to $max_addr for a total of ", michael@0: $max_addr-$min_addr, " bytes\n\n"; michael@0: michael@0: # Gather stats as we try to convert slots to children michael@0: my $slot_count = 0; # total slots examined michael@0: my $fixed_addr_count = 0; # slots into interiors that were adjusted michael@0: my $parent_child_count = 0; # Number of parent-child links michael@0: my $child_count = 0; # valid slots, discounting sibling twins michael@0: my $child_dup_count = 0; # number of duplicate child pointers michael@0: my $self_pointer_count = 0; # count of discarded self-pointers michael@0: michael@0: foreach my $parent (keys %::Objects) { michael@0: # We'll collect a list of this parent object's children michael@0: # by iterating through its slots. michael@0: my @children; michael@0: my %children_hash; michael@0: my $self_pointer = 0; michael@0: michael@0: my @slots = @{$::Objects{$parent}->{'slots'}}; michael@0: $slot_count += $#slots + 1; michael@0: SLOT: foreach my $child (@slots) { michael@0: michael@0: # We only care about pointers that refer to other objects michael@0: if (! defined $::Objects{$child}) { michael@0: # check to see if we are an interior pointer michael@0: michael@0: # Punt if we are completely out of range michael@0: next SLOT unless ($max_addr >= $child && michael@0: $child >= $min_addr); michael@0: michael@0: # Do binary search to find object below this address michael@0: my ($min_index, $beyond_index) = (0, $#::SortedAddresses + 1); michael@0: my $test_index; michael@0: while ($min_index != michael@0: ($test_index = int (($beyond_index+$min_index)/2))) { michael@0: if ($child >= $::SortedAddresses[$test_index]) { michael@0: $min_index = $test_index; michael@0: } else { michael@0: $beyond_index = $test_index; michael@0: } michael@0: } michael@0: # See if pointer is within extent of this object michael@0: my $address = $::SortedAddresses[$test_index]; michael@0: next SLOT unless ($child < michael@0: $address + $::Objects{$address}->{'size'}); michael@0: michael@0: # Make adjustment so we point to the actual child precisely michael@0: $child = $address; michael@0: $fixed_addr_count++; michael@0: } michael@0: michael@0: if ($child == $parent) { michael@0: $self_pointer_count++; michael@0: next SLOT; # Discard self-pointers michael@0: } michael@0: michael@0: # Avoid creating duplicate child-parent links michael@0: if (! defined $children_hash{$child}) { michael@0: $parent_child_count++; michael@0: # Add the parent to the child's list of parents michael@0: my $parents = $::Objects{$child}->{'parents'}; michael@0: if (! $parents) { michael@0: $parents = $::Objects{$child}->{'parents'} = []; michael@0: } michael@0: michael@0: $parents->[scalar(@$parents)] = $parent; michael@0: michael@0: # Add the child to the parent's list of children michael@0: $children_hash{$child} = 1; michael@0: } else { michael@0: $child_dup_count++; michael@0: } michael@0: } michael@0: @children = keys %children_hash; michael@0: # Track tally of unique children linked michael@0: $child_count += $#children + 1; michael@0: michael@0: $::Objects{$parent}->{'children'} = \@children; michael@0: michael@0: if (! @children) { michael@0: $::Leafs[$#::Leafs + 1] = $parent; michael@0: } michael@0: } michael@0: print "Scanning $#::SortedAddresses objects, we found $parent_child_count parents-to-child connections by chasing $slot_count pointers.\n", michael@0: "This required $fixed_addr_count interior pointer fixups, skipping $child_dup_count duplicate pointers, ", michael@0: "and $self_pointer_count self pointers\nAlso discarded ", michael@0: $slot_count - $parent_child_count -$self_pointer_count - $child_dup_count, michael@0: " out-of-range pointers\n\n"; michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # For every leaf, if a leaf has only one parent, then sweep the memory michael@0: # cost into the parent from the leaf michael@0: sub sweep_leaf_memory () { michael@0: my $sweep_count = 0; michael@0: my $leaf_counter = 0; michael@0: LEAF: while ($leaf_counter <= $#::Leafs) { michael@0: my $leaf_addr = $::Leafs[$leaf_counter++]; michael@0: my $leaf_obj = $::Objects{$leaf_addr}; michael@0: my $parents = $leaf_obj->{'parents'}; michael@0: michael@0: next LEAF if (! defined($parents) || 1 != scalar(@$parents)); michael@0: michael@0: # We have only one parent, so we'll try to sweep upwards michael@0: my $parent_addr = @$parents[0]; michael@0: my $parent_obj = $::Objects{$parent_addr}; michael@0: michael@0: # watch out for self-pointers michael@0: next LEAF if ($parent_addr == $leaf_addr); michael@0: michael@0: if ($::opt_detail) { michael@0: foreach my $obj ($parent_obj, $leaf_obj) { michael@0: if (!defined $obj->{'original_size'}) { michael@0: $obj->{'original_size'} = $obj->{'size'}; michael@0: } michael@0: } michael@0: if (defined $leaf_obj->{'sweep_details'}) { michael@0: if (defined $parent_obj->{'sweep_details'}) { # merge details michael@0: foreach my $swept_obj (keys (%{$leaf_obj->{'sweep_details'}})) { michael@0: %{$parent_obj->{'sweep_details'}}->{$swept_obj} = michael@0: %{$leaf_obj->{'sweep_details'}}->{$swept_obj}; michael@0: } michael@0: } else { # No parent info michael@0: $parent_obj->{'sweep_details'} = \%{$leaf_obj->{'sweep_details'}}; michael@0: } michael@0: delete $leaf_obj->{'sweep_details'}; michael@0: } else { # no leaf detail michael@0: if (!defined $parent_obj->{'sweep_details'}) { michael@0: $parent_obj->{'sweep_details'} = {}; michael@0: } michael@0: } michael@0: %{$parent_obj->{'sweep_details'}}->{$leaf_addr} = $leaf_obj->{'original_size'}; michael@0: } michael@0: michael@0: $parent_obj->{'size'} += $leaf_obj->{'size'}; michael@0: $leaf_obj->{'size'} = 0; michael@0: michael@0: if (defined ($leaf_obj->{'swept_in'})) { michael@0: $parent_obj->{'swept_in'} += $leaf_obj->{'swept_in'}; michael@0: $leaf_obj->{'swept_in'} = 0; # sweep has been handed off to parent michael@0: } michael@0: $parent_obj->{'swept_in'} ++; # tally swept in leaf_obj michael@0: michael@0: $sweep_count++; michael@0: michael@0: # See if we created another leaf michael@0: my $consumed_children = $parent_obj->{'consumed'}++; michael@0: my @children = $parent_obj->{'children'}; michael@0: if ($consumed_children == $#children) { michael@0: $::Leafs[$#::Leafs + 1] = @$parents[0]; michael@0: } michael@0: } michael@0: print "Processed ", $leaf_counter, " leaves sweeping memory to parents in ", $sweep_count, " objects\n"; michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Subdivide the types of objects that are in our "expand" list michael@0: # List types that should be sub-divided based on parents, and possibly michael@0: # children michael@0: # The argument supplied is a hash table with keys selecting types that michael@0: # need to be "refined" by including the types of the parent objects, michael@0: # and (when we are desparate) the types of the children objects. michael@0: michael@0: sub expand_type_names($) { michael@0: my %TypeExpand = %{$_[0]}; michael@0: michael@0: my @retype; # array of addrs that get extended type names michael@0: foreach my $child (keys %::Objects) { michael@0: my $child_obj = $::Objects{$child}; michael@0: next unless (defined ($TypeExpand{$child_obj->{'type'}})); michael@0: michael@0: foreach my $relation ('parents','children') { michael@0: my $relatives = $child_obj->{$relation}; michael@0: next unless defined @$relatives; michael@0: michael@0: # Sort out the names of the types of the relatives michael@0: my %names; michael@0: foreach my $relative (@$relatives) { michael@0: %names->{$::Objects{$relative}->{'type'}} = 1; michael@0: } michael@0: my $related_type_names = join(',' , sort(keys(%names))); michael@0: michael@0: michael@0: $child_obj->{'name' . $relation} = $related_type_names; michael@0: michael@0: # Don't bother with children if we have significant parent types michael@0: last if (!defined ($TypeExpand{$related_type_names})); michael@0: } michael@0: $retype[$#retype + 1] = $child; michael@0: } michael@0: michael@0: # Revisit all addresses we've marked michael@0: foreach my $child (@retype) { michael@0: my $child_obj = $::Objects{$child}; michael@0: $child_obj->{'type'} = $TypeExpand{$child_obj->{'type'}}; michael@0: my $extended_type = $child_obj->{'namechildren'}; michael@0: if (defined $extended_type) { michael@0: $child_obj->{'type'}.= "->(" . $extended_type . ")"; michael@0: delete ($child_obj->{'namechildren'}); michael@0: } michael@0: $extended_type = $child_obj->{'nameparents'}; michael@0: if (defined $extended_type) { michael@0: $child_obj->{'type'} = "(" . $extended_type . ")->" . $::Objects{$child}->{'type'}; michael@0: delete ($child_obj->{'nameparents'}); michael@0: } michael@0: } michael@0: } michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Print out a type histogram michael@0: michael@0: sub print_type_histogram() { michael@0: load_type_table(); michael@0: print_type_table(); michael@0: print "\n\n"; michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # Provide a nice summary of the types during the process michael@0: validate_address_ranges(); michael@0: create_parent_links(); michael@0: michael@0: print "\nBasic memory use histogram is:\n"; michael@0: print_type_histogram(); michael@0: michael@0: generate_and_print_unused_memory_histogram(); michael@0: michael@0: sweep_leaf_memory (); michael@0: print "After doing basic leaf-sweep processing of instances:\n"; michael@0: print_type_histogram(); michael@0: michael@0: { michael@0: foreach my $typename (@::opt_typedivide) { michael@0: my %expansion_table; michael@0: $expansion_table{$typename} = $typename; michael@0: expand_type_names(\%expansion_table); michael@0: print "After subdividing <$typename> based on inbound (and somtimes outbound) pointers:\n"; michael@0: print_type_histogram(); michael@0: } michael@0: } michael@0: michael@0: exit(); # Don't bother with SCCs yet. michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Determine objects that entrain equivalent sets, using the strongly michael@0: # connected component algorithm from Cormen, Leiserson, and Rivest, michael@0: # ``An Introduction to Algorithms'', MIT Press 1990, pp. 488-493. michael@0: # michael@0: sub compute_post_order($$$) { michael@0: # This routine produces a post-order of the call graph (what CLR call michael@0: # ``ordering the nodes by f[u]'') michael@0: my ($parent, $visited, $finish) = @_; michael@0: michael@0: # Bail if we've already seen this node michael@0: return if $visited->{$parent}; michael@0: michael@0: # We have now! michael@0: $visited->{$parent} = 1; michael@0: michael@0: # Walk the children michael@0: my $children = $::Objects{$parent}->{'children'}; michael@0: michael@0: foreach my $child (@$children) { michael@0: compute_post_order($child, $visited, $finish); michael@0: } michael@0: michael@0: # Now that we've walked all the kids, we can append the parent to michael@0: # the post-order michael@0: @$finish[scalar(@$finish)] = $parent; michael@0: } michael@0: michael@0: sub compute_equivalencies($$$) { michael@0: # This routine recursively computes equivalencies by walking the michael@0: # transpose of the callgraph. michael@0: my ($child, $table, $equivalencies) = @_; michael@0: michael@0: # Bail if we've already seen this node michael@0: return if $table->{$child}; michael@0: michael@0: # Otherwise, append ourself to the list of equivalencies... michael@0: @$equivalencies[scalar(@$equivalencies)] = $child; michael@0: michael@0: # ...and note our other equivalents in the table michael@0: $table->{$child} = $equivalencies; michael@0: michael@0: my $parents = $::Objects{$child}->{'parents'}; michael@0: michael@0: foreach my $parent (@$parents) { michael@0: compute_equivalencies($parent, $table, $equivalencies); michael@0: } michael@0: } michael@0: michael@0: sub compute_equivalents() { michael@0: # Here's the strongly connected components algorithm. (Step 2 has been michael@0: # done implictly by our object graph construction.) michael@0: my %visited; michael@0: my @finish; michael@0: michael@0: # Step 1. Compute a post-ordering of the object graph michael@0: foreach my $parent (keys %::Objects) { michael@0: compute_post_order($parent, \%visited, \@finish); michael@0: } michael@0: michael@0: # Step 3. Traverse the transpose of the object graph in reverse michael@0: # post-order, collecting vertices into %equivalents michael@0: my %equivalents; michael@0: foreach my $child (reverse @finish) { michael@0: compute_equivalencies($child, \%equivalents, []); michael@0: } michael@0: michael@0: # Now, we'll trim the %equivalents table, arbitrarily removing michael@0: # ``redundant'' entries. michael@0: EQUIVALENT: foreach my $node (keys %equivalents) { michael@0: my $equivalencies = $equivalents{$node}; michael@0: next EQUIVALENT unless $equivalencies; michael@0: michael@0: foreach my $equivalent (@$equivalencies) { michael@0: delete $equivalents{$equivalent} unless $equivalent == $node; michael@0: } michael@0: } michael@0: michael@0: # Note the equivalent objects in a way that will yield the most michael@0: # interesting order as we do depth-first traversal later to michael@0: # output them. michael@0: ROOT: foreach my $equivalent (reverse @finish) { michael@0: next ROOT unless $equivalents{$equivalent}; michael@0: $::Equivalents[$#::Equivalents + 1] = $equivalent; michael@0: michael@0: # XXX Lame! Should figure out function refs. michael@0: $::Objects{$equivalent}->{'entrained-size'} = 0; michael@0: } michael@0: } michael@0: michael@0: # Do it! michael@0: compute_equivalents(); michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Compute the size of each node's transitive closure. michael@0: # michael@0: sub compute_entrained($$) { michael@0: my ($parent, $visited) = @_; michael@0: michael@0: $visited->{$parent} = 1; michael@0: michael@0: $::Objects{$parent}->{'entrained-size'} = $::Objects{$parent}->{'size'}; michael@0: michael@0: my $children = $::Objects{$parent}->{'children'}; michael@0: CHILD: foreach my $child (@$children) { michael@0: next CHILD if $visited->{$child}; michael@0: michael@0: compute_entrained($child, $visited); michael@0: $::Objects{$parent}->{'entrained-size'} += $::Objects{$child}->{'entrained-size'}; michael@0: } michael@0: } michael@0: michael@0: if (! $::opt_noentrained) { michael@0: my %visited; michael@0: michael@0: PARENT: foreach my $parent (@::Equivalents) { michael@0: next PARENT if $visited{$parent}; michael@0: compute_entrained($parent, \%visited); michael@0: } michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Converts a shared library and an address into a file and line number michael@0: # using a bunch of addr2line processes. michael@0: # michael@0: sub addr2line($$) { michael@0: my ($dso, $addr) = @_; michael@0: michael@0: # $::Addr2Lines is a global table that maps a DSO's name to a pair michael@0: # of filehandles that are talking to an addr2line process. michael@0: my $fhs = $::Addr2Lines{$dso}; michael@0: if (! $fhs) { michael@0: if (!(-r $dso)) { michael@0: # bogus filename (that happens sometimes), so bail michael@0: return { 'dso' => $dso, 'addr' => $addr }; michael@0: } michael@0: my ($in, $out) = (new FileHandle, new FileHandle); michael@0: open2($in, $out, "addr2line --exe=$dso") || die "unable to open addr2line --exe=$dso"; michael@0: $::Addr2Lines{$dso} = $fhs = { 'in' => $in, 'out' => $out }; michael@0: } michael@0: michael@0: # addr2line takes a hex address as input... michael@0: $fhs->{'out'}->print($addr . "\n"); michael@0: michael@0: # ...and'll return file:lineno as output michael@0: if ($fhs->{'in'}->getline() =~ /([^:]+):(.+)/) { michael@0: return { 'file' => $1, 'line' => $2 }; michael@0: } michael@0: else { michael@0: return { 'dso' => $dso, 'addr' => $addr }; michael@0: } michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Dump the objects, using a depth-first traversal. michael@0: # michael@0: sub dump_objects($$$) { michael@0: my ($parent, $visited, $depth) = @_; michael@0: michael@0: # Have we already seen this? michael@0: my $already_visited = $visited->{$parent}; michael@0: return if ($depth == 0 && $already_visited); michael@0: michael@0: if (! $already_visited) { michael@0: $visited->{$parent} = 1; michael@0: $::Total += $::Objects{$parent}->{'size'}; michael@0: } michael@0: michael@0: my $parententry = $::Objects{$parent}; michael@0: michael@0: # Make an ``object'' div, which'll contain an ``object'' span, two michael@0: # ``toggle'' spans, an invisible ``stack'' div, and the invisible michael@0: # ``children'' div. michael@0: print "
"; michael@0: michael@0: if ($already_visited) { michael@0: print ""; michael@0: } michael@0: else { michael@0: print ""; michael@0: } michael@0: michael@0: printf "0x%x<%s>[%d]", $parent, $parententry->{'type'}, $parententry->{'size'}; michael@0: michael@0: if ($already_visited) { michael@0: print ""; michael@0: goto DONE; michael@0: } michael@0: michael@0: if ($depth == 0) { michael@0: print "($parententry->{'entrained-size'})" michael@0: if $parententry->{'entrained-size'}; michael@0: michael@0: print " Children" michael@0: if @{$parententry->{'children'}} > 0; michael@0: } michael@0: michael@0: if (($depth == 0 || !$::opt_nochildstacks) && !$::opt_nostacks) { michael@0: print " Stack"; michael@0: } michael@0: michael@0: print ""; michael@0: michael@0: # Print stack traces michael@0: print "
\n"; michael@0: michael@0: if (($depth == 0 || !$::opt_nochildstacks) && !$::opt_nostacks) { michael@0: my $depth = $::opt_depth; michael@0: michael@0: FRAME: foreach my $frame (@{$parententry->{'stack'}}) { michael@0: # Only go as deep as they've asked us to. michael@0: last FRAME unless --$depth >= 0; michael@0: michael@0: # Stack frames look like ``mangled_name[dso address]'' michael@0: $frame =~ /([^\]]+)\[(.*) \+0x([0-9A-Fa-f]+)\]/; michael@0: michael@0: # Convert address to file and line number michael@0: my $mangled = $1; michael@0: my $result = addr2line($2, $3); michael@0: michael@0: if ($result->{'file'}) { michael@0: # It's mozilla source! Clean up refs to dist/include michael@0: if (($result->{'file'} =~ s/.*\.\.\/\.\.\/dist\/include\//http:\/\/bonsai.mozilla.org\/cvsguess.cgi\?file=/) || michael@0: ($result->{'file'} =~ s/.*\/mozilla/http:\/\/bonsai.mozilla.org\/cvsblame.cgi\?file=mozilla/)) { michael@0: my $prevline = $result->{'line'} - 10; michael@0: print "{'file'}\&mark=$result->{'line'}#$prevline\">$mangled
\n"; michael@0: } michael@0: else { michael@0: print "$mangled ($result->{'file'}, line $result->{'line'})
\n"; michael@0: } michael@0: } michael@0: else { michael@0: print "$result->{'dso'} ($result->{'addr'})
\n"; michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: print "
"; michael@0: michael@0: # Recurse to children michael@0: if (@{$parententry->{'children'}} >= 0) { michael@0: print "
\n" if $depth == 0; michael@0: michael@0: foreach my $child (@{$parententry->{'children'}}) { michael@0: dump_objects($child, $visited, $depth + 1); michael@0: } michael@0: michael@0: print "
" if $depth == 0; michael@0: } michael@0: michael@0: DONE: michael@0: print "
\n"; michael@0: } michael@0: michael@0: michael@0: #---------------------------------------------------------------------- michael@0: # michael@0: # Do the output. michael@0: # michael@0: michael@0: # Force flush on STDOUT. We get funky output unless we do this. michael@0: $| = 1; michael@0: michael@0: # Header michael@0: print " michael@0: michael@0: Object Graph michael@0: michael@0: michael@0: michael@0: michael@0: "; michael@0: michael@0: { michael@0: # Body. Display ``roots'', sorted by the amount of memory they michael@0: # entrain. Because of the way we've sorted @::Equivalents, we should michael@0: # get a nice ordering that sorts things with a lot of kids early michael@0: # on. This should yield a fairly "deep" depth-first traversal, with michael@0: # most of the objects appearing as children. michael@0: # michael@0: # XXX I sure hope that Perl implements a stable sort! michael@0: my %visited; michael@0: michael@0: foreach my $parent (sort { $::Objects{$b}->{'entrained-size'} michael@0: <=> $::Objects{$a}->{'entrained-size'} } michael@0: @::Equivalents) { michael@0: dump_objects($parent, \%visited, 0); michael@0: print "\n"; michael@0: } michael@0: } michael@0: michael@0: # Footer michael@0: print "
$::Total total bytes\n" if $::Total; michael@0: print " michael@0: michael@0: "; michael@0: