#![cfg(feature = "hdr")]
use std::sync::RwLock;
#[derive(Debug)]
pub struct Histogram {
inner: RwLock<hdrhistogram::Histogram<u64>>, }
impl Default for Histogram {
fn default() -> Self {
Self::new()
}
}
impl Histogram {
#[inline]
pub fn new() -> Self {
let h = hdrhistogram::Histogram::new_with_bounds(1, 3_600_000_000_000u64, 3)
.unwrap_or_else(|e| {
debug_assert!(false, "HDR bounds init failed: {e}");
hdrhistogram::Histogram::new(3).unwrap_or_else(|_| {
hdrhistogram::Histogram::new_with_max(3_600_000_000_000u64, 3).unwrap()
})
});
Self {
inner: RwLock::new(h),
}
}
#[inline]
pub fn record(&self, value_ns: u64) {
let v = value_ns.clamp(1, 3_600_000_000_000u64);
if let Ok(mut h) = self.inner.write() {
let _ = h.record(v);
}
}
#[inline]
pub fn record_duration(&self, duration: core::time::Duration) {
let nanos = duration.as_nanos();
let v_u64 = if nanos > u128::from(u64::MAX) {
u64::MAX
} else {
u64::try_from(nanos).unwrap_or(u64::MAX)
};
self.record(v_u64);
}
#[inline]
pub fn min(&self) -> Option<u64> {
self.inner
.read()
.ok()
.and_then(|h| if h.is_empty() { None } else { Some(h.min()) })
}
#[inline]
pub fn max(&self) -> Option<u64> {
self.inner
.read()
.ok()
.and_then(|h| if h.is_empty() { None } else { Some(h.max()) })
}
#[inline]
pub fn mean(&self) -> Option<f64> {
self.inner
.read()
.ok()
.and_then(|h| if h.is_empty() { None } else { Some(h.mean()) })
}
#[inline]
pub fn count(&self) -> u64 {
self.inner.read().map(|h| h.len()).unwrap_or(0)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.count() == 0
}
#[inline]
pub fn percentile(&self, percentile: f64) -> Option<u64> {
let p = percentile.clamp(0.0, 1.0) * 100.0;
self.inner.read().ok().and_then(|h| {
if h.is_empty() {
None
} else {
Some(h.value_at_percentile(p))
}
})
}
#[inline]
pub fn median(&self) -> Option<u64> {
self.percentile(0.5)
}
#[inline]
pub fn median_duration(&self) -> Option<core::time::Duration> {
self.median().map(core::time::Duration::from_nanos)
}
#[inline]
pub fn percentile_duration(&self, p: f64) -> Option<core::time::Duration> {
self.percentile(p).map(core::time::Duration::from_nanos)
}
#[inline]
pub fn percentiles(&self, ps: &[f64]) -> Vec<Option<u64>> {
let Ok(guard) = self.inner.read() else {
return vec![None; ps.len()];
};
if guard.is_empty() {
return vec![None; ps.len()];
}
ps.iter()
.map(|&p| {
let pct = p.clamp(0.0, 1.0) * 100.0;
Some(guard.value_at_percentile(pct))
})
.collect()
}
#[inline]
pub fn reset(&self) {
if let Ok(mut h) = self.inner.write() {
h.reset();
}
}
}