docs(stats): scope the exact-median contract to the cumulative snapshot
Rolling snapshots are recomputed with `SampleMode::RunningOnly` regardless of the configured sample mode, so the new `SampleMode::Exact` wording — that every timing metric a `Snapshot` exposes reports a median — was not true of `rolling_count()` and `rolling_time()`. Scope the promise to the cumulative snapshot and say plainly on both rolling accessors that they use running statistics only and report no median, since a rolling window is a bounded recent view rather than the run's retained history. A test pins both halves so the wording cannot quietly go stale. Rolling retention behavior itself is unchanged.
This commit is contained in:
2 files changed
+61
-10
No files matched your search
@@ -28,7 +28,8 @@ pub use time_stats::TimeStats;
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
/// Configuration for statistics collection.
|
/// Configuration for statistics collection.
|
||||||
pub struct StatsConfig {
|
pub struct StatsConfig {
|
||||||
/// How timing samples are retained, and so whether medians are available.
|
/// How timing samples are retained, and so whether the cumulative snapshot
|
||||||
|
/// reports medians.
|
||||||
pub samples: SampleMode,
|
pub samples: SampleMode,
|
||||||
/// Number of recent normalized events retained for count-based rolling snapshots.
|
/// Number of recent normalized events retained for count-based rolling snapshots.
|
||||||
///
|
///
|
||||||
@@ -45,9 +46,9 @@ impl StatsConfig {
|
|||||||
/// Returns the default configuration for finite runs.
|
/// Returns the default configuration for finite runs.
|
||||||
///
|
///
|
||||||
/// Finite mode retains exact samples for every timing metric, so each of
|
/// Finite mode retains exact samples for every timing metric, so each of
|
||||||
/// them reports a median once it has samples, and keeps unbounded
|
/// them reports a median in the cumulative snapshot once it has samples,
|
||||||
/// adjacent-sequence IPDV tracking so late adjacent replies can still
|
/// and keeps unbounded adjacent-sequence IPDV tracking so late adjacent
|
||||||
/// complete IPDV pairs.
|
/// replies can still complete IPDV pairs.
|
||||||
pub fn finite() -> Self {
|
pub fn finite() -> Self {
|
||||||
Self {
|
Self {
|
||||||
samples: SampleMode::Exact,
|
samples: SampleMode::Exact,
|
||||||
@@ -154,15 +155,18 @@ impl Default for StatsConfig {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
/// Controls whether exact timing samples are retained.
|
/// Controls whether exact timing samples are retained.
|
||||||
///
|
///
|
||||||
/// The choice applies uniformly to every timing metric a [`Snapshot`] exposes,
|
/// This governs the cumulative snapshot from [`StatsCollector::snapshot`],
|
||||||
/// so a snapshot never mixes median-capable and median-incapable timing
|
/// where the choice applies uniformly to every timing metric, so that snapshot
|
||||||
/// metrics.
|
/// never mixes median-capable and median-incapable timing metrics. Rolling
|
||||||
|
/// snapshots are recomputed with running statistics whatever this is set to;
|
||||||
|
/// see [`StatsCollector::rolling_count`].
|
||||||
pub enum SampleMode {
|
pub enum SampleMode {
|
||||||
/// Keep only running statistics; medians are not available for any timing
|
/// Keep only running statistics; medians are not available for any timing
|
||||||
/// metric, and retention stays bounded for long-running sessions.
|
/// metric, and retention stays bounded for long-running sessions.
|
||||||
RunningOnly,
|
RunningOnly,
|
||||||
/// Retain exact samples for every timing metric, so each one reports a
|
/// Retain exact samples for every timing metric, so each one reports a
|
||||||
/// median once it has samples. Retention grows with the probe count.
|
/// median once it has samples in the cumulative snapshot. Retention grows
|
||||||
|
/// with the probe count.
|
||||||
Exact,
|
Exact,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,20 +205,28 @@ impl StatsCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a snapshot of all events processed by this collector.
|
/// Returns a snapshot of all events processed by this collector.
|
||||||
|
///
|
||||||
|
/// This is the snapshot [`StatsConfig::samples`] applies to, so under
|
||||||
|
/// [`SampleMode::Exact`] every timing metric here reports a median once it
|
||||||
|
/// has samples.
|
||||||
pub fn snapshot(&self) -> Snapshot {
|
pub fn snapshot(&self) -> Snapshot {
|
||||||
self.cumulative.snapshot()
|
self.cumulative.snapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the configured count-based rolling snapshot, if enabled.
|
/// Returns the configured count-based rolling snapshot, if enabled.
|
||||||
///
|
///
|
||||||
/// The snapshot is recomputed from the retained rolling events.
|
/// The snapshot is recomputed from the retained rolling events using
|
||||||
|
/// running statistics only, whatever [`StatsConfig::samples`] is set to,
|
||||||
|
/// so its timing metrics report no median. A rolling window is a bounded
|
||||||
|
/// recent view rather than the run's retained history.
|
||||||
pub fn rolling_count(&self) -> Option<Snapshot> {
|
pub fn rolling_count(&self) -> Option<Snapshot> {
|
||||||
self.rolling.count_snapshot()
|
self.rolling.count_snapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the configured time-based rolling snapshot, if enabled.
|
/// Returns the configured time-based rolling snapshot, if enabled.
|
||||||
///
|
///
|
||||||
/// The snapshot is recomputed from the retained rolling events.
|
/// Like [`StatsCollector::rolling_count`], this is recomputed with running
|
||||||
|
/// statistics only and reports no medians.
|
||||||
pub fn rolling_time(&self) -> Option<Snapshot> {
|
pub fn rolling_time(&self) -> Option<Snapshot> {
|
||||||
self.rolling.time_snapshot()
|
self.rolling.time_snapshot()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -664,6 +664,45 @@ fn untracked_late_replies_stay_untracked_in_both_policies() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rolling_snapshots_report_no_medians_even_in_exact_mode() {
|
||||||
|
let mut collector = StatsCollector::new(StatsConfig {
|
||||||
|
rolling_count: Some(8),
|
||||||
|
rolling_time: Some(Duration::from_secs(60)),
|
||||||
|
..StatsConfig::finite()
|
||||||
|
});
|
||||||
|
for seq in 0..3 {
|
||||||
|
collector.process(&sent(seq, ts(u64::from(seq) * 10)));
|
||||||
|
collector.process(&adjusted_reply(
|
||||||
|
seq,
|
||||||
|
10 + u64::from(seq),
|
||||||
|
9 + i128::from(seq),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The cumulative snapshot keeps the exact-mode contract...
|
||||||
|
let cumulative = collector.snapshot();
|
||||||
|
assert!(cumulative.rtt.primary.median_ns.is_some());
|
||||||
|
assert!(cumulative.send_call.median_ns.is_some());
|
||||||
|
assert!(cumulative.server_processing.processing.median_ns.is_some());
|
||||||
|
|
||||||
|
// ...while a rolling window is a bounded recent view recomputed from
|
||||||
|
// running statistics, so it reports no median whatever the sample mode.
|
||||||
|
for rolling in [
|
||||||
|
collector.rolling_count().unwrap(),
|
||||||
|
collector.rolling_time().unwrap(),
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
rolling.rtt.primary.count > 0,
|
||||||
|
"the window should have samples"
|
||||||
|
);
|
||||||
|
assert_eq!(rolling.rtt.primary.median_ns, None);
|
||||||
|
assert_eq!(rolling.send_call.median_ns, None);
|
||||||
|
assert_eq!(rolling.timer_error.median_ns, None);
|
||||||
|
assert_eq!(rolling.server_processing.processing.median_ns, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rolling_snapshots_use_the_same_late_reply_policy() {
|
fn rolling_snapshots_use_the_same_late_reply_policy() {
|
||||||
let mut collector = StatsCollector::new(StatsConfig {
|
let mut collector = StatsCollector::new(StatsConfig {
|
||||||
|
|||||||
Reference in new issue
Block a user