Last active
October 30, 2019 20:57
-
-
Save tintoy/cb313dab12b4e70f0d78fcb0ad29e065 to your computer and use it in GitHub Desktop.
Windowed rate-counter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// A simple windowed rate counter. | |
/// </summary> | |
/// <remarks> | |
/// Tracks an event count over a specific (moving) period of time. Locking omitted for clarity. | |
/// </remarks> | |
public class RateCounter | |
{ | |
/// <summary> | |
/// Timestamps representing each event tracked by the counter. | |
/// </summary> | |
readonly Queue<DateTime> _timestamps = new Queue<DateTime>(); | |
/// <summary> | |
/// The maximum rate permitted by the counter. | |
/// </summary> | |
readonly int _maxRate; | |
/// <summary> | |
/// The period of time over which the rate is evaluated. | |
/// </summary> | |
readonly TimeSpan _period; | |
/// <summary> | |
/// Create a new <see cref="RateCounter"/>. | |
/// </summary> | |
/// <param name="maxRate"> | |
/// The maximum rate permitted by the counter. | |
/// </param> | |
/// <param name="period"> | |
/// The period of time over which the rate is evaluated. | |
/// </param> | |
public RateCounter(int maxRate, TimeSpan period) | |
{ | |
if (maxRate < 1) | |
throw new ArgumentOutOfRangeException(nameof(maxRate), maxRate, "Maximum rate cannot be less than 1."); | |
if (period <= TimeSpan.Zero) | |
throw new ArgumentOutOfRangeException(nameof(period), period, "Period must be a positive time span."); | |
_maxRate = maxRate; | |
_period = period; | |
} | |
/// <summary> | |
/// The period of time (window) over which the counter is evaluated. | |
/// </summary> | |
public TimeSpan Period => _period; | |
/// <summary> | |
/// The current rate measured by the counter. | |
/// </summary> | |
public int Rate => _timestamps.Count; | |
/// <summary> | |
/// The maximum rate permitted by the counter. | |
/// </summary> | |
public int MaxRate => _maxRate; | |
/// <summary> | |
/// Attempt to increment the rate by 1. | |
/// </summary> | |
/// <returns> | |
/// <c>false</c>, if the rate was not incremented because it is already at its maximum; otherwise, <c>true</c>. | |
/// </returns> | |
public bool Increment() | |
{ | |
DateTime now = DateTime.Now; | |
TrimToWindow(now); | |
// We only increment if there is "room" for the new value. | |
// Since this rate counter is used to reject requests that are above tenant-level quotas, | |
// we don't want to register a request if we'd just wind up rejecting it. | |
if (Rate < MaxRate) | |
{ | |
_timestamps.Enqueue(now); | |
return true; | |
} | |
return false; | |
} | |
/// <summary> | |
/// Trim the current set of timestamps, removing any that now lie outside the window represented by <see cref="Period"/>. | |
/// </summary> | |
/// <param name="now"> | |
/// A <see cref="DateTime"/> representing the current timestamp. | |
/// </param> | |
void TrimToWindow(DateTime now) | |
{ | |
while (_timestamps.TryPeek(out DateTime oldestTimestamp) && (now - oldestTimestamp) > Period) | |
_timestamps.Dequeue(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment