Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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<%s>[%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 " <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 " <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 |