use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct LogSampler {
one_in_n: u64,
count: AtomicU64,
}
impl LogSampler {
pub fn one_in(n: u32) -> Self {
Self {
one_in_n: (n as u64).max(1),
count: AtomicU64::new(0),
}
}
pub fn unsampled() -> Self {
Self::one_in(1)
}
pub fn should_emit(&self) -> bool {
let n = self.count.fetch_add(1, Ordering::Relaxed);
n.is_multiple_of(self.one_in_n)
}
pub fn rate(&self) -> u64 {
self.one_in_n
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rate_one_emits_every_call() {
let s = LogSampler::one_in(1);
assert!((0..10).all(|_| s.should_emit()));
}
#[test]
fn zero_is_clamped_to_emit_everything() {
let s = LogSampler::one_in(0);
assert_eq!(s.rate(), 1);
assert!(s.should_emit());
}
#[test]
fn emits_first_then_every_nth() {
let s = LogSampler::one_in(4);
let pattern: Vec<bool> = (0..6).map(|_| s.should_emit()).collect();
assert_eq!(
pattern,
vec![true, false, false, false, true, false],
"first call emits, then every 4th"
);
}
#[test]
fn long_run_rate_is_one_in_n() {
let s = LogSampler::one_in(10);
let emitted = (0..1000).filter(|_| s.should_emit()).count();
assert_eq!(emitted, 100, "1000 calls at 1-in-10 → 100 emitted");
}
#[test]
fn unsampled_is_rate_one() {
assert_eq!(LogSampler::unsampled().rate(), 1);
}
}