Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Choosing a Clock

Every clock implements Clock, whose one verb, now, gives the clock’s Reading: a point on its timeline, or a span for a clock of CPU time. Choosing a clock is choosing the timeline its readings are on.

The Clocks

ClockReadsLinuxmacOSSteps
SystemClockTimestampCLOCK_REALTIMECLOCK_REALTIMEyes
CoarseSystemClockTimestampCLOCK_REALTIME_COARSECLOCK_REALTIMEyes
TaiClockTaiTimestampCLOCK_TAInonewith the wall clock
MonotonicClockUptimeCLOCK_MONOTONICCLOCK_UPTIME_RAWnever
CoarseMonotonicClockUptimeCLOCK_MONOTONIC_COARSECLOCK_UPTIME_RAW_APPROXnever
RawMonotonicClockRawUptimeCLOCK_MONOTONIC_RAWCLOCK_UPTIME_RAWnever
BootClockBootUptimeCLOCK_BOOTTIMECLOCK_MONOTONICnever
ProcessCpuClockTimedeltaCLOCK_PROCESS_CPUTIME_IDCLOCK_PROCESS_CPUTIME_IDnever
ThreadCpuClockTimedeltaCLOCK_THREAD_CPUTIME_IDCLOCK_THREAD_CPUTIME_IDnever
CounterTickstampcntvct_el0 or rdtsccntvct_el0 or rdtscnever

The operating system’s clocks read clock_gettime with the id the table gives, and need the std feature, on 64-bit Linux or macOS; TaiClock is Linux’s alone. Each id is one its system has, so a reading never fails. The counter reads cntvct_el0 on aarch64 and rdtsc on x86_64, on any system, with or without std; The CPU Counter has the rest of it.

Each clock is a unit struct, so SystemClock.now() reads one. A reference to a clock is a clock too, so code takes &C where C: Clock.

The Wall Clock

SystemClock is the clock whose readings mean something off this machine, so it stamps a capture another machine will compare. A time service disciplines it, and may step it backwards across a correction: a span that must never run backwards is measured on MonotonicClock or the counter instead.

CoarseSystemClock hands back the value the kernel’s timer last wrote, and reads no counter: the same moment, to the timer’s period, a few milliseconds. It answers whether a heartbeat or an expiry is due; it never stamps a capture. macOS has no coarse wall clock, so there it reads CLOCK_REALTIME.

fn the_wall_clock_stamps_a_capture() {
    use t2t::clock::{Clock, CoarseSystemClock, SystemClock};
    use t2t::{Timedelta, Timestamp};

    let captured = SystemClock.now();
    assert!(captured > Timestamp::from_secs(1_700_000_000), "a moment past November 2023");
    println!("captured at {captured}");

    // The coarse clock names the same moment, to the kernel's timer period.
    let gap = SystemClock.now() - CoarseSystemClock.now();
    assert!(gap.abs() < Timedelta::from_millis(100), "within a period or two: {gap}");
}

TAI

TaiClock reads International Atomic Time: the wall clock plus the kernel’s TAI offset, which a time service such as chrony or ptp4l sets to the leap seconds since 1972. Until one does, the offset is zero, and the clock reads the wall clock’s time. A leap second does not step it, but it steps with the wall clock when the wall clock is set, and when a time service sets the offset. Linux alone has the clock.

fn the_tai_clock_runs_ahead_of_the_wall_clock_by_the_kernels_offset() {
    use t2t::Timedelta;
    use t2t::clock::{Clock, SystemClock, TaiClock};

    // The two are different timelines, so their counts are compared, not the points.
    let wall = SystemClock.now().as_nanos();
    let tai = TaiClock.now().as_nanos();
    let offset = Timedelta::from_nanos(tai.saturating_sub(wall)).floor(Timedelta::SECOND);
    println!("TAI runs {offset} ahead of UTC here");
    let leap_seconds = Timedelta::ZERO..Timedelta::from_secs(60);
    assert!(leap_seconds.contains(&offset), "zero, or the leap seconds since 1972: {offset}");
}

The Monotonic Clocks

