A Rust library for measuring event-to-event latency across networked peers with automatic clock synchronisation.
The library is I/O-agnostic: it has zero dependencies on std::net or threading.
Bring your own UDP socket and event loop.
Two example CLI programs (sync-test and latency-demo) are included to demonstrate real-world usage.
Features
- Four abstraction levels from high-level instrumentation down to the raw synchronisation algorithm
- UDP-based clock sync using a windowed-minimum one-way-delay estimator with 24-bit truncated timestamps
- Zero-copy serialisation of packet types via
rkyv - Non-blocking handshake (
Initiator/Responder) for establishing peer clocks no_stdsupport at thetimesyncalgorithm layer- Optional features:
serde,uncertainty,svg(see below)
Quick Start
use impatience::clocks::PeerClock; use impatience::instrumentation::Profiler; use impatience::time; let clock = PeerClock::new(); clock.start(time::now_usec()); let profiler = Profiler::new(clock.clone()); let mut span = profiler.start("input-to-print", clock.local_ms()); let remote_finish_ms = clock.local_ms() + 50; if let Some(latency_ms) = profiler.finish_remote(&mut span, remote_finish_ms) { println!("Latency: {} ms", latency_ms); }
For a complete networked example with handshake and sync scheduling, see the crate-level documentation (cargo doc --open).
CLI Tools
impatience ships with two example programs.
sync-test
Debug clock synchronisation between two machines over UDP.
# Server impatience sync-test --server 0.0.0.0 --port 7340 # Client impatience sync-test --client 192.168.1.5 --port 7340 \ --count 100 --interval 400 --sync-interval 2000
latency-demo
Measure end-to-end event latency. The client sends keyboard input to the server after a random delay; the server echoes a completion timestamp. Results are printed to the terminal and saved as an HTML report.
# Server impatience latency-demo --server 0.0.0.0 --port 7341 # Client impatience latency-demo --client 192.168.1.5 --port 7341 \ --sync-interval 2000 --max-delay 100
Library Overview
| Module | Purpose |
|---|---|
instrumentation |
Profiler, Span, LatencyAggregator |
clocks |
PeerClock (thread-safe), SyncedClock (raw), formatting helpers |
net |
Handshake (Initiator / Responder), SyncScheduler, packet types |
timesync |
TimeSynchroniser algorithm and rollover-safe Counter24 |
time |
Wall-clock time utilities |
Level 1: Instrumentation
Use Profiler and PeerClock for application-level latency tracking.
Profiler::start creates a Span; Profiler::finish_remote records the latency when a remote completion timestamp arrives.
LatencyAggregator and Snapshot provide percentile statistics and console reporting.
Level 2: Network Protocol
Use net::Initiator and net::Responder for the two-way handshake that establishes peer start times.
SyncScheduler tracks when to emit periodic sync heartbeats.
Packet types (Packet, StartClockPacket, SyncPacket, etc.) are archived with rkyv and serialised via Packet::to_bytes and Packet::from_bytes.
Level 3: Clock Primitives
Use SyncedClock when you need raw probe and sync update methods plus correction values without the thread-safe PeerClock wrapper.
It is single-threaded and does not track peer start times; callers must manage thread safety and peer-start tracking themselves.
Level 4: Algorithm Core
Use TimeSynchroniser and Counter24 to study or extend the windowed-minimum one-way delay algorithm and rollover-safe fixed-bit-width counter arithmetic.
This layer is suitable for porting the algorithm to other languages or experimenting with custom windowing strategies.
Interoperability
Wire Format
Built-in packet types are serialised with rkyv.
Non-Rust peers must either link an rkyv deserializer or parse the archived bytes directly.
Custom formats (JSON, Protobuf, etc.) are supported by implementing the Probe and PeerSync traits.
PeerClock and SyncedClock work with any type that implements these traits, so the built-in packet types are optional.
Protocol
Clock synchronisation runs over UDP in two phases:
- Handshake: Client sends
StartClock, server replies withAckStartClock. - Periodic sync: Both peers exchange
SyncPacketcontaining a 24-bit truncated local timestamp and a minimum one-way-delay estimate. The receiver expands the truncated timestamp back to 64 bits using rollover-safeCounter24arithmetic.
Thread Safety
PeerClock is Clone + Send + Sync (backed by Arc<Mutex<_>>).
The lower-level types (TimeSynchroniser, SyncedClock, WindowedMinTS24) are single-threaded.
Cargo Features
| Feature | Default | Description |
|---|---|---|
std |
yes | Enables time, clocks, net, instrumentation, and rkyv packet serialisation |
alloc |
implied by std |
Foundation for no_std environments with heap allocation |
cli |
yes | Builds the impatience binary with the sync-test and latency-demo tools |
svg |
yes | SVG chart generation (histogram_svg, scatter_plot_svg) |
serde |
yes | Serialize/Deserialize derives on select types (e.g. Snapshot) |
uncertainty |
no | Statistical confidence intervals via statrs |
Use in no_std environments (only the timesync module is available):
[dependencies] impatience = { version = "1.0", default-features = false }
Enable the uncertainty feature for statistical confidence intervals:
[dependencies] impatience = { version = "1.0", features = ["uncertainty"] }
The CLI tools can be installed with:
cargo install impatience
License
GPL-3.0, see LICENSE.md for details.
Acknowledgements
This project used the TimeSync library by Chris Taylor as a basis. Indeed, the timesync module is more or less a straight port of that library to Rust.



















