use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
const DEFAULT_CAP: usize = 4096;
fn env_cap() -> usize {
std::env::var("CAPTRACK_SAMPLE_CAP")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.filter(|&n| n > 0)
.unwrap_or(DEFAULT_CAP)
}
pub struct Reservoir {
buf: Mutex<Vec<usize>>,
pub seen_count: AtomicU64,
cap: usize,
}
impl Reservoir {
pub fn new() -> Self {
let cap = env_cap();
Self {
buf: Mutex::new(Vec::with_capacity(cap.min(64))),
seen_count: AtomicU64::new(0),
cap,
}
}
pub fn record(&self, value: usize) {
let seen = self.seen_count.fetch_add(1, Ordering::Relaxed);
let mut buf = self.buf.lock().unwrap_or_else(|p| p.into_inner());
if seen < self.cap as u64 {
buf.push(value);
} else {
let j = fastrand::u64(0..=seen);
if (j as usize) < self.cap {
buf[j as usize] = value;
}
}
}
pub fn snapshot(&self) -> Vec<usize> {
let buf = self.buf.lock().unwrap_or_else(|p| p.into_inner());
buf.clone()
}
pub fn total_observed(&self) -> u64 {
self.seen_count.load(Ordering::Relaxed)
}
}
impl Default for Reservoir {
fn default() -> Self {
Self::new()
}
}