use std::collections::VecDeque;
pub const RATE_SAMPLE_CAPACITY: usize = 4096;
#[derive(Debug, Clone)]
pub struct RateWindow {
window_ms: u64,
samples: VecDeque<(u64, u64)>,
}
impl RateWindow {
pub fn new(window_ms: u64) -> Self {
RateWindow {
window_ms: window_ms.max(1),
samples: VecDeque::new(),
}
}
pub fn record(&mut self, at_ms: u64, tokens: u64) {
if tokens == 0 {
return;
}
if self.samples.len() == RATE_SAMPLE_CAPACITY {
self.samples.pop_front();
}
self.samples.push_back((at_ms, tokens));
}
pub fn tokens_per_second(&mut self, now_ms: u64) -> f64 {
self.evict_before(now_ms.saturating_sub(self.window_ms));
let Some((oldest, _)) = self.samples.front().copied() else {
return 0.0;
};
let tokens: u64 = self.samples.iter().map(|(_, t)| *t).sum();
let span_ms = now_ms.saturating_sub(oldest).max(1);
tokens as f64 * 1000.0 / span_ms as f64
}
fn evict_before(&mut self, cutoff: u64) {
while self.samples.front().is_some_and(|(at, _)| *at < cutoff) {
self.samples.pop_front();
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LastKnown<T: Copy> {
value: Option<T>,
}
impl<T: Copy> LastKnown<T> {
pub fn get(&self) -> Option<T> {
self.value
}
pub fn set(&mut self, value: T) {
self.value = Some(value);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_rate_is_correct_before_the_window_has_filled() {
let mut window = RateWindow::new(5_000);
for step in 0..10u64 {
window.record(step * 100, 5);
}
assert_eq!(window.tokens_per_second(1_000), 50.0);
}
#[test]
fn an_idle_server_reports_zero_rather_than_its_busiest_minute() {
let mut window = RateWindow::new(1_000);
for step in 0..10u64 {
window.record(step * 100, 10);
}
assert!(window.tokens_per_second(900) > 0.0);
assert_eq!(window.tokens_per_second(3_600_000), 0.0);
}
#[test]
fn a_rate_window_holds_a_bounded_number_of_samples_even_unpolled() {
let mut window = RateWindow::new(1_000);
for step in 0..(RATE_SAMPLE_CAPACITY as u64 + 500) {
window.record(step, 1);
}
assert_eq!(window.samples.len(), RATE_SAMPLE_CAPACITY);
}
}