Skip to main content

relay_cogs/
time.rs

1//! Time management for COGS measurements.
2//!
3//! COGS are derived from measured amount of work, which usually is done by taking a timed measure.
4//! To allow different types of time measures other than wall time, this module exposes a [`Clock`]
5//! trait.
6
7use std::cell::Cell;
8use std::sync::LazyLock;
9pub use std::time::Duration;
10
11/// A clock for taking COGS measurements.
12pub trait Clock {
13    /// Returns a current instant in in time.
14    ///
15    /// This value must be monotonically increasing but doesn't need to be strictly monotonically
16    /// increasing.
17    fn now(&self) -> Instant;
18}
19
20/// An instant in time.
21///
22/// This instant can be produced by [`Clock`]. Mixing instants from different clocks is not
23/// supported and leads to undefined results but not undefined behaviour.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
25pub struct Instant(Duration);
26
27impl Instant {
28    /// Creates a new [`Instant`] from a duration.
29    ///
30    /// The meaning of the duration is up to the caller, usually a specific [`Clock`].
31    /// For example a system clock may take offsets from a specific [`std::time::Instant`],
32    /// a CPU time measurement may use a duration representing the amount of nano seconds
33    /// passed since the start of the task.
34    pub fn from_duration(duration: Duration) -> Self {
35        Self(duration)
36    }
37
38    /// Returns the amount of time elapsed from an earlier instant to this one, or a zero duration
39    /// if that instant is later than this one.
40    pub fn since(self, earlier: Self) -> Duration {
41        self.0.saturating_sub(earlier.0)
42    }
43}
44
45/// A system [`Clock`] which uses the monotonically increasing [`Instant`] as its source, measuring
46/// wall time.
47#[derive(Copy, Clone, Debug, Default)]
48pub struct SystemClock;
49
50impl Clock for SystemClock {
51    fn now(&self) -> Instant {
52        /// A static point in time as a [`std::time::Instant`] which can be used as a common reference
53        /// point for all [`std::time::Instant`] instances created.
54        ///
55        /// This allows a consistent conversion from a [`std::time::Instant`] to [`Duration`].
56        static REFERENCE_INSTANT: LazyLock<std::time::Instant> =
57            LazyLock::new(std::time::Instant::now);
58
59        let reference = *REFERENCE_INSTANT;
60        let now = std::time::Instant::now();
61        Instant::from_duration(
62            now.checked_duration_since(reference)
63                .unwrap_or(Duration::ZERO),
64        )
65    }
66}
67
68/// A [`Clock`] which can be set to any value.
69#[derive(Clone, Debug, Default)]
70pub struct StaticClock(Cell<Duration>);
71
72impl StaticClock {
73    /// Creates a new [`StaticClock`] initialized to `start`.
74    ///
75    /// # Example:
76    ///
77    /// ```
78    /// # use relay_cogs::time::{Clock, StaticClock, Duration, Instant};
79    /// let clock = StaticClock::new(Duration::from_millis(123));
80    /// assert_eq!(clock.now(), Instant::from_duration(Duration::from_millis(123)));
81    /// ```
82    pub fn new(start: Duration) -> Self {
83        Self(Cell::new(start))
84    }
85
86    /// Advances the clock by `duration`.
87    ///
88    /// # Example:
89    ///
90    /// ```
91    /// # use relay_cogs::time::{Clock, StaticClock, Duration, Instant};
92    /// let clock = StaticClock::default();
93    /// assert_eq!(clock.now(), Instant::from_duration(Duration::ZERO));
94    /// clock.advance(Duration::from_millis(50));
95    /// assert_eq!(clock.now(), Instant::from_duration(Duration::from_millis(50)));
96    /// ```
97    pub fn advance(&self, duration: Duration) {
98        self.0.set(self.0.get() + duration);
99    }
100
101    /// Advances the clock by `millis` milliseconds.
102    ///
103    /// This is a convenience method for [`Self::advance`].
104    ///
105    /// # Example:
106    ///
107    /// ```
108    /// # use relay_cogs::time::{Clock, StaticClock, Duration, Instant};
109    /// let clock = StaticClock::default();
110    /// assert_eq!(clock.now(), Instant::from_duration(Duration::ZERO));
111    /// clock.advance_millis(50);
112    /// assert_eq!(clock.now(), Instant::from_duration(Duration::from_millis(50)));
113    /// ```
114    #[cfg(test)]
115    pub fn advance_millis(&self, millis: u64) {
116        self.advance(Duration::from_millis(millis))
117    }
118}
119
120impl Clock for StaticClock {
121    fn now(&self) -> Instant {
122        (&self).now()
123    }
124}
125
126impl Clock for &StaticClock {
127    fn now(&self) -> Instant {
128        Instant::from_duration(self.0.get())
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_system_clock_increasing() {
138        // System clocks are globally monotonically increasing.
139        let clock1 = SystemClock;
140        let clock2 = SystemClock;
141
142        let a = clock1.now();
143        let b = clock1.now();
144        let c = clock2.now();
145        let d = clock1.now();
146
147        assert!(a <= b);
148        assert!(b <= c);
149        assert!(c <= d);
150        assert!(a <= d);
151    }
152}