use crate::{Duration, Measurement};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Stats {
pub count: u64,
pub total: Duration,
pub min: Duration,
pub max: Duration,
pub mean: Duration,
}
#[derive(Clone, Debug)]
pub struct Collector {
measurements: Arc<RwLock<HashMap<&'static str, Vec<Duration>>>>,
}
impl Collector {
pub fn new() -> Self {
Self {
measurements: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
measurements: Arc::new(RwLock::new(HashMap::with_capacity(capacity))),
}
}
pub fn record(&self, measurement: &Measurement) {
let mut lock = self
.measurements
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
lock.entry(measurement.name)
.or_default()
.push(measurement.duration);
}
pub fn record_duration(&self, name: &'static str, duration: Duration) {
let mut lock = self
.measurements
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
lock.entry(name).or_default().push(duration);
}
pub fn stats(&self, name: &str) -> Option<Stats> {
let durations: Vec<Duration> = {
let lock = self
.measurements
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
lock.get(name)?.clone()
};
if durations.is_empty() {
return None;
}
let mut iter = durations.iter().copied();
let first = iter.next()?;
let mut total: u128 = first.as_nanos();
let mut min = first;
let mut max = first;
for d in iter {
let n = d.as_nanos();
total = total.saturating_add(n);
if d < min {
min = d;
}
if d > max {
max = d;
}
}
let count = durations.len() as u64;
let mean = Duration::from_nanos(total / u128::from(count));
Some(Stats {
count,
total: Duration::from_nanos(total),
min,
max,
mean,
})
}
pub fn all_stats(&self) -> Vec<(String, Stats)> {
let snapshot: Vec<(&'static str, Vec<Duration>)> = {
let lock = self
.measurements
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
lock.iter().map(|(&name, v)| (name, v.clone())).collect()
};
let mut out = Vec::with_capacity(snapshot.len());
for (name, durations) in snapshot {
if durations.is_empty() {
continue;
}
let mut iter = durations.iter().copied();
if let Some(first) = iter.next() {
let mut total: u128 = first.as_nanos();
let mut min = first;
let mut max = first;
for d in iter {
let n = d.as_nanos();
total = total.saturating_add(n);
if d < min {
min = d;
}
if d > max {
max = d;
}
}
let count = durations.len() as u64;
let mean = Duration::from_nanos(total / u128::from(count));
out.push((
name.to_string(),
Stats {
count,
total: Duration::from_nanos(total),
min,
max,
mean,
},
));
}
}
out
}
pub fn clear(&self) {
let mut lock = self
.measurements
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
lock.clear();
}
pub fn clear_name(&self, name: &str) {
let mut lock = self
.measurements
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
lock.remove(name);
}
}
impl Default for Collector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collector_basic() {
let collector = Collector::new();
collector.record_duration("test", Duration::from_nanos(1000));
collector.record_duration("test", Duration::from_nanos(2000));
collector.record_duration("test", Duration::from_nanos(3000));
let stats = collector.stats("test").unwrap();
assert_eq!(stats.count, 3);
assert_eq!(stats.total.as_nanos(), 6000);
assert_eq!(stats.min.as_nanos(), 1000);
assert_eq!(stats.max.as_nanos(), 3000);
assert_eq!(stats.mean.as_nanos(), 2000);
}
#[test]
fn test_collector_multiple_names() {
let collector = Collector::new();
collector.record_duration("foo", Duration::from_nanos(100));
collector.record_duration("bar", Duration::from_nanos(200));
assert!(collector.stats("foo").is_some());
assert!(collector.stats("bar").is_some());
assert!(collector.stats("baz").is_none());
let all = collector.all_stats();
assert_eq!(all.len(), 2);
}
#[test]
fn test_collector_thread_safety() {
use std::thread;
let collector = Arc::new(Collector::new());
let mut handles = vec![];
for i in 0u64..10 {
let c = Arc::clone(&collector);
let handle = thread::spawn(move || {
for j in 0u64..10 {
c.record_duration("test", Duration::from_nanos(u128::from(i * 10 + j)));
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let stats = collector.stats("test").unwrap();
assert_eq!(stats.count, 100);
}
#[test]
fn test_collector_clear() {
let collector = Collector::new();
collector.record_duration("test", Duration::from_nanos(1000));
assert!(collector.stats("test").is_some());
collector.clear();
assert!(collector.stats("test").is_none());
}
}