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}