refactor(stats): own retention memory estimates #97

Merged
Lochnair merged 1 commits from refactor/stats-retention-estimate into main 2026-08-15 03:30:44 +00:00
Lochnair commented 2026-08-15 03:25:52 +00:00 (Migrated from github.com)

Summary

  • irtt-stats now owns the finite/exact retained-memory estimate, exposed as
    StatsConfig::estimated_retained_bytes(probe_count) -> u64.
  • The estimator is derived from the crate's actual retention model —
    exact-sample vectors, IPDV tracker state, and count-based rolling storage —
    rather than from a magic number.
  • irtt-cli no longer carries an independent bytes-per-probe heuristic;
    FINITE_STATS_BYTES_PER_PROBE is gone.

Ownership

The boundary this establishes:

  • irtt-stats knows what it retains, and estimates the retained storage.
  • irtt-cli knows the expected probe count, the number of targets, when to
    warn, the warning thresholds, and the human guidance.

The CLI no longer duplicates knowledge of which TimeMetric fields retain
samples, nor any size assumption about IPDV samples or normalized events.

Estimate semantics

Included:

  • exact timing samples, for the metrics that actually retain them;
  • adjacent-sequence IPDV tracking state (sample map, sequence order queue,
    completed-pair set);
  • count-based rolling storage when rolling_count is set, assuming a probe
    contributes one send event and one unique reply event.

Excluded:

  • time-based rolling storage — rolling_time retains whatever arrives inside
    its window, which cannot be bounded from a probe count alone without
    assuming traffic timing, so it is documented as excluded rather than
    silently ignored;
  • everything outside the crate: sockets, client session state, managed
    counters, TUI graph history, rendered output, and caller structures.

SampleMode:

  • Exact retains one value per metric per measurable reply and keeps
    unbounded IPDV state, so the estimate grows linearly with the probe count
    (currently ~668 bytes/probe on a 64-bit target).
  • RunningOnly retains no exact samples and bounds its IPDV state at the
    continuous sequence limit, so the estimate reaches a ceiling (~1.6 MiB) and
    stops growing. It deliberately does not report exact-mode per-probe growth.

Optional measurements: adjusted RTT, one-way delay, and send/receive IPDV are
only measured when the negotiated session supplies the necessary timestamps.
The estimate assumes all of them are available, which is the upper bound.

Approximation and saturation: sizes come from size_of on the retained types,
with a documented capacity-headroom factor of 2 for the amortized-doubling
containers involved. This is a planning aid, not allocator accounting, and it
errs toward overestimating. The result is deterministic and computed with
saturating arithmetic, so an enormous probe count saturates at u64::MAX
rather than wrapping or panicking.

Behavior

  • The warning still runs before the managed run starts.
  • FINITE_STATS_MEMORY_WARNING_BYTES, ..._STRONG_..., and
    ..._VERY_STRONG_... stay in the CLI, unchanged, along with the existing
    wording and guidance.
  • No statistics retention behavior changed. The only edit in
    crates/irtt-stats/src/core.rs is making CONTINUOUS_SEQUENCE_LIMIT
    pub(crate); every TimeMetric::new(..) argument is untouched.

Scope

Deliberately not in this PR:

  • no new exact medians — send_call, timer_error, and server_processing
    still do not retain exact samples in finite mode;
  • no TimeStats redesign and no change to median_ns availability;
  • no rolling API changes;
  • no SampleMode semantic changes;
  • no client, server, protocol, or TUI behavior changes.

Verification

All run locally on this branch, all passing:

  • cargo test -p irtt-stats --all-features --locked
  • cargo test -p irtt-cli --all-features --locked
  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets --all-features --locked
  • cargo test --workspace --all-targets --all-features --locked
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked
  • cargo check -p irtt-cli --no-default-features --features client --all-targets --locked
  • cargo check -p irtt-cli --no-default-features --features tui --all-targets --locked
  • cargo check -p irtt-cli --no-default-features --features server --all-targets --locked
  • git diff --check

The client and tui feature-only checks emit pre-existing dead-code
warnings for unused helpers in crates/irtt-cli/src/cmd/format.rs, unrelated
to and untouched by this change.

Tests

Added crates/irtt-stats/tests/retention.rs: zero probes, exact-mode
monotonic growth, exact-mode linearity, saturation at u64::MAX, running-only
boundedness, a deterministic finite-config estimate in a plausible range,
count-based rolling sensitivity and its cap, and time-based rolling exclusion.
Two unit tests in retention.rs cover the non-zero per-item components and the
running-only ceiling.

Updated the two CLI tests to go through the new estimator. The target-count
test now derives the threshold-crossing target count from
StatsConfig::estimated_retained_bytes and asserts no warning just below it
and a warning at it, so it exercises the boundary and survives future
retention changes instead of pinning the old 500-byte heuristic.

