mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-23 13:45:12 +02: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
@ -1,5 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
echo "Running pre-push hook:"
|
echo "Running pre-push hook:"
|
||||||
|
|
||||||
echo "Executing: cargo +nightly clippy -- -D clippy::all"
|
echo "Executing: cargo +nightly clippy -- -D clippy::all"
|
||||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -46,10 +46,13 @@
|
|||||||
"fract",
|
"fract",
|
||||||
"gnueabihf",
|
"gnueabihf",
|
||||||
"gotop",
|
"gotop",
|
||||||
|
"gotop's",
|
||||||
"gtop",
|
"gtop",
|
||||||
"haase",
|
"haase",
|
||||||
"heim",
|
"heim",
|
||||||
"hjkl",
|
"hjkl",
|
||||||
|
"htop",
|
||||||
|
"indexmap",
|
||||||
"libc",
|
"libc",
|
||||||
"markdownlint",
|
"markdownlint",
|
||||||
"memb",
|
"memb",
|
||||||
@ -62,6 +65,7 @@
|
|||||||
"nvme",
|
"nvme",
|
||||||
"paren",
|
"paren",
|
||||||
"pmem",
|
"pmem",
|
||||||
|
"ppid",
|
||||||
"prepush",
|
"prepush",
|
||||||
"processthreadsapi",
|
"processthreadsapi",
|
||||||
"regexes",
|
"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.
|
- [#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
|
### Changes
|
||||||
|
|
||||||
- [#213](https://github.com/ClementTsang/bottom/pull/213), [#214](https://github.com/ClementTsang/bottom/pull/214): Updated help descriptions, added auto-complete generation.
|
- [#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",
|
"fern",
|
||||||
"futures",
|
"futures",
|
||||||
"heim",
|
"heim",
|
||||||
|
"indexmap",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
@ -534,6 +535,12 @@ version = "0.21.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c"
|
checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heim"
|
name = "heim"
|
||||||
version = "0.0.10"
|
version = "0.0.10"
|
||||||
@ -723,6 +730,16 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
|
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]]
|
[[package]]
|
||||||
name = "iovec"
|
name = "iovec"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -32,6 +32,7 @@ ctrlc = {version = "3.1", features = ["termination"]}
|
|||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
dirs = "3.0.1"
|
dirs = "3.0.1"
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
|
indexmap = "1.6.0"
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
regex = "1.3"
|
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)
|
- [Processes](#processes)
|
||||||
- [Process searching](#process-searching)
|
- [Process searching](#process-searching)
|
||||||
- [Process sorting](#process-sorting)
|
- [Process sorting](#process-sorting)
|
||||||
|
- [Tree mode](#tree-mode)
|
||||||
- [Zoom](#zoom)
|
- [Zoom](#zoom)
|
||||||
- [Expanding](#expanding)
|
- [Expanding](#expanding)
|
||||||
- [Basic mode](#basic-mode)
|
- [Basic mode](#basic-mode)
|
||||||
@ -246,23 +247,24 @@ Run using `btm`.
|
|||||||
| `s, F6` | Open process sort widget |
|
| `s, F6` | Open process sort widget |
|
||||||
| `I` | Invert current sort |
|
| `I` | Invert current sort |
|
||||||
| `%` | Toggle between values and percentages for memory usage |
|
| `%` | Toggle between values and percentages for memory usage |
|
||||||
|
| `t`, `F5` | Toggle tree mode |
|
||||||
|
|
||||||
#### Process search bindings
|
#### Process search bindings
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| ------------ | -------------------------------------------- |
|
| ------------- | -------------------------------------------- |
|
||||||
| `Tab` | Toggle between searching by PID or name |
|
| `Tab` | Toggle between searching by PID or name |
|
||||||
| `Esc` | Close the search widget (retains the filter) |
|
| `Esc` | Close the search widget (retains the filter) |
|
||||||
| `Ctrl-a` | Skip to the start of the search query |
|
| `Ctrl-a` | Skip to the start of the search query |
|
||||||
| `Ctrl-e` | Skip to the end of the search query |
|
| `Ctrl-e` | Skip to the end of the search query |
|
||||||
| `Ctrl-u` | Clear the current search query |
|
| `Ctrl-u` | Clear the current search query |
|
||||||
| `Backspace` | Delete the character behind the cursor |
|
| `Backspace` | Delete the character behind the cursor |
|
||||||
| `Delete` | Delete the character at the cursor |
|
| `Delete` | Delete the character at the cursor |
|
||||||
| `Alt-c`/`F1` | Toggle matching case |
|
| `Alt-c`, `F1` | Toggle matching case |
|
||||||
| `Alt-w`/`F2` | Toggle matching the entire word |
|
| `Alt-w`, `F2` | Toggle matching the entire word |
|
||||||
| `Alt-r`/`F3` | Toggle using regex |
|
| `Alt-r`, `F3` | Toggle using regex |
|
||||||
| `Left` | Move cursor left |
|
| `Left` | Move cursor left |
|
||||||
| `Right` | Move cursor right |
|
| `Right` | Move cursor right |
|
||||||
|
|
||||||
### Process sort bindings
|
### Process sort bindings
|
||||||
|
|
||||||
@ -424,6 +426,23 @@ You can sort the processes list by any column you want by pressing `s` while on
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
#### Tree mode
|
||||||
|
|
||||||
|
Use `t` or `F5` to toggle tree mode in a process widget. This is somewhat similar to htop's tree
|
||||||
|
mode.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Sorting works as well, but it is done per groups of siblings. For example, by CPU%:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Zoom
|
### 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.
|
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::{
|
use crate::{
|
||||||
canvas, constants,
|
canvas, constants,
|
||||||
utils::error::{BottomError, Result},
|
utils::error::{BottomError, Result},
|
||||||
|
Pid,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod data_farmer;
|
pub mod data_farmer;
|
||||||
@ -67,7 +68,7 @@ pub struct App {
|
|||||||
pub dd_err: Option<String>,
|
pub dd_err: Option<String>,
|
||||||
|
|
||||||
#[builder(default, setter(skip))]
|
#[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))]
|
#[builder(default = false, setter(skip))]
|
||||||
pub is_frozen: bool,
|
pub is_frozen: bool,
|
||||||
@ -265,37 +266,40 @@ impl App {
|
|||||||
.proc_state
|
.proc_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
.get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
{
|
||||||
// Toggles process widget grouping state
|
// Do NOT allow when in tree mode!
|
||||||
proc_widget_state.is_grouped = !(proc_widget_state.is_grouped);
|
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...
|
// Forcefully switch off column if we were on it...
|
||||||
if (proc_widget_state.is_grouped
|
if (proc_widget_state.is_grouped
|
||||||
&& proc_widget_state.process_sorting_type
|
|
||||||
== data_harvester::processes::ProcessSorting::Pid)
|
|
||||||
|| (!proc_widget_state.is_grouped
|
|
||||||
&& proc_widget_state.process_sorting_type
|
&& proc_widget_state.process_sorting_type
|
||||||
== data_harvester::processes::ProcessSorting::Count)
|
== data_harvester::processes::ProcessSorting::Pid)
|
||||||
{
|
|| (!proc_widget_state.is_grouped
|
||||||
proc_widget_state.process_sorting_type =
|
&& proc_widget_state.process_sorting_type
|
||||||
data_harvester::processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
|
== data_harvester::processes::ProcessSorting::Count)
|
||||||
proc_widget_state.process_sorting_reverse = true;
|
{
|
||||||
|
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) {
|
if let Some(proc_widget_state) = self.proc_state.get_mut_widget_state(widget_id) {
|
||||||
proc_widget_state.process_sorting_reverse =
|
proc_widget_state.is_process_sort_descending =
|
||||||
!proc_widget_state.process_sorting_reverse;
|
!proc_widget_state.is_process_sort_descending;
|
||||||
|
|
||||||
self.proc_state.force_update = Some(widget_id);
|
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...
|
/// One of two functions allowed to run while in a dialog...
|
||||||
pub fn on_enter(&mut self) {
|
pub fn on_enter(&mut self) {
|
||||||
if self.delete_dialog_state.is_showing_dd {
|
if self.delete_dialog_state.is_showing_dd {
|
||||||
@ -889,7 +911,7 @@ impl App {
|
|||||||
if proc_widget_state.scroll_state.current_scroll_position
|
if proc_widget_state.scroll_state.current_scroll_position
|
||||||
< corresponding_filtered_process_list.len()
|
< 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 self.is_grouped(self.current_widget.widget_id) {
|
||||||
if let Some(process) = &corresponding_filtered_process_list
|
if let Some(process) = &corresponding_filtered_process_list
|
||||||
.get(proc_widget_state.scroll_state.current_scroll_position)
|
.get(proc_widget_state.scroll_state.current_scroll_position)
|
||||||
@ -1069,13 +1091,13 @@ impl App {
|
|||||||
{
|
{
|
||||||
match proc_widget_state.process_sorting_type {
|
match proc_widget_state.process_sorting_type {
|
||||||
processes::ProcessSorting::CpuPercent => {
|
processes::ProcessSorting::CpuPercent => {
|
||||||
proc_widget_state.process_sorting_reverse =
|
proc_widget_state.is_process_sort_descending =
|
||||||
!proc_widget_state.process_sorting_reverse
|
!proc_widget_state.is_process_sort_descending
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
proc_widget_state.process_sorting_type =
|
proc_widget_state.process_sorting_type =
|
||||||
processes::ProcessSorting::CpuPercent;
|
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);
|
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||||
@ -1092,13 +1114,13 @@ impl App {
|
|||||||
{
|
{
|
||||||
match proc_widget_state.process_sorting_type {
|
match proc_widget_state.process_sorting_type {
|
||||||
processes::ProcessSorting::MemPercent => {
|
processes::ProcessSorting::MemPercent => {
|
||||||
proc_widget_state.process_sorting_reverse =
|
proc_widget_state.is_process_sort_descending =
|
||||||
!proc_widget_state.process_sorting_reverse
|
!proc_widget_state.is_process_sort_descending
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
proc_widget_state.process_sorting_type =
|
proc_widget_state.process_sorting_type =
|
||||||
processes::ProcessSorting::MemPercent;
|
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);
|
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||||
@ -1116,13 +1138,13 @@ impl App {
|
|||||||
if !proc_widget_state.is_grouped {
|
if !proc_widget_state.is_grouped {
|
||||||
match proc_widget_state.process_sorting_type {
|
match proc_widget_state.process_sorting_type {
|
||||||
processes::ProcessSorting::Pid => {
|
processes::ProcessSorting::Pid => {
|
||||||
proc_widget_state.process_sorting_reverse =
|
proc_widget_state.is_process_sort_descending =
|
||||||
!proc_widget_state.process_sorting_reverse
|
!proc_widget_state.is_process_sort_descending
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
proc_widget_state.process_sorting_type =
|
proc_widget_state.process_sorting_type =
|
||||||
processes::ProcessSorting::Pid;
|
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);
|
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||||
@ -1168,8 +1190,8 @@ impl App {
|
|||||||
match proc_widget_state.process_sorting_type {
|
match proc_widget_state.process_sorting_type {
|
||||||
processes::ProcessSorting::ProcessName
|
processes::ProcessSorting::ProcessName
|
||||||
| processes::ProcessSorting::Command => {
|
| processes::ProcessSorting::Command => {
|
||||||
proc_widget_state.process_sorting_reverse =
|
proc_widget_state.is_process_sort_descending =
|
||||||
!proc_widget_state.process_sorting_reverse
|
!proc_widget_state.is_process_sort_descending
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
proc_widget_state.process_sorting_type =
|
proc_widget_state.process_sorting_type =
|
||||||
@ -1178,7 +1200,7 @@ impl App {
|
|||||||
} else {
|
} else {
|
||||||
processes::ProcessSorting::ProcessName
|
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);
|
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),
|
'L' | 'D' => self.move_widget_selection(&WidgetDirection::Right),
|
||||||
'K' | 'W' => self.move_widget_selection(&WidgetDirection::Up),
|
'K' | 'W' => self.move_widget_selection(&WidgetDirection::Up),
|
||||||
'J' | 'S' => self.move_widget_selection(&WidgetDirection::Down),
|
'J' | 'S' => self.move_widget_selection(&WidgetDirection::Down),
|
||||||
|
't' => self.toggle_tree_mode(),
|
||||||
'+' => self.zoom_in(),
|
'+' => self.zoom_in(),
|
||||||
'-' => self.zoom_out(),
|
'-' => self.zoom_out(),
|
||||||
'=' => self.reset_zoom(),
|
'=' => 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()
|
self.to_delete_process_list.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ pub struct DataCollector {
|
|||||||
pub data: Data,
|
pub data: Data,
|
||||||
sys: System,
|
sys: System,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pid_mapping: HashMap<u32, processes::PrevProcDetails>,
|
pid_mapping: HashMap<crate::Pid, processes::PrevProcDetails>,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
prev_idle: f64,
|
prev_idle: f64,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::Pid;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use sysinfo::ProcessStatus;
|
use sysinfo::ProcessStatus;
|
||||||
|
|
||||||
@ -59,7 +60,8 @@ impl Default for ProcessSorting {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ProcessHarvest {
|
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 cpu_usage_percent: f64,
|
||||||
pub mem_usage_percent: f64,
|
pub mem_usage_percent: f64,
|
||||||
pub mem_usage_bytes: u64,
|
pub mem_usage_bytes: u64,
|
||||||
@ -89,7 +91,7 @@ pub struct PrevProcDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PrevProcDetails {
|
impl PrevProcDetails {
|
||||||
pub fn new(pid: u32) -> Self {
|
pub fn new(pid: Pid) -> Self {
|
||||||
PrevProcDetails {
|
PrevProcDetails {
|
||||||
proc_io_path: PathBuf::from(format!("/proc/{}/io", pid)),
|
proc_io_path: PathBuf::from(format!("/proc/{}/io", pid)),
|
||||||
proc_exe_path: PathBuf::from(format!("/proc/{}/exe", 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")]
|
#[cfg(target_os = "linux")]
|
||||||
fn get_linux_process_state(stat: &[&str]) -> (char, String) {
|
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() {
|
if let Some(first_char) = stat[0].chars().collect::<Vec<char>>().first() {
|
||||||
(
|
(
|
||||||
*first_char,
|
*first_char,
|
||||||
@ -241,8 +243,8 @@ fn get_linux_cpu_usage(
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn read_proc<S: core::hash::BuildHasher>(
|
fn read_proc<S: core::hash::BuildHasher>(
|
||||||
pid: u32, cpu_usage: f64, cpu_fraction: f64,
|
pid: Pid, cpu_usage: f64, cpu_fraction: f64,
|
||||||
pid_mapping: &mut HashMap<u32, PrevProcDetails, S>, use_current_cpu_total: bool,
|
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,
|
time_difference_in_secs: u64, mem_total_kb: u64, page_file_kb: u64,
|
||||||
) -> error::Result<ProcessHarvest> {
|
) -> error::Result<ProcessHarvest> {
|
||||||
let pid_stat = pid_mapping
|
let pid_stat = pid_mapping
|
||||||
@ -282,6 +284,7 @@ fn read_proc<S: core::hash::BuildHasher>(
|
|||||||
&mut pid_stat.cpu_time,
|
&mut pid_stat.cpu_time,
|
||||||
use_current_cpu_total,
|
use_current_cpu_total,
|
||||||
)?;
|
)?;
|
||||||
|
let parent_pid = stat[1].parse::<Pid>().ok();
|
||||||
let (_vsize, rss) = get_linux_process_vsize_rss(&stat);
|
let (_vsize, rss) = get_linux_process_vsize_rss(&stat);
|
||||||
let mem_usage_kb = rss * page_file_kb;
|
let mem_usage_kb = rss * page_file_kb;
|
||||||
let mem_usage_percent = mem_usage_kb as f64 / mem_total_kb as f64 * 100.0;
|
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 {
|
Ok(ProcessHarvest {
|
||||||
pid,
|
pid,
|
||||||
|
parent_pid,
|
||||||
name,
|
name,
|
||||||
command,
|
command,
|
||||||
mem_usage_percent,
|
mem_usage_percent,
|
||||||
@ -337,14 +341,16 @@ fn read_proc<S: core::hash::BuildHasher>(
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn linux_get_processes_list(
|
pub fn linux_get_processes_list(
|
||||||
prev_idle: &mut f64, prev_non_idle: &mut f64,
|
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,
|
time_difference_in_secs: u64, mem_total_kb: u64, page_file_kb: u64,
|
||||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
) -> 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) {
|
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")?
|
let process_vector: Vec<ProcessHarvest> = std::fs::read_dir("/proc")?
|
||||||
.filter_map(|dir| {
|
.filter_map(|dir| {
|
||||||
if let Ok(dir) = 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 {
|
if let Ok(pid) = pid {
|
||||||
// I skip checking if the path is also a directory, it's not needed I think?
|
// I skip checking if the path is also a directory, it's not needed I think?
|
||||||
if let Ok(process_object) = read_proc(
|
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();
|
let disk_usage = process_val.disk_usage();
|
||||||
|
|
||||||
process_vector.push(ProcessHarvest {
|
process_vector.push(ProcessHarvest {
|
||||||
pid: process_val.pid() as u32,
|
pid: process_val.pid(),
|
||||||
|
parent_pid: process_val.parent(),
|
||||||
name,
|
name,
|
||||||
command,
|
command,
|
||||||
mem_usage_percent: if mem_total_kb > 0 {
|
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.
|
/// This file is meant to house (OS specific) implementations on how to kill processes.
|
||||||
use crate::utils::error::BottomError;
|
use crate::utils::error::BottomError;
|
||||||
|
use crate::Pid;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
struct Process(HANDLE);
|
struct Process(HANDLE);
|
||||||
@ -31,9 +32,9 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Kills a process, given a PID.
|
/// Kills a process, given a PID.
|
||||||
pub fn kill_process_given_pid(pid: u32) -> crate::utils::error::Result<()> {
|
pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> {
|
||||||
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
|
if cfg!(target_family = "unix") {
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_family = "unix"))]
|
||||||
{
|
{
|
||||||
let output = unsafe { libc::kill(pid as i32, libc::SIGTERM) };
|
let output = unsafe { libc::kill(pid as i32, libc::SIGTERM) };
|
||||||
if output != 0 {
|
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") {
|
} else if cfg!(target_family = "windows") {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_family = "windows")]
|
||||||
{
|
{
|
||||||
let process = Process::open(pid as DWORD)?;
|
let process = Process::open(pid as DWORD)?;
|
||||||
process.kill()?;
|
process.kill()?;
|
||||||
|
@ -9,6 +9,7 @@ use crate::{
|
|||||||
constants,
|
constants,
|
||||||
data_harvester::processes::{self, ProcessSorting},
|
data_harvester::processes::{self, ProcessSorting},
|
||||||
};
|
};
|
||||||
|
use ProcessSorting::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ScrollDirection {
|
pub enum ScrollDirection {
|
||||||
@ -159,7 +160,6 @@ pub struct ProcColumn {
|
|||||||
|
|
||||||
impl Default for ProcColumn {
|
impl Default for ProcColumn {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
use ProcessSorting::*;
|
|
||||||
let ordered_columns = vec![
|
let ordered_columns = vec![
|
||||||
Count,
|
Count,
|
||||||
Pid,
|
Pid,
|
||||||
@ -352,11 +352,12 @@ pub struct ProcWidgetState {
|
|||||||
pub is_grouped: bool,
|
pub is_grouped: bool,
|
||||||
pub scroll_state: AppScrollWidgetState,
|
pub scroll_state: AppScrollWidgetState,
|
||||||
pub process_sorting_type: processes::ProcessSorting,
|
pub process_sorting_type: processes::ProcessSorting,
|
||||||
pub process_sorting_reverse: bool,
|
pub is_process_sort_descending: bool,
|
||||||
pub is_using_command: bool,
|
pub is_using_command: bool,
|
||||||
pub current_column_index: usize,
|
pub current_column_index: usize,
|
||||||
pub is_sort_open: bool,
|
pub is_sort_open: bool,
|
||||||
pub columns: ProcColumn,
|
pub columns: ProcColumn,
|
||||||
|
pub is_tree_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcWidgetState {
|
impl ProcWidgetState {
|
||||||
@ -390,11 +391,12 @@ impl ProcWidgetState {
|
|||||||
is_grouped,
|
is_grouped,
|
||||||
scroll_state: AppScrollWidgetState::default(),
|
scroll_state: AppScrollWidgetState::default(),
|
||||||
process_sorting_type,
|
process_sorting_type,
|
||||||
process_sorting_reverse: true,
|
is_process_sort_descending: true,
|
||||||
is_using_command: false,
|
is_using_command: false,
|
||||||
current_column_index: 0,
|
current_column_index: 0,
|
||||||
is_sort_open: false,
|
is_sort_open: false,
|
||||||
columns,
|
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 let Some(new_sort_type) = self.columns.ordered_columns.get(true_index) {
|
||||||
if *new_sort_type == self.process_sorting_type {
|
if *new_sort_type == self.process_sorting_type {
|
||||||
// Just reverse the search if we're reselecting!
|
// 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 {
|
} else {
|
||||||
self.process_sorting_type = new_sort_type.clone();
|
self.process_sorting_type = new_sort_type.clone();
|
||||||
match self.process_sorting_type {
|
match self.process_sorting_type {
|
||||||
@ -431,7 +433,7 @@ impl ProcWidgetState {
|
|||||||
| ProcessSorting::ProcessName
|
| ProcessSorting::ProcessName
|
||||||
| ProcessSorting::Command => {
|
| ProcessSorting::Command => {
|
||||||
// Also invert anything that uses alphabetical sorting by default.
|
// 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 disk_data: Vec<Vec<String>>,
|
||||||
pub temp_sensor_data: Vec<Vec<String>>,
|
pub temp_sensor_data: Vec<Vec<String>>,
|
||||||
pub single_process_data: Vec<ConvertedProcessData>, // Contains single process data
|
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 finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed
|
||||||
pub mem_label_percent: String,
|
pub mem_label_percent: String,
|
||||||
pub swap_label_percent: String,
|
pub swap_label_percent: String,
|
||||||
|
@ -28,6 +28,7 @@ pub struct CanvasColours {
|
|||||||
// Full, Medium, Low
|
// Full, Medium, Low
|
||||||
pub battery_bar_styles: Vec<Style>,
|
pub battery_bar_styles: Vec<Style>,
|
||||||
pub invalid_query_style: Style,
|
pub invalid_query_style: Style,
|
||||||
|
pub disabled_text_style: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CanvasColours {
|
impl Default for CanvasColours {
|
||||||
@ -63,7 +64,8 @@ impl Default for CanvasColours {
|
|||||||
Style::default().fg(Color::Green),
|
Style::default().fg(Color::Green),
|
||||||
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);
|
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 intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
||||||
|
|
||||||
let (border_and_title_style, highlight_style) = if is_on_widget {
|
// 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,
|
self.colours.highlighted_border_style
|
||||||
self.colours.currently_selected_text_style,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(self.colours.border_style, self.colours.text_style)
|
self.colours.border_style
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
@ -367,7 +365,7 @@ impl CpuGraphWidget for Painter {
|
|||||||
.border_style(border_and_title_style),
|
.border_style(border_and_title_style),
|
||||||
)
|
)
|
||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.highlight_style(highlight_style)
|
.highlight_style(self.colours.currently_selected_text_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -36,7 +36,7 @@ pub trait ProcessTableWidget {
|
|||||||
widget_id: u64,
|
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
|
/// - `widget_id` represents the widget ID of the search box itself --- NOT the process widget
|
||||||
/// state that is stored.
|
/// state that is stored.
|
||||||
///
|
///
|
||||||
@ -173,15 +173,6 @@ impl ProcessTableWidget for Painter {
|
|||||||
.finalized_process_data_map
|
.finalized_process_data_map
|
||||||
.get(&widget_id)
|
.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 {
|
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
@ -217,39 +208,52 @@ impl ProcessTableWidget for Painter {
|
|||||||
// Draw!
|
// Draw!
|
||||||
let is_proc_widget_grouped = proc_widget_state.is_grouped;
|
let is_proc_widget_grouped = proc_widget_state.is_grouped;
|
||||||
let is_using_command = proc_widget_state.is_using_command;
|
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);
|
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| {
|
let process_rows = sliced_vec.iter().map(|process| {
|
||||||
Row::Data(
|
let data = vec![
|
||||||
vec![
|
if is_proc_widget_grouped {
|
||||||
if is_proc_widget_grouped {
|
process.group_pids.len().to_string()
|
||||||
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 {
|
} else {
|
||||||
process.pid.to_string()
|
String::default()
|
||||||
},
|
}
|
||||||
if is_using_command {
|
} else if is_using_command {
|
||||||
process.command.clone()
|
process.command.clone()
|
||||||
} else {
|
} else {
|
||||||
process.name.clone()
|
process.name.clone()
|
||||||
},
|
},
|
||||||
format!("{:.1}%", process.cpu_percent_usage),
|
format!("{:.1}%", process.cpu_percent_usage),
|
||||||
if mem_enabled {
|
if mem_enabled {
|
||||||
format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1)
|
format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1)
|
||||||
} else {
|
} else {
|
||||||
format!("{:.1}%", process.mem_percent_usage)
|
format!("{:.1}%", process.mem_percent_usage)
|
||||||
},
|
},
|
||||||
process.read_per_sec.to_string(),
|
process.read_per_sec.clone(),
|
||||||
process.write_per_sec.to_string(),
|
process.write_per_sec.clone(),
|
||||||
process.total_read.to_string(),
|
process.total_read.clone(),
|
||||||
process.total_write.to_string(),
|
process.total_write.clone(),
|
||||||
process.process_state.to_string(),
|
process.process_state.clone(),
|
||||||
]
|
]
|
||||||
.into_iter(),
|
.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(
|
let process_headers = proc_widget_state.columns.get_column_headers(
|
||||||
&proc_widget_state.process_sorting_type,
|
&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
|
let process_headers_lens: Vec<usize> = process_headers
|
||||||
@ -269,6 +273,8 @@ impl ProcessTableWidget for Painter {
|
|||||||
}
|
}
|
||||||
} else if proc_widget_state.is_using_command {
|
} 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]
|
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 {
|
} else {
|
||||||
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
|
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 =
|
let intrinsic_widths =
|
||||||
&(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
&(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(
|
f.render_stateful_widget(
|
||||||
Table::new(process_headers.iter(), process_rows)
|
Table::new(process_headers.iter(), process_rows)
|
||||||
.block(process_block)
|
.block(process_block)
|
||||||
@ -592,6 +599,7 @@ impl ProcessTableWidget for Painter {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|column| Row::Data(vec![column].into_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;
|
let column_state = &mut proc_widget_state.columns.column_state;
|
||||||
column_state.select(Some(
|
column_state.select(Some(
|
||||||
proc_widget_state
|
proc_widget_state
|
||||||
|
@ -92,7 +92,7 @@ pub const CPU_HELP_TEXT: [&str; 2] = [
|
|||||||
|
|
||||||
// TODO [Help]: Search in help?
|
// TODO [Help]: Search in help?
|
||||||
// TODO [Help]: Move to using tables for easier formatting?
|
// 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",
|
"3 - Process widget\n",
|
||||||
"dd Kill the selected process\n",
|
"dd Kill the selected process\n",
|
||||||
"c Sort by CPU usage, press again to reverse sorting order\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",
|
"P Toggle between showing the full command or just the process name\n",
|
||||||
"s, F6 Open process sort widget\n",
|
"s, F6 Open process sort widget\n",
|
||||||
"I Invert current sort\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] = [
|
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",
|
"Ctrl-u Clear the current search query\n",
|
||||||
"Backspace Delete the character behind the cursor\n",
|
"Backspace Delete the character behind the cursor\n",
|
||||||
"Delete Delete the character at the cursor\n",
|
"Delete Delete the character at the cursor\n",
|
||||||
"Alt-c/F1 Toggle matching case\n",
|
"Alt-c, F1 Toggle matching case\n",
|
||||||
"Alt-w/F2 Toggle matching the entire word\n",
|
"Alt-w, F2 Toggle matching the entire word\n",
|
||||||
"Alt-r/F3 Toggle using regex\n",
|
"Alt-r, F3 Toggle using regex\n",
|
||||||
"Left, Alt-h Move cursor left\n",
|
"Left, Alt-h Move cursor left\n",
|
||||||
"Right, Alt-l Move cursor right\n",
|
"Right, Alt-l Move cursor right\n",
|
||||||
"\n",
|
"\n",
|
||||||
@ -142,8 +143,8 @@ pub const SEARCH_HELP_TEXT: [&str; 46] = [
|
|||||||
"<= ex: cpu <= 1\n",
|
"<= ex: cpu <= 1\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Logical operators:\n",
|
"Logical operators:\n",
|
||||||
"and/&&/<Space> ex: btm and cpu > 1 and mem > 1\n",
|
"and, &&, <Space> ex: btm and cpu > 1 and mem > 1\n",
|
||||||
"or/|| ex: btm or firefox\n",
|
"or, || ex: btm or firefox\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Supported units:\n",
|
"Supported units:\n",
|
||||||
"B ex: read > 1 b\n",
|
"B ex: read > 1 b\n",
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
//! This mainly concerns converting collected data into things that the canvas
|
//! This mainly concerns converting collected data into things that the canvas
|
||||||
//! can actually handle.
|
//! can actually handle.
|
||||||
|
use crate::Pid;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{data_farmer, data_harvester, App, Filter},
|
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
|
/// Point is of time, data
|
||||||
type Point = (f64, f64);
|
type Point = (f64, f64);
|
||||||
@ -38,16 +39,19 @@ pub struct ConvertedNetworkData {
|
|||||||
// mean_tx: f64,
|
// mean_tx: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: [REFACTOR] Process data... stuff really needs a rewrite. Again.
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct ConvertedProcessData {
|
pub struct ConvertedProcessData {
|
||||||
pub pid: u32,
|
pub pid: Pid,
|
||||||
|
pub ppid: Option<Pid>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
|
pub is_thread: Option<bool>,
|
||||||
pub cpu_percent_usage: f64,
|
pub cpu_percent_usage: f64,
|
||||||
pub mem_percent_usage: f64,
|
pub mem_percent_usage: f64,
|
||||||
pub mem_usage_bytes: u64,
|
pub mem_usage_bytes: u64,
|
||||||
pub mem_usage_str: (f64, String),
|
pub mem_usage_str: (f64, String),
|
||||||
pub group_pids: Vec<u32>,
|
pub group_pids: Vec<Pid>,
|
||||||
pub read_per_sec: String,
|
pub read_per_sec: String,
|
||||||
pub write_per_sec: String,
|
pub write_per_sec: String,
|
||||||
pub total_read: String,
|
pub total_read: String,
|
||||||
@ -57,20 +61,11 @@ pub struct ConvertedProcessData {
|
|||||||
pub tr_f64: f64,
|
pub tr_f64: f64,
|
||||||
pub tw_f64: f64,
|
pub tw_f64: f64,
|
||||||
pub process_state: String,
|
pub process_state: String,
|
||||||
}
|
pub process_char: char,
|
||||||
|
/// Prefix printed before the process when displayed.
|
||||||
#[derive(Clone, Default, Debug)]
|
pub process_description_prefix: Option<String>,
|
||||||
pub struct SingleProcessData {
|
/// Whether to mark this process entry as disabled (mostly for tree mode).
|
||||||
pub pid: u32,
|
pub is_disabled_entry: bool,
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
@ -418,6 +413,9 @@ pub enum ProcessNamingType {
|
|||||||
pub fn convert_process_data(
|
pub fn convert_process_data(
|
||||||
current_data: &data_farmer::DataCollection,
|
current_data: &data_farmer::DataCollection,
|
||||||
) -> Vec<ConvertedProcessData> {
|
) -> Vec<ConvertedProcessData> {
|
||||||
|
// FIXME: Thread highlighting and hiding support
|
||||||
|
// For macOS see https://github.com/hishamhm/htop/pull/848/files
|
||||||
|
|
||||||
current_data
|
current_data
|
||||||
.process_harvest
|
.process_harvest
|
||||||
.iter()
|
.iter()
|
||||||
@ -437,6 +435,8 @@ pub fn convert_process_data(
|
|||||||
|
|
||||||
ConvertedProcessData {
|
ConvertedProcessData {
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
|
ppid: process.parent_pid,
|
||||||
|
is_thread: None,
|
||||||
name: process.name.to_string(),
|
name: process.name.to_string(),
|
||||||
command: process.command.to_string(),
|
command: process.command.to_string(),
|
||||||
cpu_percent_usage: process.cpu_usage_percent,
|
cpu_percent_usage: process.cpu_usage_percent,
|
||||||
@ -453,21 +453,379 @@ pub fn convert_process_data(
|
|||||||
tr_f64: process.total_read_bytes as f64,
|
tr_f64: process.total_read_bytes as f64,
|
||||||
tw_f64: process.total_write_bytes as f64,
|
tw_f64: process.total_write_bytes as f64,
|
||||||
process_state: process.process_state.to_owned(),
|
process_state: process.process_state.to_owned(),
|
||||||
|
process_char: process.process_state_char,
|
||||||
|
process_description_prefix: None,
|
||||||
|
is_disabled_entry: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn group_process_data(
|
const BRANCH_ENDING: char = '└';
|
||||||
single_process_data: &[ConvertedProcessData], is_using_command: ProcessNamingType,
|
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> {
|
) -> 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();
|
let mut grouped_hashmap: HashMap<String, SingleProcessData> = std::collections::HashMap::new();
|
||||||
|
|
||||||
single_process_data.iter().for_each(|process| {
|
single_process_data.iter().for_each(|process| {
|
||||||
let entry = grouped_hashmap
|
let entry = grouped_hashmap
|
||||||
.entry(match is_using_command {
|
.entry(if is_using_command {
|
||||||
ProcessNamingType::Name => process.name.to_string(),
|
process.command.to_string()
|
||||||
ProcessNamingType::Path => process.command.to_string(),
|
} else {
|
||||||
|
process.name.to_string()
|
||||||
})
|
})
|
||||||
.or_insert(SingleProcessData {
|
.or_insert(SingleProcessData {
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
@ -478,20 +836,20 @@ pub fn group_process_data(
|
|||||||
(*entry).mem_percent_usage += process.mem_percent_usage;
|
(*entry).mem_percent_usage += process.mem_percent_usage;
|
||||||
(*entry).mem_usage_bytes += process.mem_usage_bytes;
|
(*entry).mem_usage_bytes += process.mem_usage_bytes;
|
||||||
(*entry).group_pids.push(process.pid);
|
(*entry).group_pids.push(process.pid);
|
||||||
(*entry).read_per_sec += process.rps_f64 as u64;
|
(*entry).read_per_sec += process.rps_f64;
|
||||||
(*entry).write_per_sec += process.wps_f64 as u64;
|
(*entry).write_per_sec += process.wps_f64;
|
||||||
(*entry).total_read += process.tr_f64 as u64;
|
(*entry).total_read += process.tr_f64;
|
||||||
(*entry).total_write += process.tw_f64 as u64;
|
(*entry).total_write += process.tw_f64;
|
||||||
});
|
});
|
||||||
|
|
||||||
grouped_hashmap
|
grouped_hashmap
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(identifier, process_details)| {
|
.map(|(identifier, process_details)| {
|
||||||
let p = process_details.clone();
|
let p = process_details.clone();
|
||||||
let converted_rps = get_exact_byte_values(p.read_per_sec, 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, 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, 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, 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 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);
|
let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
|
||||||
@ -503,6 +861,8 @@ pub fn group_process_data(
|
|||||||
|
|
||||||
ConvertedProcessData {
|
ConvertedProcessData {
|
||||||
pid: p.pid,
|
pid: p.pid,
|
||||||
|
ppid: None,
|
||||||
|
is_thread: None,
|
||||||
name: identifier.to_string(),
|
name: identifier.to_string(),
|
||||||
command: identifier.to_string(),
|
command: identifier.to_string(),
|
||||||
cpu_percent_usage: p.cpu_percent_usage,
|
cpu_percent_usage: p.cpu_percent_usage,
|
||||||
@ -514,11 +874,14 @@ pub fn group_process_data(
|
|||||||
write_per_sec,
|
write_per_sec,
|
||||||
total_read,
|
total_read,
|
||||||
total_write,
|
total_write,
|
||||||
rps_f64: p.read_per_sec as f64,
|
rps_f64: p.read_per_sec,
|
||||||
wps_f64: p.write_per_sec as f64,
|
wps_f64: p.write_per_sec,
|
||||||
tr_f64: p.total_read as f64,
|
tr_f64: p.total_read,
|
||||||
tw_f64: p.total_write as f64,
|
tw_f64: p.total_write,
|
||||||
process_state: p.process_state,
|
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<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
170
src/lib.rs
170
src/lib.rs
@ -47,6 +47,12 @@ pub mod options;
|
|||||||
|
|
||||||
pub mod clap;
|
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> {
|
pub enum BottomEvent<I, J> {
|
||||||
KeyInput(I),
|
KeyInput(I),
|
||||||
MouseInput(J),
|
MouseInput(J),
|
||||||
@ -111,6 +117,7 @@ pub fn handle_key_event_or_break(
|
|||||||
KeyCode::F(1) => app.toggle_ignore_case(),
|
KeyCode::F(1) => app.toggle_ignore_case(),
|
||||||
KeyCode::F(2) => app.toggle_search_whole_word(),
|
KeyCode::F(2) => app.toggle_search_whole_word(),
|
||||||
KeyCode::F(3) => app.toggle_search_regex(),
|
KeyCode::F(3) => app.toggle_search_regex(),
|
||||||
|
KeyCode::F(5) => app.toggle_tree_mode(),
|
||||||
KeyCode::F(6) => app.toggle_sort(),
|
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) {
|
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)
|
let process_states = match app.proc_state.widget_states.get(&widget_id) {
|
||||||
{
|
Some(process_state) => Some((
|
||||||
Some(process_state) => (
|
|
||||||
process_state
|
process_state
|
||||||
.process_search_state
|
.process_search_state
|
||||||
.search_state
|
.search_state
|
||||||
.is_invalid_or_blank_search(),
|
.is_invalid_or_blank_search(),
|
||||||
process_state.is_using_command,
|
process_state.is_using_command,
|
||||||
),
|
process_state.is_grouped,
|
||||||
None => (false, false),
|
process_state.is_tree_mode,
|
||||||
|
)),
|
||||||
|
None => None,
|
||||||
};
|
};
|
||||||
let is_grouped = app.is_grouped(widget_id);
|
|
||||||
|
|
||||||
if !app.is_frozen {
|
if let Some((is_invalid_or_blank, is_using_command, is_grouped, is_tree)) = process_states {
|
||||||
app.canvas_data.single_process_data = convert_process_data(&app.data_collection);
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.canvas_data
|
let process_filter = app.get_process_filter(widget_id);
|
||||||
.finalized_process_data_map
|
let filtered_process_data: Vec<ConvertedProcessData> = if is_tree {
|
||||||
.insert(widget_id, resulting_processes);
|
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: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
|
||||||
) {
|
) {
|
||||||
to_sort_vec.sort_by(|a, b| {
|
to_sort_vec.sort_by(|a, b| {
|
||||||
utils::gen_util::get_ordering(&a.name.to_lowercase(), &b.name.to_lowercase(), false)
|
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 => {
|
ProcessSorting::CpuPercent => {
|
||||||
to_sort_vec.sort_by(|a, b| {
|
to_sort_vec.sort_by(|a, b| {
|
||||||
utils::gen_util::get_ordering(
|
utils::gen_util::get_ordering(
|
||||||
a.cpu_percent_usage,
|
a.cpu_percent_usage,
|
||||||
b.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(
|
utils::gen_util::get_ordering(
|
||||||
a.mem_usage_bytes,
|
a.mem_usage_bytes,
|
||||||
b.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(
|
utils::gen_util::get_ordering(
|
||||||
a.mem_percent_usage,
|
a.mem_percent_usage,
|
||||||
b.mem_percent_usage,
|
b.mem_percent_usage,
|
||||||
proc_widget_state.process_sorting_reverse,
|
proc_widget_state.is_process_sort_descending,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ProcessSorting::ProcessName => {
|
ProcessSorting::ProcessName => {
|
||||||
// Don't repeat if false... it sorts by name by default anyways.
|
// 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| {
|
to_sort_vec.sort_by(|a, b| {
|
||||||
utils::gen_util::get_ordering(
|
utils::gen_util::get_ordering(
|
||||||
&a.name.to_lowercase(),
|
&a.name.to_lowercase(),
|
||||||
&b.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(
|
utils::gen_util::get_ordering(
|
||||||
&a.command.to_lowercase(),
|
&a.command.to_lowercase(),
|
||||||
&b.command.to_lowercase(),
|
&b.command.to_lowercase(),
|
||||||
proc_widget_state.process_sorting_reverse,
|
proc_widget_state.is_process_sort_descending,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
ProcessSorting::Pid => {
|
ProcessSorting::Pid => {
|
||||||
@ -586,7 +614,7 @@ pub fn sort_process_data(
|
|||||||
utils::gen_util::get_ordering(
|
utils::gen_util::get_ordering(
|
||||||
a.pid,
|
a.pid,
|
||||||
b.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(
|
utils::gen_util::get_ordering(
|
||||||
a.rps_f64,
|
a.rps_f64,
|
||||||
b.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(
|
utils::gen_util::get_ordering(
|
||||||
a.wps_f64,
|
a.wps_f64,
|
||||||
b.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(
|
utils::gen_util::get_ordering(
|
||||||
a.tr_f64,
|
a.tr_f64,
|
||||||
b.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(
|
utils::gen_util::get_ordering(
|
||||||
a.tw_f64,
|
a.tw_f64,
|
||||||
b.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(
|
utils::gen_util::get_ordering(
|
||||||
&a.process_state.to_lowercase(),
|
&a.process_state.to_lowercase(),
|
||||||
&b.process_state.to_lowercase(),
|
&b.process_state.to_lowercase(),
|
||||||
proc_widget_state.process_sorting_reverse,
|
proc_widget_state.is_process_sort_descending,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
ProcessSorting::Count => {
|
ProcessSorting::Count => {
|
||||||
@ -640,7 +668,7 @@ pub fn sort_process_data(
|
|||||||
utils::gen_util::get_ordering(
|
utils::gen_util::get_ordering(
|
||||||
a.group_pids.len(),
|
a.group_pids.len(),
|
||||||
b.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~
|
/// Gotta get partial ordering? No problem, here's something to deal with it~
|
||||||
pub fn get_ordering<T: std::cmp::PartialOrd>(
|
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 {
|
) -> std::cmp::Ordering {
|
||||||
match a_val.partial_cmp(&b_val) {
|
match a_val.partial_cmp(&b_val) {
|
||||||
Some(x) => match x {
|
Some(x) => match x {
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
if reverse_order {
|
if descending_order {
|
||||||
std::cmp::Ordering::Less
|
std::cmp::Ordering::Less
|
||||||
} else {
|
} else {
|
||||||
std::cmp::Ordering::Greater
|
std::cmp::Ordering::Greater
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
if reverse_order {
|
if descending_order {
|
||||||
std::cmp::Ordering::Greater
|
std::cmp::Ordering::Greater
|
||||||
} else {
|
} else {
|
||||||
std::cmp::Ordering::Less
|
std::cmp::Ordering::Less
|
||||||
|
Loading…
x
Reference in New Issue
Block a user