tools/trace-malloc/leak-soup.pl

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rwxr-xr-x

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 #!/usr/bin/perl -w
michael@0 2 #
michael@0 3 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 6
michael@0 7 # A perl version of Patrick Beard's ``Leak Soup'', which processes the
michael@0 8 # stack crawls from the Boehm GC into a graph.
michael@0 9
michael@0 10 use 5.004;
michael@0 11 use strict;
michael@0 12 use Getopt::Long;
michael@0 13 use FileHandle;
michael@0 14 use IPC::Open2;
michael@0 15
michael@0 16 # Collect program options
michael@0 17 $::opt_help = 0;
michael@0 18 $::opt_detail = 0;
michael@0 19 $::opt_fragment = 1.0; # Default to no fragment analysis
michael@0 20 $::opt_nostacks = 0;
michael@0 21 $::opt_nochildstacks = 0;
michael@0 22 $::opt_depth = 9999;
michael@0 23 $::opt_noentrained = 0;
michael@0 24 $::opt_noslop = 0;
michael@0 25 $::opt_showtype = -1; # default to listing all types
michael@0 26 $::opt_stackrefine = "C";
michael@0 27 @::opt_stackretype = ();
michael@0 28 @::opt_stackskipclass = ();
michael@0 29 @::opt_stackskipfunc = ();
michael@0 30 @::opt_typedivide = ();
michael@0 31
michael@0 32 GetOptions("help", "detail", "format=s", "fragment=f", "nostacks",
michael@0 33 "nochildstacks", "depth=i", "noentrained", "noslop", "showtype=i",
michael@0 34 "stackrefine=s", "stackretype=s@", "stackskipclass=s@", "stackskipfunc=s@",
michael@0 35 "typedivide=s@"
michael@0 36 );
michael@0 37
michael@0 38 if ($::opt_help) {
michael@0 39 die "usage: leak-soup.pl [options] <leakfile>
michael@0 40 --help Display this message
michael@0 41 --detail Provide details of memory sweeping from child to parents
michael@0 42 --fragment=ratio Histogram bucket ratio for fragmentation analysis
michael@0 43 # --nostacks Do not compute stack traces
michael@0 44 # --nochildstacks Do not compute stack traces for entrained objects
michael@0 45 # --depth=<max> Only compute stack traces to depth of <max>
michael@0 46 # --noentrained Do not compute amount of memory entrained by root objects
michael@0 47 --noslop Don't ignore low bits when searching for pointers
michael@0 48 --showtype=<i> Show memory usage histogram for most-significant <i> types
michael@0 49 --stackrefine={F|C} During stack based refinement, use 'F'ull name name or just 'C'lass
michael@0 50 --stackretype=type Use allocation stack to refine vague types like void*
michael@0 51 --stackskipclass=class When refining types, ignore stack frames from 'class'
michael@0 52 --stackskipfunc=func When refining types, ignore stack frames for 'func'
michael@0 53 --typedivide=type Subdivide 'type' based on objects pointing to each instance
michael@0 54 ";
michael@0 55 }
michael@0 56
michael@0 57 # This is the table that keeps a graph of objects. It's indexed by the
michael@0 58 # object's address (as an integer), and refers to a simple hash that
michael@0 59 # has information about the object's type, size, slots, and allocation
michael@0 60 # stack.
michael@0 61 %::Objects = %{0};
michael@0 62
michael@0 63 # This will be a list of keys to (addresses in) Objects, that is sorted
michael@0 64 # It gets used to evaluate overlaps, calculate fragmentation, and chase
michael@0 65 # parent->child (interior) pointers.
michael@0 66 @::SortedAddresses = [];
michael@0 67
michael@0 68 # This is the table that keeps track of memory usage on a per-type basis.
michael@0 69 # It is indexed by the type name (string), and keeps a tally of the
michael@0 70 # total number of such objects, and the memory usage of such objects.
michael@0 71 %::Types = %{0};
michael@0 72 $::TotalSize = 0; # sum of sizes of all objects included $::Types{}
michael@0 73
michael@0 74 # This is an array of leaf node addresses. A leaf node has no children
michael@0 75 # with memory allocations. We traverse them sweeping memory
michael@0 76 # tallies into parents. Note that after all children have
michael@0 77 # been swept into a parent, that parent may also become a leaf node.
michael@0 78 @::Leafs = @{0};
michael@0 79
michael@0 80
michael@0 81
michael@0 82
michael@0 83 #----------------------------------------------------------------------
michael@0 84 #
michael@0 85 # Decode arguments to override default values for doing call-stack-based
michael@0 86 # refinement of typename based on contents of the stack at allocation time.
michael@0 87 #
michael@0 88
michael@0 89 # List the types that we need to refine (if any) based on allocation stack
michael@0 90 $::VagueType = {
michael@0 91 'void*' => 1,
michael@0 92 };
michael@0 93
michael@0 94 # With regard to the stack, ignore stack frames in the following
michael@0 95 # overly vague classes.
michael@0 96 $::VagueClasses = {
michael@0 97 # 'nsStr' => 1,
michael@0 98 'nsVoidArray' => 1,
michael@0 99 };
michael@0 100
michael@0 101 # With regard to stack, ignore stack frames with the following vague
michael@0 102 # function names
michael@0 103 $::VagueFunctions = {
michael@0 104 'PL_ArenaAllocate' => 1,
michael@0 105 'PL_HashTableFinalize(PLHashTable *)' => 1,
michael@0 106 'PL_HashTableInit__FP11PLHashTableUiPFPCv_UiPFPCvPCv_iT3PC14PLHashAllocOpsPv' => 1,
michael@0 107 'PL_HashTableRawAdd' => 1,
michael@0 108 '__builtin_vec_new' => 1,
michael@0 109 '_init' => 1,
michael@0 110 'il_get_container(_IL_GroupContext *, ImgCachePolicy, char const *, _NI_IRGB *, IL_DitherMode, int, int, int)' => 1,
michael@0 111 'nsCStringKey::Clone(void) const' => 1,
michael@0 112 'nsCppSharedAllocator<unsigned short>::allocate(unsigned int, void const *)' => 1,
michael@0 113 'nsHashtable::Put(nsHashKey *, void *)' => 1,
michael@0 114 'nsHashtable::nsHashtable(unsigned int, int)' => 1,
michael@0 115 'nsMemory::Alloc(unsigned int)' => 1,
michael@0 116 'nsMemoryImpl::Alloc(unsigned int)' => 1,
michael@0 117 };
michael@0 118
michael@0 119 sub init_stack_based_type_refinement() {
michael@0 120 # Move across stackretype options, or use default values
michael@0 121 if ($#::opt_stackretype < 0) {
michael@0 122 print "Default --stackretype options will be used (since none were specified)\n";
michael@0 123 print " use --stackretype='nothing' to disable re-typing activity\n";
michael@0 124 } else {
michael@0 125 foreach my $type (keys %{$::VagueType}) {
michael@0 126 delete ($::VagueType->{$type});
michael@0 127 }
michael@0 128 if ($#::opt_stackretype == 0 && $::opt_stackretype[0] eq 'nothing') {
michael@0 129 print "Types will not be refined based on call stack\n";
michael@0 130 } else {
michael@0 131 foreach my $type (@::opt_stackretype) {
michael@0 132 $::VagueType->{$type} = 1;
michael@0 133 }
michael@0 134 }
michael@0 135 }
michael@0 136
michael@0 137
michael@0 138 if (keys %{$::VagueType}) {
michael@0 139 print "The following type(s) will be refined based on call stacks:\n";
michael@0 140 foreach my $type (sort keys %{$::VagueType}) {
michael@0 141 print " $type\n";
michael@0 142 }
michael@0 143 print "Equivalent command line argument(s):\n";
michael@0 144 foreach my $type (sort keys %{$::VagueType}) {
michael@0 145 print " --stackretype='$type'";
michael@0 146 }
michael@0 147 print "\n\n";
michael@0 148
michael@0 149 if ($#::opt_stackskipclass < 0) {
michael@0 150 print "Default --stackskipclass options will be used (since none were specified)\n";
michael@0 151 print " use --stackskipclass='nothing' to disable skipping stack frames based on class names\n";
michael@0 152 } else {
michael@0 153 foreach my $type (keys %{$::VagueClasses}) {
michael@0 154 delete ($::VagueClasses->{$type});
michael@0 155 }
michael@0 156 if ($#::opt_stackskipclass == 0 && $::opt_stackskipclass[0] eq 'nothing') {
michael@0 157 print "Types will not be refined based on call stack\n";
michael@0 158 } else {
michael@0 159 foreach my $type (@::opt_stackskipclass) {
michael@0 160 $::VagueClasses->{$type} = 1;
michael@0 161 }
michael@0 162 }
michael@0 163 }
michael@0 164
michael@0 165 if (keys %{$::VagueClasses}) {
michael@0 166 print "Stack frames from the following class(es) will not be used to refine types:\n";
michael@0 167 foreach my $class (sort keys %{$::VagueClasses}) {
michael@0 168 print " $class\n";
michael@0 169 }
michael@0 170 print "Equivalent command line argument(s):\n";
michael@0 171 foreach my $class (sort keys %{$::VagueClasses}) {
michael@0 172 print " --stackskipclass='$class'";
michael@0 173 }
michael@0 174 print "\n\n";
michael@0 175 }
michael@0 176
michael@0 177
michael@0 178 if ($#::opt_stackskipfunc < 0) {
michael@0 179 print "Default --stackskipfunc options will be used (since none were specified)\n";
michael@0 180 print " use --stackskipfunc='nothing' to disable skipping stack frames based on function names\n";
michael@0 181 } else {
michael@0 182 foreach my $type (keys %{$::VagueFunctions}) {
michael@0 183 delete ($::VagueFunctions->{$type});
michael@0 184 }
michael@0 185 if ($#::opt_stackskipfunc == 0 && $::opt_stackskipfunc[0] eq 'nothing') {
michael@0 186 print "Types will not be refined based on call stack\n";
michael@0 187 } else {
michael@0 188 foreach my $type (@::opt_stackskipfunc) {
michael@0 189 $::VagueFunctions->{$type} = 1;
michael@0 190 }
michael@0 191 }
michael@0 192 }
michael@0 193
michael@0 194 if (keys %{$::VagueFunctions}) {
michael@0 195 print "Stack frames from the following function(s) will not be used to refine types:\n";
michael@0 196 foreach my $func (sort keys %{$::VagueFunctions}) {
michael@0 197 print " $func\n";
michael@0 198 }
michael@0 199 print "Equivalent command line argument(s):\n";
michael@0 200 foreach my $func (sort keys %{$::VagueFunctions}) {
michael@0 201 print " --stackskipfunc='$func'";
michael@0 202 }
michael@0 203 print "\n\n";
michael@0 204 }
michael@0 205 }
michael@0 206 }
michael@0 207
michael@0 208
michael@0 209 #----------------------------------------------------------------------
michael@0 210 #
michael@0 211 # Read in the output from the Boehm GC or Trace-malloc.
michael@0 212 #
michael@0 213 sub read_boehm() {
michael@0 214 OBJECT: while (<>) {
michael@0 215 # e.g., 0x0832FBD0 <void*> (80)
michael@0 216 next OBJECT unless /^0x(\S+) <(.*)> \((\d+)\)/;
michael@0 217 my ($addr, $type, $size) = (hex $1, $2, $3);
michael@0 218
michael@0 219 my $object = $::Objects{$addr};
michael@0 220 if (! $object) {
michael@0 221 # Found a new object entry. Record its type and size
michael@0 222 $::Objects{$addr} =
michael@0 223 $object =
michael@0 224 { 'type' => $type, 'size' => $size };
michael@0 225 } else {
michael@0 226 print "Duplicate address $addr contains $object->{'type'} and $type\n";
michael@0 227 $object->{'dup_addr_count'}++;
michael@0 228 }
michael@0 229
michael@0 230 # Record the object's slots
michael@0 231 my @slots;
michael@0 232
michael@0 233 SLOT: while (<>) {
michael@0 234 # e.g., 0x00000000
michael@0 235 last SLOT unless /^\t0x(\S+)/;
michael@0 236 my $value = hex $1;
michael@0 237
michael@0 238 # Ignore low bits, unless they've specified --noslop
michael@0 239 $value &= ~0x7 unless $::opt_noslop;
michael@0 240
michael@0 241 $slots[$#slots + 1] = $value;
michael@0 242 }
michael@0 243
michael@0 244 $object->{'slots'} = \@slots;
michael@0 245
michael@0 246 if (@::opt_stackretype && (defined $::VagueType->{$type})) {
michael@0 247 # Change the value of type of the object based on stack
michael@0 248 # if we can find an interesting calling function
michael@0 249 VAGUEFRAME: while (<>) {
michael@0 250 # e.g., _dl_debug_message[/lib/ld-linux.so.2 +0x0000B858]
michael@0 251 last VAGUEFRAMEFRAME unless /^(.*)\[(.*) \+0x(\S+)\]$/;
michael@0 252 my ($func, $lib, $off) = ($1, $2, hex $3);
michael@0 253 chomp;
michael@0 254
michael@0 255 my ($class,,$fname) = split(/:/, $func);
michael@0 256 next VAGUEFRAME if (defined $::VagueFunctions->{$func} ||
michael@0 257 defined $::VagueClasses->{$class});
michael@0 258
michael@0 259 # Refine typename and exit stack scan
michael@0 260 $object->{'type'} = $type . ":" .
michael@0 261 (('C' eq $::opt_stackrefine) ?
michael@0 262 $class :
michael@0 263 $func);
michael@0 264 last VAGUEFRAME;
michael@0 265 }
michael@0 266 } else {
michael@0 267 # Save all stack info if requested
michael@0 268 if (! $::opt_nostacks) {
michael@0 269 # Record the stack by which the object was allocated
michael@0 270 my @stack;
michael@0 271
michael@0 272 FRAME: while (<>) {
michael@0 273 # e.g., _dl_debug_message[/lib/ld-linux.so.2 +0x0000B858]
michael@0 274 last FRAME unless /^(.*)\[(.*) \+0x(\S+)\]$/;
michael@0 275 my ($func, $lib, $off) = ($1, $2, hex $3);
michael@0 276 chomp;
michael@0 277
michael@0 278 $stack[$#stack + 1] = $_;
michael@0 279 }
michael@0 280
michael@0 281 $object->{'stack'} = \@stack;
michael@0 282 }
michael@0 283 }
michael@0 284
michael@0 285 # Gotta check EOF explicitly...
michael@0 286 last OBJECT if eof;
michael@0 287 }
michael@0 288 }
michael@0 289
michael@0 290
michael@0 291 #----------------------------------------------------------------------
michael@0 292 #
michael@0 293 # Read input
michael@0 294 #
michael@0 295 init_stack_based_type_refinement();
michael@0 296 read_boehm;
michael@0 297
michael@0 298
michael@0 299
michael@0 300 #----------------------------------------------------------------------
michael@0 301 #
michael@0 302 # Do basic initialization of the type hash table. Accumulate
michael@0 303 # total counts, and basic memory usage (not including children)
michael@0 304 sub load_type_table() {
michael@0 305 # Reset global counter and hash table
michael@0 306 $::TotalSize = 0;
michael@0 307 %::Types = %{0};
michael@0 308
michael@0 309 OBJECT: foreach my $addr (keys %::Objects) {
michael@0 310 my $obj = $::Objects{$addr};
michael@0 311 my ($type, $size, $swept_in, $overlap_count, $dup_addr_count) =
michael@0 312 ($obj->{'type'}, $obj->{'size'},
michael@0 313 $obj->{'swept_in'},
michael@0 314 $obj->{'overlap_count'},$obj->{'dup_addr_count'});
michael@0 315
michael@0 316 my $type_data = $::Types{$type};
michael@0 317 if (! defined $type_data) {
michael@0 318 $::Types{$type} =
michael@0 319 $type_data = {'count' => 0, 'size' => 0,
michael@0 320 'max' => $size, 'min' => $size,
michael@0 321 'swept_in' => 0, 'swept' => 0,
michael@0 322 'overlap_count' => 0,
michael@0 323 'dup_addr_count' => 0};
michael@0 324 }
michael@0 325
michael@0 326 if (!$size) {
michael@0 327 $type_data->{'swept'}++;
michael@0 328 next OBJECT;
michael@0 329 }
michael@0 330 $::TotalSize += $size;
michael@0 331
michael@0 332 $type_data->{'count'}++;
michael@0 333 $type_data->{'size'} += $size;
michael@0 334 if (defined $swept_in) {
michael@0 335 $type_data->{'swept_in'} += $swept_in;
michael@0 336
michael@0 337 if ($::opt_detail) {
michael@0 338 my $type_detail_sizes = $type_data->{'sweep_details_size'};
michael@0 339 my $type_detail_counts;
michael@0 340 if (!defined $type_detail_sizes) {
michael@0 341 $type_detail_sizes = $type_data->{'sweep_details_size'} = {};
michael@0 342 $type_detail_counts = $type_data->{'sweep_details_count'} = {};
michael@0 343 } else {
michael@0 344 $type_detail_counts = $type_data->{'sweep_details_count'};
michael@0 345 }
michael@0 346
michael@0 347 my $sweep_details = $obj->{'sweep_details'};
michael@0 348 for my $swept_addr (keys (%{$sweep_details})) {
michael@0 349 my $swept_obj = $::Objects{$swept_addr};
michael@0 350 my $swept_type = $swept_obj->{'type'};
michael@0 351 $type_detail_sizes->{$swept_type} += $sweep_details->{$swept_addr};
michael@0 352 $type_detail_counts->{$swept_type}++;
michael@0 353 }
michael@0 354 }
michael@0 355 }
michael@0 356 if (defined $overlap_count) {
michael@0 357 $type_data->{'overlap_count'} += $overlap_count;
michael@0 358 }
michael@0 359
michael@0 360 if (defined $dup_addr_count) {
michael@0 361 $type_data->{'dup_addr_count'} += $dup_addr_count;
michael@0 362 }
michael@0 363
michael@0 364 if ($type_data->{'max'} < $size) {
michael@0 365 $type_data->{'max'} = $size;
michael@0 366 }
michael@0 367 # Watch out for case where min is produced by a swept object
michael@0 368 if (!$type_data->{'min'} || $type_data->{'min'} > $size) {
michael@0 369 $type_data->{'min'} = $size;
michael@0 370 }
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374
michael@0 375 #----------------------------------------------------------------------
michael@0 376 sub print_type_table(){
michael@0 377 if (!$::opt_showtype) {
michael@0 378 return;
michael@0 379 }
michael@0 380 my $line_count = 0;
michael@0 381 my $bytes_printed_tally = 0;
michael@0 382
michael@0 383 # Display type summary information
michael@0 384 my @sorted_types = keys (%::Types);
michael@0 385 print "There are ", 1 + $#sorted_types, " types containing ", $::TotalSize, " bytes\n";
michael@0 386 @sorted_types = sort {$::Types{$b}->{'size'}
michael@0 387 <=> $::Types{$a}->{'size'} } @sorted_types;
michael@0 388
michael@0 389 foreach my $type (@sorted_types) {
michael@0 390 last if ($line_count++ == $::opt_showtype);
michael@0 391
michael@0 392 my $type_data = $::Types{$type};
michael@0 393 $bytes_printed_tally += $type_data->{'size'};
michael@0 394
michael@0 395 if ($type_data->{'count'}) {
michael@0 396 printf "%.2f%% ", $type_data->{'size'} * 100.0/$::TotalSize;
michael@0 397 print $type_data->{'size'},
michael@0 398 "\t(",
michael@0 399 $type_data->{'min'}, "/",
michael@0 400 int($type_data->{'size'} / $type_data->{'count'}),"/",
michael@0 401 $type_data->{'max'}, ")";
michael@0 402 print "\t", $type_data->{'count'},
michael@0 403 " x ";
michael@0 404 }
michael@0 405 print $type;
michael@0 406
michael@0 407 if ($type_data->{'swept_in'}) {
michael@0 408 print ", $type_data->{'swept_in'} sub-objs absorbed";
michael@0 409 }
michael@0 410 if ($type_data->{'swept'}) {
michael@0 411 print ", $type_data->{'swept'} swept away";
michael@0 412 }
michael@0 413 if ($type_data->{'overlap_count'}) {
michael@0 414 print ", $type_data->{'overlap_count'} range overlaps";
michael@0 415 }
michael@0 416 if ($type_data->{'dup_addr_count'}) {
michael@0 417 print ", $type_data->{'dup_addr_count'} duplicated addresses";
michael@0 418 }
michael@0 419
michael@0 420 print "\n" ;
michael@0 421 if (defined $type_data->{'sweep_details_size'}) {
michael@0 422 my $sizes = $type_data->{'sweep_details_size'};
michael@0 423 my $counts = $type_data->{'sweep_details_count'};
michael@0 424 my @swept_types = sort {$sizes->{$b} <=> $sizes->{$a}} keys (%{$sizes});
michael@0 425
michael@0 426 for my $type (@swept_types) {
michael@0 427 printf " %.2f%% ", $sizes->{$type} * 100.0/$::TotalSize;
michael@0 428 print "$sizes->{$type} (", int($sizes->{$type}/$counts->{$type}) , ") $counts->{$type} x $type\n";
michael@0 429 }
michael@0 430 print " ---------------\n";
michael@0 431 }
michael@0 432 }
michael@0 433 if ($bytes_printed_tally != $::TotalSize) {
michael@0 434 printf "%.2f%% ", ($::TotalSize- $bytes_printed_tally) * 100.0/$::TotalSize;
michael@0 435 print $::TotalSize - $bytes_printed_tally, "\t not shown due to truncation of type list\n";
michael@0 436 print "Currently only data on $::opt_showtype types are displayed, due to command \n",
michael@0 437 "line argument '--showtype=$::opt_showtype'\n\n";
michael@0 438 }
michael@0 439
michael@0 440 }
michael@0 441
michael@0 442 #----------------------------------------------------------------------
michael@0 443 #
michael@0 444 # Check for duplicate address ranges is Objects table, and
michael@0 445 # create list of sorted addresses for doing pointer-chasing
michael@0 446
michael@0 447 sub validate_address_ranges() {
michael@0 448 # Build sorted list of address for validating interior pointers
michael@0 449 @::SortedAddresses = sort {$a <=> $b} keys %::Objects;
michael@0 450
michael@0 451 # Validate non-overlap of memory
michael@0 452 my $prev_addr_end = -1;
michael@0 453 my $prev_addr = -1;
michael@0 454 my $index = 0;
michael@0 455 my $overlap_tally = 0; # overlapping object memory
michael@0 456 my $unused_tally = 0; # unused memory between blocks
michael@0 457 while ($index <= $#::SortedAddresses) {
michael@0 458 my $address = $::SortedAddresses[$index];
michael@0 459 if ($prev_addr_end > $address) {
michael@0 460 print "Object overlap from $::Objects{$prev_addr}->{'type'}:$prev_addr-$prev_addr_end into";
michael@0 461 my $test_index = $index;
michael@0 462 my $prev_addr_overlap_tally = 0;
michael@0 463
michael@0 464 while ($test_index <= $#::SortedAddresses) {
michael@0 465 my $test_address = $::SortedAddresses[$test_index];
michael@0 466 last if ($prev_addr_end < $test_address);
michael@0 467 print " $::Objects{$test_address}->{'type'}:$test_address";
michael@0 468
michael@0 469 $::Objects{$prev_addr}->{'overlap_count'}++;
michael@0 470 $::Objects{$test_address}->{'overlap_count'}++;
michael@0 471 my $overlap = $prev_addr_end - $test_address;
michael@0 472 if ($overlap > $::Objects{$test_address}->{'size'}) {
michael@0 473 $overlap = $::Objects{$test_address}->{'size'};
michael@0 474 }
michael@0 475 print "($overlap bytes)";
michael@0 476 $prev_addr_overlap_tally += $overlap;
michael@0 477
michael@0 478 $test_index++;
michael@0 479 }
michael@0 480 print " [total $prev_addr_overlap_tally bytes]";
michael@0 481 $overlap_tally += $prev_addr_overlap_tally;
michael@0 482 print "\n";
michael@0 483 }
michael@0 484
michael@0 485 $prev_addr = $address;
michael@0 486 $prev_addr_end = $prev_addr + $::Objects{$prev_addr}->{'size'} - 1;
michael@0 487 $index++;
michael@0 488 } #end while
michael@0 489 if ($overlap_tally) {
michael@0 490 print "Total overlap of $overlap_tally bytes\n";
michael@0 491 }
michael@0 492 }
michael@0 493
michael@0 494 #----------------------------------------------------------------------
michael@0 495 #
michael@0 496 # Evaluate sizes of interobject spacing (fragmentation loss?)
michael@0 497 # Gather the sizes into histograms for analysis
michael@0 498 # This function assumes a sorted list of addresses is present globally
michael@0 499
michael@0 500 sub generate_and_print_unused_memory_histogram() {
michael@0 501 print "\nInterobject spacing (fragmentation waste) Statistics\n";
michael@0 502 if ($::opt_fragment <= 1) {
michael@0 503 print "Statistics are not being gathered. Use '--fragment=10' to get stats\n";
michael@0 504 return;
michael@0 505 }
michael@0 506 print "Ratio of histogram buckets will be a factor of $::opt_fragment\n";
michael@0 507
michael@0 508 my $prev_addr_end = -1;
michael@0 509 my $prev_addr = -1;
michael@0 510 my $index = 0;
michael@0 511
michael@0 512 my @fragment_count;
michael@0 513 my @fragment_tally;
michael@0 514 my $power;
michael@0 515 my $bucket_size;
michael@0 516
michael@0 517 my $max_power = 0;
michael@0 518
michael@0 519 my $tally_sizes = 0;
michael@0 520
michael@0 521 while ($index <= $#::SortedAddresses) {
michael@0 522 my $address = $::SortedAddresses[$index];
michael@0 523
michael@0 524 my $unused = $address - $prev_addr_end;
michael@0 525
michael@0 526 # handle overlaps gracefully
michael@0 527 if ($unused < 0) {
michael@0 528 $unused = 0;
michael@0 529 }
michael@0 530
michael@0 531 $power = 0;
michael@0 532 $bucket_size = 1;
michael@0 533 while ($bucket_size < $unused) {
michael@0 534 $bucket_size *= $::opt_fragment;
michael@0 535 $power++;
michael@0 536 }
michael@0 537 $fragment_count[$power]++;
michael@0 538 $fragment_tally[$power] += $unused;
michael@0 539 if ($power > $max_power) {
michael@0 540 $max_power = $power;
michael@0 541 }
michael@0 542 my $size = $::Objects{$address}->{'size'};
michael@0 543 $tally_sizes += $size;
michael@0 544 $prev_addr_end = $address + $size - 1;
michael@0 545 $index++;
michael@0 546 }
michael@0 547
michael@0 548
michael@0 549 $power = 0;
michael@0 550 $bucket_size = 1;
michael@0 551 print "Basic gap histogram is (max_size:count):\n";
michael@0 552 while ($power <= $max_power) {
michael@0 553 if (! defined $fragment_count[$power]) {
michael@0 554 $fragment_count[$power] = $fragment_tally[$power] = 0;
michael@0 555 }
michael@0 556 printf " %.1f:", $bucket_size;
michael@0 557 print $fragment_count[$power];
michael@0 558 $power++;
michael@0 559 $bucket_size *= $::opt_fragment;
michael@0 560 }
michael@0 561 print "\n";
michael@0 562
michael@0 563 print "Summary gap analysis:\n";
michael@0 564
michael@0 565 $power = 0;
michael@0 566 $bucket_size = 1;
michael@0 567 my $tally = 0;
michael@0 568 my $count = 0;
michael@0 569 while ($power <= $max_power) {
michael@0 570 $count += $fragment_count[$power];
michael@0 571 $tally += $fragment_tally[$power];
michael@0 572 print "$count gaps, totaling $tally bytes, were under ";
michael@0 573 printf "%.1f bytes each", $bucket_size;
michael@0 574 if ($count) {
michael@0 575 printf ", for an average of %.1f bytes per gap", $tally/$count, ;
michael@0 576 }
michael@0 577 print "\n";
michael@0 578 $power++;
michael@0 579 $bucket_size *= $::opt_fragment;
michael@0 580 }
michael@0 581
michael@0 582 print "Total allocation was $tally_sizes bytes, or ";
michael@0 583 printf "%.0f bytes per allocation block\n\n", $tally_sizes/($count+1);
michael@0 584
michael@0 585 }
michael@0 586
michael@0 587 #----------------------------------------------------------------------
michael@0 588 #
michael@0 589 # Now thread the parents and children together by looking through the
michael@0 590 # slots for each object.
michael@0 591 #
michael@0 592 sub create_parent_links(){
michael@0 593 my $min_addr = $::SortedAddresses[0];
michael@0 594 my $max_addr = $::SortedAddresses[ $#::SortedAddresses]; #allow one beyond each object
michael@0 595 $max_addr += $::Objects{$max_addr}->{'size'};
michael@0 596
michael@0 597 print "Viable addresses range from $min_addr to $max_addr for a total of ",
michael@0 598 $max_addr-$min_addr, " bytes\n\n";
michael@0 599
michael@0 600 # Gather stats as we try to convert slots to children
michael@0 601 my $slot_count = 0; # total slots examined
michael@0 602 my $fixed_addr_count = 0; # slots into interiors that were adjusted
michael@0 603 my $parent_child_count = 0; # Number of parent-child links
michael@0 604 my $child_count = 0; # valid slots, discounting sibling twins
michael@0 605 my $child_dup_count = 0; # number of duplicate child pointers
michael@0 606 my $self_pointer_count = 0; # count of discarded self-pointers
michael@0 607
michael@0 608 foreach my $parent (keys %::Objects) {
michael@0 609 # We'll collect a list of this parent object's children
michael@0 610 # by iterating through its slots.
michael@0 611 my @children;
michael@0 612 my %children_hash;
michael@0 613 my $self_pointer = 0;
michael@0 614
michael@0 615 my @slots = @{$::Objects{$parent}->{'slots'}};
michael@0 616 $slot_count += $#slots + 1;
michael@0 617 SLOT: foreach my $child (@slots) {
michael@0 618
michael@0 619 # We only care about pointers that refer to other objects
michael@0 620 if (! defined $::Objects{$child}) {
michael@0 621 # check to see if we are an interior pointer
michael@0 622
michael@0 623 # Punt if we are completely out of range
michael@0 624 next SLOT unless ($max_addr >= $child &&
michael@0 625 $child >= $min_addr);
michael@0 626
michael@0 627 # Do binary search to find object below this address
michael@0 628 my ($min_index, $beyond_index) = (0, $#::SortedAddresses + 1);
michael@0 629 my $test_index;
michael@0 630 while ($min_index !=
michael@0 631 ($test_index = int (($beyond_index+$min_index)/2))) {
michael@0 632 if ($child >= $::SortedAddresses[$test_index]) {
michael@0 633 $min_index = $test_index;
michael@0 634 } else {
michael@0 635 $beyond_index = $test_index;
michael@0 636 }
michael@0 637 }
michael@0 638 # See if pointer is within extent of this object
michael@0 639 my $address = $::SortedAddresses[$test_index];
michael@0 640 next SLOT unless ($child <
michael@0 641 $address + $::Objects{$address}->{'size'});
michael@0 642
michael@0 643 # Make adjustment so we point to the actual child precisely
michael@0 644 $child = $address;
michael@0 645 $fixed_addr_count++;
michael@0 646 }
michael@0 647
michael@0 648 if ($child == $parent) {
michael@0 649 $self_pointer_count++;
michael@0 650 next SLOT; # Discard self-pointers
michael@0 651 }
michael@0 652
michael@0 653 # Avoid creating duplicate child-parent links
michael@0 654 if (! defined $children_hash{$child}) {
michael@0 655 $parent_child_count++;
michael@0 656 # Add the parent to the child's list of parents
michael@0 657 my $parents = $::Objects{$child}->{'parents'};
michael@0 658 if (! $parents) {
michael@0 659 $parents = $::Objects{$child}->{'parents'} = [];
michael@0 660 }
michael@0 661
michael@0 662 $parents->[scalar(@$parents)] = $parent;
michael@0 663
michael@0 664 # Add the child to the parent's list of children
michael@0 665 $children_hash{$child} = 1;
michael@0 666 } else {
michael@0 667 $child_dup_count++;
michael@0 668 }
michael@0 669 }
michael@0 670 @children = keys %children_hash;
michael@0 671 # Track tally of unique children linked
michael@0 672 $child_count += $#children + 1;
michael@0 673
michael@0 674 $::Objects{$parent}->{'children'} = \@children;
michael@0 675
michael@0 676 if (! @children) {
michael@0 677 $::Leafs[$#::Leafs + 1] = $parent;
michael@0 678 }
michael@0 679 }
michael@0 680 print "Scanning $#::SortedAddresses objects, we found $parent_child_count parents-to-child connections by chasing $slot_count pointers.\n",
michael@0 681 "This required $fixed_addr_count interior pointer fixups, skipping $child_dup_count duplicate pointers, ",
michael@0 682 "and $self_pointer_count self pointers\nAlso discarded ",
michael@0 683 $slot_count - $parent_child_count -$self_pointer_count - $child_dup_count,
michael@0 684 " out-of-range pointers\n\n";
michael@0 685 }
michael@0 686
michael@0 687
michael@0 688 #----------------------------------------------------------------------
michael@0 689 # For every leaf, if a leaf has only one parent, then sweep the memory
michael@0 690 # cost into the parent from the leaf
michael@0 691 sub sweep_leaf_memory () {
michael@0 692 my $sweep_count = 0;
michael@0 693 my $leaf_counter = 0;
michael@0 694 LEAF: while ($leaf_counter <= $#::Leafs) {
michael@0 695 my $leaf_addr = $::Leafs[$leaf_counter++];
michael@0 696 my $leaf_obj = $::Objects{$leaf_addr};
michael@0 697 my $parents = $leaf_obj->{'parents'};
michael@0 698
michael@0 699 next LEAF if (! defined($parents) || 1 != scalar(@$parents));
michael@0 700
michael@0 701 # We have only one parent, so we'll try to sweep upwards
michael@0 702 my $parent_addr = @$parents[0];
michael@0 703 my $parent_obj = $::Objects{$parent_addr};
michael@0 704
michael@0 705 # watch out for self-pointers
michael@0 706 next LEAF if ($parent_addr == $leaf_addr);
michael@0 707
michael@0 708 if ($::opt_detail) {
michael@0 709 foreach my $obj ($parent_obj, $leaf_obj) {
michael@0 710 if (!defined $obj->{'original_size'}) {
michael@0 711 $obj->{'original_size'} = $obj->{'size'};
michael@0 712 }
michael@0 713 }
michael@0 714 if (defined $leaf_obj->{'sweep_details'}) {
michael@0 715 if (defined $parent_obj->{'sweep_details'}) { # merge details
michael@0 716 foreach my $swept_obj (keys (%{$leaf_obj->{'sweep_details'}})) {
michael@0 717 %{$parent_obj->{'sweep_details'}}->{$swept_obj} =
michael@0 718 %{$leaf_obj->{'sweep_details'}}->{$swept_obj};
michael@0 719 }
michael@0 720 } else { # No parent info
michael@0 721 $parent_obj->{'sweep_details'} = \%{$leaf_obj->{'sweep_details'}};
michael@0 722 }
michael@0 723 delete $leaf_obj->{'sweep_details'};
michael@0 724 } else { # no leaf detail
michael@0 725 if (!defined $parent_obj->{'sweep_details'}) {
michael@0 726 $parent_obj->{'sweep_details'} = {};
michael@0 727 }
michael@0 728 }
michael@0 729 %{$parent_obj->{'sweep_details'}}->{$leaf_addr} = $leaf_obj->{'original_size'};
michael@0 730 }
michael@0 731
michael@0 732 $parent_obj->{'size'} += $leaf_obj->{'size'};
michael@0 733 $leaf_obj->{'size'} = 0;
michael@0 734
michael@0 735 if (defined ($leaf_obj->{'swept_in'})) {
michael@0 736 $parent_obj->{'swept_in'} += $leaf_obj->{'swept_in'};
michael@0 737 $leaf_obj->{'swept_in'} = 0; # sweep has been handed off to parent
michael@0 738 }
michael@0 739 $parent_obj->{'swept_in'} ++; # tally swept in leaf_obj
michael@0 740
michael@0 741 $sweep_count++;
michael@0 742
michael@0 743 # See if we created another leaf
michael@0 744 my $consumed_children = $parent_obj->{'consumed'}++;
michael@0 745 my @children = $parent_obj->{'children'};
michael@0 746 if ($consumed_children == $#children) {
michael@0 747 $::Leafs[$#::Leafs + 1] = @$parents[0];
michael@0 748 }
michael@0 749 }
michael@0 750 print "Processed ", $leaf_counter, " leaves sweeping memory to parents in ", $sweep_count, " objects\n";
michael@0 751 }
michael@0 752
michael@0 753
michael@0 754 #----------------------------------------------------------------------
michael@0 755 #
michael@0 756 # Subdivide the types of objects that are in our "expand" list
michael@0 757 # List types that should be sub-divided based on parents, and possibly
michael@0 758 # children
michael@0 759 # The argument supplied is a hash table with keys selecting types that
michael@0 760 # need to be "refined" by including the types of the parent objects,
michael@0 761 # and (when we are desparate) the types of the children objects.
michael@0 762
michael@0 763 sub expand_type_names($) {
michael@0 764 my %TypeExpand = %{$_[0]};
michael@0 765
michael@0 766 my @retype; # array of addrs that get extended type names
michael@0 767 foreach my $child (keys %::Objects) {
michael@0 768 my $child_obj = $::Objects{$child};
michael@0 769 next unless (defined ($TypeExpand{$child_obj->{'type'}}));
michael@0 770
michael@0 771 foreach my $relation ('parents','children') {
michael@0 772 my $relatives = $child_obj->{$relation};
michael@0 773 next unless defined @$relatives;
michael@0 774
michael@0 775 # Sort out the names of the types of the relatives
michael@0 776 my %names;
michael@0 777 foreach my $relative (@$relatives) {
michael@0 778 %names->{$::Objects{$relative}->{'type'}} = 1;
michael@0 779 }
michael@0 780 my $related_type_names = join(',' , sort(keys(%names)));
michael@0 781
michael@0 782
michael@0 783 $child_obj->{'name' . $relation} = $related_type_names;
michael@0 784
michael@0 785 # Don't bother with children if we have significant parent types
michael@0 786 last if (!defined ($TypeExpand{$related_type_names}));
michael@0 787 }
michael@0 788 $retype[$#retype + 1] = $child;
michael@0 789 }
michael@0 790
michael@0 791 # Revisit all addresses we've marked
michael@0 792 foreach my $child (@retype) {
michael@0 793 my $child_obj = $::Objects{$child};
michael@0 794 $child_obj->{'type'} = $TypeExpand{$child_obj->{'type'}};
michael@0 795 my $extended_type = $child_obj->{'namechildren'};
michael@0 796 if (defined $extended_type) {
michael@0 797 $child_obj->{'type'}.= "->(" . $extended_type . ")";
michael@0 798 delete ($child_obj->{'namechildren'});
michael@0 799 }
michael@0 800 $extended_type = $child_obj->{'nameparents'};
michael@0 801 if (defined $extended_type) {
michael@0 802 $child_obj->{'type'} = "(" . $extended_type . ")->" . $::Objects{$child}->{'type'};
michael@0 803 delete ($child_obj->{'nameparents'});
michael@0 804 }
michael@0 805 }
michael@0 806 }
michael@0 807
michael@0 808 #----------------------------------------------------------------------
michael@0 809 #
michael@0 810 # Print out a type histogram
michael@0 811
michael@0 812 sub print_type_histogram() {
michael@0 813 load_type_table();
michael@0 814 print_type_table();
michael@0 815 print "\n\n";
michael@0 816 }
michael@0 817
michael@0 818
michael@0 819 #----------------------------------------------------------------------
michael@0 820 # Provide a nice summary of the types during the process
michael@0 821 validate_address_ranges();
michael@0 822 create_parent_links();
michael@0 823
michael@0 824 print "\nBasic memory use histogram is:\n";
michael@0 825 print_type_histogram();
michael@0 826
michael@0 827 generate_and_print_unused_memory_histogram();
michael@0 828
michael@0 829 sweep_leaf_memory ();
michael@0 830 print "After doing basic leaf-sweep processing of instances:\n";
michael@0 831 print_type_histogram();
michael@0 832
michael@0 833 {
michael@0 834 foreach my $typename (@::opt_typedivide) {
michael@0 835 my %expansion_table;
michael@0 836 $expansion_table{$typename} = $typename;
michael@0 837 expand_type_names(\%expansion_table);
michael@0 838 print "After subdividing <$typename> based on inbound (and somtimes outbound) pointers:\n";
michael@0 839 print_type_histogram();
michael@0 840 }
michael@0 841 }
michael@0 842
michael@0 843 exit(); # Don't bother with SCCs yet.
michael@0 844
michael@0 845
michael@0 846 #----------------------------------------------------------------------
michael@0 847 #
michael@0 848 # Determine objects that entrain equivalent sets, using the strongly
michael@0 849 # connected component algorithm from Cormen, Leiserson, and Rivest,
michael@0 850 # ``An Introduction to Algorithms'', MIT Press 1990, pp. 488-493.
michael@0 851 #
michael@0 852 sub compute_post_order($$$) {
michael@0 853 # This routine produces a post-order of the call graph (what CLR call
michael@0 854 # ``ordering the nodes by f[u]'')
michael@0 855 my ($parent, $visited, $finish) = @_;
michael@0 856
michael@0 857 # Bail if we've already seen this node
michael@0 858 return if $visited->{$parent};
michael@0 859
michael@0 860 # We have now!
michael@0 861 $visited->{$parent} = 1;
michael@0 862
michael@0 863 # Walk the children
michael@0 864 my $children = $::Objects{$parent}->{'children'};
michael@0 865
michael@0 866 foreach my $child (@$children) {
michael@0 867 compute_post_order($child, $visited, $finish);
michael@0 868 }
michael@0 869
michael@0 870 # Now that we've walked all the kids, we can append the parent to
michael@0 871 # the post-order
michael@0 872 @$finish[scalar(@$finish)] = $parent;
michael@0 873 }
michael@0 874
michael@0 875 sub compute_equivalencies($$$) {
michael@0 876 # This routine recursively computes equivalencies by walking the
michael@0 877 # transpose of the callgraph.
michael@0 878 my ($child, $table, $equivalencies) = @_;
michael@0 879
michael@0 880 # Bail if we've already seen this node
michael@0 881 return if $table->{$child};
michael@0 882
michael@0 883 # Otherwise, append ourself to the list of equivalencies...
michael@0 884 @$equivalencies[scalar(@$equivalencies)] = $child;
michael@0 885
michael@0 886 # ...and note our other equivalents in the table
michael@0 887 $table->{$child} = $equivalencies;
michael@0 888
michael@0 889 my $parents = $::Objects{$child}->{'parents'};
michael@0 890
michael@0 891 foreach my $parent (@$parents) {
michael@0 892 compute_equivalencies($parent, $table, $equivalencies);
michael@0 893 }
michael@0 894 }
michael@0 895
michael@0 896 sub compute_equivalents() {
michael@0 897 # Here's the strongly connected components algorithm. (Step 2 has been
michael@0 898 # done implictly by our object graph construction.)
michael@0 899 my %visited;
michael@0 900 my @finish;
michael@0 901
michael@0 902 # Step 1. Compute a post-ordering of the object graph
michael@0 903 foreach my $parent (keys %::Objects) {
michael@0 904 compute_post_order($parent, \%visited, \@finish);
michael@0 905 }
michael@0 906
michael@0 907 # Step 3. Traverse the transpose of the object graph in reverse
michael@0 908 # post-order, collecting vertices into %equivalents
michael@0 909 my %equivalents;
michael@0 910 foreach my $child (reverse @finish) {
michael@0 911 compute_equivalencies($child, \%equivalents, []);
michael@0 912 }
michael@0 913
michael@0 914 # Now, we'll trim the %equivalents table, arbitrarily removing
michael@0 915 # ``redundant'' entries.
michael@0 916 EQUIVALENT: foreach my $node (keys %equivalents) {
michael@0 917 my $equivalencies = $equivalents{$node};
michael@0 918 next EQUIVALENT unless $equivalencies;
michael@0 919
michael@0 920 foreach my $equivalent (@$equivalencies) {
michael@0 921 delete $equivalents{$equivalent} unless $equivalent == $node;
michael@0 922 }
michael@0 923 }
michael@0 924
michael@0 925 # Note the equivalent objects in a way that will yield the most
michael@0 926 # interesting order as we do depth-first traversal later to
michael@0 927 # output them.
michael@0 928 ROOT: foreach my $equivalent (reverse @finish) {
michael@0 929 next ROOT unless $equivalents{$equivalent};
michael@0 930 $::Equivalents[$#::Equivalents + 1] = $equivalent;
michael@0 931
michael@0 932 # XXX Lame! Should figure out function refs.
michael@0 933 $::Objects{$equivalent}->{'entrained-size'} = 0;
michael@0 934 }
michael@0 935 }
michael@0 936
michael@0 937 # Do it!
michael@0 938 compute_equivalents();
michael@0 939
michael@0 940
michael@0 941 #----------------------------------------------------------------------
michael@0 942 #
michael@0 943 # Compute the size of each node's transitive closure.
michael@0 944 #
michael@0 945 sub compute_entrained($$) {
michael@0 946 my ($parent, $visited) = @_;
michael@0 947
michael@0 948 $visited->{$parent} = 1;
michael@0 949
michael@0 950 $::Objects{$parent}->{'entrained-size'} = $::Objects{$parent}->{'size'};
michael@0 951
michael@0 952 my $children = $::Objects{$parent}->{'children'};
michael@0 953 CHILD: foreach my $child (@$children) {
michael@0 954 next CHILD if $visited->{$child};
michael@0 955
michael@0 956 compute_entrained($child, $visited);
michael@0 957 $::Objects{$parent}->{'entrained-size'} += $::Objects{$child}->{'entrained-size'};
michael@0 958 }
michael@0 959 }
michael@0 960
michael@0 961 if (! $::opt_noentrained) {
michael@0 962 my %visited;
michael@0 963
michael@0 964 PARENT: foreach my $parent (@::Equivalents) {
michael@0 965 next PARENT if $visited{$parent};
michael@0 966 compute_entrained($parent, \%visited);
michael@0 967 }
michael@0 968 }
michael@0 969
michael@0 970
michael@0 971 #----------------------------------------------------------------------
michael@0 972 #
michael@0 973 # Converts a shared library and an address into a file and line number
michael@0 974 # using a bunch of addr2line processes.
michael@0 975 #
michael@0 976 sub addr2line($$) {
michael@0 977 my ($dso, $addr) = @_;
michael@0 978
michael@0 979 # $::Addr2Lines is a global table that maps a DSO's name to a pair
michael@0 980 # of filehandles that are talking to an addr2line process.
michael@0 981 my $fhs = $::Addr2Lines{$dso};
michael@0 982 if (! $fhs) {
michael@0 983 if (!(-r $dso)) {
michael@0 984 # bogus filename (that happens sometimes), so bail
michael@0 985 return { 'dso' => $dso, 'addr' => $addr };
michael@0 986 }
michael@0 987 my ($in, $out) = (new FileHandle, new FileHandle);
michael@0 988 open2($in, $out, "addr2line --exe=$dso") || die "unable to open addr2line --exe=$dso";
michael@0 989 $::Addr2Lines{$dso} = $fhs = { 'in' => $in, 'out' => $out };
michael@0 990 }
michael@0 991
michael@0 992 # addr2line takes a hex address as input...
michael@0 993 $fhs->{'out'}->print($addr . "\n");
michael@0 994
michael@0 995 # ...and'll return file:lineno as output
michael@0 996 if ($fhs->{'in'}->getline() =~ /([^:]+):(.+)/) {
michael@0 997 return { 'file' => $1, 'line' => $2 };
michael@0 998 }
michael@0 999 else {
michael@0 1000 return { 'dso' => $dso, 'addr' => $addr };
michael@0 1001 }
michael@0 1002 }
michael@0 1003
michael@0 1004
michael@0 1005 #----------------------------------------------------------------------
michael@0 1006 #
michael@0 1007 # Dump the objects, using a depth-first traversal.
michael@0 1008 #
michael@0 1009 sub dump_objects($$$) {
michael@0 1010 my ($parent, $visited, $depth) = @_;
michael@0 1011
michael@0 1012 # Have we already seen this?
michael@0 1013 my $already_visited = $visited->{$parent};
michael@0 1014 return if ($depth == 0 && $already_visited);
michael@0 1015
michael@0 1016 if (! $already_visited) {
michael@0 1017 $visited->{$parent} = 1;
michael@0 1018 $::Total += $::Objects{$parent}->{'size'};
michael@0 1019 }
michael@0 1020
michael@0 1021 my $parententry = $::Objects{$parent};
michael@0 1022
michael@0 1023 # Make an ``object'' div, which'll contain an ``object'' span, two
michael@0 1024 # ``toggle'' spans, an invisible ``stack'' div, and the invisible
michael@0 1025 # ``children'' div.
michael@0 1026 print "<div class='object'>";
michael@0 1027
michael@0 1028 if ($already_visited) {
michael@0 1029 print "<a href='#$parent'>";
michael@0 1030 }
michael@0 1031 else {
michael@0 1032 print "<span id='$parent' class='object";
michael@0 1033 print " root" if $depth == 0;
michael@0 1034 print "'>";
michael@0 1035 }
michael@0 1036
michael@0 1037 printf "0x%x&lt;%s&gt;[%d]", $parent, $parententry->{'type'}, $parententry->{'size'};
michael@0 1038
michael@0 1039 if ($already_visited) {
michael@0 1040 print "</a>";
michael@0 1041 goto DONE;
michael@0 1042 }
michael@0 1043
michael@0 1044 if ($depth == 0) {
michael@0 1045 print "($parententry->{'entrained-size'})"
michael@0 1046 if $parententry->{'entrained-size'};
michael@0 1047
michael@0 1048 print "&nbsp;<span class='toggle' onclick='toggleDisplay(this.parentNode.nextSibling.nextSibling);'>Children</span>"
michael@0 1049 if @{$parententry->{'children'}} > 0;
michael@0 1050 }
michael@0 1051
michael@0 1052 if (($depth == 0 || !$::opt_nochildstacks) && !$::opt_nostacks) {
michael@0 1053 print "&nbsp;<span class='toggle' onclick='toggleDisplay(this.parentNode.nextSibling);'>Stack</span>";
michael@0 1054 }
michael@0 1055
michael@0 1056 print "</span>";
michael@0 1057
michael@0 1058 # Print stack traces
michael@0 1059 print "<div class='stack'>\n";
michael@0 1060
michael@0 1061 if (($depth == 0 || !$::opt_nochildstacks) && !$::opt_nostacks) {
michael@0 1062 my $depth = $::opt_depth;
michael@0 1063
michael@0 1064 FRAME: foreach my $frame (@{$parententry->{'stack'}}) {
michael@0 1065 # Only go as deep as they've asked us to.
michael@0 1066 last FRAME unless --$depth >= 0;
michael@0 1067
michael@0 1068 # Stack frames look like ``mangled_name[dso address]''
michael@0 1069 $frame =~ /([^\]]+)\[(.*) \+0x([0-9A-Fa-f]+)\]/;
michael@0 1070
michael@0 1071 # Convert address to file and line number
michael@0 1072 my $mangled = $1;
michael@0 1073 my $result = addr2line($2, $3);
michael@0 1074
michael@0 1075 if ($result->{'file'}) {
michael@0 1076 # It's mozilla source! Clean up refs to dist/include
michael@0 1077 if (($result->{'file'} =~ s/.*\.\.\/\.\.\/dist\/include\//http:\/\/bonsai.mozilla.org\/cvsguess.cgi\?file=/) ||
michael@0 1078 ($result->{'file'} =~ s/.*\/mozilla/http:\/\/bonsai.mozilla.org\/cvsblame.cgi\?file=mozilla/)) {
michael@0 1079 my $prevline = $result->{'line'} - 10;
michael@0 1080 print "<a target=\"lxr_source\" href=\"$result->{'file'}\&mark=$result->{'line'}#$prevline\">$mangled</a><br>\n";
michael@0 1081 }
michael@0 1082 else {
michael@0 1083 print "$mangled ($result->{'file'}, line $result->{'line'})<br>\n";
michael@0 1084 }
michael@0 1085 }
michael@0 1086 else {
michael@0 1087 print "$result->{'dso'} ($result->{'addr'})<br>\n";
michael@0 1088 }
michael@0 1089 }
michael@0 1090
michael@0 1091 }
michael@0 1092
michael@0 1093 print "</div>";
michael@0 1094
michael@0 1095 # Recurse to children
michael@0 1096 if (@{$parententry->{'children'}} >= 0) {
michael@0 1097 print "<div class='children'>\n" if $depth == 0;
michael@0 1098
michael@0 1099 foreach my $child (@{$parententry->{'children'}}) {
michael@0 1100 dump_objects($child, $visited, $depth + 1);
michael@0 1101 }
michael@0 1102
michael@0 1103 print "</div>" if $depth == 0;
michael@0 1104 }
michael@0 1105
michael@0 1106 DONE:
michael@0 1107 print "</div>\n";
michael@0 1108 }
michael@0 1109
michael@0 1110
michael@0 1111 #----------------------------------------------------------------------
michael@0 1112 #
michael@0 1113 # Do the output.
michael@0 1114 #
michael@0 1115
michael@0 1116 # Force flush on STDOUT. We get funky output unless we do this.
michael@0 1117 $| = 1;
michael@0 1118
michael@0 1119 # Header
michael@0 1120 print "<html>
michael@0 1121 <head>
michael@0 1122 <title>Object Graph</title>
michael@0 1123 <style type='text/css'>
michael@0 1124 body { font: medium monospace; background-color: white; }
michael@0 1125
michael@0 1126 /* give nested div's some margins to make it look like a tree */
michael@0 1127 div.children > div.object { margin-left: 1em; }
michael@0 1128 div.object > div.object { margin-left: 1em; }
michael@0 1129
michael@0 1130 /* Indent stacks, too */
michael@0 1131 div.object > div.stack { margin-left: 3em; }
michael@0 1132
michael@0 1133 /* apply font decorations to special ``object'' spans */
michael@0 1134 span.object { font-weight: bold; color: darkgrey; }
michael@0 1135 span.object.root { color: black; }
michael@0 1136
michael@0 1137 /* hide ``stack'' divs by default; JS will show them */
michael@0 1138 div.stack { display: none; }
michael@0 1139
michael@0 1140 /* hide ``children'' divs by default; JS will show them */
michael@0 1141 div.children { display: none; }
michael@0 1142
michael@0 1143 /* make ``toggle'' spans look like links */
michael@0 1144 span.toggle { color: blue; text-decoration: underline; cursor: pointer; }
michael@0 1145 span.toggle:active { color: red; }
michael@0 1146 </style>
michael@0 1147 <script language='JavaScript'>
michael@0 1148 function toggleDisplay(element)
michael@0 1149 {
michael@0 1150 element.style.display = (element.style.display == 'block') ? 'none' : 'block';
michael@0 1151 }
michael@0 1152 </script>
michael@0 1153 </head>
michael@0 1154 <body>
michael@0 1155 ";
michael@0 1156
michael@0 1157 {
michael@0 1158 # Body. Display ``roots'', sorted by the amount of memory they
michael@0 1159 # entrain. Because of the way we've sorted @::Equivalents, we should
michael@0 1160 # get a nice ordering that sorts things with a lot of kids early
michael@0 1161 # on. This should yield a fairly "deep" depth-first traversal, with
michael@0 1162 # most of the objects appearing as children.
michael@0 1163 #
michael@0 1164 # XXX I sure hope that Perl implements a stable sort!
michael@0 1165 my %visited;
michael@0 1166
michael@0 1167 foreach my $parent (sort { $::Objects{$b}->{'entrained-size'}
michael@0 1168 <=> $::Objects{$a}->{'entrained-size'} }
michael@0 1169 @::Equivalents) {
michael@0 1170 dump_objects($parent, \%visited, 0);
michael@0 1171 print "\n";
michael@0 1172 }
michael@0 1173 }
michael@0 1174
michael@0 1175 # Footer
michael@0 1176 print "<br> $::Total total bytes\n" if $::Total;
michael@0 1177 print "</body>
michael@0 1178 </html>
michael@0 1179 ";
michael@0 1180

mercurial