## Summary - `irtt-stats` now owns the finite/exact retained-memory estimate, exposed as `StatsConfig::estimated_retained_bytes(probe_count) -> u64`. - The estimator is derived from the crate's actual retention model — exact-sample vectors, IPDV tracker state, and count-based rolling storage — rather than from a magic number. - `irtt-cli` no longer carries an independent bytes-per-probe heuristic; `FINITE_STATS_BYTES_PER_PROBE` is gone. ## Ownership The boundary this establishes: - `irtt-stats` knows what it retains, and estimates the retained storage. - `irtt-cli` knows the expected probe count, the number of targets, when to warn, the warning thresholds, and the human guidance. The CLI no longer duplicates knowledge of which `TimeMetric` fields retain samples, nor any size assumption about IPDV samples or normalized events. ## Estimate semantics Included: - exact timing samples, for the metrics that actually retain them; - adjacent-sequence IPDV tracking state (sample map, sequence order queue, completed-pair set); - count-based rolling storage when `rolling_count` is set, assuming a probe contributes one send event and one unique reply event. Excluded: - time-based rolling storage — `rolling_time` retains whatever arrives inside its window, which cannot be bounded from a probe count alone without assuming traffic timing, so it is documented as excluded rather than silently ignored; - everything outside the crate: sockets, client session state, managed counters, TUI graph history, rendered output, and caller structures. `SampleMode`: - `Exact` retains one value per metric per measurable reply and keeps unbounded IPDV state, so the estimate grows linearly with the probe count (currently ~668 bytes/probe on a 64-bit target). - `RunningOnly` retains no exact samples and bounds its IPDV state at the continuous sequence limit, so the estimate reaches a ceiling (~1.6 MiB) and stops growing. It deliberately does not report exact-mode per-probe growth. Optional measurements: adjusted RTT, one-way delay, and send/receive IPDV are only measured when the negotiated session supplies the necessary timestamps. The estimate assumes all of them are available, which is the upper bound. Approximation and saturation: sizes come from `size_of` on the retained types, with a documented capacity-headroom factor of 2 for the amortized-doubling containers involved. This is a planning aid, not allocator accounting, and it errs toward overestimating. The result is deterministic and computed with saturating arithmetic, so an enormous probe count saturates at `u64::MAX` rather than wrapping or panicking. ## Behavior - The warning still runs before the managed run starts. - `FINITE_STATS_MEMORY_WARNING_BYTES`, `..._STRONG_...`, and `..._VERY_STRONG_...` stay in the CLI, unchanged, along with the existing wording and guidance. - No statistics retention behavior changed. The only edit in `crates/irtt-stats/src/core.rs` is making `CONTINUOUS_SEQUENCE_LIMIT` `pub(crate)`; every `TimeMetric::new(..)` argument is untouched. ## Scope Deliberately not in this PR: - no new exact medians — `send_call`, `timer_error`, and `server_processing` still do not retain exact samples in finite mode; - no `TimeStats` redesign and no change to `median_ns` availability; - no rolling API changes; - no `SampleMode` semantic changes; - no client, server, protocol, or TUI behavior changes. ## Verification All run locally on this branch, all passing: - `cargo test -p irtt-stats --all-features --locked` - `cargo test -p irtt-cli --all-features --locked` - `cargo fmt --all -- --check` - `cargo check --workspace --all-targets --all-features --locked` - `cargo test --workspace --all-targets --all-features --locked` - `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` - `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked` - `cargo check -p irtt-cli --no-default-features --features client --all-targets --locked` - `cargo check -p irtt-cli --no-default-features --features tui --all-targets --locked` - `cargo check -p irtt-cli --no-default-features --features server --all-targets --locked` - `git diff --check` The `client` and `tui` feature-only checks emit pre-existing dead-code warnings for unused helpers in `crates/irtt-cli/src/cmd/format.rs`, unrelated to and untouched by this change. ### Tests Added `crates/irtt-stats/tests/retention.rs`: zero probes, exact-mode monotonic growth, exact-mode linearity, saturation at `u64::MAX`, running-only boundedness, a deterministic finite-config estimate in a plausible range, count-based rolling sensitivity and its cap, and time-based rolling exclusion. Two unit tests in `retention.rs` cover the non-zero per-item components and the running-only ceiling. Updated the two CLI tests to go through the new estimator. The target-count test now derives the threshold-crossing target count from `StatsConfig::estimated_retained_bytes` and asserts no warning just below it and a warning at it, so it exercises the boundary and survives future retention changes instead of pinning the old 500-byte heuristic.
Sign in to join this conversation.