diff options
author | Brooks Rady <b.j.rady@gmail.com> | 2020-04-05 17:30:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-05 18:30:43 +0200 |
commit | 62a39464d7893cbb747f321c3bb662a0d939de14 (patch) | |
tree | aeae4458ff213d4199805c10b880fe70994d50f4 /src | |
parent | 1569f6038df215a85532fe2e77a8ed47ca797c0b (diff) |
feat(network): add accumlated usage (#155)
* Add accumlated usage
* Fixed a nasty performace leak
* Fix a silly reverse-ordering bug
* Address some feedback
* Finished some reorganizing
* Another silly reversal bug
* Let the test cases run
* Most tests are working
* Fix some formatting that got left behind
* Fix the rest of the tests
* Add some new tests to cover the total mode
* Whoops, a double newline
Diffstat (limited to 'src')
66 files changed, 757 insertions, 181 deletions
diff --git a/src/display/components/display_bandwidth.rs b/src/display/components/display_bandwidth.rs index 31653e6..dcc204c 100644 --- a/src/display/components/display_bandwidth.rs +++ b/src/display/components/display_bandwidth.rs @@ -1,17 +1,21 @@ use ::std::fmt; -pub struct DisplayBandwidth(pub f64); +pub struct DisplayBandwidth { + pub bandwidth: f64, + pub as_rate: bool, +} impl fmt::Display for DisplayBandwidth { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.0 > 999_999_999.0 { - write!(f, "{:.2}GBps", self.0 / 1_000_000_000.0) - } else if self.0 > 999_999.0 { - write!(f, "{:.2}MBps", self.0 / 1_000_000.0) - } else if self.0 > 999.0 { - write!(f, "{:.2}KBps", self.0 / 1000.0) + let suffix = if self.as_rate { "ps" } else { "" }; + if self.bandwidth > 999_999_999.0 { + write!(f, "{:.2}GB{}", self.bandwidth / 1_000_000_000.0, suffix) + } else if self.bandwidth > 999_999.0 { + write!(f, "{:.2}MB{}", self.bandwidth / 1_000_000.0, suffix) + } else if self.bandwidth > 999.0 { + write!(f, "{:.2}KB{}", self.bandwidth / 1000.0, suffix) } else { - write!(f, "{}Bps", self.0) + write!(f, "{}B{}", self.bandwidth, suffix) } } } diff --git a/src/display/components/table.rs b/src/display/components/table.rs index 5786106..059b5ed 100644 --- a/src/display/components/table.rs +++ b/src/display/components/table.rs @@ -10,35 +10,21 @@ use crate::display::{Bandwidth, DisplayBandwidth, UIState}; use crate::network::{display_connection_string, display_ip_or_host}; use ::std::net::IpAddr; -use std::iter::FromIterator; -fn display_upload_and_download(bandwidth: &impl Bandwidth) -> String { +fn display_upload_and_download(bandwidth: &impl Bandwidth, total: bool) -> String { format!( "{} / {}", - DisplayBandwidth(bandwidth.get_total_bytes_uploaded() as f64), - DisplayBandwidth(bandwidth.get_total_bytes_downloaded() as f64) + DisplayBandwidth { + bandwidth: bandwidth.get_total_bytes_uploaded() as f64, + as_rate: !total, + }, + DisplayBandwidth { + bandwidth: bandwidth.get_total_bytes_downloaded() as f64, + as_rate: !total, + }, ) } -fn sort_by_bandwidth<'a, T>( - list: &'a mut Vec<(T, &impl Bandwidth)>, -) -> &'a Vec<(T, &'a impl Bandwidth)> { - list.sort_by(|(_, a), (_, b)| { - let a_highest = if a.get_total_bytes_downloaded() > a.get_total_bytes_uploaded() { - a.get_total_bytes_downloaded() - } else { - a.get_total_bytes_uploaded() - }; - let b_highest = if b.get_total_bytes_downloaded() > b.get_total_bytes_uploaded() { - b.get_total_bytes_downloaded() - } else { - b.get_total_bytes_uploaded() - }; - b_highest.cmp(&a_highest) - }); - list -} - pub enum ColumnCount { Two, Three, @@ -77,9 +63,8 @@ fn truncate_middle(row: &str, max_length: u16) -> String { impl<'a> Table<'a> { pub fn create_connections_table(state: &UIState, ip_to_host: &HashMap<IpAddr, String>) -> Self { - let mut connections_list = Vec::from_iter(&state.connections); - sort_by_bandwidth(&mut connections_list); - let connections_rows = connections_list + let connections_rows = state + .connections .iter() .map(|(connection, connection_data)| { vec![ @@ -89,12 +74,12 @@ impl<'a> Table<'a> { &connection_data.interface_name, ), connection_data.process_name.to_string(), - display_upload_and_download(*connection_data), + display_upload_and_download(connection_data, state.cumulative_mode), ] }) .collect(); let connections_title = "Utilization by connection"; - let connections_column_names = &["Connection", "Process", "Rate Up / Down"]; + let connections_column_names = &["Connection", "Process", "Up / Down"]; let mut breakpoints = BTreeMap::new(); breakpoints.insert( 0, @@ -132,20 +117,19 @@ impl<'a> Table<'a> { } } pub fn create_processes_table(state: &UIState) -> Self { - let mut processes_list = Vec::from_iter(&state.processes); - sort_by_bandwidth(&mut processes_list); - let processes_rows = processes_list + let processes_rows = state + .processes .iter() .map(|(process_name, data_for_process)| { vec![ (*process_name).to_string(), data_for_process.connection_count.to_string(), - display_upload_and_download(*data_for_process), + display_upload_and_download(data_for_process, state.cumulative_mode), ] }) .collect(); let processes_title = "Utilization by process name"; - let processes_column_names = &["Process", "Connections", "Rate Up / Down"]; + let processes_column_names = &["Process", "Connections", "Up / Down"]; let mut breakpoints = BTreeMap::new(); breakpoints.insert( 0, @@ -186,21 +170,20 @@ impl<'a> Table<'a> { state: &UIState, ip_to_host: &HashMap<IpAddr, String>, ) -> Self { - let mut remote_addresses_list = Vec::from_iter(&state.remote_addresses); - sort_by_bandwidth(&mut remote_addresses_list); - let remote_addresses_rows = remote_addresses_list + let remote_addresses_rows = state + .remote_addresses .iter() .map(|(remote_address, data_for_remote_address)| { - let remote_address = display_ip_or_host(**remote_address, &ip_to_host); + let remote_address = display_ip_or_host(*remote_address, &ip_to_host); vec![ remote_address, data_for_remote_address.connection_count.to_string(), - display_upload_and_download(*data_for_remote_address), + display_upload_and_download(data_for_remote_address, state.cumulative_mode), ] }) .collect(); let remote_addresses_title = "Utilization by remote address"; - let remote_addresses_column_names = &["Remote Address", "Connections", "Rate Up / Down"]; + let remote_addresses_column_names = &["Remote Address", "Connections", "Up / Down"]; let mut breakpoints = BTreeMap::new(); breakpoints.insert( 0, diff --git a/src/display/components/total_bandwidth.rs b/src/display/components/total_bandwidth.rs index 4a7ae9d..80d892f 100644 --- a/src/display/components/total_bandwidth.rs +++ b/src/display/components/total_bandwidth.rs @@ -13,6 +13,7 @@ pub struct TotalBandwidth<'a> { 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 { @@ -23,9 +24,15 @@ impl<'a> TotalBandwidth<'a> { [Text::styled( format!( - " Total Rate Up / Down: {} / {} {}", - DisplayBandwidth(self.state.total_bytes_uploaded as f64), - DisplayBandwidth(self.state.total_bytes_downloaded as f64), + " 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), diff --git a/src/display/ui.rs b/src/display/ui.rs index 4530664..68f133a 100644 --- a/src/display/ui.rs +++ b/src/display/ui.rs @@ -30,9 +30,11 @@ where let mut terminal = Terminal::new(terminal_backend).unwrap(); terminal.clear().unwrap(); terminal.hide_cursor().unwrap(); + let mut state: UIState = Default::default(); + state.cumulative_mode = opts.total_utilization; Ui { terminal, - state: Default::default(), + state, ip_to_host: Default::default(), opts, } diff --git a/src/display/ui_state.rs b/src/display/ui_state.rs index 4edd142..a65e610 100644 --- a/src/display/ui_state.rs +++ b/src/display/ui_state.rs @@ -1,23 +1,29 @@ -use ::std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use ::std::cmp; +use ::std::collections::{HashMap, HashSet, VecDeque}; +use ::std::hash::Hash; +use ::std::iter::FromIterator; use ::std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::network::{Connection, LocalSocket, Utilization}; static RECALL_LENGTH: usize = 5; +static MAX_BANDWIDTH_ITEMS: usize = 1000; pub trait Bandwidth { fn get_total_bytes_downloaded(&self) -> u128; fn get_total_bytes_uploaded(&self) -> u128; + fn combine_bandwidth(&mut self, other: &Self); + fn divide_by(&mut self, amount: u128); } -#[derive(Default)] +#[derive(Clone, Default)] pub struct NetworkData { pub total_bytes_downloaded: u128, pub total_bytes_uploaded: u128, pub connection_count: u128, } -#[derive(Default)] +#[derive(Clone, Default)] pub struct ConnectionData { pub total_bytes_downloaded: u128, pub total_bytes_uploaded: u128, @@ -25,35 +31,38 @@ pub struct ConnectionData { pub interface_name: String, } -impl NetworkData { - pub fn divide_by(&mut self, amount: u128) { - self.total_bytes_downloaded /= amount; - self.total_bytes_uploaded /= amount; +impl Bandwidth for NetworkData { + fn get_total_bytes_downloaded(&self) -> u128 { + self.total_bytes_downloaded } -} - -impl ConnectionData { - pub fn divide_by(&mut self, amount: u128) { + fn get_total_bytes_uploaded(&self) -> u128 { + self.total_bytes_uploaded + } + fn combine_bandwidth(&mut self, other: &NetworkData) { + self.total_bytes_downloaded += other.get_total_bytes_downloaded(); + self.total_bytes_uploaded += other.get_total_bytes_uploaded(); + self.connection_count = other.connection_count; + } + fn divide_by(&mut self, amount: u128) { self.total_bytes_downloaded /= amount; self.total_bytes_uploaded /= amount; } } impl Bandwidth for ConnectionData { - fn get_total_bytes_uploaded(&self) -> u128 { - self.total_bytes_uploaded - } fn get_total_bytes_downloaded(&self) -> u128 { self.total_bytes_downloaded } -} - -impl Bandwidth for NetworkData { fn get_total_bytes_uploaded(&self) -> u128 { self.total_bytes_uploaded } - fn get_total_bytes_downloaded(&self) -> u128 { - self.total_bytes_downloaded + fn combine_bandwidth(&mut self, other: &ConnectionData) { + self.total_bytes_downloaded += other.get_total_bytes_downloaded(); + self.total_bytes_uploaded += other.get_total_bytes_uploaded(); + } + fn divide_by(&mut self, amount: u128) { + self.total_bytes_downloaded /= amount; + self.total_bytes_uploaded /= amount; } } @@ -64,12 +73,16 @@ pub struct UtilizationData { #[derive(Default)] pub struct UIState { - pub processes: BTreeMap<String, NetworkData>, - pub remote_addresses: BTreeMap<IpAddr, NetworkData>, - pub connections: BTreeMap<Connection, ConnectionData>, + pub processes: Vec<(String, NetworkData)>, + pub remote_addresses: Vec<(IpAddr, NetworkData)>, + pub connections: Vec<(Connection, ConnectionData)>, pub total_bytes_downloaded: u128, pub total_bytes_uploaded: u128, + pub cumulative_mode: bool, utilization_data: VecDeque<UtilizationData>, + processes_map: HashMap<String, NetworkData>, + remote_addresses_map: HashMap<IpAddr, NetworkData>, + connections_map: HashMap<Connection, ConnectionData>, } impl UIState { @@ -105,9 +118,9 @@ impl UIState { if self.utilization_data.len() > RECALL_LENGTH { self.utilization_data.pop_front(); } - let mut processes: BTreeMap<String, NetworkData> = BTreeMap::new(); - let mut remote_addresses: BTreeMap<IpAddr, NetworkData> = BTreeMap::new(); - let mut connections: BTreeMap<Connection, ConnectionData> = BTreeMap::new(); + let mut processes: HashMap<String, NetworkData> = HashMap::new(); + let mut remote_addresses: HashMap<IpAddr, NetworkData> = HashMap::new(); + let mut connections: HashMap<Connection, ConnectionData> = HashMap::new(); let mut total_bytes_downloaded: u128 = 0; let mut total_bytes_uploaded: u128 = 0; @@ -115,6 +128,7 @@ impl UIState { for state in self.utilization_data.iter().rev() { let connections_to_procs = &state.connections_to_procs; let network_utilization = &state.network_utilization; + for (connection, connection_info) in &network_utilization.connections { let connection_previously_seen = !seen_connections.insert(connection); let connection_data = connections.entry(connection.clone()).or_default(); @@ -167,10 +181,54 @@ impl UIState { for (_, connection_data) in connections.iter_mut() { connection_data.divide_by(divide_by) } - self.processes = processes; - self.remote_addresses = remote_addresses; - self.connections = connections; - self.total_bytes_downloaded = total_bytes_downloaded / divide_by; - self.total_bytes_uploaded = total_bytes_uploaded / divide_by; + + if self.cumulative_mode { + merge_bandwidth(&mut self.processes_map, processes); + merge_bandwidth(&mut self.remote_addresses_map, remote_addresses); + merge_bandwidth(&mut self.connections_map, connections); + self.total_bytes_downloaded += total_bytes_downloaded / divide_by; + self.total_bytes_uploaded += total_bytes_uploaded / divide_by; + } else { + self.processes_map = processes; + self.remote_addresses_map = remote_addresses; + self.connections_map = connections; + self.total_bytes_downloaded = total_bytes_downloaded / divide_by; + self.total_bytes_uploaded = total_bytes_uploaded / divide_by; + } + self.processes = sort_and_prune(&mut self.processes_map); + self.remote_addresses = sort_and_prune(&mut self.remote_addresses_map); + self.connections = sort_and_prune(&mut self.connections_map); } } + +fn merge_bandwidth<K, V>(self_map: &mut HashMap<K, V>, other_map: HashMap<K, V>) +where + K: Eq + Hash, + V: Bandwidth, +{ + for (key, b_other) in other_map { + self_map + .entry(key) + .and_modify(|b_self| b_self.combine_bandwidth(&b_other)) + .or_insert(b_other); + } +} + +fn sort_and_prune<K, V>(map: &mut HashMap<K, V>) -> Vec<(K, V)> +where + K: Eq + Hash + Clone, + V: Bandwidth + Clone, |