tokio_eld/
lib.rs

1// Copyright 2024 Divy Srivastava <dj.srivastava23@gmail.com>
2
3//! _tokio_eld_ provides a histogram-based sampler for recording and analyzing
4//! event loop delays in a current_thread Tokio runtime. The API is similar to
5//! Node.js's `perf_hooks.monitorEventLoopDelay()`.
6//!
7//! # EldHistogram
8//!
9//! EldHistogram supports recording and analyzing event loop delay using a High Dynamic Range (HDR)
10//! Histogram. The recorded delays are in nanoseconds.
11//!
12//! Refer to documentation for [`hdrhistogram::Histogram`](https://docs.rs/hdrhistogram/latest/hdrhistogram/struct.Histogram.html) for more information
13//! on how to use the core data structure.
14//!
15//! # Usage
16//!
17//! ```rust
18//! use tokio_eld::EldHistogram;
19//!
20//! # #[tokio::test]
21//! # async fn test_example() {
22//! let mut h = EldHistogram::<u64>::new(20).unwrap();
23//! h.start();
24//! // do some work
25//! h.stop();
26//!
27//! println!("min: {}", h.min());
28//! println!("max: {}", h.max());
29//! println!("mean: {}", h.mean());
30//! println!("stddev: {}", h.stdev());
31//! println!("p50: {}", h.value_at_percentile(50.0));
32//! println!("p90: {}", h.value_at_percentile(90.0));
33//! # }
34//! ```
35
36use hdrhistogram::errors::CreationError;
37use hdrhistogram::Counter;
38use hdrhistogram::Histogram;
39
40use tokio::task::AbortHandle;
41
42use std::cell::Cell;
43use std::cell::UnsafeCell;
44
45/// Error types used in this crate.
46#[derive(Debug)]
47pub enum Error {
48  CreationError(CreationError),
49}
50
51impl std::fmt::Display for Error {
52  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
53    match self {
54      Error::CreationError(e) => write!(f, "CreationError: {}", e),
55    }
56  }
57}
58
59impl std::error::Error for Error {}
60
61impl From<CreationError> for Error {
62  fn from(e: CreationError) -> Self {
63    Error::CreationError(e)
64  }
65}
66
67pub type Result<T> = std::result::Result<T, Error>;
68
69/// A `Histogram` that can written to concurrently by mutiple tasks, used to
70/// measure event loop delays.
71///
72/// Look at
73/// [`hdrhistogram::Histogram`](https://docs.rs/hdrhistogram/latest/hdrhistogram/struct.Histogram.html) for more information on how to use the
74/// core data structure.
75pub struct EldHistogram<C: Counter> {
76  ht: UnsafeCell<Histogram<C>>,
77
78  fut: Cell<Option<AbortHandle>>,
79
80  resolution: usize,
81}
82
83impl<C: Counter> std::ops::Deref for EldHistogram<C> {
84  type Target = Histogram<C>;
85
86  fn deref(&self) -> &Self::Target {
87    unsafe { &*self.ht.get() }
88  }
89}
90
91impl<C: Counter> std::ops::DerefMut for EldHistogram<C> {
92  fn deref_mut(&mut self) -> &mut Self::Target {
93    unsafe { &mut *self.ht.get() }
94  }
95}
96
97impl<C: Counter + Send + 'static> EldHistogram<C> {
98  /// Creates a new `EldHistogram` with the given timer resolution that samples
99  /// the event loop delay over time. The delays are recorded in nanoseconds.
100  pub fn new(resolution: usize) -> Result<Self> {
101    let ht = Histogram::<C>::new(5)?;
102
103    Ok(Self {
104      ht: UnsafeCell::new(ht),
105      fut: Cell::new(None),
106      resolution,
107    })
108  }
109
110  /// Start the update interval recorder.
111  ///
112  /// This will start a new task that will record the event loop delay at the
113  /// given resolution.
114  pub fn start(&self) {
115    let r = self.resolution as u64;
116
117    let ht = unsafe { &mut *self.ht.get() };
118    let fut = tokio::spawn(async move {
119      let mut interval =
120        tokio::time::interval(tokio::time::Duration::from_millis(r));
121      loop {
122        interval.tick().await;
123
124        let clock = tokio::time::Instant::now();
125
126        tokio::task::yield_now().await;
127        let _ = ht.record(clock.elapsed().as_nanos() as u64);
128      }
129    });
130
131    self.fut.set(Some(fut.abort_handle()));
132  }
133
134  /// Stop the update interval recorder.
135  pub fn stop(&self) {
136    if let Some(fut) = self.fut.take() {
137      fut.abort();
138    }
139  }
140}
141
142#[cfg(test)]
143mod tests {
144  use super::*;
145
146  #[tokio::test]
147  async fn test_eld() {
148    let mut h = EldHistogram::<u64>::new(20).unwrap();
149    h.start();
150    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
151    h.stop();
152
153    assert!(h.min() > 0);
154  }
155}