use hdrhistogram::errors::CreationError;
use hdrhistogram::Counter;
use hdrhistogram::Histogram;
use tokio::task::AbortHandle;
use std::cell::Cell;
use std::sync::Arc;
use std::sync::Mutex;
#[derive(Debug)]
pub enum Error {
CreationError(CreationError),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::CreationError(e) => write!(f, "CreationError: {}", e),
}
}
}
impl std::error::Error for Error {}
impl From<CreationError> for Error {
fn from(e: CreationError) -> Self {
Error::CreationError(e)
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct EldHistogram<C: Counter> {
ht: Arc<Mutex<Histogram<C>>>,
fut: Cell<Option<AbortHandle>>,
resolution: usize,
}
impl<C: Counter + Send + 'static> EldHistogram<C> {
pub fn new(resolution: usize) -> Result<Self> {
let ht = Histogram::<C>::new(5)?;
Ok(Self {
ht: Arc::new(Mutex::new(ht)),
fut: Cell::new(None),
resolution,
})
}
pub fn start(&self) {
let r = self.resolution as u64;
let ht = Arc::clone(&self.ht);
let fut = tokio::spawn(async move {
let mut interval =
tokio::time::interval(tokio::time::Duration::from_millis(r));
loop {
interval.tick().await;
let clock = tokio::time::Instant::now();
tokio::task::yield_now().await;
if let Ok(mut ht) = ht.lock() {
let _ = ht.record(clock.elapsed().as_nanos() as u64);
}
}
});
if let Some(prev) = self.fut.replace(Some(fut.abort_handle())) {
prev.abort();
}
}
pub fn stop(&self) {
if let Some(fut) = self.fut.take() {
fut.abort();
}
}
fn with_ht<R>(&self, f: impl FnOnce(&Histogram<C>) -> R) -> R {
let g = self.ht.lock().expect("histogram mutex poisoned");
f(&g)
}
fn with_ht_mut<R>(&self, f: impl FnOnce(&mut Histogram<C>) -> R) -> R {
let mut g = self.ht.lock().expect("histogram mutex poisoned");
f(&mut g)
}
pub fn reset(&self) {
self.with_ht_mut(|h| h.reset());
}
pub fn record(&self, value: u64) {
let _ = self.with_ht_mut(|h| h.record(value));
}
pub fn len(&self) -> u64 {
self.with_ht(|h| h.len())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn min(&self) -> u64 {
self.with_ht(|h| h.min())
}
pub fn max(&self) -> u64 {
self.with_ht(|h| h.max())
}
pub fn mean(&self) -> f64 {
self.with_ht(|h| h.mean())
}
pub fn stdev(&self) -> f64 {
self.with_ht(|h| h.stdev())
}
pub fn value_at_percentile(&self, percentile: f64) -> u64 {
self.with_ht(|h| h.value_at_percentile(percentile))
}
}
impl<C: Counter> Drop for EldHistogram<C> {
fn drop(&mut self) {
if let Some(fut) = self.fut.take() {
fut.abort();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_eld() {
let h = EldHistogram::<u64>::new(20).unwrap();
h.start();
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
h.stop();
assert!(h.min() > 0);
}
}