#![cfg(all(feature = "std", feature = "metrics"))]
use core::marker::PhantomData;
#[cfg(feature = "parking-lot-locks")]
use parking_lot::RwLock;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
#[cfg(not(feature = "parking-lot-locks"))]
use std::sync::RwLock;
use std::time::Instant;
use crate::hist_backend::HistBackend;
#[cfg(feature = "trace")]
use crate::trace;
#[cfg(feature = "hdr")]
type Backend = crate::hist_hdr::Histogram;
#[cfg(not(feature = "hdr"))]
type Backend = crate::histogram::FastHistogram;
#[cfg(feature = "parking-lot-locks")]
type ReadGuard<'a, B> = parking_lot::RwLockReadGuard<'a, HashMap<Arc<str>, Arc<B>>>;
#[cfg(not(feature = "parking-lot-locks"))]
type ReadGuard<'a, B> = std::sync::RwLockReadGuard<'a, HashMap<Arc<str>, Arc<B>>>;
#[cfg(feature = "parking-lot-locks")]
type WriteGuard<'a, B> = parking_lot::RwLockWriteGuard<'a, HashMap<Arc<str>, Arc<B>>>;
#[cfg(not(feature = "parking-lot-locks"))]
type WriteGuard<'a, B> = std::sync::RwLockWriteGuard<'a, HashMap<Arc<str>, Arc<B>>>;
const DEFAULT_LOWEST: u64 = 1;
const DEFAULT_HIGHEST: u64 = 3_600_000_000_000;
pub struct WatchGeneric<B: HistBackend> {
inner: Arc<Inner<B>>,
}
pub type Watch = WatchGeneric<Backend>;
impl<B: HistBackend> Default for WatchGeneric<B> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<B: HistBackend> Clone for WatchGeneric<B> {
#[inline]
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
struct Inner<B: HistBackend> {
hist: RwLock<HashMap<Arc<str>, Arc<B>>>,
lowest: u64,
highest: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct WatchStats {
pub count: u64,
pub min: u64,
pub max: u64,
pub p50: u64,
pub p90: u64,
pub p95: u64,
pub p99: u64,
pub p999: u64,
pub mean: f64,
}
impl<B: HistBackend> fmt::Debug for WatchGeneric<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let len = self.read_hist().len();
f.debug_struct("Watch").field("metrics_len", &len).finish()
}
}
impl<B: HistBackend> WatchGeneric<B> {
#[cfg(feature = "parking-lot-locks")]
#[inline]
fn read_hist(&self) -> ReadGuard<'_, B> {
self.inner.hist.read()
}
#[cfg(not(feature = "parking-lot-locks"))]
#[inline]
fn read_hist(&self) -> ReadGuard<'_, B> {
self.inner
.hist
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(feature = "parking-lot-locks")]
#[inline]
fn write_hist(&self) -> WriteGuard<'_, B> {
self.inner.hist.write()
}
#[cfg(not(feature = "parking-lot-locks"))]
#[inline]
fn write_hist(&self) -> WriteGuard<'_, B> {
self.inner
.hist
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub fn new() -> Self {
Self::with_bounds(DEFAULT_LOWEST, DEFAULT_HIGHEST)
}
#[inline]
pub fn builder() -> WatchBuilderGeneric<B> {
WatchBuilderGeneric::new()
}
pub fn with_bounds(lowest_discernible: u64, highest_trackable: u64) -> Self {
let lowest = lowest_discernible.max(1);
let highest = highest_trackable.max(lowest + 1);
Self {
inner: Arc::new(Inner {
hist: RwLock::new(HashMap::new()),
lowest,
highest,
}),
}
}
pub fn record(&self, name: &str, duration_ns: u64) {
let ns = duration_ns.clamp(self.inner.lowest, self.inner.highest);
let existing: Option<Arc<B>> = {
let map = self.read_hist();
map.get(name).cloned()
};
if let Some(h) = existing {
h.record(ns);
#[cfg(feature = "trace")]
trace::record_event(name, ns);
return;
}
let mut map = self.write_hist();
let key: Arc<str> = Arc::<str>::from(name);
let h = map.entry(key).or_insert_with(|| Arc::new(B::new())).clone();
h.record(ns);
#[cfg(feature = "trace")]
trace::record_event(name, ns);
}
pub fn record_instant(&self, name: &str, start: Instant) -> u64 {
let ns_u128 = start.elapsed().as_nanos();
let ns_u64 = if ns_u128 > u128::from(u64::MAX) {
u64::MAX
} else {
u64::try_from(ns_u128).unwrap_or(u64::MAX)
};
self.record(name, ns_u64);
ns_u64
}
pub fn snapshot(&self) -> HashMap<String, WatchStats> {
let items: Vec<(Arc<str>, Arc<B>)> = {
let map = self.read_hist();
map.iter()
.map(|(k, v)| (Arc::clone(k), Arc::clone(v)))
.collect()
};
let mut out = HashMap::with_capacity(items.len());
for (name, h) in items {
let count = h.count();
if count == 0 {
out.insert(
name.to_string(),
WatchStats {
count: 0,
min: 0,
max: 0,
p50: 0,
p90: 0,
p95: 0,
p99: 0,
p999: 0,
mean: 0.0,
},
);
continue;
}
let min = h.min().unwrap_or(0);
let max = h.max().unwrap_or(0);
let p50 = h.percentile(0.50).unwrap_or(min);
let p90 = h.percentile(0.90).unwrap_or(max);
let p95 = h.percentile(0.95).unwrap_or(max);
let p99 = h.percentile(0.99).unwrap_or(max);
let p999 = h.percentile(0.999).unwrap_or(max);
let mean = h.mean().unwrap_or(0.0);
out.insert(
name.to_string(),
WatchStats {
count,
min,
max,
p50,
p90,
p95,
p99,
p999,
mean,
},
);
}
out
}
pub fn clear(&self) {
let mut map = self.write_hist();
map.clear();
}
pub fn clear_name(&self, name: &str) {
let mut map = self.write_hist();
map.remove(name);
}
}
#[derive(Debug, Clone, Copy)]
pub struct WatchBuilderGeneric<B: HistBackend> {
lowest: u64,
highest: u64,
_marker: PhantomData<B>,
}
pub type WatchBuilder = WatchBuilderGeneric<Backend>;
impl<B: HistBackend> Default for WatchBuilderGeneric<B> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<B: HistBackend> WatchBuilderGeneric<B> {
#[inline]
pub fn new() -> Self {
Self {
lowest: DEFAULT_LOWEST,
highest: DEFAULT_HIGHEST,
_marker: PhantomData,
}
}
#[inline]
#[must_use]
pub fn lowest(mut self, ns: u64) -> Self {
self.lowest = ns.max(1);
self
}
#[inline]
#[must_use]
pub fn highest(mut self, ns: u64) -> Self {
self.highest = ns;
self
}
#[inline]
pub fn build(self) -> WatchGeneric<B> {
let lowest = self.lowest.max(1);
let highest = self.highest.max(lowest + 1);
WatchGeneric::<B>::with_bounds(lowest, highest)
}
}