refactor(cli): simplify feature flags and make stats mandatory for clients

Remove the optional "stats" feature by merging it into the "client" and "tui" features to reduce conditional compilation complexity. Replace the "client-runtime" internal feature with direct dependency sets.
This commit is contained in:
Nils Andreas Svee committed 2026-08-15 00:24:28 +07:00
1 parent 88419d4144
commit 90c04b6569
6 files changed
+29 -82

No files matched your search

+3 -6
View File
@@ -37,13 +37,10 @@ required-features = ["server"]
doc = false doc = false
[features] [features]
default = ["client", "server", "stats"] default = ["client", "server", "tui"]
client-runtime = ["dep:irtt-client", "irtt-client/ancillary", "irtt-client/tokio", "dep:irtt-proto"] client = ["dep:irtt-client", "irtt-client/ancillary", "irtt-client/tokio", "dep:irtt-proto", "dep:irtt-stats"]
client = ["client-runtime"]
server = ["dep:irtt-server", "dep:tokio"] server = ["dep:irtt-server", "dep:tokio"]
stats = ["client-runtime", "dep:irtt-stats"] tui =["dep:irtt-client", "irtt-client/ancillary", "irtt-client/tokio", "dep:irtt-proto", "dep:irtt-stats", "dep:ratatui", "dep:crossterm"]
tui = ["client-runtime", "stats", "dep:ratatui", "dep:crossterm"]
full = ["client", "server", "stats", "tui"]
[dependencies] [dependencies]
clap = { version = "4.6.6", features = ["derive"] } clap = { version = "4.6.6", features = ["derive"] }
-1
View File
@@ -1,7 +1,6 @@
pub mod args; pub mod args;
pub mod output; pub mod output;
pub mod run; pub mod run;
#[cfg(feature = "stats")]
pub mod summary; pub mod summary;
pub use args::{ClientArgs, HeaderMode, OutputFormat}; pub use args::{ClientArgs, HeaderMode, OutputFormat};
-2
View File
@@ -164,7 +164,6 @@ pub struct IpdvPair {
pub receive_ipdv: Option<Duration>, pub receive_ipdv: Option<Duration>,
} }
#[cfg(feature = "stats")]
impl From<irtt_stats::EventStatsUpdate> for EventRenderStats { impl From<irtt_stats::EventStatsUpdate> for EventRenderStats {
fn from(value: irtt_stats::EventStatsUpdate) -> Self { fn from(value: irtt_stats::EventStatsUpdate) -> Self {
Self { Self {
@@ -174,7 +173,6 @@ impl From<irtt_stats::EventStatsUpdate> for EventRenderStats {
} }
} }
#[cfg(feature = "stats")]
impl From<irtt_stats::IpdvPairUpdate> for IpdvPair { impl From<irtt_stats::IpdvPairUpdate> for IpdvPair {
fn from(value: irtt_stats::IpdvPairUpdate) -> Self { fn from(value: irtt_stats::IpdvPairUpdate) -> Self {
Self { Self {
+21 -68
View File
@@ -1,4 +1,3 @@
#[cfg(feature = "stats")]
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::{ use std::{
collections::HashSet, collections::HashSet,
@@ -21,7 +20,7 @@ use super::{
args::ClientArgs, args::ClientArgs,
output::{EventRenderStats, OutputConfig}, output::{EventRenderStats, OutputConfig},
}; };
#[cfg(feature = "stats")]
use crate::shared::client::expected_probe_count; use crate::shared::client::expected_probe_count;
use crate::shared::client::{ use crate::shared::client::{
is_shutdown_requested, is_shutdown_requested,
@@ -30,20 +29,19 @@ use crate::shared::client::{
request_managed_stop_once, should_print_final_summary, ManagedDrainState, request_managed_stop_once, should_print_final_summary, ManagedDrainState,
}, },
}; };
#[cfg(feature = "stats")]
use irtt_stats::{StatsCollector, StatsConfig}; use irtt_stats::{StatsCollector, StatsConfig};
#[cfg(feature = "stats")]
const FINITE_STATS_BYTES_PER_PROBE: u64 = 500; const FINITE_STATS_BYTES_PER_PROBE: u64 = 500;
#[cfg(feature = "stats")]
const MIB: u64 = 1024 * 1024; const MIB: u64 = 1024 * 1024;
#[cfg(feature = "stats")]
const GIB: u64 = 1024 * MIB; const GIB: u64 = 1024 * MIB;
#[cfg(feature = "stats")]
const FINITE_STATS_MEMORY_WARNING_BYTES: u64 = 128 * MIB; const FINITE_STATS_MEMORY_WARNING_BYTES: u64 = 128 * MIB;
#[cfg(feature = "stats")]
const FINITE_STATS_MEMORY_STRONG_WARNING_BYTES: u64 = 512 * MIB; const FINITE_STATS_MEMORY_STRONG_WARNING_BYTES: u64 = 512 * MIB;
#[cfg(feature = "stats")]
const FINITE_STATS_MEMORY_VERY_STRONG_WARNING_BYTES: u64 = GIB; const FINITE_STATS_MEMORY_VERY_STRONG_WARNING_BYTES: u64 = GIB;
const MANAGED_EVENT_WAIT_SLICE: Duration = Duration::from_millis(20); const MANAGED_EVENT_WAIT_SLICE: Duration = Duration::from_millis(20);
const MANAGED_EVENT_CAPACITY: usize = 16_384; const MANAGED_EVENT_CAPACITY: usize = 16_384;
@@ -70,7 +68,7 @@ pub fn run_stream(
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
let continuous = args.is_continuous(); let continuous = args.is_continuous();
let target_count = targets.len(); let target_count = targets.len();
#[cfg(feature = "stats")]
if let Some(warning) = finite_stats_memory_warning(&args, target_count) { if let Some(warning) = finite_stats_memory_warning(&args, target_count) {
eprintln!("{warning}"); eprintln!("{warning}");
} }
@@ -102,7 +100,7 @@ pub fn run_stream(
show_running_only_summary_note: false, show_running_only_summary_note: false,
out: &mut stdout, out: &mut stdout,
}; };
#[cfg(feature = "stats")]
let mut stats = targets let mut stats = targets
.iter() .iter()
.map(|target| { .map(|target| {
@@ -135,7 +133,6 @@ pub fn run_stream(
let drain_state = drain_events( let drain_state = drain_events(
&mut events, &mut events,
&mut stream_output, &mut stream_output,
#[cfg(feature = "stats")]
&mut stats, &mut stats,
&mut terminal_targets, &mut terminal_targets,
&mut dropped_events, &mut dropped_events,
@@ -156,7 +153,6 @@ pub fn run_stream(
drain_final_events( drain_final_events(
&mut events, &mut events,
&mut stream_output, &mut stream_output,
#[cfg(feature = "stats")]
&mut stats, &mut stats,
&mut terminal_targets, &mut terminal_targets,
&mut dropped_events, &mut dropped_events,
@@ -196,7 +192,6 @@ pub fn run_stream(
stream_output.print_final_summary = should_print_final_summary(continuous, interrupted); stream_output.print_final_summary = should_print_final_summary(continuous, interrupted);
stream_output.show_running_only_summary_note = stream_output.show_running_only_summary_note =
continuous && interrupted && stream_output.print_final_summary; continuous && interrupted && stream_output.print_final_summary;
#[cfg(feature = "stats")]
for (label, stats) in &stats { for (label, stats) in &stats {
if multi_target if multi_target
&& stream_output.print_final_summary && stream_output.print_final_summary
@@ -207,8 +202,6 @@ pub fn run_stream(
} }
stream_output.print_summary(stats)?; stream_output.print_summary(stats)?;
} }
#[cfg(not(feature = "stats"))]
stream_output.print_summary()?;
stream_output.out.flush()?; stream_output.out.flush()?;
if let Some(error) = terminal_error { if let Some(error) = terminal_error {
return Err(error.into()); return Err(error.into());
@@ -219,28 +212,19 @@ pub fn run_stream(
fn process_event<W: Write>( fn process_event<W: Write>(
event: ManagedEvent, event: ManagedEvent,
stream_output: &mut StreamOutput<'_, W>, stream_output: &mut StreamOutput<'_, W>,
#[cfg(feature = "stats")] stats: &mut BTreeMap<String, StatsCollector>, stats: &mut BTreeMap<String, StatsCollector>,
terminal_targets: &mut HashSet<TargetInstance>, terminal_targets: &mut HashSet<TargetInstance>,
) -> io::Result<()> { ) -> io::Result<()> {
match event { match event {
ManagedEvent::Client { target, event } => { ManagedEvent::Client { target, event } => {
#[cfg(feature = "stats")] let collector = stats
{ .entry(target.id.as_str().to_owned())
let collector = stats .or_insert_with(|| StatsCollector::new(stats_config(false)));
.entry(target.id.as_str().to_owned())
.or_insert_with(|| StatsCollector::new(stats_config(false)));
print_events_with_stats(
stream_output,
std::slice::from_ref(&event),
Some(target.id.as_str()),
collector,
)?;
}
#[cfg(not(feature = "stats"))]
print_events_with_stats( print_events_with_stats(
stream_output, stream_output,
std::slice::from_ref(&event), std::slice::from_ref(&event),
Some(target.id.as_str()), Some(target.id.as_str()),
collector,
)?; )?;
} }
ManagedEvent::TargetFinished { outcome } => { ManagedEvent::TargetFinished { outcome } => {
@@ -255,37 +239,25 @@ fn process_event<W: Write>(
fn drain_events<W: Write>( fn drain_events<W: Write>(
events: &mut ManagedEventSubscription, events: &mut ManagedEventSubscription,
stream_output: &mut StreamOutput<'_, W>, stream_output: &mut StreamOutput<'_, W>,
#[cfg(feature = "stats")] stats: &mut BTreeMap<String, StatsCollector>, stats: &mut BTreeMap<String, StatsCollector>,
terminal_targets: &mut HashSet<TargetInstance>, terminal_targets: &mut HashSet<TargetInstance>,
dropped_events: &mut u64, dropped_events: &mut u64,
) -> io::Result<ManagedDrainState> { ) -> io::Result<ManagedDrainState> {
drain_managed_events(events, dropped_events, |event| { drain_managed_events(events, dropped_events, |event| {
process_event( process_event(event, stream_output, stats, terminal_targets)
event,
stream_output,
#[cfg(feature = "stats")]
stats,
terminal_targets,
)
}) })
} }
fn drain_final_events<W: Write>( fn drain_final_events<W: Write>(
events: &mut ManagedEventSubscription, events: &mut ManagedEventSubscription,
stream_output: &mut StreamOutput<'_, W>, stream_output: &mut StreamOutput<'_, W>,
#[cfg(feature = "stats")] stats: &mut BTreeMap<String, StatsCollector>, stats: &mut BTreeMap<String, StatsCollector>,
terminal_targets: &mut HashSet<TargetInstance>, terminal_targets: &mut HashSet<TargetInstance>,
dropped_events: &mut u64, dropped_events: &mut u64,
) -> io::Result<()> { ) -> io::Result<()> {
loop { loop {
match events.try_recv() { match events.try_recv() {
Ok(event) => process_event( Ok(event) => process_event(event, stream_output, stats, terminal_targets)?,
event,
stream_output,
#[cfg(feature = "stats")]
stats,
terminal_targets,
)?,
Err(ManagedEventTryRecvError::Empty | ManagedEventTryRecvError::Closed) => break, Err(ManagedEventTryRecvError::Empty | ManagedEventTryRecvError::Closed) => break,
Err(ManagedEventTryRecvError::Lagged(count)) => { Err(ManagedEventTryRecvError::Lagged(count)) => {
*dropped_events = dropped_events.saturating_add(count); *dropped_events = dropped_events.saturating_add(count);
@@ -353,7 +325,7 @@ impl<W: Write> StreamOutput<'_, W> {
} }
Ok(()) Ok(())
} }
#[cfg(feature = "stats")]
fn print_summary(&mut self, stats: &StatsCollector) -> io::Result<()> { fn print_summary(&mut self, stats: &StatsCollector) -> io::Result<()> {
if self.print_final_summary && self.config.prints_summary() { if self.print_final_summary && self.config.prints_summary() {
write!( write!(
@@ -370,12 +342,8 @@ impl<W: Write> StreamOutput<'_, W> {
} }
Ok(()) Ok(())
} }
#[cfg(not(feature = "stats"))]
fn print_summary(&mut self) -> io::Result<()> {
Ok(())
}
} }
#[cfg(feature = "stats")]
fn print_events_with_stats<W: Write>( fn print_events_with_stats<W: Write>(
stream_output: &mut StreamOutput<'_, W>, stream_output: &mut StreamOutput<'_, W>,
events: &[ClientEvent], events: &[ClientEvent],
@@ -388,19 +356,7 @@ fn print_events_with_stats<W: Write>(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
stream_output.print_events(events, target, &updates) stream_output.print_events(events, target, &updates)
} }
#[cfg(not(feature = "stats"))]
fn print_events_with_stats<W: Write>(
stream_output: &mut StreamOutput<'_, W>,
events: &[ClientEvent],
target: Option<&str>,
) -> io::Result<()> {
stream_output.print_events(
events,
target,
&vec![EventRenderStats::default(); events.len()],
)
}
#[cfg(feature = "stats")]
fn stats_config(continuous: bool) -> StatsConfig { fn stats_config(continuous: bool) -> StatsConfig {
if continuous { if continuous {
StatsConfig::continuous() StatsConfig::continuous()
@@ -408,7 +364,6 @@ fn stats_config(continuous: bool) -> StatsConfig {
StatsConfig::finite() StatsConfig::finite()
} }
} }
#[cfg(feature = "stats")]
fn finite_stats_memory_warning(args: &ClientArgs, target_count: usize) -> Option<String> { fn finite_stats_memory_warning(args: &ClientArgs, target_count: usize) -> Option<String> {
if args.is_continuous() || args.duration.is_zero() { if args.is_continuous() || args.duration.is_zero() {
return None; return None;
@@ -443,7 +398,6 @@ mod tests {
}; };
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "stats")]
#[test] #[test]
fn finite_stats_memory_warning_accounts_for_target_count() { fn finite_stats_memory_warning_accounts_for_target_count() {
use clap::Parser; use clap::Parser;
@@ -468,7 +422,6 @@ mod tests {
); );
} }
#[cfg(feature = "stats")]
#[test] #[test]
fn finite_stats_memory_warning_saturates_target_count_multiplication() { fn finite_stats_memory_warning_saturates_target_count_multiplication() {
use clap::Parser; use clap::Parser;
+4 -4
View File
@@ -1,12 +1,12 @@
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(feature = "client-runtime")] #[cfg(any(feature = "client", feature = "tui"))]
use irtt_client::managed::{ManagedEvent, ManagedEventSubscription, ManagedEventTryRecvError}; use irtt_client::managed::{ManagedEvent, ManagedEventSubscription, ManagedEventTryRecvError};
#[cfg(feature = "client-runtime")] #[cfg(any(feature = "client", feature = "tui"))]
pub(crate) const MANAGED_EVENT_WORK_BUDGET: usize = 128; pub(crate) const MANAGED_EVENT_WORK_BUDGET: usize = 128;
#[cfg(feature = "client-runtime")] #[cfg(any(feature = "client", feature = "tui"))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ManagedDrainState { pub(crate) enum ManagedDrainState {
Empty, Empty,
@@ -16,7 +16,7 @@ pub(crate) enum ManagedDrainState {
/// Drain a bounded amount of lossy presentation work before returning to the /// Drain a bounded amount of lossy presentation work before returning to the
/// frontend control loop. /// frontend control loop.
#[cfg(feature = "client-runtime")] #[cfg(any(feature = "client", feature = "tui"))]
pub(crate) fn drain_managed_events<E>( pub(crate) fn drain_managed_events<E>(
events: &mut ManagedEventSubscription, events: &mut ManagedEventSubscription,
dropped_events: &mut u64, dropped_events: &mut u64,
+1 -1
View File
@@ -1,2 +1,2 @@
#[cfg(feature = "client-runtime")] #[cfg(any(feature = "client", feature = "tui"))]
pub mod client; pub mod client;