Carp-Capture

 view release on metacpan or  search on metacpan

lib/Carp/Capture.pm  view on Meta::CPAN

      _anno_next_id => 'count',
      _anno_store   => 'push',
     },
    );

has 'capture_status',
    (
     documentation  => q{ The capturing ENABLED/DISABLED stack },
     is             => 'rw',
     isa            => 'ArrayRef',
     default        => sub{ [$ENABLED] },
     traits         => [qw( Array )],
     handles        =>
     {
      status        => [ get  => -1        ],
      enable        => [ push => $ENABLED  ],
      disable       => [ push => $DISABLED ],
      _pop_status   => 'pop',
      _stack_depth  => 'count',
     },
    );

no Moose;
__PACKAGE__->meta->make_immutable();

#-----
# To capture a callstack we use Perl's caller(), in a loop.  Each stackframe
# reported by caller() contains three values of interest: the subroutine, the
# filename and the line number.  In effect we want to save a list of
# triplets, one triplet for each stackframe.
#
# The obvious choice would be an Array of three-element ArrayRefs, or maybe
# just a flat Array where the number of elements is a multiple of three.
# Unfortunately, Arrays of ArrayRefs of Scalars, or even just flat Arrays of
# Scalars, consume an apalling amount of storage.
#
# With an eye on minimizing storage we decide to use a hash (the 'components'
# attribute) to hold the subroutine name and filename strings returned by
# caller().  Filenames are likely to be reused within a single callstack, and
# subroutines are likely to be reused by different callstacks.  The hash
# stores one copy of a string as its key and assigns a unique integer as the
# value.
#
# Using the Components hash to map subroutines and filenames to integers
# makes all three of our frame components become integers.  The entire
# callstack, in fact, becomes a list of integers.  Using pack() we can
# convert a list of integers into a binary string.  The amount of storage
# required for this string is a small fraction of that required for an Array
# of Arrays, so we choose to internally represent callstacks as packstrings.
#
# We could return the packstring to the user, as the representation of their
# callstack.  Instead, we choose to employ the same strategy of hash-key
# storage with unique-integer identifier as value.  The 'callstacks'
# attribute is a hash that holds callstack packstrings as keys.  This means
# that the user is given a simple integer to track their callstack.
#
# There are two other reasons, besides binary avoidance at the user-level,
# for mapping packstrings with the Callstacks hash.  Repeated callstacks
# happen whenever capturing is requested from a loop.  By mapping callstacks
# we only have to store a repeated callstack once.  We can also help the user
# identify loop iterations by offering storage for an 'annotation' value.
#
# If the user provides an annotation then we will need to save a copy of it,
# which we do pushing it onto an array - the 'annotations' attribute.  The
# index of the new element becomes the annotation's id.
#
# We can't use the same (hash) strategy employed for components and pack
# strings because annotation values can be any kind of scalar including
# references and undefs; things that don't survive stringification needed for
# storage as hash keys.
#
# So, there is no redundancy avoidance mechanism for annotations, i.e.
# storage is not as efficient as it might be.  This is not a big deal because
# we expect that:
#   1) Most users won't even use annotations in the first place.
#   2) When annotations are supplied, they are likely to be different.
#
# We then combine the annotation_id and the callstack_id together in an
# Association data structure - another packstring.  The Association is then
# inserted in the 'callstacks' hash.  The 'callstacks' attribute stores BOTH
# callstacks and associations.  This is OK, because we can distinguish
# between callstack packstrings and association packstrings by their
# lengths.  Associations always contain 2 values, callstacks always contain
# 3N values.
#
# If a packstring pulled out of the Callstacks hash is of length 2 then we
# extract the first value for annotation id, or the second value for a
# callstack id.  The callstack id is then used to fetch another packstring,
# which will be the real callstack.
#
# Fetching a packstring from a hash, given the corresponding identifier
# value, is a slow, linear search.  We have to search through all the values
# until we find a match.  We accept this tradeoff because
#   1) Providing fast lookup from both directions (string->id AND id->string)
#      would double storage requirements.
#   2) Each capture would require two insertions and would therefore be slower.
#   3) If the user is retrieving a callstack for presentation as a
#      stacktrace then some kind of exception has probably happened and
#      performance is no longer that important.
#
# If you have followed all the above you may be questioning why not
# combine the 'components' attribute and 'callstacks' attribute into a single
# hash - they are both just string->id mappings.  You're right, this would
# work just fine, and it would save a very small amount of storage - one
# hash.  I decided against combining hashes because leaving them separate
# is somewhat safer.  If the user mangles an id they simply get the wrong
# callstack; they can't crash us by identifying a filename as a callstack.
#-----

sub capture {
    my $self = shift;

    return $UNCAPTURED
        if $self->status == $DISABLED;

    #-----
    # undef is a valid annotation.  Therefore we need to distinguish between
    # not providing an annotation argument and providing undef.
    #-----
    my( $annotation_provided, $annotation ) = (@_ > 0)
        ? ( 1, shift )



( run in 0.816 second using v1.01-cache-2.11-cpan-71847e10f99 )