Time-windowed rate tracker with configurable resolution
gem install philiprehberger-rate_windowTime-windowed rate tracker with configurable resolution
Add to your Gemfile:
gem "philiprehberger-rate_window"
Or install directly:
gem install philiprehberger-rate_window
require "philiprehberger/rate_window"
tracker = Philiprehberger::RateWindow.new(window: 60, resolution: 1)
tracker.record(1)
tracker.record(5)
tracker.record(3)
tracker.rate # => events per second over the window
tracker.sum # => 9.0
tracker.count # => 3
tracker.average # => 3.0
tracker = Philiprehberger::RateWindow.new(window: 60, resolution: 1)
100.times { |i| tracker.record(i) }
tracker.percentile(50) # => median value (with linear interpolation)
tracker.percentile(95) # => 95th percentile
tracker.percentile(99) # => 99th percentile
tracker.median # => shortcut for percentile(50)
tracker.p95 # => shortcut for percentile(95)
Compute several quantiles together (values are sorted only once per call):
tracker.quantiles(0.25, 0.5, 0.75, 0.95)
# => { 0.25 => 4.0, 0.5 => 7.5, 0.75 => 10.0, 0.95 => 18.2 }
Fractions must be between 0.0 and 1.0 inclusive. An empty tracker returns 0.0 for each requested fraction.
tracker = Philiprehberger::RateWindow.new(window: 60, resolution: 1)
tracker.record(5)
tracker.record(20)
tracker.record(3)
tracker.min # => 3.0
tracker.max # => 20.0
tracker = Philiprehberger::RateWindow.new(window: 60, resolution: 0.001)
[2, 4, 4, 4, 5, 5, 7, 9].each do |v|
tracker.record(v)
sleep(0.002)
end
tracker.variance # => 4.0 (population variance)
tracker.stddev # => 2.0 (population standard deviation)
An empty tracker returns 0.0 for both. A single value also returns 0.0.
tracker = Philiprehberger::RateWindow.new(window: 60, resolution: 1)
100.times { |i| tracker.record(i) }
tracker.histogram(buckets: 5)
# => [
# { range: 0.0..20.0, count: ... },
# { range: 20.0..40.0, count: ... },
# ...
# ]
# 5-minute window with 10-second buckets
tracker = Philiprehberger::RateWindow.new(window: 300, resolution: 10)
tracker.record(42)
tracker.rate # => rate per second over 5 minutes
Get all stats atomically in a single call (one mutex acquisition, one cleanup pass):
tracker = Philiprehberger::RateWindow.new(window: 60, resolution: 1)
tracker.record(10)
tracker.record(20)
tracker.record(30)
tracker.snapshot
# => {
# sum: 60.0,
# count: 3,
# rate: 1.0,
# average: 20.0,
# min: 10.0,
# max: 30.0,
# median: 20.0,
# p95: 28.0,
# variance: 66.666...,
# stddev: 8.164...
# }
All values reflect the same instant. An empty tracker returns 0.0 for all numeric fields and 0 for count.
tracker.reset
tracker.sum # => 0.0
tracker.count # => 0
#snapshot_and_reset returns the current snapshot hash and clears all
buckets in a single mutex acquisition, so no samples can slip in between
the read and the clear. This is the standard pattern for periodic metric
exporters that want to capture-and-clear without losing samples between
two separate calls:
# Periodic metric exporter pattern
loop do
stats = tracker.snapshot_and_reset
publish_metrics(stats)
sleep 60
end
| Method | Description |
|---|---|
.new(window:, resolution:) | Create a tracker with window (seconds) and bucket resolution |
#record(value = 1) | Record a value in the current time bucket |
#rate | Calculate rate per second over the window |
#sum | Sum of all values in the window |
#count | Number of recordings in the window |
#average | Average value per recording |
#percentile(p) | Calculate percentile (0-100) with linear interpolation |
#median | Shortcut for percentile(50) |
#p95 | Shortcut for percentile(95) |
#quantiles(*fractions) | Hash mapping each fraction (0.0–1.0) to its percentile value in one pass |
#min | Minimum recorded value in the window |
#max | Maximum recorded value in the window |
#variance | Population variance of values in the window |
#stddev | Population standard deviation of values in the window |
#histogram(buckets: 10) | Value distribution as array of { range:, count: } hashes |
#snapshot | Atomic hash of all stats: sum, count, rate, average, min, max, median, p95, variance, stddev |
#snapshot_and_reset | Returns the current snapshot and atomically resets the tracker |
#reset | Clear all recorded data |
bundle install
bundle exec rspec
bundle exec rubocop
If you find this project useful: