diff --git a/Cargo.toml b/Cargo.toml index 30dc0aa6..8490cb56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,16 +15,17 @@ name = "btm" path = "src/main.rs" [profile.release] -debug = 1 # debug = true +debug = 1 opt-level = 'z' # Optimize for size. +# opt-level = 3 # Optimize for speed. lto = true [dependencies] chrono = "0.4.10" clap = "2.33.0" fern = "0.5.9" -futures-timer = "3.0.1" +futures-timer = "3.0.2" futures = "0.3.4" heim = "0.0.10" log = "0.4.8" diff --git a/src/app.rs b/src/app.rs index 12c17bd6..a5400aea 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,11 +28,6 @@ pub enum ScrollDirection { DOWN, } -lazy_static! { - static ref BASE_REGEX: std::result::Result = - regex::Regex::new(".*"); -} - /// AppScrollWidgetState deals with fields for a scrollable app's current state. #[derive(Default)] pub struct AppScrollWidgetState { @@ -64,9 +59,10 @@ impl Default for AppScrollState { pub struct AppSearchState { is_enabled: bool, current_search_query: String, - current_regex: std::result::Result, + current_regex: Option>, current_cursor_position: usize, - pub is_invalid_or_blank_search: bool, + pub is_blank_search: bool, + pub is_invalid_search: bool, } impl Default for AppSearchState { @@ -74,13 +70,20 @@ impl Default for AppSearchState { AppSearchState { is_enabled: false, current_search_query: String::default(), - current_regex: BASE_REGEX.clone(), + current_regex: None, current_cursor_position: 0, - is_invalid_or_blank_search: true, + is_invalid_search: false, + is_blank_search: true, } } } +impl AppSearchState { + pub fn is_invalid_or_blank_search(&self) -> bool { + self.is_blank_search || self.is_invalid_search + } +} + /// ProcessSearchState only deals with process' search's current settings and state. pub struct ProcessSearchState { pub search_state: AppSearchState, @@ -388,6 +391,7 @@ impl App { !self.cpu_state.core_show_vec[curr_posn as usize]; } } + WidgetPosition::Network => {} _ => {} } } @@ -457,43 +461,44 @@ impl App { } pub fn update_regex(&mut self) { - self.process_search_state.search_state.current_regex = if self + if self .process_search_state .search_state .current_search_query .is_empty() { - self.process_search_state - .search_state - .is_invalid_or_blank_search = true; - BASE_REGEX.clone() + self.process_search_state.search_state.is_invalid_search = false; + self.process_search_state.search_state.is_blank_search = true; } else { - let mut final_regex_string = self - .process_search_state - .search_state - .current_search_query - .clone(); + let regex_string = &self.process_search_state.search_state.current_search_query; + let escaped_regex: String; + let final_regex_string = &format!( + "{}{}{}", + if self.process_search_state.is_searching_whole_word { + "^{}$" + } else { + "" + }, + if self.process_search_state.is_ignoring_case { + "(?i){}" + } else { + "" + }, + if !self.process_search_state.is_searching_with_regex { + escaped_regex = regex::escape(regex_string); + &escaped_regex + } else { + regex_string + } + ); - if !self.process_search_state.is_searching_with_regex { - final_regex_string = regex::escape(&final_regex_string); - } + self.process_search_state.search_state.is_blank_search = false; - if self.process_search_state.is_searching_whole_word { - final_regex_string = format!("^{}$", final_regex_string); - } - if self.process_search_state.is_ignoring_case { - final_regex_string = format!("(?i){}", final_regex_string); - } + let new_regex = regex::Regex::new(final_regex_string); + self.process_search_state.search_state.is_invalid_search = new_regex.is_err(); - regex::Regex::new(&final_regex_string) - }; - self.process_search_state - .search_state - .is_invalid_or_blank_search = self - .process_search_state - .search_state - .current_regex - .is_err(); + self.process_search_state.search_state.current_regex = Some(new_regex); + } self.app_scroll_positions .process_scroll_state .previous_scroll_position = 0; @@ -585,13 +590,7 @@ impl App { pub fn clear_search(&mut self) { if let WidgetPosition::ProcessSearch = self.current_widget_selected { - self.process_search_state - .search_state - .current_cursor_position = 0; - self.process_search_state.search_state.current_search_query = String::default(); - self.process_search_state - .search_state - .is_invalid_or_blank_search = true; + self.process_search_state = ProcessSearchState::default(); self.update_process_gui = true; } } @@ -623,7 +622,9 @@ impl App { } } - pub fn get_current_regex_matcher(&self) -> &std::result::Result { + pub fn get_current_regex_matcher( + &self, + ) -> &Option> { &self.process_search_state.search_state.current_regex } diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index cca65075..9a2bc548 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -116,9 +116,10 @@ fn get_process_cpu_stats(pid: u32) -> std::io::Result { /// Note that cpu_fraction should be represented WITHOUT the \times 100 factor! fn linux_cpu_usage( pid: u32, cpu_usage: f64, cpu_fraction: f64, - prev_pid_stats: &HashMap, use_current_cpu_total: bool, + prev_pid_stats: &HashMap, + new_pid_stats: &mut HashMap, use_current_cpu_total: bool, curr_time: Instant, -) -> std::io::Result<(f64, (String, (f64, Instant)))> { +) -> std::io::Result { // Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556 let before_proc_val: f64 = if prev_pid_stats.contains_key(&pid.to_string()) { prev_pid_stats @@ -139,36 +140,28 @@ fn linux_cpu_usage( (after_proc_val - before_proc_val) / cpu_usage * 100_f64 );*/ - let new_dict_entry = (pid.to_string(), (after_proc_val, curr_time)); + new_pid_stats.insert(pid.to_string(), (after_proc_val, curr_time)); + if use_current_cpu_total { - Ok(( - (after_proc_val - before_proc_val) / cpu_usage * 100_f64, - new_dict_entry, - )) + Ok((after_proc_val - before_proc_val) / cpu_usage * 100_f64) } else { - Ok(( - (after_proc_val - before_proc_val) / cpu_usage * 100_f64 * cpu_fraction, - new_dict_entry, - )) + Ok((after_proc_val - before_proc_val) / cpu_usage * 100_f64 * cpu_fraction) } } fn convert_ps( process: &str, cpu_usage: f64, cpu_fraction: f64, - prev_pid_stats: &HashMap, use_current_cpu_total: bool, + prev_pid_stats: &HashMap, + new_pid_stats: &mut HashMap, use_current_cpu_total: bool, curr_time: Instant, -) -> std::io::Result<(ProcessHarvest, (String, (f64, Instant)))> { +) -> std::io::Result { if process.trim().to_string().is_empty() { - let dummy_result = (String::default(), (0.0, Instant::now())); - return Ok(( - ProcessHarvest { - pid: 0, - name: "".to_string(), - mem_usage_percent: 0.0, - cpu_usage_percent: 0.0, - }, - dummy_result, - )); + return Ok(ProcessHarvest { + pid: 0, + name: "".to_string(), + mem_usage_percent: 0.0, + cpu_usage_percent: 0.0, + }); } let pid = (&process[..11]) @@ -183,23 +176,21 @@ fn convert_ps( .parse::() .unwrap_or(0_f64); - let (cpu_usage_percent, new_entry) = linux_cpu_usage( + let cpu_usage_percent = linux_cpu_usage( pid, cpu_usage, cpu_fraction, prev_pid_stats, + new_pid_stats, use_current_cpu_total, curr_time, )?; - Ok(( - ProcessHarvest { - pid, - name, - mem_usage_percent, - cpu_usage_percent: cpu_usage_percent, - }, - new_entry, - )) + Ok(ProcessHarvest { + pid, + name, + mem_usage_percent, + cpu_usage_percent, + }) } pub fn get_sorted_processes_list( @@ -222,19 +213,18 @@ pub fn get_sorted_processes_list( let mut new_pid_stats: HashMap = HashMap::new(); for process in process_stream { - if let Ok((process_object, new_entry)) = convert_ps( + if let Ok(process_object) = convert_ps( process, cpu_usage, cpu_fraction, &prev_pid_stats, + &mut new_pid_stats, use_current_cpu_total, curr_time, ) { if !process_object.name.is_empty() { process_vector.push(process_object); } - - new_pid_stats.insert(new_entry.0, new_entry.1); } } diff --git a/src/canvas.rs b/src/canvas.rs index f888934c..30fc0973 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -470,10 +470,8 @@ impl Painter { }; // Set up blocks and their components - // CPU graph + // CPU graph + legend self.draw_cpu_graph(&mut f, &app_state, cpu_chunk[graph_index]); - - // CPU legend self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]); //Memory usage graph @@ -481,7 +479,6 @@ impl Painter { // Network graph self.draw_network_graph(&mut f, &app_state, network_chunk[0]); - self.draw_network_labels(&mut f, app_state, network_chunk[1]); // Temperature table @@ -535,33 +532,24 @@ impl Painter { .bounds([-0.5, 100.5]) .labels(&["0%", "100%"]); - let mut dataset_vector: Vec = Vec::new(); - let mut cpu_entries_vec: Vec<(Style, Vec<(f64, f64)>)> = Vec::new(); - - for (itx, cpu) in cpu_data.iter().enumerate().rev() { - if app_state.cpu_state.core_show_vec[itx] { - cpu_entries_vec.push(( - self.colours.cpu_colour_styles[(itx) % self.colours.cpu_colour_styles.len()], - cpu.cpu_data - .iter() - .map(<(f64, f64)>::from) - .collect::>(), - )); - } - } - - for cpu_entry in &cpu_entries_vec { - dataset_vector.push( + let dataset_vector: Vec = cpu_data + .iter() + .enumerate() + .rev() + .filter(|(itx, _)| app_state.cpu_state.core_show_vec[*itx]) + .map(|(itx, cpu)| { Dataset::default() .marker(if app_state.app_config_fields.use_dot { Marker::Dot } else { Marker::Braille }) - .style(cpu_entry.0) - .data(&(cpu_entry.1)), - ); - } + .style( + self.colours.cpu_colour_styles[itx % self.colours.cpu_colour_styles.len()], + ) + .data(&cpu.cpu_data[..]) + }) + .collect::>(); let title = if app_state.is_expanded && !app_state.cpu_state.is_showing_tray { const TITLE_BASE: &str = " CPU ── Esc to go back "; @@ -620,26 +608,29 @@ impl Painter { let sliced_cpu_data = &cpu_data[start_position as usize..]; let mut stringified_cpu_data: Vec> = Vec::new(); - for (itx, cpu) in sliced_cpu_data.iter().enumerate() { - if let Some(cpu_data) = cpu.cpu_data.last() { - let entry = if app_state.cpu_state.is_showing_tray { - vec![ + if app_state.cpu_state.is_showing_tray { + for (itx, cpu) in sliced_cpu_data.iter().enumerate() { + if let Some(cpu_data) = cpu.cpu_data.last() { + let entry = vec![ if app_state.cpu_state.core_show_vec[itx + start_position as usize] { "[*]".to_string() } else { "[ ]".to_string() }, cpu.cpu_name.clone(), - format!("{:.0}%", cpu_data.usage.round()), - ] - } else { - vec![ - cpu.cpu_name.clone(), - format!("{:.0}%", cpu_data.usage.round()), - ] - }; + format!("{:.0}%", cpu_data.1.round()), + ]; - stringified_cpu_data.push(entry); + stringified_cpu_data.push(entry); + } + } + } else { + for cpu in sliced_cpu_data.iter() { + if let Some(cpu_data) = cpu.cpu_data.last() { + let entry = vec![cpu.cpu_name.clone(), format!("{:.0}%", cpu_data.1.round())]; + + stringified_cpu_data.push(entry); + } } } @@ -873,10 +864,7 @@ impl Painter { .y_axis(y_axis) .datasets(&[ Dataset::default() - .name(&format!( - "RX: {:7}", - app_state.canvas_data.rx_display.clone() - )) + .name(&format!("RX: {:7}", app_state.canvas_data.rx_display)) .marker(if app_state.app_config_fields.use_dot { Marker::Dot } else { @@ -885,10 +873,7 @@ impl Painter { .style(self.colours.rx_style) .data(&network_data_rx), Dataset::default() - .name(&format!( - "TX: {:7}", - app_state.canvas_data.tx_display.clone() - )) + .name(&format!("TX: {:7}", app_state.canvas_data.tx_display)) .marker(if app_state.app_config_fields.use_dot { Marker::Dot } else { @@ -898,11 +883,11 @@ impl Painter { .data(&network_data_tx), Dataset::default().name(&format!( "Total RX: {:7}", - app_state.canvas_data.total_rx_display.clone() + app_state.canvas_data.total_rx_display )), Dataset::default().name(&format!( "Total TX: {:7}", - app_state.canvas_data.total_tx_display.clone() + app_state.canvas_data.total_tx_display )), ]) .render(f, draw_loc); @@ -911,10 +896,10 @@ impl Painter { fn draw_network_labels( &self, f: &mut Frame, app_state: &mut app::App, draw_loc: Rect, ) { - let rx_display: String = app_state.canvas_data.rx_display.clone(); - let tx_display: String = app_state.canvas_data.tx_display.clone(); - let total_rx_display: String = app_state.canvas_data.total_rx_display.clone(); - let total_tx_display: String = app_state.canvas_data.total_tx_display.clone(); + let rx_display = &app_state.canvas_data.rx_display; + let tx_display = &app_state.canvas_data.tx_display; + let total_rx_display = &app_state.canvas_data.total_rx_display; + let total_tx_display = &app_state.canvas_data.total_tx_display; // Gross but I need it to work... let total_network = vec![vec![ @@ -1252,7 +1237,11 @@ impl Painter { ); let title = format!("{} Esc to close ", "─".repeat(repeat_num as usize)); - let current_border_style: Style = if app_state.get_current_regex_matcher().is_err() { + let current_border_style: Style = if app_state + .process_search_state + .search_state + .is_invalid_search + { Style::default().fg(Color::Rgb(255, 0, 0)) } else { match app_state.current_widget_selected { diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 71f921b1..26cbd9c1 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -35,27 +35,7 @@ pub struct ConvertedProcessData { #[derive(Clone, Default, Debug)] pub struct ConvertedCpuData { pub cpu_name: String, - pub cpu_data: Vec, -} - -#[derive(Clone, Default, Debug)] -pub struct CpuPoint { - pub time: f64, - pub usage: f64, -} - -impl From for (f64, f64) { - fn from(c: CpuPoint) -> (f64, f64) { - let CpuPoint { time, usage } = c; - (time, usage) - } -} - -impl From<&CpuPoint> for (f64, f64) { - fn from(c: &CpuPoint) -> (f64, f64) { - let CpuPoint { time, usage } = c; - (*time, *usage) - } + pub cpu_data: Vec<(f64, f64)>, } pub fn convert_temp_row(app: &App) -> Vec> { @@ -150,16 +130,14 @@ pub fn convert_cpu_data_points( //Insert joiner points for &(joiner_offset, joiner_val) in &cpu.1 { let offset_time = time_from_start - joiner_offset as f64; - cpu_data_vector[itx_offset].cpu_data.push(CpuPoint { - time: offset_time, - usage: joiner_val, - }); + cpu_data_vector[itx_offset] + .cpu_data + .push((offset_time, joiner_val)); } - cpu_data_vector[itx_offset].cpu_data.push(CpuPoint { - time: time_from_start, - usage: cpu.0, - }); + cpu_data_vector[itx_offset] + .cpu_data + .push((time_from_start, cpu.0)); } } diff --git a/src/main.rs b/src/main.rs index a21d7a83..aefa5626 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ use crossterm::{ }; use std::{ + boxed::Box, io::{stdout, Write}, panic::{self, PanicInfo}, sync::mpsc, @@ -47,7 +48,7 @@ use utils::error::{self, BottomError}; enum Event { KeyInput(I), MouseInput(J), - Update(data_harvester::Data), + Update(Box), Clean, } @@ -210,6 +211,7 @@ fn main() -> error::Result<()> { painter.colours.generate_remaining_cpu_colours(); painter.initialize(); + let mut first_run = true; loop { // TODO: [OPT] this should not block... if let Ok(recv) = rx.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) { @@ -259,10 +261,13 @@ fn main() -> error::Result<()> { ); // Pre-fill CPU if needed - for itx in 0..app.canvas_data.cpu_data.len() { - if app.cpu_state.core_show_vec.len() <= itx { - app.cpu_state.core_show_vec.push(true); + if first_run { + for itx in 0..app.canvas_data.cpu_data.len() { + if app.cpu_state.core_show_vec.len() <= itx { + app.cpu_state.core_show_vec.push(true); + } } + first_run = false; } // Processes @@ -756,22 +761,24 @@ fn update_final_process_list(app: &mut app::App) { let mut filtered_process_data: Vec = if app.is_grouped() { app.canvas_data .grouped_process_data - .clone() - .into_iter() + .iter() .filter(|process| { if app .process_search_state .search_state - .is_invalid_or_blank_search + .is_invalid_or_blank_search() { - true - } else if let Ok(matcher) = app.get_current_regex_matcher() { - matcher.is_match(&process.name) - } else { - true + return true; + } else if let Some(matcher_result) = app.get_current_regex_matcher() { + if let Ok(matcher) = matcher_result { + return matcher.is_match(&process.name); + } } + + true }) - .collect::>() + .cloned() + .collect::>() } else { app.canvas_data .process_data @@ -780,18 +787,19 @@ fn update_final_process_list(app: &mut app::App) { if app .process_search_state .search_state - .is_invalid_or_blank_search + .is_invalid_or_blank_search() { - true - } else if let Ok(matcher) = app.get_current_regex_matcher() { - if app.process_search_state.is_searching_with_pid { - matcher.is_match(&process.pid.to_string()) - } else { - matcher.is_match(&process.name) + return true; + } else if let Some(matcher_result) = app.get_current_regex_matcher() { + if let Ok(matcher) = matcher_result { + if app.process_search_state.is_searching_with_pid { + return matcher.is_match(&process.pid.to_string()); + } else { + return matcher.is_match(&process.name); + } } - } else { - true } + true }) .map(|(_pid, process)| ConvertedProcessData { pid: process.pid, @@ -800,7 +808,7 @@ fn update_final_process_list(app: &mut app::App) { mem_usage: process.mem_usage_percent, group_pids: vec![process.pid], }) - .collect::>() + .collect::>() }; sort_process_data(&mut filtered_process_data, app); @@ -887,7 +895,7 @@ fn create_event_thread( } } futures::executor::block_on(data_state.update_data()); - let event = Event::Update(data_state.data); + let event = Event::Update(Box::from(data_state.data)); data_state.data = data_harvester::Data::default(); tx.send(event).unwrap(); thread::sleep(Duration::from_millis(update_rate_in_milliseconds));