Algorithm-EventsPerSecond
view release on metacpan or search on metacpan
lib/Algorithm/EventsPerSecond.pm view on Meta::CPAN
if ( eval { require Algorithm::EventsPerSecond::XS; 1 } ) {
$BACKEND = 'XS';
}
}
}
=head1 SYNOPSIS
use Algorithm::EventsPerSecond;
my $meter = Algorithm::EventsPerSecond->new( window => 10 ); # 10-second window
while (my $event = get_next_event()) {
$meter->mark; # record one event
# $meter->mark(5); # or record several at once
printf "current rate: %.2f events/sec\n", $meter->rate;
}
print "events seen in window: ", $meter->count, "\n";
print "lifetime total: ", $meter->total, "\n";
=head1 DESCRIPTION
Algorithm::EventsPerSecond keeps per-second counts in a fixed-size ring buffer and
reports the average event rate over the most recent N seconds (the
"window"). Memory use is constant regardless of event volume, and both
C<mark> and C<rate> are O(1) averaged out over time.
=head1 METHODS
=head2 new( window => $seconds )
Construct a meter. C<window> is the length of the averaging window in
seconds and defaults to 60.
=cut
sub new {
my ( $class, %args ) = @_;
my $window = $args{window} // 60;
die "window must be a positive integer" unless $window =~ /^\d+$/ && $window > 0;
my $self = {
window => $window,
total => 0, # lifetime event count
started => time(),
};
if ( $BACKEND eq 'XS' ) {
# packed int64_t ring buffers, scanned in C
$self->{buckets} = "\0" x ( $window * 8 );
$self->{stamps} = "\0" x ( $window * 8 );
} else {
$self->{buckets} = [ (0) x $window ]; # counts, indexed by (epoch_sec % window)
$self->{stamps} = [ (0) x $window ]; # epoch second each bucket belongs to
}
return bless $self, $class;
} ## end sub new
# Internal, PP backend: get the bucket for the current second, clearing it if stale.
sub _bucket_index {
my ( $self, $now_sec ) = @_;
my $i = $now_sec % $self->{window};
if ( $self->{stamps}[$i] != $now_sec ) {
$self->{buckets}[$i] = 0;
$self->{stamps}[$i] = $now_sec;
}
return $i;
}
=head2 mark( [$count] )
Record one event, or C<$count> events. C<$count> must be a
non-negative integer; zero is a no-op. Anything else dies. Returns the
meter object, so calls can be chained.
=cut
sub _mark_pp {
my ( $self, $count ) = @_;
if ( defined $count ) {
die "count must be a non-negative integer" unless $count =~ /^\d+$/;
} else {
$count = 1;
}
my $now_sec = int( time() );
my $i = $self->_bucket_index($now_sec);
$self->{buckets}[$i] += $count;
$self->{total} += $count;
return $self;
} ## end sub _mark_pp
sub _mark_xs {
my ( $self, $count ) = @_;
if ( defined $count ) {
die "count must be a non-negative integer" unless $count =~ /^\d+$/;
} else {
$count = 1;
}
Algorithm::EventsPerSecond::XS::_xs_mark( $self->{buckets}, $self->{stamps},
$self->{window}, int( time() ), $count, );
$self->{total} += $count;
return $self;
} ## end sub _mark_xs
=head2 count
Number of events recorded within the current window.
=cut
sub _count_pp {
( run in 0.673 second using v1.01-cache-2.11-cpan-0b5f733616e )