PHPackages                             automattic/sliding-window-counter - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. automattic/sliding-window-counter

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

automattic/sliding-window-counter
=================================

Sliding window counter

0.6(5mo ago)33.7k2GPL-2.0-or-laterPHPPHP ^8.1CI passing

Since Jun 16Pushed 5mo ago3 watchersCompare

[ Source](https://github.com/Automattic/sliding-window-counter)[ Packagist](https://packagist.org/packages/automattic/sliding-window-counter)[ RSS](/packages/automattic-sliding-window-counter/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (6)Dependencies (10)Versions (8)Used By (0)

Sliding Window Counter
======================

[](#sliding-window-counter)

```
composer require automattic/sliding-window-counter

```

What's this all about?
----------------------

[](#whats-this-all-about)

We needed a way to have arbitrary short-lived time series. For example, to tackle a messaging spam problem, we would like to know if the number of statistically suspicious messages sent by users connecting from a specific range of IP addresses exceeds previously observed numbers.

We can use a time series built from logs or database entries to work around this. It requires having a log or a database in the first place, not to mention that calculating the necessary statistics on every request could be expensive. We could back the calculation with a cache, but why don't we use the in-memory cache in the first place? That's what we are going for here.

First, we need to solve the input side of the problem. To do that, we divide the time into equal chunks and record events falling into matching periods. And we have to include the observation period and the window size in the cache key to ensure we don't accidentally reuse numbers from another time series. Straightforward as it is.

The slightly more complicated part is to assemble everything back. Let's agree on the following terminology:

- A frame is a window-long period of time.
- The *material frame* is a frame that matches the data stored in the cache to the last second.
- The *logical frame* is a window-long frame aligned to the current time but not necessarily aligned to the cache data.

It is all straightforward if the current time is aligned with the material frame boundary. Take what you have in the cache, and presto!

[![Logical Frames Aligned](docs/images/logical-frames-aligned.png)](docs/images/logical-frames-aligned.png)

But if logical and material frames are not aligned (the end of the last material frame could be twenty seconds ago, and the logical frame is always now), we are up for some extrapolation.

[![Logical Frames Not Aligned](docs/images/logical-frames-disaligned.png)](docs/images/logical-frames-disaligned.png)

- To extrapolate the value for the most recent logical frame, we can take a portion of the previous material frame and the full value of the most recent material frame. [This post on Cloudflare's blog explains it beautifully.](https://blog.cloudflare.com/counting-things-a-lot-of-different-things/)
- And we use portions of underlying material frames for every other logical frame.

And there are many edge cases: for example, if we don't specify the anomaly detection start time, we discard all heading null values. But for relatively rare events, it might make sense to keep every empty frame, so there's an option to specify the start time.

The API
-------

[](#the-api)

Here's how it should be used:

```
// Configure a counter to work in hourly buckets, for the last 24 hours
// and using the Memcached adapter.
$counter = new \Automattic\SlidingWindowCounter\SlidingWindowCounter(
    'my-counters',
    3600,
    3600 * 24,
    new \Automattic\SlidingWindowCounter\Cache\MemcachedAdapter($memcached)
);

// Increment the counter when a certain event happens.
$counter->increment($_SERVER['REMOTE_ADDR']);
```

And at some later point in time

```
// Try to detect an anomaly.
$anomaly_result = $counter->detectAnomaly($_SERVER['REMOTE_ADDR']);
if ($anomaly_result->isAnomaly()) {
    // Inspect the result using the toArray() method.
    $anomaly_result->toArray();

    // Or individual accessors.
    $anomaly_result->getMean();
    $anomaly_result->getStandardDeviation();
    // ...
}

// And explore the historic variance...
$variance = $counter->getHistoricVariance($_SERVER['REMOTE_ADDR']);
// ...using various accessors.
$variance->getCount();
$variance->getMean();
$variance->getStandardDeviation();
```

There's also a handy adapter for the Memcached-backed `WP_Object_Cache`:

```
$counter = new \Automattic\SlidingWindowCounter\SlidingWindowCounter(
    'my-counters',
    3600,
    3600 * 24,
    new \Automattic\SlidingWindowCounter\Cache\WPCacheAdapter($wp_object_cache)
);
```

###  Health Score

41

—

FairBetter than 89% of packages

Maintenance70

Regular maintenance activity

Popularity23

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 93.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~179 days

Recently: every ~224 days

Total

6

Last Release

168d ago

PHP version history (2 changes)0.1PHP ^7.4 || ^8.0

0.4PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c5869ecbb8e0eac7e8b8e0f3cf7bdd8d5fcdc4abc10a72281872c53f8639d44?d=identicon)[automattic](/maintainers/automattic)

---

Top Contributors

[![sanmai](https://avatars.githubusercontent.com/u/139488?v=4)](https://github.com/sanmai "sanmai (14 commits)")[![lengare](https://avatars.githubusercontent.com/u/25574979?v=4)](https://github.com/lengare "lengare (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Psalm

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/automattic-sliding-window-counter/health.svg)

```
[![Health](https://phpackages.com/badges/automattic-sliding-window-counter/health.svg)](https://phpackages.com/packages/automattic-sliding-window-counter)
```

###  Alternatives

[sylvainjule/embed

Embed field for Kirby

7812.8k](/packages/sylvainjule-embed)[laravel-frontend-presets/paper

Laravel 10.x Front-end preset for paper dashboard

5018.0k](/packages/laravel-frontend-presets-paper)[nuxtifyts/dash-stack-theme

DashStack theme for filament php

1914.0k](/packages/nuxtifyts-dash-stack-theme)[phpviet/number-to-words

Thư viện hổ trợ chuyển đổi số sang chữ số.

219.1k5](/packages/phpviet-number-to-words)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
