Algorithm-EventsPerSecond

 view release on metacpan or  search on metacpan

t/lib/AEPS_TestSuite.pm  view on Meta::CPAN

		$meter->mark(3)->mark(4);
		is( $meter->count, 7, 'window of 1 counts the current second' );
		advance(1);
		is( $meter->count, 0, 'window of 1 expires after one second' );
	}

	#
	# rate
	#
	{
		my $meter = Algorithm::EventsPerSecond->new( window => 10 );

		is( $meter->rate, 0, 'rate is 0 when no time has elapsed' );

		$meter->mark(10);
		advance(5);
		is( $meter->rate, 2, 'rate uses elapsed lifetime while younger than the window' );

		advance(15);    # lifetime 20s > window; only recent events count
		$meter->mark(30);
		is( $meter->rate, 3, 'rate averages over the full window once mature' );
	}

	#
	# reset
	#
	{
		my $meter = Algorithm::EventsPerSecond->new( window => 10 );

		$meter->mark(4);
		advance(2);
		$meter->mark(6);

		is( $meter->reset, $meter, 'reset returns the meter object' );
		is( $meter->count, 0,      'count is 0 after reset' );
		is( $meter->total, 0,      'total is 0 after reset' );
		is( $meter->rate,  0,      'rate is 0 after reset' );

		advance(4);
		$meter->mark(8);
		is( $meter->count, 8, 'meter is usable after reset' );
		is( $meter->rate,  2, 'rate clock restarts at reset' );
	}

	#
	# clock stepping backwards (an NTP step): must not crash, must not
	# produce negative readings, and must count correctly once the
	# clock recovers
	#
	{
		my $meter = Algorithm::EventsPerSecond->new( window => 5 );

		$meter->mark(3);
		advance(-11);       # -11 is not a multiple of the window, so this
		$meter->mark(2);    # mark lands in a different bucket than the one above

		ok( $meter->count >= 0, 'count is non-negative after the clock steps back' );
		is( $meter->total, 5, 'total is unaffected by the clock stepping back' );
		is( $meter->rate,  0, 'rate reads 0 while the clock is behind the start time' );

		advance(13);        # two seconds past where the clock originally was
		$meter->mark(4);
		is( $meter->count, 7, 'only in-window events counted once the clock recovers' );
		is( $meter->total, 9, 'total tracks marks made at every clock position' );

		advance(10);
		is( $meter->count, 0, 'everything ages out normally after recovery' );
		is( $meter->total, 9, 'total survives the aging out' );
	}

	#
	# randomized cross-check against an independent reference model.
	# Window sizes chosen to exercise the SIMD main loop, its scalar
	# tail, and tail-only runs (AVX2 does 4 buckets a step, SSE4.2 2);
	# 257 and 1024 add long main-loop runs on odd and even boundaries.
	#
	for my $window ( 1, 2, 3, 5, 8, 16, 33, 64, 257, 1024 ) {
		my $meter = Algorithm::EventsPerSecond->new( window => $window );
		my %events;    # second => count, the reference model
		my $total = 0;
		my $bad   = 0;

		for ( 1 .. 200 ) {
			advance( _rnd(4) );    # 0-3 seconds pass
			my $n = _rnd(5);       # 0-4 events
			if ($n) {
				$meter->mark($n);
				$events{$fake_time} += $n;
				$total += $n;
			}

			my $oldest   = $fake_time - $window + 1;
			my $expected = 0;
			for my $sec ( keys %events ) {
				$expected += $events{$sec} if $sec >= $oldest;
			}

			$bad++ if $meter->count != $expected;
			$bad++ if $meter->total != $total;
		} ## end for ( 1 .. 200 )

		is( $bad, 0, "randomized marks match reference model (window $window)" );
	} ## end for my $window ( 1, 2, 3, 5, 8, 16, 33, 64,...)

	return;
} ## end sub run_suite

1;



( run in 1.493 second using v1.01-cache-2.11-cpan-7fcb06a456a )