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