Skip to content

Commit

Permalink
feat(tui): display individual tracing flows in Tui (#777) - WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
fujiapple852 committed Nov 25, 2023
1 parent 9223c8b commit 8424854
Show file tree
Hide file tree
Showing 16 changed files with 230 additions and 33 deletions.
26 changes: 24 additions & 2 deletions src/backend/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ use trippy::tracing::{Extensions, Probe, ProbeStatus, TracerRound};
#[derive(Debug, Clone)]
pub struct Trace {
max_samples: usize,
/// The flow id for the current round.
round_flow_id: FlowId,
/// Tracing data per registered flow id.
trace_data: HashMap<FlowId, TraceData>,
/// Flow registry.
registry: FlowRegistry,
/// Tracing error message.
error: Option<String>,
}

Expand All @@ -22,6 +27,7 @@ impl Trace {
Self {
trace_data: once((Self::default_flow_id(), TraceData::new(max_samples)))
.collect::<HashMap<FlowId, TraceData>>(),
round_flow_id: Self::default_flow_id(),
max_samples,
registry: FlowRegistry::new(),
error: None,
Expand Down Expand Up @@ -68,6 +74,11 @@ impl Trace {
self.trace_data[&flow_id].round_count()
}

/// The `FlowId` for the current round.
pub fn round_flow_id(&self) -> FlowId {
self.round_flow_id
}

/// The registry of flows in the trace.
pub fn flows(&self) -> &[(Flow, FlowId)] {
self.registry.flows()
Expand All @@ -94,6 +105,7 @@ impl Trace {
.map(|p| p.host),
);
let flow_id = self.registry.register(flow);
self.round_flow_id = flow_id;
self.update_trace_flow(Self::default_flow_id(), round);
self.update_trace_flow(flow_id, round);
}
Expand All @@ -110,18 +122,28 @@ impl Trace {
/// Information about a single `Hop` within a `Trace`.
#[derive(Debug, Clone)]
pub struct Hop {
/// The ttl of this hop.
ttl: u8,
/// The addrs of this hop and associated counts.
addrs: IndexMap<IpAddr, usize>,
/// The total probes sent for this hop.
total_sent: usize,
/// The total probes received for this hop.
total_recv: usize,
/// The total round trip time for this hop across all rounds.
total_time: Duration,
/// The round trip time for this hop in the current round.
last: Option<Duration>,
/// The best round trip time for this hop across all rounds.
best: Option<Duration>,
/// The worst round trip time for this hop across all rounds.
worst: Option<Duration>,
mean: f64,
m2: f64,
/// The history of round trip times across the last N rounds.
samples: Vec<Duration>,
/// The ICMP extensions for this hop.
extensions: Option<Extensions>,
mean: f64,
m2: f64,
}

impl Hop {
Expand Down
9 changes: 9 additions & 0 deletions src/config/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct TuiBindings {
pub toggle_freeze: TuiKeyBinding,
pub toggle_chart: TuiKeyBinding,
pub toggle_map: TuiKeyBinding,
pub toggle_flows: TuiKeyBinding,
pub expand_hosts: TuiKeyBinding,
pub contract_hosts: TuiKeyBinding,
pub expand_hosts_max: TuiKeyBinding,
Expand Down Expand Up @@ -60,6 +61,7 @@ impl Default for TuiBindings {
),
toggle_chart: TuiKeyBinding::new(KeyCode::Char('c')),
toggle_map: TuiKeyBinding::new(KeyCode::Char('m')),
toggle_flows: TuiKeyBinding::new(KeyCode::Char('f')),
expand_hosts: TuiKeyBinding::new(KeyCode::Char(']')),
contract_hosts: TuiKeyBinding::new(KeyCode::Char('[')),
expand_hosts_max: TuiKeyBinding::new(KeyCode::Char('}')),
Expand Down Expand Up @@ -106,6 +108,7 @@ impl TuiBindings {
(self.toggle_freeze, TuiCommandItem::ToggleFreeze),
(self.toggle_chart, TuiCommandItem::ToggleChart),
(self.toggle_map, TuiCommandItem::ToggleMap),
(self.toggle_flows, TuiCommandItem::ToggleFlows),
(self.expand_hosts, TuiCommandItem::ExpandHosts),
(self.expand_hosts_max, TuiCommandItem::ExpandHostsMax),
(self.contract_hosts, TuiCommandItem::ContractHosts),
Expand Down Expand Up @@ -201,6 +204,10 @@ impl From<(HashMap<TuiCommandItem, TuiKeyBinding>, ConfigBindings)> for TuiBindi
.get(&TuiCommandItem::ToggleChart)
.or(cfg.toggle_chart.as_ref())
.unwrap_or(&Self::default().toggle_chart),
toggle_flows: *cmd_items
.get(&TuiCommandItem::ToggleFlows)
.or(cfg.toggle_flows.as_ref())
.unwrap_or(&TuiKeyBinding::new(KeyCode::Char('f'))),
toggle_map: *cmd_items
.get(&TuiCommandItem::ToggleMap)
.or(cfg.toggle_map.as_ref())
Expand Down Expand Up @@ -506,6 +513,8 @@ pub enum TuiCommandItem {
ToggleChart,
/// Toggle the map.
ToggleMap,
/// Toggle the flows panel.
ToggleFlows,
/// Expand hosts.
ExpandHosts,
/// Expand hosts to max.
Expand Down
2 changes: 2 additions & 0 deletions src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ pub struct ConfigBindings {
pub address_mode_both: Option<TuiKeyBinding>,
pub toggle_freeze: Option<TuiKeyBinding>,
pub toggle_chart: Option<TuiKeyBinding>,
pub toggle_flows: Option<TuiKeyBinding>,
pub toggle_map: Option<TuiKeyBinding>,
pub expand_hosts: Option<TuiKeyBinding>,
pub contract_hosts: Option<TuiKeyBinding>,
Expand Down Expand Up @@ -356,6 +357,7 @@ impl Default for ConfigBindings {
address_mode_both: Some(bindings.address_mode_both),
toggle_freeze: Some(bindings.toggle_freeze),
toggle_chart: Some(bindings.toggle_chart),
toggle_flows: Some(bindings.toggle_flows),
toggle_map: Some(bindings.toggle_map),
expand_hosts: Some(bindings.expand_hosts),
contract_hosts: Some(bindings.contract_hosts),
Expand Down
20 changes: 16 additions & 4 deletions src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub fn run_frontend(
Ok(())
}

#[allow(clippy::too_many_lines)]
fn run_app<B: Backend>(
terminal: &mut Terminal<B>,
trace_info: Vec<TraceInfo>,
Expand All @@ -66,6 +67,7 @@ fn run_app<B: Backend>(
if app.frozen_start.is_none() {
app.snapshot_trace_data();
app.clamp_selected_hop();
app.update_order_flow_counts();
};
terminal.draw(|f| render::app::render(f, &mut app))?;
if event::poll(app.tui_config.refresh_rate)? {
Expand Down Expand Up @@ -105,11 +107,19 @@ fn run_app<B: Backend>(
} else if bindings.previous_hop.check(key) {
app.previous_hop();
} else if bindings.previous_trace.check(key) {
app.previous_trace();
app.clear();
if app.show_flows {
app.previous_flow();
} else {
app.previous_trace();
app.clear();
}
} else if bindings.next_trace.check(key) {
app.next_trace();
app.clear();
if app.show_flows {
app.next_flow();
} else {
app.next_trace();
app.clear();
}
} else if bindings.next_hop_address.check(key) {
app.next_hop_address();
} else if bindings.previous_hop_address.check(key) {
Expand All @@ -126,6 +136,8 @@ fn run_app<B: Backend>(
app.toggle_chart();
} else if bindings.toggle_map.check(key) {
app.toggle_map();
} else if bindings.toggle_flows.check(key) {
app.toggle_flows();
} else if bindings.contract_hosts_min.check(key) {
app.contract_hosts_min();
} else if bindings.expand_hosts_max.check(key) {
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct Bindings {
pub toggle_freeze: KeyBinding,
pub toggle_chart: KeyBinding,
pub toggle_map: KeyBinding,
pub toggle_flows: KeyBinding,
pub expand_hosts: KeyBinding,
pub contract_hosts: KeyBinding,
pub expand_hosts_max: KeyBinding,
Expand Down Expand Up @@ -53,6 +54,7 @@ impl From<TuiBindings> for Bindings {
toggle_freeze: KeyBinding::from(value.toggle_freeze),
toggle_chart: KeyBinding::from(value.toggle_chart),
toggle_map: KeyBinding::from(value.toggle_map),
toggle_flows: KeyBinding::from(value.toggle_flows),
expand_hosts: KeyBinding::from(value.expand_hosts),
contract_hosts: KeyBinding::from(value.contract_hosts),
expand_hosts_max: KeyBinding::from(value.expand_hosts_max),
Expand Down
1 change: 1 addition & 0 deletions src/frontend/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod app;
pub mod body;
pub mod bsod;
pub mod chart;
pub mod flows;
pub mod footer;
pub mod header;
pub mod help;
Expand Down
18 changes: 16 additions & 2 deletions src/frontend/render/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::frontend::render::{body, footer, header, help, settings, tabs};
use crate::frontend::render::{body, flows, footer, header, help, settings, tabs};
use crate::frontend::tui_app::TuiApp;
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::Frame;
Expand Down Expand Up @@ -33,7 +33,10 @@ use ratatui::Frame;
/// On startup a splash screen is shown in place of the hops table, until the completion of the
/// first round.
pub fn render(f: &mut Frame<'_>, app: &mut TuiApp) {
let constraints = if app.trace_info.len() > 1 {
// TODO ensure we don't allow flows and tabs at the same time or mix up layouts
let constraints = if app.show_flows {
LAYOUT_WITH_FLOWS.as_slice()
} else if app.trace_info.len() > 1 {
LAYOUT_WITH_TABS.as_slice()
} else {
LAYOUT_WITHOUT_TABS.as_slice()
Expand All @@ -47,6 +50,10 @@ pub fn render(f: &mut Frame<'_>, app: &mut TuiApp) {
tabs::render(f, chunks[1], app);
body::render(f, chunks[2], app);
footer::render(f, chunks[3], app);
} else if app.show_flows {
flows::render(f, chunks[1], app);
body::render(f, chunks[2], app);
footer::render(f, chunks[3], app);
} else {
body::render(f, chunks[1], app);
footer::render(f, chunks[2], app);
Expand All @@ -70,3 +77,10 @@ const LAYOUT_WITH_TABS: [Constraint; 4] = [
Constraint::Min(10),
Constraint::Length(6),
];

const LAYOUT_WITH_FLOWS: [Constraint; 4] = [
Constraint::Length(5),
Constraint::Length(6),
Constraint::Min(10),
Constraint::Length(6),
];
3 changes: 1 addition & 2 deletions src/frontend/render/chart.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::backend::trace::Trace;
use crate::frontend::tui_app::TuiApp;
use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::style::Style;
Expand All @@ -13,7 +12,7 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
let samples = app.tui_config.max_samples / app.zoom_factor;
let series_data = app
.selected_tracer_data
.hops(Trace::default_flow_id())
.hops(app.selected_flow)
.iter()
.map(|hop| {
hop.samples()
Expand Down
58 changes: 58 additions & 0 deletions src/frontend/render/flows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::frontend::tui_app::TuiApp;
use ratatui::layout::{Alignment, Rect};
use ratatui::prelude::Color;
use ratatui::style::{Modifier, Style};
use ratatui::text::Line;
use ratatui::widgets::{Bar, BarChart, BarGroup, Block, BorderType, Borders};
use ratatui::Frame;

/// Render the flows.
pub fn render(f: &mut Frame<'_>, rect: Rect, app: &TuiApp) {
let round_flow_id = app.tracer_data().round_flow_id();
let data: Vec<_> = app
.flow_counts
.iter()
.map(|(flow_id, count)| {
let (bar_fg, bg, fg) = if flow_id == &app.selected_flow && flow_id == &round_flow_id {
(
Color::Green,
app.tui_config.theme.frequency_chart_bar_color,
Color::Green,
)
} else if flow_id == &app.selected_flow {
(
Color::Green,
app.tui_config.theme.frequency_chart_bar_color,
app.tui_config.theme.frequency_chart_text_color,
)
} else if flow_id == &round_flow_id {
(Color::DarkGray, Color::DarkGray, Color::Green)
} else {
(Color::DarkGray, Color::DarkGray, Color::White)
};
Bar::default()
.label(Line::from(format!("{flow_id}")))
.value(*count as u64)
.style(Style::default().fg(bar_fg))
.value_style(Style::default().bg(bg).fg(fg).add_modifier(Modifier::BOLD))
})
.collect();
let block = Block::default()
.title("Flows")
.title_alignment(Alignment::Left)
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(app.tui_config.theme.border_color))
.style(
Style::default()
.bg(app.tui_config.theme.bg_color)
.fg(app.tui_config.theme.text_color),
);
let group = BarGroup::default().bars(&data);
let flow_counts = BarChart::default()
.block(block)
.data(group)
.bar_width(4)
.bar_gap(1);
f.render_widget(flow_counts, rect);
}
3 changes: 1 addition & 2 deletions src/frontend/render/header.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::backend::trace::Trace;
use crate::frontend::tui_app::TuiApp;
use chrono::SecondsFormat;
use humantime::format_duration;
Expand Down Expand Up @@ -95,7 +94,7 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
Span::raw(render_status(app)),
Span::raw(format!(
", discovered {} hops",
app.tracer_data().hops(Trace::default_flow_id()).len()
app.tracer_data().hops(app.selected_flow).len()
)),
]),
];
Expand Down
7 changes: 4 additions & 3 deletions src/frontend/render/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp) {
f.render_widget(control, area);
}

const HELP_LINES: [&str; 20] = [
const HELP_LINES: [&str; 21] = [
"[up] & [down] - select hop",
"[left] & [right] - select trace",
"[left] & [right] - select trace or flow",
", & . - select hop address",
"[esc] - clear selection",
"d - toggle hop details",
"f - toggle flows",
"c - toggle chart",
"m - toggle map",
"ctrl+f - toggle freeze display",
"Ctrl-f - toggle freeze display",
"Ctrl+r - reset statistics",
"Ctrl+k - flush DNS cache",
"i - show IP only",
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/render/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ fn format_binding_settings(app: &TuiApp) -> Vec<SettingsItem> {
SettingsItem::new("toggle-freeze", format!("{}", binds.toggle_freeze)),
SettingsItem::new("toggle-chart", format!("{}", binds.toggle_chart)),
SettingsItem::new("toggle-map", format!("{}", binds.toggle_map)),
SettingsItem::new("toggle-flows", format!("{}", binds.toggle_flows)),
SettingsItem::new("expand-hosts", format!("{}", binds.expand_hosts)),
SettingsItem::new("expand-hosts-max", format!("{}", binds.expand_hosts_max)),
SettingsItem::new("contract-hosts", format!("{}", binds.contract_hosts)),
Expand Down Expand Up @@ -403,7 +404,7 @@ pub const SETTINGS_TABS: [(&str, usize); 6] = [
("Trace", 14),
("Dns", 4),
("GeoIp", 1),
("Bindings", 26),
("Bindings", 27),
("Theme", 27),
];

Expand Down
Loading

0 comments on commit 8424854

Please sign in to comment.