MonotonicClock never steps, and every process on the machine reads the same clock, so a deadline is MonotonicClock.now() plus a timeout. It stops while the machine sleeps. Linux lets a time service slew its rate; macOS does not. CoarseMonotonicClock reads the same timeline to the timer’s period, for a deadline that is far off, or polled often; both read an Uptime, so their readings compare.

fn the_monotonic_clock_keeps_a_deadline() {
    use core::time::Duration;
    use std::thread;

    use t2t::Timedelta;
    use t2t::clock::{Clock, CoarseMonotonicClock, MonotonicClock};

    // Sleep for what is left until the deadline, however early a sleep returns.
    let deadline = MonotonicClock.now() + Timedelta::from_millis(5);
    let mut left = deadline - MonotonicClock.now();
    while left.is_positive() {
        thread::sleep(Duration::try_from(left).expect("a span forwards is a duration"));
        left = deadline - MonotonicClock.now();
    }

    // A deadline far off, checked often, is cheaper on the coarse clock: both read `Uptime`.
    let expiry = CoarseMonotonicClock.now() + Timedelta::from_secs(30);
    assert!(MonotonicClock.now() < expiry, "the two read one timeline, so they compare");
}

RawMonotonicClock runs at the hardware’s own rate, which no time service adjusts: the clock to measure a span free of a time service’s corrections, and the one t2t measures an x86_64 counter’s rate against. It stops while the machine sleeps too. Its readings are a RawUptime, so they never mix with the monotonic clock’s. On macOS it reads the same clock as MonotonicClock, which no time service slews there.

BootClock counts the time the machine was suspended, which MonotonicClock does not: the clock for a lease or a timeout that must run down across a suspension.

fn the_raw_and_boot_clocks_read_timelines_of_their_own() {
    use t2t::Timedelta;
    use t2t::clock::{BootClock, Clock, RawMonotonicClock};

    // A span no time service's slewing bends.
    let start = RawMonotonicClock.now();
    let span = RawMonotonicClock.now() - start;
    assert!(span >= Timedelta::ZERO, "it never runs backwards");

    // A lease that a suspension of the machine runs down too.
    let lease = BootClock.now() + Timedelta::from_mins(5);
    assert!(BootClock.now() < lease, "the lease lies ahead");
}

CPU Time

ProcessCpuClock reads the CPU time the process has used, on every thread, and ThreadCpuClock the time the calling thread has used, so two threads’ readings count two different times. Each reads a Timedelta, the CPU time used so far, and each takes a system call, not the fast path the clocks above take.

fn the_cpu_clocks_count_the_work_done() {
    use core::hint::black_box;

    use t2t::Timedelta;
    use t2t::clock::{Clock, ProcessCpuClock, ThreadCpuClock};

    // The process's clock is read first and last, so its span holds the thread's.
    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 this thread's time: {thread_time}");
    assert!(process_time >= thread_time, "and the process counts every thread's: {process_time}");
}

What a Reading Costs

One reading of each, on an AWS Graviton4 under Linux 6.12, pinned to an isolated core, the median of each of two runs:

ClockOne reading
CoarseSystemClock, CoarseMonotonicClock9.8 to 9.9 ns
Counter11.8 ns
SystemClock, TaiClock, MonotonicClock, RawMonotonicClock, BootClock33.2 ns
ThreadCpuClock269 to 273 ns
ProcessCpuClock388 to 394 ns

The run is under crates/t2t-clock/benches/results/2026-10-03T10-21Z-8aeae1f-clock-reads/; Performance has the rest, and how to measure on your own machine. The coarse clocks cost least because they read no counter, and for the same reason are right only to the timer’s period.

Which to Read

  • To stamp a capture, read SystemClock: its reading names a moment another machine names too.
  • To wait, read MonotonicClock: it never steps, so a deadline holds.
  • To measure, read the Counter: one instruction, and one counter for every core and process on the machine.
  • To poll, read CoarseSystemClock or CoarseMonotonicClock: either costs least, and is right to the timer’s period.
  • To run a test or a replay, read a ManualClock, or an AtomicManualClock across threads, as Testing with Manual Clocks shows.