refactor(stats): retain exact finite timing samples

`SampleMode::Exact` retained exact samples for eight of the eleven public
timing metrics. Send call, timer error, and server processing always built
their `TimeMetric` with retention disabled, so a finite run reported no
median for them even though they are the same public `TimeStats` type and
appear in the same summary table with a Median column.

Derive one `retain_exact` decision from the sample mode in `CoreStats::new`
and apply it to every timing metric, so exact mode means the same thing for
all of them. Running-only mode is unchanged and still retains none of them.

The retention estimator counted eight exact sample streams per measurable
reply. Rename it to the eleven exact samples an ordinary successful probe
can retain and document the wider optional-measurement upper bound, which
raises the finite estimate on this target from 668 to 764 bytes per probe.
A test counts the metrics a fully measured probe actually retains and
compares it against that constant, so retention and its estimate cannot
drift apart unnoticed.

No new median-capability type was needed: `TimeStats::median_ns` already
distinguishes a retained median from an absent one, and an optional
measurement the server never supplied still produces no samples at all.
This commit is contained in:
Nils Andreas Svee committed 2026-08-15 10:41:17 +07:00
1 parent 76dc904da1
commit db9bd515bb
8 files changed
+491 -44

No files matched your search

+16 -12
View File
@@ -32,7 +32,11 @@ pub(crate) struct CoreStats {
impl CoreStats {
pub(crate) fn new(sample_mode: SampleMode, late_replies: LateReplyMode) -> Self {
let sequence_limit = if sample_mode == SampleMode::Exact {
// One decision for every public timing metric: in exact mode they all
// retain their samples so they can all report an exact median. The
// retention estimate in `retention.rs` counts the same set.
let retain_exact = sample_mode == SampleMode::Exact;
let sequence_limit = if retain_exact {
None
} else {
Some(CONTINUOUS_SEQUENCE_LIMIT)
@@ -41,17 +45,17 @@ impl CoreStats {
Self {
events: EventCounts::default(),
packets: PacketCounts::default(),
send_call: TimeMetric::new(false),
timer_error: TimeMetric::new(false),
rtt_primary: TimeMetric::new(sample_mode == SampleMode::Exact),
rtt_raw: TimeMetric::new(sample_mode == SampleMode::Exact),
rtt_adjusted: TimeMetric::new(sample_mode == SampleMode::Exact),
ipdv_round_trip: TimeMetric::new(sample_mode == SampleMode::Exact),
ipdv_send: TimeMetric::new(sample_mode == SampleMode::Exact),
ipdv_receive: TimeMetric::new(sample_mode == SampleMode::Exact),
send_delay: TimeMetric::new(sample_mode == SampleMode::Exact),
receive_delay: TimeMetric::new(sample_mode == SampleMode::Exact),
server_processing: TimeMetric::new(false),
send_call: TimeMetric::new(retain_exact),
timer_error: TimeMetric::new(retain_exact),
rtt_primary: TimeMetric::new(retain_exact),
rtt_raw: TimeMetric::new(retain_exact),
rtt_adjusted: TimeMetric::new(retain_exact),
ipdv_round_trip: TimeMetric::new(retain_exact),
ipdv_send: TimeMetric::new(retain_exact),
ipdv_receive: TimeMetric::new(retain_exact),
send_delay: TimeMetric::new(retain_exact),
receive_delay: TimeMetric::new(retain_exact),
server_processing: TimeMetric::new(retain_exact),
ipdv_tracker: IpdvTracker::new(sequence_limit),
late_replies,
}