use libc::{clock_gettime, clockid_t, timespec};
#[cfg(tai_clock)]
use t2t_core::TaiTimestamp;
use t2t_core::{BootUptime, RawUptime, Timedelta, Timestamp, Uptime};
use crate::Clock;
const NANOS_PER_SECOND: i64 = Timedelta::SECOND.as_nanos();
#[cfg(target_os = "linux")]
mod ids {
pub(super) use libc::{
CLOCK_BOOTTIME as BOOT, CLOCK_MONOTONIC as MONOTONIC,
CLOCK_MONOTONIC_COARSE as COARSE_MONOTONIC, CLOCK_MONOTONIC_RAW as RAW_MONOTONIC,
CLOCK_PROCESS_CPUTIME_ID as PROCESS_CPU, CLOCK_REALTIME as SYSTEM,
CLOCK_REALTIME_COARSE as COARSE_SYSTEM, CLOCK_TAI as TAI,
CLOCK_THREAD_CPUTIME_ID as THREAD_CPU,
};
}
#[cfg(target_os = "macos")]
mod ids {
pub(super) use libc::{
CLOCK_MONOTONIC as BOOT, CLOCK_PROCESS_CPUTIME_ID as PROCESS_CPU,
CLOCK_REALTIME as COARSE_SYSTEM, CLOCK_REALTIME as SYSTEM,
CLOCK_THREAD_CPUTIME_ID as THREAD_CPU, CLOCK_UPTIME_RAW as MONOTONIC,
CLOCK_UPTIME_RAW as RAW_MONOTONIC, CLOCK_UPTIME_RAW_APPROX as COARSE_MONOTONIC,
};
}
#[inline]
fn read(id: clockid_t) -> i64 {
let mut time = timespec { tv_sec: 0, tv_nsec: 0 };
#[expect(unsafe_code, reason = "`clock_gettime` is a C function")]
unsafe {
clock_gettime(id, &raw mut time);
}
time.tv_sec.saturating_mul(NANOS_PER_SECOND).saturating_add(time.tv_nsec)
}
macro_rules! os_clock {
($(#[$attribute:meta])* $clock:ident, $reading:ident, $id:expr) => {
$(#[$attribute])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct $clock;
impl Clock for $clock {
type Reading = $reading;
#[inline]
fn now(&self) -> $reading {
$reading::from_nanos(read($id))
}
}
};
}
os_clock!(
SystemClock,
Timestamp,
ids::SYSTEM
);
os_clock!(
CoarseSystemClock,
Timestamp,
ids::COARSE_SYSTEM
);
#[cfg(tai_clock)]
os_clock!(
TaiClock,
TaiTimestamp,
ids::TAI
);
os_clock!(
MonotonicClock,
Uptime,
ids::MONOTONIC
);
os_clock!(
CoarseMonotonicClock,
Uptime,
ids::COARSE_MONOTONIC
);
os_clock!(
RawMonotonicClock,
RawUptime,
ids::RAW_MONOTONIC
);
os_clock!(
BootClock,
BootUptime,
ids::BOOT
);
os_clock!(
ProcessCpuClock,
Timedelta,
ids::PROCESS_CPU
);
os_clock!(
ThreadCpuClock,
Timedelta,
ids::THREAD_CPU
);
#[cfg(test)]
mod tests {
use core::hint::black_box;
use core::time::Duration;
use std::thread;
use t2t_core::{Timedelta, Timestamp};
#[cfg(tai_clock)]
use crate::TaiClock;
use crate::{
BootClock, Clock, CoarseMonotonicClock, CoarseSystemClock, MonotonicClock, ProcessCpuClock,
RawMonotonicClock, SystemClock, ThreadCpuClock,
};
#[test]
#[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
fn the_wall_clock_reads_a_plausible_moment() {
let now = SystemClock.now();
assert!(now > Timestamp::from_secs(1_700_000_000), "after November 2023: {now}");
assert!(now < Timestamp::from_secs(4_102_444_800), "before 2100: {now}");
}
#[test]
#[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
fn a_coarse_clock_trails_its_exact_one_by_a_timer_period_or_two() {
let coarse = CoarseSystemClock.now();
let exact = SystemClock.now();
assert!(exact >= coarse, "the exact reading came second: {coarse} then {exact}");
assert!(exact - coarse < Timedelta::from_millis(100), "within a period or two");
let coarse = CoarseMonotonicClock.now();
let exact = MonotonicClock.now();
assert!(exact >= coarse, "the exact reading came second: {coarse} then {exact}");
assert!(exact - coarse < Timedelta::from_millis(100), "within a period or two");
}
#[test]
#[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
fn the_monotonic_clocks_advance_by_what_was_slept() {
let (start, raw_start, boot_start) =
(MonotonicClock.now(), RawMonotonicClock.now(), BootClock.now());
thread::sleep(Duration::from_millis(20));
for span in [
MonotonicClock.now() - start,
RawMonotonicClock.now() - raw_start,
BootClock.now() - boot_start,
] {
assert!(span >= Timedelta::from_millis(20), "at least what was slept: {span}");
assert!(span < Timedelta::from_secs(5), "and not wildly more: {span}");
}
}
#[test]
#[cfg(tai_clock)]
#[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
fn tai_runs_at_or_ahead_of_the_wall_clock() {
let wall = SystemClock.now().as_nanos();
let tai = TaiClock.now().as_nanos();
let offset = Timedelta::from_nanos(tai.saturating_sub(wall));
assert!(offset >= Timedelta::ZERO, "no time service sets a negative offset: {offset}");
assert!(offset < Timedelta::from_secs(60), "the leap seconds, at most: {offset}");
}
#[test]
#[cfg_attr(miri, ignore = "Miri isolates the process from the OS's clocks")]
fn the_cpu_clocks_count_the_work_done() {
let (process_start, thread_start) = (ProcessCpuClock.now(), ThreadCpuClock.now());
for value in 0_u64..1_000_000 {
black_box(value);
}
let thread_time = ThreadCpuClock.now() - thread_start;
let process_time = ProcessCpuClock.now() - process_start;
assert!(thread_time > Timedelta::ZERO, "the loop took CPU time");
assert!(process_time >= thread_time, "the process counts its threads': {process_time}");
}
}