Algorithm::EventsPerSecond

A sliding-window events-per-second rate counter for Perl, with an optional C/SIMD-accelerated backend and an automatic pure-Perl fallback.

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 mark and rate are O(1) averaged out over time.

For extra zoomies XS acceleration is available and SIMD if available.

Synopsis

use Algorithm::EventsPerSecond;

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

while (my $event = get_next_event()) {
    # record one event
    $meter->mark;

	# or record several at once
    #$meter->mark(5);

    printf "current rate: %.2f events/sec\n", $meter->rate;
}

print "events seen in window: ", $meter->count, "\n";
print "lifetime total:        ", $meter->total, "\n";

The iqbi-damiq daemon

The dist ships iqbi-damiq, a unix-socket daemon built on Algorithm::EventsPerSecond::Sukkal. Clients mark events against keys of their choosing and query per-key rates over a simple line protocol; each key gets its own meter, idle keys are evicted automatically, and marks are coalesced so the hot path is socket I/O, not the meters.

iqbi-damiq -s /var/run/iqbi-damiq.sock -w 60

printf 'MARK requests 5\nRATE requests\nQUIT\n' \
    | socat - UNIX:/var/run/iqbi-damiq.sock

# or MARKRATE to mark and read the rate back in a single command
printf 'MARKRATE requests 5\nQUIT\n' \
    | socat - UNIX:/var/run/iqbi-damiq.sock

Memory is bounded by max_keys and the window: each key owns one meter of two ring buffers with a slot per window second, so worst case is max_keys * bytes_per_key, where per key is roughly 16 * window + 800 bytes on the XS backend and 48 * window + 2000 bytes pure-Perl — about 170 MB (XS) or 490 MB (PP) at the defaults of a 60 second window and 100000 keys. Idle keys are evicted, so the worst case needs that many distinct keys live at once.

See perldoc Algorithm::EventsPerSecond::Sukkal for the protocol and memory-sizing details, and iqbi-damiq --help for options. Example startup scripts ship in rc/: a FreeBSD rc.d script (rc/freebsd/iqbi_damiq) and a systemd unit (rc/systemd/iqbi-damiq.service).

Installation

The module builds with the standard Perl toolchain. The XS backend is optional: without a working compiler (or with PUREPERL_ONLY=1) it installs as pure Perl and falls back automatically.

From source

perl Makefile.PL
make
make test
make install        # may need sudo, depending on your Perl

Build-time controls

The XS backend is compiled during perl Makefile.PL && make, so these take effect at install time:

| Control | Effect | |--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | IF_OPT | The -O optimization level for the XS backend. IF_OPT=2 (or IF_OPT=-O2) compiles with -O2. Default is -O3. | | IF_ARCH | Target architecture. IF_ARCH=native (or IF_ARCH=-march=native) compiles with -march=native, unlocking whatever SIMD the build host supports. Unset leaves the compiler's baseline. | | PUREPERL_ONLY=1 | Passed to Makefile.PL; skips building the XS backend entirely. | | ALGORITHM_EVENTSPERSECOND_PP | Runtime environment variable; when true, skips the XS backend and uses pure Perl. |

Example — build a machine-tuned SIMD backend:

IF_ARCH=native IF_OPT=3 perl Makefile.PL
make && make test && make install

Example — force a pure-Perl install (no compiler needed):

perl Makefile.PL PUREPERL_ONLY=1
make && make test && make install

Debian

Install a compiler and the Perl build tools, then build as above:

sudo apt-get install build-essential perl cpanminus
cpanm Algorithm::EventsPerSecond

FreeBSD

Install Perl and a CPAN client from packages, then build from source:

pkg install p5-App-cpanminus
cpanm Algorithm::EventsPerSecond

Acceleration

If a working C compiler is available at install time, the XS backend (Algorithm::EventsPerSecond::XS) is built and loaded automatically. It keeps the ring buffer in packed int64_t buffers and scans the window in C, using SIMD (AVX2 or SSE4.2) when the compiler targets a CPU that has it. When the backend cannot be loaded for any reason, the pure-Perl implementation is used instead.

Which backend is active, and its SIMD flavor, can be checked at runtime:

use Algorithm::EventsPerSecond;
print Algorithm::EventsPerSecond->backend, "\n";   # XS or PP
print Algorithm::EventsPerSecond->new->simd, "\n"; # AVX2 / SSE4.2 / scalar

For comparing the two, see benchmark.pl.