Sliding-window request counters.
Time is cut into epochs of the window's duration. Each window has three
slots used round-robin: the current epoch, the previous one, and one being
cleared by Limen.State.Rotator. Clearing a whole slot replaces per-entry
TTL scans. Keys carry their epoch, so a slot that is cleared late can never
leak counts into a newer epoch.
The rate over the last window is estimated from the current and previous epochs, weighting the previous one by how much of it still overlaps the sliding window:
estimate = current + previous × (duration - elapsed) / durationA key holds one count (incr/4, count/4) or a row of several (add/5,
read/5). A row keeps counts about the same subject together: updating
any of them costs one table update and one lookup of the previous epoch,
and returns all of them.
Each slot counts up to :max_keys keys exactly, a row being one key. After
that, new keys are counted in the slot's Count-Min Sketch
(Limen.Sketch.CountMin), whose estimates may overcount but never
undercount.
Summary
Functions
Adds increments to the row of counts of key at now (milliseconds)
and returns the sliding estimate of every count, including them.
Like add/5, and also returns whether this update made the row's first
count positive in the current epoch. For a row whose first count is
requests, that is whether this is the key's first request of the epoch.
Returns the sliding estimate for key at now without counting.
Counts one event for key at now (milliseconds) and returns the sliding
estimate including it.
Returns the sliding estimates of the width counts in the row of key at
now without counting.
Clears the slot that the next epoch will use.
Returns the exact counts of the current epoch, largest first.
Types
@type window() :: Limen.State.window()
Functions
Adds increments to the row of counts of key at now (milliseconds)
and returns the sliding estimate of every count, including them.
increments has one non-negative integer per count, at least one of them
positive. A key's rows always have the same number of counts.
Like add/5, and also returns whether this update made the row's first
count positive in the current epoch. For a row whose first count is
requests, that is whether this is the key's first request of the epoch.
A row counted in the sketch, once the slot is full, always reports true:
the sketch cannot tell.
@spec count(Limen.Instance.t(), window(), term(), integer()) :: non_neg_integer()
Returns the sliding estimate for key at now without counting.
@spec incr(Limen.Instance.t(), window(), term(), integer()) :: non_neg_integer()
Counts one event for key at now (milliseconds) and returns the sliding
estimate including it.
@spec read(Limen.Instance.t(), window(), term(), pos_integer(), integer()) :: tuple()
Returns the sliding estimates of the width counts in the row of key at
now without counting.
@spec rotate(Limen.Instance.t(), window(), integer()) :: non_neg_integer()
Clears the slot that the next epoch will use.
Only entries older than epoch are removed, so running late (after the
next epoch started) never drops fresh counts.
@spec top( Limen.Instance.t(), window(), (term() -> boolean()), pos_integer(), integer(), pos_integer() ) :: [{term(), non_neg_integer()}]
Returns the exact counts of the current epoch, largest first.
Keys for which filter returns true are ranked by their count, and rows
by their count in column, from 1.
Scans the slot, so it is meant for dashboards, not for the request path. Keys counted in the sketch are not included.