Skip to main content

vortex_metrics/
timer.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::sync::Arc;
5use std::time::Duration;
6use std::time::Instant;
7
8use parking_lot::RwLock;
9use sketches_ddsketch::DDSketch;
10
11/// A specialized histogram for storing timed measurements. Like [`Histogram`], it uses [DDSketch] to store approximated values
12/// but accepts and returns nano-scale durations.
13///
14/// [`Histogram`]: crate::Histogram
15#[derive(Clone, Default)]
16pub struct Timer(Arc<RwLock<DDSketch>>);
17
18impl std::fmt::Debug for Timer {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        f.debug_tuple("Timer").finish_non_exhaustive()
21    }
22}
23
24impl Timer {
25    pub(crate) fn new() -> Self {
26        Self(Default::default())
27    }
28
29    /// Record a duration.
30    pub fn update(&self, duration: Duration) {
31        self.0.write().add(duration.as_secs_f64());
32    }
33
34    /// Returns the sum of all recorded durations.
35    pub fn total(&self) -> Duration {
36        self.0
37            .read()
38            .sum()
39            .map(Duration::from_secs_f64)
40            .unwrap_or_default()
41    }
42
43    /// Returns the estimated quantile value, which must be in the [0.0, 1.0] range, will panic otherwise.
44    /// Returns `None` if the timer is empty.
45    #[expect(clippy::expect_used)]
46    pub fn quantile(&self, quantile: f64) -> Option<Duration> {
47        assert!(
48            (0.0..=1.0).contains(&quantile),
49            "quantile must be between 0.0 and 1.0"
50        );
51
52        self.0
53            .read()
54            .quantile(quantile)
55            .expect("quantile range checked")
56            .map(Duration::from_secs_f64)
57    }
58
59    /// Returns the number of values recorded.
60    pub fn count(&self) -> usize {
61        self.0.read().count()
62    }
63
64    /// Returns true if the timer contains 0 samples.
65    pub fn is_empty(&self) -> bool {
66        self.0.read().count() == 0
67    }
68
69    /// Returns a RAII guard that starts measuring time, recording time passed between it being created to being dropped.
70    pub fn time(&self) -> TimeGuard<'_> {
71        TimeGuard {
72            source: self,
73            start: Instant::now(),
74        }
75    }
76}
77
78/// RAII guard attached to a [`Timer`] instance, will record the time passed since its creation when dropped.
79pub struct TimeGuard<'a> {
80    source: &'a Timer,
81    start: Instant,
82}
83
84impl Drop for TimeGuard<'_> {
85    fn drop(&mut self) {
86        let elapsed = self.start.elapsed();
87        self.source.update(elapsed);
88    }
89}