feat(client): implement automatic session reconnection and disable statistics
Rust CI / cargo fmt (push) Failing after 16s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 36s
Rust CI / cargo clippy (push) Successful in 37s
Netem Interop / netem interop (push) Failing after 14s
Supply chain / cargo-deny advisories (push) Failing after 4s
Rust CI / cargo doc (push) Successful in 26s
Supply chain / cargo-deny bans licenses sources (push) Failing after 4s
Supply chain / actionlint (push) Successful in 3s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 31s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m19s
Rust CI / cargo fmt (push) Failing after 16s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 36s
Rust CI / cargo clippy (push) Successful in 37s
Netem Interop / netem interop (push) Failing after 14s
Supply chain / cargo-deny advisories (push) Failing after 4s
Rust CI / cargo doc (push) Successful in 26s
Supply chain / cargo-deny bans licenses sources (push) Failing after 4s
Supply chain / actionlint (push) Successful in 3s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 31s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m19s
This commit is contained in:
9 files changed
+282
-35
No files matched your search
@@ -58,8 +58,11 @@ pub async fn run_stream(
|
||||
args.verbose,
|
||||
)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
|
||||
if let Some(warning) = reconnect_stats_warning(&args) {
|
||||
eprintln!("{warning}");
|
||||
}
|
||||
if setup.stdin_controlled {
|
||||
return run_stdin_stream(setup, output_config, shutdown).await;
|
||||
return run_stdin_stream(setup, output_config, shutdown, &args).await;
|
||||
}
|
||||
let continuous = args.is_continuous();
|
||||
let target_count = setup.target_count();
|
||||
@@ -70,12 +73,21 @@ pub async fn run_stream(
|
||||
if *shutdown.borrow() {
|
||||
return Ok(());
|
||||
}
|
||||
let (task, handle) = ManagedClient::task(setup.managed_config(), setup.managed_targets())?;
|
||||
let (task, handle) = ManagedClient::task(
|
||||
{
|
||||
let mut config = setup.managed_config();
|
||||
config.reconnect_timeout = args.reconnect.then_some(args.reconnect_timeout);
|
||||
config
|
||||
},
|
||||
setup.managed_targets(),
|
||||
)?;
|
||||
let mut events = handle.subscribe()?;
|
||||
let mut status = handle.subscribe_status();
|
||||
let owner = ManagedWorker::start(task, handle.clone())?;
|
||||
let mut stdout = io::LineWriter::new(io::stdout().lock());
|
||||
let mut stream_output = StreamOutput {
|
||||
continuous: args.is_continuous(),
|
||||
collect_stats: !args.no_stats,
|
||||
config: output_config,
|
||||
header_printed: false,
|
||||
print_final_summary: false,
|
||||
@@ -86,6 +98,7 @@ pub async fn run_stream(
|
||||
let mut stats = setup
|
||||
.targets
|
||||
.iter()
|
||||
.filter(|_| !args.no_stats)
|
||||
.map(|target| {
|
||||
(
|
||||
target.label.clone(),
|
||||
@@ -168,13 +181,13 @@ pub async fn run_stream(
|
||||
}),
|
||||
};
|
||||
stream_output.print_final_summary =
|
||||
!args.no_test && should_print_final_summary(continuous, interrupted);
|
||||
!args.no_stats && !args.no_test && should_print_final_summary(continuous, interrupted);
|
||||
stream_output.show_running_only_summary_note =
|
||||
continuous && interrupted && stream_output.print_final_summary;
|
||||
for target in &setup.targets {
|
||||
let target_stats = stats.get(&target.label).expect(
|
||||
"every prepared target must have a stats collector inserted before the run begins",
|
||||
);
|
||||
let Some(target_stats) = stats.get(&target.label) else {
|
||||
continue;
|
||||
};
|
||||
if multi_target
|
||||
&& stream_output.print_final_summary
|
||||
&& stream_output.config.prints_summary()
|
||||
@@ -361,17 +374,22 @@ fn process_stdin_event<W: Write>(
|
||||
terminal_targets: &mut BoundedTargetSet,
|
||||
) -> io::Result<()> {
|
||||
match event {
|
||||
ManagedEvent::TargetStateChanged { target, .. } => {
|
||||
ManagedEvent::TargetStateChanged { target, .. } if stream_output.collect_stats => {
|
||||
stats
|
||||
.entry(target)
|
||||
.or_insert_with(|| StatsCollector::new(stats_config(true)));
|
||||
}
|
||||
ManagedEvent::Client { target, event } => {
|
||||
let collector = stats
|
||||
.entry(target.clone())
|
||||
.or_insert_with(|| StatsCollector::new(stats_config(true)));
|
||||
let update = collector.process(&event);
|
||||
stream_output.print_event(&event, Some(target.id.as_str()), &update)?;
|
||||
let update = stream_output.collect_stats.then(|| {
|
||||
let collector = stats
|
||||
.entry(target.clone())
|
||||
.or_insert_with(|| StatsCollector::new(stats_config(true)));
|
||||
if matches!(event, ClientEvent::SessionStarted(_)) {
|
||||
*collector = StatsCollector::new(stats_config(true));
|
||||
}
|
||||
collector.process(&event)
|
||||
});
|
||||
stream_output.print_event(&event, Some(target.id.as_str()), update.as_ref())?;
|
||||
}
|
||||
ManagedEvent::TargetFinished { outcome }
|
||||
if terminal_targets.insert(outcome.target.clone()) =>
|
||||
@@ -402,7 +420,11 @@ fn reconcile_stdin_stats(
|
||||
fn snapshot_stdin_summary_targets(
|
||||
stats: &mut BTreeMap<TargetInstance, StatsCollector>,
|
||||
status: &ManagedStatus,
|
||||
enabled: bool,
|
||||
) -> BTreeSet<TargetInstance> {
|
||||
if !enabled {
|
||||
return BTreeSet::new();
|
||||
}
|
||||
let targets = status
|
||||
.targets
|
||||
.iter()
|
||||
@@ -427,11 +449,19 @@ async fn run_stdin_stream(
|
||||
setup: ManagedRunSetup,
|
||||
output_config: OutputConfig,
|
||||
mut shutdown: tokio::sync::watch::Receiver<bool>,
|
||||
args: &ClientArgs,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if *shutdown.borrow() {
|
||||
return Ok(());
|
||||
}
|
||||
let (task, handle) = ManagedClient::task(setup.managed_config(), setup.managed_targets())?;
|
||||
let (task, handle) = ManagedClient::task(
|
||||
{
|
||||
let mut config = setup.managed_config();
|
||||
config.reconnect_timeout = args.reconnect.then_some(args.reconnect_timeout);
|
||||
config
|
||||
},
|
||||
setup.managed_targets(),
|
||||
)?;
|
||||
let mut events = handle.subscribe()?;
|
||||
let mut status = handle.subscribe_status();
|
||||
let owner = ManagedWorker::start(task, handle.clone())?;
|
||||
@@ -440,6 +470,8 @@ async fn run_stdin_stream(
|
||||
|
||||
let mut stdout = io::LineWriter::new(io::stdout().lock());
|
||||
let mut stream_output = StreamOutput {
|
||||
continuous: args.is_continuous(),
|
||||
collect_stats: !args.no_stats,
|
||||
config: output_config,
|
||||
header_printed: false,
|
||||
print_final_summary: false,
|
||||
@@ -510,7 +542,11 @@ async fn run_stdin_stream(
|
||||
}
|
||||
if stop.is_some() {
|
||||
preparing = None;
|
||||
summary_targets = Some(snapshot_stdin_summary_targets(&mut stats, &snapshot));
|
||||
summary_targets = Some(snapshot_stdin_summary_targets(
|
||||
&mut stats,
|
||||
&snapshot,
|
||||
!args.no_stats,
|
||||
));
|
||||
if request_managed_stop_once(&mut stop_requested) {
|
||||
drop(handle.stop());
|
||||
}
|
||||
@@ -537,7 +573,7 @@ async fn run_stdin_stream(
|
||||
}
|
||||
Err(error) => {
|
||||
stop = Some(StdinStop::Fatal(format!("invalid --targets-stdin target set: {error}")));
|
||||
summary_targets = Some(snapshot_stdin_summary_targets(&mut stats, &status.borrow()));
|
||||
summary_targets = Some(snapshot_stdin_summary_targets(&mut stats, &status.borrow(), !args.no_stats));
|
||||
if request_managed_stop_once(&mut stop_requested) { drop(handle.stop()); }
|
||||
}
|
||||
}
|
||||
@@ -558,7 +594,7 @@ async fn run_stdin_stream(
|
||||
}
|
||||
Err(_) => {
|
||||
stop = Some(StdinStop::Fatal("--targets-stdin update was rejected".to_owned()));
|
||||
summary_targets = Some(snapshot_stdin_summary_targets(&mut stats, &status.borrow()));
|
||||
summary_targets = Some(snapshot_stdin_summary_targets(&mut stats, &status.borrow(), !args.no_stats));
|
||||
if request_managed_stop_once(&mut stop_requested) { drop(handle.stop()); }
|
||||
}
|
||||
}
|
||||
@@ -595,7 +631,8 @@ async fn run_stdin_stream(
|
||||
);
|
||||
}
|
||||
|
||||
let print_summary = matches!(stop, Some(StdinStop::Interrupted | StdinStop::Eof));
|
||||
let print_summary =
|
||||
!args.no_stats && matches!(stop, Some(StdinStop::Interrupted | StdinStop::Eof));
|
||||
stream_output.print_final_summary = print_summary;
|
||||
stream_output.show_running_only_summary_note = print_summary;
|
||||
if let Some(summary_targets) = summary_targets {
|
||||
@@ -636,11 +673,17 @@ fn process_event<W: Write>(
|
||||
) -> io::Result<()> {
|
||||
match event {
|
||||
ManagedEvent::Client { target, event } => {
|
||||
let collector = stats
|
||||
.entry(target.id.as_str().to_owned())
|
||||
.or_insert_with(|| StatsCollector::new(stats_config(false)));
|
||||
let update = collector.process(&event);
|
||||
stream_output.print_event(&event, Some(target.id.as_str()), &update)?;
|
||||
let update = stream_output.collect_stats.then(|| {
|
||||
let collector = stats
|
||||
.get_mut(target.id.as_str())
|
||||
.expect("prepared target has a statistics collector");
|
||||
if matches!(event, ClientEvent::SessionStarted(_)) {
|
||||
let config = stats_config(stream_output.continuous);
|
||||
*collector = StatsCollector::new(config);
|
||||
}
|
||||
collector.process(&event)
|
||||
});
|
||||
stream_output.print_event(&event, Some(target.id.as_str()), update.as_ref())?;
|
||||
}
|
||||
ManagedEvent::TargetFinished { outcome } => {
|
||||
terminal_targets.insert(outcome.target.clone());
|
||||
@@ -678,6 +721,8 @@ fn dropped_event_warning(dropped_events: u64) -> Option<String> {
|
||||
}
|
||||
|
||||
struct StreamOutput<'a, W: Write> {
|
||||
continuous: bool,
|
||||
collect_stats: bool,
|
||||
config: OutputConfig,
|
||||
header_printed: bool,
|
||||
print_final_summary: bool,
|
||||
@@ -689,10 +734,10 @@ impl<W: Write> StreamOutput<'_, W> {
|
||||
&mut self,
|
||||
event: &ClientEvent,
|
||||
target: Option<&str>,
|
||||
update: &EventStatsUpdate,
|
||||
update: Option<&EventStatsUpdate>,
|
||||
) -> io::Result<()> {
|
||||
self.print_header()?;
|
||||
if let Some(line) = self.config.render_event(event, target, Some(update)) {
|
||||
if let Some(line) = self.config.render_event(event, target, update) {
|
||||
writeln!(self.out, "{line}")?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -734,7 +779,7 @@ fn stats_config(continuous: bool) -> StatsConfig {
|
||||
}
|
||||
}
|
||||
fn finite_stats_memory_warning(args: &ClientArgs, target_count: usize) -> Option<String> {
|
||||
if args.no_test || args.is_continuous() || args.duration.is_zero() {
|
||||
if args.no_stats || args.no_test || args.is_continuous() || args.duration.is_zero() {
|
||||
return None;
|
||||
}
|
||||
let target_count = u64::try_from(target_count).unwrap_or(u64::MAX);
|
||||
@@ -761,3 +806,8 @@ fn finite_stats_memory_warning(args: &ClientArgs, target_count: usize) -> Option
|
||||
};
|
||||
Some(format!("irtt-rs: warning: finite exact statistics may retain about {formatted} for this run; {guidance}"))
|
||||
}
|
||||
|
||||
fn reconnect_stats_warning(args: &ClientArgs) -> Option<&'static str> {
|
||||
(args.reconnect && !args.no_stats).then_some("irtt-rs: warning: statistics spanning multiple sessions may not accurately represent one uninterrupted measurement run; use --no-stats for measurement events only. Session statistics reset on reconnect.")
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user