Guard-Stats
view release on metacpan or search on metacpan
lib/Guard/Stats.pm view on Meta::CPAN
use strict;
use warnings;
package Guard::Stats;
=head1 NAME
Guard::Stats - Create guard objects and gather averall usage statistics from them.
=head1 SYNOPSIS
Suppose we have a long-running application making heavy use of closures,
and need to monitor the number of executed, not executed, and gone subrefs.
So we put a guard into each closure to update the statistics:
# in initial section
use Guard::Stats;
my $stat = Guard::Stats->new;
# when running
my $guard = $stat->guard;
my $callback = sub {
$guard->finish("taken route 1");
# now do useful stuff
};
# ... do whatever we need and call $callback eventually
# in diagnostic procedures triggered by an external event
my $data = $stat->get_stat;
warn "$data->{running} callbacks still waiting to be executed";
Of course, alive/dead counts of any objects (not only sub refs) may be
monitored in a similar way.
=head1 DESCRIPTION
A guard is a special object that does something useful in destructor, typically
freeing a resource or lock. These guards however don't free anything. Instead,
they call home to keep their master (YOU) informed.
=head2 The classes
Guard::Stats is a long-lived object that spawns guards and
gathers statistical information.
Its public methods are guard() and various statistic getters.
Guard::Stats::Instance is the guard. When it is DESTROYed, it signals the stat
object which created it.
Its public methods are end( [$result] ) and is_done().
=head2 The counters
When a guard is created, the C<total> counter increases. When it's detroyed,
C<dead> counter increases. C<alive> = C<total> - C<dead> is the number of
guards that still exist.
Additionally, guards implement a C<end()> method which indicates that
the action associates with the guard is complete. Typically a guard should
be destroyed soon afterwards. The guards for which neither DESTROY nor
end were called are considered C<running> (this is used in C<on_level>).
The full matrix or DESTROY()/end() combinations is as follows:
DESTROY: * 0 1
end:* total+ alive dead
end:0 ? running broken+
end:1 done+ zombie complete+
A "+" marks values directly measured by Guard::Stats. They all happen to be
monotonous. Other statistics are derived from these.
Note that counter showing end() NOT called regardless of DESTROY() does not
have a meaningful name (yet?).
=head2 Running count callback
Whenever number of guards in the C<running> state passes given level,
a function may be called. This can be used to monitor load, prevent
uncontrolled memory usage growth, etc.
See C<on_level> below.
=head1 METHODS
=cut
our $VERSION = 0.03;
use Carp;
use Guard::Stats::Instance;
my @values;
BEGIN { @values = qw( total done complete broken ) };
use fields qw(guard_class time_stat results on_level), @values;
=head2 new (%options)
%options may include:
=over
=item * time_stat - an object or class to store time statistics. The class
should support C<new> and C<add_data( $number )> operations for this to work.
Suitable candidates are L<Statistics::Descriptive::Sparse> and
L<Statistics::Descriptive::LogScale> (both have sublinear memory usage).
=item * guard_class - packge name to override default guard class. See
"overriding guard class" below.
=back
=cut
sub new {
my $class = shift;
my %opt = @_;
my $self = fields::new($class);
if ( my $stat = $opt{time_stat} ) {
$stat->can("add_data")
or croak( __PACKAGE__.": time_stat object $stat doesn't have add_data() method" );
$self->{time_stat} = ref $stat ? $stat : $stat->new;
};
$self->{guard_class} = $opt{guard_class} || 'Guard::Stats::Instance';
lib/Guard/Stats.pm view on Meta::CPAN
=head2 guard( %options )
Create a guard object. All options will be forwarded to the guard's new()
"as is", except for C<owner> and C<want_time> which are reserved.
As of current, the built-in guard class supports no other options, so
supplying a hash is useless unless the guard class is redefined. See
"overriding guard class" below. See also L<Guard::Stats::Instance> for the
detailed description of default guard class.
=cut
sub guard {
my __PACKAGE__ $self = shift;
my %opt = @_;
my $g = $self->{guard_class}->new(
%opt,
owner => $self,
want_time => $self->{time_stat} ? 1 : 0,
);
$self->{total}++;
my $running = $self->running;
if (my $code = $self->{on_level}{$running}) {
$code->($running, $self);
};
return $g;
};
=head2 $guard->end( [ $result ] )
Signal that action associated with the guard is over. If $result is provided,
it is saved in a special hash (see get_stat_result() below). This can be used
e.g. to measure the number of successful/unsuccessful actions.
Calling end() a second time on the same guard will result in a warning, and
change no counters.
=head2 $guard->is_done
Tell whether end() was ever called on the guard.
=head2 undef $guard
The guard's DESTROY() method will signal stats object that guard is gone, and
whether it was finished before destruction.
=cut
=head1 Statistics
The following getters represent numbers of guards in respective states:
=over
=item * total() - all guards ever created;
=item * dead() - DESTROY was called;
=item * alive() - DESTROY was NOT called;
=item * done() - end() was called;
=item * complete() - both end() and DESTROY were called;
=item * zombie() - end() was called, but not DESTROY;
=item * running() - neither end() nor DESTROY called;
=item * broken() - number of guards for which DESTROY was called,
but NOT end().
=back
Growing broken and/or zombie counts usually indicate something went wrong.
=cut
# create lots of identic subs
foreach (@values) {
my $name = $_;
my $code = sub { return shift->{$name} };
no strict 'refs'; ## no critic
*$name = $code;
};
sub running {
my __PACKAGE__ $self = shift;
return $self->{total} - $self->{done} - $self->{broken};
};
sub alive {
my __PACKAGE__ $self = shift;
return $self->{total} - $self->{complete} - $self->{broken};
};
sub dead {
my __PACKAGE__ $self = shift;
return $self->{complete} + $self->{broken};
};
sub zombie {
my __PACKAGE__ $self = shift;
return $self->{done} - $self->{complete};
};
=head2 get_stat
Get all statistics as a single hashref.
=cut
sub get_stat {
my __PACKAGE__ $self = shift;
my %ret;
$ret{$_} = $self->{$_} for @values;
$ret{dead} = $ret{complete} + $ret{broken};
$ret{zombie} = $ret{done} - $ret{complete};
$ret{alive} = $ret{total} - $ret{dead};
$ret{running} = $ret{alive} - $ret{zombie};
return \%ret;
};
=head2 get_stat_result
Provide statistics on agruments provided to end() method.
=cut
sub get_stat_result {
my __PACKAGE__ $self = shift;
my %ret = %{ $self->{results} };
return \%ret;
};
=head2 get_stat_time
Return time statistics object, if any.
=cut
sub get_stat_time {
my __PACKAGE__ $self = shift;
return $self->{time_stat};
};
=head2 on_level( $n, CODEREF )
Set on_level callback. If $n is positive, run CODEREF->($n)
when number of running guard instances is increased to $n.
If $n is negative or 0, run CODEREF->($n) when it is decreased to $n.
CAVEAT: Normally, CODEREF should not die as it may be called within
a destructor.
=cut
sub on_level {
my __PACKAGE__ $self = shift;
my ($level, $code) = @_;
$self->{on_level}{$level} = $code;
return $self;
};
=head1 Overriding guard class
Custom guard classes may be used with Guard::Stats.
A guard_class supplied to new() must exhibit the following properties:
=over
=item * It must have a new() method, accepting a hash. C<owner>=object and
C<want_time>=0|1 parameters MUST be acceptable.
=item * The object returned by new() MUST have end(), is_done() and DESTROY()
methods.
( run in 1.352 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )