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.925 second using v1.01-cache-2.11-cpan-7fcb06a456a )