Merge branch 'feature/simplify-client-output'
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 33s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m14s
Rust CI / cargo doc (push) Successful in 25s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 32s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m5s
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 33s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m14s
Rust CI / cargo doc (push) Successful in 25s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 32s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m5s
This commit is contained in:
2 files changed
+205
-443
No files matched your search
@@ -9,6 +9,8 @@ use irtt_client::{
|
||||
ServerTiming, SignedDuration, WarningKind,
|
||||
};
|
||||
|
||||
use irtt_stats::{EventStatsUpdate, IpdvPairUpdate};
|
||||
|
||||
use super::args::{HeaderMode, OutputFormat};
|
||||
use crate::cmd::format::{format_duration, format_signed_duration, ABSENT};
|
||||
|
||||
@@ -94,7 +96,7 @@ impl OutputConfig {
|
||||
&self,
|
||||
event: &ClientEvent,
|
||||
target: Option<&str>,
|
||||
stats: Option<&EventRenderStats>,
|
||||
stats: Option<&EventStatsUpdate>,
|
||||
) -> Option<String> {
|
||||
let row = OutputRow::from_event(event);
|
||||
if self.default_table_rows && row.is_default_table_hidden() {
|
||||
@@ -133,10 +135,12 @@ impl OutputConfig {
|
||||
}
|
||||
writeln!(out).unwrap();
|
||||
writeln!(out, "Aliases:").unwrap();
|
||||
writeln!(out, " receive_delay, receive_delay_us").unwrap();
|
||||
writeln!(out, " send_delay, send_delay_us").unwrap();
|
||||
writeln!(out, " server_processing").unwrap();
|
||||
writeln!(out, " server_received_count, server_received_window").unwrap();
|
||||
for column in ALL_COLUMNS {
|
||||
let aliases = column.aliases();
|
||||
if !aliases.is_empty() {
|
||||
writeln!(out, " {} ({})", aliases.join(", "), column.name()).unwrap();
|
||||
}
|
||||
}
|
||||
writeln!(out).unwrap();
|
||||
writeln!(out, "Special column sets:").unwrap();
|
||||
writeln!(
|
||||
@@ -149,45 +153,9 @@ impl OutputConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct EventRenderStats {
|
||||
pub contributed_sample: bool,
|
||||
pub ipdv_pairs: Vec<IpdvPair>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct IpdvPair {
|
||||
pub previous_seq: u32,
|
||||
pub current_seq: u32,
|
||||
pub rtt_ipdv: Duration,
|
||||
pub send_ipdv: Option<Duration>,
|
||||
pub receive_ipdv: Option<Duration>,
|
||||
}
|
||||
|
||||
impl From<irtt_stats::EventStatsUpdate> for EventRenderStats {
|
||||
fn from(value: irtt_stats::EventStatsUpdate) -> Self {
|
||||
Self {
|
||||
contributed_sample: value.contributed_sample,
|
||||
ipdv_pairs: value.ipdv_pairs.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<irtt_stats::IpdvPairUpdate> for IpdvPair {
|
||||
fn from(value: irtt_stats::IpdvPairUpdate) -> Self {
|
||||
Self {
|
||||
previous_seq: value.previous_seq,
|
||||
current_seq: value.current_seq,
|
||||
rtt_ipdv: value.rtt_ipdv,
|
||||
send_ipdv: value.send_ipdv,
|
||||
receive_ipdv: value.receive_ipdv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct RenderContext<'a> {
|
||||
stats: Option<&'a EventRenderStats>,
|
||||
stats: Option<&'a EventStatsUpdate>,
|
||||
target: Option<&'a str>,
|
||||
verbose: bool,
|
||||
}
|
||||
@@ -368,6 +336,106 @@ impl OutputRow {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn seq(&self) -> Option<u32> {
|
||||
match self {
|
||||
Self::EchoSent { seq, .. } | Self::Loss { seq, .. } | Self::Duplicate { seq, .. } => {
|
||||
Some(*seq)
|
||||
}
|
||||
Self::EchoReply(reply) | Self::Late { reply, .. } => Some(reply.seq),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn remote(&self) -> Option<SocketAddr> {
|
||||
match self {
|
||||
Self::SessionStarted(row) | Self::NoTestCompleted(row) => Some(row.remote),
|
||||
Self::SessionClosed { remote, .. }
|
||||
| Self::EchoSent { remote, .. }
|
||||
| Self::Duplicate { remote, .. } => Some(*remote),
|
||||
Self::EchoReply(reply) | Self::Late { reply, .. } => Some(reply.remote),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn token(&self) -> Option<u64> {
|
||||
match self {
|
||||
Self::SessionStarted(row) | Self::NoTestCompleted(row) => row.token,
|
||||
Self::SessionClosed { token, .. } => Some(*token),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes(&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::EchoSent { bytes, .. } | Self::Duplicate { bytes, .. } => Some(*bytes),
|
||||
Self::EchoReply(reply) | Self::Late { reply, .. } => Some(reply.bytes),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn message(&self, context: RenderContext<'_>) -> Option<String> {
|
||||
match self {
|
||||
Self::SessionStarted(row) => Some(format!(
|
||||
"token={:#x} duration_ns={} interval_ns={} length={}",
|
||||
row.token?, row.duration_ns, row.interval_ns, row.payload_length
|
||||
)),
|
||||
Self::NoTestCompleted(row) => Some(format!(
|
||||
"duration_ns={} interval_ns={} length={}",
|
||||
row.duration_ns, row.interval_ns, row.payload_length
|
||||
)),
|
||||
Self::SessionClosed { token, .. } => Some(format!("token={token:#x}")),
|
||||
Self::Late { highest_seen, .. } => Some(format!("highest_seen={highest_seen}")),
|
||||
Self::Warning { message, .. } => Some(message.clone()),
|
||||
Self::Loss { .. } => Some("timeout".to_owned()),
|
||||
Self::Duplicate { .. } if context.verbose => Some("duplicate reply".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn event_wall(&self) -> Option<SystemTime> {
|
||||
match self {
|
||||
Self::SessionStarted(row) | Self::NoTestCompleted(row) => Some(row.event_wall),
|
||||
Self::SessionClosed { event_wall, .. } | Self::Warning { event_wall, .. } => {
|
||||
Some(*event_wall)
|
||||
}
|
||||
Self::EchoSent {
|
||||
client_send_wall, ..
|
||||
}
|
||||
| Self::Loss {
|
||||
client_send_wall, ..
|
||||
} => Some(*client_send_wall),
|
||||
Self::Duplicate {
|
||||
client_receive_wall,
|
||||
..
|
||||
} => Some(*client_receive_wall),
|
||||
Self::EchoReply(reply) | Self::Late { reply, .. } => Some(reply.client_receive_wall),
|
||||
}
|
||||
}
|
||||
|
||||
fn client_send_wall(&self) -> Option<SystemTime> {
|
||||
match self {
|
||||
Self::EchoSent {
|
||||
client_send_wall, ..
|
||||
}
|
||||
| Self::Loss {
|
||||
client_send_wall, ..
|
||||
} => Some(*client_send_wall),
|
||||
Self::EchoReply(reply) | Self::Late { reply, .. } => reply.client_send_wall,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn client_receive_wall(&self) -> Option<SystemTime> {
|
||||
match self {
|
||||
Self::Duplicate {
|
||||
client_receive_wall,
|
||||
..
|
||||
} => Some(*client_receive_wall),
|
||||
Self::EchoReply(reply) | Self::Late { reply, .. } => Some(reply.client_receive_wall),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -415,275 +483,92 @@ struct ReplyRow {
|
||||
packet_meta: PacketMeta,
|
||||
}
|
||||
|
||||
// Only schema metadata is generated here; cell_for keeps rendering behavior explicit.
|
||||
macro_rules! columns {
|
||||
($($variant:ident($name:literal, [$($alias:literal),*], $width:expr, $align:ident, $description:literal);)*) => {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Column {
|
||||
Target,
|
||||
Event,
|
||||
Seq,
|
||||
Remote,
|
||||
Token,
|
||||
Rtt,
|
||||
RttUs,
|
||||
RawRttUs,
|
||||
EffectiveRttUs,
|
||||
AdjustedRttUs,
|
||||
ReceiveDelay,
|
||||
ReceiveDelayUs,
|
||||
SendDelay,
|
||||
SendDelayUs,
|
||||
Ipdv,
|
||||
IpdvUs,
|
||||
ServerProcessing,
|
||||
ServerProcessingUs,
|
||||
Bytes,
|
||||
SendCallUs,
|
||||
TimerErrorUs,
|
||||
HighestSeen,
|
||||
ServerReceivedCount,
|
||||
ServerReceivedWindow,
|
||||
Dscp,
|
||||
Ecn,
|
||||
TrafficClass,
|
||||
KernelRxNs,
|
||||
WarningKind,
|
||||
Message,
|
||||
EventWallNs,
|
||||
ClientSendWallNs,
|
||||
ClientReceiveWallNs,
|
||||
DurationNs,
|
||||
IntervalNs,
|
||||
PayloadLength,
|
||||
ServerReceiveWallNs,
|
||||
ServerReceiveMonoNs,
|
||||
ServerSendWallNs,
|
||||
ServerSendMonoNs,
|
||||
ServerMidpointWallNs,
|
||||
ServerMidpointMonoNs,
|
||||
}
|
||||
enum Column { $($variant),* }
|
||||
|
||||
const ALL_COLUMNS: &[Column] = &[
|
||||
Column::Target,
|
||||
Column::Event,
|
||||
Column::Seq,
|
||||
Column::Remote,
|
||||
Column::Token,
|
||||
Column::Rtt,
|
||||
Column::RttUs,
|
||||
Column::RawRttUs,
|
||||
Column::EffectiveRttUs,
|
||||
Column::AdjustedRttUs,
|
||||
Column::ReceiveDelay,
|
||||
Column::ReceiveDelayUs,
|
||||
Column::SendDelay,
|
||||
Column::SendDelayUs,
|
||||
Column::Ipdv,
|
||||
Column::IpdvUs,
|
||||
Column::ServerProcessing,
|
||||
Column::ServerProcessingUs,
|
||||
Column::Bytes,
|
||||
Column::SendCallUs,
|
||||
Column::TimerErrorUs,
|
||||
Column::HighestSeen,
|
||||
Column::ServerReceivedCount,
|
||||
Column::ServerReceivedWindow,
|
||||
Column::Dscp,
|
||||
Column::Ecn,
|
||||
Column::TrafficClass,
|
||||
Column::KernelRxNs,
|
||||
Column::WarningKind,
|
||||
Column::Message,
|
||||
Column::EventWallNs,
|
||||
Column::ClientSendWallNs,
|
||||
Column::ClientReceiveWallNs,
|
||||
Column::DurationNs,
|
||||
Column::IntervalNs,
|
||||
Column::PayloadLength,
|
||||
Column::ServerReceiveWallNs,
|
||||
Column::ServerReceiveMonoNs,
|
||||
Column::ServerSendWallNs,
|
||||
Column::ServerSendMonoNs,
|
||||
Column::ServerMidpointWallNs,
|
||||
Column::ServerMidpointMonoNs,
|
||||
];
|
||||
const ALL_COLUMNS: &[Column] = &[$(Column::$variant),*];
|
||||
|
||||
impl Column {
|
||||
fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Target => "target",
|
||||
Self::Event => "event",
|
||||
Self::Seq => "seq",
|
||||
Self::Remote => "remote",
|
||||
Self::Token => "token",
|
||||
Self::Rtt => "rtt",
|
||||
Self::RttUs => "rtt_us",
|
||||
Self::RawRttUs => "raw_rtt_us",
|
||||
Self::EffectiveRttUs => "effective_rtt_us",
|
||||
Self::AdjustedRttUs => "adjusted_rtt_us",
|
||||
Self::ReceiveDelay => "rd",
|
||||
Self::ReceiveDelayUs => "rd_us",
|
||||
Self::SendDelay => "sd",
|
||||
Self::SendDelayUs => "sd_us",
|
||||
Self::Ipdv => "ipdv",
|
||||
Self::IpdvUs => "ipdv_us",
|
||||
Self::ServerProcessing => "proc",
|
||||
Self::ServerProcessingUs => "server_processing_us",
|
||||
Self::Bytes => "bytes",
|
||||
Self::SendCallUs => "send_call_us",
|
||||
Self::TimerErrorUs => "timer_error_us",
|
||||
Self::HighestSeen => "highest_seen",
|
||||
Self::ServerReceivedCount => "server_received",
|
||||
Self::ServerReceivedWindow => "server_window",
|
||||
Self::Dscp => "dscp",
|
||||
Self::Ecn => "ecn",
|
||||
Self::TrafficClass => "traffic_class",
|
||||
Self::KernelRxNs => "kernel_rx_ns",
|
||||
Self::WarningKind => "warning_kind",
|
||||
Self::Message => "message",
|
||||
Self::EventWallNs => "event_wall_ns",
|
||||
Self::ClientSendWallNs => "client_send_wall_ns",
|
||||
Self::ClientReceiveWallNs => "client_receive_wall_ns",
|
||||
Self::DurationNs => "duration_ns",
|
||||
Self::IntervalNs => "interval_ns",
|
||||
Self::PayloadLength => "payload_length",
|
||||
Self::ServerReceiveWallNs => "server_receive_wall_ns",
|
||||
Self::ServerReceiveMonoNs => "server_receive_mono_ns",
|
||||
Self::ServerSendWallNs => "server_send_wall_ns",
|
||||
Self::ServerSendMonoNs => "server_send_mono_ns",
|
||||
Self::ServerMidpointWallNs => "server_midpoint_wall_ns",
|
||||
Self::ServerMidpointMonoNs => "server_midpoint_mono_ns",
|
||||
}
|
||||
match self { $(Self::$variant => $name),* }
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Option<Self> {
|
||||
Some(match input {
|
||||
"target" => Self::Target,
|
||||
"event" => Self::Event,
|
||||
"seq" => Self::Seq,
|
||||
"remote" => Self::Remote,
|
||||
"token" => Self::Token,
|
||||
"rtt" => Self::Rtt,
|
||||
"rtt_us" => Self::RttUs,
|
||||
"raw_rtt_us" => Self::RawRttUs,
|
||||
"effective_rtt_us" => Self::EffectiveRttUs,
|
||||
"adjusted_rtt_us" => Self::AdjustedRttUs,
|
||||
"rd" | "receive_delay" => Self::ReceiveDelay,
|
||||
"rd_us" | "receive_delay_us" => Self::ReceiveDelayUs,
|
||||
"sd" | "send_delay" => Self::SendDelay,
|
||||
"sd_us" | "send_delay_us" => Self::SendDelayUs,
|
||||
"ipdv" => Self::Ipdv,
|
||||
"ipdv_us" => Self::IpdvUs,
|
||||
"proc" | "server_processing" => Self::ServerProcessing,
|
||||
"server_processing_us" => Self::ServerProcessingUs,
|
||||
"bytes" => Self::Bytes,
|
||||
"send_call_us" => Self::SendCallUs,
|
||||
"timer_error_us" => Self::TimerErrorUs,
|
||||
"highest_seen" => Self::HighestSeen,
|
||||
"server_received" | "server_received_count" => Self::ServerReceivedCount,
|
||||
"server_window" | "server_received_window" => Self::ServerReceivedWindow,
|
||||
"dscp" => Self::Dscp,
|
||||
"ecn" => Self::Ecn,
|
||||
"traffic_class" => Self::TrafficClass,
|
||||
"kernel_rx_ns" => Self::KernelRxNs,
|
||||
"warning_kind" => Self::WarningKind,
|
||||
"message" => Self::Message,
|
||||
"event_wall_ns" => Self::EventWallNs,
|
||||
"client_send_wall_ns" => Self::ClientSendWallNs,
|
||||
"client_receive_wall_ns" => Self::ClientReceiveWallNs,
|
||||
"duration_ns" => Self::DurationNs,
|
||||
"interval_ns" => Self::IntervalNs,
|
||||
"payload_length" => Self::PayloadLength,
|
||||
"server_receive_wall_ns" => Self::ServerReceiveWallNs,
|
||||
"server_receive_mono_ns" => Self::ServerReceiveMonoNs,
|
||||
"server_send_wall_ns" => Self::ServerSendWallNs,
|
||||
"server_send_mono_ns" => Self::ServerSendMonoNs,
|
||||
"server_midpoint_wall_ns" => Self::ServerMidpointWallNs,
|
||||
"server_midpoint_mono_ns" => Self::ServerMidpointMonoNs,
|
||||
_ => return None,
|
||||
})
|
||||
match input {
|
||||
$($name $(| $alias)* => Some(Self::$variant),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn aliases(self) -> &'static [&'static str] {
|
||||
match self { $(Self::$variant => &[$($alias),*]),* }
|
||||
}
|
||||
|
||||
fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::Target => "logical CLI target label",
|
||||
Self::Event => "event kind",
|
||||
Self::Seq => "probe sequence number",
|
||||
Self::Remote => "remote socket address",
|
||||
Self::Token => "session token as hexadecimal",
|
||||
Self::Rtt => "human-readable effective RTT",
|
||||
Self::RttUs => "effective RTT in signed microseconds",
|
||||
Self::RawRttUs => "raw client send-to-receive RTT in microseconds",
|
||||
Self::EffectiveRttUs => "effective RTT in signed microseconds",
|
||||
Self::AdjustedRttUs => "adjusted RTT in signed microseconds",
|
||||
Self::ReceiveDelay => "human-readable server-to-client delay",
|
||||
Self::ReceiveDelayUs => "server-to-client delay in signed microseconds",
|
||||
Self::SendDelay => "human-readable client-to-server delay",
|
||||
Self::SendDelayUs => "client-to-server delay in signed microseconds",
|
||||
Self::Ipdv => "human-readable round-trip IPDV for adjacent samples",
|
||||
Self::IpdvUs => "round-trip IPDV in microseconds",
|
||||
Self::ServerProcessing => "human-readable server processing time",
|
||||
Self::ServerProcessingUs => "server processing time in microseconds",
|
||||
Self::Bytes => "packet bytes for packet events",
|
||||
Self::SendCallUs => "send system call duration in microseconds",
|
||||
Self::TimerErrorUs => "scheduled-vs-actual send timer error in microseconds",
|
||||
Self::HighestSeen => "highest sequence seen when a late reply arrived",
|
||||
Self::ServerReceivedCount => "server-reported received packet count",
|
||||
Self::ServerReceivedWindow => "server-reported received window as hexadecimal",
|
||||
Self::Dscp => "received packet DSCP codepoint",
|
||||
Self::Ecn => "received packet ECN bits",
|
||||
Self::TrafficClass => "received packet traffic class byte",
|
||||
Self::KernelRxNs => "kernel receive timestamp as Unix nanoseconds",
|
||||
Self::WarningKind => "warning classifier",
|
||||
Self::Message => "warning or lifecycle message",
|
||||
Self::EventWallNs => "event wall timestamp as Unix nanoseconds",
|
||||
Self::ClientSendWallNs => "client send wall timestamp as Unix nanoseconds",
|
||||
Self::ClientReceiveWallNs => "client receive wall timestamp as Unix nanoseconds",
|
||||
Self::DurationNs => "negotiated test duration in nanoseconds",
|
||||
Self::IntervalNs => "negotiated probe interval in nanoseconds",
|
||||
Self::PayloadLength => "negotiated payload length",
|
||||
Self::ServerReceiveWallNs => "server receive wall timestamp in nanoseconds",
|
||||
Self::ServerReceiveMonoNs => "server receive monotonic timestamp in nanoseconds",
|
||||
Self::ServerSendWallNs => "server send wall timestamp in nanoseconds",
|
||||
Self::ServerSendMonoNs => "server send monotonic timestamp in nanoseconds",
|
||||
Self::ServerMidpointWallNs => "server midpoint wall timestamp in nanoseconds",
|
||||
Self::ServerMidpointMonoNs => "server midpoint monotonic timestamp in nanoseconds",
|
||||
}
|
||||
match self { $(Self::$variant => $description),* }
|
||||
}
|
||||
|
||||
fn table_width(self) -> usize {
|
||||
match self {
|
||||
Self::Target => 18,
|
||||
Self::Event => 17,
|
||||
Self::Seq => 6,
|
||||
Self::Remote => 21,
|
||||
Self::Token => 18,
|
||||
Self::Rtt
|
||||
| Self::ReceiveDelay
|
||||
| Self::SendDelay
|
||||
| Self::Ipdv
|
||||
| Self::ServerProcessing => 9,
|
||||
Self::Message => 24,
|
||||
Self::WarningKind => 28,
|
||||
Self::ServerReceivedWindow => 13,
|
||||
Self::ServerReceivedCount | Self::HighestSeen => 15,
|
||||
Self::Dscp | Self::Ecn => 4,
|
||||
Self::TrafficClass => 13,
|
||||
_ => self.name().len().max(10),
|
||||
}
|
||||
match self { $(Self::$variant => $width),* }
|
||||
}
|
||||
|
||||
fn align_right(self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
Self::Target
|
||||
| Self::Event
|
||||
| Self::Remote
|
||||
| Self::Token
|
||||
| Self::Message
|
||||
| Self::WarningKind
|
||||
| Self::ServerReceivedWindow
|
||||
)
|
||||
match self { $(Self::$variant => columns!(@align $align)),* }
|
||||
}
|
||||
}
|
||||
};
|
||||
(@align left) => { false };
|
||||
(@align right) => { true };
|
||||
}
|
||||
|
||||
// Variant(canonical name, aliases, table width, alignment, description).
|
||||
columns! {
|
||||
Target("target", [], 18, left, "logical CLI target label");
|
||||
Event("event", [], 17, left, "event kind");
|
||||
Seq("seq", [], 6, right, "probe sequence number");
|
||||
Remote("remote", [], 21, left, "remote socket address");
|
||||
Token("token", [], 18, left, "session token as hexadecimal");
|
||||
Rtt("rtt", [], 9, right, "human-readable effective RTT");
|
||||
RttUs("rtt_us", [], 10, right, "effective RTT in signed microseconds");
|
||||
RawRttUs("raw_rtt_us", [], 10, right, "raw client send-to-receive RTT in microseconds");
|
||||
EffectiveRttUs("effective_rtt_us", [], 16, right, "effective RTT in signed microseconds");
|
||||
AdjustedRttUs("adjusted_rtt_us", [], 15, right, "adjusted RTT in signed microseconds");
|
||||
ReceiveDelay("rd", ["receive_delay"], 9, right, "human-readable server-to-client delay");
|
||||
ReceiveDelayUs("rd_us", ["receive_delay_us"], 10, right, "server-to-client delay in signed microseconds");
|
||||
SendDelay("sd", ["send_delay"], 9, right, "human-readable client-to-server delay");
|
||||
SendDelayUs("sd_us", ["send_delay_us"], 10, right, "client-to-server delay in signed microseconds");
|
||||
Ipdv("ipdv", [], 9, right, "human-readable round-trip IPDV for adjacent samples");
|
||||
IpdvUs("ipdv_us", [], 10, right, "round-trip IPDV in microseconds");
|
||||
ServerProcessing("proc", ["server_processing"], 9, right, "human-readable server processing time");
|
||||
ServerProcessingUs("server_processing_us", [], 20, right, "server processing time in microseconds");
|
||||
Bytes("bytes", [], 10, right, "packet bytes for packet events");
|
||||
SendCallUs("send_call_us", [], 12, right, "send system call duration in microseconds");
|
||||
TimerErrorUs("timer_error_us", [], 14, right, "scheduled-vs-actual send timer error in microseconds");
|
||||
HighestSeen("highest_seen", [], 15, right, "highest sequence seen when a late reply arrived");
|
||||
ServerReceivedCount("server_received", ["server_received_count"], 15, right, "server-reported received packet count");
|
||||
ServerReceivedWindow("server_window", ["server_received_window"], 13, left, "server-reported received window as hexadecimal");
|
||||
Dscp("dscp", [], 4, right, "received packet DSCP codepoint");
|
||||
Ecn("ecn", [], 4, right, "received packet ECN bits");
|
||||
TrafficClass("traffic_class", [], 13, right, "received packet traffic class byte");
|
||||
KernelRxNs("kernel_rx_ns", [], 12, right, "kernel receive timestamp as Unix nanoseconds");
|
||||
WarningKind("warning_kind", [], 28, left, "warning classifier");
|
||||
Message("message", [], 24, left, "warning or lifecycle message");
|
||||
EventWallNs("event_wall_ns", [], 13, right, "event wall timestamp as Unix nanoseconds");
|
||||
ClientSendWallNs("client_send_wall_ns", [], 19, right, "client send wall timestamp as Unix nanoseconds");
|
||||
ClientReceiveWallNs("client_receive_wall_ns", [], 22, right, "client receive wall timestamp as Unix nanoseconds");
|
||||
DurationNs("duration_ns", [], 11, right, "negotiated test duration in nanoseconds");
|
||||
IntervalNs("interval_ns", [], 11, right, "negotiated probe interval in nanoseconds");
|
||||
PayloadLength("payload_length", [], 14, right, "negotiated payload length");
|
||||
ServerReceiveWallNs("server_receive_wall_ns", [], 22, right, "server receive wall timestamp in nanoseconds");
|
||||
ServerReceiveMonoNs("server_receive_mono_ns", [], 22, right, "server receive monotonic timestamp in nanoseconds");
|
||||
ServerSendWallNs("server_send_wall_ns", [], 19, right, "server send wall timestamp in nanoseconds");
|
||||
ServerSendMonoNs("server_send_mono_ns", [], 19, right, "server send monotonic timestamp in nanoseconds");
|
||||
ServerMidpointWallNs("server_midpoint_wall_ns", [], 23, right, "server midpoint wall timestamp in nanoseconds");
|
||||
ServerMidpointMonoNs("server_midpoint_mono_ns", [], 23, right, "server midpoint monotonic timestamp in nanoseconds");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum CellValue {
|
||||
@@ -890,9 +775,11 @@ fn cell_for(row: &OutputRow, column: Column, context: RenderContext<'_>) -> Opti
|
||||
match column {
|
||||
Column::Target => context.target.map(CellValue::text),
|
||||
Column::Event => Some(CellValue::text(row.event_name())),
|
||||
Column::Seq => row_seq(row).map(|seq| CellValue::Unsigned(u128::from(seq))),
|
||||
Column::Remote => row_remote(row).map(|remote| CellValue::text(remote.to_string())),
|
||||
Column::Token => row_token(row).map(|token| CellValue::Hex(u128::from(token))),
|
||||
Column::Seq => row.seq().map(|seq| CellValue::Unsigned(u128::from(seq))),
|
||||
Column::Remote => row
|
||||
.remote()
|
||||
.map(|remote| CellValue::text(remote.to_string())),
|
||||
Column::Token => row.token().map(|token| CellValue::Hex(u128::from(token))),
|
||||
Column::Rtt => row.reply().and_then(|reply| {
|
||||
reply
|
||||
.rtt
|
||||
@@ -930,10 +817,12 @@ fn cell_for(row: &OutputRow, column: Column, context: RenderContext<'_>) -> Opti
|
||||
.reply()
|
||||
.and_then(|reply| reply.one_way.and_then(|one_way| one_way.client_to_server))
|
||||
.map(|value| CellValue::Integer(signed_duration_us(value))),
|
||||
Column::Ipdv => row_seq(row)
|
||||
Column::Ipdv => row
|
||||
.seq()
|
||||
.and_then(|seq| ipdv_pair(context.stats, seq))
|
||||
.map(|pair| CellValue::text(format_duration(pair.rtt_ipdv))),
|
||||
Column::IpdvUs => row_seq(row)
|
||||
Column::IpdvUs => row
|
||||
.seq()
|
||||
.and_then(|seq| ipdv_pair(context.stats, seq))
|
||||
.map(|pair| CellValue::Unsigned(duration_us(pair.rtt_ipdv))),
|
||||
Column::ServerProcessing => row
|
||||
@@ -944,7 +833,7 @@ fn cell_for(row: &OutputRow, column: Column, context: RenderContext<'_>) -> Opti
|
||||
.reply()
|
||||
.and_then(|reply| reply.server_timing.and_then(|timing| timing.processing))
|
||||
.map(|value| CellValue::Unsigned(duration_us(value))),
|
||||
Column::Bytes => row_bytes(row).map(|bytes| CellValue::Unsigned(bytes as u128)),
|
||||
Column::Bytes => row.bytes().map(|bytes| CellValue::Unsigned(bytes as u128)),
|
||||
Column::SendCallUs => match row {
|
||||
OutputRow::EchoSent { send_call, .. } => {
|
||||
Some(CellValue::Unsigned(duration_us(*send_call)))
|
||||
@@ -992,14 +881,17 @@ fn cell_for(row: &OutputRow, column: Column, context: RenderContext<'_>) -> Opti
|
||||
OutputRow::Warning { kind, .. } => Some(CellValue::text(warning_kind(*kind))),
|
||||
_ => None,
|
||||
},
|
||||
Column::Message => row_message(row, context).map(CellValue::text),
|
||||
Column::EventWallNs => row_event_wall(row)
|
||||
Column::Message => row.message(context).map(CellValue::text),
|
||||
Column::EventWallNs => row
|
||||
.event_wall()
|
||||
.and_then(wall_time_ns)
|
||||
.map(CellValue::Unsigned),
|
||||
Column::ClientSendWallNs => row_client_send_wall(row)
|
||||
Column::ClientSendWallNs => row
|
||||
.client_send_wall()
|
||||
.and_then(wall_time_ns)
|
||||
.map(CellValue::Unsigned),
|
||||
Column::ClientReceiveWallNs => row_client_receive_wall(row)
|
||||
Column::ClientReceiveWallNs => row
|
||||
.client_receive_wall()
|
||||
.and_then(wall_time_ns)
|
||||
.map(CellValue::Unsigned),
|
||||
Column::DurationNs => match row {
|
||||
@@ -1029,110 +921,6 @@ fn cell_for(row: &OutputRow, column: Column, context: RenderContext<'_>) -> Opti
|
||||
}
|
||||
}
|
||||
|
||||
fn row_seq(row: &OutputRow) -> Option<u32> {
|
||||
match row {
|
||||
OutputRow::EchoSent { seq, .. }
|
||||
| OutputRow::Loss { seq, .. }
|
||||
| OutputRow::Duplicate { seq, .. } => Some(*seq),
|
||||
OutputRow::EchoReply(reply) | OutputRow::Late { reply, .. } => Some(reply.seq),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn row_remote(row: &OutputRow) -> Option<SocketAddr> {
|
||||
match row {
|
||||
OutputRow::SessionStarted(row) | OutputRow::NoTestCompleted(row) => Some(row.remote),
|
||||
OutputRow::SessionClosed { remote, .. }
|
||||
| OutputRow::EchoSent { remote, .. }
|
||||
| OutputRow::Duplicate { remote, .. } => Some(*remote),
|
||||
OutputRow::EchoReply(reply) | OutputRow::Late { reply, .. } => Some(reply.remote),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn row_token(row: &OutputRow) -> Option<u64> {
|
||||
match row {
|
||||
OutputRow::SessionStarted(row) | OutputRow::NoTestCompleted(row) => row.token,
|
||||
OutputRow::SessionClosed { token, .. } => Some(*token),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn row_bytes(row: &OutputRow) -> Option<usize> {
|
||||
match row {
|
||||
OutputRow::EchoSent { bytes, .. } | OutputRow::Duplicate { bytes, .. } => Some(*bytes),
|
||||
OutputRow::EchoReply(reply) | OutputRow::Late { reply, .. } => Some(reply.bytes),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn row_message(row: &OutputRow, context: RenderContext<'_>) -> Option<String> {
|
||||
match row {
|
||||
OutputRow::SessionStarted(row) => Some(format!(
|
||||
"token={:#x} duration_ns={} interval_ns={} length={}",
|
||||
row.token?, row.duration_ns, row.interval_ns, row.payload_length
|
||||
)),
|
||||
OutputRow::NoTestCompleted(row) => Some(format!(
|
||||
"duration_ns={} interval_ns={} length={}",
|
||||
row.duration_ns, row.interval_ns, row.payload_length
|
||||
)),
|
||||
OutputRow::SessionClosed { token, .. } => Some(format!("token={token:#x}")),
|
||||
OutputRow::Late { highest_seen, .. } => Some(format!("highest_seen={highest_seen}")),
|
||||
OutputRow::Warning { message, .. } => Some(message.clone()),
|
||||
OutputRow::Loss { .. } => Some("timeout".to_owned()),
|
||||
OutputRow::Duplicate { .. } if context.verbose => Some("duplicate reply".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn row_event_wall(row: &OutputRow) -> Option<SystemTime> {
|
||||
match row {
|
||||
OutputRow::SessionStarted(row) | OutputRow::NoTestCompleted(row) => Some(row.event_wall),
|
||||
OutputRow::SessionClosed { event_wall, .. } | OutputRow::Warning { event_wall, .. } => {
|
||||
Some(*event_wall)
|
||||
}
|
||||
OutputRow::EchoSent {
|
||||
client_send_wall, ..
|
||||
}
|
||||
| OutputRow::Loss {
|
||||
client_send_wall, ..
|
||||
} => Some(*client_send_wall),
|
||||
OutputRow::Duplicate {
|
||||
client_receive_wall,
|
||||
..
|
||||
} => Some(*client_receive_wall),
|
||||
OutputRow::EchoReply(reply) | OutputRow::Late { reply, .. } => {
|
||||
Some(reply.client_receive_wall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn row_client_send_wall(row: &OutputRow) -> Option<SystemTime> {
|
||||
match row {
|
||||
OutputRow::EchoSent {
|
||||
client_send_wall, ..
|
||||
}
|
||||
| OutputRow::Loss {
|
||||
client_send_wall, ..
|
||||
} => Some(*client_send_wall),
|
||||
OutputRow::EchoReply(reply) | OutputRow::Late { reply, .. } => reply.client_send_wall,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn row_client_receive_wall(row: &OutputRow) -> Option<SystemTime> {
|
||||
match row {
|
||||
OutputRow::Duplicate {
|
||||
client_receive_wall,
|
||||
..
|
||||
} => Some(*client_receive_wall),
|
||||
OutputRow::EchoReply(reply) | OutputRow::Late { reply, .. } => {
|
||||
Some(reply.client_receive_wall)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn server_timing_i64(
|
||||
row: &OutputRow,
|
||||
select: impl FnOnce(ServerTiming) -> Option<i64>,
|
||||
@@ -1143,7 +931,7 @@ fn server_timing_i64(
|
||||
.map(|value| CellValue::Integer(i128::from(value)))
|
||||
}
|
||||
|
||||
fn ipdv_pair(stats: Option<&EventRenderStats>, seq: u32) -> Option<&IpdvPair> {
|
||||
fn ipdv_pair(stats: Option<&EventStatsUpdate>, seq: u32) -> Option<&IpdvPairUpdate> {
|
||||
let stats = stats?;
|
||||
stats
|
||||
.ipdv_pairs
|
||||
|
||||
@@ -13,10 +13,7 @@ use irtt_client::{
|
||||
ClientEvent,
|
||||
};
|
||||
|
||||
use super::{
|
||||
args::ClientArgs,
|
||||
output::{EventRenderStats, OutputConfig},
|
||||
};
|
||||
use super::{args::ClientArgs, output::OutputConfig};
|
||||
|
||||
use crate::shared::client::{
|
||||
expected_probe_count, parse_stdin_target_set,
|
||||
@@ -28,7 +25,7 @@ use crate::shared::client::{
|
||||
ManagedRunSetup, STDIN_MAX_DESIRED_TARGETS, STDIN_OUTCOME_HISTORY_LIMIT,
|
||||
};
|
||||
|
||||
use irtt_stats::{StatsCollector, StatsConfig};
|
||||
use irtt_stats::{EventStatsUpdate, StatsCollector, StatsConfig};
|
||||
|
||||
const MIB: u64 = 1024 * 1024;
|
||||
|
||||
@@ -373,12 +370,8 @@ fn process_stdin_event<W: Write>(
|
||||
let collector = stats
|
||||
.entry(target.clone())
|
||||
.or_insert_with(|| StatsCollector::new(stats_config(true)));
|
||||
print_events_with_stats(
|
||||
stream_output,
|
||||
std::slice::from_ref(&event),
|
||||
Some(target.id.as_str()),
|
||||
collector,
|
||||
)?;
|
||||
let update = collector.process(&event);
|
||||
stream_output.print_event(&event, Some(target.id.as_str()), &update)?;
|
||||
}
|
||||
ManagedEvent::TargetFinished { outcome }
|
||||
if terminal_targets.insert(outcome.target.clone()) =>
|
||||
@@ -611,12 +604,8 @@ fn process_event<W: Write>(
|
||||
let collector = stats
|
||||
.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,
|
||||
)?;
|
||||
let update = collector.process(&event);
|
||||
stream_output.print_event(&event, Some(target.id.as_str()), &update)?;
|
||||
}
|
||||
ManagedEvent::TargetFinished { outcome } => {
|
||||
terminal_targets.insert(outcome.target.clone());
|
||||
@@ -661,18 +650,16 @@ struct StreamOutput<'a, W: Write> {
|
||||
out: &'a mut W,
|
||||
}
|
||||
impl<W: Write> StreamOutput<'_, W> {
|
||||
fn print_events(
|
||||
fn print_event(
|
||||
&mut self,
|
||||
events: &[ClientEvent],
|
||||
event: &ClientEvent,
|
||||
target: Option<&str>,
|
||||
stats_updates: &[EventRenderStats],
|
||||
update: &EventStatsUpdate,
|
||||
) -> io::Result<()> {
|
||||
self.print_header()?;
|
||||
for (event, stats_update) in events.iter().zip(stats_updates) {
|
||||
if let Some(line) = self.config.render_event(event, target, Some(stats_update)) {
|
||||
if let Some(line) = self.config.render_event(event, target, Some(update)) {
|
||||
writeln!(self.out, "{line}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn print_header(&mut self) -> io::Result<()> {
|
||||
@@ -704,19 +691,6 @@ impl<W: Write> StreamOutput<'_, W> {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_events_with_stats<W: Write>(
|
||||
stream_output: &mut StreamOutput<'_, W>,
|
||||
events: &[ClientEvent],
|
||||
target: Option<&str>,
|
||||
stats: &mut StatsCollector,
|
||||
) -> io::Result<()> {
|
||||
let updates = events
|
||||
.iter()
|
||||
.map(|event| EventRenderStats::from(stats.process(event)))
|
||||
.collect::<Vec<_>>();
|
||||
stream_output.print_events(events, target, &updates)
|
||||
}
|
||||
|
||||
fn stats_config(continuous: bool) -> StatsConfig {
|
||||
if continuous {
|
||||
StatsConfig::continuous()
|
||||
|
||||
Reference in new issue
Block a user