clock-kit
Precision time substrate for Rust — monotonic/wall separation, TSC calibration, PTP quality detection, and a deterministic test clock. The estate's time source for latency-critical paths (feed handlers, matching threads, latency measurement): it answers how expensive reading the clock is, how much to trust it, and how to test against it deterministically.
- Monotonic by construction (G1):
Timestamp::mononever goes backward within a process — enforced by a process-widefetch_maxfloor (with a critical-section fallback on targets without 64-bit atomics), not by trust in the underlying source. A regression is a bug, not a documented possibility. - Wall and monotonic are separate (REQ-CLK-002):
Timestampcarries both with independent rules. Wall time may step (NTP slew, PTP step, operator action); mono never steps and never regresses. The single cross-derivation in the API isTimestamp::mono_from_wall_lossy— explicitly labelled lossy. - Cheap reads behind calibration (REQ-CLK-003): x86_64
rdtsc/ aarch64cntvct_el0behind a calibrated 32.32 multiply-shift (no division on the read path); invariant-counter check before enabling the fast path; transparent fallback toInstant/CLOCK_MONOTONIC, with the active source observable throughClock::source(). - PTP detection, not participation (REQ-CLK-004):
detect_sync_qualityclassifiesPtp/Ntp/Fallbackfrom/sys/class/ptppresence plus a coherentCLOCK_TAIoffset probe. Read-only, best-effort, fail-closed to distrust. It runs no daemon and touches nothing. - Deterministic fiction (G3 / REQ-CLK-005):
MockClocknever consults the OS. Time advances only when the test advances it;step_wallsimulates NTP/PTP wall steps without touching mono. - Allocation-free latency statistics (REQ-CLK-006):
LatencyRing<CAP>is a fixed-capacity sliding window of nanos;stats()is exact over the current window (iterative heapsort, stack scratch, zero heap allocation) reporting min/max/mean/p50/p99/p99.9 at nearest rank. #![deny(unsafe_code)],#![deny(missing_docs)], clippyunwrap_used/expect_used/panic/indexing_slicingdenied. The crate's only unsafe is the hardware-counter reads and theclock_gettimeFFI wrappers, confined tosrc/low.rs— every other module re-establishes#![forbid(unsafe_code)]locally (see SECURITY.md).
Install
[]
= "0.1"
Example
use ;
use Duration;
// Production: observe cost and trust, not just time.
let t = new.now;
let _ = ;
// Deterministic tests: the OS is never consulted.
let clock = start_at;
clock.advance;
clock.step_wall; // NTP-style wall step; mono untouched
assert_eq!;
assert_eq!;
assert_eq!;
// Latency stats: exact over a fixed window, no allocation.
let mut ring = default;
ring.record;
ring.record;
let s = ring.stats;
assert_eq!;
The chronoshift bridge — one estate time idiom
chronoshift (L0) owns time abstraction; clock-kit (L1) owns time
precision and cost. clock-kit does not mint a rival trait. With the
chronoshift feature, a blanket impl makes every chronoshift::Clock
a clock_kit::Clock: its now_ns() is bridged into a full Timestamp
(negative pre-epoch values clamp to 0) with the monotonic floor applied,
quality() fails closed to Fallback, and source() reports External.
[]
= { = "0.1", = ["chronoshift"] }
Note: types implementing chronoshift::Clock become clock-kit Clocks
automatically — do not also impl clock_kit::Clock for them (coherence).
The blanket points one way; clock-kit's own clocks are not
chronoshift::Clocks.
Feature flags
| Feature | Default | Description |
|---|---|---|
std |
yes | SystemTime-backed wall, Instant-anchored mono |
libc |
no | clock_gettime(2) wall/mono for no_std hosts |
tsc |
no | Hardware counters (x86_64 rdtsc, aarch64 cntvct_el0) behind calibration; per-arch cfg(target_arch) inside one feature |
ptp |
no | Sync-quality detection (/sys/class/ptp + CLOCK_TAI); implies std |
chronoshift |
no | Blanket Clock impl for every chronoshift::Clock |
no_std posture
--no-default-features builds pure core: Timestamp, WallTime,
Clock, MockClock, LatencyRing, SyncQuality, and the public
mono_floor. Wall reads need std or libc; the calibrated clock needs
std or libc as its reference. On targets without 64-bit atomics
(e.g. thumbv7em), the floor and mock cells fall back to
critical-section — the impl is provided by the final binary, per
critical-section convention (the chronoshift pattern).
Calibration model and drift caveat
calibrate(reps) brackets counter reads between reference-clock reads and
derives a 32.32 ns-per-tick factor (two independent measurements must
agree within 1%). Conversion is multiply-shift only:
ns = ticks * ns_per_tick + (ticks * frac) >> 32
Calibration is repeatable, not continuous: thermal drift and frequency
scaling slowly bias the factor. Consumers needing hard accuracy over long
runs should re-call calibrate periodically (the factor is cheap to swap:
CalibratedClock::with_calibration). The invariant-TSC CPUID check guards
against non-constant-rate counters at construction; a per-socket TSC skew
caveat applies to multi-socket hosts (documented; see SECURITY.md).
Percentile semantics
LatencyRing::stats is exact over the window (sorts a bounded stack
copy), not a streaming approximation — HFT latency gates want exact
numbers over a known window. Percentiles are nearest-rank, re-implemented
lean from the percentile-kit pattern: percentile-kit is L2 and cannot be a
dependency of this L1 crate (estate layer rule: no upward edges). If the
ring is ever shared, the shared primitive must be lifted down to L0.
MSRV
Rust 1.85 (edition 2021).
License
MIT OR Apache-2.0.