refactor(client): correct late-reply docs and drop private counter tests

The counter tests instantiated the private TargetCounters and mirrored its
match arms, duplicating outcome-level coverage that already asserts
replies_received and late through ManagedTargetOutcome, and coupling a
private refactor to persistent test churn.

The new counter documentation also defined late replies in timeout-only
terms. A reply that arrives behind an already-received sequence is
classified late by process_echo_reply even when its own probe has not timed
out, so both counters are now described in terms of order and timeout.
This commit is contained in:
Nils Andreas Svee committed 2026-08-15 01:49:25 +07:00
1 parent 5506220f67
commit dd68608d56
3 files changed
+16 -114

No files matched your search

+9 -5
View File
@@ -209,15 +209,19 @@ pub struct ManagedTargetOutcome {
pub end_reason: ManagedTargetEndReason, pub end_reason: ManagedTargetEndReason,
/// Probes the underlying client reported as sent. /// Probes the underlying client reported as sent.
pub packets_sent: u64, pub packets_sent: u64,
/// On-time unique echo replies. /// Unique echo replies that arrived in order and before their probe timed
/// out.
/// ///
/// A unique reply that arrived after its probe timed out is counted by /// A unique reply that arrives behind an already-received sequence, or
/// `late` instead, so this is the count of replies that arrived in time. /// after its own probe timed out, is counted by `late` instead.
pub replies_received: u64, pub replies_received: u64,
/// Replies for a sequence that had already been answered. /// Replies for a sequence that had already been answered.
pub duplicates: u64, pub duplicates: u64,
/// Replies that arrived after their probe timed out, whether or not the /// Late replies: those arriving behind an already-received sequence
/// client could still match them to retained send state. /// (reordering) or after their own probe timed out.
///
/// This counts every late reply, whether or not the client could still
/// match it to retained send state.
pub late: u64, pub late: u64,
/// Warning events observed for this target. /// Warning events observed for this target.
pub warning_events: u64, pub warning_events: u64,
+7 -6
View File
@@ -394,15 +394,16 @@ impl TargetState {
} }
} }
#[derive(Debug, Default, Eq, PartialEq)] #[derive(Default)]
struct TargetCounters { struct TargetCounters {
packets_sent: u64, packets_sent: u64,
/// On-time unique echo replies. /// Unique echo replies that arrived in order and before their probe timed
/// out.
/// ///
/// A late unique reply is counted by `late` and deliberately not here, so /// A reply that arrives behind an already-received sequence, or after its
/// `replies_received` stays the count of replies that arrived within their /// own probe timed out, is counted by `late` and deliberately not here.
/// probe timeout. These counters are durable, unlike the lossy managed /// These counters are durable, unlike the lossy managed presentation
/// presentation events. /// events.
replies_received: u64, replies_received: u64,
duplicates: u64, duplicates: u64,
late: u64, late: u64,
-103
View File
@@ -2954,106 +2954,3 @@ fn terminal_subscription_is_closed() {
Err(ManagedSubscribeError::Closed) Err(ManagedSubscribeError::Closed)
)); ));
} }
/// Build the client events target accounting distinguishes.
fn accounting_events() -> Vec<ClientEvent> {
let remote: std::net::SocketAddr = "127.0.0.1:2112".parse().unwrap();
let at = ClientTimestamp {
mono: Instant::now(),
wall: SystemTime::UNIX_EPOCH,
};
let rtt = crate::RttSample {
raw: Duration::from_millis(1),
adjusted: None,
effective: crate::SignedDuration::from_nanos(1_000_000),
};
vec![
ClientEvent::EchoSent {
seq: 0,
remote,
scheduled_at: at.mono,
sent_at: at,
bytes: 64,
send_call: Duration::ZERO,
timer_error: Duration::ZERO,
},
ClientEvent::EchoReply {
seq: 0,
remote,
sent_at: at,
received_at: at,
rtt,
server_timing: None,
one_way: None,
received_stats: None,
bytes: 64,
packet_meta: crate::PacketMeta::default(),
},
ClientEvent::DuplicateReply {
seq: 0,
remote,
received_at: at,
bytes: 64,
},
ClientEvent::LateReply {
seq: 1,
highest_seen: 2,
remote,
sent_at: Some(at),
received_at: at,
rtt: Some(rtt),
server_timing: None,
one_way: None,
received_stats: None,
bytes: 64,
packet_meta: crate::PacketMeta::default(),
},
ClientEvent::EchoLoss {
seq: 2,
sent_at: at,
timeout_at: at.mono,
},
ClientEvent::Warning {
kind: crate::WarningKind::WrongToken,
message: "wrong token".to_owned(),
at,
},
]
}
#[test]
fn target_counters_account_for_each_observed_event_once() {
let mut counters = TargetCounters::default();
for event in accounting_events() {
counters.observe(&event);
}
// A late unique reply is counted as late, not as a received reply, and a
// probe send is not accounted here because the client owns packets_sent.
assert_eq!(counters.packets_sent, 0);
assert_eq!(counters.replies_received, 1);
assert_eq!(counters.duplicates, 1);
assert_eq!(counters.late, 1);
assert_eq!(counters.warning_events, 1);
}
#[test]
fn target_counters_ignore_events_without_a_durable_count() {
let ignored: Vec<ClientEvent> = accounting_events()
.into_iter()
.filter(|event| {
matches!(
event,
ClientEvent::EchoSent { .. } | ClientEvent::EchoLoss { .. }
)
})
.collect();
assert_eq!(ignored.len(), 2);
let mut counters = TargetCounters::default();
for event in ignored {
counters.observe(&event);
}
assert_eq!(counters, TargetCounters::default());
}