use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
pub struct ThrottledLog {
start: Instant,
next_log_ms: AtomicU64,
throttle_ms: u64,
}
fn u64_millis(d: Duration) -> u64 {
let ms: u128 = d.as_millis();
u64::try_from(ms).unwrap_or(u64::MAX)
}
impl ThrottledLog {
#[must_use]
pub fn new(throttle: Duration) -> Self {
Self {
start: Instant::now(),
next_log_ms: AtomicU64::new(0),
throttle_ms: u64_millis(throttle),
}
}
pub fn should_log(&self) -> bool {
let now_ms = u64_millis(self.start.elapsed());
let next = self.next_log_ms.load(Ordering::Relaxed);
if now_ms < next {
return false;
}
let new_next = now_ms.saturating_add(self.throttle_ms);
self.next_log_ms
.compare_exchange(next, new_next, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_call_returns_true() {
let throttle = ThrottledLog::new(Duration::from_secs(10));
assert!(throttle.should_log());
}
#[test]
fn second_call_within_interval_returns_false() {
let throttle = ThrottledLog::new(Duration::from_secs(10));
assert!(throttle.should_log());
assert!(!throttle.should_log());
}
}