Plack-Middleware-Debug-RefCounts

 view release on metacpan or  search on metacpan

lib/Plack/Middleware/Debug/RefCounts.pm  view on Meta::CPAN

#pod     \%diff_list = $self->calculate_arena_refs;
#pod
#pod Walks the arena (of Perl variables) via L<Devel::Gladiator/walk_arena>, and
#pod catalogs all non-SCALAR/REFs into ref types and memory locations.  Returns a
#pod diff list hashref.
#pod
#pod I<After> the first (initializing) run, if L</PLACK_MW_DEBUG_REFCOUNTS_DUMP_RE>
#pod is set, newly discovered matching variables will be dumped to C<STDERR>.
#pod
#pod =cut

sub calculate_arena_refs {
    my $self    = shift;
    my $dump_re = $PLACK_MW_DEBUG_REFCOUNTS_DUMP_RE;

    # To save on memory with this memory-intensive operation, we operate against
    # $Arena_Ref as the sole storage mechanism for variable type/addresses.  The $all
    # variable has the arena, but it's strictly a pointer without any memory usage.

    $dump_re = undef unless %$Arena_Refs;  # don't dump the first run

    # refs start out "deleted", until they are found again
    $Arena_Refs->{$_} = -1 for keys %$Arena_Refs;

    # This creates string address lists of all of the existing arena variables.
    # This is much cleaner and memory-friendly than storing real refs.
    my $all = Devel::Gladiator::walk_arena();
    foreach my $it (@$all) {
        my $type = ref $it;

        # There are so many of these that even cataloging the memory addresses
        # of these is enough to cause an OOM in some systems.
        next if $type eq 'SCALAR' || $type eq 'REF';

        # Get the pointer address
        my $addr = sprintf '%x', refaddr $it;
        my $id   = "$type/$addr";

        unless ($Arena_Refs->{$id}) {
            # New ref
            if ($dump_re && $type =~ /$dump_re/) {
                # Sometimes this dies. If so, just move on to the next one.
                eval {
                    local $Data::Dumper::Maxdepth = 2;
                    print STDERR "+$id = ".Dumper($it);
                };
                if ($@) {
                    print STDERR "+$id > ERROR: $@";
                }
            }
        }
        # either equalize to 0 for an existing ref, or go to 1 for a new one
        $Arena_Refs->{$id}++;

        $it = undef;
    }
    $all = undef;

    my %diff_list;
    foreach my $id (keys %$Arena_Refs) {
        my ($type, $addr) = split m!/!, $id, 2;
        my $cmp = $Arena_Refs->{$id};

        # Process the diff list
        $diff_list{$type}   //= [0,0,0];
        $diff_list{$type}[0] += $cmp;              # diff
        $diff_list{$type}[1]++ unless $cmp ==  1;  # count_a
        $diff_list{$type}[2]++ unless $cmp == -1;  # count_b

        # Also dump the removed refs, if requested
        if ($dump_re && $type =~ /$dump_re/ && $cmp == -1) {
            say STDERR "-$id";
        }

        # Remove any deleted references
        delete $Arena_Refs->{$id} if $cmp == -1;
    }

    say STDERR '' if $dump_re;

    return \%diff_list;
}

#pod =head2 compare_arena_counts
#pod
#pod     @lines = $self->compare_arena_counts(\%diff_list);
#pod
#pod Using a diff list from L</calculate_arena_refs>, this displays the new ref
#pod counts on STDERR, and returns those displayed lines.
#pod
#pod Anything listed here has either shrunk or grown the variables within the arena.
#pod
#pod Example output:
#pod
#pod     === Reference growth counts ===
#pod     +4    (diff) =>       4 (now) => Class::MOP::Class::Immutable::Moose::Meta::Class
#pod     +1    (diff) =>       1 (now) => Class::MOP::Method::Wrapped
#pod     +12   (diff) =>      19 (now) => DBD::mysql::st_mem
#pod     +24   (diff) =>      38 (now) => DBI::st
#pod     +1    (diff) =>       1 (now) => Data::Visitor::Callback
#pod     +4    (diff) =>       4 (now) => DateTime
#pod     +1    (diff) =>       1 (now) => DateTime::TimeZone::America::New_York
#pod     +1    (diff) =>       1 (now) => Devel::StackTrace
#pod     +1    (diff) =>       1 (now) => FCGI
#pod     +3    (diff) =>       3 (now) => FCGI::Stream
#pod
#pod =cut

sub compare_arena_counts {
    my ($self, $diff_list) = @_;

    my @lines = ( "=== Reference growth counts ===\n" );

    foreach my $key (sort keys %$diff_list) {
        my ($diff, $count_a, $count_b) = @{ $diff_list->{$key} };

        next unless $diff;
        push @lines, sprintf "%+-5d (diff) => %7d (now) => %-s\n", $diff, $count_b, $key;
    }

    print STDERR $_ for @lines, "\n";



( run in 2.433 seconds using v1.01-cache-2.11-cpan-71847e10f99 )