diff options
-rw-r--r-- | .vscode/settings.json | 1 | ||||
-rw-r--r-- | CHANGELOG.md | 8 | ||||
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | src/app.rs | 129 | ||||
-rw-r--r-- | src/app/states.rs | 2 | ||||
-rw-r--r-- | src/canvas.rs | 7 | ||||
-rw-r--r-- | src/canvas/widgets/process_table.rs | 2 | ||||
-rw-r--r-- | src/clap.rs | 16 | ||||
-rw-r--r-- | src/constants.rs | 11 | ||||
-rw-r--r-- | src/data_conversion.rs | 223 | ||||
-rw-r--r-- | src/lib.rs | 19 | ||||
-rw-r--r-- | src/options.rs | 5 |
12 files changed, 309 insertions, 123 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index f5f60e4f..0ebd8ca7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -48,6 +48,7 @@ "cvars", "czvf", "denylist", + "eselect", "fedoracentos", "fpath", "fract", diff --git a/CHANGELOG.md b/CHANGELOG.md index eedb7ed2..8c25a651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.1] - Unreleased + +### Features + +### Changes + +### Bug Fixes + ## [0.5.0] - Unreleased ### Features @@ -137,6 +137,7 @@ sudo dnf install bottom ### Gentoo Available in [dm9pZCAq overlay](https://github.com/gentoo-mirror/dm9pZCAq) + ```bash sudo eselect repository enable dm9pZCAq sudo emerge --sync dm9pZCAq @@ -232,7 +233,6 @@ Run using `btm`. --hide_time Completely hides the time scaling. -k, --kelvin Sets the temperature type to Kelvin. -l, --left_legend Puts the CPU chart legend to the left side. - --no_write Disables writing to the config file. -r, --rate <MS> Sets a refresh rate in ms. -R, --regex Enables regex by default. -d, --time_delta <MS> The amount in ms changed upon zooming. @@ -401,6 +401,12 @@ Note that the `and` operator takes precedence over the `or` operator. | ------ | --------------------------------------------------------------------- | | Scroll | Scrolling over an CPU core/average shows only that entry on the chart | +#### Process bindings + +| | | +| ----- | --------------------------------------------------------------------------------------------------- | +| Click | If in tree mode and you click on a selected entry, it toggles whether the branch is expanded or not | + ## Features As yet _another_ process/system visualization and management application, bottom supports the typical features: @@ -742,6 +748,7 @@ Thanks to all contributors ([emoji key](https://allcontributors.org/docs/en/emoj <!-- markdownlint-enable --> <!-- prettier-ignore-end --> + <!-- ALL-CONTRIBUTORS-LIST:END --> ## Thanks @@ -697,7 +697,7 @@ impl App { .process_search_state .search_state .is_enabled - && proc_widget_state.get_cursor_position() + && proc_widget_state.get_search_cursor_position() < proc_widget_state .process_search_state .search_state @@ -708,13 +708,13 @@ impl App { .process_search_state .search_state .current_search_query - .remove(proc_widget_state.get_cursor_position()); + .remove(proc_widget_state.get_search_cursor_position()); proc_widget_state .process_search_state .search_state .grapheme_cursor = GraphemeCursor::new( - proc_widget_state.get_cursor_position(), + proc_widget_state.get_search_cursor_position(), proc_widget_state .process_search_state .search_state @@ -746,21 +746,22 @@ impl App { .process_search_state .search_state .is_enabled - && proc_widget_state.get_cursor_position() > 0 + && proc_widget_state.get_search_cursor_position() > 0 { - proc_widget_state.search_walk_back(proc_widget_state.get_cursor_position()); + proc_widget_state + .search_walk_back(proc_widget_state.get_search_cursor_position()); let removed_char = proc_widget_state .process_search_state .search_state .current_search_query - .remove(proc_widget_state.get_cursor_position()); + .remove(proc_widget_state.get_search_cursor_position()); proc_widget_state .process_search_state .search_state .grapheme_cursor = GraphemeCursor::new( - proc_widget_state.get_cursor_position(), + proc_widget_state.get_search_cursor_position(), proc_widget_state .process_search_state .search_state @@ -838,15 +839,15 @@ impl App { .get_mut_widget_state(self.current_widget.widget_id - 1) { if is_in_search_widget { - let prev_cursor = proc_widget_state.get_cursor_position(); + let prev_cursor = proc_widget_state.get_search_cursor_position(); proc_widget_state - .search_walk_back(proc_widget_state.get_cursor_position()); - if proc_widget_state.get_cursor_position() < prev_cursor { + .search_walk_back(proc_widget_state.get_search_cursor_position()); + if proc_widget_state.get_search_cursor_position() < prev_cursor { let str_slice = &proc_widget_state .process_search_state .search_state .current_search_query - [proc_widget_state.get_cursor_position()..prev_cursor]; + [proc_widget_state.get_search_cursor_position()..prev_cursor]; proc_widget_state .process_search_state .search_state @@ -905,15 +906,16 @@ impl App { .get_mut_widget_state(self.current_widget.widget_id - 1) { if is_in_search_widget { - let prev_cursor = proc_widget_state.get_cursor_position(); - proc_widget_state - .search_walk_forward(proc_widget_state.get_cursor_position()); - if proc_widget_state.get_cursor_position() > prev_cursor { + let prev_cursor = proc_widget_state.get_search_cursor_position(); + proc_widget_state.search_walk_forward( + proc_widget_state.get_search_cursor_position(), + ); + if proc_widget_state.get_search_cursor_position() > prev_cursor { let str_slice = &proc_widget_state .process_search_state .search_state .current_search_query - [prev_cursor..proc_widget_state.get_cursor_position()]; + [prev_cursor..proc_widget_state.get_search_cursor_position()]; proc_widget_state .process_search_state .search_state @@ -1124,13 +1126,13 @@ impl App { .process_search_state .search_state .current_search_query - .insert(proc_widget_state.get_cursor_position(), caught_char); + .insert(proc_widget_state.get_search_cursor_position(), caught_char); proc_widget_state .process_search_state .search_state .grapheme_cursor = GraphemeCursor::new( - proc_widget_state.get_cursor_position(), + proc_widget_state.get_search_cursor_position(), proc_widget_state .process_search_state .search_state @@ -1139,7 +1141,7 @@ impl App { true, ); proc_widget_state - .search_walk_forward(proc_widget_state.get_cursor_position()); + .search_walk_forward(proc_widget_state.get_search_cursor_position()); proc_widget_state .process_search_state @@ -1371,8 +1373,8 @@ impl App { 'K' | 'W' => self.move_widget_selection(&WidgetDirection::Up), 'J' | 'S' => self.move_widget_selection(&WidgetDirection::Down), 't' => self.toggle_tree_mode(), - '+' => self.zoom_in(), - '-' => self.zoom_out(), + '+' => self.on_plus(), + '-' => self.on_minus(), '=' => self.reset_zoom(), 'e' => self.toggle_expand_widget(), 's' => self.toggle_sort(), @@ -2058,7 +2060,9 @@ impl App { pub fn decrement_position_count(&mut self) { if !self.ignore_normal_keybinds() { match self.current_widget.widget_type { - BottomWidgetType::Proc => self.increment_process_position(-1), + BottomWidgetType::Proc => { + self.increment_process_position(-1); + } BottomWidgetType::ProcSort => self.increment_process_sort_position(-1), BottomWidgetType::Temp => self.increment_temp_position(-1), BottomWidgetType::Disk => self.increment_disk_position(-1), @@ -2071,7 +2075,9 @@ impl App { pub fn increment_position_count(&mut self) { if !self.ignore_normal_keybinds() { match self.current_widget.widget_type { - BottomWidgetType::Proc => self.increment_process_position(1), + BottomWidgetType::Proc => { + self.increment_process_position(1); + } BottomWidgetType::ProcSort => self.increment_process_sort_position(1), BottomWidgetType::Temp => self.increment_temp_position(1), BottomWidgetType::Disk => self.increment_disk_position(1), @@ -2128,7 +2134,8 @@ impl App { } } - fn increment_process_position(&mut self, num_to_change_by: i64) { + /// Returns the new position. + fn increment_process_position(&mut self, num_to_change_by: i64) -> Option<usize> { if let Some(proc_widget_state) = self .proc_state .get_mut_widget_state(self.current_widget.widget_id) @@ -2144,6 +2151,8 @@ impl App { { proc_widget_state.scroll_state.current_scroll_position = (current_posn as i64 + num_to_change_by) as usize; + } else { + return None; } } @@ -2152,7 +2161,11 @@ impl App { } else { proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Down; } + + return Some(proc_widget_state.scroll_state.current_scroll_position); } + + None } fn increment_temp_position(&mut self, num_to_change_by: i64) { @@ -2245,6 +2258,53 @@ impl App { } } + fn on_plus(&mut self) { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + // Toggle collapsing if tree + self.toggle_collapsing_process_branch(); + } else { + self.zoom_in(); + } + } + + fn on_minus(&mut self) { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + // Toggle collapsing if tree + self.toggle_collapsing_process_branch(); + } else { + self.zoom_out(); + } + } + + fn toggle_collapsing_process_branch(&mut self) { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + let current_posn = proc_widget_state.scroll_state.current_scroll_position; + + if let Some(displayed_process_list) = self + .canvas_data + .finalized_process_data_map + .get(&self.current_widget.widget_id) + { + if let Some(corresponding_process) = displayed_process_list.get(current_posn) { + let corresponding_pid = corresponding_process.pid; + + if let Some(process_data) = self + .canvas_data + .single_process_data + .get_mut(&corresponding_pid) + { + process_data.is_collapsed_entry = !process_data.is_collapsed_entry; + self.proc_state.force_update = Some(self.current_widget.widget_id); + } + } + } + } + } + fn zoom_out(&mut self) { match self.current_widget.widget_type { BottomWidgetType::Cpu => { @@ -2464,11 +2524,12 @@ impl App { // Pretty dead simple - iterate through the widget map and go to the widget where the click // is within. - // TODO: [MOUSE] double click functionality...? // TODO: [REFACTOR] might want to refactor this, it's ugly as sin. // TODO: [REFACTOR] Might wanna refactor ALL state things in general, currently everything // is grouped up as an app state. We should separate stuff like event state and gui state and etc. + // TODO: [MOUSE] double click functionality...? We would do this above all other actions and SC if needed. + // Short circuit if we're in basic table... we might have to handle the basic table arrow // case here... if let Some(bt) = &mut self.basic_table_widget_state { @@ -2620,9 +2681,25 @@ impl App { if let Some(visual_index) = proc_widget_state.scroll_state.table_state.selected() { - self.increment_process_position( + // If in tree mode, also check to see if this click is on + // the same entry as the already selected one - if it is, + // then we minimize. + + let previous_scroll_position = + proc_widget_state.scroll_state.current_scroll_position; + let is_tree_mode = proc_widget_state.is_tree_mode; + + let new_position = self.increment_process_position( offset_clicked_entry as i64 - visual_index as i64, ); + + if is_tree_mode { + if let Some(new_position) = new_position { + if previous_scroll_position == new_position { + self.toggle_collapsing_process_branch(); + } + } + } } } } diff --git a/src/app/states.rs b/src/app/states.rs index dea49b18..12037907 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -488,7 +488,7 @@ impl ProcWidgetState { } } - pub fn get_cursor_position(&self) -> usize { + pub fn get_search_cursor_position(&self) -> usize { self.process_search_state .search_state .grapheme_cursor diff --git a/src/canvas.rs b/src/canvas.rs index 626f4f7f..7af29f33 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -25,6 +25,7 @@ use crate::{ options::Config, utils::error, utils::error::BottomError, + Pid, }; mod canvas_colours; @@ -46,9 +47,9 @@ pub struct DisplayableData { pub network_data_tx: Vec<Point>, pub disk_data: Vec<Vec<String>>, pub temp_sensor_data: Vec<Vec<String>>, - pub single_process_data: Vec<ConvertedProcessData>, // Contains single process data - pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed - pub stringified_process_data_map: HashMap<u64, Vec<(Vec<(String, Option<String>)>, bool)>>, // Represents the row and whether it is disabled + pub single_process_data: HashMap<Pid, ConvertedProcessData>, // Contains single process data, key is PID + pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed, key is the widget ID. + pub stringified_process_data_map: HashMap<u64, Vec<(Vec<(String, Option<String>)>, bool)>>, // Represents the row and whether it is disabled, key is the widget ID pub mem_label_percent: String, pub swap_label_percent: String, pub mem_label_frac: String, diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index 0ee25b1f..22e54c16 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -506,7 +506,7 @@ impl ProcessTableWidget for Painter { let search_title = "> "; let num_chars_for_text = search_title.len(); - let cursor_position = proc_widget_state.get_cursor_position(); + let cursor_position = proc_widget_state.get_search_cursor_position(); let current_cursor_position = proc_widget_state.get_char_cursor_position(); let start_position: usize = get_search_start_position( diff --git a/src/clap.rs b/src/clap.rs index 5f6d7667..70972d62 100644 --- a/src/clap.rs +++ b/src/clap.rs @@ -143,13 +143,13 @@ Completely hides the time scaling from being shown.\n\n", "\ Puts the CPU chart legend to the left side rather than the right side.\n\n", ); - let no_write = Arg::with_name("no_write") - .long("no_write") - .help("Disables writing to the config file.") - .long_help( - "\ -Disables config changes in-app from writing to the config file.", - ); + // let no_write = Arg::with_name("no_write") + // .long("no_write") + // .help("Disables writing to the config file.") + // .long_help( + // "\ + // Disables config changes in-app from writing to the config file.", + // ); let regex = Arg::with_name("regex") .short("R") .long("regex") @@ -355,7 +355,7 @@ The minimum is 1s (1000), and defaults to 15s (15000).\n\n\n", .arg(hide_table_gap) .arg(hide_time) .arg(left_legend) - .arg(no_write) + // .arg(no_write) .arg(rate) .arg(regex) .arg(time_delta) diff --git a/src/constants.rs b/src/constants.rs index 91e9419c..0bccec77 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -158,7 +158,6 @@ lazy_static! { // }; } -// FIXME: [HELP] I wanna update this before release... it's missing mouse too. // Help text pub const HELP_CONTENTS_TEXT: [&str; 8] = [ "Press the corresponding numbers to jump to the section, or scroll:", @@ -171,7 +170,9 @@ pub const HELP_CONTENTS_TEXT: [&str; 8] = [ "7 - Basic memory widget", ]; -pub const GENERAL_HELP_TEXT: [&str; 29] = [ +// TODO [Help]: Search in help? +// TODO [Help]: Move to using tables for easier formatting? +pub const GENERAL_HELP_TEXT: [&str; 30] = [ "1 - General", "q, Ctrl-c Quit", "Esc Close dialog windows, search, widgets, or exit expanded mode", @@ -201,6 +202,7 @@ pub const GENERAL_HELP_TEXT: [&str; 29] = [ "- Zoom out on chart (increase time range)", "= Reset zoom", "Mouse scroll Scroll through the tables or zoom in/out of charts by scrolling up/down", + "Mouse click Selects the clicked widget, table entry, dialog option, or tab", ]; pub const CPU_HELP_TEXT: [&str; 2] = [ @@ -208,9 +210,7 @@ pub const CPU_HELP_TEXT: [&str; 2] = [ "Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart", ]; -// TODO [Help]: Search in help? -// TODO [Help]: Move to using tables for easier formatting? -pub const PROCESS_HELP_TEXT: [&str; 13] = [ +pub const PROCESS_HELP_TEXT: [&str; 14] = [ "3 - Process widget", "dd Kill the selected process", "c Sort by CPU usage, press again to reverse sorting order", @@ -224,6 +224,7 @@ pub const PROCESS_HELP_TEXT: [&str; 13] = [ "I Invert current sort", "% Toggle between values and percentages for memory usage", "t, F5 Toggle tree mode", + "+, -, click Collapse/expand a branch while in tree mode", ]; pub const SEARCH_HELP_TEXT: [&str; 46] = [ diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 3b83d935..5bf31edf 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -7,7 +7,7 @@ use crate::{ }; use data_harvester::processes::ProcessSorting; use indexmap::IndexSet; -use std::collections::{HashMap, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; /// Point is of time, data type Point = (f64, f64); @@ -66,6 +66,8 @@ pub struct ConvertedProcessData { pub process_description_prefix: Option<String>, /// Whether to mark this process entry as disabled (mostly for tree mode). pub is_disabled_entry: bool, + /// Whether this entry is collapsed, hiding all its children (for tree mode). + pub is_collapsed_entry: bool, } #[derive(Clone, Default, Debug)] @@ -194,19 +196,17 @@ pub fn convert_cpu_data_points( for (itx, cpu) in data.cpu_data.iter().enumerate() { // Check if the vector exists yet if cpu_data_vector.len() <= itx { - let mut new_cpu_data = ConvertedCpuData::default(); - new_cpu_data.cpu_name = if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx) - { - if let Some(cpu_count) = cpu_harvest.cpu_count { - format!("{}{}", cpu_harvest.cpu_prefix, cpu_count) + let new_cpu_data = ConvertedCpuData { + cpu_name: if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx) { + if let Some(cpu_count) = cpu_harvest.cpu_count { + format!("{}{}", cpu_harvest.cpu_prefix, cpu_count) + } else { + cpu_harvest.cpu_prefix.to_string() + } } else { - cpu_harvest.cpu_prefix.to_string() - } - } else { - String::default() - }; - new_cpu_data.short_cpu_name = - if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx) { + String::default() + }, + short_cpu_name: if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx) { if let Some(cpu_count) = cpu_harvest.cpu_count { cpu_count.to_string() } else { @@ -214,7 +214,10 @@ pub fn convert_cpu_data_points( } } else { String::default() - }; + }, + ..ConvertedCpuData::default() + }; + cpu_data_vector.push(new_cpu_data); } @@ -426,55 +429,120 @@ pub enum ProcessNamingType { Path, } +/// Because we needed to UPDATE data entries rather than REPLACING entries, we instead update +/// the existing vector. pub fn convert_process_data( current_data: &data_farmer::DataCollection, -) -> Vec<ConvertedProcessData> { + existing_converted_process_data: &mut HashMap<Pid, ConvertedProcessData>, +) { // TODO [THREAD]: Thread highlighting and hiding support // For macOS see https://github.com/hishamhm/htop/pull/848/files - 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); + let mut complete_pid_set: HashSet<Pid> = + existing_converted_process_data.keys().copied().collect(); - 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 - ); + for process in ¤t_data.process_harvest { + 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); - ConvertedProcessData { - pid: process.pid, - ppid: process.parent_pid, - is_thread: None, - name: process.name.to_string(), - command: process.command.to_string(), - cpu_percent_usage: process.cpu_usage_percent, - mem_percent_usage: process.mem_usage_percent, - mem_usage_bytes: process.mem_usage_bytes, - mem_usage_str: get_exact_byte_values(process.mem_usage_bytes, false), - 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_state: process.process_state.to_owned(), - process_char: process.process_state_char, - process_description_prefix: None, - is_disabled_entry: 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 + ); + + if let Some(process_entry) = existing_converted_process_data.get_mut(&process.pid) { + complete_pid_set.remove(&process.pid); + + // Very dumb way to see if there's PID reuse... + if process_entry.ppid == process.parent_pid { + process_entry.name = process.name.to_string(); + process_entry.command = process.command.to_string(); + process_entry.cpu_percent_usage = process.cpu_usage_percent; + process_entry.mem_percent_usage = process.mem_usage_percent; + process_entry.mem_usage_bytes = process.mem_usage_bytes; + process_entry.mem_usage_str = get_exact_byte_values(process.mem_usage_bytes, false); + process_entry.group_pids = vec![process.pid]; + process_entry.read_per_sec = read_per_sec; + process_entry.write_per_sec = write_per_sec; + process_entry.total_read = total_read; + process_entry.total_write = total_write; + process_entry.rps_f64 = process.read_bytes_per_sec as f64; + process_entry.wps_f64 = process.write_bytes_per_sec as f64; + process_entry.tr_f64 = process.total_read_bytes as f64; + process_entry.tw_f64 = process.total_write_bytes as f64; + process_entry.process_state = process.process_state.to_owned(); + process_entry.process_char = process.process_state_char; + process_entry.process_description_prefix = None; + process_entry.is_disabled_entry = false; + } else { + // ...I hate that I can't combine if let and an if statement in one line... + *process_entry = ConvertedProcessData { + pid: process.pid, + ppid: process.parent_pid, + is_thread: None, + name: process.name.to_string(), + command: process.command.to_string(), + cpu_percent_usage: process.cpu_usage_percent, + mem_percent_usage: process.mem_usage_percent, + mem_usage_bytes: process.mem_usage_bytes, + mem_usage_str: get_exact_byte_values(process.mem_usage_bytes, false), + 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_state: process.process_state.to_owned(), + process_char: process.process_state_char, + process_description_prefix: None, + is_disabled_entry: false, + is_collapsed_entry: false, + }; } - }) - .collect::<Vec<_>>() + } else { + existing_converted_process_data.insert( + process.pid, + ConvertedProcessData { + pid: process.pid, + ppid: process.parent_pid, + is_thread: None, + name: process.name.to_string(), + command: process.command.to_string(), + cpu_percent_usage: process.cpu_usage_percent, + mem_percent_usage: process.mem_usage_percent, + mem_usage_bytes: process.mem_usage_bytes, + mem_usage_str: get_exact_byte_values(process.mem_usage_bytes, false), + 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_state: process.process_state.to_owned(), + process_char: process.process_state_char, + process_description_prefix: None, + is_disabled_entry: false, + is_collapsed_entry: false, + }, + ); + } + } + + // Now clean up any spare entries that weren't visited, to avoid clutter: + complete_pid_set.iter().for_each(|pid| { + existing_converted_process_data.remove(pid); + }) } const BRANCH_ENDING: char = '└'; @@ -483,32 +551,36 @@ const BRANCH_SPLIT: char = '├'; const BRANCH_HORIZONTAL: char = '─'; pub fn tree_process_data( - single_process_data: &[ConvertedProcessData], is_using_command: bool, - sort_type: &ProcessSorting, is_sort_descending: bool, + filtered_process_data: &[ConvertedProcessData], is_using_command: bool, + sorting_type: &ProcessSorting, is_sort_descending: bool, ) -> Vec<ConvertedProcessData> { - // FIXME: [TREE] Allow for collapsing entries. // TODO: [TREE] Option to sort usage by total branch usage or individual value usage? // Let's first build up a (really terrible) parent -> child mapping... // At the same time, let's make a mapping of PID -> process data! let mut parent_child_mapping: HashMap<Pid, IndexSet<Pid>> = HashMap::default(); - let mut pid_process_mapping: HashMap<Pid, &ConvertedProcessData> = HashMap::default(); + let mut pid_process_mapping: HashMap<Pid, &ConvertedProcessData> = HashMap::default(); // We actually already have this stored, but it's unfiltered... oh well. let mut orphan_set: IndexSet<Pid> = IndexSet::new(); + let mut collapsed_set: IndexSet<Pid> = IndexSet::new(); - single_process_data.iter().for_each(|process| { + filtered_process_data.iter().for_each(|process| { if let Some(ppid) = process.ppid { orphan_set.insert(ppid); } orphan_set.insert(process.pid); }); - single_process_data.iter().for_each(|process| { + filtered_process_data.iter().for_each(|process| { // Create a mapping for the process if it DNE. parent_child_mapping .entry(process.pid) .or_insert_with(IndexSet::new); pid_process_mapping.insert(process.pid, process); + if process.is_collapsed_entry { + collapsed_set.insert(process.pid); + } + // Insert its mapping to the process' parent if needed (create if it DNE). if let Some(ppid) = process.ppid { orphan_set.remove(&process.pid); @@ -521,8 +593,8 @@ pub fn tree_process_data( // Keep only orphans, or promote children of orphans to a top-level orphan // if their parents DNE in our pid to process mapping... - #[allow(clippy::redundant_clone)] - orphan_set.clone().iter().for_each(|pid |