summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-08-31 16:53:43 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2021-09-05 19:09:11 -0400
commit204b4dc3510b70618741f82a1e2df6c17c6dadda (patch)
tree5c13259b11bee7e044f18bb7a1ca33831d1af651
parentb1889b09344fea9eef98e5c2b9ce3b269dc8234b (diff)
refactor: add back grouping and command
-rw-r--r--src/app.rs2
-rw-r--r--src/app/data_farmer.rs27
-rw-r--r--src/app/widgets/base/sort_text_table.rs28
-rw-r--r--src/app/widgets/base/text_input.rs16
-rw-r--r--src/app/widgets/base/text_table.rs16
-rw-r--r--src/app/widgets/process.rs171
-rw-r--r--src/constants.rs4
7 files changed, 230 insertions, 34 deletions
diff --git a/src/app.rs b/src/app.rs
index fba73c07..91b243f3 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1374,7 +1374,7 @@ impl AppState {
}
pub fn start_killing_process(&mut self) {
- self.reset_multi_tap_keys();
+ todo!()
// if let Some(proc_widget_state) = self
// .proc_state
diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs
index ccf2688e..25d265ab 100644
--- a/src/app/data_farmer.rs
+++ b/src/app/data_farmer.rs
@@ -56,7 +56,8 @@ pub struct DataCollection {
pub cpu_harvest: cpu::CpuHarvest,
pub load_avg_harvest: cpu::LoadAvgHarvest,
pub process_harvest: Vec<processes::ProcessHarvest>,
- pub process_count_mapping: HashMap<String, Pid>,
+ pub process_name_pid_map: HashMap<String, Vec<Pid>>,
+ pub process_cmd_pid_map: HashMap<String, Vec<Pid>>,
pub disk_harvest: Vec<disks::DiskHarvest>,
pub io_harvest: disks::IoHarvest,
pub io_labels_and_prev: Vec<((u64, u64), (u64, u64))>,
@@ -77,7 +78,8 @@ impl Default for DataCollection {
cpu_harvest: cpu::CpuHarvest::default(),
load_avg_harvest: cpu::LoadAvgHarvest::default(),
process_harvest: Vec::default(),
- process_count_mapping: HashMap::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(),
@@ -312,6 +314,27 @@ impl DataCollection {
}
fn eat_proc(&mut self, list_of_processes: Vec<processes::ProcessHarvest>) {
+ // TODO: 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;
}
diff --git a/src/app/widgets/base/sort_text_table.rs b/src/app/widgets/base/sort_text_table.rs
index 2b59f948..ceb7eb09 100644
--- a/src/app/widgets/base/sort_text_table.rs
+++ b/src/app/widgets/base/sort_text_table.rs
@@ -301,8 +301,28 @@ where
&self.table.columns
}
- pub fn set_column(&mut self, column: S, index: usize) {
- self.table.set_column(index, column)
+ pub fn set_column(&mut self, mut column: S, index: usize) {
+ if let Some(old_column) = self.table.columns().get(index) {
+ column.set_sorting_status(old_column.sorting_status());
+ }
+ self.table.set_column(index, column);
+ }
+
+ pub fn add_column(&mut self, column: S, index: usize) {
+ self.table.add_column(index, column);
+ }
+
+ pub fn remove_column(&mut self, index: usize, new_sort_index: Option<usize>) {
+ self.table.remove_column(index);
+
+ // Reset the sort index either a supplied one or a new one if needed.
+ if index == self.sort_index {
+ if let Some(new_sort_index) = new_sort_index {
+ self.set_sort_index(new_sort_index);
+ } else {
+ self.set_sort_index(0);
+ }
+ }
}
pub fn set_sort_index(&mut self, new_index: usize) {
@@ -341,6 +361,10 @@ where
}
}
+ pub fn invalidate_cached_columns(&mut self) {
+ self.table.invalidate_cached_columns();
+ }
+
/// Draws a [`tui::widgets::Table`] on screen.
///
/// Note if the number of columns don't match in the [`SortableTextTable`] and data,
diff --git a/src/app/widgets/base/text_input.rs b/src/app/widgets/base/text_input.rs
index a6119c79..f3480c0e 100644
--- a/src/app/widgets/base/text_input.rs
+++ b/src/app/widgets/base/text_input.rs
@@ -44,7 +44,7 @@ pub struct TextInput {
}
impl Default for TextInput {
- fn default() -> Self {
+ fn default() -> Self {
Self {
text: Default::default(),
bounds: Default::default(),
@@ -100,15 +100,17 @@ impl TextInput {
fn move_word_forward(&mut self) -> WidgetEventResult {
let current_index = self.cursor.cur_cursor();
- for (index, _word) in self.text[current_index..].unicode_word_indices() {
- if index > current_index {
- self.cursor.set_cursor(index);
- self.cursor_direction = CursorDirection::Right;
- return WidgetEventResult::Redraw;
+ if current_index < self.text.len() {
+ for (index, _word) in self.text[current_index..].unicode_word_indices() {
+ if index > 0 {
+ self.cursor.set_cursor(index + current_index);
+ self.cursor_direction = CursorDirection::Right;
+ return WidgetEventResult::Redraw;
+ }
}
+ self.cursor.set_cursor(self.text.len());
}
- self.cursor.set_cursor(self.text.len());
WidgetEventResult::Redraw
}
diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs
index 44f6d488..03111566 100644
--- a/src/app/widgets/base/text_table.rs
+++ b/src/app/widgets/base/text_table.rs
@@ -195,6 +195,18 @@ where
}
}
+ pub fn add_column(&mut self, index: usize, column: C) {
+ if self.columns.len() >= index {
+ self.columns.insert(index, column);
+ }
+ }
+
+ pub fn remove_column(&mut self, index: usize) {
+ if self.columns.len() > index {
+ self.columns.remove(index);
+ }
+ }
+
pub fn current_scroll_index(&self) -> usize {
self.scrollable.current_index()
}
@@ -360,6 +372,10 @@ where
}
}
+ pub fn invalidate_cached_columns(&mut self) {
+ self.cached_column_widths = CachedColumnWidths::Uncached;
+ }
+
/// Draws a [`Table`] on screen corresponding to the [`TextTable`].
pub fn draw_tui_table<B: Backend>(
&mut self, painter: &Painter, f: &mut Frame<'_, B>, data: &TextTableDataRef,
diff --git a/src/app/widgets/process.rs b/src/app/widgets/process.rs
index d127d02d..00554244 100644
--- a/src/app/widgets/process.rs
+++ b/src/app/widgets/process.rs
@@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::HashMap};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use float_ord::FloatOrd;
-use itertools::Itertools;
+use itertools::{Either, Itertools};
use unicode_segmentation::GraphemeCursor;
use tui::{
@@ -928,6 +928,64 @@ impl ProcessManager {
pub fn is_case_sensitive(&self) -> bool {
self.search_modifiers.enable_case_sensitive
}
+
+ fn is_using_command(&self) -> bool {
+ matches!(
+ self.process_table.columns()[1].sort_type,
+ ProcessSortType::Command
+ )
+ }
+
+ fn toggle_command(&mut self) -> WidgetEventResult {
+ if self.is_using_command() {
+ self.process_table
+ .set_column(ProcessSortColumn::new(ProcessSortType::Name), 1);
+ } else {
+ self.process_table
+ .set_column(ProcessSortColumn::new(ProcessSortType::Command), 1);
+ }
+
+ // Invalidate row cache.
+ self.process_table.invalidate_cached_columns();
+
+ WidgetEventResult::Signal(ReturnSignal::Update)
+ }
+
+ fn is_grouped(&self) -> bool {
+ matches!(
+ self.process_table.columns()[0].sort_type,
+ ProcessSortType::Count
+ )
+ }
+
+ fn toggle_grouped(&mut self) -> WidgetEventResult {
+ if self.is_grouped() {
+ self.process_table
+ .set_column(ProcessSortColumn::new(ProcessSortType::Pid), 0);
+
+ self.process_table
+ .add_column(ProcessSortColumn::new(ProcessSortType::State), 8);
+ #[cfg(target_family = "unix")]
+ {
+ self.process_table
+ .add_column(ProcessSortColumn::new(ProcessSortType::User), 8);
+ }
+ } else {
+ self.process_table
+ .set_column(ProcessSortColumn::new(ProcessSortType::Count), 0);
+
+ #[cfg(target_family = "unix")]
+ {
+ self.process_table.remove_column(9, Some(2));
+ }
+ self.process_table.remove_column(8, Some(2));
+ }
+
+ // Invalidate row cache.
+ self.process_table.invalidate_cached_columns();
+
+ WidgetEventResult::Signal(ReturnSignal::Update)
+ }
}
impl Component for ProcessManager {
@@ -964,9 +1022,11 @@ impl Component for ProcessManager {
match event.code {
KeyCode::Tab => {
// Handle grouping/ungrouping
+ return self.toggle_grouped();
}
KeyCode::Char('P') => {
// Show full command/process name
+ return self.toggle_command();
}
KeyCode::Char('d') => {
match self.dd_multi.input('d') {
@@ -1009,6 +1069,7 @@ impl Component for ProcessManager {
} else if let KeyModifiers::SHIFT = event.modifiers {
if let KeyCode::Char('P') = event.code {
// Show full command/process name
+ return self.toggle_command();
}
}
@@ -1229,29 +1290,74 @@ impl Widget for ProcessManager {
}
fn update_data(&mut self, data_collection: &DataCollection) {
- let filtered_sorted_iterator = data_collection
- .process_harvest
- .iter()
- .filter(|process| {
- if let Some(Ok(query)) = &self.process_filter {
- query.check(
- process,
- matches!(
- self.process_table.columns()[1].sort_type,
- ProcessSortType::Command
- ),
- )
+ let mut id_pid_map: HashMap<String, ProcessHarvest>;
+
+ let filtered_iter = data_collection.process_harvest.iter().filter(|process| {
+ if let Some(Ok(query)) = &self.process_filter {
+ query.check(process, self.is_using_command())
+ } else {
+ true
+ }
+ });
+
+ let filtered_grouped_iter = if self.is_grouped() {
+ id_pid_map = HashMap::new();
+ filtered_iter.for_each(|process_harvest| {
+ let id = if self.is_using_command() {
+ &process_harvest.command
+ } else {
+ &process_harvest.name
+ };
+
+ if let Some(grouped_process_harvest) = id_pid_map.get_mut(id) {
+ grouped_process_harvest.cpu_usage_percent += process_harvest.cpu_usage_percent;
+ grouped_process_harvest.mem_usage_bytes += process_harvest.mem_usage_bytes;
+ grouped_process_harvest.mem_usage_percent += process_harvest.mem_usage_percent;
+ grouped_process_harvest.read_bytes_per_sec +=
+ process_harvest.read_bytes_per_sec;
+ grouped_process_harvest.write_bytes_per_sec +=
+ process_harvest.write_bytes_per_sec;
+ grouped_process_harvest.total_read_bytes += process_harvest.total_read_bytes;
+ grouped_process_harvest.total_write_bytes += process_harvest.total_write_bytes;
} else {
- true
+ id_pid_map.insert(id.clone(), process_harvest.clone());
}
- })
- .sorted_by(
+ });
+
+ Either::Left(id_pid_map.values())
+ } else {
+ Either::Right(filtered_iter)
+ };
+
+ let filtered_sorted_iter = if let ProcessSortType::Count =
+ self.process_table.current_sorting_column().sort_type
+ {
+ let mut v = filtered_grouped_iter.collect::<Vec<_>>();
+ v.sort_by_cached_key(|k| {
+ if self.is_using_command() {
+ data_collection
+ .process_cmd_pid_map
+ .get(&k.command)
+ .map(|v| v.len())
+ .unwrap_or(0)
+ } else {
+ data_collection
+ .process_name_pid_map
+ .get(&k.name)
+ .map(|v| v.len())
+ .unwrap_or(0)
+ }
+ });
+ Either::Left(v.into_iter())
+ } else {
+ Either::Right(filtered_grouped_iter.sorted_by(
match self.process_table.current_sorting_column().sort_type {
ProcessSortType::Pid => {
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.pid.cmp(&b.pid)
}
ProcessSortType::Count => {
- todo!()
+ // This case should be impossible by the above check.
+ unreachable!()
}
ProcessSortType::Name => {
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.name.cmp(&b.name)
@@ -1287,14 +1393,15 @@ impl Widget for ProcessManager {
}
#[cfg(not(target_family = "unix"))]
{
- |_a: &&ProcessHarvest, _b: &&ProcessHarvest| Ord::Eq
+ |_a: &&ProcessHarvest, _b: &&ProcessHarvest| std::cmp::Ordering::Equal
}
}
ProcessSortType::State => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
a.process_state.cmp(&b.process_state)
},
},
- );
+ ))
+ };
self.display_data = if let SortStatus::SortDescending = self
.process_table
@@ -1302,9 +1409,9 @@ impl Widget for ProcessManager {
.sortable_column
.sorting_status()
{
- itertools::Either::Left(filtered_sorted_iterator.rev())
+ Either::Left(filtered_sorted_iter.rev())
} else {
- itertools::Either::Right(filtered_sorted_iterator)
+ Either::Right(filtered_sorted_iter)
}
.map(|process| {
self.process_table
@@ -1312,7 +1419,27 @@ impl Widget for ProcessManager {
.iter()
.map(|column| match &column.sort_type {
ProcessSortType::Pid => (process.pid.to_string().into(), None, None),
- ProcessSortType::Count => ("".into(), None, None),
+ ProcessSortType::Count => (
+ if self.is_using_command() {
+ data_collection
+ .process_cmd_pid_map
+ .get(&process.command)
+ .map(|v| v.len())
+ .unwrap_or(0)
+ .to_string()
+ .into()
+ } else {
+ data_collection
+ .process_name_pid_map
+ .get(&process.name)
+ .map(|v| v.len())
+ .unwrap_or(0)
+ .to_string()
+ .into()
+ },
+ None,
+ None,
+ ),
ProcessSortType::Name => (process.name.clone().into(), None, None),
ProcessSortType::Command => (process.command.clone().into(), None, None),
ProcessSortType::Cpu => (
diff --git a/src/constants.rs b/src/constants.rs
index 63953d1c..9074a78f 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -435,6 +435,10 @@ pub const DEFAULT_BATTERY_LAYOUT: &str = r##"
default=true
"##;
+pub const DEFAULT_BASIC_LAYOUT: &str = r##"
+[[row]]
+"##;
+
// Config and flags
pub const DEFAULT_CONFIG_FILE_PATH: &str = "bottom/bottom.toml";