refactor(tui): streamline TUI state management and graph data representation
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 8s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 9s
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 29s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m4s
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 8s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 9s
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 29s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m4s
Consolidate target creation logic and store richer RTT sample types directly. Improve module encapsulation and enforce invariants for robustness.
This commit is contained in:
1 file changed
+109
-171
+109
-171
@@ -11,8 +11,8 @@ use crossterm::{
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use irtt_client::managed::{ManagedTargetEndReason, ManagedTargetOutcome, TargetInstance};
|
||||
use irtt_client::{ClientEvent, NegotiationResult, SignedDuration};
|
||||
use irtt_stats::{Snapshot, StatsCollector, TimeStats};
|
||||
use irtt_client::{ClientEvent, NegotiationResult, OneWayDelaySample, RttSample, SignedDuration};
|
||||
use irtt_stats::{StatsCollector, TimeStats};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Constraint, Layout, Rect},
|
||||
@@ -109,10 +109,6 @@ pub(super) struct TuiState {
|
||||
}
|
||||
|
||||
impl TuiState {
|
||||
pub(super) fn new(config: TuiConfig) -> Self {
|
||||
Self::with_target_labels(config, ["target".to_owned()])
|
||||
}
|
||||
|
||||
pub(super) fn with_target_labels(
|
||||
config: TuiConfig,
|
||||
labels: impl IntoIterator<Item = String>,
|
||||
@@ -122,6 +118,9 @@ impl TuiState {
|
||||
.into_iter()
|
||||
.map(|label| TuiTargetState::new(label, stats_config))
|
||||
.collect::<Vec<_>>();
|
||||
// The sole caller supplies prepared, nonempty targets. Entries are never
|
||||
// removed, and selection only cycles within this vector.
|
||||
assert!(!targets.is_empty(), "TUI requires prepared targets");
|
||||
let target_index = targets
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -145,37 +144,29 @@ impl TuiState {
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_target(&mut self, label: &str) -> usize {
|
||||
if let Some(&idx) = self.target_index.get(label) {
|
||||
return idx;
|
||||
}
|
||||
let idx = self.targets.len();
|
||||
let mut target = TuiTargetState::new(
|
||||
label.to_owned(),
|
||||
stats_config(self.config.duration.is_none()),
|
||||
);
|
||||
target.status = TargetStatus::Unknown;
|
||||
self.targets.push(target);
|
||||
self.target_index.insert(label.to_owned(), idx);
|
||||
idx
|
||||
}
|
||||
|
||||
pub(super) fn process_target_event(&mut self, target: &TargetInstance, event: &ClientEvent) {
|
||||
let label = target.id.as_str();
|
||||
let idx = if let Some(idx) = self.target_index.get(label).copied() {
|
||||
idx
|
||||
} else {
|
||||
let idx = self.targets.len();
|
||||
let mut target = TuiTargetState::new(
|
||||
label.to_owned(),
|
||||
stats_config(self.config.duration.is_none()),
|
||||
);
|
||||
target.status = TargetStatus::Unknown;
|
||||
self.targets.push(target);
|
||||
self.target_index.insert(label.to_owned(), idx);
|
||||
idx
|
||||
};
|
||||
let idx = self.ensure_target(target.id.as_str());
|
||||
self.process_event_for_target(idx, event);
|
||||
}
|
||||
|
||||
pub(super) fn process_target_outcome(&mut self, outcome: &ManagedTargetOutcome) {
|
||||
let label = outcome.target.id.as_str();
|
||||
let idx = if let Some(idx) = self.target_index.get(label).copied() {
|
||||
idx
|
||||
} else {
|
||||
let idx = self.targets.len();
|
||||
self.targets.push(TuiTargetState::new(
|
||||
label.to_owned(),
|
||||
stats_config(self.config.duration.is_none()),
|
||||
));
|
||||
self.target_index.insert(label.to_owned(), idx);
|
||||
idx
|
||||
};
|
||||
let idx = self.ensure_target(label);
|
||||
|
||||
let (status, recent, primary_warning) = match &outcome.end_reason {
|
||||
ManagedTargetEndReason::TestComplete => {
|
||||
@@ -223,12 +214,11 @@ impl TuiState {
|
||||
(None, None) => None,
|
||||
};
|
||||
|
||||
if let Some(target) = self.targets.get_mut(idx) {
|
||||
target.remote = outcome.remote.map(|remote| remote.to_string());
|
||||
target.status = status;
|
||||
if let Some(warning) = &warning {
|
||||
target.last_warning = Some(warning.clone());
|
||||
}
|
||||
let target = &mut self.targets[idx];
|
||||
target.remote = outcome.remote.map(|remote| remote.to_string());
|
||||
target.status = status;
|
||||
if let Some(warning) = &warning {
|
||||
target.last_warning = Some(warning.clone());
|
||||
}
|
||||
if let Some(warning) = warning {
|
||||
self.last_warning = Some(format!("{label}: {warning}"));
|
||||
@@ -262,9 +252,7 @@ impl TuiState {
|
||||
let mut global_status = None;
|
||||
let mut global_warning = None;
|
||||
let label = {
|
||||
let Some(target) = self.targets.get_mut(target_idx) else {
|
||||
return;
|
||||
};
|
||||
let target = &mut self.targets[target_idx];
|
||||
target.stats.process(event);
|
||||
match event {
|
||||
ClientEvent::SessionStarted(irtt_client::SessionStarted {
|
||||
@@ -278,7 +266,7 @@ impl TuiState {
|
||||
target.negotiated = Some(negotiated.clone());
|
||||
target.status = TargetStatus::Active;
|
||||
global_status = Some(TuiStatus::Running);
|
||||
recent = Some(format!("session started token={token:#x}"));
|
||||
recent = format!("session started token={token:#x}");
|
||||
}
|
||||
ClientEvent::NoTestCompleted(irtt_client::NoTestCompleted {
|
||||
remote,
|
||||
@@ -289,16 +277,16 @@ impl TuiState {
|
||||
target.negotiated = Some(negotiated.clone());
|
||||
target.status = TargetStatus::NoTest;
|
||||
global_status = Some(TuiStatus::Complete);
|
||||
recent = Some("no-test negotiation completed".to_owned());
|
||||
recent = "no-test negotiation completed".to_owned();
|
||||
}
|
||||
ClientEvent::SessionClosed { token, .. } => {
|
||||
target.session = Some(format!("{token:#x}"));
|
||||
target.status = TargetStatus::Closed;
|
||||
global_status = Some(TuiStatus::Complete);
|
||||
recent = Some(format!("session closed token={token:#x}"));
|
||||
recent = format!("session closed token={token:#x}");
|
||||
}
|
||||
ClientEvent::EchoSent { seq, bytes, .. } => {
|
||||
recent = Some(format!("sent seq={} bytes={bytes}", seq));
|
||||
recent = format!("sent seq={} bytes={bytes}", seq);
|
||||
}
|
||||
ClientEvent::EchoReply {
|
||||
seq,
|
||||
@@ -308,36 +296,24 @@ impl TuiState {
|
||||
server_timing,
|
||||
..
|
||||
} => {
|
||||
let client_to_server_ns = one_way
|
||||
.and_then(|sample| sample.client_to_server)
|
||||
.map(SignedDuration::as_nanos);
|
||||
let server_to_client_ns = one_way
|
||||
.and_then(|sample| sample.server_to_client)
|
||||
.map(SignedDuration::as_nanos);
|
||||
let server_processing_ns = server_timing
|
||||
.and_then(|timing| timing.processing)
|
||||
.map(duration_ns);
|
||||
target.push_graph_sample(GraphSample {
|
||||
timestamp: received_at.mono,
|
||||
seq: *seq,
|
||||
effective_ns: rtt.effective.as_nanos(),
|
||||
raw_ns: duration_ns(rtt.raw),
|
||||
adjusted_ns: rtt.adjusted.map(SignedDuration::as_nanos),
|
||||
client_to_server_ns,
|
||||
server_to_client_ns,
|
||||
server_processing_ns,
|
||||
rtt: *rtt,
|
||||
one_way: *one_way,
|
||||
server_processing: server_timing.and_then(|timing| timing.processing),
|
||||
});
|
||||
recent = Some(format!(
|
||||
recent = format!(
|
||||
"reply seq={} effective={}",
|
||||
seq,
|
||||
format_optional_ns_i128(Some(rtt.effective.as_nanos()))
|
||||
));
|
||||
);
|
||||
}
|
||||
ClientEvent::EchoLoss { seq, .. } => {
|
||||
recent = Some(format!("loss seq={}", seq));
|
||||
recent = format!("loss seq={}", seq);
|
||||
}
|
||||
ClientEvent::DuplicateReply { seq, remote, .. } => {
|
||||
recent = Some(format!("duplicate seq={} from {remote}", seq));
|
||||
recent = format!("duplicate seq={} from {remote}", seq);
|
||||
}
|
||||
ClientEvent::LateReply {
|
||||
seq,
|
||||
@@ -353,16 +329,13 @@ impl TuiState {
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
recent = Some(format!(
|
||||
"late seq={} highest_seen={}{}",
|
||||
seq, highest_seen, timing
|
||||
));
|
||||
recent = format!("late seq={} highest_seen={}{}", seq, highest_seen, timing);
|
||||
}
|
||||
ClientEvent::Warning { kind, message, .. } => {
|
||||
let warning = format!("{kind:?}: {message}");
|
||||
target.last_warning = Some(warning.clone());
|
||||
global_warning = Some(warning.clone());
|
||||
recent = Some(format!("warning {warning}"));
|
||||
recent = format!("warning {warning}");
|
||||
}
|
||||
}
|
||||
target.label.clone()
|
||||
@@ -382,9 +355,7 @@ impl TuiState {
|
||||
if let Some(warning) = global_warning {
|
||||
self.last_warning = Some(format!("{label}: {warning}"));
|
||||
}
|
||||
if let Some(recent) = recent {
|
||||
self.push_event(format!("{label}: {recent}"));
|
||||
}
|
||||
self.push_event(format!("{label}: {recent}"));
|
||||
}
|
||||
|
||||
pub(super) fn set_status(&mut self, status: TuiStatus) {
|
||||
@@ -392,10 +363,6 @@ impl TuiState {
|
||||
}
|
||||
|
||||
pub(super) fn mark_dropped_managed_events(&mut self, dropped_events: u64) {
|
||||
self.mark_dropped_events(dropped_events, "managed run");
|
||||
}
|
||||
|
||||
fn mark_dropped_events(&mut self, dropped_events: u64, source: &str) {
|
||||
if dropped_events == 0 {
|
||||
return;
|
||||
}
|
||||
@@ -405,20 +372,18 @@ impl TuiState {
|
||||
} else {
|
||||
"events"
|
||||
};
|
||||
let warning =
|
||||
format!("dropped {dropped_events} {source} {event_word}; statistics may be incomplete");
|
||||
let warning = format!(
|
||||
"dropped {dropped_events} managed run {event_word}; statistics may be incomplete"
|
||||
);
|
||||
self.last_warning = Some(warning.clone());
|
||||
self.push_event(format!("warning {warning}"));
|
||||
}
|
||||
|
||||
pub(super) fn set_error(&mut self, message: String) {
|
||||
self.status = TuiStatus::Error;
|
||||
self.last_warning = Some(message.clone());
|
||||
if let Some(target) = self.targets.first_mut() {
|
||||
target.status = TargetStatus::Failed;
|
||||
target.last_warning = Some(message.clone());
|
||||
}
|
||||
self.push_event(format!("error {message}"));
|
||||
let target = &mut self.targets[0];
|
||||
target.status = TargetStatus::Failed;
|
||||
target.last_warning = Some(message.clone());
|
||||
self.set_run_error(message);
|
||||
}
|
||||
|
||||
pub(super) fn set_run_error(&mut self, message: String) {
|
||||
@@ -447,14 +412,12 @@ impl TuiState {
|
||||
}
|
||||
KeyCode::Tab | KeyCode::BackTab => {
|
||||
let count = self.targets.len();
|
||||
if count != 0 {
|
||||
self.selected_target = if key == KeyCode::Tab {
|
||||
(self.selected_target + 1) % count
|
||||
} else {
|
||||
(self.selected_target + count - 1) % count
|
||||
};
|
||||
self.details_scroll = (0, 0);
|
||||
}
|
||||
self.selected_target = if key == KeyCode::Tab {
|
||||
(self.selected_target + 1) % count
|
||||
} else {
|
||||
(self.selected_target + count - 1) % count
|
||||
};
|
||||
self.details_scroll = (0, 0);
|
||||
}
|
||||
KeyCode::Char('p') => self.toggle_pause(),
|
||||
KeyCode::Char('r') => self.clear_visible_history(),
|
||||
@@ -502,14 +465,8 @@ impl TuiState {
|
||||
push_bounded(&mut self.recent_events, event, RECENT_EVENT_LIMIT);
|
||||
}
|
||||
|
||||
fn selected_target(&self) -> Option<&TuiTargetState> {
|
||||
self.targets.get(self.selected_target)
|
||||
}
|
||||
|
||||
fn selected_snapshot(&self) -> Snapshot {
|
||||
self.selected_target()
|
||||
.map(|target| target.stats.snapshot())
|
||||
.unwrap_or_else(|| StatsCollector::new(stats_config(true)).snapshot())
|
||||
fn selected_target(&self) -> &TuiTargetState {
|
||||
&self.targets[self.selected_target]
|
||||
}
|
||||
|
||||
fn oldest_graph_sample_time(&self) -> Option<Instant> {
|
||||
@@ -528,7 +485,7 @@ impl TuiState {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct TuiTargetState {
|
||||
struct TuiTargetState {
|
||||
label: String,
|
||||
remote: Option<String>,
|
||||
session: Option<String>,
|
||||
@@ -633,12 +590,6 @@ fn stats_config(continuous: bool) -> irtt_stats::StatsConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TuiState {
|
||||
fn default() -> Self {
|
||||
Self::new(TuiConfig::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct TuiConfig {
|
||||
interval: Duration,
|
||||
@@ -661,18 +612,6 @@ impl TuiConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TuiConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
interval: Duration::from_secs(1),
|
||||
duration: Some(Duration::from_secs(10)),
|
||||
timeout: Duration::from_secs(2),
|
||||
target_probes: Some(10),
|
||||
pacing: GroupPacingArg::Staggered,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum TuiStatus {
|
||||
Opening,
|
||||
@@ -828,6 +767,15 @@ enum GraphMetric {
|
||||
}
|
||||
|
||||
impl GraphMetric {
|
||||
const ALL: &[Self] = &[
|
||||
Self::EffectiveRtt,
|
||||
Self::RawRtt,
|
||||
Self::AdjustedRtt,
|
||||
Self::ClientToServer,
|
||||
Self::ServerToClient,
|
||||
Self::ServerProcessing,
|
||||
];
|
||||
|
||||
fn next(self) -> Self {
|
||||
match self {
|
||||
Self::EffectiveRtt => Self::RawRtt,
|
||||
@@ -861,12 +809,20 @@ impl GraphMetric {
|
||||
|
||||
fn value_ns(self, sample: &GraphSample) -> Option<i128> {
|
||||
match self {
|
||||
Self::EffectiveRtt => Some(sample.effective_ns),
|
||||
Self::RawRtt => Some(sample.raw_ns),
|
||||
Self::AdjustedRtt => sample.adjusted_ns,
|
||||
Self::ClientToServer => sample.client_to_server_ns,
|
||||
Self::ServerToClient => sample.server_to_client_ns,
|
||||
Self::ServerProcessing => sample.server_processing_ns,
|
||||
Self::EffectiveRtt => Some(sample.rtt.effective.as_nanos()),
|
||||
Self::RawRtt => Some(SignedDuration::from(sample.rtt.raw).as_nanos()),
|
||||
Self::AdjustedRtt => sample.rtt.adjusted.map(SignedDuration::as_nanos),
|
||||
Self::ClientToServer => sample
|
||||
.one_way
|
||||
.and_then(|sample| sample.client_to_server)
|
||||
.map(SignedDuration::as_nanos),
|
||||
Self::ServerToClient => sample
|
||||
.one_way
|
||||
.and_then(|sample| sample.server_to_client)
|
||||
.map(SignedDuration::as_nanos),
|
||||
Self::ServerProcessing => sample
|
||||
.server_processing
|
||||
.map(|duration| SignedDuration::from(duration).as_nanos()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,15 +838,12 @@ impl GraphMetric {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) struct GraphSample {
|
||||
struct GraphSample {
|
||||
timestamp: Instant,
|
||||
seq: u32,
|
||||
effective_ns: i128,
|
||||
raw_ns: i128,
|
||||
adjusted_ns: Option<i128>,
|
||||
client_to_server_ns: Option<i128>,
|
||||
server_to_client_ns: Option<i128>,
|
||||
server_processing_ns: Option<i128>,
|
||||
rtt: RttSample,
|
||||
one_way: Option<OneWayDelaySample>,
|
||||
server_processing: Option<Duration>,
|
||||
}
|
||||
|
||||
fn push_bounded<T>(items: &mut VecDeque<T>, item: T, limit: usize) {
|
||||
@@ -905,7 +858,7 @@ fn push_bounded<T>(items: &mut VecDeque<T>, item: T, limit: usize) {
|
||||
items.push_back(item);
|
||||
}
|
||||
|
||||
pub(super) fn draw_dashboard(frame: &mut Frame<'_>, state: &TuiState) {
|
||||
fn draw_dashboard(frame: &mut Frame<'_>, state: &TuiState) {
|
||||
let area = frame.area();
|
||||
if area.width < MIN_WIDTH || area.height < MIN_HEIGHT {
|
||||
frame.render_widget(
|
||||
@@ -952,7 +905,7 @@ fn dashboard_layout(area: Rect, state: &TuiState) -> [Rect; 4] {
|
||||
|
||||
fn header(state: &TuiState) -> Paragraph<'static> {
|
||||
let selected = state.selected_target();
|
||||
let last = selected.and_then(|target| target.last_sample);
|
||||
let last = selected.last_sample;
|
||||
let incomplete = if state.dropped_events > 0 {
|
||||
format!(" incomplete:dropped={}", state.dropped_events)
|
||||
} else {
|
||||
@@ -973,17 +926,17 @@ fn header(state: &TuiState) -> Paragraph<'static> {
|
||||
)),
|
||||
Line::from(format!(
|
||||
"{} | {}",
|
||||
selected
|
||||
.map(|target| target.label.as_str())
|
||||
.unwrap_or("target"),
|
||||
selected
|
||||
.and_then(|target| target.remote.as_deref())
|
||||
.unwrap_or(ABSENT),
|
||||
selected.label,
|
||||
selected.remote.as_deref().unwrap_or(ABSENT),
|
||||
)),
|
||||
Line::from(format!(
|
||||
"one-way c2s {} / s2c {}",
|
||||
format_optional_ns_i128(last.and_then(|sample| sample.client_to_server_ns)),
|
||||
format_optional_ns_i128(last.and_then(|sample| sample.server_to_client_ns)),
|
||||
format_optional_ns_i128(
|
||||
last.and_then(|sample| GraphMetric::ClientToServer.value_ns(&sample))
|
||||
),
|
||||
format_optional_ns_i128(
|
||||
last.and_then(|sample| GraphMetric::ServerToClient.value_ns(&sample))
|
||||
),
|
||||
)),
|
||||
])
|
||||
}
|
||||
@@ -1015,7 +968,11 @@ fn target_table(state: &TuiState, area: Rect) -> Table<'static> {
|
||||
} else {
|
||||
target.status.label().to_owned()
|
||||
},
|
||||
format_optional_ns_i128(target.last_sample.map(|sample| sample.effective_ns)),
|
||||
format_optional_ns_i128(
|
||||
target
|
||||
.last_sample
|
||||
.map(|sample| sample.rtt.effective.as_nanos()),
|
||||
),
|
||||
if snapshot.packets.packets_sent == 0 {
|
||||
ABSENT.to_owned()
|
||||
} else {
|
||||
@@ -1056,23 +1013,16 @@ fn target_table(state: &TuiState, area: Rect) -> Table<'static> {
|
||||
}
|
||||
|
||||
fn details_text(state: &TuiState) -> Text<'static> {
|
||||
let snapshot = state.selected_snapshot();
|
||||
let target = state.selected_target();
|
||||
let last = target.and_then(|target| target.last_sample);
|
||||
let snapshot = target.stats.snapshot();
|
||||
let last = target.last_sample;
|
||||
let packets = snapshot.packets;
|
||||
let mut lines = vec![
|
||||
Line::from(format!(
|
||||
"target: {}",
|
||||
target.map(|target| target.label.as_str()).unwrap_or(ABSENT)
|
||||
)),
|
||||
Line::from(format!("target: {}", target.label)),
|
||||
Line::from(format!(
|
||||
"remote: {} session: {}",
|
||||
target
|
||||
.and_then(|target| target.remote.as_deref())
|
||||
.unwrap_or(ABSENT),
|
||||
target
|
||||
.and_then(|target| target.session.as_deref())
|
||||
.unwrap_or(ABSENT)
|
||||
target.remote.as_deref().unwrap_or(ABSENT),
|
||||
target.session.as_deref().unwrap_or(ABSENT)
|
||||
)),
|
||||
Line::from(format!(
|
||||
"interval: {} timeout: {} pacing: {}",
|
||||
@@ -1083,15 +1033,14 @@ fn details_text(state: &TuiState) -> Text<'static> {
|
||||
Line::from(format!(
|
||||
"negotiated: {}",
|
||||
target
|
||||
.and_then(|target| target.negotiated.as_ref())
|
||||
.negotiated
|
||||
.as_ref()
|
||||
.map(format_negotiated)
|
||||
.unwrap_or_else(|| ABSENT.to_owned())
|
||||
)),
|
||||
Line::from(format!(
|
||||
"target warning: {}",
|
||||
target
|
||||
.and_then(|target| target.last_warning.as_deref())
|
||||
.unwrap_or(ABSENT)
|
||||
target.last_warning.as_deref().unwrap_or(ABSENT)
|
||||
)),
|
||||
Line::from(format!(
|
||||
"run warning: {}",
|
||||
@@ -1139,14 +1088,7 @@ fn details_text(state: &TuiState) -> Text<'static> {
|
||||
.unwrap_or_else(|| ABSENT.to_owned())
|
||||
)),
|
||||
];
|
||||
for metric in [
|
||||
GraphMetric::EffectiveRtt,
|
||||
GraphMetric::RawRtt,
|
||||
GraphMetric::AdjustedRtt,
|
||||
GraphMetric::ClientToServer,
|
||||
GraphMetric::ServerToClient,
|
||||
GraphMetric::ServerProcessing,
|
||||
] {
|
||||
for metric in GraphMetric::ALL {
|
||||
lines.push(Line::from(format!(
|
||||
"{}: {}",
|
||||
metric.label(),
|
||||
@@ -1554,7 +1496,3 @@ fn format_optional_hex(value: Option<u64>) -> String {
|
||||
.map(|value| format!("0x{value:x}"))
|
||||
.unwrap_or_else(|| ABSENT.to_owned())
|
||||
}
|
||||
|
||||
fn duration_ns(value: Duration) -> i128 {
|
||||
i128::try_from(value.as_nanos()).unwrap_or(i128::MAX)
|
||||
}
|
||||
Reference in new issue
Block a user