mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-28 15:10:34 +00:00
feature: Adds tree view (#223)
Adds a tree process view to bottom. Currently uses a pretty jank method of column width setting, should get fixed in #225.
This commit is contained in:
parent
0d8572c692
commit
eb8295c430
22 changed files with 719 additions and 242 deletions
|
@ -1,5 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Running pre-push hook:"
|
||||
|
||||
echo "Executing: cargo +nightly clippy -- -D clippy::all"
|
||||
|
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -46,10 +46,13 @@
|
|||
"fract",
|
||||
"gnueabihf",
|
||||
"gotop",
|
||||
"gotop's",
|
||||
"gtop",
|
||||
"haase",
|
||||
"heim",
|
||||
"hjkl",
|
||||
"htop",
|
||||
"indexmap",
|
||||
"libc",
|
||||
"markdownlint",
|
||||
"memb",
|
||||
|
@ -62,6 +65,7 @@
|
|||
"nvme",
|
||||
"paren",
|
||||
"pmem",
|
||||
"ppid",
|
||||
"prepush",
|
||||
"processthreadsapi",
|
||||
"regexes",
|
||||
|
|
|
@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- [#220](https://github.com/ClementTsang/bottom/pull/220): Add ability to hide specific temperature and disk entries via config.
|
||||
|
||||
- [#223](https://github.com/ClementTsang/bottom/pull/223): Add tree mode for processes.
|
||||
|
||||
### Changes
|
||||
|
||||
- [#213](https://github.com/ClementTsang/bottom/pull/213), [#214](https://github.com/ClementTsang/bottom/pull/214): Updated help descriptions, added auto-complete generation.
|
||||
|
|
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -150,6 +150,7 @@ dependencies = [
|
|||
"fern",
|
||||
"futures",
|
||||
"heim",
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
|
@ -534,6 +535,12 @@ version = "0.21.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7"
|
||||
|
||||
[[package]]
|
||||
name = "heim"
|
||||
version = "0.0.10"
|
||||
|
@ -723,6 +730,16 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
|
|
|
@ -32,6 +32,7 @@ ctrlc = {version = "3.1", features = ["termination"]}
|
|||
clap = "2.33"
|
||||
dirs = "3.0.1"
|
||||
futures = "0.3.5"
|
||||
indexmap = "1.6.0"
|
||||
itertools = "0.9.0"
|
||||
libc = "0.2"
|
||||
regex = "1.3"
|
||||
|
|
47
README.md
47
README.md
|
@ -41,6 +41,7 @@ A cross-platform graphical process/system monitor with a customizable interface
|
|||
- [Processes](#processes)
|
||||
- [Process searching](#process-searching)
|
||||
- [Process sorting](#process-sorting)
|
||||
- [Tree mode](#tree-mode)
|
||||
- [Zoom](#zoom)
|
||||
- [Expanding](#expanding)
|
||||
- [Basic mode](#basic-mode)
|
||||
|
@ -246,23 +247,24 @@ Run using `btm`.
|
|||
| `s, F6` | Open process sort widget |
|
||||
| `I` | Invert current sort |
|
||||
| `%` | Toggle between values and percentages for memory usage |
|
||||
| `t`, `F5` | Toggle tree mode |
|
||||
|
||||
#### Process search bindings
|
||||
|
||||
| | |
|
||||
| ------------ | -------------------------------------------- |
|
||||
| `Tab` | Toggle between searching by PID or name |
|
||||
| `Esc` | Close the search widget (retains the filter) |
|
||||
| `Ctrl-a` | Skip to the start of the search query |
|
||||
| `Ctrl-e` | Skip to the end of the search query |
|
||||
| `Ctrl-u` | Clear the current search query |
|
||||
| `Backspace` | Delete the character behind the cursor |
|
||||
| `Delete` | Delete the character at the cursor |
|
||||
| `Alt-c`/`F1` | Toggle matching case |
|
||||
| `Alt-w`/`F2` | Toggle matching the entire word |
|
||||
| `Alt-r`/`F3` | Toggle using regex |
|
||||
| `Left` | Move cursor left |
|
||||
| `Right` | Move cursor right |
|
||||
| | |
|
||||
| ------------- | -------------------------------------------- |
|
||||
| `Tab` | Toggle between searching by PID or name |
|
||||
| `Esc` | Close the search widget (retains the filter) |
|
||||
| `Ctrl-a` | Skip to the start of the search query |
|
||||
| `Ctrl-e` | Skip to the end of the search query |
|
||||
| `Ctrl-u` | Clear the current search query |
|
||||
| `Backspace` | Delete the character behind the cursor |
|
||||
| `Delete` | Delete the character at the cursor |
|
||||
| `Alt-c`, `F1` | Toggle matching case |
|
||||
| `Alt-w`, `F2` | Toggle matching the entire word |
|
||||
| `Alt-r`, `F3` | Toggle using regex |
|
||||
| `Left` | Move cursor left |
|
||||
| `Right` | Move cursor right |
|
||||
|
||||
### Process sort bindings
|
||||
|
||||
|
@ -424,6 +426,23 @@ You can sort the processes list by any column you want by pressing `s` while on
|
|||
|
||||
![sorting](assets/sort.png)
|
||||
|
||||
#### Tree mode
|
||||
|
||||
Use `t` or `F5` to toggle tree mode in a process widget. This is somewhat similar to htop's tree
|
||||
mode.
|
||||
|
||||
![Standard tree](assets/trees_1.png)
|
||||
|
||||
Sorting works as well, but it is done per groups of siblings. For example, by CPU%:
|
||||
|
||||
![Standard tree](assets/trees_2.png)
|
||||
|
||||
You can also still filter processes. Branches that entirely do not match the query are pruned out,
|
||||
but if a branch contains an element that does match the query, any non-matching elements will instead
|
||||
just be greyed out, so the tree structure is still maintained:
|
||||
|
||||
![Standard tree](assets/trees_3.png)
|
||||
|
||||
### Zoom
|
||||
|
||||
Using the `+`/`-` keys or the scroll wheel will move the current time intervals of the currently selected widget, and `=` to reset the zoom levels to the default.
|
||||
|
|
BIN
assets/trees_1.png
Normal file
BIN
assets/trees_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 361 KiB |
BIN
assets/trees_2.png
Normal file
BIN
assets/trees_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 KiB |
BIN
assets/trees_3.png
Normal file
BIN
assets/trees_3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
113
src/app.rs
113
src/app.rs
|
@ -13,6 +13,7 @@ pub use states::*;
|
|||
use crate::{
|
||||
canvas, constants,
|
||||
utils::error::{BottomError, Result},
|
||||
Pid,
|
||||
};
|
||||
|
||||
pub mod data_farmer;
|
||||
|
@ -67,7 +68,7 @@ pub struct App {
|
|||
pub dd_err: Option<String>,
|
||||
|
||||
#[builder(default, setter(skip))]
|
||||
to_delete_process_list: Option<(String, Vec<u32>)>,
|
||||
to_delete_process_list: Option<(String, Vec<Pid>)>,
|
||||
|
||||
#[builder(default = false, setter(skip))]
|
||||
pub is_frozen: bool,
|
||||
|
@ -265,37 +266,40 @@ impl App {
|
|||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
// Toggles process widget grouping state
|
||||
proc_widget_state.is_grouped = !(proc_widget_state.is_grouped);
|
||||
// Do NOT allow when in tree mode!
|
||||
if !proc_widget_state.is_tree_mode {
|
||||
// Toggles process widget grouping state
|
||||
proc_widget_state.is_grouped = !(proc_widget_state.is_grouped);
|
||||
|
||||
// Forcefully switch off column if we were on it...
|
||||
if (proc_widget_state.is_grouped
|
||||
&& proc_widget_state.process_sorting_type
|
||||
== data_harvester::processes::ProcessSorting::Pid)
|
||||
|| (!proc_widget_state.is_grouped
|
||||
// Forcefully switch off column if we were on it...
|
||||
if (proc_widget_state.is_grouped
|
||||
&& proc_widget_state.process_sorting_type
|
||||
== data_harvester::processes::ProcessSorting::Count)
|
||||
{
|
||||
proc_widget_state.process_sorting_type =
|
||||
data_harvester::processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
|
||||
proc_widget_state.process_sorting_reverse = true;
|
||||
== data_harvester::processes::ProcessSorting::Pid)
|
||||
|| (!proc_widget_state.is_grouped
|
||||
&& proc_widget_state.process_sorting_type
|
||||
== data_harvester::processes::ProcessSorting::Count)
|
||||
{
|
||||
proc_widget_state.process_sorting_type =
|
||||
data_harvester::processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
|
||||
proc_widget_state.is_process_sort_descending = true;
|
||||
}
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
.column_mapping
|
||||
.get_mut(&processes::ProcessSorting::State)
|
||||
.unwrap()
|
||||
.enabled = !(proc_widget_state.is_grouped);
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
.toggle(&processes::ProcessSorting::Count);
|
||||
proc_widget_state
|
||||
.columns
|
||||
.toggle(&processes::ProcessSorting::Pid);
|
||||
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
}
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
.column_mapping
|
||||
.get_mut(&processes::ProcessSorting::State)
|
||||
.unwrap()
|
||||
.enabled = !(proc_widget_state.is_grouped);
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
.toggle(&processes::ProcessSorting::Count);
|
||||
proc_widget_state
|
||||
.columns
|
||||
.toggle(&processes::ProcessSorting::Pid);
|
||||
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -384,8 +388,8 @@ impl App {
|
|||
};
|
||||
|
||||
if let Some(proc_widget_state) = self.proc_state.get_mut_widget_state(widget_id) {
|
||||
proc_widget_state.process_sorting_reverse =
|
||||
!proc_widget_state.process_sorting_reverse;
|
||||
proc_widget_state.is_process_sort_descending =
|
||||
!proc_widget_state.is_process_sort_descending;
|
||||
|
||||
self.proc_state.force_update = Some(widget_id);
|
||||
}
|
||||
|
@ -483,6 +487,24 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn toggle_tree_mode(&mut self) {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.widget_states
|
||||
.get_mut(&(self.current_widget.widget_id))
|
||||
{
|
||||
proc_widget_state.is_tree_mode = !proc_widget_state.is_tree_mode;
|
||||
|
||||
if proc_widget_state.is_tree_mode {
|
||||
// We enabled... set PID sort type to ascending.
|
||||
proc_widget_state.process_sorting_type = processes::ProcessSorting::Pid;
|
||||
proc_widget_state.is_process_sort_descending = false;
|
||||
}
|
||||
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// One of two functions allowed to run while in a dialog...
|
||||
pub fn on_enter(&mut self) {
|
||||
if self.delete_dialog_state.is_showing_dd {
|
||||
|
@ -889,7 +911,7 @@ impl App {
|
|||
if proc_widget_state.scroll_state.current_scroll_position
|
||||
< corresponding_filtered_process_list.len()
|
||||
{
|
||||
let current_process: (String, Vec<u32>);
|
||||
let current_process: (String, Vec<Pid>);
|
||||
if self.is_grouped(self.current_widget.widget_id) {
|
||||
if let Some(process) = &corresponding_filtered_process_list
|
||||
.get(proc_widget_state.scroll_state.current_scroll_position)
|
||||
|
@ -1069,13 +1091,13 @@ impl App {
|
|||
{
|
||||
match proc_widget_state.process_sorting_type {
|
||||
processes::ProcessSorting::CpuPercent => {
|
||||
proc_widget_state.process_sorting_reverse =
|
||||
!proc_widget_state.process_sorting_reverse
|
||||
proc_widget_state.is_process_sort_descending =
|
||||
!proc_widget_state.is_process_sort_descending
|
||||
}
|
||||
_ => {
|
||||
proc_widget_state.process_sorting_type =
|
||||
processes::ProcessSorting::CpuPercent;
|
||||
proc_widget_state.process_sorting_reverse = true;
|
||||
proc_widget_state.is_process_sort_descending = true;
|
||||
}
|
||||
}
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
|
@ -1092,13 +1114,13 @@ impl App {
|
|||
{
|
||||
match proc_widget_state.process_sorting_type {
|
||||
processes::ProcessSorting::MemPercent => {
|
||||
proc_widget_state.process_sorting_reverse =
|
||||
!proc_widget_state.process_sorting_reverse
|
||||
proc_widget_state.is_process_sort_descending =
|
||||
!proc_widget_state.is_process_sort_descending
|
||||
}
|
||||
_ => {
|
||||
proc_widget_state.process_sorting_type =
|
||||
processes::ProcessSorting::MemPercent;
|
||||
proc_widget_state.process_sorting_reverse = true;
|
||||
proc_widget_state.is_process_sort_descending = true;
|
||||
}
|
||||
}
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
|
@ -1116,13 +1138,13 @@ impl App {
|
|||
if !proc_widget_state.is_grouped {
|
||||
match proc_widget_state.process_sorting_type {
|
||||
processes::ProcessSorting::Pid => {
|
||||
proc_widget_state.process_sorting_reverse =
|
||||
!proc_widget_state.process_sorting_reverse
|
||||
proc_widget_state.is_process_sort_descending =
|
||||
!proc_widget_state.is_process_sort_descending
|
||||
}
|
||||
_ => {
|
||||
proc_widget_state.process_sorting_type =
|
||||
processes::ProcessSorting::Pid;
|
||||
proc_widget_state.process_sorting_reverse = false;
|
||||
proc_widget_state.is_process_sort_descending = false;
|
||||
}
|
||||
}
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
|
@ -1168,8 +1190,8 @@ impl App {
|
|||
match proc_widget_state.process_sorting_type {
|
||||
processes::ProcessSorting::ProcessName
|
||||
| processes::ProcessSorting::Command => {
|
||||
proc_widget_state.process_sorting_reverse =
|
||||
!proc_widget_state.process_sorting_reverse
|
||||
proc_widget_state.is_process_sort_descending =
|
||||
!proc_widget_state.is_process_sort_descending
|
||||
}
|
||||
_ => {
|
||||
proc_widget_state.process_sorting_type =
|
||||
|
@ -1178,7 +1200,7 @@ impl App {
|
|||
} else {
|
||||
processes::ProcessSorting::ProcessName
|
||||
};
|
||||
proc_widget_state.process_sorting_reverse = false;
|
||||
proc_widget_state.is_process_sort_descending = false;
|
||||
}
|
||||
}
|
||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
|
@ -1194,6 +1216,7 @@ impl App {
|
|||
'L' | 'D' => self.move_widget_selection(&WidgetDirection::Right),
|
||||
'K' | 'W' => self.move_widget_selection(&WidgetDirection::Up),
|
||||
'J' | 'S' => self.move_widget_selection(&WidgetDirection::Down),
|
||||
't' => self.toggle_tree_mode(),
|
||||
'+' => self.zoom_in(),
|
||||
'-' => self.zoom_out(),
|
||||
'=' => self.reset_zoom(),
|
||||
|
@ -1228,7 +1251,7 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_to_delete_processes(&self) -> Option<(String, Vec<u32>)> {
|
||||
pub fn get_to_delete_processes(&self) -> Option<(String, Vec<Pid>)> {
|
||||
self.to_delete_process_list.clone()
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ pub struct DataCollector {
|
|||
pub data: Data,
|
||||
sys: System,
|
||||
#[cfg(target_os = "linux")]
|
||||
pid_mapping: HashMap<u32, processes::PrevProcDetails>,
|
||||
pid_mapping: HashMap<crate::Pid, processes::PrevProcDetails>,
|
||||
#[cfg(target_os = "linux")]
|
||||
prev_idle: f64,
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::Pid;
|
||||
use std::path::PathBuf;
|
||||
use sysinfo::ProcessStatus;
|
||||
|
||||
|
@ -59,7 +60,8 @@ impl Default for ProcessSorting {
|
|||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProcessHarvest {
|
||||
pub pid: u32,
|
||||
pub pid: Pid,
|
||||
pub parent_pid: Option<Pid>, // Remember, parent_pid 0 is root...
|
||||
pub cpu_usage_percent: f64,
|
||||
pub mem_usage_percent: f64,
|
||||
pub mem_usage_bytes: u64,
|
||||
|
@ -89,7 +91,7 @@ pub struct PrevProcDetails {
|
|||
}
|
||||
|
||||
impl PrevProcDetails {
|
||||
pub fn new(pid: u32) -> Self {
|
||||
pub fn new(pid: Pid) -> Self {
|
||||
PrevProcDetails {
|
||||
proc_io_path: PathBuf::from(format!("/proc/{}/io", pid)),
|
||||
proc_exe_path: PathBuf::from(format!("/proc/{}/exe", pid)),
|
||||
|
@ -200,7 +202,7 @@ fn read_path_contents(path: &PathBuf) -> std::io::Result<String> {
|
|||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn get_linux_process_state(stat: &[&str]) -> (char, String) {
|
||||
// The -2 offset is because of us cutting off name + pid
|
||||
// The -2 offset is because of us cutting off name + pid, normally it's 2
|
||||
if let Some(first_char) = stat[0].chars().collect::<Vec<char>>().first() {
|
||||
(
|
||||
*first_char,
|
||||
|
@ -241,8 +243,8 @@ fn get_linux_cpu_usage(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn read_proc<S: core::hash::BuildHasher>(
|
||||
pid: u32, cpu_usage: f64, cpu_fraction: f64,
|
||||
pid_mapping: &mut HashMap<u32, PrevProcDetails, S>, use_current_cpu_total: bool,
|
||||
pid: Pid, cpu_usage: f64, cpu_fraction: f64,
|
||||
pid_mapping: &mut HashMap<Pid, PrevProcDetails, S>, use_current_cpu_total: bool,
|
||||
time_difference_in_secs: u64, mem_total_kb: u64, page_file_kb: u64,
|
||||
) -> error::Result<ProcessHarvest> {
|
||||
let pid_stat = pid_mapping
|
||||
|
@ -282,6 +284,7 @@ fn read_proc<S: core::hash::BuildHasher>(
|
|||
&mut pid_stat.cpu_time,
|
||||
use_current_cpu_total,
|
||||
)?;
|
||||
let parent_pid = stat[1].parse::<Pid>().ok();
|
||||
let (_vsize, rss) = get_linux_process_vsize_rss(&stat);
|
||||
let mem_usage_kb = rss * page_file_kb;
|
||||
let mem_usage_percent = mem_usage_kb as f64 / mem_total_kb as f64 * 100.0;
|
||||
|
@ -320,6 +323,7 @@ fn read_proc<S: core::hash::BuildHasher>(
|
|||
|
||||
Ok(ProcessHarvest {
|
||||
pid,
|
||||
parent_pid,
|
||||
name,
|
||||
command,
|
||||
mem_usage_percent,
|
||||
|
@ -337,14 +341,16 @@ fn read_proc<S: core::hash::BuildHasher>(
|
|||
#[cfg(target_os = "linux")]
|
||||
pub fn linux_get_processes_list(
|
||||
prev_idle: &mut f64, prev_non_idle: &mut f64,
|
||||
pid_mapping: &mut HashMap<u32, PrevProcDetails, RandomState>, use_current_cpu_total: bool,
|
||||
pid_mapping: &mut HashMap<Pid, PrevProcDetails, RandomState>, use_current_cpu_total: bool,
|
||||
time_difference_in_secs: u64, mem_total_kb: u64, page_file_kb: u64,
|
||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||
// TODO: [PROC THREADS] Add threads
|
||||
|
||||
if let Ok((cpu_usage, cpu_fraction)) = cpu_usage_calculation(prev_idle, prev_non_idle) {
|
||||
let process_vector: Vec<ProcessHarvest> = std::fs::read_dir("/proc")?
|
||||
.filter_map(|dir| {
|
||||
if let Ok(dir) = dir {
|
||||
let pid = dir.file_name().to_string_lossy().trim().parse::<u32>();
|
||||
let pid = dir.file_name().to_string_lossy().trim().parse::<Pid>();
|
||||
if let Ok(pid) = pid {
|
||||
// I skip checking if the path is also a directory, it's not needed I think?
|
||||
if let Ok(process_object) = read_proc(
|
||||
|
@ -424,7 +430,8 @@ pub fn windows_macos_get_processes_list(
|
|||
let disk_usage = process_val.disk_usage();
|
||||
|
||||
process_vector.push(ProcessHarvest {
|
||||
pid: process_val.pid() as u32,
|
||||
pid: process_val.pid(),
|
||||
parent_pid: process_val.parent(),
|
||||
name,
|
||||
command,
|
||||
mem_usage_percent: if mem_total_kb > 0 {
|
||||
|
|
|
@ -10,6 +10,7 @@ use winapi::{
|
|||
|
||||
/// This file is meant to house (OS specific) implementations on how to kill processes.
|
||||
use crate::utils::error::BottomError;
|
||||
use crate::Pid;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
struct Process(HANDLE);
|
||||
|
@ -31,9 +32,9 @@ impl Process {
|
|||
}
|
||||
|
||||
/// Kills a process, given a PID.
|
||||
pub fn kill_process_given_pid(pid: u32) -> crate::utils::error::Result<()> {
|
||||
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> {
|
||||
if cfg!(target_family = "unix") {
|
||||
#[cfg(any(target_family = "unix"))]
|
||||
{
|
||||
let output = unsafe { libc::kill(pid as i32, libc::SIGTERM) };
|
||||
if output != 0 {
|
||||
|
@ -59,8 +60,8 @@ pub fn kill_process_given_pid(pid: u32) -> crate::utils::error::Result<()> {
|
|||
};
|
||||
}
|
||||
}
|
||||
} else if cfg!(target_os = "windows") {
|
||||
#[cfg(target_os = "windows")]
|
||||
} else if cfg!(target_family = "windows") {
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
let process = Process::open(pid as DWORD)?;
|
||||
process.kill()?;
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::{
|
|||
constants,
|
||||
data_harvester::processes::{self, ProcessSorting},
|
||||
};
|
||||
use ProcessSorting::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScrollDirection {
|
||||
|
@ -159,7 +160,6 @@ pub struct ProcColumn {
|
|||
|
||||
impl Default for ProcColumn {
|
||||
fn default() -> Self {
|
||||
use ProcessSorting::*;
|
||||
let ordered_columns = vec![
|
||||
Count,
|
||||
Pid,
|
||||
|
@ -352,11 +352,12 @@ pub struct ProcWidgetState {
|
|||
pub is_grouped: bool,
|
||||
pub scroll_state: AppScrollWidgetState,
|
||||
pub process_sorting_type: processes::ProcessSorting,
|
||||
pub process_sorting_reverse: bool,
|
||||
pub is_process_sort_descending: bool,
|
||||
pub is_using_command: bool,
|
||||
pub current_column_index: usize,
|
||||
pub is_sort_open: bool,
|
||||
pub columns: ProcColumn,
|
||||
pub is_tree_mode: bool,
|
||||
}
|
||||
|
||||
impl ProcWidgetState {
|
||||
|
@ -390,11 +391,12 @@ impl ProcWidgetState {
|
|||
is_grouped,
|
||||
scroll_state: AppScrollWidgetState::default(),
|
||||
process_sorting_type,
|
||||
process_sorting_reverse: true,
|
||||
is_process_sort_descending: true,
|
||||
is_using_command: false,
|
||||
current_column_index: 0,
|
||||
is_sort_open: false,
|
||||
columns,
|
||||
is_tree_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -422,7 +424,7 @@ impl ProcWidgetState {
|
|||
if let Some(new_sort_type) = self.columns.ordered_columns.get(true_index) {
|
||||
if *new_sort_type == self.process_sorting_type {
|
||||
// Just reverse the search if we're reselecting!
|
||||
self.process_sorting_reverse = !(self.process_sorting_reverse);
|
||||
self.is_process_sort_descending = !(self.is_process_sort_descending);
|
||||
} else {
|
||||
self.process_sorting_type = new_sort_type.clone();
|
||||
match self.process_sorting_type {
|
||||
|
@ -431,7 +433,7 @@ impl ProcWidgetState {
|
|||
| ProcessSorting::ProcessName
|
||||
| ProcessSorting::Command => {
|
||||
// Also invert anything that uses alphabetical sorting by default.
|
||||
self.process_sorting_reverse = false;
|
||||
self.is_process_sort_descending = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ pub struct DisplayableData {
|
|||
pub disk_data: Vec<Vec<String>>,
|
||||
pub temp_sensor_data: Vec<Vec<String>>,
|
||||
pub single_process_data: Vec<ConvertedProcessData>, // Contains single process data
|
||||
pub process_data: Vec<ConvertedProcessData>, // Not the final value, may be grouped or single
|
||||
pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed
|
||||
pub mem_label_percent: String,
|
||||
pub swap_label_percent: String,
|
||||
|
|
|
@ -28,6 +28,7 @@ pub struct CanvasColours {
|
|||
// Full, Medium, Low
|
||||
pub battery_bar_styles: Vec<Style>,
|
||||
pub invalid_query_style: Style,
|
||||
pub disabled_text_style: Style,
|
||||
}
|
||||
|
||||
impl Default for CanvasColours {
|
||||
|
@ -63,7 +64,8 @@ impl Default for CanvasColours {
|
|||
Style::default().fg(Color::Green),
|
||||
Style::default().fg(Color::Green),
|
||||
],
|
||||
invalid_query_style: tui::style::Style::default().fg(tui::style::Color::Red),
|
||||
invalid_query_style: Style::default().fg(tui::style::Color::Red),
|
||||
disabled_text_style: Style::default().fg(Color::DarkGray),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -349,13 +349,11 @@ impl CpuGraphWidget for Painter {
|
|||
get_variable_intrinsic_widths(width as u16, &width_ratios, &CPU_LEGEND_HEADER_LENS);
|
||||
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
||||
|
||||
let (border_and_title_style, highlight_style) = if is_on_widget {
|
||||
(
|
||||
self.colours.highlighted_border_style,
|
||||
self.colours.currently_selected_text_style,
|
||||
)
|
||||
// Note we don't set highlight_style, as it should always be shown for this widget.
|
||||
let border_and_title_style = if is_on_widget {
|
||||
self.colours.highlighted_border_style
|
||||
} else {
|
||||
(self.colours.border_style, self.colours.text_style)
|
||||
self.colours.border_style
|
||||
};
|
||||
|
||||
// Draw
|
||||
|
@ -367,7 +365,7 @@ impl CpuGraphWidget for Painter {
|
|||
.border_style(border_and_title_style),
|
||||
)
|
||||
.header_style(self.colours.table_header_style)
|
||||
.highlight_style(highlight_style)
|
||||
.highlight_style(self.colours.currently_selected_text_style)
|
||||
.widths(
|
||||
&(intrinsic_widths
|
||||
.iter()
|
||||
|
|
|
@ -36,7 +36,7 @@ pub trait ProcessTableWidget {
|
|||
widget_id: u64,
|
||||
);
|
||||
|
||||
/// Draws the process sort box.
|
||||
/// Draws the process search field.
|
||||
/// - `widget_id` represents the widget ID of the search box itself --- NOT the process widget
|
||||
/// state that is stored.
|
||||
///
|
||||
|
@ -173,15 +173,6 @@ impl ProcessTableWidget for Painter {
|
|||
.finalized_process_data_map
|
||||
.get(&widget_id)
|
||||
{
|
||||
// Admittedly this is kinda a hack... but we need to:
|
||||
// * Scroll
|
||||
// * Show/hide elements based on scroll position
|
||||
//
|
||||
// As such, we use a process_counter to know when we've
|
||||
// hit the process we've currently scrolled to.
|
||||
// We also need to move the list - we can
|
||||
// do so by hiding some elements!
|
||||
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
|
@ -217,39 +208,52 @@ impl ProcessTableWidget for Painter {
|
|||
// Draw!
|
||||
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);
|
||||
|
||||
// FIXME: [PROC OPTIMIZE] This can definitely be optimized; string references work fine here!
|
||||
let process_rows = sliced_vec.iter().map(|process| {
|
||||
Row::Data(
|
||||
vec![
|
||||
if is_proc_widget_grouped {
|
||||
process.group_pids.len().to_string()
|
||||
let data = vec![
|
||||
if is_proc_widget_grouped {
|
||||
process.group_pids.len().to_string()
|
||||
} else {
|
||||
process.pid.to_string()
|
||||
},
|
||||
if is_tree {
|
||||
if let Some(prefix) = &process.process_description_prefix {
|
||||
prefix.clone()
|
||||
} else {
|
||||
process.pid.to_string()
|
||||
},
|
||||
if is_using_command {
|
||||
process.command.clone()
|
||||
} else {
|
||||
process.name.clone()
|
||||
},
|
||||
format!("{:.1}%", process.cpu_percent_usage),
|
||||
if mem_enabled {
|
||||
format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1)
|
||||
} else {
|
||||
format!("{:.1}%", process.mem_percent_usage)
|
||||
},
|
||||
process.read_per_sec.to_string(),
|
||||
process.write_per_sec.to_string(),
|
||||
process.total_read.to_string(),
|
||||
process.total_write.to_string(),
|
||||
process.process_state.to_string(),
|
||||
]
|
||||
.into_iter(),
|
||||
)
|
||||
String::default()
|
||||
}
|
||||
} else if is_using_command {
|
||||
process.command.clone()
|
||||
} else {
|
||||
process.name.clone()
|
||||
},
|
||||
format!("{:.1}%", process.cpu_percent_usage),
|
||||
if mem_enabled {
|
||||
format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1)
|
||||
} else {
|
||||
format!("{:.1}%", process.mem_percent_usage)
|
||||
},
|
||||
process.read_per_sec.clone(),
|
||||
process.write_per_sec.clone(),
|
||||
process.total_read.clone(),
|
||||
process.total_write.clone(),
|
||||
process.process_state.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
if process.is_disabled_entry {
|
||||
Row::StyledData(data, self.colours.disabled_text_style)
|
||||
} else {
|
||||
Row::Data(data)
|
||||
}
|
||||
});
|
||||
|
||||
let process_headers = proc_widget_state.columns.get_column_headers(
|
||||
&proc_widget_state.process_sorting_type,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
);
|
||||
|
||||
let process_headers_lens: Vec<usize> = process_headers
|
||||
|
@ -269,6 +273,8 @@ impl ProcessTableWidget for Painter {
|
|||
}
|
||||
} else if proc_widget_state.is_using_command {
|
||||
vec![0.05, 0.7, 0.05, 0.05, 0.03, 0.03, 0.03, 0.03]
|
||||
} else if proc_widget_state.is_tree_mode {
|
||||
vec![0.05, 0.4, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
|
||||
} else {
|
||||
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
|
||||
};
|
||||
|
@ -280,6 +286,7 @@ impl ProcessTableWidget for Painter {
|
|||
let intrinsic_widths =
|
||||
&(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
||||
|
||||
// TODO: gotop's "x out of y" thing is really nice to help keep track of the scroll position.
|
||||
f.render_stateful_widget(
|
||||
Table::new(process_headers.iter(), process_rows)
|
||||
.block(process_block)
|
||||
|
@ -592,6 +599,7 @@ impl ProcessTableWidget for Painter {
|
|||
.iter()
|
||||
.map(|column| Row::Data(vec![column].into_iter()));
|
||||
|
||||
// FIXME: [State] Shorten state to small form if it can't fit...?
|
||||
let column_state = &mut proc_widget_state.columns.column_state;
|
||||
column_state.select(Some(
|
||||
proc_widget_state
|
||||
|
|
|
@ -92,7 +92,7 @@ pub const CPU_HELP_TEXT: [&str; 2] = [
|
|||
|
||||
// TODO [Help]: Search in help?
|
||||
// TODO [Help]: Move to using tables for easier formatting?
|
||||
pub const PROCESS_HELP_TEXT: [&str; 12] = [
|
||||
pub const PROCESS_HELP_TEXT: [&str; 13] = [
|
||||
"3 - Process widget\n",
|
||||
"dd Kill the selected process\n",
|
||||
"c Sort by CPU usage, press again to reverse sorting order\n",
|
||||
|
@ -104,7 +104,8 @@ pub const PROCESS_HELP_TEXT: [&str; 12] = [
|
|||
"P Toggle between showing the full command or just the process name\n",
|
||||
"s, F6 Open process sort widget\n",
|
||||
"I Invert current sort\n",
|
||||
"% Toggle between values and percentages for memory usage",
|
||||
"% Toggle between values and percentages for memory usage\n",
|
||||
"t, F5 Toggle tree mode",
|
||||
];
|
||||
|
||||
pub const SEARCH_HELP_TEXT: [&str; 46] = [
|
||||
|
@ -116,9 +117,9 @@ pub const SEARCH_HELP_TEXT: [&str; 46] = [
|
|||
"Ctrl-u Clear the current search query\n",
|
||||
"Backspace Delete the character behind the cursor\n",
|
||||
"Delete Delete the character at the cursor\n",
|
||||
"Alt-c/F1 Toggle matching case\n",
|
||||
"Alt-w/F2 Toggle matching the entire word\n",
|
||||
"Alt-r/F3 Toggle using regex\n",
|
||||
"Alt-c, F1 Toggle matching case\n",
|
||||
"Alt-w, F2 Toggle matching the entire word\n",
|
||||
"Alt-r, F3 Toggle using regex\n",
|
||||
"Left, Alt-h Move cursor left\n",
|
||||
"Right, Alt-l Move cursor right\n",
|
||||
"\n",
|
||||
|
@ -142,8 +143,8 @@ pub const SEARCH_HELP_TEXT: [&str; 46] = [
|
|||
"<= ex: cpu <= 1\n",
|
||||
"\n",
|
||||
"Logical operators:\n",
|
||||
"and/&&/<Space> ex: btm and cpu > 1 and mem > 1\n",
|
||||
"or/|| ex: btm or firefox\n",
|
||||
"and, &&, <Space> ex: btm and cpu > 1 and mem > 1\n",
|
||||
"or, || ex: btm or firefox\n",
|
||||
"\n",
|
||||
"Supported units:\n",
|
||||
"B ex: read > 1 b\n",
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
//! This mainly concerns converting collected data into things that the canvas
|
||||
//! can actually handle.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::Pid;
|
||||
use crate::{
|
||||
app::{data_farmer, data_harvester, App, Filter},
|
||||
utils::gen_util::*,
|
||||
utils::{self, gen_util::*},
|
||||
};
|
||||
use data_harvester::processes::ProcessSorting;
|
||||
use indexmap::IndexSet;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
/// Point is of time, data
|
||||
type Point = (f64, f64);
|
||||
|
@ -38,16 +39,19 @@ pub struct ConvertedNetworkData {
|
|||
// mean_tx: f64,
|
||||
}
|
||||
|
||||
// TODO: [REFACTOR] Process data... stuff really needs a rewrite. Again.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct ConvertedProcessData {
|
||||
pub pid: u32,
|
||||
pub pid: Pid,
|
||||
pub ppid: Option<Pid>,
|
||||
pub name: String,
|
||||
pub command: String,
|
||||
pub is_thread: Option<bool>,
|
||||
pub cpu_percent_usage: f64,
|
||||
pub mem_percent_usage: f64,
|
||||
pub mem_usage_bytes: u64,
|
||||
pub mem_usage_str: (f64, String),
|
||||
pub group_pids: Vec<u32>,
|
||||
pub group_pids: Vec<Pid>,
|
||||
pub read_per_sec: String,
|
||||
pub write_per_sec: String,
|
||||
pub total_read: String,
|
||||
|
@ -57,20 +61,11 @@ pub struct ConvertedProcessData {
|
|||
pub tr_f64: f64,
|
||||
pub tw_f64: f64,
|
||||
pub process_state: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct SingleProcessData {
|
||||
pub pid: u32,
|
||||
pub cpu_percent_usage: f64,
|
||||
pub mem_percent_usage: f64,
|
||||
pub mem_usage_bytes: u64,
|
||||
pub group_pids: Vec<u32>,
|
||||
pub read_per_sec: u64,
|
||||
pub write_per_sec: u64,
|
||||
pub total_read: u64,
|
||||
pub total_write: u64,
|
||||
pub process_state: String,
|
||||
pub process_char: char,
|
||||
/// Prefix printed before the process when displayed.
|
||||
pub process_description_prefix: Option<String>,
|
||||
/// Whether to mark this process entry as disabled (mostly for tree mode).
|
||||
pub is_disabled_entry: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
|
@ -418,6 +413,9 @@ pub enum ProcessNamingType {
|
|||
pub fn convert_process_data(
|
||||
current_data: &data_farmer::DataCollection,
|
||||
) -> Vec<ConvertedProcessData> {
|
||||
// FIXME: Thread highlighting and hiding support
|
||||
// For macOS see https://github.com/hishamhm/htop/pull/848/files
|
||||
|
||||
current_data
|
||||
.process_harvest
|
||||
.iter()
|
||||
|
@ -437,6 +435,8 @@ pub fn convert_process_data(
|
|||
|
||||
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,
|
||||
|
@ -453,21 +453,379 @@ pub fn convert_process_data(
|
|||
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,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn group_process_data(
|
||||
single_process_data: &[ConvertedProcessData], is_using_command: ProcessNamingType,
|
||||
const BRANCH_ENDING: char = '└';
|
||||
const BRANCH_VERTICAL: char = '│';
|
||||
const BRANCH_SPLIT: char = '├';
|
||||
const BRANCH_HORIZONTAL: char = '─';
|
||||
|
||||
pub fn tree_process_data(
|
||||
single_process_data: &[ConvertedProcessData], is_using_command: bool,
|
||||
sort_type: &ProcessSorting, is_sort_descending: bool,
|
||||
) -> Vec<ConvertedProcessData> {
|
||||
// TODO: [TREE] Allow for collapsing entries.
|
||||
|
||||
// Let's first build up a (really terrible) parent -> child mapping...
|
||||
// At the same time, let's make a mapping of PID -> process data!
|
||||
let mut parent_child_mapping: HashMap<Pid, IndexSet<Pid>> = HashMap::default();
|
||||
let mut pid_process_mapping: HashMap<Pid, &ConvertedProcessData> = HashMap::default();
|
||||
let mut orphan_set: IndexSet<Pid> = IndexSet::new();
|
||||
|
||||
single_process_data.iter().for_each(|process| {
|
||||
if let Some(ppid) = process.ppid {
|
||||
orphan_set.insert(ppid);
|
||||
}
|
||||
orphan_set.insert(process.pid);
|
||||
});
|
||||
|
||||
single_process_data.iter().for_each(|process| {
|
||||
// Create a mapping for the process if it DNE.
|
||||
parent_child_mapping
|
||||
.entry(process.pid)
|
||||
.or_insert_with(IndexSet::new);
|
||||
pid_process_mapping.insert(process.pid, process);
|
||||
|
||||
// Insert its mapping to the process' parent if needed (create if it DNE).
|
||||
if let Some(ppid) = process.ppid {
|
||||
orphan_set.remove(&process.pid);
|
||||
parent_child_mapping
|
||||
.entry(ppid)
|
||||
.or_insert_with(IndexSet::new)
|
||||
.insert(process.pid);
|
||||
}
|
||||
});
|
||||
|
||||
// Keep only orphans, or promote children of orphans to a top-level orphan
|
||||
// if their parents DNE in our pid to process mapping...
|
||||
#[allow(clippy::redundant_clone)]
|
||||
orphan_set.clone().iter().for_each(|pid| {
|
||||
if pid_process_mapping.get(pid).is_none() {
|
||||
// DNE! Promote the mapped children and remove the current parent...
|
||||
orphan_set.remove(pid);
|
||||
if let Some(children) = parent_child_mapping.get(pid) {
|
||||
orphan_set.extend(children);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Turn the parent-child mapping into a "list" via DFS...
|
||||
let mut pids_to_explore: VecDeque<Pid> = orphan_set.into_iter().collect();
|
||||
let mut explored_pids: Vec<Pid> = vec![];
|
||||
let mut lines: Vec<String> = vec![];
|
||||
|
||||
/// A post-order traversal to correctly prune entire branches that only contain children
|
||||
/// that are disabled and themselves are also disabled ~~wait that sounds wrong~~.
|
||||
///
|
||||
/// Basically, go through the hashmap, and prune out all branches that are no longer relevant.
|
||||
fn prune_disabled_pids(
|
||||
current_pid: Pid, parent_child_mapping: &mut HashMap<Pid, IndexSet<Pid>>,
|
||||
pid_process_mapping: &HashMap<Pid, &ConvertedProcessData>,
|
||||
) -> bool {
|
||||
// Let's explore all the children first, and make sure they (and their children)
|
||||
// aren't all disabled...
|
||||
let mut are_all_children_disabled = true;
|
||||
if let Some(children) = parent_child_mapping.get(¤t_pid) {
|
||||
for child_pid in children.clone() {
|
||||
let is_child_disabled =
|
||||
prune_disabled_pids(child_pid, parent_child_mapping, pid_process_mapping);
|
||||
|
||||
if is_child_disabled {
|
||||
if let Some(current_mapping) = parent_child_mapping.get_mut(¤t_pid) {
|
||||
current_mapping.remove(&child_pid);
|
||||
}
|
||||
} else if are_all_children_disabled {
|
||||
are_all_children_disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now consider the current pid and whether to prune...
|
||||
// If the node itself is not disabled, then never prune. If it is, then check if all
|
||||
// of its are disabled.
|
||||
if let Some(process) = pid_process_mapping.get(¤t_pid) {
|
||||
if process.is_disabled_entry && are_all_children_disabled {
|
||||
parent_child_mapping.remove(¤t_pid);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn sort_remaining_pids(
|
||||
current_pid: Pid, sort_type: &ProcessSorting, is_sort_descending: bool,
|
||||
parent_child_mapping: &mut HashMap<Pid, IndexSet<Pid>>,
|
||||
pid_process_mapping: &HashMap<Pid, &ConvertedProcessData>,
|
||||
) {
|
||||
// Sorting is special for tree data. So, by default, things are "sorted"
|
||||
// via the DFS, except for (at least Unix) PID 1 and 2, which are in that order.
|
||||
// Otherwise, since this is DFS of the scanned PIDs (which are in order), you actually
|
||||
// get a REVERSE order --- so, you get higher PIDs earlier than lower ones.
|
||||
// But this is a tree. So, you'll get a bit of a combination, but the general idea
|
||||
// is that in a tree level, it's descending order, except, again, for the first layer.
|
||||
// This is how htop does it by default.
|
||||
//
|
||||
// So how do we "sort"? The current idea is that:
|
||||
// - We sort *per-level*. Say, I want to sort by CPU. The "first level" is sorted
|
||||
// by CPU in terms of its usage. All its direct children are sorted by CPU
|
||||
// with *their* siblings. Etc.
|
||||
// - The default is thus PIDs in reverse order (descending). We set it to this when
|
||||
// we first enable the mode.
|
||||
|
||||
// So first, let's look at the children... (post-order again)
|
||||
if let Some(children) = parent_child_mapping.get(¤t_pid) {
|
||||
let mut to_sort_vec: Vec<(Pid, &ConvertedProcessData)> = vec![];
|
||||
for child_pid in children.clone() {
|
||||
if let Some(child_process) = pid_process_mapping.get(&child_pid) {
|
||||
to_sort_vec.push((child_pid, child_process));
|
||||
}
|
||||
sort_remaining_pids(
|
||||
child_pid,
|
||||
sort_type,
|
||||
is_sort_descending,
|
||||
parent_child_mapping,
|
||||
pid_process_mapping,
|
||||
);
|
||||
}
|
||||
|
||||
// Now let's sort the immediate children!
|
||||
sort_vec(&mut to_sort_vec, sort_type, is_sort_descending);
|
||||
|
||||
// Need to reverse what we got, apparently...
|
||||
if let Some(current_mapping) = parent_child_mapping.get_mut(¤t_pid) {
|
||||
*current_mapping = to_sort_vec
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|(pid, _proc)| *pid)
|
||||
.collect::<IndexSet<Pid>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_vec(
|
||||
to_sort_vec: &mut Vec<(Pid, &ConvertedProcessData)>, sort_type: &ProcessSorting,
|
||||
is_sort_descending: bool,
|
||||
) {
|
||||
// Sort by PID first (descending)
|
||||
to_sort_vec.sort_by(|a, b| utils::gen_util::get_ordering(a.1.pid, b.1.pid, false));
|
||||
|
||||
match sort_type {
|
||||
ProcessSorting::CpuPercent => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.1.cpu_percent_usage,
|
||||
b.1.cpu_percent_usage,
|
||||
is_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::Mem => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.1.mem_usage_bytes,
|
||||
b.1.mem_usage_bytes,
|
||||
is_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::MemPercent => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.1.mem_percent_usage,
|
||||
b.1.mem_percent_usage,
|
||||
is_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::ProcessName => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
&a.1.name.to_lowercase(),
|
||||
&b.1.name.to_lowercase(),
|
||||
is_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::Command => to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
&a.1.command.to_lowercase(),
|
||||
&b.1.command.to_lowercase(),
|
||||
is_sort_descending,
|
||||
)
|
||||
}),
|
||||
ProcessSorting::Pid => {
|
||||
if is_sort_descending {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(a.0, b.0, is_sort_descending)
|
||||
});
|
||||
}
|
||||
}
|
||||
ProcessSorting::ReadPerSecond => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(a.1.rps_f64, b.1.rps_f64, is_sort_descending)
|
||||
});
|
||||
}
|
||||
ProcessSorting::WritePerSecond => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(a.1.wps_f64, b.1.wps_f64, is_sort_descending)
|
||||
});
|
||||
}
|
||||
ProcessSorting::TotalRead => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(a.1.tr_f64, b.1.tr_f64, is_sort_descending)
|
||||
});
|
||||
}
|
||||
ProcessSorting::TotalWrite => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(a.1.tw_f64, b.1.tw_f64, is_sort_descending)
|
||||
});
|
||||
}
|
||||
ProcessSorting::State => to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
&a.1.process_state.to_lowercase(),
|
||||
&b.1.process_state.to_lowercase(),
|
||||
is_sort_descending,
|
||||
)
|
||||
}),
|
||||
ProcessSorting::Count => {
|
||||
// Should never occur in this case.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A DFS traversal to correctly build the prefix lines (the pretty '├' and '─' lines) and
|
||||
/// the correct order to the PID tree as a vector (DFS is the default order htop seems to use
|
||||
/// so we're shamelessly copying that).
|
||||
fn build_explored_pids(
|
||||
current_pid: Pid, parent_child_mapping: &HashMap<Pid, IndexSet<Pid>>,
|
||||
prev_drawn_lines: &str,
|
||||
) -> (Vec<Pid>, Vec<String>) {
|
||||
let mut explored_pids: Vec<Pid> = vec![current_pid];
|
||||
let mut lines: Vec<String> = vec![];
|
||||
|
||||
if let Some(children) = parent_child_mapping.get(¤t_pid) {
|
||||
for (itx, child) in children.iter().rev().enumerate() {
|
||||
let new_drawn_lines = if itx == children.len() - 1 {
|
||||
format!("{} ", prev_drawn_lines)
|
||||
} else {
|
||||
format!("{}{} ", prev_drawn_lines, BRANCH_VERTICAL)
|
||||
};
|
||||
|
||||
let (pid_res, branch_res) =
|
||||
build_explored_pids(*child, parent_child_mapping, new_drawn_lines.as_str());
|
||||
|
||||
if itx == children.len() - 1 {
|
||||
lines.push(format!(
|
||||
"{}{}",
|
||||
prev_drawn_lines,
|
||||
if !new_drawn_lines.is_empty() {
|
||||
format!("{}{} ", BRANCH_ENDING, BRANCH_HORIZONTAL)
|
||||
} else {
|
||||
String::default()
|
||||
}
|
||||
));
|
||||
} else {
|
||||
lines.push(format!(
|
||||
"{}{}",
|
||||
prev_drawn_lines,
|
||||
if !new_drawn_lines.is_empty() {
|
||||
format!("{}{} ", BRANCH_SPLIT, BRANCH_HORIZONTAL)
|
||||
} else {
|
||||
String::default()
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
explored_pids.extend(pid_res);
|
||||
lines.extend(branch_res);
|
||||
}
|
||||
}
|
||||
|
||||
(explored_pids, lines)
|
||||
}
|
||||
|
||||
let mut to_sort_vec = Vec::new();
|
||||
for pid in pids_to_explore {
|
||||
if let Some(process) = pid_process_mapping.get(&pid) {
|
||||
to_sort_vec.push((pid, *process));
|
||||
}
|
||||
}
|
||||
sort_vec(&mut to_sort_vec, sort_type, is_sort_descending);
|
||||
pids_to_explore = to_sort_vec.iter().map(|(pid, _proc)| *pid).collect();
|
||||
|
||||
while let Some(current_pid) = pids_to_explore.pop_front() {
|
||||
if !prune_disabled_pids(current_pid, &mut parent_child_mapping, &pid_process_mapping) {
|
||||
sort_remaining_pids(
|
||||
current_pid,
|
||||
sort_type,
|
||||
is_sort_descending,
|
||||
&mut parent_child_mapping,
|
||||
&pid_process_mapping,
|
||||
);
|
||||
|
||||
let (pid_res, branch_res) = build_explored_pids(current_pid, &parent_child_mapping, "");
|
||||
lines.push(String::default());
|
||||
lines.extend(branch_res);
|
||||
explored_pids.extend(pid_res);
|
||||
}
|
||||
}
|
||||
|
||||
// Now let's "rearrange" our current list of converted process data into the correct
|
||||
// order required... and we're done!
|
||||
explored_pids
|
||||
.iter()
|
||||
.zip(lines)
|
||||
.filter_map(|(pid, prefix)| match pid_process_mapping.remove(pid) {
|
||||
Some(process) => {
|
||||
let mut p = process.clone();
|
||||
p.process_description_prefix = Some(format!(
|
||||
"{}{}",
|
||||
prefix,
|
||||
if is_using_command {
|
||||
&p.command
|
||||
} else {
|
||||
&p.name
|
||||
}
|
||||
));
|
||||
Some(p)
|
||||
}
|
||||
None => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub 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(match is_using_command {
|
||||
ProcessNamingType::Name => process.name.to_string(),
|
||||
ProcessNamingType::Path => process.command.to_string(),
|
||||
.entry(if is_using_command {
|
||||
process.command.to_string()
|
||||
} else {
|
||||
process.name.to_string()
|
||||
})
|
||||
.or_insert(SingleProcessData {
|
||||
pid: process.pid,
|
||||
|
@ -478,20 +836,20 @@ pub fn group_process_data(
|
|||
(*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 as u64;
|
||||
(*entry).write_per_sec += process.wps_f64 as u64;
|
||||
(*entry).total_read += process.tr_f64 as u64;
|
||||
(*entry).total_write += process.tw_f64 as u64;
|
||||
(*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 converted_rps = get_exact_byte_values(p.read_per_sec, false);
|
||||
let converted_wps = get_exact_byte_values(p.write_per_sec, false);
|
||||
let converted_total_read = get_exact_byte_values(p.total_read, false);
|
||||
let converted_total_write = get_exact_byte_values(p.total_write, false);
|
||||
let converted_rps = get_exact_byte_values(p.read_per_sec as u64, false);
|
||||
let converted_wps = get_exact_byte_values(p.write_per_sec as u64, false);
|
||||
let converted_total_read = get_exact_byte_values(p.total_read as u64, false);
|
||||
let converted_total_write = get_exact_byte_values(p.total_write as u64, false);
|
||||
|
||||
let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1);
|
||||
let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
|
||||
|
@ -503,6 +861,8 @@ pub fn group_process_data(
|
|||
|
||||
ConvertedProcessData {
|
||||
pid: p.pid,
|
||||
ppid: None,
|
||||
is_thread: None,
|
||||
name: identifier.to_string(),
|
||||
command: identifier.to_string(),
|
||||
cpu_percent_usage: p.cpu_percent_usage,
|
||||
|
@ -514,11 +874,14 @@ pub fn group_process_data(
|
|||
write_per_sec,
|
||||
total_read,
|
||||
total_write,
|
||||
rps_f64: p.read_per_sec as f64,
|
||||
wps_f64: p.write_per_sec as f64,
|
||||
tr_f64: p.total_read as f64,
|
||||
tw_f64: p.total_write as f64,
|
||||
process_state: p.process_state,
|
||||
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, // TODO: What the heck
|
||||
process_description_prefix: None,
|
||||
process_char: char::default(), // TODO: What the heck
|
||||
is_disabled_entry: false,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
|
170
src/lib.rs
170
src/lib.rs
|
@ -47,6 +47,12 @@ pub mod options;
|
|||
|
||||
pub mod clap;
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
pub type Pid = usize;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub type Pid = libc::pid_t;
|
||||
|
||||
pub enum BottomEvent<I, J> {
|
||||
KeyInput(I),
|
||||
MouseInput(J),
|
||||
|
@ -111,6 +117,7 @@ pub fn handle_key_event_or_break(
|
|||
KeyCode::F(1) => app.toggle_ignore_case(),
|
||||
KeyCode::F(2) => app.toggle_search_whole_word(),
|
||||
KeyCode::F(3) => app.toggle_search_regex(),
|
||||
KeyCode::F(5) => app.toggle_tree_mode(),
|
||||
KeyCode::F(6) => app.toggle_sort(),
|
||||
_ => {}
|
||||
}
|
||||
|
@ -458,88 +465,109 @@ pub fn update_all_process_lists(app: &mut App) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||
let (is_invalid_or_blank, is_using_command) = match app.proc_state.widget_states.get(&widget_id)
|
||||
{
|
||||
Some(process_state) => (
|
||||
fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||
let process_states = match app.proc_state.widget_states.get(&widget_id) {
|
||||
Some(process_state) => Some((
|
||||
process_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.is_invalid_or_blank_search(),
|
||||
process_state.is_using_command,
|
||||
),
|
||||
None => (false, false),
|
||||
process_state.is_grouped,
|
||||
process_state.is_tree_mode,
|
||||
)),
|
||||
None => None,
|
||||
};
|
||||
let is_grouped = app.is_grouped(widget_id);
|
||||
|
||||
if !app.is_frozen {
|
||||
app.canvas_data.single_process_data = convert_process_data(&app.data_collection);
|
||||
}
|
||||
|
||||
if is_grouped {
|
||||
app.canvas_data.process_data = group_process_data(
|
||||
&app.canvas_data.single_process_data,
|
||||
if is_using_command {
|
||||
ProcessNamingType::Path
|
||||
} else {
|
||||
ProcessNamingType::Name
|
||||
},
|
||||
);
|
||||
} else {
|
||||
app.canvas_data.process_data = app.canvas_data.single_process_data.clone();
|
||||
}
|
||||
|
||||
let process_filter = app.get_process_filter(widget_id);
|
||||
let filtered_process_data: Vec<ConvertedProcessData> = app
|
||||
.canvas_data
|
||||
.process_data
|
||||
.iter()
|
||||
.filter(|process| {
|
||||
if !is_invalid_or_blank {
|
||||
if let Some(process_filter) = process_filter {
|
||||
process_filter.check(&process, is_using_command)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Quick fix for tab updating the table headers
|
||||
if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
|
||||
let mut resulting_processes = filtered_process_data;
|
||||
sort_process_data(&mut resulting_processes, proc_widget_state);
|
||||
|
||||
if proc_widget_state.scroll_state.current_scroll_position >= resulting_processes.len() {
|
||||
proc_widget_state.scroll_state.current_scroll_position =
|
||||
resulting_processes.len().saturating_sub(1);
|
||||
proc_widget_state.scroll_state.previous_scroll_position = 0;
|
||||
proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::Down;
|
||||
if let Some((is_invalid_or_blank, is_using_command, is_grouped, is_tree)) = process_states {
|
||||
if !app.is_frozen {
|
||||
app.canvas_data.single_process_data = convert_process_data(&app.data_collection);
|
||||
}
|
||||
|
||||
app.canvas_data
|
||||
.finalized_process_data_map
|
||||
.insert(widget_id, resulting_processes);
|
||||
let process_filter = app.get_process_filter(widget_id);
|
||||
let filtered_process_data: Vec<ConvertedProcessData> = if is_tree {
|
||||
app.canvas_data
|
||||
.single_process_data
|
||||
.iter()
|
||||
.map(|process| {
|
||||
let mut process_clone = process.clone();
|
||||
if !is_invalid_or_blank {
|
||||
if let Some(process_filter) = process_filter {
|
||||
process_clone.is_disabled_entry =
|
||||
!process_filter.check(&process_clone, is_using_command);
|
||||
}
|
||||
}
|
||||
process_clone
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
app.canvas_data
|
||||
.single_process_data
|
||||
.iter()
|
||||
.filter(|process| {
|
||||
if !is_invalid_or_blank {
|
||||
if let Some(process_filter) = process_filter {
|
||||
process_filter.check(&process, is_using_command)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
|
||||
let mut finalized_process_data = if is_tree {
|
||||
tree_process_data(
|
||||
&filtered_process_data,
|
||||
is_using_command,
|
||||
&proc_widget_state.process_sorting_type,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
} else if is_grouped {
|
||||
group_process_data(&filtered_process_data, is_using_command)
|
||||
} else {
|
||||
filtered_process_data
|
||||
};
|
||||
|
||||
// Note tree mode is sorted well before this, as it's special.
|
||||
if !is_tree {
|
||||
sort_process_data(&mut finalized_process_data, proc_widget_state);
|
||||
}
|
||||
|
||||
if proc_widget_state.scroll_state.current_scroll_position
|
||||
>= finalized_process_data.len()
|
||||
{
|
||||
proc_widget_state.scroll_state.current_scroll_position =
|
||||
finalized_process_data.len().saturating_sub(1);
|
||||
proc_widget_state.scroll_state.previous_scroll_position = 0;
|
||||
proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::Down;
|
||||
}
|
||||
|
||||
app.canvas_data
|
||||
.finalized_process_data_map
|
||||
.insert(widget_id, finalized_process_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort_process_data(
|
||||
fn sort_process_data(
|
||||
to_sort_vec: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
|
||||
) {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(&a.name.to_lowercase(), &b.name.to_lowercase(), false)
|
||||
});
|
||||
|
||||
match proc_widget_state.process_sorting_type {
|
||||
match &proc_widget_state.process_sorting_type {
|
||||
ProcessSorting::CpuPercent => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.cpu_percent_usage,
|
||||
b.cpu_percent_usage,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -548,7 +576,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
a.mem_usage_bytes,
|
||||
b.mem_usage_bytes,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -557,18 +585,18 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
a.mem_percent_usage,
|
||||
b.mem_percent_usage,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::ProcessName => {
|
||||
// Don't repeat if false... it sorts by name by default anyways.
|
||||
if proc_widget_state.process_sorting_reverse {
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
&a.name.to_lowercase(),
|
||||
&b.name.to_lowercase(),
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -577,7 +605,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
&a.command.to_lowercase(),
|
||||
&b.command.to_lowercase(),
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
}),
|
||||
ProcessSorting::Pid => {
|
||||
|
@ -586,7 +614,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
a.pid,
|
||||
b.pid,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -596,7 +624,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
a.rps_f64,
|
||||
b.rps_f64,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -605,7 +633,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
a.wps_f64,
|
||||
b.wps_f64,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -614,7 +642,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
a.tr_f64,
|
||||
b.tr_f64,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -623,7 +651,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
a.tw_f64,
|
||||
b.tw_f64,
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -631,7 +659,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
&a.process_state.to_lowercase(),
|
||||
&b.process_state.to_lowercase(),
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
}),
|
||||
ProcessSorting::Count => {
|
||||
|
@ -640,7 +668,7 @@ pub fn sort_process_data(
|
|||
utils::gen_util::get_ordering(
|
||||
a.group_pids.len(),
|
||||
b.group_pids.len(),
|
||||
proc_widget_state.process_sorting_reverse,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -91,19 +91,19 @@ pub fn get_simple_byte_values(bytes: u64, spacing: bool) -> (f64, String) {
|
|||
|
||||
/// Gotta get partial ordering? No problem, here's something to deal with it~
|
||||
pub fn get_ordering<T: std::cmp::PartialOrd>(
|
||||
a_val: T, b_val: T, reverse_order: bool,
|
||||
a_val: T, b_val: T, descending_order: bool,
|
||||
) -> std::cmp::Ordering {
|
||||
match a_val.partial_cmp(&b_val) {
|
||||
Some(x) => match x {
|
||||
Ordering::Greater => {
|
||||
if reverse_order {
|
||||
if descending_order {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
}
|
||||
Ordering::Less => {
|
||||
if reverse_order {
|
||||
if descending_order {
|
||||
std::cmp::Ordering::Greater
|
||||
} else {
|
||||
std::cmp::Ordering::Less
|
||||
|
|
Loading…
Reference in a new issue