Class: Philiprehberger::RateCounter::Counter

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/rate_counter.rb

Overview

A sliding-window rate counter for measuring event throughput

Examples:

counter = Counter.new(window: 60)
counter.increment
counter.rate  # => events per second

Instance Method Summary collapse

Constructor Details

#initialize(window: 60) ⇒ Counter

Create a new rate counter

Parameters:

  • window (Numeric) (defaults to: 60)

    the sliding window size in seconds

Raises:

  • (Error)

    if window is not positive



27
28
29
30
31
32
33
34
# File 'lib/philiprehberger/rate_counter.rb', line 27

def initialize(window: 60)
  raise Error, 'Window must be positive' unless window.is_a?(Numeric) && window.positive?

  @window = window
  @buckets = []
  @peak_rate = 0.0
  @mutex = Mutex.new
end

Instance Method Details

#busy?(threshold:, unit: :second) ⇒ Boolean

True when the current rate exceeds threshold.

Useful for back-pressure decisions and threshold-based alerts. Returns false when no events have been recorded yet.

Parameters:

  • threshold (Numeric)

    the rate threshold to compare against

  • unit (Symbol) (defaults to: :second)

    :second, :minute, or :hour (default :second)

Returns:

  • (Boolean)

    true when current rate is strictly greater than threshold

Raises:

  • (Error)

    if unit is unknown



129
130
131
# File 'lib/philiprehberger/rate_counter.rb', line 129

def busy?(threshold:, unit: :second)
  rate_per(unit) > threshold
end

#countInteger

Get the current event count within the window

Returns:

  • (Integer)

    total events in the current window



53
54
55
56
57
58
# File 'lib/philiprehberger/rate_counter.rb', line 53

def count
  @mutex.synchronize do
    prune
    count_internal
  end
end

#increment(n = 1) ⇒ self

Increment the counter

Parameters:

  • n (Integer) (defaults to: 1)

    number of events to record (default: 1)

Returns:

  • (self)


40
41
42
43
44
45
46
47
48
# File 'lib/philiprehberger/rate_counter.rb', line 40

def increment(n = 1)
  @mutex.synchronize do
    @buckets << [now, n]
    prune
    current_rate = rate_internal
    @peak_rate = current_rate if current_rate > @peak_rate
  end
  self
end

#peak_rateFloat

Get the highest rate per second observed since creation or last reset

Returns:

  • (Float)

    peak events per second



73
74
75
# File 'lib/philiprehberger/rate_counter.rb', line 73

def peak_rate
  @mutex.synchronize { @peak_rate }
end

#rateFloat

Get the rate of events per second

Returns:

  • (Float)

    events per second



63
64
65
66
67
68
# File 'lib/philiprehberger/rate_counter.rb', line 63

def rate
  @mutex.synchronize do
    prune
    rate_internal
  end
end

#rate_per(unit) ⇒ Float

Get the rate projected to a specific time unit

Parameters:

  • unit (Symbol)

    :second, :minute, or :hour

Returns:

  • (Float)

    events per unit

Raises:

  • (Error)

    if unit is unknown



113
114
115
116
117
118
# File 'lib/philiprehberger/rate_counter.rb', line 113

def rate_per(unit)
  multiplier = RATE_UNITS[unit]
  raise Error, "Unknown unit: #{unit}. Use :second, :minute, or :hour" unless multiplier

  rate * multiplier
end

#resetvoid

This method returns an undefined value.

Reset the counter



136
137
138
139
140
141
# File 'lib/philiprehberger/rate_counter.rb', line 136

def reset
  @mutex.synchronize do
    @buckets.clear
    @peak_rate = 0.0
  end
end

#snapshotHash

Return a frozen snapshot of the counter state

Returns:

  • (Hash)

    frozen hash with count, rate, peak_rate, window, and timestamp



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/philiprehberger/rate_counter.rb', line 80

def snapshot
  @mutex.synchronize do
    prune
    {
      count: count_internal,
      rate: rate_internal,
      peak_rate: @peak_rate,
      window: @window,
      timestamp: Process.clock_gettime(Process::CLOCK_MONOTONIC)
    }.freeze
  end
end

#time_since_lastFloat?

Get the number of seconds since the most recent increment

Returns nil if the counter has never been incremented or if all events have fully expired from the sliding window.

Returns:

  • (Float, nil)

    seconds since the last increment, or nil if the window is empty



99
100
101
102
103
104
105
106
# File 'lib/philiprehberger/rate_counter.rb', line 99

def time_since_last
  @mutex.synchronize do
    prune
    return nil if @buckets.empty?

    now - @buckets.last[0]
  end
end