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    #[allow(clippy::expect_used)]
46    #[allow(clippy::unwrap_in_result)]
47    pub fn quantile(&self, quantile: f64) -> Option<Duration> {
48        assert!(
49            (0.0..=1.0).contains(&quantile),
50            "quantile must be between 0.0 and 1.0"
51        );
52
53        self.0
54            .read()
55            .quantile(quantile)
56            .expect("quantile range checked")
57            .map(Duration::from_secs_f64)
58    }
59
60    /// Returns the number of values recorded.
61    pub fn count(&self) -> usize {
62        self.0.read().count()
63    }
64
65    /// Returns true if the timer contains 0 samples.
66    pub fn is_empty(&self) -> bool {
67        self.0.read().count() == 0
68    }
69
70    /// Returns a RAII guard that starts measuring time, recording time passed between it being created to being dropped.
71    pub fn time(&self) -> TimeGuard<'_> {
72        TimeGuard {
73            source: self,
74            start: Instant::now(),
75        }
76    }
77}
78
79/// RAII guard attached to a [`Timer`] instance, will record the time passed since its creation when dropped.
80pub struct TimeGuard<'a> {
81    source: &'a Timer,
82    start: Instant,
83}
84
85impl Drop for TimeGuard<'_> {
86    fn drop(&mut self) {
87        let elapsed = self.start.elapsed();
88        self.source.update(elapsed);
89    }
90}