mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 07:34:27 +02:00
refactor: various bug fixes and code removal
This commit is contained in:
parent
9089231bc4
commit
f02daa0a2b
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -266,7 +266,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
"toml",
|
||||
"tui",
|
||||
"typed-builder",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"winapi",
|
||||
@ -1529,17 +1528,6 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a46ee5bd706ff79131be9c94e7edcb82b703c487766a114434e5790361cf08c5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.14.0"
|
||||
|
@ -59,7 +59,6 @@ thiserror = "1.0.24"
|
||||
textwrap = "0.14.2"
|
||||
toml = "0.5.8"
|
||||
tui = { version = "0.16.0", features = ["crossterm"], default-features = false }
|
||||
typed-builder = "0.9.0"
|
||||
unicode-segmentation = "1.8.0"
|
||||
unicode-width = "0.1"
|
||||
|
||||
|
16
src/app.rs
16
src/app.rs
@ -19,9 +19,7 @@ pub use filter::*;
|
||||
use layout_manager::*;
|
||||
pub use widgets::*;
|
||||
|
||||
use crate::{
|
||||
canvas, constants, units::data_units::DataUnit, utils::error::Result, BottomEvent, Pid,
|
||||
};
|
||||
use crate::{constants, units::data_units::DataUnit, utils::error::Result, BottomEvent, Pid};
|
||||
|
||||
use self::event::{ComponentEventResult, EventResult, ReturnSignal};
|
||||
|
||||
@ -87,7 +85,7 @@ pub struct AppConfigFields {
|
||||
pub hide_time: bool,
|
||||
pub autohide_time: bool,
|
||||
pub use_old_network_legend: bool,
|
||||
pub table_gap: u16, // TODO: [Config, Refactor] Just make this a bool...
|
||||
pub table_gap: bool,
|
||||
pub disable_click: bool,
|
||||
pub no_write: bool,
|
||||
pub show_table_scroll_position: bool,
|
||||
@ -101,7 +99,7 @@ pub struct AppConfigFields {
|
||||
/// the data collected at that instant.
|
||||
pub enum FrozenState {
|
||||
NotFrozen,
|
||||
Frozen(DataCollection),
|
||||
Frozen(Box<DataCollection>),
|
||||
}
|
||||
|
||||
impl Default for FrozenState {
|
||||
@ -115,8 +113,6 @@ pub struct AppState {
|
||||
|
||||
to_delete_process_list: Option<(String, Vec<Pid>)>,
|
||||
|
||||
pub canvas_data: canvas::DisplayableData,
|
||||
|
||||
pub data_collection: DataCollection,
|
||||
|
||||
pub is_expanded: bool,
|
||||
@ -167,7 +163,6 @@ impl AppState {
|
||||
// Use defaults.
|
||||
dd_err: Default::default(),
|
||||
to_delete_process_list: Default::default(),
|
||||
canvas_data: Default::default(),
|
||||
data_collection: Default::default(),
|
||||
is_expanded: Default::default(),
|
||||
delete_dialog_state: Default::default(),
|
||||
@ -186,7 +181,7 @@ impl AppState {
|
||||
if matches!(self.frozen_state, FrozenState::Frozen(_)) {
|
||||
self.frozen_state = FrozenState::NotFrozen;
|
||||
} else {
|
||||
self.frozen_state = FrozenState::Frozen(self.data_collection.clone());
|
||||
self.frozen_state = FrozenState::Frozen(Box::new(self.data_collection.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -418,7 +413,6 @@ impl AppState {
|
||||
|
||||
if was_id_already_selected {
|
||||
returned_result = self.convert_widget_event_result(result);
|
||||
break;
|
||||
} else {
|
||||
// If the weren't equal, *force* a redraw, and correct the layout tree.
|
||||
correct_layout_last_selections(
|
||||
@ -427,8 +421,8 @@ impl AppState {
|
||||
);
|
||||
let _ = self.convert_widget_event_result(result);
|
||||
returned_result = EventResult::Redraw;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
SelectableType::Unselectable => {
|
||||
let result = widget.handle_mouse_event(event);
|
||||
|
@ -26,17 +26,14 @@ use crate::{
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
pub type TimeOffset = f64;
|
||||
pub type Value = f64;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TimedData {
|
||||
pub rx_data: Value,
|
||||
pub tx_data: Value,
|
||||
pub cpu_data: Vec<Value>,
|
||||
pub rx_data: f64,
|
||||
pub tx_data: f64,
|
||||
pub cpu_data: Vec<f64>,
|
||||
pub load_avg_data: [f32; 3],
|
||||
pub mem_data: Option<Value>,
|
||||
pub swap_data: Option<Value>,
|
||||
pub mem_data: Option<f64>,
|
||||
pub swap_data: Option<f64>,
|
||||
}
|
||||
|
||||
/// AppCollection represents the pooled data stored within the main app
|
||||
|
@ -78,7 +78,8 @@ impl Default for ProcessSorting {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProcessHarvest {
|
||||
pub pid: Pid,
|
||||
pub parent_pid: Option<Pid>, // Remember, parent_pid 0 is root...
|
||||
pub parent_pid: Option<Pid>,
|
||||
pub children_pids: Vec<Pid>,
|
||||
pub cpu_usage_percent: f64,
|
||||
pub mem_usage_percent: f64,
|
||||
pub mem_usage_bytes: u64,
|
||||
@ -93,10 +94,11 @@ pub struct ProcessHarvest {
|
||||
pub process_state: String,
|
||||
pub process_state_char: char,
|
||||
|
||||
/// This is the *effective* user ID.
|
||||
/// This is the *effective* user ID. This is only used on Unix platforms.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub uid: libc::uid_t,
|
||||
|
||||
/// This is the process' user. This is only used on Unix platforms.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub user: Cow<'static, str>,
|
||||
}
|
||||
|
@ -203,6 +203,7 @@ fn read_proc(
|
||||
ProcessHarvest {
|
||||
pid: process.pid,
|
||||
parent_pid,
|
||||
children_pids: vec![],
|
||||
cpu_usage_percent,
|
||||
mem_usage_percent,
|
||||
mem_usage_bytes,
|
||||
|
@ -89,6 +89,7 @@ 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 {
|
||||
|
@ -58,6 +58,7 @@ 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 {
|
||||
|
@ -13,7 +13,6 @@ use fxhash::FxHashMap;
|
||||
use indextree::{Arena, NodeId};
|
||||
use std::cmp::min;
|
||||
use tui::layout::Rect;
|
||||
use typed_builder::*;
|
||||
|
||||
use crate::app::widgets::Widget;
|
||||
|
||||
@ -21,130 +20,6 @@ use super::{
|
||||
event::SelectionAction, AppConfigFields, CpuGraph, TimeGraph, TmpBottomWidget, UsedWidgets,
|
||||
};
|
||||
|
||||
/// Represents a more usable representation of the layout, derived from the
|
||||
/// config.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BottomLayout {
|
||||
pub rows: Vec<OldBottomRow>,
|
||||
pub total_row_height_ratio: u32,
|
||||
}
|
||||
|
||||
/// Represents a single row in the layout.
|
||||
#[derive(Clone, Debug, TypedBuilder)]
|
||||
pub struct OldBottomRow {
|
||||
pub children: Vec<OldBottomCol>,
|
||||
|
||||
#[builder(default = 1)]
|
||||
pub total_col_ratio: u32,
|
||||
|
||||
#[builder(default = 1)]
|
||||
pub row_height_ratio: u32,
|
||||
|
||||
#[builder(default = false)]
|
||||
pub canvas_handle_height: bool,
|
||||
|
||||
#[builder(default = false)]
|
||||
pub flex_grow: bool,
|
||||
}
|
||||
|
||||
/// Represents a single column in the layout. We assume that even if the column
|
||||
/// contains only ONE element, it is still a column (rather than either a col or
|
||||
/// a widget, as per the config, for simplicity's sake).
|
||||
#[derive(Clone, Debug, TypedBuilder)]
|
||||
pub struct OldBottomCol {
|
||||
pub children: Vec<BottomColRow>,
|
||||
|
||||
#[builder(default = 1)]
|
||||
pub total_col_row_ratio: u32,
|
||||
|
||||
#[builder(default = 1)]
|
||||
pub col_width_ratio: u32,
|
||||
|
||||
#[builder(default = false)]
|
||||
pub canvas_handle_width: bool,
|
||||
|
||||
#[builder(default = false)]
|
||||
pub flex_grow: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, TypedBuilder)]
|
||||
pub struct BottomColRow {
|
||||
pub children: Vec<BottomWidget>,
|
||||
|
||||
#[builder(default = 1)]
|
||||
pub total_widget_ratio: u32,
|
||||
|
||||
#[builder(default = 1)]
|
||||
pub col_row_height_ratio: u32,
|
||||
|
||||
#[builder(default = false)]
|
||||
pub canvas_handle_height: bool,
|
||||
|
||||
#[builder(default = false)]
|
||||
pub flex_grow: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum WidgetDirection {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
impl WidgetDirection {
|
||||
pub fn is_opposite(&self, other_direction: &WidgetDirection) -> bool {
|
||||
match &self {
|
||||
WidgetDirection::Left => *other_direction == WidgetDirection::Right,
|
||||
WidgetDirection::Right => *other_direction == WidgetDirection::Left,
|
||||
WidgetDirection::Up => *other_direction == WidgetDirection::Down,
|
||||
WidgetDirection::Down => *other_direction == WidgetDirection::Up,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a single widget.
|
||||
#[derive(Debug, Default, Clone, TypedBuilder)]
|
||||
pub struct BottomWidget {
|
||||
pub widget_type: BottomWidgetType,
|
||||
pub widget_id: u64,
|
||||
|
||||
#[builder(default = 1)]
|
||||
pub width_ratio: u32,
|
||||
|
||||
#[builder(default = None)]
|
||||
pub left_neighbour: Option<u64>,
|
||||
|
||||
#[builder(default = None)]
|
||||
pub right_neighbour: Option<u64>,
|
||||
|
||||
#[builder(default = None)]
|
||||
pub up_neighbour: Option<u64>,
|
||||
|
||||
#[builder(default = None)]
|
||||
pub down_neighbour: Option<u64>,
|
||||
|
||||
/// If set to true, the canvas will override any ratios.
|
||||
#[builder(default = false)]
|
||||
pub canvas_handle_width: bool,
|
||||
|
||||
/// Whether we want this widget to take up all available room (and ignore any ratios).
|
||||
#[builder(default = false)]
|
||||
pub flex_grow: bool,
|
||||
|
||||
/// The value is the direction to bounce, as well as the parent offset.
|
||||
#[builder(default = None)]
|
||||
pub parent_reflector: Option<(WidgetDirection, u64)>,
|
||||
|
||||
/// Top left corner when drawn, for mouse click detection. (x, y)
|
||||
#[builder(default = None)]
|
||||
pub top_left_corner: Option<(u16, u16)>,
|
||||
|
||||
/// Bottom right corner when drawn, for mouse click detection. (x, y)
|
||||
#[builder(default = None)]
|
||||
pub bottom_right_corner: Option<(u16, u16)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum BottomWidgetType {
|
||||
Empty,
|
||||
@ -238,8 +113,6 @@ Supported widget names:
|
||||
}
|
||||
}
|
||||
|
||||
// --- New stuff ---
|
||||
|
||||
/// Represents a row in the layout tree.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct RowLayout {
|
||||
@ -380,7 +253,7 @@ pub fn create_layout_tree(
|
||||
BottomWidgetType::Proc => {
|
||||
widget_lookup_map.insert(
|
||||
widget_id,
|
||||
ProcessManager::new(process_defaults)
|
||||
ProcessManager::new(process_defaults, app_config_fields)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.basic_mode(app_config_fields.use_basic_mode)
|
||||
@ -391,7 +264,7 @@ pub fn create_layout_tree(
|
||||
BottomWidgetType::Temp => {
|
||||
widget_lookup_map.insert(
|
||||
widget_id,
|
||||
TempTable::default()
|
||||
TempTable::from_config(app_config_fields)
|
||||
.set_temp_type(app_config_fields.temperature_type.clone())
|
||||
.width(width)
|
||||
.height(height)
|
||||
@ -403,7 +276,7 @@ pub fn create_layout_tree(
|
||||
BottomWidgetType::Disk => {
|
||||
widget_lookup_map.insert(
|
||||
widget_id,
|
||||
DiskTable::default()
|
||||
DiskTable::from_config(app_config_fields)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.basic_mode(app_config_fields.use_basic_mode)
|
||||
@ -620,7 +493,7 @@ pub fn create_layout_tree(
|
||||
///
|
||||
/// We can do this by just going through the ancestors, starting from the widget itself.
|
||||
pub fn correct_layout_last_selections(arena: &mut Arena<LayoutNode>, selected: NodeId) {
|
||||
let mut selected_ancestors = selected.ancestors(&arena).collect::<Vec<_>>();
|
||||
let mut selected_ancestors = selected.ancestors(arena).collect::<Vec<_>>();
|
||||
let prev_node = selected_ancestors.pop();
|
||||
if let Some(mut prev_node) = prev_node {
|
||||
for node in selected_ancestors {
|
||||
|
@ -1,78 +1,7 @@
|
||||
// Copied from SO: https://stackoverflow.com/a/55231715
|
||||
#[cfg(target_os = "windows")]
|
||||
use winapi::{
|
||||
shared::{minwindef::DWORD, ntdef::HANDLE},
|
||||
um::{
|
||||
processthreadsapi::{OpenProcess, TerminateProcess},
|
||||
winnt::{PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE},
|
||||
},
|
||||
};
|
||||
//! This file is meant to house (OS specific) implementations on how to kill processes.
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) mod windows;
|
||||
|
||||
/// This file is meant to house (OS specific) implementations on how to kill processes.
|
||||
#[cfg(target_family = "unix")]
|
||||
use crate::utils::error::BottomError;
|
||||
use crate::Pid;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
struct Process(HANDLE);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
impl Process {
|
||||
fn open(pid: DWORD) -> Result<Process, String> {
|
||||
let pc = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, 0, pid) };
|
||||
if pc.is_null() {
|
||||
return Err("OpenProcess".to_string());
|
||||
}
|
||||
Ok(Process(pc))
|
||||
}
|
||||
|
||||
fn kill(self) -> Result<(), String> {
|
||||
let result = unsafe { TerminateProcess(self.0, 1) };
|
||||
if result == 0 {
|
||||
return Err("Failed to kill process".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Kills a process, given a PID, for unix.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn kill_process_given_pid(pid: Pid, signal: usize) -> crate::utils::error::Result<()> {
|
||||
let output = unsafe { libc::kill(pid as i32, signal as i32) };
|
||||
if output != 0 {
|
||||
// We had an error...
|
||||
let err_code = std::io::Error::last_os_error().raw_os_error();
|
||||
let err = match err_code {
|
||||
Some(libc::ESRCH) => "the target process did not exist.",
|
||||
Some(libc::EPERM) => "the calling process does not have the permissions to terminate the target process(es).",
|
||||
Some(libc::EINVAL) => "an invalid signal was specified.",
|
||||
_ => "Unknown error occurred."
|
||||
};
|
||||
|
||||
return if let Some(err_code) = err_code {
|
||||
Err(BottomError::GenericError(format!(
|
||||
"Error code {} - {}",
|
||||
err_code, err,
|
||||
)))
|
||||
} else {
|
||||
Err(BottomError::GenericError(format!(
|
||||
"Error code ??? - {}",
|
||||
err,
|
||||
)))
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Kills a process, given a PID, for windows.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> {
|
||||
{
|
||||
let process = Process::open(pid as DWORD)?;
|
||||
process.kill()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub(crate) mod unix;
|
||||
|
31
src/app/process_killer/unix.rs
Normal file
31
src/app/process_killer/unix.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use crate::utils::error::BottomError;
|
||||
use crate::Pid;
|
||||
|
||||
/// Kills a process, given a PID, for unix.
|
||||
pub(crate) fn kill_process_given_pid(pid: Pid, signal: usize) -> crate::utils::error::Result<()> {
|
||||
let output = unsafe { libc::kill(pid as i32, signal as i32) };
|
||||
if output != 0 {
|
||||
// We had an error...
|
||||
let err_code = std::io::Error::last_os_error().raw_os_error();
|
||||
let err = match err_code {
|
||||
Some(libc::ESRCH) => "the target process did not exist.",
|
||||
Some(libc::EPERM) => "the calling process does not have the permissions to terminate the target process(es).",
|
||||
Some(libc::EINVAL) => "an invalid signal was specified.",
|
||||
_ => "Unknown error occurred."
|
||||
};
|
||||
|
||||
return if let Some(err_code) = err_code {
|
||||
Err(BottomError::GenericError(format!(
|
||||
"Error code {} - {}",
|
||||
err_code, err,
|
||||
)))
|
||||
} else {
|
||||
Err(BottomError::GenericError(format!(
|
||||
"Error code ??? - {}",
|
||||
err,
|
||||
)))
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
40
src/app/process_killer/windows.rs
Normal file
40
src/app/process_killer/windows.rs
Normal file
@ -0,0 +1,40 @@
|
||||
// Copied from SO: https://stackoverflow.com/a/55231715
|
||||
use winapi::{
|
||||
shared::{minwindef::DWORD, ntdef::HANDLE},
|
||||
um::{
|
||||
processthreadsapi::{OpenProcess, TerminateProcess},
|
||||
winnt::{PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE},
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) struct Process(HANDLE);
|
||||
|
||||
impl Process {
|
||||
pub(crate) fn open(pid: DWORD) -> Result<Process, String> {
|
||||
let pc = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, 0, pid) };
|
||||
if pc.is_null() {
|
||||
return Err("OpenProcess".to_string());
|
||||
}
|
||||
Ok(Process(pc))
|
||||
}
|
||||
|
||||
pub(crate) fn kill(self) -> Result<(), String> {
|
||||
let result = unsafe { TerminateProcess(self.0, 1) };
|
||||
if result == 0 {
|
||||
return Err("Failed to kill process".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Kills a process, given a PID, for windows.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> {
|
||||
{
|
||||
let process = Process::open(pid as DWORD)?;
|
||||
process.kill()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -138,11 +138,6 @@ pub trait Widget {
|
||||
/// Returns the desired height from the [`Widget`].
|
||||
fn height(&self) -> LayoutRule;
|
||||
|
||||
/// Returns whether this [`Widget`] can be expanded. The default implementation returns `true`.
|
||||
fn expandable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns whether this [`Widget`] can be selected. The default implementation returns [`SelectableType::Selectable`].
|
||||
fn selectable_type(&self) -> SelectableType {
|
||||
SelectableType::Selectable
|
||||
|
@ -33,6 +33,11 @@ impl SortMenu {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_show_gap(mut self, show_gap: bool) -> Self {
|
||||
self.table = self.table.try_show_gap(show_gap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Updates the index of the [`SortMenu`].
|
||||
pub fn set_index(&mut self, index: usize) {
|
||||
self.table.scrollable.set_index(index);
|
||||
|
@ -274,8 +274,8 @@ where
|
||||
st
|
||||
}
|
||||
|
||||
pub fn default_ltr(mut self, ltr: bool) -> Self {
|
||||
self.table = self.table.default_ltr(ltr);
|
||||
pub fn try_show_gap(mut self, show_gap: bool) -> Self {
|
||||
self.table = self.table.try_show_gap(show_gap);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -399,7 +399,10 @@ where
|
||||
f.render_widget(block, block_area);
|
||||
return;
|
||||
}
|
||||
let table_gap = if !self.show_gap || inner_area.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
let table_gap = if !self.show_gap
|
||||
|| (data.len() + 2 > inner_area.height.into()
|
||||
&& inner_area.height < TABLE_GAP_HEIGHT_LIMIT)
|
||||
{
|
||||
0
|
||||
} else {
|
||||
1
|
||||
|
@ -185,6 +185,7 @@ impl Widget for BatteryTable {
|
||||
split_area[0].width,
|
||||
split_area[0].height,
|
||||
);
|
||||
// FIXME: [URGENT] See if this should be changed; TABLE_GAP_HEIGHT_LIMIT should be removed maybe. May also need to grab the table gap from the config?
|
||||
let data_area =
|
||||
if inner_area.height >= TABLE_GAP_HEIGHT_LIMIT && split_area[1].height > 0 {
|
||||
Rect::new(
|
||||
|
@ -56,7 +56,8 @@ impl CpuGraph {
|
||||
SimpleColumn::new_flex("CPU".into(), 0.5),
|
||||
SimpleColumn::new_hard("Use".into(), None),
|
||||
])
|
||||
.default_ltr(false);
|
||||
.default_ltr(false)
|
||||
.try_show_gap(app_config_fields.table_gap);
|
||||
let legend_position = if app_config_fields.left_legend {
|
||||
CpuGraphLegendPosition::Left
|
||||
} else {
|
||||
|
@ -4,8 +4,8 @@ use tui::{backend::Backend, layout::Rect, widgets::Borders, Frame};
|
||||
use crate::{
|
||||
app::{
|
||||
data_farmer::DataCollection, event::ComponentEventResult,
|
||||
sort_text_table::SimpleSortableColumn, text_table::TextTableData, Component, TextTable,
|
||||
Widget,
|
||||
sort_text_table::SimpleSortableColumn, text_table::TextTableData, AppConfigFields,
|
||||
Component, TextTable, Widget,
|
||||
},
|
||||
canvas::Painter,
|
||||
data_conversion::convert_disk_row,
|
||||
@ -25,8 +25,9 @@ pub struct DiskTable {
|
||||
show_scroll_index: bool,
|
||||
}
|
||||
|
||||
impl Default for DiskTable {
|
||||
fn default() -> Self {
|
||||
impl DiskTable {
|
||||
/// Creates a [`DiskTable`] from a config.
|
||||
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
|
||||
let table = TextTable::new(vec![
|
||||
SimpleSortableColumn::new_flex("Disk".into(), None, false, 0.2),
|
||||
SimpleSortableColumn::new_flex("Mount".into(), None, false, 0.2),
|
||||
@ -35,7 +36,8 @@ impl Default for DiskTable {
|
||||
SimpleSortableColumn::new_hard("Total".into(), None, false, Some(6)),
|
||||
SimpleSortableColumn::new_hard("R/s".into(), None, false, Some(7)),
|
||||
SimpleSortableColumn::new_hard("W/s".into(), None, false, Some(7)),
|
||||
]);
|
||||
])
|
||||
.try_show_gap(app_config_fields.table_gap);
|
||||
|
||||
Self {
|
||||
table,
|
||||
@ -47,9 +49,7 @@ impl Default for DiskTable {
|
||||
show_scroll_index: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskTable {
|
||||
/// Sets the width.
|
||||
pub fn width(mut self, width: LayoutRule) -> Self {
|
||||
self.width = width;
|
||||
|
@ -599,6 +599,7 @@ impl OldNetGraph {
|
||||
SimpleColumn::new_flex("Total RX".into(), 0.25),
|
||||
SimpleColumn::new_flex("Total TX".into(), 0.25),
|
||||
])
|
||||
.try_show_gap(config.table_gap)
|
||||
.unselectable(),
|
||||
bounds: Rect::default(),
|
||||
width: LayoutRule::default(),
|
||||
@ -646,11 +647,14 @@ impl Widget for OldNetGraph {
|
||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool,
|
||||
expanded: bool,
|
||||
) {
|
||||
const CONSTRAINTS: [Constraint; 2] = [Constraint::Min(0), Constraint::Length(4)];
|
||||
let constraints = [
|
||||
Constraint::Min(0),
|
||||
Constraint::Length(if self.table.show_gap { 5 } else { 4 }),
|
||||
];
|
||||
|
||||
let split_area = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(CONSTRAINTS)
|
||||
.constraints(constraints)
|
||||
.split(area);
|
||||
|
||||
let graph_area = split_area[0];
|
||||
|
@ -20,7 +20,7 @@ use crate::{
|
||||
query::*,
|
||||
text_table::DesiredColumnWidth,
|
||||
widgets::tui_stuff::BlockBuilder,
|
||||
DataCollection,
|
||||
AppConfigFields, DataCollection,
|
||||
},
|
||||
canvas::Painter,
|
||||
data_conversion::get_string_with_bytes,
|
||||
@ -273,7 +273,7 @@ pub struct ProcessManager {
|
||||
|
||||
impl ProcessManager {
|
||||
/// Creates a new [`ProcessManager`].
|
||||
pub fn new(process_defaults: &ProcessDefaults) -> Self {
|
||||
pub fn new(process_defaults: &ProcessDefaults, config: &AppConfigFields) -> Self {
|
||||
let process_table_columns = vec![
|
||||
ProcessSortColumn::new(ProcessSortType::Pid),
|
||||
ProcessSortColumn::new(ProcessSortType::Name),
|
||||
@ -290,8 +290,10 @@ impl ProcessManager {
|
||||
|
||||
let mut manager = Self {
|
||||
bounds: Rect::default(),
|
||||
sort_menu: SortMenu::new(process_table_columns.len()),
|
||||
process_table: SortableTextTable::new(process_table_columns).default_sort_index(2),
|
||||
sort_menu: SortMenu::new(process_table_columns.len()).try_show_gap(config.table_gap),
|
||||
process_table: SortableTextTable::new(process_table_columns)
|
||||
.default_sort_index(2)
|
||||
.try_show_gap(config.table_gap),
|
||||
search_input: TextInput::default(),
|
||||
search_block_bounds: Rect::default(),
|
||||
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: [Optimization] Maybe use something static/const/arrayvec?...
|
||||
@ -494,6 +496,12 @@ impl ProcessManager {
|
||||
self.search_modifiers.toggle_regex();
|
||||
ComponentEventResult::Signal(ReturnSignal::Update)
|
||||
}
|
||||
|
||||
/// Toggles tree mode.
|
||||
fn toggle_tree_mode(&mut self) -> ComponentEventResult {
|
||||
self.in_tree_mode = !self.in_tree_mode;
|
||||
ComponentEventResult::Signal(ReturnSignal::Update)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ProcessManager {
|
||||
@ -567,8 +575,7 @@ impl Component for ProcessManager {
|
||||
// Collapse a branch
|
||||
}
|
||||
KeyCode::Char('t') | KeyCode::F(5) => {
|
||||
self.in_tree_mode = !self.in_tree_mode;
|
||||
return ComponentEventResult::Redraw;
|
||||
return self.toggle_tree_mode();
|
||||
}
|
||||
KeyCode::Char('s') | KeyCode::F(6) => {
|
||||
return self.open_sort();
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
app::{
|
||||
data_farmer::DataCollection, data_harvester::temperature::TemperatureType,
|
||||
event::ComponentEventResult, sort_text_table::SimpleSortableColumn,
|
||||
text_table::TextTableData, Component, TextTable, Widget,
|
||||
text_table::TextTableData, AppConfigFields, Component, TextTable, Widget,
|
||||
},
|
||||
canvas::Painter,
|
||||
data_conversion::convert_temp_row,
|
||||
@ -24,13 +24,15 @@ pub struct TempTable {
|
||||
show_scroll_index: bool,
|
||||
}
|
||||
|
||||
impl Default for TempTable {
|
||||
fn default() -> Self {
|
||||
impl TempTable {
|
||||
/// Creates a [`TempTable`] from a config.
|
||||
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
|
||||
let table = TextTable::new(vec![
|
||||
SimpleSortableColumn::new_flex("Sensor".into(), None, false, 0.8),
|
||||
SimpleSortableColumn::new_hard("Temp".into(), None, false, Some(5)),
|
||||
])
|
||||
.default_ltr(false);
|
||||
.default_ltr(false)
|
||||
.try_show_gap(app_config_fields.table_gap);
|
||||
|
||||
Self {
|
||||
table,
|
||||
@ -43,9 +45,7 @@ impl Default for TempTable {
|
||||
show_scroll_index: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TempTable {
|
||||
/// Sets the [`TemperatureType`] for the [`TempTable`].
|
||||
pub fn set_temp_type(mut self, temp_type: TemperatureType) -> Self {
|
||||
self.temp_type = temp_type;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
use indextree::{Arena, NodeId};
|
||||
@ -17,47 +17,18 @@ use crate::{
|
||||
app::{
|
||||
self,
|
||||
layout_manager::{generate_layout, ColLayout, LayoutNode, RowLayout},
|
||||
text_table::TextTableData,
|
||||
widgets::{Component, Widget},
|
||||
DialogState, TmpBottomWidget,
|
||||
},
|
||||
constants::*,
|
||||
data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData},
|
||||
options::Config,
|
||||
utils::error,
|
||||
utils::error::BottomError,
|
||||
Pid,
|
||||
};
|
||||
|
||||
mod canvas_colours;
|
||||
mod dialogs;
|
||||
|
||||
/// Point is of time, data
|
||||
type Point = (f64, f64);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DisplayableData {
|
||||
pub rx_display: String,
|
||||
pub tx_display: String,
|
||||
pub total_rx_display: String,
|
||||
pub total_tx_display: String,
|
||||
pub network_data_rx: Vec<Point>,
|
||||
pub network_data_tx: Vec<Point>,
|
||||
pub disk_data: TextTableData,
|
||||
pub temp_sensor_data: TextTableData,
|
||||
pub single_process_data: HashMap<Pid, ConvertedProcessData>, // Contains single process data, key is PID
|
||||
pub stringified_process_data_map: HashMap<NodeId, Vec<(Vec<(String, Option<String>)>, bool)>>, // Represents the row and whether it is disabled, key is the widget ID
|
||||
|
||||
pub mem_labels: Option<(String, String)>,
|
||||
pub swap_labels: Option<(String, String)>,
|
||||
pub mem_data: Vec<Point>,
|
||||
pub swap_data: Vec<Point>,
|
||||
|
||||
pub load_avg_data: [f32; 3],
|
||||
pub cpu_data: Vec<ConvertedCpuData>,
|
||||
pub battery_data: Vec<ConvertedBatteryData>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ColourScheme {
|
||||
Default,
|
||||
@ -208,7 +179,7 @@ impl Painter {
|
||||
})
|
||||
.split(vertical_dialog_chunk[1]);
|
||||
|
||||
help_dialog.draw_help(&self, f, middle_dialog_chunk[1]);
|
||||
help_dialog.draw_help(self, f, middle_dialog_chunk[1]);
|
||||
} else if app_state.delete_dialog_state.is_showing_dd {
|
||||
// TODO: [Drawing] Better dd sizing needs the paragraph wrap feature from tui-rs to be pushed to
|
||||
// complete... but for now it's pretty close!
|
||||
@ -306,8 +277,7 @@ impl Painter {
|
||||
fn traverse_and_draw_tree<B: Backend>(
|
||||
node: NodeId, arena: &Arena<LayoutNode>, f: &mut Frame<'_, B>,
|
||||
lookup_map: &mut FxHashMap<NodeId, TmpBottomWidget>, painter: &Painter,
|
||||
canvas_data: &DisplayableData, selected_id: NodeId, offset_x: u16,
|
||||
offset_y: u16,
|
||||
selected_id: NodeId, offset_x: u16, offset_y: u16,
|
||||
) {
|
||||
if let Some(layout_node) = arena.get(node).map(|n| n.get()) {
|
||||
match layout_node {
|
||||
@ -320,7 +290,6 @@ impl Painter {
|
||||
f,
|
||||
lookup_map,
|
||||
painter,
|
||||
canvas_data,
|
||||
selected_id,
|
||||
offset_x + bound.x,
|
||||
offset_y + bound.y,
|
||||
@ -371,23 +340,12 @@ impl Painter {
|
||||
|
||||
let root = &app_state.layout_tree_root;
|
||||
let arena = &mut app_state.layout_tree;
|
||||
let canvas_data = &app_state.canvas_data;
|
||||
let selected_id = app_state.selected_widget;
|
||||
|
||||
generate_layout(*root, arena, draw_area, &app_state.widget_lookup_map);
|
||||
|
||||
let lookup_map = &mut app_state.widget_lookup_map;
|
||||
traverse_and_draw_tree(
|
||||
*root,
|
||||
arena,
|
||||
f,
|
||||
lookup_map,
|
||||
self,
|
||||
canvas_data,
|
||||
selected_id,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
traverse_and_draw_tree(*root, arena, f, lookup_map, self, selected_id, 0, 0);
|
||||
}
|
||||
})?;
|
||||
|
||||
|
@ -10,10 +10,8 @@ pub const STALE_MIN_MILLISECONDS: u64 = 30 * 1000; // Lowest is 30 seconds
|
||||
pub const TIME_CHANGE_MILLISECONDS: u64 = 15 * 1000; // How much to increment each time
|
||||
pub const AUTOHIDE_TIMEOUT_MILLISECONDS: u64 = 5000; // 5 seconds to autohide
|
||||
|
||||
pub const TICK_RATE_IN_MILLISECONDS: u64 = 200;
|
||||
// How fast the screen refreshes
|
||||
pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u64 = 1000;
|
||||
pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u64 = 1000;
|
||||
|
||||
// Limits for when we should stop showing table gaps/labels (anything less means not shown)
|
||||
pub const TABLE_GAP_HEIGHT_LIMIT: u16 = 5;
|
||||
@ -30,14 +28,6 @@ pub const MAX_SIGNAL: usize = 31;
|
||||
// Side borders
|
||||
pub static SIDE_BORDERS: Lazy<tui::widgets::Borders> =
|
||||
Lazy::new(|| tui::widgets::Borders::from_bits_truncate(20));
|
||||
pub static TOP_LEFT_RIGHT: Lazy<tui::widgets::Borders> =
|
||||
Lazy::new(|| tui::widgets::Borders::from_bits_truncate(22));
|
||||
pub static BOTTOM_LEFT_RIGHT: Lazy<tui::widgets::Borders> =
|
||||
Lazy::new(|| tui::widgets::Borders::from_bits_truncate(28));
|
||||
pub static DEFAULT_TEXT_STYLE: Lazy<tui::style::Style> =
|
||||
Lazy::new(|| tui::style::Style::default().fg(tui::style::Color::Gray));
|
||||
pub static DEFAULT_HEADER_STYLE: Lazy<tui::style::Style> =
|
||||
Lazy::new(|| tui::style::Style::default().fg(tui::style::Color::LightBlue));
|
||||
|
||||
// Colour profiles
|
||||
pub static DEFAULT_LIGHT_MODE_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigColours {
|
||||
|
@ -546,16 +546,6 @@ pub fn convert_network_data_points(
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ProcessGroupingType {
|
||||
Grouped,
|
||||
Ungrouped,
|
||||
}
|
||||
|
||||
pub enum ProcessNamingType {
|
||||
Name,
|
||||
Path,
|
||||
}
|
||||
|
||||
/// Given read/s, write/s, total read, and total write values, return 4 strings that represent read/s, write/s, total read, and total write
|
||||
pub fn get_disk_io_strings(
|
||||
rps: u64, wps: u64, total_read: u64, total_write: u64,
|
||||
@ -579,142 +569,15 @@ pub fn get_string_with_bytes(value: u64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Because we needed to UPDATE data entries rather than REPLACING entries, we instead update
|
||||
/// the existing vector.
|
||||
pub fn convert_process_data(
|
||||
current_data: &DataCollection,
|
||||
existing_converted_process_data: &mut HashMap<Pid, ConvertedProcessData>,
|
||||
#[cfg(target_family = "unix")] user_table: &mut data_harvester::processes::UserTable,
|
||||
) {
|
||||
// TODO: [Feature] Thread highlighting and hiding support; can we also count number of threads per process and display it as a column?
|
||||
// For macOS see https://github.com/hishamhm/htop/pull/848/files
|
||||
|
||||
let mut complete_pid_set: fxhash::FxHashSet<Pid> =
|
||||
existing_converted_process_data.keys().copied().collect();
|
||||
|
||||
for process in ¤t_data.process_harvest {
|
||||
let (read_per_sec, write_per_sec, total_read, total_write) = get_disk_io_strings(
|
||||
process.read_bytes_per_sec,
|
||||
process.write_bytes_per_sec,
|
||||
process.total_read_bytes,
|
||||
process.total_write_bytes,
|
||||
);
|
||||
|
||||
let mem_usage_str = get_binary_bytes(process.mem_usage_bytes);
|
||||
|
||||
let user = {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
user_table.get_uid_to_username_mapping(process.uid).ok()
|
||||
}
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
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 = mem_usage_str;
|
||||
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;
|
||||
process_entry.user = user;
|
||||
} 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,
|
||||
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,
|
||||
user,
|
||||
};
|
||||
}
|
||||
} 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,
|
||||
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,
|
||||
user,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = '└';
|
||||
const BRANCH_VERTICAL: char = '│';
|
||||
const BRANCH_SPLIT: char = '├';
|
||||
const BRANCH_HORIZONTAL: char = '─';
|
||||
|
||||
fn tree_process_data(
|
||||
filtered_process_data: &[ConvertedProcessData], is_using_command: bool,
|
||||
sorting_type: &ProcessSorting, is_sort_descending: bool,
|
||||
) -> Vec<ConvertedProcessData> {
|
||||
const BRANCH_ENDING: char = '└';
|
||||
const BRANCH_VERTICAL: char = '│';
|
||||
const BRANCH_SPLIT: char = '├';
|
||||
const BRANCH_HORIZONTAL: char = '─';
|
||||
|
||||
// TODO: [Feature] Option to sort usage by total branch usage or individual value usage?
|
||||
|
||||
// Let's first build up a (really terrible) parent -> child mapping...
|
||||
@ -1178,166 +1041,6 @@ fn tree_process_data(
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
// FIXME: [URGENT] Delete this
|
||||
// // TODO: [Optimization] This is an easy target for optimization, too many to_strings!
|
||||
// fn stringify_process_data(
|
||||
// proc_widget_state: &ProcWidgetState, finalized_process_data: &[ConvertedProcessData],
|
||||
// ) -> Vec<(Vec<(String, Option<String>)>, bool)> {
|
||||
// let is_proc_widget_grouped = proc_widget_state.is_grouped;
|
||||
// let is_using_command = proc_widget_state.is_using_command;
|
||||
// let is_tree = proc_widget_state.is_tree_mode;
|
||||
// let mem_enabled = proc_widget_state.columns.is_enabled(&ProcessSorting::Mem);
|
||||
|
||||
// finalized_process_data
|
||||
// .iter()
|
||||
// .map(|process| {
|
||||
// (
|
||||
// vec![
|
||||
// (
|
||||
// if is_proc_widget_grouped {
|
||||
// process.group_pids.len().to_string()
|
||||
// } else {
|
||||
// process.pid.to_string()
|
||||
// },
|
||||
// None,
|
||||
// ),
|
||||
// (
|
||||
// if is_tree {
|
||||
// if let Some(prefix) = &process.process_description_prefix {
|
||||
// prefix.clone()
|
||||
// } else {
|
||||
// String::default()
|
||||
// }
|
||||
// } else if is_using_command {
|
||||
// process.command.clone()
|
||||
// } else {
|
||||
// process.name.clone()
|
||||
// },
|
||||
// None,
|
||||
// ),
|
||||
// (format!("{:.1}%", process.cpu_percent_usage), None),
|
||||
// (
|
||||
// if mem_enabled {
|
||||
// if process.mem_usage_bytes <= GIBI_LIMIT {
|
||||
// format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1)
|
||||
// } else {
|
||||
// format!("{:.1}{}", process.mem_usage_str.0, process.mem_usage_str.1)
|
||||
// }
|
||||
// } else {
|
||||
// format!("{:.1}%", process.mem_percent_usage)
|
||||
// },
|
||||
// None,
|
||||
// ),
|
||||
// (process.read_per_sec.clone(), None),
|
||||
// (process.write_per_sec.clone(), None),
|
||||
// (process.total_read.clone(), None),
|
||||
// (process.total_write.clone(), None),
|
||||
// #[cfg(target_family = "unix")]
|
||||
// (
|
||||
// if let Some(user) = &process.user {
|
||||
// user.clone()
|
||||
// } else {
|
||||
// "N/A".to_string()
|
||||
// },
|
||||
// None,
|
||||
// ),
|
||||
// (
|
||||
// process.process_state.clone(),
|
||||
// Some(process.process_char.to_string()),
|
||||
// ),
|
||||
// ],
|
||||
// process.is_disabled_entry,
|
||||
// )
|
||||
// })
|
||||
// .collect()
|
||||
// }
|
||||
|
||||
/// Takes a set of converted process data and groups it together.
|
||||
///
|
||||
/// To be honest, I really don't like how this is done, even though I've rewritten this like 3 times.
|
||||
fn group_process_data(
|
||||
single_process_data: &[ConvertedProcessData], is_using_command: bool,
|
||||
) -> Vec<ConvertedProcessData> {
|
||||
#[derive(Clone, Default, Debug)]
|
||||
struct SingleProcessData {
|
||||
pub pid: Pid,
|
||||
pub cpu_percent_usage: f64,
|
||||
pub mem_percent_usage: f64,
|
||||
pub mem_usage_bytes: u64,
|
||||
pub group_pids: Vec<Pid>,
|
||||
pub read_per_sec: f64,
|
||||
pub write_per_sec: f64,
|
||||
pub total_read: f64,
|
||||
pub total_write: f64,
|
||||
pub process_state: String,
|
||||
}
|
||||
|
||||
let mut grouped_hashmap: HashMap<String, SingleProcessData> = std::collections::HashMap::new();
|
||||
|
||||
single_process_data.iter().for_each(|process| {
|
||||
let entry = grouped_hashmap
|
||||
.entry(if is_using_command {
|
||||
process.command.to_string()
|
||||
} else {
|
||||
process.name.to_string()
|
||||
})
|
||||
.or_insert(SingleProcessData {
|
||||
pid: process.pid,
|
||||
..SingleProcessData::default()
|
||||
});
|
||||
|
||||
(*entry).cpu_percent_usage += process.cpu_percent_usage;
|
||||
(*entry).mem_percent_usage += process.mem_percent_usage;
|
||||
(*entry).mem_usage_bytes += process.mem_usage_bytes;
|
||||
(*entry).group_pids.push(process.pid);
|
||||
(*entry).read_per_sec += process.rps_f64;
|
||||
(*entry).write_per_sec += process.wps_f64;
|
||||
(*entry).total_read += process.tr_f64;
|
||||
(*entry).total_write += process.tw_f64;
|
||||
});
|
||||
|
||||
grouped_hashmap
|
||||
.iter()
|
||||
.map(|(identifier, process_details)| {
|
||||
let p = process_details.clone();
|
||||
|
||||
let (read_per_sec, write_per_sec, total_read, total_write) = get_disk_io_strings(
|
||||
p.read_per_sec as u64,
|
||||
p.write_per_sec as u64,
|
||||
p.total_read as u64,
|
||||
p.total_write as u64,
|
||||
);
|
||||
|
||||
ConvertedProcessData {
|
||||
pid: p.pid,
|
||||
ppid: None,
|
||||
is_thread: None,
|
||||
name: identifier.to_string(),
|
||||
command: identifier.to_string(),
|
||||
cpu_percent_usage: p.cpu_percent_usage,
|
||||
mem_percent_usage: p.mem_percent_usage,
|
||||
mem_usage_bytes: p.mem_usage_bytes,
|
||||
mem_usage_str: get_decimal_bytes(p.mem_usage_bytes),
|
||||
group_pids: p.group_pids,
|
||||
read_per_sec,
|
||||
write_per_sec,
|
||||
total_read,
|
||||
total_write,
|
||||
rps_f64: p.read_per_sec,
|
||||
wps_f64: p.write_per_sec,
|
||||
tr_f64: p.total_read,
|
||||
tw_f64: p.total_write,
|
||||
process_state: p.process_state,
|
||||
process_description_prefix: None,
|
||||
process_char: char::default(),
|
||||
is_disabled_entry: false,
|
||||
is_collapsed_entry: false,
|
||||
user: None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[cfg(feature = "battery")]
|
||||
pub fn convert_battery_harvest(current_data: &DataCollection) -> Vec<ConvertedBatteryData> {
|
||||
current_data
|
||||
|
@ -42,7 +42,7 @@ pub mod clap;
|
||||
pub mod constants;
|
||||
pub mod data_conversion;
|
||||
pub mod options;
|
||||
pub mod units;
|
||||
pub(crate) mod units;
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
pub type Pid = usize;
|
||||
|
@ -211,11 +211,7 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
|
||||
hide_time: get_hide_time(matches, config),
|
||||
autohide_time,
|
||||
use_old_network_legend: get_use_old_network_legend(matches, config),
|
||||
table_gap: if get_hide_table_gap(matches, config) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
},
|
||||
table_gap: !get_hide_table_gap(matches, config),
|
||||
disable_click: get_disable_click(matches, config),
|
||||
// no_write: get_no_write(matches, config),
|
||||
no_write: false,
|
||||
@ -551,18 +547,6 @@ fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) -> bool
|
||||
false
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn get_no_write(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||
if matches.is_present("no_write") {
|
||||
return true;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(no_write) = flags.no_write {
|
||||
return no_write;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> error::Result<Option<Filter>> {
|
||||
if let Some(ignore_list) = ignore_list {
|
||||
let list: Result<Vec<_>, _> = ignore_list
|
||||
|
@ -31,9 +31,6 @@ pub enum BottomError {
|
||||
/// An error to represent errors with querying.
|
||||
#[error("Query error, {0}")]
|
||||
QueryError(Cow<'static, str>),
|
||||
/// An error that just signifies something minor went wrong; no message.
|
||||
#[error("Minor error.")]
|
||||
MinorError,
|
||||
/// An error to represent errors with procfs
|
||||
#[cfg(target_os = "linux")]
|
||||
#[error("Procfs error, {0}")]
|
||||
|
@ -7,7 +7,6 @@ pub const TERA_LIMIT: u64 = 1_000_000_000_000;
|
||||
pub const KIBI_LIMIT: u64 = 1024;
|
||||
pub const MEBI_LIMIT: u64 = 1_048_576;
|
||||
pub const GIBI_LIMIT: u64 = 1_073_741_824;
|
||||
pub const TEBI_LIMIT: u64 = 1_099_511_627_776;
|
||||
|
||||
pub const KILO_LIMIT_F64: f64 = 1000.0;
|
||||
pub const MEGA_LIMIT_F64: f64 = 1_000_000.0;
|
||||
@ -30,16 +29,6 @@ pub const LOG_GIBI_LIMIT: f64 = 30.0;
|
||||
pub const LOG_TEBI_LIMIT: f64 = 40.0;
|
||||
pub const LOG_PEBI_LIMIT: f64 = 50.0;
|
||||
|
||||
pub const LOG_KILO_LIMIT_U32: u32 = 3;
|
||||
pub const LOG_MEGA_LIMIT_U32: u32 = 6;
|
||||
pub const LOG_GIGA_LIMIT_U32: u32 = 9;
|
||||
pub const LOG_TERA_LIMIT_U32: u32 = 12;
|
||||
|
||||
pub const LOG_KIBI_LIMIT_U32: u32 = 10;
|
||||
pub const LOG_MEBI_LIMIT_U32: u32 = 20;
|
||||
pub const LOG_GIBI_LIMIT_U32: u32 = 30;
|
||||
pub const LOG_TEBI_LIMIT_U32: u32 = 40;
|
||||
|
||||
/// Returns a tuple containing the value and the unit in bytes. In units of 1024.
|
||||
/// This only supports up to a tebi. Note the "single" unit will have a space appended to match the others if
|
||||
/// `spacing` is true.
|
||||
|
Loading…
x
Reference in New Issue
Block a user