Merge pull request #72 from Lochnair/audit/recv-window-downstream-loss
docs(stats): clarify server receive-window semantics
This commit is contained in:
5 files changed
+67
-3
No files matched your search
@@ -361,7 +361,10 @@ pub struct OneWayDelaySample {
|
|||||||
pub struct ReceivedStatsSample {
|
pub struct ReceivedStatsSample {
|
||||||
/// Server-reported count of received packets, when available.
|
/// Server-reported count of received packets, when available.
|
||||||
pub count: Option<u32>,
|
pub count: Option<u32>,
|
||||||
/// Server-reported receive window value, when available.
|
/// Raw server-reported receive window bitmap, when available.
|
||||||
|
///
|
||||||
|
/// The bitmap is preserved exactly as it appeared on the wire; the client
|
||||||
|
/// does not interpret its bits.
|
||||||
pub window: Option<u64>,
|
pub window: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -229,8 +229,20 @@ pub struct PacketCounts {
|
|||||||
/// exposed separately.
|
/// exposed separately.
|
||||||
pub bytes_received: u64,
|
pub bytes_received: u64,
|
||||||
/// Highest cumulative server-reported packets received, when available.
|
/// Highest cumulative server-reported packets received, when available.
|
||||||
|
///
|
||||||
|
/// This is the only server-reported input to the directional loss estimates
|
||||||
|
/// in [`LossStats`].
|
||||||
pub server_packets_received: Option<u64>,
|
pub server_packets_received: Option<u64>,
|
||||||
/// Server-reported receive window, when available.
|
/// Raw server-reported receive window from the most recently processed
|
||||||
|
/// reply that carried one, when available.
|
||||||
|
///
|
||||||
|
/// The value is stored exactly as the server reported it and is not
|
||||||
|
/// interpreted by this crate. It is a bounded bitmap of recent server
|
||||||
|
/// receive history rather than a fixed 64-packet loss mask, so a value of
|
||||||
|
/// `0x1` means no earlier history is represented and not that the previous
|
||||||
|
/// 63 packets were lost. Replies are aggregated in local arrival order, so
|
||||||
|
/// a late reply can leave an older window here than an already-processed
|
||||||
|
/// reply carried.
|
||||||
pub server_received_window: Option<u64>,
|
pub server_received_window: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ use crate::PacketCounts;
|
|||||||
/// Percentage fields are percentages from `0.0` to `100.0` in normal cases.
|
/// Percentage fields are percentages from `0.0` to `100.0` in normal cases.
|
||||||
/// Directional upstream and downstream loss can be negative when
|
/// Directional upstream and downstream loss can be negative when
|
||||||
/// server-reported counts exceed local expectations.
|
/// server-reported counts exceed local expectations.
|
||||||
|
///
|
||||||
|
/// The directional fields are derived from
|
||||||
|
/// [`PacketCounts::server_packets_received`] and are `None` when the server
|
||||||
|
/// reported no receive count. [`PacketCounts::server_received_window`] is never
|
||||||
|
/// used to derive loss.
|
||||||
pub struct LossStats {
|
pub struct LossStats {
|
||||||
/// Locally inferred total lost packets.
|
/// Locally inferred total lost packets.
|
||||||
pub lost_packets: u64,
|
pub lost_packets: u64,
|
||||||
|
|||||||
@@ -84,6 +84,22 @@ fn reply_with_rtt(seq: u32, raw_ms: u64, rtt: RttSample) -> ClientEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds a normal reply carrying exactly the supplied server received stats.
|
||||||
|
pub fn reply_with_received_stats(
|
||||||
|
seq: u32,
|
||||||
|
raw_ms: u64,
|
||||||
|
count: Option<u32>,
|
||||||
|
window: Option<u64>,
|
||||||
|
) -> ClientEvent {
|
||||||
|
let mut event = unadjusted_reply(seq, raw_ms);
|
||||||
|
let ClientEvent::EchoReply { received_stats, .. } = &mut event else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
*received_stats =
|
||||||
|
(count.is_some() || window.is_some()).then_some(ReceivedStatsSample { count, window });
|
||||||
|
event
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unadjusted_late_reply(seq: u32, raw_ms: u64) -> ClientEvent {
|
pub fn unadjusted_late_reply(seq: u32, raw_ms: u64) -> ClientEvent {
|
||||||
late_reply_from(unadjusted_reply(seq, raw_ms))
|
late_reply_from(unadjusted_reply(seq, raw_ms))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ mod common;
|
|||||||
|
|
||||||
use std::time::{Duration, Instant, UNIX_EPOCH};
|
use std::time::{Duration, Instant, UNIX_EPOCH};
|
||||||
|
|
||||||
use common::{adjusted_reply, sent, ts, unadjusted_late_reply, unadjusted_reply};
|
use common::{
|
||||||
|
adjusted_reply, reply_with_received_stats, sent, ts, unadjusted_late_reply, unadjusted_reply,
|
||||||
|
};
|
||||||
use irtt_client::{
|
use irtt_client::{
|
||||||
ClientEvent, ClientTimestamp, OneWayDelaySample, PacketMeta, RttSample, SignedDuration,
|
ClientEvent, ClientTimestamp, OneWayDelaySample, PacketMeta, RttSample, SignedDuration,
|
||||||
};
|
};
|
||||||
@@ -378,6 +380,32 @@ fn empty_and_all_lost_edges_are_defined() {
|
|||||||
assert_eq!(all_lost.loss.packet_loss_percent, 100.0);
|
assert_eq!(all_lost.loss.packet_loss_percent, 100.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn receive_window_is_stored_raw_and_never_drives_directional_loss() {
|
||||||
|
// Window-only negotiation: the server reports a bitmap but no receive count.
|
||||||
|
let mut collector = StatsCollector::new(StatsConfig::finite());
|
||||||
|
collector.process(&sent(0, ts(0)));
|
||||||
|
collector.process(&reply_with_received_stats(0, 10, None, Some(0x1)));
|
||||||
|
|
||||||
|
// `0x1` represents no earlier history. It must be preserved verbatim and
|
||||||
|
// must not be read as 63 lost packets or stand in for a receive count.
|
||||||
|
let snapshot = collector.snapshot();
|
||||||
|
assert_eq!(snapshot.packets.server_received_window, Some(0x1));
|
||||||
|
assert_eq!(snapshot.packets.server_packets_received, None);
|
||||||
|
assert_eq!(snapshot.loss.upstream_loss_packets, None);
|
||||||
|
assert_eq!(snapshot.loss.downstream_loss_packets, None);
|
||||||
|
assert_eq!(snapshot.loss.upstream_loss_percent, 0.0);
|
||||||
|
assert_eq!(snapshot.loss.downstream_loss_percent, 0.0);
|
||||||
|
|
||||||
|
// An advancing bitmap is stored verbatim across the full 64-bit range.
|
||||||
|
collector.process(&sent(1, ts(10)));
|
||||||
|
collector.process(&reply_with_received_stats(1, 10, None, Some(u64::MAX)));
|
||||||
|
assert_eq!(
|
||||||
|
collector.snapshot().packets.server_received_window,
|
||||||
|
Some(u64::MAX)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn directional_loss_uses_server_received_count_when_available() {
|
fn directional_loss_uses_server_received_count_when_available() {
|
||||||
let mut collector = StatsCollector::new(StatsConfig::finite());
|
let mut collector = StatsCollector::new(StatsConfig::finite());
|
||||||
|
|||||||
Reference in new issue
Block a user