Algorithm-EventsPerSecond
view release on metacpan or search on metacpan
lib/Algorithm/EventsPerSecond/Sukkal.pm view on Meta::CPAN
=head1 DESCRIPTION
A sukkal is the vizier-messenger of a Mesopotamian court: petitioners
speak to it, and it relays word of them to the throne. This sukkal
listens on a unix stream socket, records events marked against
arbitrary client-chosen keys, and answers queries about their rates.
Each key gets its own L<Algorithm::EventsPerSecond> meter, so C<mark>
stays O(1) and memory per key is constant regardless of event volume.
The daemon is a single process driven by a non-blocking select loop;
no non-core modules are required. Marks arriving back-to-back on a
connection are coalesced per key and applied with a single C<mark($n)>
call, so the hot path is dominated by socket reads and line parsing,
not by the meters.
Keys that go idle longer than L</idle_timeout> are evicted by a
periodic sweep. Because the timeout is never shorter than the window,
an evicted key by definition has zero events inside the window, so
queries for it correctly read as zero; the only state lost is its
lifetime L</TOTAL>.
lib/Algorithm/EventsPerSecond/Sukkal.pm view on Meta::CPAN
$self->{started} = time();
$self->{self_meter} = Algorithm::EventsPerSecond->new( window => $self->{window} );
$self->{key_re} = qr/^[\x21-\x7E\x80-\xFF]{1,$self->{max_key_length}}$/;
return bless $self, $class;
} ## end sub new
=head2 run
Bind the socket and serve until L</stop> is called (typically from a
signal handler; signals interrupt the select and are honored
promptly). On return the socket file has been unlinked and all client
connections closed. Dies if the socket cannot be bound.
=cut
sub run {
my ($self) = @_;
die "already running\n" if $self->{running};
lib/Algorithm/EventsPerSecond/Sukkal.pm view on Meta::CPAN
my $rsel = $self->{rsel} = IO::Select->new( $self->{listener} );
my $wsel = $self->{wsel} = IO::Select->new;
$self->{running} = 1;
$self->{next_sweep} = time() + $self->{sweep_interval};
while ( $self->{running} ) {
my $timeout = $self->{next_sweep} - time();
$timeout = 0 if $timeout < 0;
my ( $r, $w ) = IO::Select->select( $rsel, $wsel, undef, $timeout );
for my $fh ( @{ $r || [] } ) {
if ( fileno($fh) == $self->{listener_fd} ) {
$self->_accept;
} else {
$self->_read_client($fh);
}
}
for my $fh ( @{ $w || [] } ) {
t/sukkal-lifecycle.t view on Meta::CPAN
# back and forth. MARK is fire-and-forget, so nothing to read yet.
for ( 1 .. 20 ) {
print $a "MARK alpha\n";
print $b "MARK beta 2\n";
}
# each connection pipelines a query then a PING; replies must come
# back on the asking socket, in the order that connection asked. A
# connection's own marks always precede its own query (same buffer,
# processed in order), so each sees its full count. Cross-connection
# ordering within a select pass is deliberately not relied on.
print $a "COUNT alpha\n";
print $a "PING\n";
print $b "COUNT beta\n";
print $b "PING\n";
is( read_line($a), 'OK 20', 'A: COUNT reflects its own 20 alpha marks' );
is( read_line($a), 'OK PONG', 'A: PING answered next, in order, on A' );
is( read_line($b), 'OK 40', 'B: COUNT reflects its own 40 beta marks' );
is( read_line($b), 'OK PONG', 'B: PING answered next, in order, on B' );
( run in 0.482 second using v1.01-cache-2.11-cpan-7fcb06a456a )