pub struct LatencyTracker<D, I: Instant = Instant, H = RandomState, const N: usize = DEFAULT_SUB_WINDOWS> { /* private fields */ }Expand description
Tracks latencies per destination and provides quantile estimates.
Each destination gets its own sliding-window histogram. Create one tracker per service/operation type to track latencies independently.
§Type parameters
D— destination key (node, endpoint, shard, …).I— time source, defaults tostd::time::Instant.N— number of sub-windows in each sliding-window histogram, defaults toDEFAULT_SUB_WINDOWS(10).
§Sliding window and sub-windows
Think of it like the Linux load average displayed by top: the 1-min,
5-min, and 15-min averages all track the same metric but react to changes
at different speeds. A shorter window (1-min) catches spikes quickly but
is noisy; a longer window (15-min) is smoother but slow to reflect new
conditions.
Here, two settings control this behaviour:
window_ms sets how far back the
histogram looks (default: 60s). This is the “memory” of the tracker —
like choosing between a 1-min or 15-min load average:
-
Longer window (e.g. 5 min): more samples, more stable estimates, but slower to react to sudden latency changes. Good for low-traffic destinations where samples arrive infrequently.
-
Shorter window (e.g. 5s): reacts quickly to latency shifts, but with fewer samples the estimates are noisier. May drop below
min_samplesduring traffic lulls, causing the system to fall back to exponential backoff.
N controls how smoothly old data is shed as the window slides
forward. The window is divided into N equal sub-windows, and as time
advances, sub-windows rotate out one at a time:
-
Higher
N(e.g. 20): samples expire in small increments (window / Nper step), so quantile estimates transition smoothly — like a load average that updates every few seconds. The cost is more memory (each sub-window is a separate HdrHistogram allocation). -
Lower
N(e.g. 3): samples expire in large chunks. Uses less memory, but a bigger fraction of data disappears at once — like a load average that only updates every few minutes, causing jumpy readings. -
N = 1: the entire window expires at once — a tumbling window. The histogram alternates between “full of data” and “completely empty” eachwindow_msperiod.
The two settings interact: the effective rotation interval is
window_ms / N. With the defaults (window_ms = 60_000, N = 10), each
sub-window covers 6 seconds — old data is shed in 10% increments every
6 seconds. If you want finer granularity (e.g. 1-second rotations at a
60s window), set N = 60.
§Example
use std::time::{Duration, Instant};
use adaptive_timeout::LatencyTracker;
let now = Instant::now();
let mut tracker = LatencyTracker::<u32, Instant>::default();
for _ in 0..100 {
tracker.record_latency_ms(&1u32, 50, now);
}
let p99 = tracker.quantile_ms(&1u32, 0.99, now);
assert_eq!(p99, Some(50));Implementations§
Source§impl<D, I, const N: usize> LatencyTracker<D, I, FixedState, N>
impl<D, I, const N: usize> LatencyTracker<D, I, FixedState, N>
Sourcepub const fn const_new(config: TrackerConfig) -> Self
pub const fn const_new(config: TrackerConfig) -> Self
Creates a new tracker with the given configuration.
Source§impl<D, I, H, const N: usize> LatencyTracker<D, I, H, N>
impl<D, I, H, const N: usize> LatencyTracker<D, I, H, N>
Sourcepub fn new(config: TrackerConfig) -> Self
pub fn new(config: TrackerConfig) -> Self
Creates a new tracker with the given configuration.
Source§impl<D, I, H, const N: usize> LatencyTracker<D, I, H, N>
impl<D, I, H, const N: usize> LatencyTracker<D, I, H, N>
pub const fn with_hasher_and_config(hasher: H, config: TrackerConfig) -> Self
Source§impl<D, I, H, const N: usize> LatencyTracker<D, I, H, N>
impl<D, I, H, const N: usize> LatencyTracker<D, I, H, N>
Sourcepub fn record_latency_from<Q>(
&mut self,
dest: &Q,
earlier: I,
now: I,
) -> Duration
pub fn record_latency_from<Q>( &mut self, dest: &Q, earlier: I, now: I, ) -> Duration
Records a latency sample given two instants. Returns the computed duration.
let now = Instant::now();
let mut tracker = LatencyTracker::<u32, Instant>::default();
let later = now + Duration::from_millis(42);
let latency = tracker.record_latency_from(&1u32, now, later);
assert_eq!(latency, Duration::from_millis(42));Sourcepub fn record_latency<Q>(&mut self, dest: &Q, latency: Duration, now: I)
pub fn record_latency<Q>(&mut self, dest: &Q, latency: Duration, now: I)
Records a latency sample as a Duration.
Sourcepub fn record_latency_ms<Q>(&mut self, dest: &Q, latency_ms: u64, now: I)
pub fn record_latency_ms<Q>(&mut self, dest: &Q, latency_ms: u64, now: I)
Records a latency sample in milliseconds. This is the fastest
recording path — no Duration conversion, no allocation on the
hot path (destination already seen).
Sourcepub fn quantile_ms<Q>(&mut self, dest: &Q, quantile: f64, now: I) -> Option<u64>
pub fn quantile_ms<Q>(&mut self, dest: &Q, quantile: f64, now: I) -> Option<u64>
Returns the estimated latency in milliseconds at the given quantile,
or None if insufficient data.
Sourcepub fn quantile<Q>(
&mut self,
dest: &Q,
quantile: f64,
now: I,
) -> Option<Duration>
pub fn quantile<Q>( &mut self, dest: &Q, quantile: f64, now: I, ) -> Option<Duration>
Returns the estimated latency as a Duration at the given quantile,
or None if insufficient data.
Sourcepub fn config(&self) -> &TrackerConfig
pub fn config(&self) -> &TrackerConfig
Returns a reference to the tracker configuration.