Added sweeping of memory attribution to parent objects

This commit is contained in:
jar%netscape.com 2000-12-15 21:12:09 +00:00
parent eea1482d41
commit b2951a4ef6

View File

@ -19,6 +19,7 @@
#
# Contributor(s):
# Chris Waterson <waterson@netscape.com>
# Jim Roskind <jar@netscape.com>
#
#
# A perl version of Patrick Beard's ``Leak Soup'', which processes the
@ -39,8 +40,9 @@ $::opt_nochildstacks = 0;
$::opt_depth = 9999;
$::opt_noentrained = 0;
$::opt_noslop = 0;
$::opt_showtype = 100000000;
GetOptions("help", "format=s", "nostacks", "nochildstacks", "depth=i", "noentrained", "noslop");
GetOptions("help", "format=s", "nostacks", "nochildstacks", "depth=i", "noentrained", "noslop", "showtype=i");
if ($::opt_help) {
die "usage: leak-soup.pl [options] <leakfile>
@ -51,14 +53,27 @@ if ($::opt_help) {
--nochildstacks Do not compute stack traces for entrained objects
--depth=<max> Only compute stack traces to depth of <max>
--noentrained Do not compute amount of memory entrained by root objects
--noslop Don't ignore low bits when searching for pointers";
--noslop Don't ignore low bits when searching for pointers
--showtype=<i> Show memory usage most-significant <i> types";
}
# This is the table that keeps a graph of objects. It's indexed by the
# object's address (as an integer), and refers to a simple hash that
# has information about the object's type, size, slots, and allocation
# stack.
$::Objects = { };
%::Objects = %{0};
# This is the table that keeps track of memory usage on a per-type basis.
# It is indexed by the type name (string), and keeps a tally of the
# total number of such objects, and the memory usage of such objects.
%::Types = %{0};
$::TotalSize = 0; # sum of sizes of all objects included $::Types{}
# This is an array of leaf node addresses. A leaf node has no children
# with memory allocations. We traverse them sweeping memory
# tallies into parents. Note that after all children have
# been swept into a parent, that parent may also become a leaf node.
@::Leafs = @{0};
#----------------------------------------------------------------------
@ -187,36 +202,320 @@ else {
die "unknown format ``$::opt_format''.";
}
#----------------------------------------------------------------------
#
# Do basic initialization of the type hash table. Accumulate
# total counts, and basic memory usage (not including children)
sub init_type_table() {
# Reset global counter and hash table
$::TotalSize = 0;
%::Types = %{0};
OBJECT: foreach my $addr (keys %::Objects) {
my $obj = $::Objects{$addr};
my ($type, $size, $swept_in) =
($obj->{'type'}, $obj->{'size'}, $obj->{'swept_in'});
my $type_data = $::Types{$type};
if (! defined $type_data) {
$::Types{$type} =
$type_data = {'count' => 0, 'size' => 0,
'max' => $size, 'min' => $size,
'swept_in' => 0, 'swept' => 0};
}
if (!$size) {
$type_data->{'swept'}++;
next OBJECT;
}
$::TotalSize += $size;
$type_data->{'count'}++;
$type_data->{'size'} += $size;
if (defined $swept_in) {
$type_data->{'swept_in'} += $swept_in;
}
if ($type_data->{'max'} < $size) {
$type_data->{'max'} = $size;
}
# Watch out for case where min is produced by a swept object
if (!$type_data->{'min'} || $type_data->{'min'} > $size) {
$type_data->{'min'} = $size;
}
}
}
#----------------------------------------------------------------------
sub print_type_table(){
if (!$::opt_showtype) {
return;
}
my $line_count = 0;
my $bytes_printed_tally = 0;
# Display type summary information
my @sorted_types = keys (%::Types);
print "There are ", 1 + $#sorted_types, " types containing ", $::TotalSize, " bytes\n";
@sorted_types = sort {$::Types{$b}->{'size'}
<=> $::Types{$a}->{'size'} } @sorted_types;
foreach my $type (@sorted_types) {
last if (++$line_count > $::opt_showtype);
my $type_data = $::Types{$type};
$bytes_printed_tally += $type_data->{'size'};
if ($type_data->{'count'}) {
printf "%2.2f%% ", $type_data->{'size'} * 100.0/$::TotalSize;
print $type_data->{'size'},
"\t(",
$type_data->{'min'}, "/",
int($type_data->{'size'} / $type_data->{'count'}),"/",
$type_data->{'max'}, ")";
print "\t", $type_data->{'count'},
" x ";
}
print $type, "\t";
if ($type_data->{'swept_in'}) {
print $type_data->{'swept_in'}, " sub-objs absorbed ";
}
if (0 < $type_data->{'swept'}) {
print $type_data->{'swept'}, " swept away";
}
print "\n" ;
}
if ($bytes_printed_tally != $::TotalSize) {
printf "%2.2f%% ", ($::TotalSize- $bytes_printed_tally) * 100.0/$::TotalSize;
print $::TotalSize - $bytes_printed_tally, "\t not shown due to truncation of type list\n\n";
}
}
#----------------------------------------------------------------------
#
# Now thread the parents and children together by looking through the
# slots for each object.
#
foreach my $parent (keys %::Objects) {
# We'll collect a list of this parent object's children
# by iterating through its slots.
my @children;
sub create_parent_links(){
# Build sorted list of address for validating interior pointers
my @addrs = sort {$a <=> $b} keys %::Objects;
my $min_addr = $addrs[0];
my $max_addr = $addrs[ $#addrs]; #allow one beyond each object
$max_addr += $::Objects{$max_addr}->{'size'};
my $slots = $::Objects{$parent}->{'slots'};
SLOT: foreach my $child (@$slots) {
# We only care about pointers that refer to other objects
next SLOT unless $::Objects{$child};
# Gather stats as we try to convert slots to children
my $slot_count = 0; # total slots examined
my $fixed_addr_count = 0; # slots into interiors that were adjusted
my $parent_child_count = 0; # Number of parent-child links
my $child_count = 0; # valid slots, discounting sibling twins
my $child_dup_count = 0; # number of duplicate child pointers
my $self_pointer_count = 0; # count of discarded self-pointers
# Add the parent to the child's list of parents
my $parents = $::Objects{$child}->{'parents'};
if (! $parents) {
$parents = $::Objects{$child}->{'parents'} = [];
}
foreach my $parent (keys %::Objects) {
# We'll collect a list of this parent object's children
# by iterating through its slots.
my @children;
my %children_hash;
my $self_pointer = 0;
$parents->[scalar(@$parents)] = $parent;
my @slots = @{$::Objects{$parent}->{'slots'}};
$slot_count += $#slots + 1;
SLOT: foreach my $child (@slots) {
# Add the child to the parent's list of children
$children[$#children + 1] = $child;
# We only care about pointers that refer to other objects
if (! defined $::Objects{$child}) {
# check to see if we are an interior pointer
# Punt if we are completely out of range
next SLOT unless ($max_addr >= $child &&
$child >= $min_addr);
# Do binary search to find object below this address
my ($min_index, $beyond_index) = (0, $#addrs + 1);
my $test_index;
while ($min_index !=
($test_index = int (($beyond_index+$min_index)/2))) {
if ($child >= $addrs[$test_index]) {
$min_index = $test_index;
} else {
$beyond_index = $test_index;
}
}
# See if pointer is within extent of this object
my $address = $addrs[$test_index];
next SLOT unless ($child <
$address + $::Objects{$address}->{'size'});
# Make adjustment so we point to the actual child precisely
$child = $address;
$fixed_addr_count++;
}
if ($child == $parent) {
$self_pointer_count++;
next SLOT; # Discard self-pointers
}
# Avoid creating duplicate child-parent links
if (! defined $children_hash{$child}) {
$parent_child_count++;
# Add the parent to the child's list of parents
my $parents = $::Objects{$child}->{'parents'};
if (! $parents) {
$parents = $::Objects{$child}->{'parents'} = [];
}
$parents->[scalar(@$parents)] = $parent;
# Add the child to the parent's list of children
$children_hash{$child} = 1;
} else {
$child_dup_count++;
}
}
@children = keys %children_hash;
# Track tally of unique children linked
$child_count += $#children + 1;
$::Objects{$parent}->{'children'} = \@children;
if (! @children) {
$::Leafs[$#::Leafs + 1] = $parent;
}
}
print "Scanning $#addrs objects linked $parent_child_count pointers by chasing $slot_count addresses.\n",
"This required $fixed_addr_count interior pointer fixups, skipping $child_dup_count duplicates, ",
"and $self_pointer_count self pointers\nAlso discarded ",
$slot_count - $parent_child_count -$self_pointer_count - $child_dup_count,
" out-of-range pointers\n";
}
#----------------------------------------------------------------------
# For every leaf, if a leaf has only one parent, then sweep the memory
# cost into the parent from the leaf
sub sweep_leaf_memory () {
my $sweep_count = 0;
my $leaf_counter = 0;
LEAF: while ($leaf_counter <= $#::Leafs) {
my $leaf_obj = $::Objects{$::Leafs[$leaf_counter++]};
my $parents = $leaf_obj->{'parents'};
next LEAF if (! defined($parents) || 1 != scalar(@$parents));
# We have only one parent, so we'll try to sweep upwards
my $parent_obj = $::Objects{@$parents[0]};
# watch out for self-pointers
next LEAF if ($parent_obj == $leaf_obj);
$parent_obj->{'size'} += $leaf_obj->{'size'};
$parent_obj->{'swept_in'} +=
defined ($leaf_obj->{'swept_in'})
? $leaf_obj->{'swept_in'} + 1
: 1;
$leaf_obj->{'size'} = 0;
$sweep_count++;
# Tally another swept child, and see if we created another leaf
my $consumed_children = $parent_obj->{'consumed'}++;
my @children = $parent_obj->{'children'};
if ($consumed_children == $#children) {
$::Leafs[$#::Leafs + 1] = @$parents[0];
}
}
print "Processed ", $leaf_counter, " leaves sweeping memory to parents in ", $sweep_count, " objects\n";
}
#----------------------------------------------------------------------
#
# Subdivide the types of objects that are in our "expand" list
# List types that should be sub-divided based on parents, and possibly
# children
# The argument supplied is a hash table with keys selecting types that
# need to be "refined" by including the types of the parent objects,
# and (when we are desparate) the types of the children objects.
sub expand_type_names($) {
my %TypeExpand = %{$_[0]};
my @retype; # array of addrs that get extended type names
foreach my $child (keys %::Objects) {
my $child_obj = $::Objects{$child};
next unless (defined ($TypeExpand{$child_obj->{'type'}}));
foreach my $relation ('parents','children') {
my $relatives = $child_obj->{$relation};
next unless defined @$relatives;
# Sort out the names of the types of the relatives
my %names;
foreach my $relative (@$relatives) {
%names->{$::Objects{$relative}->{'type'}} = 1;
}
my $related_type_names = join(',' , sort(keys(%names)));
$child_obj->{'name' . $relation} = $related_type_names;
# Don't bother with children if we have significant parent types
last if (!defined ($TypeExpand{$related_type_names}));
}
$retype[$#retype + 1] = $child;
}
$::Objects{$parent}->{'children'} = \@children;
# Revisit all addresses we've marked
foreach my $child (@retype) {
my $child_obj = $::Objects{$child};
$child_obj->{'type'} = $TypeExpand{$child_obj->{'type'}};
my $extended_type = $child_obj->{'namechildren'};
if (defined $extended_type) {
$child_obj->{'type'}.= "->(" . $extended_type . ")";
delete ($child_obj->{'namechildren'});
}
$extended_type = $child_obj->{'nameparents'};
if (defined $extended_type) {
$child_obj->{'type'} = "(" . $extended_type . ")->" . $::Objects{$child}->{'type'};
delete ($child_obj->{'nameparents'});
}
}
}
#----------------------------------------------------------------------
# Provide a nice summary of the types during the process
print "Before doing any work on types:\n";
init_type_table();
print_type_table();
print "\n\n";
create_parent_links();
sweep_leaf_memory ();
print "After doing basic leaf-sweep processing of instances:\n";
init_type_table();
print_type_table();
print "\n\n";
expand_type_names({'void*'=>'void*'});
print "After subdividing <void*> types:\n";
init_type_table();
print_type_table();
print "\n\n";
expand_type_names({'(void*)->void*->(void*)'=>'void*'});
print "After subdividing <(void*)->void*->(void*)> types:\n";
init_type_table();
print_type_table();
print "\n\n";
exit(); # Don't bother with SCCs yet.
#----------------------------------------------------------------------