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}