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. --- 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 - 17 files changed, 750 insertions(+), 946 deletions(-) (limited to 'src') 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