Skip to main content

t2t_clock/
os.rs

1//! The operating system's clocks, read through `clock_gettime`.
2
3use libc::{clock_gettime, clockid_t, timespec};
4#[cfg(tai_clock)]
5use t2t_core::TaiTimestamp;
6use t2t_core::{BootUptime, RawUptime, Timedelta, Timestamp, Uptime};
7
8use crate::Clock;
9
10/// Nanoseconds in a second, the unit `tv_nsec` counts beside `tv_sec`.
11const NANOS_PER_SECOND: i64 = Timedelta::SECOND.as_nanos();
12
13/// The ids each clock reads on Linux.
14#[cfg(target_os = "linux")]
15mod ids {
16    pub(super) use libc::{
17        CLOCK_BOOTTIME as BOOT, CLOCK_MONOTONIC as MONOTONIC,
18        CLOCK_MONOTONIC_COARSE as COARSE_MONOTONIC, CLOCK_MONOTONIC_RAW as RAW_MONOTONIC,
19        CLOCK_PROCESS_CPUTIME_ID as PROCESS_CPU, CLOCK_REALTIME as SYSTEM,
20        CLOCK_REALTIME_COARSE as COARSE_SYSTEM, CLOCK_TAI as TAI,
21        CLOCK_THREAD_CPUTIME_ID as THREAD_CPU,
22    };
23}
24
25/// The ids each clock reads on macOS, which has no coarse wall clock and no TAI; its uptime
26/// clocks stop while the machine sleeps, and its `CLOCK_MONOTONIC` counts the sleep.
27#[cfg(target_os = "macos")]
28mod ids {
29    pub(super) use libc::{
30        CLOCK_MONOTONIC as BOOT, CLOCK_PROCESS_CPUTIME_ID as PROCESS_CPU,
31        CLOCK_REALTIME as COARSE_SYSTEM, CLOCK_REALTIME as SYSTEM,
32        CLOCK_THREAD_CPUTIME_ID as THREAD_CPU, CLOCK_UPTIME_RAW as MONOTONIC,
33        CLOCK_UPTIME_RAW as RAW_MONOTONIC, CLOCK_UPTIME_RAW_APPROX as COARSE_MONOTONIC,
34    };
35}
36
37/// Nanoseconds on the clock `id`, one of [`ids`], each a clock its OS has, so the call cannot
38/// fail; past an `i64`'s range, they saturate.
39#[inline]
40fn read(id: clockid_t) -> i64 {
41    let mut time = timespec { tv_sec: 0, tv_nsec: 0 };
42    // SAFETY: `time` is a valid, writable `timespec`, the only memory `clock_gettime` writes.
43    #[expect(unsafe_code, reason = "`clock_gettime` is a C function")]
44    unsafe {
45        clock_gettime(id, &raw mut time);
46    }
47    time.tv_sec.saturating_mul(NANOS_PER_SECOND).saturating_add(time.tv_nsec)
48}
49
50/// A clock that reads the OS clock `$id` as a `$reading`.
51macro_rules! os_clock {
52    ($(#[$attribute:meta])* $clock:ident, $reading:ident, $id:expr) => {
53        $(#[$attribute])*
54        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
55        pub struct $clock;
56
57        impl Clock for $clock {
58            type Reading = $reading;
59
60            #[inline]
61            fn now(&self) -> $reading {
62                $reading::from_nanos(read($id))
63            }
64        }
65    };
66}
67
68os_clock!(
69    /// The wall clock, `CLOCK_REALTIME`.
70    ///
71    /// The clock whose readings mean something off this machine, which makes it the stamp for a
72    /// capture. A time service disciplines it, so it may step backwards across a correction; a
73    /// span that must never run backwards is measured on [`MonotonicClock`] or a
74    /// [`Counter`](crate::Counter).
75    ///
76    /// # Examples
77    /// ```
78    /// use t2t_clock::{Clock, SystemClock};
79    /// use t2t_core::Timestamp;
80    ///
81    /// assert!(SystemClock.now() > Timestamp::from_secs(1_700_000_000), "past November 2023");
82    /// ```
83    SystemClock,
84    Timestamp,
85    ids::SYSTEM
86);
87
88os_clock!(
89    /// The wall clock as the kernel's timer last left it: `CLOCK_REALTIME_COARSE` on Linux,
90    /// `CLOCK_REALTIME` on macOS, which has no coarse wall clock.
91    ///
92    /// The moment [`SystemClock`] names, to the timer's period, a few milliseconds: the kernel
93    /// hands back a value its timer wrote, and reads no counter. Right for asking whether a
94    /// heartbeat or an expiry is due; never for stamping a capture.
95    ///
96    /// # Examples
97    /// ```
98    /// use t2t_clock::{Clock, CoarseSystemClock, SystemClock};
99    /// use t2t_core::Timedelta;
100    ///
101    /// let gap = SystemClock.now() - CoarseSystemClock.now();
102    /// assert!(gap.abs() < Timedelta::from_millis(100), "within a period or two: {gap}");
103    /// ```
104    CoarseSystemClock,
105    Timestamp,
106    ids::COARSE_SYSTEM
107);
108
109#[cfg(tai_clock)]
110os_clock!(
111    /// International Atomic Time, `CLOCK_TAI`, on Linux.
112    ///
113    /// The wall clock plus the kernel's TAI offset, which `chrony` or `ptp4l` sets to the leap
114    /// seconds since 1972; until one does, the offset is zero and this reads the wall clock.
115    ///
116    /// # Examples
117    /// ```
118    /// use t2t_clock::{Clock, SystemClock, TaiClock};
119    /// use t2t_core::Timedelta;
120    ///
121    /// let ahead = Timedelta::from_nanos(TaiClock.now().as_nanos() - SystemClock.now().as_nanos());
122    /// assert!(ahead < Timedelta::from_secs(60), "at most the leap seconds since 1972: {ahead}");
123    /// ```
124    TaiClock,
125    TaiTimestamp,
126    ids::TAI
127);
128
129os_clock!(
130    /// The monotonic clock: `CLOCK_MONOTONIC` on Linux, `CLOCK_UPTIME_RAW` on macOS.
131    ///
132    /// It never steps, and every process on the machine reads the same clock, which makes it the
133    /// clock for a deadline. It stops while the machine sleeps. Linux lets a time service slew its
134    /// rate; macOS does not.
135    ///
136    /// # Examples
137    /// ```
138    /// use t2t_clock::{Clock, MonotonicClock};
139    /// use t2t_core::Timedelta;
140    ///
141    /// let deadline = MonotonicClock.now() + Timedelta::from_millis(50);
142    /// assert!(MonotonicClock.now() < deadline, "the deadline lies ahead");
143    /// ```
144    MonotonicClock,
145    Uptime,
146    ids::MONOTONIC
147);
148
149os_clock!(
150    /// The monotonic clock as the kernel's timer last left it: `CLOCK_MONOTONIC_COARSE` on Linux,
151    /// `CLOCK_UPTIME_RAW_APPROX` on macOS.
152    ///
153    /// The point [`MonotonicClock`] names, to the timer's period, a few milliseconds: right for a
154    /// deadline that is far off, or polled often.
155    ///
156    /// # Examples
157    /// ```
158    /// use t2t_clock::{Clock, CoarseMonotonicClock};
159    /// use t2t_core::Timedelta;
160    ///
161    /// let expiry = CoarseMonotonicClock.now() + Timedelta::from_secs(30);
162    /// assert!(CoarseMonotonicClock.now() < expiry, "a heartbeat's expiry, polled cheaply");
163    /// ```
164    CoarseMonotonicClock,
165    Uptime,
166    ids::COARSE_MONOTONIC
167);
168
169os_clock!(
170    /// The monotonic clock at the hardware's own rate: `CLOCK_MONOTONIC_RAW` on Linux,
171    /// `CLOCK_UPTIME_RAW` on macOS.
172    ///
173    /// No time service adjusts its rate, which makes it the clock to measure a counter's rate
174    /// against, and a span free of a time service's corrections. It stops while the machine
175    /// sleeps.
176    ///
177    /// # Examples
178    /// ```
179    /// use t2t_clock::{Clock, RawMonotonicClock};
180    /// use t2t_core::Timedelta;
181    ///
182    /// let start = RawMonotonicClock.now();
183    /// assert!(RawMonotonicClock.now() - start >= Timedelta::ZERO, "a span no slewing bends");
184    /// ```
185    RawMonotonicClock,
186    RawUptime,
187    ids::RAW_MONOTONIC
188);
189
190os_clock!(
191    /// The boot clock, which counts the time the machine was suspended: `CLOCK_BOOTTIME` on Linux,
192    /// `CLOCK_MONOTONIC` on macOS.
193    ///
194    /// Right for a timeout that must expire across a suspension, which [`MonotonicClock`] would
195    /// carry past.
196    ///
197    /// # Examples
198    /// ```
199    /// use t2t_clock::{BootClock, Clock};
200    /// use t2t_core::Timedelta;
201    ///
202    /// let lease = BootClock.now() + Timedelta::from_mins(5);
203    /// assert!(BootClock.now() < lease, "a lease that expires across a suspension");
204    /// ```
205    BootClock,
206    BootUptime,
207    ids::BOOT
208);
209
210os_clock!(
211    /// The CPU time the process has used, on every thread, `CLOCK_PROCESS_CPUTIME_ID`.
212    ///
213    /// Read through a system call, not the fast path the clocks above take.
214    ///
215    /// # Examples
216    /// ```
217    /// use t2t_clock::{Clock, ProcessCpuClock};
218    /// use t2t_core::Timedelta;
219    ///
220    /// assert!(ProcessCpuClock.now() > Timedelta::ZERO, "the process has run by now");
221    /// ```
222    ProcessCpuClock,
223    Timedelta,
224    ids::PROCESS_CPU
225);
226
227os_clock!(
228    /// The CPU time the calling thread has used, `CLOCK_THREAD_CPUTIME_ID`.
229    ///
230    /// Read through a system call, not the fast path the clocks above take. Two readings on two
231    /// threads count two different times.
232    ///
233    /// # Examples
234    /// ```
235    /// use core::hint::black_box;
236    ///
237    /// use t2t_clock::{Clock, ThreadCpuClock};
238    /// use t2t_core::Timedelta;
239    ///
240    /// let start = ThreadCpuClock.now();
241    /// for value in 0_u64..1_000_000 {
242    ///     black_box(value);
243    /// }
244    /// assert!(ThreadCpuClock.now() - start > Timedelta::ZERO, "the loop took CPU time");
245    /// ```
246    ThreadCpuClock,
247    Timedelta,
248    ids::THREAD_CPU
249);
250
251#[cfg(test)]
252mod tests {
253    use core::hint::black_box;
254    use core::time::Duration;
255    use std::thread;
256
257    use t2t_core::{Timedelta, Timestamp};
258
259    #[cfg(tai_clock)]
260    use crate::TaiClock;
261    use crate::{
262        BootClock, Clock, CoarseMonotonicClock, CoarseSystemClock, MonotonicClock, ProcessCpuClock,
263        RawMonotonicClock, SystemClock, ThreadCpuClock,
264    };
265
266    #[test]
267    #[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
268    fn the_wall_clock_reads_a_plausible_moment() {
269        let now = SystemClock.now();
270        assert!(now > Timestamp::from_secs(1_700_000_000), "after November 2023: {now}");
271        assert!(now < Timestamp::from_secs(4_102_444_800), "before 2100: {now}");
272    }
273
274    #[test]
275    #[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
276    fn a_coarse_clock_trails_its_exact_one_by_a_timer_period_or_two() {
277        let coarse = CoarseSystemClock.now();
278        let exact = SystemClock.now();
279        assert!(exact >= coarse, "the exact reading came second: {coarse} then {exact}");
280        assert!(exact - coarse < Timedelta::from_millis(100), "within a period or two");
281
282        let coarse = CoarseMonotonicClock.now();
283        let exact = MonotonicClock.now();
284        assert!(exact >= coarse, "the exact reading came second: {coarse} then {exact}");
285        assert!(exact - coarse < Timedelta::from_millis(100), "within a period or two");
286    }
287
288    #[test]
289    #[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
290    fn the_monotonic_clocks_advance_by_what_was_slept() {
291        let (start, raw_start, boot_start) =
292            (MonotonicClock.now(), RawMonotonicClock.now(), BootClock.now());
293        thread::sleep(Duration::from_millis(20));
294        for span in [
295            MonotonicClock.now() - start,
296            RawMonotonicClock.now() - raw_start,
297            BootClock.now() - boot_start,
298        ] {
299            assert!(span >= Timedelta::from_millis(20), "at least what was slept: {span}");
300            assert!(span < Timedelta::from_secs(5), "and not wildly more: {span}");
301        }
302    }
303
304    #[test]
305    #[cfg(tai_clock)]
306    #[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
307    fn tai_runs_at_or_ahead_of_the_wall_clock() {
308        let wall = SystemClock.now().as_nanos();
309        let tai = TaiClock.now().as_nanos();
310        let offset = Timedelta::from_nanos(tai.saturating_sub(wall));
311        assert!(offset >= Timedelta::ZERO, "no time service sets a negative offset: {offset}");
312        assert!(offset < Timedelta::from_secs(60), "the leap seconds, at most: {offset}");
313    }
314
315    #[test]
316    #[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
317    fn the_cpu_clocks_count_the_work_done() {
318        let (process_start, thread_start) = (ProcessCpuClock.now(), ThreadCpuClock.now());
319        for value in 0_u64..1_000_000 {
320            black_box(value);
321        }
322        let thread_time = ThreadCpuClock.now() - thread_start;
323        let process_time = ProcessCpuClock.now() - process_start;
324        assert!(thread_time > Timedelta::ZERO, "the loop took CPU time");
325        assert!(process_time >= thread_time, "the process counts its threads': {process_time}");
326    }
327}