From 2a740f48f79498e6a812670176c45c53e0383c05 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Wed, 12 Oct 2022 16:25:38 -0400 Subject: refactor: tables V2 (#749) * refactor: move to new data table implementation * more work towards refactor * move disk and temp over, fix longstanding bug with disk and temp if removing the last value and selected * work towards porting over CPU work towards porting over CPU fix typo partially port over cpu, fix some potentially inefficient concat_string calls more work towards cpu widget migration some refactoring * sortable data sortable data more refactoring some sort refactoring more refactoringgggg column refactoring renaming and reorganizing more refactoring regarding column logic add sort arrows again * move over sort menu * port over process port over process precommit temp temp two, remember to squash work fix broken ltr calculation and CPU hiding add back row styling temp fix a bunch of issues, get proc working more fixes around click fix frozen issues * fix dd process killing * revert some of the persistent config changes from #257 * fix colouring for trees * fix missing entries in tree * keep columns if there is no data * add and remove tests * Fix ellipsis --- src/app/widgets/process_table/proc_widget_data.rs | 274 ++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 src/app/widgets/process_table/proc_widget_data.rs (limited to 'src/app/widgets/process_table/proc_widget_data.rs') diff --git a/src/app/widgets/process_table/proc_widget_data.rs b/src/app/widgets/process_table/proc_widget_data.rs new file mode 100644 index 00000000..4957c87f --- /dev/null +++ b/src/app/widgets/process_table/proc_widget_data.rs @@ -0,0 +1,274 @@ +use std::{ + cmp::{max, Ordering}, + fmt::Display, +}; + +use concat_string::concat_string; +use tui::{text::Text, widgets::Row}; + +use crate::{ + app::data_harvester::processes::ProcessHarvest, + canvas::Painter, + components::data_table::{DataTableColumn, DataToCell}, + data_conversion::{binary_byte_string, dec_bytes_per_second_string, dec_bytes_string}, + utils::gen_util::truncate_text, + Pid, +}; + +use super::proc_widget_column::ProcColumn; + +#[derive(Clone)] +enum IdType { + Name(String), + Command(String), +} + +#[derive(Clone)] +pub struct Id { + id_type: IdType, + prefix: Option, +} + +impl Id { + /// Returns the ID as a lowercase [`String`], with no prefix. This is primarily useful for + /// cases like sorting where we treat everything as the same case (e.g. `Discord` comes before + /// `dkms`). + pub fn to_lowercase(&self) -> String { + match &self.id_type { + IdType::Name(name) => name.to_lowercase(), + IdType::Command(cmd) => cmd.to_lowercase(), + } + } + + /// Return the ID as a borrowed [`str`] with no prefix. + pub fn as_str(&self) -> &str { + match &self.id_type { + IdType::Name(name) => name.as_str(), + IdType::Command(cmd) => cmd.as_str(), + } + } + + /// Returns the ID as a [`String`] with prefix. + pub fn to_prefixed_string(&self) -> String { + if let Some(prefix) = &self.prefix { + concat_string!( + prefix, + match &self.id_type { + IdType::Name(name) => name, + IdType::Command(cmd) => cmd, + } + ) + } else { + match &self.id_type { + IdType::Name(name) => name.to_string(), + IdType::Command(cmd) => cmd.to_string(), + } + } + } +} + +impl Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +// TODO: Can reduce this to 32 bytes. +#[derive(PartialEq, Clone)] +pub enum MemUsage { + Percent(f64), + Bytes(u64), +} + +impl PartialOrd for MemUsage { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (MemUsage::Percent(a), MemUsage::Percent(b)) => a.partial_cmp(b), + (MemUsage::Bytes(a), MemUsage::Bytes(b)) => a.partial_cmp(b), + _ => unreachable!(), + } + } +} + +impl Display for MemUsage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MemUsage::Percent(percent) => f.write_fmt(format_args!("{:.1}%", percent)), + MemUsage::Bytes(bytes) => f.write_str(&binary_byte_string(*bytes)), + } + } +} + +#[derive(Clone)] +pub struct ProcWidgetData { + pub pid: Pid, + pub ppid: Option, + pub id: Id, + pub cpu_usage_percent: f64, + pub mem_usage: MemUsage, + pub rps: u64, + pub wps: u64, + pub total_read: u64, + pub total_write: u64, + pub process_state: String, + pub process_char: char, + #[cfg(target_family = "unix")] + pub user: String, + pub num_similar: u64, + pub disabled: bool, +} + +impl ProcWidgetData { + pub fn from_data(process: &ProcessHarvest, is_command: bool, is_mem_percent: bool) -> Self { + let id = Id { + id_type: if is_command { + IdType::Command(process.command.clone()) + } else { + IdType::Name(process.name.clone()) + }, + prefix: None, + }; + + let mem_usage = if is_mem_percent { + MemUsage::Percent(process.mem_usage_percent) + } else { + MemUsage::Bytes(process.mem_usage_bytes) + }; + + Self { + pid: process.pid, + ppid: process.parent_pid, + id, + cpu_usage_percent: process.cpu_usage_percent, + mem_usage, + rps: process.read_bytes_per_sec, + wps: process.write_bytes_per_sec, + total_read: process.total_read_bytes, + total_write: process.total_write_bytes, + process_state: process.process_state.0.clone(), + process_char: process.process_state.1, + #[cfg(target_family = "unix")] + user: process.user.to_string(), + num_similar: 1, + disabled: false, + } + } + + pub fn num_similar(mut self, num_similar: u64) -> Self { + self.num_similar = num_similar; + self + } + + pub fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } + + pub fn prefix(mut self, prefix: Option) -> Self { + self.id.prefix = prefix; + self + } + + pub fn add(&mut self, other: &Self) { + self.cpu_usage_percent += other.cpu_usage_percent; + self.mem_usage = match (&self.mem_usage, &other.mem_usage) { + (MemUsage::Percent(a), MemUsage::Percent(b)) => MemUsage::Percent(a + b), + (MemUsage::Bytes(a), MemUsage::Bytes(b)) => MemUsage::Bytes(a + b), + (MemUsage::Percent(_), MemUsage::Bytes(_)) + | (MemUsage::Bytes(_), MemUsage::Percent(_)) => { + unreachable!("trying to add together two different memory usage types!") + } + }; + self.rps += other.rps; + self.wps += other.wps; + self.total_read += other.total_read; + self.total_write += other.total_write; + } + + fn to_string(&self, column: &ProcColumn) -> String { + match column { + ProcColumn::CpuPercent => format!("{:.1}%", self.cpu_usage_percent), + ProcColumn::MemoryVal | ProcColumn::MemoryPercent => self.mem_usage.to_string(), + ProcColumn::Pid => self.pid.to_string(), + ProcColumn::Count => self.num_similar.to_string(), + ProcColumn::Name | ProcColumn::Command => self.id.to_prefixed_string(), + ProcColumn::ReadPerSecond => dec_bytes_per_second_string(self.rps), + ProcColumn::WritePerSecond => dec_bytes_per_second_string(self.wps), + ProcColumn::TotalRead => dec_bytes_string(self.total_read), + ProcColumn::TotalWrite => dec_bytes_string(self.total_write), + ProcColumn::State => self.process_char.to_string(), + ProcColumn::User => { + #[cfg(target_family = "unix")] + { + self.user.clone() + } + #[cfg(not(target_family = "unix"))] + { + "".to_string() + } + } + } + } +} + +impl DataToCell for ProcWidgetData { + fn to_cell<'a>(&'a self, column: &ProcColumn, calculated_width: u16) -> Option> { + Some(truncate_text( + &match column { + ProcColumn::CpuPercent => { + format!("{:.1}%", self.cpu_usage_percent) + } + ProcColumn::MemoryVal | ProcColumn::MemoryPercent => self.mem_usage.to_string(), + ProcColumn::Pid => self.pid.to_string(), + ProcColumn::Count => self.num_similar.to_string(), + ProcColumn::Name | ProcColumn::Command => self.id.to_prefixed_string(), + ProcColumn::ReadPerSecond => dec_bytes_per_second_string(self.rps), + ProcColumn::WritePerSecond => dec_bytes_per_second_string(self.wps), + ProcColumn::TotalRead => dec_bytes_string(self.total_read), + ProcColumn::TotalWrite => dec_bytes_string(self.total_write), + ProcColumn::State => { + if calculated_width < 8 { + self.process_char.to_string() + } else { + self.process_state.clone() + } + } + ProcColumn::User => { + #[cfg(target_family = "unix")] + { + self.user.clone() + } + #[cfg(not(target_family = "unix"))] + { + "".to_string() + } + } + }, + calculated_width, + )) + } + + #[inline(always)] + fn style_row<'a>(&self, row: Row<'a>, painter: &Painter) -> Row<'a> { + if self.disabled { + row.style(painter.colours.disabled_text_style) + } else { + row + } + } + + fn column_widths>(data: &[Self], columns: &[C]) -> Vec + where + Self: Sized, + { + let mut widths = vec![0; columns.len()]; + + for d in data { + for (w, c) in widths.iter_mut().zip(columns) { + *w = max(*w, d.to_string(c.inner()).len() as u16); + } + } + + widths + } +} -- cgit v1.2.3