1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/tools/trace-malloc/leak-soup.pl Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1180 @@ 1.4 +#!/usr/bin/perl -w 1.5 +# 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 +# A perl version of Patrick Beard's ``Leak Soup'', which processes the 1.11 +# stack crawls from the Boehm GC into a graph. 1.12 + 1.13 +use 5.004; 1.14 +use strict; 1.15 +use Getopt::Long; 1.16 +use FileHandle; 1.17 +use IPC::Open2; 1.18 + 1.19 +# Collect program options 1.20 +$::opt_help = 0; 1.21 +$::opt_detail = 0; 1.22 +$::opt_fragment = 1.0; # Default to no fragment analysis 1.23 +$::opt_nostacks = 0; 1.24 +$::opt_nochildstacks = 0; 1.25 +$::opt_depth = 9999; 1.26 +$::opt_noentrained = 0; 1.27 +$::opt_noslop = 0; 1.28 +$::opt_showtype = -1; # default to listing all types 1.29 +$::opt_stackrefine = "C"; 1.30 +@::opt_stackretype = (); 1.31 +@::opt_stackskipclass = (); 1.32 +@::opt_stackskipfunc = (); 1.33 +@::opt_typedivide = (); 1.34 + 1.35 +GetOptions("help", "detail", "format=s", "fragment=f", "nostacks", 1.36 + "nochildstacks", "depth=i", "noentrained", "noslop", "showtype=i", 1.37 + "stackrefine=s", "stackretype=s@", "stackskipclass=s@", "stackskipfunc=s@", 1.38 + "typedivide=s@" 1.39 + ); 1.40 + 1.41 +if ($::opt_help) { 1.42 + die "usage: leak-soup.pl [options] <leakfile> 1.43 + --help Display this message 1.44 + --detail Provide details of memory sweeping from child to parents 1.45 + --fragment=ratio Histogram bucket ratio for fragmentation analysis 1.46 +# --nostacks Do not compute stack traces 1.47 +# --nochildstacks Do not compute stack traces for entrained objects 1.48 +# --depth=<max> Only compute stack traces to depth of <max> 1.49 +# --noentrained Do not compute amount of memory entrained by root objects 1.50 + --noslop Don't ignore low bits when searching for pointers 1.51 + --showtype=<i> Show memory usage histogram for most-significant <i> types 1.52 + --stackrefine={F|C} During stack based refinement, use 'F'ull name name or just 'C'lass 1.53 + --stackretype=type Use allocation stack to refine vague types like void* 1.54 + --stackskipclass=class When refining types, ignore stack frames from 'class' 1.55 + --stackskipfunc=func When refining types, ignore stack frames for 'func' 1.56 + --typedivide=type Subdivide 'type' based on objects pointing to each instance 1.57 +"; 1.58 +} 1.59 + 1.60 +# This is the table that keeps a graph of objects. It's indexed by the 1.61 +# object's address (as an integer), and refers to a simple hash that 1.62 +# has information about the object's type, size, slots, and allocation 1.63 +# stack. 1.64 +%::Objects = %{0}; 1.65 + 1.66 +# This will be a list of keys to (addresses in) Objects, that is sorted 1.67 +# It gets used to evaluate overlaps, calculate fragmentation, and chase 1.68 +# parent->child (interior) pointers. 1.69 +@::SortedAddresses = []; 1.70 + 1.71 +# This is the table that keeps track of memory usage on a per-type basis. 1.72 +# It is indexed by the type name (string), and keeps a tally of the 1.73 +# total number of such objects, and the memory usage of such objects. 1.74 +%::Types = %{0}; 1.75 +$::TotalSize = 0; # sum of sizes of all objects included $::Types{} 1.76 + 1.77 +# This is an array of leaf node addresses. A leaf node has no children 1.78 +# with memory allocations. We traverse them sweeping memory 1.79 +# tallies into parents. Note that after all children have 1.80 +# been swept into a parent, that parent may also become a leaf node. 1.81 +@::Leafs = @{0}; 1.82 + 1.83 + 1.84 + 1.85 + 1.86 +#---------------------------------------------------------------------- 1.87 +# 1.88 +# Decode arguments to override default values for doing call-stack-based 1.89 +# refinement of typename based on contents of the stack at allocation time. 1.90 +# 1.91 + 1.92 +# List the types that we need to refine (if any) based on allocation stack 1.93 +$::VagueType = { 1.94 + 'void*' => 1, 1.95 +}; 1.96 + 1.97 +# With regard to the stack, ignore stack frames in the following 1.98 +# overly vague classes. 1.99 +$::VagueClasses = { 1.100 +# 'nsStr' => 1, 1.101 + 'nsVoidArray' => 1, 1.102 +}; 1.103 + 1.104 +# With regard to stack, ignore stack frames with the following vague 1.105 +# function names 1.106 +$::VagueFunctions = { 1.107 + 'PL_ArenaAllocate' => 1, 1.108 + 'PL_HashTableFinalize(PLHashTable *)' => 1, 1.109 + 'PL_HashTableInit__FP11PLHashTableUiPFPCv_UiPFPCvPCv_iT3PC14PLHashAllocOpsPv' => 1, 1.110 + 'PL_HashTableRawAdd' => 1, 1.111 + '__builtin_vec_new' => 1, 1.112 + '_init' => 1, 1.113 + 'il_get_container(_IL_GroupContext *, ImgCachePolicy, char const *, _NI_IRGB *, IL_DitherMode, int, int, int)' => 1, 1.114 + 'nsCStringKey::Clone(void) const' => 1, 1.115 + 'nsCppSharedAllocator<unsigned short>::allocate(unsigned int, void const *)' => 1, 1.116 + 'nsHashtable::Put(nsHashKey *, void *)' => 1, 1.117 + 'nsHashtable::nsHashtable(unsigned int, int)' => 1, 1.118 + 'nsMemory::Alloc(unsigned int)' => 1, 1.119 + 'nsMemoryImpl::Alloc(unsigned int)' => 1, 1.120 +}; 1.121 + 1.122 +sub init_stack_based_type_refinement() { 1.123 + # Move across stackretype options, or use default values 1.124 + if ($#::opt_stackretype < 0) { 1.125 + print "Default --stackretype options will be used (since none were specified)\n"; 1.126 + print " use --stackretype='nothing' to disable re-typing activity\n"; 1.127 + } else { 1.128 + foreach my $type (keys %{$::VagueType}) { 1.129 + delete ($::VagueType->{$type}); 1.130 + } 1.131 + if ($#::opt_stackretype == 0 && $::opt_stackretype[0] eq 'nothing') { 1.132 + print "Types will not be refined based on call stack\n"; 1.133 + } else { 1.134 + foreach my $type (@::opt_stackretype) { 1.135 + $::VagueType->{$type} = 1; 1.136 + } 1.137 + } 1.138 + } 1.139 + 1.140 + 1.141 + if (keys %{$::VagueType}) { 1.142 + print "The following type(s) will be refined based on call stacks:\n"; 1.143 + foreach my $type (sort keys %{$::VagueType}) { 1.144 + print " $type\n"; 1.145 + } 1.146 + print "Equivalent command line argument(s):\n"; 1.147 + foreach my $type (sort keys %{$::VagueType}) { 1.148 + print " --stackretype='$type'"; 1.149 + } 1.150 + print "\n\n"; 1.151 + 1.152 + if ($#::opt_stackskipclass < 0) { 1.153 + print "Default --stackskipclass options will be used (since none were specified)\n"; 1.154 + print " use --stackskipclass='nothing' to disable skipping stack frames based on class names\n"; 1.155 + } else { 1.156 + foreach my $type (keys %{$::VagueClasses}) { 1.157 + delete ($::VagueClasses->{$type}); 1.158 + } 1.159 + if ($#::opt_stackskipclass == 0 && $::opt_stackskipclass[0] eq 'nothing') { 1.160 + print "Types will not be refined based on call stack\n"; 1.161 + } else { 1.162 + foreach my $type (@::opt_stackskipclass) { 1.163 + $::VagueClasses->{$type} = 1; 1.164 + } 1.165 + } 1.166 + } 1.167 + 1.168 + if (keys %{$::VagueClasses}) { 1.169 + print "Stack frames from the following class(es) will not be used to refine types:\n"; 1.170 + foreach my $class (sort keys %{$::VagueClasses}) { 1.171 + print " $class\n"; 1.172 + } 1.173 + print "Equivalent command line argument(s):\n"; 1.174 + foreach my $class (sort keys %{$::VagueClasses}) { 1.175 + print " --stackskipclass='$class'"; 1.176 + } 1.177 + print "\n\n"; 1.178 + } 1.179 + 1.180 + 1.181 + if ($#::opt_stackskipfunc < 0) { 1.182 + print "Default --stackskipfunc options will be used (since none were specified)\n"; 1.183 + print " use --stackskipfunc='nothing' to disable skipping stack frames based on function names\n"; 1.184 + } else { 1.185 + foreach my $type (keys %{$::VagueFunctions}) { 1.186 + delete ($::VagueFunctions->{$type}); 1.187 + } 1.188 + if ($#::opt_stackskipfunc == 0 && $::opt_stackskipfunc[0] eq 'nothing') { 1.189 + print "Types will not be refined based on call stack\n"; 1.190 + } else { 1.191 + foreach my $type (@::opt_stackskipfunc) { 1.192 + $::VagueFunctions->{$type} = 1; 1.193 + } 1.194 + } 1.195 + } 1.196 + 1.197 + if (keys %{$::VagueFunctions}) { 1.198 + print "Stack frames from the following function(s) will not be used to refine types:\n"; 1.199 + foreach my $func (sort keys %{$::VagueFunctions}) { 1.200 + print " $func\n"; 1.201 + } 1.202 + print "Equivalent command line argument(s):\n"; 1.203 + foreach my $func (sort keys %{$::VagueFunctions}) { 1.204 + print " --stackskipfunc='$func'"; 1.205 + } 1.206 + print "\n\n"; 1.207 + } 1.208 + } 1.209 +} 1.210 + 1.211 + 1.212 +#---------------------------------------------------------------------- 1.213 +# 1.214 +# Read in the output from the Boehm GC or Trace-malloc. 1.215 +# 1.216 +sub read_boehm() { 1.217 + OBJECT: while (<>) { 1.218 + # e.g., 0x0832FBD0 <void*> (80) 1.219 + next OBJECT unless /^0x(\S+) <(.*)> \((\d+)\)/; 1.220 + my ($addr, $type, $size) = (hex $1, $2, $3); 1.221 + 1.222 + my $object = $::Objects{$addr}; 1.223 + if (! $object) { 1.224 + # Found a new object entry. Record its type and size 1.225 + $::Objects{$addr} = 1.226 + $object = 1.227 + { 'type' => $type, 'size' => $size }; 1.228 + } else { 1.229 + print "Duplicate address $addr contains $object->{'type'} and $type\n"; 1.230 + $object->{'dup_addr_count'}++; 1.231 + } 1.232 + 1.233 + # Record the object's slots 1.234 + my @slots; 1.235 + 1.236 + SLOT: while (<>) { 1.237 + # e.g., 0x00000000 1.238 + last SLOT unless /^\t0x(\S+)/; 1.239 + my $value = hex $1; 1.240 + 1.241 + # Ignore low bits, unless they've specified --noslop 1.242 + $value &= ~0x7 unless $::opt_noslop; 1.243 + 1.244 + $slots[$#slots + 1] = $value; 1.245 + } 1.246 + 1.247 + $object->{'slots'} = \@slots; 1.248 + 1.249 + if (@::opt_stackretype && (defined $::VagueType->{$type})) { 1.250 + # Change the value of type of the object based on stack 1.251 + # if we can find an interesting calling function 1.252 + VAGUEFRAME: while (<>) { 1.253 + # e.g., _dl_debug_message[/lib/ld-linux.so.2 +0x0000B858] 1.254 + last VAGUEFRAMEFRAME unless /^(.*)\[(.*) \+0x(\S+)\]$/; 1.255 + my ($func, $lib, $off) = ($1, $2, hex $3); 1.256 + chomp; 1.257 + 1.258 + my ($class,,$fname) = split(/:/, $func); 1.259 + next VAGUEFRAME if (defined $::VagueFunctions->{$func} || 1.260 + defined $::VagueClasses->{$class}); 1.261 + 1.262 + # Refine typename and exit stack scan 1.263 + $object->{'type'} = $type . ":" . 1.264 + (('C' eq $::opt_stackrefine) ? 1.265 + $class : 1.266 + $func); 1.267 + last VAGUEFRAME; 1.268 + } 1.269 + } else { 1.270 + # Save all stack info if requested 1.271 + if (! $::opt_nostacks) { 1.272 + # Record the stack by which the object was allocated 1.273 + my @stack; 1.274 + 1.275 + FRAME: while (<>) { 1.276 + # e.g., _dl_debug_message[/lib/ld-linux.so.2 +0x0000B858] 1.277 + last FRAME unless /^(.*)\[(.*) \+0x(\S+)\]$/; 1.278 + my ($func, $lib, $off) = ($1, $2, hex $3); 1.279 + chomp; 1.280 + 1.281 + $stack[$#stack + 1] = $_; 1.282 + } 1.283 + 1.284 + $object->{'stack'} = \@stack; 1.285 + } 1.286 + } 1.287 + 1.288 + # Gotta check EOF explicitly... 1.289 + last OBJECT if eof; 1.290 + } 1.291 +} 1.292 + 1.293 + 1.294 +#---------------------------------------------------------------------- 1.295 +# 1.296 +# Read input 1.297 +# 1.298 +init_stack_based_type_refinement(); 1.299 +read_boehm; 1.300 + 1.301 + 1.302 + 1.303 +#---------------------------------------------------------------------- 1.304 +# 1.305 +# Do basic initialization of the type hash table. Accumulate 1.306 +# total counts, and basic memory usage (not including children) 1.307 +sub load_type_table() { 1.308 + # Reset global counter and hash table 1.309 + $::TotalSize = 0; 1.310 + %::Types = %{0}; 1.311 + 1.312 + OBJECT: foreach my $addr (keys %::Objects) { 1.313 + my $obj = $::Objects{$addr}; 1.314 + my ($type, $size, $swept_in, $overlap_count, $dup_addr_count) = 1.315 + ($obj->{'type'}, $obj->{'size'}, 1.316 + $obj->{'swept_in'}, 1.317 + $obj->{'overlap_count'},$obj->{'dup_addr_count'}); 1.318 + 1.319 + my $type_data = $::Types{$type}; 1.320 + if (! defined $type_data) { 1.321 + $::Types{$type} = 1.322 + $type_data = {'count' => 0, 'size' => 0, 1.323 + 'max' => $size, 'min' => $size, 1.324 + 'swept_in' => 0, 'swept' => 0, 1.325 + 'overlap_count' => 0, 1.326 + 'dup_addr_count' => 0}; 1.327 + } 1.328 + 1.329 + if (!$size) { 1.330 + $type_data->{'swept'}++; 1.331 + next OBJECT; 1.332 + } 1.333 + $::TotalSize += $size; 1.334 + 1.335 + $type_data->{'count'}++; 1.336 + $type_data->{'size'} += $size; 1.337 + if (defined $swept_in) { 1.338 + $type_data->{'swept_in'} += $swept_in; 1.339 + 1.340 + if ($::opt_detail) { 1.341 + my $type_detail_sizes = $type_data->{'sweep_details_size'}; 1.342 + my $type_detail_counts; 1.343 + if (!defined $type_detail_sizes) { 1.344 + $type_detail_sizes = $type_data->{'sweep_details_size'} = {}; 1.345 + $type_detail_counts = $type_data->{'sweep_details_count'} = {}; 1.346 + } else { 1.347 + $type_detail_counts = $type_data->{'sweep_details_count'}; 1.348 + } 1.349 + 1.350 + my $sweep_details = $obj->{'sweep_details'}; 1.351 + for my $swept_addr (keys (%{$sweep_details})) { 1.352 + my $swept_obj = $::Objects{$swept_addr}; 1.353 + my $swept_type = $swept_obj->{'type'}; 1.354 + $type_detail_sizes->{$swept_type} += $sweep_details->{$swept_addr}; 1.355 + $type_detail_counts->{$swept_type}++; 1.356 + } 1.357 + } 1.358 + } 1.359 + if (defined $overlap_count) { 1.360 + $type_data->{'overlap_count'} += $overlap_count; 1.361 + } 1.362 + 1.363 + if (defined $dup_addr_count) { 1.364 + $type_data->{'dup_addr_count'} += $dup_addr_count; 1.365 + } 1.366 + 1.367 + if ($type_data->{'max'} < $size) { 1.368 + $type_data->{'max'} = $size; 1.369 + } 1.370 + # Watch out for case where min is produced by a swept object 1.371 + if (!$type_data->{'min'} || $type_data->{'min'} > $size) { 1.372 + $type_data->{'min'} = $size; 1.373 + } 1.374 + } 1.375 +} 1.376 + 1.377 + 1.378 +#---------------------------------------------------------------------- 1.379 +sub print_type_table(){ 1.380 + if (!$::opt_showtype) { 1.381 + return; 1.382 + } 1.383 + my $line_count = 0; 1.384 + my $bytes_printed_tally = 0; 1.385 + 1.386 + # Display type summary information 1.387 + my @sorted_types = keys (%::Types); 1.388 + print "There are ", 1 + $#sorted_types, " types containing ", $::TotalSize, " bytes\n"; 1.389 + @sorted_types = sort {$::Types{$b}->{'size'} 1.390 + <=> $::Types{$a}->{'size'} } @sorted_types; 1.391 + 1.392 + foreach my $type (@sorted_types) { 1.393 + last if ($line_count++ == $::opt_showtype); 1.394 + 1.395 + my $type_data = $::Types{$type}; 1.396 + $bytes_printed_tally += $type_data->{'size'}; 1.397 + 1.398 + if ($type_data->{'count'}) { 1.399 + printf "%.2f%% ", $type_data->{'size'} * 100.0/$::TotalSize; 1.400 + print $type_data->{'size'}, 1.401 + "\t(", 1.402 + $type_data->{'min'}, "/", 1.403 + int($type_data->{'size'} / $type_data->{'count'}),"/", 1.404 + $type_data->{'max'}, ")"; 1.405 + print "\t", $type_data->{'count'}, 1.406 + " x "; 1.407 + } 1.408 + print $type; 1.409 + 1.410 + if ($type_data->{'swept_in'}) { 1.411 + print ", $type_data->{'swept_in'} sub-objs absorbed"; 1.412 + } 1.413 + if ($type_data->{'swept'}) { 1.414 + print ", $type_data->{'swept'} swept away"; 1.415 + } 1.416 + if ($type_data->{'overlap_count'}) { 1.417 + print ", $type_data->{'overlap_count'} range overlaps"; 1.418 + } 1.419 + if ($type_data->{'dup_addr_count'}) { 1.420 + print ", $type_data->{'dup_addr_count'} duplicated addresses"; 1.421 + } 1.422 + 1.423 + print "\n" ; 1.424 + if (defined $type_data->{'sweep_details_size'}) { 1.425 + my $sizes = $type_data->{'sweep_details_size'}; 1.426 + my $counts = $type_data->{'sweep_details_count'}; 1.427 + my @swept_types = sort {$sizes->{$b} <=> $sizes->{$a}} keys (%{$sizes}); 1.428 + 1.429 + for my $type (@swept_types) { 1.430 + printf " %.2f%% ", $sizes->{$type} * 100.0/$::TotalSize; 1.431 + print "$sizes->{$type} (", int($sizes->{$type}/$counts->{$type}) , ") $counts->{$type} x $type\n"; 1.432 + } 1.433 + print " ---------------\n"; 1.434 + } 1.435 + } 1.436 + if ($bytes_printed_tally != $::TotalSize) { 1.437 + printf "%.2f%% ", ($::TotalSize- $bytes_printed_tally) * 100.0/$::TotalSize; 1.438 + print $::TotalSize - $bytes_printed_tally, "\t not shown due to truncation of type list\n"; 1.439 + print "Currently only data on $::opt_showtype types are displayed, due to command \n", 1.440 + "line argument '--showtype=$::opt_showtype'\n\n"; 1.441 + } 1.442 + 1.443 +} 1.444 + 1.445 +#---------------------------------------------------------------------- 1.446 +# 1.447 +# Check for duplicate address ranges is Objects table, and 1.448 +# create list of sorted addresses for doing pointer-chasing 1.449 + 1.450 +sub validate_address_ranges() { 1.451 + # Build sorted list of address for validating interior pointers 1.452 + @::SortedAddresses = sort {$a <=> $b} keys %::Objects; 1.453 + 1.454 + # Validate non-overlap of memory 1.455 + my $prev_addr_end = -1; 1.456 + my $prev_addr = -1; 1.457 + my $index = 0; 1.458 + my $overlap_tally = 0; # overlapping object memory 1.459 + my $unused_tally = 0; # unused memory between blocks 1.460 + while ($index <= $#::SortedAddresses) { 1.461 + my $address = $::SortedAddresses[$index]; 1.462 + if ($prev_addr_end > $address) { 1.463 + print "Object overlap from $::Objects{$prev_addr}->{'type'}:$prev_addr-$prev_addr_end into"; 1.464 + my $test_index = $index; 1.465 + my $prev_addr_overlap_tally = 0; 1.466 + 1.467 + while ($test_index <= $#::SortedAddresses) { 1.468 + my $test_address = $::SortedAddresses[$test_index]; 1.469 + last if ($prev_addr_end < $test_address); 1.470 + print " $::Objects{$test_address}->{'type'}:$test_address"; 1.471 + 1.472 + $::Objects{$prev_addr}->{'overlap_count'}++; 1.473 + $::Objects{$test_address}->{'overlap_count'}++; 1.474 + my $overlap = $prev_addr_end - $test_address; 1.475 + if ($overlap > $::Objects{$test_address}->{'size'}) { 1.476 + $overlap = $::Objects{$test_address}->{'size'}; 1.477 + } 1.478 + print "($overlap bytes)"; 1.479 + $prev_addr_overlap_tally += $overlap; 1.480 + 1.481 + $test_index++; 1.482 + } 1.483 + print " [total $prev_addr_overlap_tally bytes]"; 1.484 + $overlap_tally += $prev_addr_overlap_tally; 1.485 + print "\n"; 1.486 + } 1.487 + 1.488 + $prev_addr = $address; 1.489 + $prev_addr_end = $prev_addr + $::Objects{$prev_addr}->{'size'} - 1; 1.490 + $index++; 1.491 + } #end while 1.492 + if ($overlap_tally) { 1.493 + print "Total overlap of $overlap_tally bytes\n"; 1.494 + } 1.495 +} 1.496 + 1.497 +#---------------------------------------------------------------------- 1.498 +# 1.499 +# Evaluate sizes of interobject spacing (fragmentation loss?) 1.500 +# Gather the sizes into histograms for analysis 1.501 +# This function assumes a sorted list of addresses is present globally 1.502 + 1.503 +sub generate_and_print_unused_memory_histogram() { 1.504 + print "\nInterobject spacing (fragmentation waste) Statistics\n"; 1.505 + if ($::opt_fragment <= 1) { 1.506 + print "Statistics are not being gathered. Use '--fragment=10' to get stats\n"; 1.507 + return; 1.508 + } 1.509 + print "Ratio of histogram buckets will be a factor of $::opt_fragment\n"; 1.510 + 1.511 + my $prev_addr_end = -1; 1.512 + my $prev_addr = -1; 1.513 + my $index = 0; 1.514 + 1.515 + my @fragment_count; 1.516 + my @fragment_tally; 1.517 + my $power; 1.518 + my $bucket_size; 1.519 + 1.520 + my $max_power = 0; 1.521 + 1.522 + my $tally_sizes = 0; 1.523 + 1.524 + while ($index <= $#::SortedAddresses) { 1.525 + my $address = $::SortedAddresses[$index]; 1.526 + 1.527 + my $unused = $address - $prev_addr_end; 1.528 + 1.529 + # handle overlaps gracefully 1.530 + if ($unused < 0) { 1.531 + $unused = 0; 1.532 + } 1.533 + 1.534 + $power = 0; 1.535 + $bucket_size = 1; 1.536 + while ($bucket_size < $unused) { 1.537 + $bucket_size *= $::opt_fragment; 1.538 + $power++; 1.539 + } 1.540 + $fragment_count[$power]++; 1.541 + $fragment_tally[$power] += $unused; 1.542 + if ($power > $max_power) { 1.543 + $max_power = $power; 1.544 + } 1.545 + my $size = $::Objects{$address}->{'size'}; 1.546 + $tally_sizes += $size; 1.547 + $prev_addr_end = $address + $size - 1; 1.548 + $index++; 1.549 + } 1.550 + 1.551 + 1.552 + $power = 0; 1.553 + $bucket_size = 1; 1.554 + print "Basic gap histogram is (max_size:count):\n"; 1.555 + while ($power <= $max_power) { 1.556 + if (! defined $fragment_count[$power]) { 1.557 + $fragment_count[$power] = $fragment_tally[$power] = 0; 1.558 + } 1.559 + printf " %.1f:", $bucket_size; 1.560 + print $fragment_count[$power]; 1.561 + $power++; 1.562 + $bucket_size *= $::opt_fragment; 1.563 + } 1.564 + print "\n"; 1.565 + 1.566 + print "Summary gap analysis:\n"; 1.567 + 1.568 + $power = 0; 1.569 + $bucket_size = 1; 1.570 + my $tally = 0; 1.571 + my $count = 0; 1.572 + while ($power <= $max_power) { 1.573 + $count += $fragment_count[$power]; 1.574 + $tally += $fragment_tally[$power]; 1.575 + print "$count gaps, totaling $tally bytes, were under "; 1.576 + printf "%.1f bytes each", $bucket_size; 1.577 + if ($count) { 1.578 + printf ", for an average of %.1f bytes per gap", $tally/$count, ; 1.579 + } 1.580 + print "\n"; 1.581 + $power++; 1.582 + $bucket_size *= $::opt_fragment; 1.583 + } 1.584 + 1.585 + print "Total allocation was $tally_sizes bytes, or "; 1.586 + printf "%.0f bytes per allocation block\n\n", $tally_sizes/($count+1); 1.587 + 1.588 +} 1.589 + 1.590 +#---------------------------------------------------------------------- 1.591 +# 1.592 +# Now thread the parents and children together by looking through the 1.593 +# slots for each object. 1.594 +# 1.595 +sub create_parent_links(){ 1.596 + my $min_addr = $::SortedAddresses[0]; 1.597 + my $max_addr = $::SortedAddresses[ $#::SortedAddresses]; #allow one beyond each object 1.598 + $max_addr += $::Objects{$max_addr}->{'size'}; 1.599 + 1.600 + print "Viable addresses range from $min_addr to $max_addr for a total of ", 1.601 + $max_addr-$min_addr, " bytes\n\n"; 1.602 + 1.603 + # Gather stats as we try to convert slots to children 1.604 + my $slot_count = 0; # total slots examined 1.605 + my $fixed_addr_count = 0; # slots into interiors that were adjusted 1.606 + my $parent_child_count = 0; # Number of parent-child links 1.607 + my $child_count = 0; # valid slots, discounting sibling twins 1.608 + my $child_dup_count = 0; # number of duplicate child pointers 1.609 + my $self_pointer_count = 0; # count of discarded self-pointers 1.610 + 1.611 + foreach my $parent (keys %::Objects) { 1.612 + # We'll collect a list of this parent object's children 1.613 + # by iterating through its slots. 1.614 + my @children; 1.615 + my %children_hash; 1.616 + my $self_pointer = 0; 1.617 + 1.618 + my @slots = @{$::Objects{$parent}->{'slots'}}; 1.619 + $slot_count += $#slots + 1; 1.620 + SLOT: foreach my $child (@slots) { 1.621 + 1.622 + # We only care about pointers that refer to other objects 1.623 + if (! defined $::Objects{$child}) { 1.624 + # check to see if we are an interior pointer 1.625 + 1.626 + # Punt if we are completely out of range 1.627 + next SLOT unless ($max_addr >= $child && 1.628 + $child >= $min_addr); 1.629 + 1.630 + # Do binary search to find object below this address 1.631 + my ($min_index, $beyond_index) = (0, $#::SortedAddresses + 1); 1.632 + my $test_index; 1.633 + while ($min_index != 1.634 + ($test_index = int (($beyond_index+$min_index)/2))) { 1.635 + if ($child >= $::SortedAddresses[$test_index]) { 1.636 + $min_index = $test_index; 1.637 + } else { 1.638 + $beyond_index = $test_index; 1.639 + } 1.640 + } 1.641 + # See if pointer is within extent of this object 1.642 + my $address = $::SortedAddresses[$test_index]; 1.643 + next SLOT unless ($child < 1.644 + $address + $::Objects{$address}->{'size'}); 1.645 + 1.646 + # Make adjustment so we point to the actual child precisely 1.647 + $child = $address; 1.648 + $fixed_addr_count++; 1.649 + } 1.650 + 1.651 + if ($child == $parent) { 1.652 + $self_pointer_count++; 1.653 + next SLOT; # Discard self-pointers 1.654 + } 1.655 + 1.656 + # Avoid creating duplicate child-parent links 1.657 + if (! defined $children_hash{$child}) { 1.658 + $parent_child_count++; 1.659 + # Add the parent to the child's list of parents 1.660 + my $parents = $::Objects{$child}->{'parents'}; 1.661 + if (! $parents) { 1.662 + $parents = $::Objects{$child}->{'parents'} = []; 1.663 + } 1.664 + 1.665 + $parents->[scalar(@$parents)] = $parent; 1.666 + 1.667 + # Add the child to the parent's list of children 1.668 + $children_hash{$child} = 1; 1.669 + } else { 1.670 + $child_dup_count++; 1.671 + } 1.672 + } 1.673 + @children = keys %children_hash; 1.674 + # Track tally of unique children linked 1.675 + $child_count += $#children + 1; 1.676 + 1.677 + $::Objects{$parent}->{'children'} = \@children; 1.678 + 1.679 + if (! @children) { 1.680 + $::Leafs[$#::Leafs + 1] = $parent; 1.681 + } 1.682 + } 1.683 + print "Scanning $#::SortedAddresses objects, we found $parent_child_count parents-to-child connections by chasing $slot_count pointers.\n", 1.684 + "This required $fixed_addr_count interior pointer fixups, skipping $child_dup_count duplicate pointers, ", 1.685 + "and $self_pointer_count self pointers\nAlso discarded ", 1.686 + $slot_count - $parent_child_count -$self_pointer_count - $child_dup_count, 1.687 + " out-of-range pointers\n\n"; 1.688 +} 1.689 + 1.690 + 1.691 +#---------------------------------------------------------------------- 1.692 +# For every leaf, if a leaf has only one parent, then sweep the memory 1.693 +# cost into the parent from the leaf 1.694 +sub sweep_leaf_memory () { 1.695 + my $sweep_count = 0; 1.696 + my $leaf_counter = 0; 1.697 + LEAF: while ($leaf_counter <= $#::Leafs) { 1.698 + my $leaf_addr = $::Leafs[$leaf_counter++]; 1.699 + my $leaf_obj = $::Objects{$leaf_addr}; 1.700 + my $parents = $leaf_obj->{'parents'}; 1.701 + 1.702 + next LEAF if (! defined($parents) || 1 != scalar(@$parents)); 1.703 + 1.704 + # We have only one parent, so we'll try to sweep upwards 1.705 + my $parent_addr = @$parents[0]; 1.706 + my $parent_obj = $::Objects{$parent_addr}; 1.707 + 1.708 + # watch out for self-pointers 1.709 + next LEAF if ($parent_addr == $leaf_addr); 1.710 + 1.711 + if ($::opt_detail) { 1.712 + foreach my $obj ($parent_obj, $leaf_obj) { 1.713 + if (!defined $obj->{'original_size'}) { 1.714 + $obj->{'original_size'} = $obj->{'size'}; 1.715 + } 1.716 + } 1.717 + if (defined $leaf_obj->{'sweep_details'}) { 1.718 + if (defined $parent_obj->{'sweep_details'}) { # merge details 1.719 + foreach my $swept_obj (keys (%{$leaf_obj->{'sweep_details'}})) { 1.720 + %{$parent_obj->{'sweep_details'}}->{$swept_obj} = 1.721 + %{$leaf_obj->{'sweep_details'}}->{$swept_obj}; 1.722 + } 1.723 + } else { # No parent info 1.724 + $parent_obj->{'sweep_details'} = \%{$leaf_obj->{'sweep_details'}}; 1.725 + } 1.726 + delete $leaf_obj->{'sweep_details'}; 1.727 + } else { # no leaf detail 1.728 + if (!defined $parent_obj->{'sweep_details'}) { 1.729 + $parent_obj->{'sweep_details'} = {}; 1.730 + } 1.731 + } 1.732 + %{$parent_obj->{'sweep_details'}}->{$leaf_addr} = $leaf_obj->{'original_size'}; 1.733 + } 1.734 + 1.735 + $parent_obj->{'size'} += $leaf_obj->{'size'}; 1.736 + $leaf_obj->{'size'} = 0; 1.737 + 1.738 + if (defined ($leaf_obj->{'swept_in'})) { 1.739 + $parent_obj->{'swept_in'} += $leaf_obj->{'swept_in'}; 1.740 + $leaf_obj->{'swept_in'} = 0; # sweep has been handed off to parent 1.741 + } 1.742 + $parent_obj->{'swept_in'} ++; # tally swept in leaf_obj 1.743 + 1.744 + $sweep_count++; 1.745 + 1.746 + # See if we created another leaf 1.747 + my $consumed_children = $parent_obj->{'consumed'}++; 1.748 + my @children = $parent_obj->{'children'}; 1.749 + if ($consumed_children == $#children) { 1.750 + $::Leafs[$#::Leafs + 1] = @$parents[0]; 1.751 + } 1.752 + } 1.753 + print "Processed ", $leaf_counter, " leaves sweeping memory to parents in ", $sweep_count, " objects\n"; 1.754 +} 1.755 + 1.756 + 1.757 +#---------------------------------------------------------------------- 1.758 +# 1.759 +# Subdivide the types of objects that are in our "expand" list 1.760 +# List types that should be sub-divided based on parents, and possibly 1.761 +# children 1.762 +# The argument supplied is a hash table with keys selecting types that 1.763 +# need to be "refined" by including the types of the parent objects, 1.764 +# and (when we are desparate) the types of the children objects. 1.765 + 1.766 +sub expand_type_names($) { 1.767 + my %TypeExpand = %{$_[0]}; 1.768 + 1.769 + my @retype; # array of addrs that get extended type names 1.770 + foreach my $child (keys %::Objects) { 1.771 + my $child_obj = $::Objects{$child}; 1.772 + next unless (defined ($TypeExpand{$child_obj->{'type'}})); 1.773 + 1.774 + foreach my $relation ('parents','children') { 1.775 + my $relatives = $child_obj->{$relation}; 1.776 + next unless defined @$relatives; 1.777 + 1.778 + # Sort out the names of the types of the relatives 1.779 + my %names; 1.780 + foreach my $relative (@$relatives) { 1.781 + %names->{$::Objects{$relative}->{'type'}} = 1; 1.782 + } 1.783 + my $related_type_names = join(',' , sort(keys(%names))); 1.784 + 1.785 + 1.786 + $child_obj->{'name' . $relation} = $related_type_names; 1.787 + 1.788 + # Don't bother with children if we have significant parent types 1.789 + last if (!defined ($TypeExpand{$related_type_names})); 1.790 + } 1.791 + $retype[$#retype + 1] = $child; 1.792 + } 1.793 + 1.794 + # Revisit all addresses we've marked 1.795 + foreach my $child (@retype) { 1.796 + my $child_obj = $::Objects{$child}; 1.797 + $child_obj->{'type'} = $TypeExpand{$child_obj->{'type'}}; 1.798 + my $extended_type = $child_obj->{'namechildren'}; 1.799 + if (defined $extended_type) { 1.800 + $child_obj->{'type'}.= "->(" . $extended_type . ")"; 1.801 + delete ($child_obj->{'namechildren'}); 1.802 + } 1.803 + $extended_type = $child_obj->{'nameparents'}; 1.804 + if (defined $extended_type) { 1.805 + $child_obj->{'type'} = "(" . $extended_type . ")->" . $::Objects{$child}->{'type'}; 1.806 + delete ($child_obj->{'nameparents'}); 1.807 + } 1.808 + } 1.809 +} 1.810 + 1.811 +#---------------------------------------------------------------------- 1.812 +# 1.813 +# Print out a type histogram 1.814 + 1.815 +sub print_type_histogram() { 1.816 + load_type_table(); 1.817 + print_type_table(); 1.818 + print "\n\n"; 1.819 +} 1.820 + 1.821 + 1.822 +#---------------------------------------------------------------------- 1.823 +# Provide a nice summary of the types during the process 1.824 +validate_address_ranges(); 1.825 +create_parent_links(); 1.826 + 1.827 +print "\nBasic memory use histogram is:\n"; 1.828 +print_type_histogram(); 1.829 + 1.830 +generate_and_print_unused_memory_histogram(); 1.831 + 1.832 +sweep_leaf_memory (); 1.833 +print "After doing basic leaf-sweep processing of instances:\n"; 1.834 +print_type_histogram(); 1.835 + 1.836 +{ 1.837 + foreach my $typename (@::opt_typedivide) { 1.838 + my %expansion_table; 1.839 + $expansion_table{$typename} = $typename; 1.840 + expand_type_names(\%expansion_table); 1.841 + print "After subdividing <$typename> based on inbound (and somtimes outbound) pointers:\n"; 1.842 + print_type_histogram(); 1.843 + } 1.844 +} 1.845 + 1.846 +exit(); # Don't bother with SCCs yet. 1.847 + 1.848 + 1.849 +#---------------------------------------------------------------------- 1.850 +# 1.851 +# Determine objects that entrain equivalent sets, using the strongly 1.852 +# connected component algorithm from Cormen, Leiserson, and Rivest, 1.853 +# ``An Introduction to Algorithms'', MIT Press 1990, pp. 488-493. 1.854 +# 1.855 +sub compute_post_order($$$) { 1.856 +# This routine produces a post-order of the call graph (what CLR call 1.857 +# ``ordering the nodes by f[u]'') 1.858 + my ($parent, $visited, $finish) = @_; 1.859 + 1.860 + # Bail if we've already seen this node 1.861 + return if $visited->{$parent}; 1.862 + 1.863 + # We have now! 1.864 + $visited->{$parent} = 1; 1.865 + 1.866 + # Walk the children 1.867 + my $children = $::Objects{$parent}->{'children'}; 1.868 + 1.869 + foreach my $child (@$children) { 1.870 + compute_post_order($child, $visited, $finish); 1.871 + } 1.872 + 1.873 + # Now that we've walked all the kids, we can append the parent to 1.874 + # the post-order 1.875 + @$finish[scalar(@$finish)] = $parent; 1.876 +} 1.877 + 1.878 +sub compute_equivalencies($$$) { 1.879 +# This routine recursively computes equivalencies by walking the 1.880 +# transpose of the callgraph. 1.881 + my ($child, $table, $equivalencies) = @_; 1.882 + 1.883 + # Bail if we've already seen this node 1.884 + return if $table->{$child}; 1.885 + 1.886 + # Otherwise, append ourself to the list of equivalencies... 1.887 + @$equivalencies[scalar(@$equivalencies)] = $child; 1.888 + 1.889 + # ...and note our other equivalents in the table 1.890 + $table->{$child} = $equivalencies; 1.891 + 1.892 + my $parents = $::Objects{$child}->{'parents'}; 1.893 + 1.894 + foreach my $parent (@$parents) { 1.895 + compute_equivalencies($parent, $table, $equivalencies); 1.896 + } 1.897 +} 1.898 + 1.899 +sub compute_equivalents() { 1.900 +# Here's the strongly connected components algorithm. (Step 2 has been 1.901 +# done implictly by our object graph construction.) 1.902 + my %visited; 1.903 + my @finish; 1.904 + 1.905 + # Step 1. Compute a post-ordering of the object graph 1.906 + foreach my $parent (keys %::Objects) { 1.907 + compute_post_order($parent, \%visited, \@finish); 1.908 + } 1.909 + 1.910 + # Step 3. Traverse the transpose of the object graph in reverse 1.911 + # post-order, collecting vertices into %equivalents 1.912 + my %equivalents; 1.913 + foreach my $child (reverse @finish) { 1.914 + compute_equivalencies($child, \%equivalents, []); 1.915 + } 1.916 + 1.917 + # Now, we'll trim the %equivalents table, arbitrarily removing 1.918 + # ``redundant'' entries. 1.919 + EQUIVALENT: foreach my $node (keys %equivalents) { 1.920 + my $equivalencies = $equivalents{$node}; 1.921 + next EQUIVALENT unless $equivalencies; 1.922 + 1.923 + foreach my $equivalent (@$equivalencies) { 1.924 + delete $equivalents{$equivalent} unless $equivalent == $node; 1.925 + } 1.926 + } 1.927 + 1.928 + # Note the equivalent objects in a way that will yield the most 1.929 + # interesting order as we do depth-first traversal later to 1.930 + # output them. 1.931 + ROOT: foreach my $equivalent (reverse @finish) { 1.932 + next ROOT unless $equivalents{$equivalent}; 1.933 + $::Equivalents[$#::Equivalents + 1] = $equivalent; 1.934 + 1.935 + # XXX Lame! Should figure out function refs. 1.936 + $::Objects{$equivalent}->{'entrained-size'} = 0; 1.937 + } 1.938 +} 1.939 + 1.940 +# Do it! 1.941 +compute_equivalents(); 1.942 + 1.943 + 1.944 +#---------------------------------------------------------------------- 1.945 +# 1.946 +# Compute the size of each node's transitive closure. 1.947 +# 1.948 +sub compute_entrained($$) { 1.949 + my ($parent, $visited) = @_; 1.950 + 1.951 + $visited->{$parent} = 1; 1.952 + 1.953 + $::Objects{$parent}->{'entrained-size'} = $::Objects{$parent}->{'size'}; 1.954 + 1.955 + my $children = $::Objects{$parent}->{'children'}; 1.956 + CHILD: foreach my $child (@$children) { 1.957 + next CHILD if $visited->{$child}; 1.958 + 1.959 + compute_entrained($child, $visited); 1.960 + $::Objects{$parent}->{'entrained-size'} += $::Objects{$child}->{'entrained-size'}; 1.961 + } 1.962 +} 1.963 + 1.964 +if (! $::opt_noentrained) { 1.965 + my %visited; 1.966 + 1.967 + PARENT: foreach my $parent (@::Equivalents) { 1.968 + next PARENT if $visited{$parent}; 1.969 + compute_entrained($parent, \%visited); 1.970 + } 1.971 +} 1.972 + 1.973 + 1.974 +#---------------------------------------------------------------------- 1.975 +# 1.976 +# Converts a shared library and an address into a file and line number 1.977 +# using a bunch of addr2line processes. 1.978 +# 1.979 +sub addr2line($$) { 1.980 + my ($dso, $addr) = @_; 1.981 + 1.982 + # $::Addr2Lines is a global table that maps a DSO's name to a pair 1.983 + # of filehandles that are talking to an addr2line process. 1.984 + my $fhs = $::Addr2Lines{$dso}; 1.985 + if (! $fhs) { 1.986 + if (!(-r $dso)) { 1.987 + # bogus filename (that happens sometimes), so bail 1.988 + return { 'dso' => $dso, 'addr' => $addr }; 1.989 + } 1.990 + my ($in, $out) = (new FileHandle, new FileHandle); 1.991 + open2($in, $out, "addr2line --exe=$dso") || die "unable to open addr2line --exe=$dso"; 1.992 + $::Addr2Lines{$dso} = $fhs = { 'in' => $in, 'out' => $out }; 1.993 + } 1.994 + 1.995 + # addr2line takes a hex address as input... 1.996 + $fhs->{'out'}->print($addr . "\n"); 1.997 + 1.998 + # ...and'll return file:lineno as output 1.999 + if ($fhs->{'in'}->getline() =~ /([^:]+):(.+)/) { 1.1000 + return { 'file' => $1, 'line' => $2 }; 1.1001 + } 1.1002 + else { 1.1003 + return { 'dso' => $dso, 'addr' => $addr }; 1.1004 + } 1.1005 +} 1.1006 + 1.1007 + 1.1008 +#---------------------------------------------------------------------- 1.1009 +# 1.1010 +# Dump the objects, using a depth-first traversal. 1.1011 +# 1.1012 +sub dump_objects($$$) { 1.1013 + my ($parent, $visited, $depth) = @_; 1.1014 + 1.1015 + # Have we already seen this? 1.1016 + my $already_visited = $visited->{$parent}; 1.1017 + return if ($depth == 0 && $already_visited); 1.1018 + 1.1019 + if (! $already_visited) { 1.1020 + $visited->{$parent} = 1; 1.1021 + $::Total += $::Objects{$parent}->{'size'}; 1.1022 + } 1.1023 + 1.1024 + my $parententry = $::Objects{$parent}; 1.1025 + 1.1026 + # Make an ``object'' div, which'll contain an ``object'' span, two 1.1027 + # ``toggle'' spans, an invisible ``stack'' div, and the invisible 1.1028 + # ``children'' div. 1.1029 + print "<div class='object'>"; 1.1030 + 1.1031 + if ($already_visited) { 1.1032 + print "<a href='#$parent'>"; 1.1033 + } 1.1034 + else { 1.1035 + print "<span id='$parent' class='object"; 1.1036 + print " root" if $depth == 0; 1.1037 + print "'>"; 1.1038 + } 1.1039 + 1.1040 + printf "0x%x<%s>[%d]", $parent, $parententry->{'type'}, $parententry->{'size'}; 1.1041 + 1.1042 + if ($already_visited) { 1.1043 + print "</a>"; 1.1044 + goto DONE; 1.1045 + } 1.1046 + 1.1047 + if ($depth == 0) { 1.1048 + print "($parententry->{'entrained-size'})" 1.1049 + if $parententry->{'entrained-size'}; 1.1050 + 1.1051 + print " <span class='toggle' onclick='toggleDisplay(this.parentNode.nextSibling.nextSibling);'>Children</span>" 1.1052 + if @{$parententry->{'children'}} > 0; 1.1053 + } 1.1054 + 1.1055 + if (($depth == 0 || !$::opt_nochildstacks) && !$::opt_nostacks) { 1.1056 + print " <span class='toggle' onclick='toggleDisplay(this.parentNode.nextSibling);'>Stack</span>"; 1.1057 + } 1.1058 + 1.1059 + print "</span>"; 1.1060 + 1.1061 + # Print stack traces 1.1062 + print "<div class='stack'>\n"; 1.1063 + 1.1064 + if (($depth == 0 || !$::opt_nochildstacks) && !$::opt_nostacks) { 1.1065 + my $depth = $::opt_depth; 1.1066 + 1.1067 + FRAME: foreach my $frame (@{$parententry->{'stack'}}) { 1.1068 + # Only go as deep as they've asked us to. 1.1069 + last FRAME unless --$depth >= 0; 1.1070 + 1.1071 + # Stack frames look like ``mangled_name[dso address]'' 1.1072 + $frame =~ /([^\]]+)\[(.*) \+0x([0-9A-Fa-f]+)\]/; 1.1073 + 1.1074 + # Convert address to file and line number 1.1075 + my $mangled = $1; 1.1076 + my $result = addr2line($2, $3); 1.1077 + 1.1078 + if ($result->{'file'}) { 1.1079 + # It's mozilla source! Clean up refs to dist/include 1.1080 + if (($result->{'file'} =~ s/.*\.\.\/\.\.\/dist\/include\//http:\/\/bonsai.mozilla.org\/cvsguess.cgi\?file=/) || 1.1081 + ($result->{'file'} =~ s/.*\/mozilla/http:\/\/bonsai.mozilla.org\/cvsblame.cgi\?file=mozilla/)) { 1.1082 + my $prevline = $result->{'line'} - 10; 1.1083 + print "<a target=\"lxr_source\" href=\"$result->{'file'}\&mark=$result->{'line'}#$prevline\">$mangled</a><br>\n"; 1.1084 + } 1.1085 + else { 1.1086 + print "$mangled ($result->{'file'}, line $result->{'line'})<br>\n"; 1.1087 + } 1.1088 + } 1.1089 + else { 1.1090 + print "$result->{'dso'} ($result->{'addr'})<br>\n"; 1.1091 + } 1.1092 + } 1.1093 + 1.1094 + } 1.1095 + 1.1096 + print "</div>"; 1.1097 + 1.1098 + # Recurse to children 1.1099 + if (@{$parententry->{'children'}} >= 0) { 1.1100 + print "<div class='children'>\n" if $depth == 0; 1.1101 + 1.1102 + foreach my $child (@{$parententry->{'children'}}) { 1.1103 + dump_objects($child, $visited, $depth + 1); 1.1104 + } 1.1105 + 1.1106 + print "</div>" if $depth == 0; 1.1107 + } 1.1108 + 1.1109 + DONE: 1.1110 + print "</div>\n"; 1.1111 +} 1.1112 + 1.1113 + 1.1114 +#---------------------------------------------------------------------- 1.1115 +# 1.1116 +# Do the output. 1.1117 +# 1.1118 + 1.1119 +# Force flush on STDOUT. We get funky output unless we do this. 1.1120 +$| = 1; 1.1121 + 1.1122 +# Header 1.1123 +print "<html> 1.1124 +<head> 1.1125 +<title>Object Graph</title> 1.1126 +<style type='text/css'> 1.1127 + body { font: medium monospace; background-color: white; } 1.1128 + 1.1129 + /* give nested div's some margins to make it look like a tree */ 1.1130 + div.children > div.object { margin-left: 1em; } 1.1131 + div.object > div.object { margin-left: 1em; } 1.1132 + 1.1133 + /* Indent stacks, too */ 1.1134 + div.object > div.stack { margin-left: 3em; } 1.1135 + 1.1136 + /* apply font decorations to special ``object'' spans */ 1.1137 + span.object { font-weight: bold; color: darkgrey; } 1.1138 + span.object.root { color: black; } 1.1139 + 1.1140 + /* hide ``stack'' divs by default; JS will show them */ 1.1141 + div.stack { display: none; } 1.1142 + 1.1143 + /* hide ``children'' divs by default; JS will show them */ 1.1144 + div.children { display: none; } 1.1145 + 1.1146 + /* make ``toggle'' spans look like links */ 1.1147 + span.toggle { color: blue; text-decoration: underline; cursor: pointer; } 1.1148 + span.toggle:active { color: red; } 1.1149 +</style> 1.1150 +<script language='JavaScript'> 1.1151 +function toggleDisplay(element) 1.1152 +{ 1.1153 + element.style.display = (element.style.display == 'block') ? 'none' : 'block'; 1.1154 +} 1.1155 +</script> 1.1156 +</head> 1.1157 +<body> 1.1158 +"; 1.1159 + 1.1160 +{ 1.1161 +# Body. Display ``roots'', sorted by the amount of memory they 1.1162 +# entrain. Because of the way we've sorted @::Equivalents, we should 1.1163 +# get a nice ordering that sorts things with a lot of kids early 1.1164 +# on. This should yield a fairly "deep" depth-first traversal, with 1.1165 +# most of the objects appearing as children. 1.1166 +# 1.1167 +# XXX I sure hope that Perl implements a stable sort! 1.1168 + my %visited; 1.1169 + 1.1170 + foreach my $parent (sort { $::Objects{$b}->{'entrained-size'} 1.1171 + <=> $::Objects{$a}->{'entrained-size'} } 1.1172 + @::Equivalents) { 1.1173 + dump_objects($parent, \%visited, 0); 1.1174 + print "\n"; 1.1175 + } 1.1176 +} 1.1177 + 1.1178 +# Footer 1.1179 +print "<br> $::Total total bytes\n" if $::Total; 1.1180 +print "</body> 1.1181 +</html> 1.1182 +"; 1.1183 +