diff options
author | Brooks Rady <b.j.rady@gmail.com> | 2020-05-23 12:16:55 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-23 12:16:55 +0100 |
commit | e9b8a189b2323c337e11da317646cc601f963cf8 (patch) | |
tree | 4a43685c5de76dd679aef447e84ef62bcddf9369 | |
parent | 55e8885302172ccf0a79bc9829d339acfdc3564d (diff) | |
parent | b6f7f4295770e88a0967fcf55ddefbe10a57001d (diff) |
Merge pull request #167 from Eosis/issue-163-elapsed-time
feat(interface): Added elapsed time (#167)
14 files changed, 185 insertions, 72 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index fe0469a..b3a9513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added * Ability to change the window layout with <TAB> (https://github.com/imsnif/bandwhich/pull/118) - [@Louis-Lesage](https://github.com/Louis-Lesage) +* Show duration of current capture when running in "total utilization" mode. - [@Eosis](https://github.com/Eosis) ### Fixed * Add terabytes as a display unit (for cumulative mode) (https://github.com/imsnif/bandwhich/pull/168) - [@TheLostLambda](https://github.com/TheLostLambda) diff --git a/src/display/components/header_details.rs b/src/display/components/header_details.rs new file mode 100644 index 0000000..042d667 --- /dev/null +++ b/src/display/components/header_details.rs @@ -0,0 +1,117 @@ +use crate::display::{DisplayBandwidth, UIState}; +use ::std::time::{Duration, Instant}; +use ::tui::backend::Backend; +use ::tui::layout::{Alignment, Rect}; +use ::tui::style::{Color, Modifier, Style}; +use ::tui::terminal::Frame; +use ::tui::widgets::{Paragraph, Text, Widget}; + +const SECONDS_IN_DAY: u64 = 86400; + +pub struct HeaderDetails<'a> { + pub state: &'a UIState, + pub elapsed_time: std::time::Duration, + pub paused: bool, +} + +pub fn elapsed_time(last_start_time: Instant, cumulative_time: Duration, paused: bool) -> Duration { + if paused { + cumulative_time + } else { + cumulative_time + last_start_time.elapsed() + } +} + +impl<'a> HeaderDetails<'a> { + #[allow(clippy::int_plus_one)] + pub fn render(&self, frame: &mut Frame<impl Backend>, rect: Rect) { + let bandwidth = self.bandwidth_string(); + let mut elapsed_time = None; + let print_elapsed_time = if self.state.cumulative_mode { + elapsed_time = Some(self.elapsed_time_string()); + bandwidth.len() + elapsed_time.as_ref().unwrap().len() + 1 <= rect.width as usize + } else { + false + }; + + let color = if self.paused { + Color::Yellow + } else { + Color::Green + }; + + if print_elapsed_time { + self.render_elapsed_time(frame, rect, elapsed_time.as_ref().unwrap(), color); + } + self.render_bandwidth(frame, rect, &bandwidth, color); + } + + fn render_bandwidth( + &self, + frame: &mut Frame<impl Backend>, + rect: Rect, + bandwidth: &str, + color: Color, + ) { + let bandwidth_text = { + [Text::styled( + bandwidth, + Style::default().fg(color).modifier(Modifier::BOLD), + )] + }; + + Paragraph::new(bandwidth_text.iter()) + .alignment(Alignment::Left) + .render(frame, rect); + } + + fn bandwidth_string(&self) -> String { + let c_mode = self.state.cumulative_mode; + format!( + " Total Up / Down: {} / {}{}", + DisplayBandwidth { + bandwidth: self.state.total_bytes_uploaded as f64, + as_rate: !c_mode, + }, + DisplayBandwidth { + bandwidth: self.state.total_bytes_downloaded as f64, + as_rate: !c_mode, + }, + if self.paused { " [PAUSED]" } else { "" } + ) + } + + fn render_elapsed_time( + &self, + frame: &mut Frame<impl Backend>, + rect: Rect, + elapsed_time: &str, + color: Color, + ) { + let elapsed_time_text = [Text::styled( + elapsed_time, + Style::default().fg(color).modifier(Modifier::BOLD), + )]; + Paragraph::new(elapsed_time_text.iter()) + .alignment(Alignment::Right) + .render(frame, rect); + } + + fn days_string(&self) -> String { + match self.elapsed_time.as_secs() / SECONDS_IN_DAY { + 0 => "".to_string(), + 1 => "1 day, ".to_string(), + n => format!("{} days, ", n), + } + } + + fn elapsed_time_string(&self) -> String { + format!( + "{}{:02}:{:02}:{:02} ", + self.days_string(), + (self.elapsed_time.as_secs() % SECONDS_IN_DAY) / 3600, + (self.elapsed_time.as_secs() % 3600) / 60, + self.elapsed_time.as_secs() % 60 + ) + } +} diff --git a/src/display/components/layout.rs b/src/display/components/layout.rs index 79186cd..51c0175 100644 --- a/src/display/components/layout.rs +++ b/src/display/components/layout.rs @@ -2,9 +2,9 @@ use ::tui::backend::Backend; use ::tui::layout::{Constraint, Direction, Rect}; use ::tui::terminal::Frame; +use super::HeaderDetails; use super::HelpText; use super::Table; -use super::TotalBandwidth; const FIRST_HEIGHT_BREAKPOINT: u16 = 30; const FIRST_WIDTH_BREAKPOINT: u16 = 120; @@ -26,7 +26,7 @@ fn top_app_and_bottom_split(rect: Rect) -> (Rect, Rect, Rect) { } pub struct Layout<'a> { - pub header: TotalBandwidth<'a>, + pub header: HeaderDetails<'a>, pub children: Vec<Table<'a>>, pub footer: HelpText, } @@ -99,6 +99,7 @@ impl<'a> Layout<'a> { self.build_three_children_layout(rect) } } + pub fn render(&self, frame: &mut Frame<impl Backend>, rect: Rect, ui_offset: usize) { let (top, app, bottom) = top_app_and_bottom_split(rect); let layout_slots = self.build_layout(app); diff --git a/src/display/components/mod.rs b/src/display/components/mod.rs index c99596c..b5457f7 100644 --- a/src/display/components/mod.rs +++ b/src/display/components/mod.rs @@ -1,11 +1,11 @@ mod display_bandwidth; +mod header_details; mod help_text; mod layout; mod table; -mod total_bandwidth; pub use display_bandwidth::*; +pub use header_details::*; pub use help_text::*; pub use layout::*; pub use table::*; -pub use total_bandwidth::*; diff --git a/src/display/components/total_bandwidth.rs b/src/display/components/total_bandwidth.rs deleted file mode 100644 index 80d892f..0000000 --- a/src/display/components/total_bandwidth.rs +++ /dev/null @@ -1,45 +0,0 @@ -use ::tui::backend::Backend; -use ::tui::layout::{Alignment, Rect}; -use ::tui::style::{Color, Modifier, Style}; -use ::tui::terminal::Frame; -use ::tui::widgets::{Paragraph, Text, Widget}; - -use crate::display::{DisplayBandwidth, UIState}; - -pub struct TotalBandwidth<'a> { - pub state: &'a UIState, - pub paused: bool, -} - -impl<'a> TotalBandwidth<'a> { - pub fn render(&self, frame: &mut Frame<impl Backend>, rect: Rect) { - let c_mode = self.state.cumulative_mode; - let title_text = { - let paused_str = if self.paused { "[PAUSED]" } else { "" }; - let color = if self.paused { - Color::Yellow - } else { - Color::Green - }; - - [Text::styled( - format!( - " Total Up / Down: {} / {} {}", - DisplayBandwidth { - bandwidth: self.state.total_bytes_uploaded as f64, - as_rate: !c_mode, - }, - DisplayBandwidth { - bandwidth: self.state.total_bytes_downloaded as f64, - as_rate: !c_mode, - }, - paused_str - ), - Style::default().fg(color).modifier(Modifier::BOLD), - )] - }; - Paragraph::new(title_text.iter()) - .alignment(Alignment::Left) - .render(frame, rect); - } -} diff --git a/src/display/ui.rs b/src/display/ui.rs index 600a6db..6361a7f 100644 --- a/src/display/ui.rs +++ b/src/display/ui.rs @@ -3,7 +3,7 @@ use ::std::collections::HashMap; use ::tui::backend::Backend; use ::tui::Terminal; -use crate::display::components::{HelpText, Layout, Table, TotalBandwidth}; +use crate::display::components::{HeaderDetails, HelpText, Layout, Table}; use crate::display::UIState; use crate::network::{display_connection_string, display_ip_or_host, LocalSocket, Utilization}; @@ -11,6 +11,7 @@ use ::std::net::IpAddr; use crate::RenderOpts; use chrono::prelude::*; +use std::time::Duration; pub struct Ui<B> where @@ -79,14 +80,16 @@ where )); } } - pub fn draw(&mut self, paused: bool, show_dns: bool, ui_offset: usize) { + + pub fn draw(&mut self, paused: bool, show_dns: bool, elapsed_time: Duration, ui_offset: usize) { let state = &self.state; let children = self.get_tables_to_display(); self.terminal .draw(|mut frame| { let size = frame.size(); - let total_bandwidth = TotalBandwidth { + let total_bandwidth = HeaderDetails { state: &state, + elapsed_time, paused, }; let help_text = HelpText { paused, show_dns }; diff --git a/src/main.rs b/src/main.rs index e327be5..586b9d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ mod os; #[cfg(test)] mod tests; -use display::{RawTerminalBackend, Ui}; +use display::{elapsed_time, RawTerminalBackend, Ui}; use network::{ dns::{self, IpTable}, Connection, LocalSocket, Sniffer, Utilization, @@ -17,20 +17,21 @@ use ::pnet::datalink::{DataLinkReceiver, NetworkInterface}; use ::std::collections::HashMap; use ::std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use ::std::sync::{Arc, Mutex}; +use ::std::thread; use ::std::thread::park_timeout; -use ::std::{thread, time}; use ::termion::event::{Event, Key}; use ::tui::backend::Backend; use std::process; use ::std::io; -use ::std::time::Instant; +use ::std::time::{Duration, Instant}; use ::termion::raw::IntoRawMode; use ::tui::backend::TermionBackend; +use std::sync::RwLock; use structopt::StructOpt; -const DISPLAY_DELTA: time::Duration = time::Duration::from_millis(1000); +const DISPLAY_DELTA: Duration = Duration::from_millis(1000); #[derive(StructOpt, Debug)] #[structopt(name = "bandwhich")] @@ -121,6 +122,8 @@ where { let running = Arc::new(AtomicBool::new(true)); let paused = Arc::new(AtomicBool::new(false)); + let last_start_time = Arc::new(RwLock::new(Instant::now())); + let cumulative_time = Arc::new(RwLock::new(Duration::new(0, 0))); let ui_offset = Arc::new(AtomicUsize::new(0)); let dns_shown = opts.show_dns; @@ -145,15 +148,23 @@ where .spawn({ let ui = ui.clone(); let paused = paused.clone(); + let cumulative_time = cumulative_time.clone(); + let last_start_time = last_start_time.clone(); let ui_offset = ui_offset.clone(); move || { on_winch({ Box::new(move || { let mut ui = ui.lock().unwrap(); + let paused = paused.load(Ordering::SeqCst); ui.draw( - paused.load(Ordering::SeqCst), + paused, dns_shown, + elapsed_time( + *last_start_time.read().unwrap(), + *cumulative_time.read().unwrap(), + paused, + ), ui_offset.load(Ordering::SeqCst), ); }) @@ -172,6 +183,8 @@ where let ui_offset = ui_offset.clone(); let network_utilization = network_utilization.clone(); + let last_start_time = last_start_time.clone(); + let cumulative_time = cumulative_time.clone(); let ui = ui.clone(); move || { @@ -200,10 +213,16 @@ where if !paused { ui.update_state(sockets_to_procs, utilization, ip_to_host); } + let elapsed_time = elapsed_time( + *last_start_time.read().unwrap(), + *cumulative_time.read().unwrap(), + paused, + ); + if raw_mode { ui.output_text(&mut write_to_stdout); } else { - ui.draw(paused, dns_shown, ui_offset); + ui.draw(paused, dns_shown, elapsed_time, ui_offset); } } let render_duration = render_start_time.elapsed(); @@ -238,14 +257,31 @@ where break; } Event::Key(Key::Char(' ')) => { - paused.fetch_xor(true, Ordering::SeqCst); + let restarting = paused.fetch_xor(true, Ordering::SeqCst); + if restarting { + *last_start_time.write().unwrap() = Instant::now(); + } else { + let last_start_time_copy = *last_start_time.read().unwrap(); + let current_cumulative_time_copy = + *cumulative_time.read().unwrap(); + let new_cumulative_time = current_cumulative_time_copy + + last_start_time_copy.elapsed(); + *cumulative_time.write().unwrap() = new_cumulative_time; + } + display_handler.unpark(); } Event::Key(Key::Char('\t')) => { + let paused = paused.load(Ordering::SeqCst); + let elapsed_time = elapsed_time( + *last_start_time.read().unwrap(), + *cumulative_time.read().unwrap(), + paused, + ); let table_count = ui.get_table_count(); let new = ui_offset.load(Ordering::SeqCst) + 1 % table_count; ui_offset.store(new, Ordering::SeqCst); - ui.draw(paused.load(Ordering::SeqCst), dns_shown, new); + ui.draw(paused, dns_shown, elapsed_time, new); } _ => (), }; diff --git a/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap b/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap index 62a5202..27a8b30 100644 --- a/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap +++ b/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_bi_directional_total-2.snap @@ -1,8 +1,8 @@ --- source: src/tests/cases/ui.rs -expression: "&terminal_draw_events_mirror[2]" +expression: "&terminal_draw_events_mirror[2].replace(\"1 \\n\", \"2 \\n\")" --- - 98 109B + 98 109B 2 diff --git a/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap b/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap index abe1164..81929e3 100644 --- a/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap +++ b/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_bi_directional_total.snap @@ -2,7 +2,7 @@ source: src/tests/cases/ui.rs expression: "&terminal_draw_events_mirror[1]" --- - 45B / 49B + 45B / 49B 1 diff --git a/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_total-2.snap b/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_total-2.snap index 3fec816..cf6c136 100644 --- a/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_total-2.snap +++ b/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_total-2.snap @@ -1,8 +1,8 @@ --- source: src/tests/cases/ui.rs -expression: "&terminal_draw_events_mirror[2]" +expression: "&terminal_draw_events_mirror[2].replace(\"1 \\n\", \"2 \\n\")" --- - 106B + 106B 2 diff --git a/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_total.snap b/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_total.snap index 3012f6b..f09e329 100644 --- a/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_total.snap +++ b/src/tests/cases/snapshots/ui__sustained_traffic_from_multiple_processes_total.snap @@ -2,7 +2,7 @@ source: src/tests/cases/ui.rs expression: "&terminal_draw_events_mirror[1]" --- - 41B + 41B 1 diff --git a/src/tests/cases/snapshots/ui__sustained_traffic_from_one_process_total-2.snap b/src/tests/cases/snapshots/ui__sustained_traffic_from_one_process_total-2.snap index b6456b7..8dfceee 100644 --- a/src/tests/cases/snapshots/ui__sustained_traffic_from_one_process_total-2.snap +++ b/src/tests/cases/snapshots/ui__sustained_traffic_from_one_process_total-2.snap @@ -1,8 +1,8 @@ --- source: src/tests/cases/ui.rs -expression: "&terminal_draw_events_mirror[2]" +expression: "&terminal_draw_events_mirror[2].replace(\"1 \\n\", \"2 \\n\")" --- - 53 + 53 2 diff --git a/src/tests/cases/snapshots/ui__sustained_traffic_from_one_process_total.snap b/src/tests/cases/snapshots/ui__sustained_traffic_from_one_process_total.snap index 8c590f5..3f5c319 100644 --- a/src/tests/cases/snapshots/ui__sustained_traffic_from_one_process_total.snap +++ b/src/tests/cases/snapshots/ui__sustained_traffic_from_one_process_total.snap @@ -2,7 +2,7 @@ source: src/tests/cases/ui.rs expression: "&terminal_draw_events_mirror[1]" --- - 22B + 22B 1 diff --git a/src/tests/cases/ui.rs b/src/tests/cases/ui.rs index e755b5a..d3df052 100644 --- a/src/tests/cases/ui.rs +++ b/src/tests/cases/ui.rs @@ -707,7 +707,7 @@ fn sustained_traffic_from_one_process_total() { assert_eq!(terminal_draw_events_mirror.len(), 3); assert_snapshot!(&terminal_draw_events_mirror[1]); - assert_snapshot!(&terminal_draw_events_mirror[2]); + assert_snapshot!(&terminal_draw_events_mirror[2].replace("1 \n", "2 \n")); } #[test] @@ -816,7 +816,7 @@ fn sustained_traffic_from_multiple_processes_total() { assert_eq!(terminal_draw_events_mirror.len(), 3); assert_snapshot!(&terminal_draw_events_mirror[1]); - assert_snapshot!(&terminal_draw_events_mirror[2]); + assert_snapshot!(&terminal_draw_events_mirror[2].replace("1 \n", "2 \n")); } #[test] @@ -981,7 +981,7 @@ fn sustained_traffic_from_multiple_processes_bi_directional_total() { assert_eq!(terminal_draw_events_mirror.len(), 3); assert_snapshot!(&terminal_draw_events_mirror[1]); - assert_snapshot!(&terminal_draw_events_mirror[2]); + assert_snapshot!(&terminal_draw_events_mirror[2].replace("1 \n", "2 \n")); } #[test] |