Skip to main content

elfo_utils/time/
instant.rs

1//! Provides the [`Instant`] type.
2//!
3//! The main purpose of this module is to abstract over quanta/minstant/etc.
4//!
5//! Another purpose is to provide a way to mock instant time in tests.
6
7use std::time::Duration;
8
9use quanta::Clock;
10
11/// A measurement of a monotonically nondecreasing clock.
12#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
13pub struct Instant(u64 /* raw TSC value */); // TODO: make it `NonZeroU64`?
14
15impl Instant {
16    /// Returns the current monotonic time.
17    #[inline]
18    pub fn now() -> Self {
19        Self(with_clock(|c| c.raw()))
20    }
21
22    /// Returns the amount of time elapsed since this instant.
23    ///
24    /// Prefer `elapsed_secs_f64()` if used for metrics.
25    pub fn elapsed(&self) -> Duration {
26        Self::now().duration_since(*self)
27    }
28
29    /// Returns the number of seconds elapsed since this instant.
30    ///
31    /// This method saturates to zero.
32    pub fn elapsed_secs_f64(&self) -> f64 {
33        Self::now().secs_f64_since(*self)
34    }
35
36    /// Returns the number of nanoseconds elapsed since this instant.
37    ///
38    /// This method saturates to zero.
39    pub fn elapsed_nanos(&self) -> u64 {
40        Self::now().nanos_since(*self)
41    }
42
43    /// Returns the amount of time elapsed from another instant to this one.
44    ///
45    /// This method saturates to zero.
46    ///
47    /// Prefer `secs_f64_since()` if used for metrics.
48    #[inline]
49    pub fn duration_since(&self, earlier: Self) -> Duration {
50        with_clock(|c| c.delta(earlier.0, self.0))
51    }
52
53    /// Returns the number of seconds elapsed from another instant to this one.
54    ///
55    /// This method saturates to zero.
56    #[inline]
57    pub fn secs_f64_since(&self, earlier: Self) -> f64 {
58        self.nanos_since(earlier) as f64 * 1e-9
59    }
60
61    /// Returns the number of nanosecs elapsed from another instant to this one.
62    ///
63    /// This method saturates to zero.
64    #[inline]
65    pub fn nanos_since(&self, earlier: Self) -> u64 {
66        with_clock(|c| c.delta_as_nanos(earlier.0, self.0))
67    }
68}
69
70pub(crate) fn nanos_since_unknown_epoch() -> u64 {
71    with_clock(|c| c.delta_as_nanos(0, c.raw()))
72}
73
74fn with_clock<R>(f: impl FnOnce(&Clock) -> R) -> R {
75    use std::sync::OnceLock;
76
77    static CLOCK: OnceLock<Clock> = OnceLock::new();
78
79    #[cfg(any(test, feature = "test-util"))]
80    return mock::CLOCK.with(|c| match c.borrow().as_ref() {
81        Some(c) => f(c),
82        None => f(CLOCK.get_or_init(Clock::new)),
83    });
84
85    #[cfg(not(any(test, feature = "test-util")))]
86    f(CLOCK.get_or_init(Clock::new))
87}
88
89#[cfg(any(test, feature = "test-util"))]
90pub use mock::{with_instant_mock, InstantMock};
91
92#[cfg(any(test, feature = "test-util"))]
93mod mock {
94    use std::cell::RefCell;
95
96    use super::*;
97
98    thread_local! {
99        pub(super) static CLOCK: RefCell<Option<Clock>> = const { RefCell::new(None) };
100    }
101
102    /// Mocks `Instant`, see [`InstantMock`].
103    pub fn with_instant_mock<R>(f: impl FnOnce(InstantMock) -> R) -> R {
104        let (clock, mock) = Clock::mock();
105        let mock = InstantMock(mock);
106        CLOCK.with(|c| *c.borrow_mut() = Some(clock));
107        let result = f(mock);
108        CLOCK.with(|c| *c.borrow_mut() = None);
109        result
110    }
111
112    /// Controllable time source for use in tests.
113    pub struct InstantMock(std::sync::Arc<quanta::Mock>);
114
115    impl InstantMock {
116        /// Increase the time by the given duration.
117        pub fn advance(&self, duration: Duration) {
118            self.0.increment(duration);
119        }
120    }
121}