summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2020-11-18 01:28:53 -0500
committerGitHub <noreply@github.com>2020-11-18 01:28:53 -0500
commit669b245367d194b7f4f7a12fe29573fcd9ca4e4e (patch)
tree5d4822751b16bf4a6b46a2995348ca90b6866152
parente43456207be0757fbb9c05f93a169063faf673b7 (diff)
feature: Add collapsible tree entries (#304)
Adds collapsible trees to the tree mode for processes. These can be toggled via the + or - keys and the mouse by clicking on a selected entry.
-rw-r--r--.vscode/settings.json1
-rw-r--r--CHANGELOG.md8
-rw-r--r--README.md9
-rw-r--r--src/app.rs129
-rw-r--r--src/app/states.rs2
-rw-r--r--src/canvas.rs7
-rw-r--r--src/canvas/widgets/process_table.rs2
-rw-r--r--src/clap.rs16
-rw-r--r--src/constants.rs11
-rw-r--r--src/data_conversion.rs223
-rw-r--r--src/lib.rs19
-rw-r--r--src/options.rs5
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
diff --git a/README.md b/README.md
index afe7df74..e3346e4c 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/src/app.rs b/src/app.rs
index d80a206c..9f8fb577 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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 &current_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 o