feat(stats): bound sequence tracking in continuous mode
This commit is contained in:
1 parent
c345f4b549
commit
ca99b752a6
1 file changed
+58
@@ -12,6 +12,8 @@ use irtt_client::{
|
|||||||
SignedDuration,
|
SignedDuration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CONTINUOUS_SEQUENCE_LIMIT: usize = 4096;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct StatsConfig {
|
pub struct StatsConfig {
|
||||||
pub median: MedianMode,
|
pub median: MedianMode,
|
||||||
@@ -28,6 +30,10 @@ impl StatsConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for long-running use.
|
||||||
|
///
|
||||||
|
/// This disables finite median retention and bounds sequence-adjacent IPDV
|
||||||
|
/// tracking. Exact arbitrary-late IPDV completion is finite-mode behavior.
|
||||||
pub fn continuous() -> Self {
|
pub fn continuous() -> Self {
|
||||||
Self {
|
Self {
|
||||||
median: MedianMode::Disabled,
|
median: MedianMode::Disabled,
|
||||||
@@ -116,6 +122,11 @@ impl StatsCollector {
|
|||||||
fn retained_median_samples(&self) -> usize {
|
fn retained_median_samples(&self) -> usize {
|
||||||
self.cumulative.retained_median_samples()
|
self.cumulative.retained_median_samples()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn retained_sequence_samples(&self) -> usize {
|
||||||
|
self.cumulative.retained_sequence_samples()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type RollingSnapshot = CumulativeSnapshot;
|
pub type RollingSnapshot = CumulativeSnapshot;
|
||||||
@@ -255,6 +266,7 @@ impl SignedDurationStatsWithMedian {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
struct CoreStats {
|
struct CoreStats {
|
||||||
median: MedianMode,
|
median: MedianMode,
|
||||||
|
sequence_limit: Option<usize>,
|
||||||
events: EventCounts,
|
events: EventCounts,
|
||||||
packets: PacketCounts,
|
packets: PacketCounts,
|
||||||
send_call: MetricU64,
|
send_call: MetricU64,
|
||||||
@@ -269,6 +281,7 @@ struct CoreStats {
|
|||||||
receive_delay: MetricU64,
|
receive_delay: MetricU64,
|
||||||
server_processing: MetricU64,
|
server_processing: MetricU64,
|
||||||
samples: HashMap<u64, UniqueSample>,
|
samples: HashMap<u64, UniqueSample>,
|
||||||
|
sample_order: VecDeque<u64>,
|
||||||
ipdv_pairs: HashSet<u64>,
|
ipdv_pairs: HashSet<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,6 +289,11 @@ impl CoreStats {
|
|||||||
fn new(median: MedianMode) -> Self {
|
fn new(median: MedianMode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
median,
|
median,
|
||||||
|
sequence_limit: if median == MedianMode::ExactFinite {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(CONTINUOUS_SEQUENCE_LIMIT)
|
||||||
|
},
|
||||||
events: EventCounts::default(),
|
events: EventCounts::default(),
|
||||||
packets: PacketCounts::default(),
|
packets: PacketCounts::default(),
|
||||||
send_call: MetricU64::new(false),
|
send_call: MetricU64::new(false),
|
||||||
@@ -290,6 +308,7 @@ impl CoreStats {
|
|||||||
receive_delay: MetricU64::new(median == MedianMode::ExactFinite),
|
receive_delay: MetricU64::new(median == MedianMode::ExactFinite),
|
||||||
server_processing: MetricU64::new(false),
|
server_processing: MetricU64::new(false),
|
||||||
samples: HashMap::new(),
|
samples: HashMap::new(),
|
||||||
|
sample_order: VecDeque::new(),
|
||||||
ipdv_pairs: HashSet::new(),
|
ipdv_pairs: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,6 +364,8 @@ impl CoreStats {
|
|||||||
|
|
||||||
let seq = sample.logical_seq;
|
let seq = sample.logical_seq;
|
||||||
if self.samples.insert(seq, sample).is_none() {
|
if self.samples.insert(seq, sample).is_none() {
|
||||||
|
self.sample_order.push_back(seq);
|
||||||
|
self.enforce_sequence_limit();
|
||||||
self.try_ipdv_pair(seq);
|
self.try_ipdv_pair(seq);
|
||||||
if let Some(next) = seq.checked_add(1) {
|
if let Some(next) = seq.checked_add(1) {
|
||||||
self.try_ipdv_pair(next);
|
self.try_ipdv_pair(next);
|
||||||
@@ -368,6 +389,23 @@ impl CoreStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enforce_sequence_limit(&mut self) {
|
||||||
|
let Some(limit) = self.sequence_limit else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
while self.samples.len() > limit {
|
||||||
|
let Some(seq) = self.sample_order.pop_front() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
if self.samples.remove(&seq).is_some() {
|
||||||
|
self.ipdv_pairs.remove(&seq);
|
||||||
|
if let Some(next) = seq.checked_add(1) {
|
||||||
|
self.ipdv_pairs.remove(&next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn try_ipdv_pair(&mut self, current_seq: u64) {
|
fn try_ipdv_pair(&mut self, current_seq: u64) {
|
||||||
let Some(previous_seq) = current_seq.checked_sub(1) else {
|
let Some(previous_seq) = current_seq.checked_sub(1) else {
|
||||||
return;
|
return;
|
||||||
@@ -434,6 +472,11 @@ impl CoreStats {
|
|||||||
+ self.send_delay.retained_samples()
|
+ self.send_delay.retained_samples()
|
||||||
+ self.receive_delay.retained_samples()
|
+ self.receive_delay.retained_samples()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn retained_sequence_samples(&self) -> usize {
|
||||||
|
self.samples.len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@@ -1102,6 +1145,21 @@ mod tests {
|
|||||||
assert_eq!(collector.cumulative().rtt.primary.median_ns, None);
|
assert_eq!(collector.cumulative().rtt.primary.median_ns, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn continuous_mode_bounds_sequence_tracking() {
|
||||||
|
let mut collector = StatsCollector::new(StatsConfig::continuous());
|
||||||
|
for seq in 0..(CONTINUOUS_SEQUENCE_LIMIT as u64 + 8) {
|
||||||
|
collector.process(&reply(seq, 10, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(collector.retained_median_samples(), 0);
|
||||||
|
assert!(collector.retained_sequence_samples() <= CONTINUOUS_SEQUENCE_LIMIT);
|
||||||
|
assert_eq!(
|
||||||
|
collector.cumulative().rtt.primary.stats.count,
|
||||||
|
CONTINUOUS_SEQUENCE_LIMIT as u64 + 8
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cumulative_rtt_uses_signed_effective_and_tracks_raw() {
|
fn cumulative_rtt_uses_signed_effective_and_tracks_raw() {
|
||||||
let mut collector = StatsCollector::new(StatsConfig::finite());
|
let mut collector = StatsCollector::new(StatsConfig::finite());
|
||||||
|
|||||||
Reference in new issue
Block a user