From cc66f1fcaea6d31581efea7dfc1b0029e4754f49 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sat, 20 Nov 2021 22:45:49 -0500 Subject: refactor: mostly add back tree mode for process Mouse control on collapse is not working yet, need to do some work internally first. --- Cargo.lock | 48 +- Cargo.toml | 6 +- docs/content/configuration/command-line-flags.md | 2 - sample_configs/default_config.toml | 3 - sample_configs/demo_config.toml | 2 - src/app.rs | 12 +- src/app/data_farmer.rs | 151 ++-- src/app/data_harvester.rs | 2 +- src/app/data_harvester/processes.rs | 43 +- src/app/data_harvester/processes/linux.rs | 3 +- src/app/data_harvester/processes/macos.rs | 1 - src/app/data_harvester/processes/windows.rs | 1 - src/app/layout_manager.rs | 2 +- src/app/widgets/base/sort_text_table.rs | 7 +- src/app/widgets/base/text_table.rs | 18 +- src/app/widgets/bottom_widgets/process.rs | 836 ++++++++++++++------- src/app/widgets/dialogs/help.rs | 2 +- src/canvas.rs | 2 +- src/clap.rs | 99 --- src/constants.rs | 8 +- src/data_conversion.rs | 505 +------------ src/options.rs | 4 - tests/arg_tests.rs | 42 -- tests/invalid_config_tests.rs | 22 +- .../invalid_default_widget_count.toml | 3 - .../invalid_configs/lone_default_widget_count.toml | 2 - 26 files changed, 788 insertions(+), 1038 deletions(-) delete mode 100644 tests/invalid_configs/invalid_default_widget_count.toml delete mode 100644 tests/invalid_configs/lone_default_widget_count.toml diff --git a/Cargo.lock b/Cargo.lock index 7d6f91c7..ec194f46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,7 +239,7 @@ dependencies = [ "cargo-husky", "cfg-if", "clap", - "crossterm", + "crossterm 0.22.1", "ctrlc", "dirs", "enum_dispatch", @@ -247,7 +247,6 @@ dependencies = [ "float-ord", "futures", "futures-timer", - "fxhash", "heim", "indexmap", "indextree", @@ -258,6 +257,7 @@ dependencies = [ "predicates 1.0.8", "procfs", "regex", + "rustc-hash", "serde", "smol", "sysinfo", @@ -434,7 +434,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" dependencies = [ "bitflags", - "crossterm_winapi", + "crossterm_winapi 0.8.0", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c" +dependencies = [ + "bitflags", + "crossterm_winapi 0.9.0", "libc", "mio", "parking_lot", @@ -452,6 +468,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + [[package]] name = "ctrlc" version = "3.2.1" @@ -684,15 +709,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "getrandom" version = "0.2.3" @@ -1306,6 +1322,12 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1521,7 +1543,7 @@ checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23" dependencies = [ "bitflags", "cassowary", - "crossterm", + "crossterm 0.20.0", "unicode-segmentation", "unicode-width", ] diff --git a/Cargo.toml b/Cargo.toml index 20a936aa..713228cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,6 @@ doc = false [profile.release] debug = 0 lto = true -# debug = true -# lto = false opt-level = 3 codegen-units = 1 @@ -36,7 +34,7 @@ default = ["fern", "log", "battery"] [dependencies] anyhow = "1.0.40" backtrace = "0.3.59" -crossterm = "0.20.0" +crossterm = "0.22.1" ctrlc = { version = "3.1.9", features = ["termination"] } clap = "2.33" cfg-if = "1.0" @@ -45,12 +43,12 @@ enum_dispatch = "0.3.7" float-ord = "0.3.2" futures = "0.3.14" futures-timer = "3.0.2" -fxhash = "0.2.1" indexmap = "1.6.2" indextree = "4.3.1" itertools = "0.10.0" once_cell = "1.5.2" regex = "1.5.4" +rustc-hash = "1.1.0" serde = { version = "1.0.125", features = ["derive"] } # Sysinfo is still used in Linux for the ProcessStatus sysinfo = "0.18.2" diff --git a/docs/content/configuration/command-line-flags.md b/docs/content/configuration/command-line-flags.md index b5d6d3d3..25d0c1b8 100644 --- a/docs/content/configuration/command-line-flags.md +++ b/docs/content/configuration/command-line-flags.md @@ -17,8 +17,6 @@ The following flags can be provided to bottom in the command line to change the | `-C, --config ` | Sets the location of the config file. | | `-u, --current_usage` | Sets process CPU% to be based on current CPU%. | | `-t, --default_time_value ` | Default time value for graphs in ms. | -| `--default_widget_count ` | Sets the n'th selected widget type as the default. | -| `--default_widget_type ` | Sets the default widget type, use --help for more info. | | `--disable_advanced_kill` | Hides advanced options to stop a process on Unix-like systems. | | `--disable_click` | Disables mouse clicks. | | `-m, --dot_marker` | Uses a dot marker for graphs. | diff --git a/sample_configs/default_config.toml b/sample_configs/default_config.toml index 1e2d56a4..556c131d 100644 --- a/sample_configs/default_config.toml +++ b/sample_configs/default_config.toml @@ -38,9 +38,6 @@ #time_delta = 15000 # Hides the time scale. #hide_time = false -# Override layout default widget -#default_widget_type = "proc" -#default_widget_count = 1 # Use basic mode #basic = false # Use the old network legend style diff --git a/sample_configs/demo_config.toml b/sample_configs/demo_config.toml index e6c5cf71..0874779b 100644 --- a/sample_configs/demo_config.toml +++ b/sample_configs/demo_config.toml @@ -11,5 +11,3 @@ group_processes = false case_sensitive = false whole_word = false regex = true -default_widget_type = "cpu" -default_widget_count = 1 diff --git a/src/app.rs b/src/app.rs index d81b88fa..40577e4e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,8 +10,8 @@ pub mod widgets; use std::time::Instant; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent}; -use fxhash::FxHashMap; use indextree::{Arena, NodeId}; +use rustc_hash::FxHashMap; pub use data_farmer::*; use data_harvester::temperature; @@ -474,7 +474,7 @@ impl AppState { } #[cfg(target_family = "unix")] - pub fn on_number(&mut self, number_char: char) { + fn on_number(&mut self, number_char: char) { if self.delete_dialog_state.is_showing_dd { if self .delete_dialog_state @@ -507,7 +507,7 @@ impl AppState { } } - pub fn on_left_key(&mut self) { + fn on_left_key(&mut self) { // if !self.is_in_dialog() { // match self.current_widget.widget_type { // BottomWidgetType::ProcSearch => { @@ -566,7 +566,7 @@ impl AppState { // } } - pub fn on_right_key(&mut self) { + fn on_right_key(&mut self) { // if !self.is_in_dialog() { // match self.current_widget.widget_type { // BottomWidgetType::ProcSearch => { @@ -626,7 +626,7 @@ impl AppState { // } } - pub fn start_killing_process(&mut self) { + fn start_killing_process(&mut self) { todo!() // if let Some(proc_widget_state) = self @@ -666,7 +666,7 @@ impl AppState { // } } - pub fn kill_highlighted_process(&mut self) -> Result<()> { + fn kill_highlighted_process(&mut self) -> Result<()> { // if let BottomWidgetType::Proc = self.current_widget.widget_type { // if let Some(current_selected_processes) = &self.to_delete_process_list { // #[cfg(target_family = "unix")] diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 83b53d29..75edc3ea 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; /// In charge of cleaning, processing, and managing data. I couldn't think of /// a better name for the file. Since I called data collection "harvesting", /// then this is the farmer I guess. @@ -13,6 +14,7 @@ /// memory usage and higher CPU usage - you will be trying to process more and /// more points as this is used! use once_cell::sync::Lazy; +use rustc_hash::FxHashMap; use std::{collections::HashMap, time::Instant, vec::Vec}; @@ -26,6 +28,8 @@ use crate::{ }; use regex::Regex; +use super::data_harvester::processes::ProcessHarvest; + #[derive(Clone, Debug, Default)] pub struct TimedData { pub rx_data: f64, @@ -36,6 +40,90 @@ pub struct TimedData { pub swap_data: Option, } +#[derive(Clone, Debug, Default)] +pub struct ProcessData { + /// A PID to process data map. + pub process_harvest: FxHashMap, + + /// A mapping from a process name to any PID with that name. + pub process_name_pid_map: HashMap>, + + /// A mapping from a process command to any PID with that name. + pub process_cmd_pid_map: HashMap>, + + /// A mapping between a process PID to any children process PIDs. + pub process_parent_mapping: FxHashMap>, + + /// PIDs corresponding to processes that have no parents. + pub orphan_pids: Vec, +} + +impl ProcessData { + fn ingest(&mut self, list_of_processes: Vec) { + // TODO: [Optimization] Probably more efficient to all of this in the data collection step, but it's fine for now. + self.process_name_pid_map.clear(); + self.process_cmd_pid_map.clear(); + self.process_parent_mapping.clear(); + + // Reverse as otherwise the pid mappings are in the wrong order. + list_of_processes.iter().rev().for_each(|process_harvest| { + if let Some(entry) = self.process_name_pid_map.get_mut(&process_harvest.name) { + entry.push(process_harvest.pid); + } else { + self.process_name_pid_map + .insert(process_harvest.name.to_string(), vec![process_harvest.pid]); + } + + if let Some(entry) = self.process_cmd_pid_map.get_mut(&process_harvest.command) { + entry.push(process_harvest.pid); + } else { + self.process_cmd_pid_map.insert( + process_harvest.command.to_string(), + vec![process_harvest.pid], + ); + } + + if let Some(parent_pid) = process_harvest.parent_pid { + if let Some(entry) = self.process_parent_mapping.get_mut(&parent_pid) { + entry.push(process_harvest.pid); + } else { + self.process_parent_mapping + .insert(parent_pid, vec![process_harvest.pid]); + } + } + }); + + self.process_name_pid_map.shrink_to_fit(); + self.process_cmd_pid_map.shrink_to_fit(); + self.process_parent_mapping.shrink_to_fit(); + + let process_pid_map = list_of_processes + .into_iter() + .map(|process| (process.pid, process)) + .collect(); + self.process_harvest = process_pid_map; + + // This also needs a quick sort + reverse to be in the correct order. + self.orphan_pids = self + .process_harvest + .iter() + .filter_map(|(pid, process_harvest)| { + if let Some(parent_pid) = process_harvest.parent_pid { + if self.process_harvest.contains_key(&parent_pid) { + None + } else { + Some(*pid) + } + } else { + Some(*pid) + } + }) + .sorted() + .rev() + .collect(); + } +} + /// AppCollection represents the pooled data stored within the main app /// thread. Basically stores a (occasionally cleaned) record of the data /// collected, and what is needed to convert into a displayable form. @@ -48,16 +136,14 @@ pub struct TimedData { /// not the data collector. #[derive(Clone, Debug)] pub struct DataCollection { - pub current_instant: Instant, + pub current_instant: Instant, // TODO: [Refactor] Can I get rid of this? If I could, then I could just use #[derive(Default)] too! pub timed_data_vec: Vec<(Instant, TimedData)>, pub network_harvest: network::NetworkHarvest, pub memory_harvest: memory::MemHarvest, pub swap_harvest: memory::MemHarvest, pub cpu_harvest: cpu::CpuHarvest, pub load_avg_harvest: cpu::LoadAvgHarvest, - pub process_harvest: Vec, - pub process_name_pid_map: HashMap>, - pub process_cmd_pid_map: HashMap>, + pub process_data: ProcessData, pub disk_harvest: Vec, pub io_harvest: disks::IoHarvest, pub io_labels_and_prev: Vec<((u64, u64), (u64, u64))>, @@ -71,22 +157,20 @@ impl Default for DataCollection { fn default() -> Self { DataCollection { current_instant: Instant::now(), - timed_data_vec: Vec::default(), - network_harvest: network::NetworkHarvest::default(), - memory_harvest: memory::MemHarvest::default(), - swap_harvest: memory::MemHarvest::default(), - cpu_harvest: cpu::CpuHarvest::default(), - load_avg_harvest: cpu::LoadAvgHarvest::default(), - process_harvest: Vec::default(), - process_name_pid_map: HashMap::default(), - process_cmd_pid_map: HashMap::default(), - disk_harvest: Vec::default(), - io_harvest: disks::IoHarvest::default(), - io_labels_and_prev: Vec::default(), - io_labels: Vec::default(), - temp_harvest: Vec::default(), + timed_data_vec: Default::default(), + network_harvest: Default::default(), + memory_harvest: Default::default(), + swap_harvest: Default::default(), + cpu_harvest: Default::default(), + load_avg_harvest: Default::default(), + process_data: Default::default(), + disk_harvest: Default::default(), + io_harvest: Default::default(), + io_labels_and_prev: Default::default(), + io_labels: Default::default(), + temp_harvest: Default::default(), #[cfg(feature = "battery")] - battery_harvest: Vec::default(), + battery_harvest: Default::default(), } } } @@ -98,9 +182,7 @@ impl DataCollection { self.memory_harvest = Default::default(); self.swap_harvest = Default::default(); self.cpu_harvest = Default::default(); - self.process_harvest = Default::default(); - self.process_name_pid_map = Default::default(); - self.process_cmd_pid_map = Default::default(); + self.process_data = Default::default(); self.disk_harvest = Default::default(); self.io_harvest = Default::default(); self.io_labels_and_prev = Default::default(); @@ -132,8 +214,6 @@ impl DataCollection { pub fn eat_data(&mut self, harvested_data: Box) { let harvested_time = harvested_data.last_collection_time; - // trace!("Harvested time: {:?}", harvested_time); - // trace!("New current instant: {:?}", self.current_instant); let mut new_entry = TimedData::default(); // Network @@ -316,28 +396,7 @@ impl DataCollection { } fn eat_proc(&mut self, list_of_processes: Vec) { - // TODO: [Optimization] Probably more efficient to do this in the data collection step, but it's fine for now. - self.process_name_pid_map.clear(); - self.process_cmd_pid_map.clear(); - list_of_processes.iter().for_each(|process_harvest| { - if let Some(entry) = self.process_name_pid_map.get_mut(&process_harvest.name) { - entry.push(process_harvest.pid); - } else { - self.process_name_pid_map - .insert(process_harvest.name.to_string(), vec![process_harvest.pid]); - } - - if let Some(entry) = self.process_cmd_pid_map.get_mut(&process_harvest.command) { - entry.push(process_harvest.pid); - } else { - self.process_cmd_pid_map.insert( - process_harvest.command.to_string(), - vec![process_harvest.pid], - ); - } - }); - - self.process_harvest = list_of_processes; + self.process_data.ingest(list_of_processes); } #[cfg(feature = "battery")] diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs index 2672022d..75d5b4b6 100644 --- a/src/app/data_harvester.rs +++ b/src/app/data_harvester.rs @@ -3,7 +3,7 @@ use std::time::Instant; #[cfg(target_os = "linux")] -use fxhash::FxHashMap; +use rustc_hash::FxHashMap; #[cfg(not(target_os = "linux"))] use sysinfo::{System, SystemExt}; diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index 1ee60123..bcbc97e0 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -45,41 +45,10 @@ pub enum ProcessSorting { Count, } -impl std::fmt::Display for ProcessSorting { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match &self { - ProcessSorting::CpuPercent => "CPU%", - ProcessSorting::MemPercent => "Mem%", - ProcessSorting::Mem => "Mem", - ProcessSorting::ReadPerSecond => "R/s", - ProcessSorting::WritePerSecond => "W/s", - ProcessSorting::TotalRead => "T.Read", - ProcessSorting::TotalWrite => "T.Write", - ProcessSorting::State => "State", - ProcessSorting::ProcessName => "Name", - ProcessSorting::Command => "Command", - ProcessSorting::Pid => "PID", - ProcessSorting::Count => "Count", - ProcessSorting::User => "User", - } - ) - } -} - -impl Default for ProcessSorting { - fn default() -> Self { - ProcessSorting::CpuPercent - } -} - #[derive(Debug, Clone, Default)] pub struct ProcessHarvest { pub pid: Pid, pub parent_pid: Option, - pub children_pids: Vec, pub cpu_usage_percent: f64, pub mem_usage_percent: f64, pub mem_usage_bytes: u64, @@ -102,3 +71,15 @@ pub struct ProcessHarvest { #[cfg(target_family = "unix")] pub user: Cow<'static, str>, } + +impl ProcessHarvest { + pub(crate) fn add(&mut self, rhs: &ProcessHarvest) { + self.cpu_usage_percent += rhs.cpu_usage_percent; + self.mem_usage_bytes += rhs.mem_usage_bytes; + self.mem_usage_percent += rhs.mem_usage_percent; + self.read_bytes_per_sec += rhs.read_bytes_per_sec; + self.write_bytes_per_sec += rhs.write_bytes_per_sec; + self.total_read_bytes += rhs.total_read_bytes; + self.total_write_bytes += rhs.total_write_bytes; + } +} diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs index 5d11ca7b..63158297 100644 --- a/src/app/data_harvester/processes/linux.rs +++ b/src/app/data_harvester/processes/linux.rs @@ -11,7 +11,7 @@ use sysinfo::ProcessStatus; use procfs::process::{Process, Stat}; -use fxhash::{FxHashMap, FxHashSet}; +use rustc_hash::{FxHashMap, FxHashSet}; /// Maximum character length of a /proc//stat process name. /// If it's equal or greater, then we instead refer to the command for the name. @@ -203,7 +203,6 @@ fn read_proc( ProcessHarvest { pid: process.pid, parent_pid, - children_pids: vec![], cpu_usage_percent, mem_usage_percent, mem_usage_bytes, diff --git a/src/app/data_harvester/processes/macos.rs b/src/app/data_harvester/processes/macos.rs index d5dffe85..a65f2627 100644 --- a/src/app/data_harvester/processes/macos.rs +++ b/src/app/data_harvester/processes/macos.rs @@ -89,7 +89,6 @@ pub fn get_process_data( process_vector.push(ProcessHarvest { pid: process_val.pid(), parent_pid: process_val.parent(), - children_pids: vec![], name, command, mem_usage_percent: if mem_total_kb > 0 { diff --git a/src/app/data_harvester/processes/windows.rs b/src/app/data_harvester/processes/windows.rs index 763f9cf8..08cde500 100644 --- a/src/app/data_harvester/processes/windows.rs +++ b/src/app/data_harvester/processes/windows.rs @@ -58,7 +58,6 @@ pub fn get_process_data( process_vector.push(ProcessHarvest { pid: process_val.pid(), parent_pid: process_val.parent(), - children_pids: vec![], name, command, mem_usage_percent: if mem_total_kb > 0 { diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 7f0dd663..b5d54480 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -9,8 +9,8 @@ use crate::{ ProcessDefaults, }, }; -use fxhash::FxHashMap; use indextree::{Arena, NodeId}; +use rustc_hash::FxHashMap; use std::cmp::min; use tui::layout::Rect; diff --git a/src/app/widgets/base/sort_text_table.rs b/src/app/widgets/base/sort_text_table.rs index 2c2d9e21..001624a4 100644 --- a/src/app/widgets/base/sort_text_table.rs +++ b/src/app/widgets/base/sort_text_table.rs @@ -288,11 +288,16 @@ where self.table.current_scroll_index() } - /// Returns the current column the table is sorting by. + /// Returns a reference to the current column the table is sorting by. pub fn current_sorting_column(&self) -> &S { &self.table.columns[self.sort_index] } + /// Returns a mutable reference to the current column the table is sorting by. + pub fn current_mut_sorting_column(&mut self) -> &mut S { + &mut self.table.columns[self.sort_index] + } + /// Returns the current column index the table is sorting by. pub fn current_sorting_column_index(&self) -> usize { self.sort_index diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs index d3c9c601..896eda85 100644 --- a/src/app/widgets/base/text_table.rs +++ b/src/app/widgets/base/text_table.rs @@ -39,8 +39,9 @@ pub trait TableColumn { fn set_x_bounds(&mut self, x_bounds: Option<(u16, u16)>); } -pub type TextTableData = Vec, Option>, Option