From 30bdaa6073ad28eb1083997e874654727460fcbb Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Fri, 7 Aug 2020 01:29:20 -0700 Subject: [PATCH] feature: add full command to process widget This PR adds the ability to toggle between the process name and process path. Currently, this uses `P` as the modifier key. Currently, the longer command names are dealt with by forcefully changing the width of the columns, but this can be handled in a more graceful manner IMO. --- .vscode/settings.json | 3 + Cargo.lock | 4 +- README.md | 20 +-- src/app.rs | 104 ++++++++------- src/app/data_harvester/processes.rs | 33 +++-- src/app/states.rs | 62 +++++++++ src/canvas.rs | 2 - src/canvas/dialogs/dd_dialog.rs | 7 +- src/canvas/dialogs/help_dialog.rs | 2 +- src/canvas/widgets/process_table.rs | 32 +++-- src/constants.rs | 5 +- src/data_conversion.rs | 193 ++++++++++++++++------------ src/main.rs | 68 +++++----- src/options.rs | 2 - 14 files changed, 330 insertions(+), 207 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c0539685..610580f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,12 +8,15 @@ "Nonexhaustive", "Qudsi", "Tebibytes", + "Ungrouped", "Wojnarowski", "andys", "crossterm", "curr", + "czvf", "gotop", "gtop", + "haase", "heim", "hjkl", "markdownlint", diff --git a/Cargo.lock b/Cargo.lock index 322ae37b..dbb3bff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1340,9 +1340,9 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "tui" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c25eac88406f384894aa6db56ac0378c767254ee5829824ce5b0fc8fd24d5778" +checksum = "9533d39bef0ae8f510e8a99d78702e68d1bbf0b98a78ec9740509d287010ae1e" dependencies = [ "bitflags", "cassowary", diff --git a/README.md b/README.md index 5d0df94c..6b1dee99 100644 --- a/README.md +++ b/README.md @@ -206,15 +206,16 @@ Run using `btm`. #### Process bindings -| | | -| ------------- | ---------------------------------------------------------- | -| `dd` | Kill the selected process | -| `c` | Sort by CPU usage, press again to reverse sorting order | -| `m` | Sort by memory usage, press again to reverse sorting order | -| `p` | Sort by PID name, press again to reverse sorting order | -| `n` | Sort by process name, press again to reverse sorting order | -| `Tab` | Group/un-group processes with the same name | -| `Ctrl-f`, `/` | Open process search widget | +| | | +| ------------- | ------------------------------------------------------------- | +| `dd` | Kill the selected process | +| `c` | Sort by CPU usage, press again to reverse sorting order | +| `m` | Sort by memory usage, press again to reverse sorting order | +| `p` | Sort by PID name, press again to reverse sorting order | +| `n` | Sort by process name, press again to reverse sorting order | +| `Tab` | Group/un-group processes with the same name | +| `Ctrl-f`, `/` | Open process search widget | +| `P` | Toggle between showing the full path or just the process name | #### Process search bindings @@ -551,6 +552,7 @@ Thanks to all contributors ([emoji key](https://allcontributors.org/docs/en/emoj + ## Thanks diff --git a/src/app.rs b/src/app.rs index e61c9792..9c206a03 100644 --- a/src/app.rs +++ b/src/app.rs @@ -149,8 +149,7 @@ impl App { BottomWidgetType::Proc => { if let Some(current_proc_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { if current_proc_state.is_search_enabled() { current_proc_state @@ -164,8 +163,7 @@ impl App { BottomWidgetType::ProcSearch => { if let Some(current_proc_state) = self .proc_state - .widget_states - .get_mut(&(self.current_widget.widget_id - 1)) + .get_mut_widget_state(self.current_widget.widget_id - 1) { if current_proc_state.is_search_enabled() { current_proc_state @@ -243,8 +241,7 @@ impl App { BottomWidgetType::Cpu => { if let Some(cpu_widget_state) = self .cpu_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { cpu_widget_state.is_multi_graph_mode = !cpu_widget_state.is_multi_graph_mode; @@ -253,8 +250,7 @@ impl App { BottomWidgetType::Proc => { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { // Toggles process widget grouping state proc_widget_state.is_grouped = !(proc_widget_state.is_grouped); @@ -285,8 +281,7 @@ impl App { // Toggle on if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { proc_widget_state .process_search_state @@ -500,12 +495,20 @@ impl App { pub fn on_left_key(&mut self) { if !self.is_in_dialog() { match self.current_widget.widget_type { + BottomWidgetType::Proc => { + if let Some(proc_widget_state) = self + .proc_state + .get_mut_widget_state(self.current_widget.widget_id) + { + proc_widget_state.current_column_index = + proc_widget_state.current_column_index.saturating_sub(1); + } + } BottomWidgetType::ProcSearch => { let is_in_search_widget = self.is_in_search_widget(); if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&(self.current_widget.widget_id - 1)) + .get_mut_widget_state(self.current_widget.widget_id - 1) { if is_in_search_widget { let prev_cursor = proc_widget_state.get_cursor_position(); @@ -533,8 +536,7 @@ impl App { if !self.canvas_data.battery_data.is_empty() { if let Some(battery_widget_state) = self .battery_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { if battery_widget_state.currently_selected_battery_index > 0 { battery_widget_state.currently_selected_battery_index -= 1; @@ -552,12 +554,21 @@ impl App { pub fn on_right_key(&mut self) { if !self.is_in_dialog() { match self.current_widget.widget_type { + BottomWidgetType::Proc => { + if let Some(proc_widget_state) = self + .proc_state + .get_mut_widget_state(self.current_widget.widget_id) + { + if proc_widget_state.current_column_index < proc_widget_state.num_columns { + proc_widget_state.current_column_index += 1; + } + } + } BottomWidgetType::ProcSearch => { let is_in_search_widget = self.is_in_search_widget(); if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&(self.current_widget.widget_id - 1)) + .get_mut_widget_state(self.current_widget.widget_id - 1) { if is_in_search_widget { let prev_cursor = proc_widget_state.get_cursor_position(); @@ -586,8 +597,7 @@ impl App { let battery_count = self.canvas_data.battery_data.len(); if let Some(battery_widget_state) = self .battery_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { if battery_widget_state.currently_selected_battery_index < battery_count - 1 @@ -887,8 +897,7 @@ impl App { if let BottomWidgetType::Proc = self.current_widget.widget_type { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { match proc_widget_state.process_sorting_type { processes::ProcessSorting::CPU => { @@ -911,8 +920,7 @@ impl App { if let BottomWidgetType::Proc = self.current_widget.widget_type { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { match proc_widget_state.process_sorting_type { processes::ProcessSorting::MEM => { @@ -934,8 +942,7 @@ impl App { if let BottomWidgetType::Proc = self.current_widget.widget_type { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { // Skip if grouped if !proc_widget_state.is_grouped { @@ -956,21 +963,32 @@ impl App { } } } + 'P' => { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .proc_state + .get_mut_widget_state(self.current_widget.widget_id) + { + proc_widget_state.is_using_full_path = + !proc_widget_state.is_using_full_path; + self.proc_state.force_update = Some(self.current_widget.widget_id); + } + } + } 'n' => { if let BottomWidgetType::Proc = self.current_widget.widget_type { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { match proc_widget_state.process_sorting_type { - processes::ProcessSorting::NAME => { + processes::ProcessSorting::IDENTIFIER => { proc_widget_state.process_sorting_reverse = !proc_widget_state.process_sorting_reverse } _ => { proc_widget_state.process_sorting_type = - processes::ProcessSorting::NAME; + processes::ProcessSorting::IDENTIFIER; proc_widget_state.process_sorting_reverse = false; } } @@ -1371,8 +1389,7 @@ impl App { if let Some(new_widget) = self.widget_map.get(&new_widget_id) { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get(&self.current_widget.widget_id) + .get_widget_state(self.current_widget.widget_id) { if proc_widget_state.is_search_enabled() { self.current_widget = new_widget.clone(); @@ -1393,8 +1410,7 @@ impl App { BottomWidgetType::Proc => { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { proc_widget_state.scroll_state.current_scroll_position = 0; proc_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; @@ -1403,8 +1419,7 @@ impl App { BottomWidgetType::Temp => { if let Some(temp_widget_state) = self .temp_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { temp_widget_state.scroll_state.current_scroll_position = 0; temp_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; @@ -1413,8 +1428,7 @@ impl App { BottomWidgetType::Disk => { if let Some(disk_widget_state) = self .disk_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { disk_widget_state.scroll_state.current_scroll_position = 0; disk_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; @@ -1423,8 +1437,7 @@ impl App { BottomWidgetType::CpuLegend => { if let Some(cpu_widget_state) = self .cpu_state - .widget_states - .get_mut(&(self.current_widget.widget_id - 1)) + .get_mut_widget_state(self.current_widget.widget_id - 1) { cpu_widget_state.scroll_state.current_scroll_position = 0; cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; @@ -1445,8 +1458,7 @@ impl App { BottomWidgetType::Proc => { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { if let Some(finalized_process_data) = self .canvas_data @@ -1465,8 +1477,7 @@ impl App { BottomWidgetType::Temp => { if let Some(temp_widget_state) = self .temp_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { if !self.canvas_data.temp_sensor_data.is_empty() { temp_widget_state.scroll_state.current_scroll_position = @@ -1478,8 +1489,7 @@ impl App { BottomWidgetType::Disk => { if let Some(disk_widget_state) = self .disk_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { if !self.canvas_data.disk_data.is_empty() { disk_widget_state.scroll_state.current_scroll_position = @@ -1491,8 +1501,7 @@ impl App { BottomWidgetType::CpuLegend => { if let Some(cpu_widget_state) = self .cpu_state - .widget_states - .get_mut(&(self.current_widget.widget_id - 1)) + .get_mut_widget_state(self.current_widget.widget_id - 1) { let cap = self.canvas_data.cpu_data.len(); if cap > 0 { @@ -1564,8 +1573,7 @@ impl App { fn change_process_position(&mut self, num_to_change_by: i64) { if let Some(proc_widget_state) = self .proc_state - .widget_states - .get_mut(&self.current_widget.widget_id) + .get_mut_widget_state(self.current_widget.widget_id) { let current_posn = proc_widget_state.scroll_state.current_scroll_position; diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index bfb820ac..bc6a2d40 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -17,7 +17,7 @@ pub enum ProcessSorting { CPU, MEM, PID, - NAME, + IDENTIFIER, } impl Default for ProcessSorting { @@ -32,6 +32,7 @@ pub struct ProcessHarvest { pub cpu_usage_percent: f64, pub mem_usage_percent: f64, pub name: String, + pub path: String, pub read_bytes_per_sec: u64, pub write_bytes_per_sec: u64, pub total_read_bytes: u64, @@ -46,6 +47,7 @@ pub struct PrevProcDetails { pub total_write_bytes: u64, pub cpu_time: f64, pub proc_stat_path: PathBuf, + pub proc_exe_path: PathBuf, pub proc_io_path: PathBuf, } @@ -54,6 +56,7 @@ impl PrevProcDetails { let pid_string = pid.to_string(); PrevProcDetails { proc_io_path: PathBuf::from(format!("/proc/{}/io", pid_string)), + proc_exe_path: PathBuf::from(format!("/proc/{}/exe", pid_string)), proc_stat_path: PathBuf::from(format!("/proc/{}/stat", pid_string)), ..PrevProcDetails::default() } @@ -174,7 +177,9 @@ fn get_linux_cpu_usage( // Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556 let after_proc_val = get_process_cpu_stats(&proc_stats); - if use_current_cpu_total { + if cpu_usage == 0.0 { + Ok((0_f64, after_proc_val)) + } else if use_current_cpu_total { Ok(( (after_proc_val - before_proc_val) / cpu_usage * 100_f64, after_proc_val, @@ -194,17 +199,18 @@ fn convert_ps( new_pid_stats: &mut HashMap, use_current_cpu_total: bool, time_difference_in_secs: u64, ) -> std::io::Result { - let pid = (&process[..11]) + let pid = (&process[..10]) .trim() .to_string() .parse::() .unwrap_or(0); - let name = (&process[11..61]).trim().to_string(); - let mem_usage_percent = (&process[62..]) + let name = (&process[11..111]).trim().to_string(); + let mem_usage_percent = (&process[112..116]) .trim() .to_string() .parse::() .unwrap_or(0_f64); + let path = (&process[117..]).trim().to_string(); let mut new_pid_stat = if let Some(prev_proc_stats) = prev_pid_stats.remove(&pid) { prev_proc_stats @@ -265,9 +271,11 @@ fn convert_ps( new_pid_stats.insert(pid, new_pid_stat); + // TODO: Is there a way to re-use these stats so I don't have to do so many syscalls? Ok(ProcessHarvest { pid, name, + path, mem_usage_percent, cpu_usage_percent, total_read_bytes, @@ -286,7 +294,7 @@ pub fn linux_get_processes_list( time_difference_in_secs: u64, ) -> crate::utils::error::Result> { let ps_result = Command::new("ps") - .args(&["-axo", "pid:10,comm:50,%mem:5", "--noheader"]) + .args(&["-axo", "pid:10,comm:100,%mem:5,args:100", "--noheader"]) .output()?; let ps_stdout = String::from_utf8_lossy(&ps_result.stdout); let split_string = ps_stdout.split('\n'); @@ -356,13 +364,21 @@ pub fn windows_macos_get_processes_list( } else { process_val.name().to_string() }; + let path = { + let path = process_val.cmd().join(" "); + if path.is_empty() { + name.to_string() + } else { + path + } + }; - let pcu = if cfg!(target_os = "windows") { + let pcu = if cfg!(target_os = "windows") || num_cpus == 0.0 { process_val.cpu_usage() as f64 } else { process_val.cpu_usage() as f64 / num_cpus }; - let process_cpu_usage = if use_current_cpu_total { + let process_cpu_usage = if use_current_cpu_total && cpu_usage > 0.0 { pcu / cpu_usage } else { pcu @@ -373,6 +389,7 @@ pub fn windows_macos_get_processes_list( process_vector.push(ProcessHarvest { pid: process_val.pid() as u32, name, + path, mem_usage_percent: if mem_total_kb > 0 { process_val.memory() as f64 * 100.0 / mem_total_kb as f64 } else { diff --git a/src/app/states.rs b/src/app/states.rs index 52f98499..89de370d 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -147,6 +147,9 @@ pub struct ProcWidgetState { pub scroll_state: AppScrollWidgetState, pub process_sorting_type: processes::ProcessSorting, pub process_sorting_reverse: bool, + pub is_using_full_path: bool, + pub current_column_index: usize, + pub num_columns: usize, } impl ProcWidgetState { @@ -171,6 +174,9 @@ impl ProcWidgetState { scroll_state: AppScrollWidgetState::default(), process_sorting_type: processes::ProcessSorting::CPU, process_sorting_reverse: true, + is_using_full_path: false, + current_column_index: 0, + num_columns: 1, } } @@ -263,6 +269,14 @@ impl ProcState { force_update_all: false, } } + + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut ProcWidgetState> { + self.widget_states.get_mut(&widget_id) + } + + pub fn get_widget_state(&self, widget_id: u64) -> Option<&ProcWidgetState> { + self.widget_states.get(&widget_id) + } } pub struct NetWidgetState { @@ -291,6 +305,14 @@ impl NetState { widget_states, } } + + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut NetWidgetState> { + self.widget_states.get_mut(&widget_id) + } + + pub fn get_widget_state(&self, widget_id: u64) -> Option<&NetWidgetState> { + self.widget_states.get(&widget_id) + } } pub struct CpuWidgetState { @@ -325,6 +347,14 @@ impl CpuState { widget_states, } } + + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut CpuWidgetState> { + self.widget_states.get_mut(&widget_id) + } + + pub fn get_widget_state(&self, widget_id: u64) -> Option<&CpuWidgetState> { + self.widget_states.get(&widget_id) + } } pub struct MemWidgetState { @@ -353,6 +383,14 @@ impl MemState { widget_states, } } + + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut MemWidgetState> { + self.widget_states.get_mut(&widget_id) + } + + pub fn get_widget_state(&self, widget_id: u64) -> Option<&MemWidgetState> { + self.widget_states.get(&widget_id) + } } pub struct TempWidgetState { @@ -375,6 +413,14 @@ impl TempState { pub fn init(widget_states: HashMap) -> Self { TempState { widget_states } } + + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut TempWidgetState> { + self.widget_states.get_mut(&widget_id) + } + + pub fn get_widget_state(&self, widget_id: u64) -> Option<&TempWidgetState> { + self.widget_states.get(&widget_id) + } } pub struct DiskWidgetState { @@ -397,6 +443,14 @@ impl DiskState { pub fn init(widget_states: HashMap) -> Self { DiskState { widget_states } } + + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskWidgetState> { + self.widget_states.get_mut(&widget_id) + } + + pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskWidgetState> { + self.widget_states.get(&widget_id) + } } pub struct BasicTableWidgetState { // Since this is intended (currently) to only be used for ONE widget, that's @@ -420,6 +474,14 @@ impl BatteryState { pub fn init(widget_states: HashMap) -> Self { BatteryState { widget_states } } + + pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut BatteryWidgetState> { + self.widget_states.get_mut(&widget_id) + } + + pub fn get_widget_state(&self, widget_id: u64) -> Option<&BatteryWidgetState> { + self.widget_states.get(&widget_id) + } } #[derive(Default)] diff --git a/src/canvas.rs b/src/canvas.rs index e141214e..f67e23a2 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -40,8 +40,6 @@ pub struct DisplayableData { pub temp_sensor_data: Vec>, // Not the final value pub process_data: Vec, - // Not the final value - pub grouped_process_data: Vec, // What's actually displayed pub finalized_process_data_map: HashMap>, pub mem_label: String, diff --git a/src/canvas/dialogs/dd_dialog.rs b/src/canvas/dialogs/dd_dialog.rs index d4b605d6..cb123df2 100644 --- a/src/canvas/dialogs/dd_dialog.rs +++ b/src/canvas/dialogs/dd_dialog.rs @@ -28,20 +28,19 @@ impl KillDialog for Painter { if app_state.is_grouped(app_state.current_widget.widget_id) { if to_kill_processes.1.len() != 1 { Text::raw(format!( - "\nKill {} processes with the name {}?", + "\nKill {} processes with the name \"{}\"?", to_kill_processes.1.len(), to_kill_processes.0 )) } else { Text::raw(format!( - "\nKill {} process with the name {}?", - to_kill_processes.1.len(), + "\nKill 1 process with the name \"{}\"?", to_kill_processes.0 )) } } else { Text::raw(format!( - "\nKill process {} with PID {}?", + "\nKill process \"{}\" with PID {}?", to_kill_processes.0, first_pid )) }, diff --git a/src/canvas/dialogs/help_dialog.rs b/src/canvas/dialogs/help_dialog.rs index a11e19e1..d0156d1a 100644 --- a/src/canvas/dialogs/help_dialog.rs +++ b/src/canvas/dialogs/help_dialog.rs @@ -31,7 +31,7 @@ impl HelpDialog for Painter { // small terminal sizes... oh joy. let mut overflow_buffer = 0; - let paragraph_width = draw_loc.width - 2; + let paragraph_width = std::cmp::max(draw_loc.width.saturating_sub(2), 1); let mut prev_section_len = 0; constants::HELP_TEXT diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index 721d34b0..5ceb8b28 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -133,13 +133,17 @@ impl ProcessTableWidget for Painter { }); use app::data_harvester::processes::ProcessSorting; - let mut pid_or_name = if proc_widget_state.is_grouped { + let mut pid_or_count = if proc_widget_state.is_grouped { "Count" } else { "PID(p)" } .to_string(); - let mut name = "Name(n)".to_string(); + let mut identifier = if proc_widget_state.is_using_full_path { + "Command(n)".to_string() + } else { + "Name(n)".to_string() + }; let mut cpu = "CPU%(c)".to_string(); let mut mem = "Mem%(m)".to_string(); let rps = "R/s".to_string(); @@ -157,14 +161,15 @@ impl ProcessTableWidget for Painter { match proc_widget_state.process_sorting_type { ProcessSorting::CPU => cpu += &direction_val, ProcessSorting::MEM => mem += &direction_val, - ProcessSorting::PID => pid_or_name += &direction_val, - ProcessSorting::NAME => name += &direction_val, + ProcessSorting::PID => pid_or_count += &direction_val, + ProcessSorting::IDENTIFIER => identifier += &direction_val, }; + // TODO: Gonna have to figure out how to do left/right GUI notation. let process_headers = if proc_widget_state.is_grouped { vec![ - pid_or_name, - name, + pid_or_count, + identifier, cpu, mem, rps, @@ -174,8 +179,8 @@ impl ProcessTableWidget for Painter { ] } else { vec![ - pid_or_name, - name, + pid_or_count, + identifier, cpu, mem, rps, @@ -185,6 +190,7 @@ impl ProcessTableWidget for Painter { process_state, ] }; + proc_widget_state.num_columns = process_headers.len(); let process_headers_lens: Vec = process_headers .iter() .map(|entry| entry.len()) @@ -192,8 +198,16 @@ impl ProcessTableWidget for Painter { // Calculate widths let width = f64::from(draw_loc.width); + + // TODO: This is a ugly work-around for now. let width_ratios = if proc_widget_state.is_grouped { - vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15] + if proc_widget_state.is_using_full_path { + vec![0.1, 0.7, 0.05, 0.05, 0.025, 0.025, 0.025, 0.025] + } else { + vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15] + } + } else if proc_widget_state.is_using_full_path { + vec![0.1, 0.7, 0.05, 0.05, 0.02, 0.02, 0.02, 0.02, 0.02] } else { vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] }; diff --git a/src/constants.rs b/src/constants.rs index d4bd2ceb..e72eb82a 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -79,7 +79,7 @@ pub const CPU_HELP_TEXT: [&str; 2] = [ "Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart", ]; -pub const PROCESS_HELP_TEXT: [&str; 8] = [ +pub const PROCESS_HELP_TEXT: [&str; 9] = [ "3 - Process widget\n", "dd Kill the selected process\n", "c Sort by CPU usage, press again to reverse sorting order\n", @@ -87,7 +87,8 @@ pub const PROCESS_HELP_TEXT: [&str; 8] = [ "p Sort by PID name, press again to reverse sorting order\n", "n Sort by process name, press again to reverse sorting order\n", "Tab Group/un-group processes with the same name\n", - "Ctrl-f, / Open process search widget", + "Ctrl-f, / Open process search widget\n", + "P Toggle between showing the full path or just the process name", ]; pub const SEARCH_HELP_TEXT: [&str; 43] = [ diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 5081d04f..200ac998 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -359,99 +359,122 @@ pub fn convert_network_data_points( } } +pub enum ProcessGroupingType { + Grouped, + Ungrouped, +} + +pub enum ProcessNamingType { + Name, + Path, +} + pub fn convert_process_data( - current_data: &data_farmer::DataCollection, -) -> (Vec, Vec) { - let mut single_list = Vec::new(); + current_data: &data_farmer::DataCollection, grouping_type: ProcessGroupingType, + name_type: ProcessNamingType, +) -> Vec { + match grouping_type { + ProcessGroupingType::Ungrouped => current_data + .process_harvest + .iter() + .map(|process| { + let converted_rps = get_exact_byte_values(process.read_bytes_per_sec, false); + let converted_wps = get_exact_byte_values(process.write_bytes_per_sec, false); + let converted_total_read = get_exact_byte_values(process.total_read_bytes, false); + let converted_total_write = get_exact_byte_values(process.total_write_bytes, false); - // cpu, mem, pids - let mut grouped_hashmap: HashMap = std::collections::HashMap::new(); + let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1); + let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1); + let total_read = + format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1); + let total_write = format!( + "{:.*}{}", + 0, converted_total_write.0, converted_total_write.1 + ); - // Go through every single process in the list... and build a hashmap + single list - for process in &(current_data).process_harvest { - let entry = grouped_hashmap - .entry(process.name.clone()) - .or_insert(SingleProcessData { - pid: process.pid, - ..SingleProcessData::default() + ConvertedProcessData { + pid: process.pid, + name: match name_type { + ProcessNamingType::Name => process.name.to_string(), + ProcessNamingType::Path => process.path.to_string(), + }, + cpu_usage: process.cpu_usage_percent, + mem_usage: process.mem_usage_percent, + group_pids: vec![process.pid], + read_per_sec, + write_per_sec, + total_read, + total_write, + rps_f64: process.read_bytes_per_sec as f64, + wps_f64: process.write_bytes_per_sec as f64, + tr_f64: process.total_read_bytes as f64, + tw_f64: process.total_write_bytes as f64, + process_states: process.process_state.to_owned(), + } + }) + .collect::>(), + ProcessGroupingType::Grouped => { + let mut grouped_hashmap: HashMap = + std::collections::HashMap::new(); + + current_data.process_harvest.iter().for_each(|process| { + let entry = grouped_hashmap + .entry(match name_type { + ProcessNamingType::Name => process.name.to_string(), + ProcessNamingType::Path => process.path.to_string(), + }) + .or_insert(SingleProcessData { + pid: process.pid, + ..SingleProcessData::default() + }); + + (*entry).cpu_usage += process.cpu_usage_percent; + (*entry).mem_usage += process.mem_usage_percent; + (*entry).group_pids.push(process.pid); + (*entry).read_per_sec += process.read_bytes_per_sec; + (*entry).write_per_sec += process.write_bytes_per_sec; + (*entry).total_read += process.total_read_bytes; + (*entry).total_write += process.total_write_bytes; }); - (*entry).cpu_usage += process.cpu_usage_percent; - (*entry).mem_usage += process.mem_usage_percent; - (*entry).group_pids.push(process.pid); - (*entry).read_per_sec += process.read_bytes_per_sec; - (*entry).write_per_sec += process.write_bytes_per_sec; - (*entry).total_read += process.total_read_bytes; - (*entry).total_write += process.total_write_bytes; + grouped_hashmap + .iter() + .map(|(identifier, process_details)| { + let p = process_details.clone(); + let converted_rps = get_exact_byte_values(p.read_per_sec, false); + let converted_wps = get_exact_byte_values(p.write_per_sec, false); + let converted_total_read = get_exact_byte_values(p.total_read, false); + let converted_total_write = get_exact_byte_values(p.total_write, false); - let converted_rps = get_exact_byte_values(process.read_bytes_per_sec, false); - let converted_wps = get_exact_byte_values(process.write_bytes_per_sec, false); - let converted_total_read = get_exact_byte_values(process.total_read_bytes, false); - let converted_total_write = get_exact_byte_values(process.total_write_bytes, false); + let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1); + let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1); + let total_read = + format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1); + let total_write = format!( + "{:.*}{}", + 0, converted_total_write.0, converted_total_write.1 + ); - let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1); - let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1); - let total_read = format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1); - let total_write = format!( - "{:.*}{}", - 0, converted_total_write.0, converted_total_write.1 - ); - - single_list.push(ConvertedProcessData { - pid: process.pid, - name: process.name.to_string(), - cpu_usage: process.cpu_usage_percent, - mem_usage: process.mem_usage_percent, - group_pids: vec![process.pid], - read_per_sec, - write_per_sec, - total_read, - total_write, - rps_f64: process.read_bytes_per_sec as f64, - wps_f64: process.write_bytes_per_sec as f64, - tr_f64: process.total_read_bytes as f64, - tw_f64: process.total_write_bytes as f64, - process_states: process.process_state.to_owned(), - }); + ConvertedProcessData { + pid: p.pid, + name: identifier.to_string(), + cpu_usage: p.cpu_usage, + mem_usage: p.mem_usage, + group_pids: p.group_pids, + read_per_sec, + write_per_sec, + total_read, + total_write, + rps_f64: p.read_per_sec as f64, + wps_f64: p.write_per_sec as f64, + tr_f64: p.total_read as f64, + tw_f64: p.total_write as f64, + process_states: p.process_state, + } + }) + .collect::>() + } } - - let grouped_list: Vec = grouped_hashmap - .iter() - .map(|(name, process_details)| { - let p = process_details.clone(); - let converted_rps = get_exact_byte_values(p.read_per_sec, false); - let converted_wps = get_exact_byte_values(p.write_per_sec, false); - let converted_total_read = get_exact_byte_values(p.total_read, false); - let converted_total_write = get_exact_byte_values(p.total_write, false); - - let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1); - let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1); - let total_read = format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1); - let total_write = format!( - "{:.*}{}", - 0, converted_total_write.0, converted_total_write.1 - ); - - ConvertedProcessData { - pid: p.pid, - name: name.to_string(), - cpu_usage: p.cpu_usage, - mem_usage: p.mem_usage, - group_pids: p.group_pids, - read_per_sec, - write_per_sec, - total_read, - total_write, - rps_f64: p.read_per_sec as f64, - wps_f64: p.write_per_sec as f64, - tr_f64: p.total_read as f64, - tw_f64: p.total_write as f64, - process_states: p.process_state, - } - }) - .collect::>(); - - (single_list, grouped_list) } pub fn convert_battery_harvest( diff --git a/src/main.rs b/src/main.rs index b2aa1f23..e4871563 100644 --- a/src/main.rs +++ b/src/main.rs @@ -225,9 +225,6 @@ fn main() -> error::Result<()> { // Processes if app.used_widgets.use_proc { - let (single, grouped) = convert_process_data(&app.data_collection); - app.canvas_data.process_data = single; - app.canvas_data.grouped_process_data = grouped; update_all_process_lists(&mut app); } @@ -582,44 +579,45 @@ fn update_final_process_list(app: &mut App, widget_id: u64) { .is_invalid_or_blank_search(), None => false, }; + let is_grouped = app.is_grouped(widget_id); + + if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) { + app.canvas_data.process_data = convert_process_data( + &app.data_collection, + if is_grouped { + ProcessGroupingType::Grouped + } else { + ProcessGroupingType::Ungrouped + }, + if proc_widget_state.is_using_full_path { + ProcessNamingType::Path + } else { + ProcessNamingType::Name + }, + ); + } let process_filter = app.get_process_filter(widget_id); - let filtered_process_data: Vec = if app.is_grouped(widget_id) { - app.canvas_data - .grouped_process_data - .iter() - .filter(|process| { - if is_invalid_or_blank { - true - } else if let Some(process_filter) = process_filter { - process_filter.check(process) + let filtered_process_data: Vec = app + .canvas_data + .process_data + .iter() + .filter(|process| { + if !is_invalid_or_blank { + if let Some(process_filter) = process_filter { + process_filter.check(&process) } else { true } - }) - .cloned() - .collect::>() - } else { - app.canvas_data - .process_data - .iter() - .filter(|process| { - if !is_invalid_or_blank { - if let Some(process_filter) = process_filter { - process_filter.check(&process) - } else { - true - } - } else { - true - } - }) - .cloned() - .collect::>() - }; + } else { + true + } + }) + .cloned() + .collect::>(); // Quick fix for tab updating the table headers - if let Some(proc_widget_state) = app.proc_state.widget_states.get_mut(&widget_id) { + if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) { if let data_harvester::processes::ProcessSorting::PID = proc_widget_state.process_sorting_type { @@ -672,7 +670,7 @@ fn sort_process_data( ) }); } - ProcessSorting::NAME => { + ProcessSorting::IDENTIFIER => { // Don't repeat if false... if proc_widget_state.process_sorting_reverse { to_sort_vec.sort_by(|a, b| { diff --git a/src/options.rs b/src/options.rs index 27f087bf..634a478f 100644 --- a/src/options.rs +++ b/src/options.rs @@ -357,8 +357,6 @@ fn get_temperature( /// Yes, this function gets whether to show average CPU (true) or not (false) fn get_show_average_cpu(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { - // FIXME: Update the demo config file and default config files! Need to remove - // old options and change to hide_avg_cpu option. if matches.is_present("HIDE_AVG_CPU") { return false; } else if let Some(flags) = &config.flags {