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.
- 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)
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).
impatience ships with two example programs.
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 2000Measure 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| 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 |
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.
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.
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.
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.
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.
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.
PeerClock is Clone + Send + Sync (backed by Arc<Mutex<_>>).
The lower-level types (TimeSynchroniser, SyncedClock, WindowedMinTS24) are single-threaded.
| 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 impatienceGPL-3.0, see LICENSE.md for details.
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.