warn(irtt-cli): flag large finite stats runs
This commit is contained in:
1 parent
ea85384ced
commit
480cb4fa92
1 file changed
+161
@@ -25,6 +25,18 @@ const RECV_BUDGET: RecvBudget = RecvBudget { max_packets: 16 };
|
|||||||
const MAX_FINAL_DRAIN: Duration = Duration::from_secs(30);
|
const MAX_FINAL_DRAIN: Duration = Duration::from_secs(30);
|
||||||
const IDLE_SLEEP: Duration = Duration::from_millis(5);
|
const IDLE_SLEEP: Duration = Duration::from_millis(5);
|
||||||
const MAX_SLEEP: Duration = Duration::from_millis(20);
|
const MAX_SLEEP: Duration = Duration::from_millis(20);
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
const FINITE_STATS_BYTES_PER_PROBE: u64 = 500;
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
const MIB: u64 = 1024 * 1024;
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
const GIB: u64 = 1024 * MIB;
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
const FINITE_STATS_MEMORY_WARNING_BYTES: u64 = 128 * MIB;
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
const FINITE_STATS_MEMORY_STRONG_WARNING_BYTES: u64 = 512 * MIB;
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
const FINITE_STATS_MEMORY_VERY_STRONG_WARNING_BYTES: u64 = GIB;
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
let args = CliArgs::parse();
|
let args = CliArgs::parse();
|
||||||
@@ -52,6 +64,10 @@ fn install_signal_handler(shutdown_requested: Arc<AtomicBool>) -> Result<(), ctr
|
|||||||
fn run(args: CliArgs, shutdown_requested: &AtomicBool) -> Result<(), Box<dyn std::error::Error>> {
|
fn run(args: CliArgs, shutdown_requested: &AtomicBool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mode = args.output;
|
let mode = args.output;
|
||||||
let continuous = args.is_continuous();
|
let continuous = args.is_continuous();
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
if let Some(warning) = finite_stats_memory_warning(&args) {
|
||||||
|
eprintln!("{warning}");
|
||||||
|
}
|
||||||
let mut stdout = io::LineWriter::new(io::stdout().lock());
|
let mut stdout = io::LineWriter::new(io::stdout().lock());
|
||||||
#[cfg(feature = "stats")]
|
#[cfg(feature = "stats")]
|
||||||
let mut stats = StatsCollector::new(stats_config(continuous));
|
let mut stats = StatsCollector::new(stats_config(continuous));
|
||||||
@@ -258,6 +274,60 @@ fn stats_config(continuous: bool) -> StatsConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
fn expected_probe_count(duration: Duration, interval: Duration) -> u64 {
|
||||||
|
let interval_nanos = interval.as_nanos();
|
||||||
|
if interval_nanos == 0 {
|
||||||
|
return u64::MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected = duration
|
||||||
|
.as_nanos()
|
||||||
|
.saturating_add(interval_nanos.saturating_sub(1))
|
||||||
|
/ interval_nanos;
|
||||||
|
expected.min(u128::from(u64::MAX)) as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
fn estimate_finite_stats_memory_bytes(expected_probes: u64) -> u64 {
|
||||||
|
expected_probes.saturating_mul(FINITE_STATS_BYTES_PER_PROBE)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
fn finite_stats_memory_warning(args: &CliArgs) -> Option<String> {
|
||||||
|
if args.is_continuous() || args.duration.is_zero() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_probes = expected_probe_count(args.duration, args.interval);
|
||||||
|
let estimated_bytes = estimate_finite_stats_memory_bytes(expected_probes);
|
||||||
|
if estimated_bytes < FINITE_STATS_MEMORY_WARNING_BYTES {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let formatted = format_bytes_for_warning(estimated_bytes);
|
||||||
|
let guidance = if estimated_bytes >= FINITE_STATS_MEMORY_VERY_STRONG_WARNING_BYTES {
|
||||||
|
"this may be unsuitable on memory-constrained systems"
|
||||||
|
} else if estimated_bytes >= FINITE_STATS_MEMORY_STRONG_WARNING_BYTES {
|
||||||
|
"consider shortening the run, increasing the interval, or using continuous mode"
|
||||||
|
} else {
|
||||||
|
"use continuous mode for bounded-memory long-running tests"
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(format!(
|
||||||
|
"irtt-rs: warning: finite exact statistics may retain about {formatted} for this run; {guidance}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "stats")]
|
||||||
|
fn format_bytes_for_warning(bytes: u64) -> String {
|
||||||
|
if bytes >= GIB {
|
||||||
|
format!("{} GiB", bytes.saturating_add(GIB / 2) / GIB)
|
||||||
|
} else {
|
||||||
|
format!("{} MiB", bytes.saturating_add(MIB / 2) / MIB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn final_drain_duration(probe_timeout: Duration) -> Duration {
|
fn final_drain_duration(probe_timeout: Duration) -> Duration {
|
||||||
probe_timeout.min(MAX_FINAL_DRAIN)
|
probe_timeout.min(MAX_FINAL_DRAIN)
|
||||||
}
|
}
|
||||||
@@ -341,6 +411,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cli_args(args: &[&str]) -> CliArgs {
|
||||||
|
let mut argv = vec!["irtt-rs"];
|
||||||
|
argv.extend_from_slice(args);
|
||||||
|
CliArgs::try_parse_from(argv).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn output_helper_streams_and_collects_events() {
|
fn output_helper_streams_and_collects_events() {
|
||||||
let sent_at = test_timestamp(Duration::from_secs(1));
|
let sent_at = test_timestamp(Duration::from_secs(1));
|
||||||
@@ -465,6 +541,91 @@ mod tests {
|
|||||||
assert_eq!(collector.snapshot().rtt.primary.median_ns, None);
|
assert_eq!(collector.snapshot().rtt.primary.median_ns, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finite_stats_memory_warning_skips_continuous_mode() {
|
||||||
|
let args = cli_args(&["--duration", "0", "--interval", "1ms", "127.0.0.1:2112"]);
|
||||||
|
|
||||||
|
assert_eq!(finite_stats_memory_warning(&args), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finite_stats_memory_warning_skips_small_finite_run() {
|
||||||
|
let args = cli_args(&[
|
||||||
|
"--duration",
|
||||||
|
"1000ms",
|
||||||
|
"--interval",
|
||||||
|
"1ms",
|
||||||
|
"127.0.0.1:2112",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(finite_stats_memory_warning(&args), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finite_stats_memory_warning_reports_128_mib_tier() {
|
||||||
|
let expected_probes =
|
||||||
|
FINITE_STATS_MEMORY_WARNING_BYTES.div_ceil(FINITE_STATS_BYTES_PER_PROBE);
|
||||||
|
let args = cli_args(&[
|
||||||
|
"--duration",
|
||||||
|
&format!("{expected_probes}ms"),
|
||||||
|
"--interval",
|
||||||
|
"1ms",
|
||||||
|
"127.0.0.1:2112",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let warning = finite_stats_memory_warning(&args).unwrap();
|
||||||
|
assert!(warning.contains("about 128 MiB"));
|
||||||
|
assert!(warning.contains("bounded-memory long-running tests"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finite_stats_memory_warning_reports_512_mib_tier() {
|
||||||
|
let expected_probes =
|
||||||
|
FINITE_STATS_MEMORY_STRONG_WARNING_BYTES.div_ceil(FINITE_STATS_BYTES_PER_PROBE);
|
||||||
|
let args = cli_args(&[
|
||||||
|
"--duration",
|
||||||
|
&format!("{expected_probes}ms"),
|
||||||
|
"--interval",
|
||||||
|
"1ms",
|
||||||
|
"127.0.0.1:2112",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let warning = finite_stats_memory_warning(&args).unwrap();
|
||||||
|
assert!(warning.contains("about 512 MiB"));
|
||||||
|
assert!(warning.contains("shortening the run"));
|
||||||
|
assert!(warning.contains("increasing the interval"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finite_stats_memory_warning_reports_1_gib_tier() {
|
||||||
|
let expected_probes =
|
||||||
|
FINITE_STATS_MEMORY_VERY_STRONG_WARNING_BYTES.div_ceil(FINITE_STATS_BYTES_PER_PROBE);
|
||||||
|
let args = cli_args(&[
|
||||||
|
"--duration",
|
||||||
|
&format!("{expected_probes}ms"),
|
||||||
|
"--interval",
|
||||||
|
"1ms",
|
||||||
|
"127.0.0.1:2112",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let warning = finite_stats_memory_warning(&args).unwrap();
|
||||||
|
assert!(warning.contains("about 1 GiB"));
|
||||||
|
assert!(warning.contains("memory-constrained systems"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expected_probe_count_rounds_up() {
|
||||||
|
assert_eq!(
|
||||||
|
expected_probe_count(Duration::from_millis(1001), Duration::from_secs(1)),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn estimate_finite_stats_memory_bytes_saturates() {
|
||||||
|
assert_eq!(estimate_finite_stats_memory_bytes(u64::MAX), u64::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn final_drain_uses_capped_probe_timeout() {
|
fn final_drain_uses_capped_probe_timeout() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
Reference in new issue
Block a user