Skip to content

Commit

Permalink
feat(tui): display individual tracing flows in Tui (#777)
Browse files Browse the repository at this point in the history
  • Loading branch information
fujiapple852 committed Nov 25, 2023
1 parent 56125f3 commit fc0358b
Show file tree
Hide file tree
Showing 17 changed files with 286 additions and 31 deletions.
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
10 changes: 10 additions & 0 deletions src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ pub struct ConfigThemeColors {
pub hops_chart_axis_color: Option<TuiColor>,
pub frequency_chart_bar_color: Option<TuiColor>,
pub frequency_chart_text_color: Option<TuiColor>,
pub flows_chart_bar_selected_color: Option<TuiColor>,
pub flows_chart_bar_unselected_color: Option<TuiColor>,
pub flows_chart_text_current_color: Option<TuiColor>,
pub flows_chart_text_non_current_color: Option<TuiColor>,
pub samples_chart_color: Option<TuiColor>,
pub help_dialog_bg_color: Option<TuiColor>,
pub help_dialog_text_color: Option<TuiColor>,
Expand Down Expand Up @@ -288,6 +292,10 @@ impl Default for ConfigThemeColors {
hops_chart_axis_color: Some(theme.hops_chart_axis_color),
frequency_chart_bar_color: Some(theme.frequency_chart_bar_color),
frequency_chart_text_color: Some(theme.frequency_chart_text_color),
flows_chart_bar_selected_color: Some(theme.flows_chart_bar_selected_color),
flows_chart_bar_unselected_color: Some(theme.flows_chart_bar_unselected_color),
flows_chart_text_current_color: Some(theme.flows_chart_text_current_color),
flows_chart_text_non_current_color: Some(theme.flows_chart_text_non_current_color),
samples_chart_color: Some(theme.samples_chart_color),
help_dialog_bg_color: Some(theme.help_dialog_bg_color),
help_dialog_text_color: Some(theme.help_dialog_text_color),
Expand Down Expand Up @@ -323,6 +331,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 +365,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
36 changes: 36 additions & 0 deletions src/config/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ pub struct TuiTheme {
pub frequency_chart_bar_color: TuiColor,
/// The color of text in the bars of the frequency chart.
pub frequency_chart_text_color: TuiColor,
/// The color of the selected flow bar in the flows chart.
pub flows_chart_bar_selected_color: TuiColor,
/// The color of the unselected flow bar in the flows chart.
pub flows_chart_bar_unselected_color: TuiColor,
/// The color of the current flow text in the flows chart.
pub flows_chart_text_current_color: TuiColor,
/// The color of the non-current flow text in the flows chart.
pub flows_chart_text_non_current_color: TuiColor,
/// The color of the samples chart.
pub samples_chart_color: TuiColor,
/// The background color of the help dialog.
Expand Down Expand Up @@ -85,6 +93,10 @@ impl Default for TuiTheme {
hops_chart_axis_color: TuiColor::DarkGray,
frequency_chart_bar_color: TuiColor::Green,
frequency_chart_text_color: TuiColor::Gray,
flows_chart_bar_selected_color: TuiColor::Green,
flows_chart_bar_unselected_color: TuiColor::DarkGray,
flows_chart_text_current_color: TuiColor::LightGreen,
flows_chart_text_non_current_color: TuiColor::White,
samples_chart_color: TuiColor::Yellow,
help_dialog_bg_color: TuiColor::Blue,
help_dialog_text_color: TuiColor::Gray,
Expand Down Expand Up @@ -160,6 +172,22 @@ impl From<(HashMap<TuiThemeItem, TuiColor>, ConfigThemeColors)> for TuiTheme {
.get(&TuiThemeItem::FrequencyChartTextColor)
.or(cfg.frequency_chart_text_color.as_ref())
.unwrap_or(&Self::default().frequency_chart_text_color),
flows_chart_bar_selected_color: *color_map
.get(&TuiThemeItem::FlowsChartBarSelectedColor)
.or(cfg.flows_chart_bar_selected_color.as_ref())
.unwrap_or(&Self::default().flows_chart_bar_selected_color),
flows_chart_bar_unselected_color: *color_map
.get(&TuiThemeItem::FlowsChartBarUnselectedColor)
.or(cfg.flows_chart_bar_unselected_color.as_ref())
.unwrap_or(&Self::default().flows_chart_bar_unselected_color),
flows_chart_text_current_color: *color_map
.get(&TuiThemeItem::FlowsChartTextCurrentColor)
.or(cfg.flows_chart_text_current_color.as_ref())
.unwrap_or(&Self::default().flows_chart_text_current_color),
flows_chart_text_non_current_color: *color_map
.get(&TuiThemeItem::FlowsChartTextNonCurrentColor)
.or(cfg.flows_chart_text_non_current_color.as_ref())
.unwrap_or(&Self::default().flows_chart_text_non_current_color),
samples_chart_color: *color_map
.get(&TuiThemeItem::SamplesChartColor)
.or(cfg.samples_chart_color.as_ref())
Expand Down Expand Up @@ -251,6 +279,14 @@ pub enum TuiThemeItem {
FrequencyChartBarColor,
/// The color of text in the bars of the frequency chart.
FrequencyChartTextColor,
/// The color of the selected flow bar in the flows chart.
FlowsChartBarSelectedColor,
/// The color of the unselected flow bar in the flows chart.
FlowsChartBarUnselectedColor,
/// The color of the current flow text in the flows chart.
FlowsChartTextCurrentColor,
/// The color of the non-current flow text in the flows chart.
FlowsChartTextNonCurrentColor,
/// The color of the samples chart.
SamplesChartColor,
/// The background color of the help dialog.
Expand Down
16 changes: 14 additions & 2 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,9 +107,17 @@ 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();
if app.show_flows {
app.previous_flow();
} else {
app.previous_trace();
}
} else if bindings.next_trace.check(key) {
app.next_trace();
if app.show_flows {
app.next_flow();
} else {
app.next_trace();
}
} else if bindings.next_hop_address.check(key) {
app.next_hop_address();
} else if bindings.previous_hop_address.check(key) {
Expand All @@ -124,6 +134,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
22 changes: 19 additions & 3 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 All @@ -10,7 +10,9 @@ use ratatui::Frame;
/// ____________________________________
/// | Header |
/// ------------------------------------
/// | Tabs |
/// | Tabs |
/// ------------------------------------
/// | Flows |
/// ------------------------------------
/// | |
/// | |
Expand All @@ -25,7 +27,8 @@ use ratatui::Frame;
/// ------------------------------------
///
/// Header - the title, configuration, destination, clock and keyboard controls
/// Tab - a tab for each target being traced (only shown if > 1 target requested)
/// Tab - a tab for each target being traced (shown if > 1 target requested, can't be used with flows)
/// Flows - a navigable chart of individual trace flows (toggled on/off, can't be used with tabs)
/// Hops - a table where each row represents a single hop (time-to-live) in the trace
/// History - a graph of historic round-trip ping samples for the target host
/// Frequency - a histogram of sample frequencies by round-trip time for the target host
Expand All @@ -35,6 +38,8 @@ use ratatui::Frame;
pub fn render(f: &mut Frame<'_>, app: &mut TuiApp) {
let constraints = if app.trace_info.len() > 1 {
LAYOUT_WITH_TABS.as_slice()
} else if app.show_flows {
LAYOUT_WITH_FLOWS.as_slice()
} else {
LAYOUT_WITHOUT_TABS.as_slice()
};
Expand All @@ -47,6 +52,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 +79,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
55 changes: 55 additions & 0 deletions src/frontend/render/flows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::frontend::tui_app::TuiApp;
use ratatui::layout::{Alignment, Rect};
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_color = if flow_id == &app.selected_flow {
app.tui_config.theme.flows_chart_bar_selected_color
} else {
app.tui_config.theme.flows_chart_bar_unselected_color
};
let label_color = if flow_id == &round_flow_id {
app.tui_config.theme.flows_chart_text_current_color
} else {
app.tui_config.theme.flows_chart_text_non_current_color
};
Bar::default()
.label(Line::from(format!("{flow_id}")))
.value(*count as u64)
.style(Style::default().fg(bar_color))
.value_style(
Style::default()
.bg(bar_color)
.fg(label_color)
.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
Loading

0 comments on commit fc0358b

Please sign in to comment.