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 )