Skip to content

Commit

Permalink
feature: add customizable process columns (#1115)
Browse files Browse the repository at this point in the history
* feature: add customizable process columns

* Add some tests and actual logic

* more tests

* update changelog

* update config field

* even more tests

* update documentation

* more testing
  • Loading branch information
ClementTsang committed Apr 29, 2023
1 parent 7162e9c commit 605314d
Show file tree
Hide file tree
Showing 9 changed files with 621 additions and 171 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#1024](https://github.com/ClementTsang/bottom/pull/1024): Support FreeBSD temperature sensors based on `hw.temperature`.
- [#1063](https://github.com/ClementTsang/bottom/pull/1063): Add buffer and cache memory tracking.
- [#1106](https://github.com/ClementTsang/bottom/pull/1106): Add current battery charging state.
- [#1115](https://github.com/ClementTsang/bottom/pull/1115): Add customizable process columns to config file.

## Changes

Expand Down
28 changes: 14 additions & 14 deletions docs/content/usage/widgets/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,20 +126,20 @@ You can also paste search queries (e.g. ++shift+insert++, ++ctrl+shift+v++).

Note all keywords are case-insensitive. To search for a process/command that collides with a keyword, surround the term with quotes (e.x. `"cpu"`).

| Keywords | Example | Description |
| ------------------------ | ------------------------------------- | ------------------------------------------------------------------------------- |
| | `btm` | Matches by process or command name; supports regex |
| `pid` | `pid=1044` | Matches by PID; supports regex |
| `cpu` <br/> `cpu%` | `cpu > 0.5` | Matches the CPU column; supports comparison operators |
| `memb` | `memb > 1000 b` | Matches the memory column in terms of bytes; supports comparison operators |
| `mem` <br/> `mem%` | `mem < 0.5` | Matches the memory column in terms of percent; supports comparison operators |
| `read` <br/> `r/s` | `read = 1 mb` | Matches the read/s column in terms of bytes; supports comparison operators |
| `write` <br/> `w/s` | `write >= 1 kb` | Matches the write/s column in terms of bytes; supports comparison operators |
| `tread` <br/> `t.read` | `tread <= 1024 gb` | Matches he total read column in terms of bytes; supports comparison operators |
| `twrite` <br/> `t.write` | `twrite > 1024 tb` | Matches the total write column in terms of bytes; supports comparison operators |
| `user` | `user=root` | Matches by user; supports regex |
| `state` | `state=running` | Matches by state; supports regex |
| `()` | `(<COND 1> AND <COND 2>) OR <COND 3>` | Group together a condition |
| Keywords | Example | Description |
| ------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------- |
| | `btm` | Matches by process or command name; supports regex |
| `pid` | `pid=1044` | Matches by PID; supports regex |
| `cpu` <br/> `cpu%` | `cpu > 0.5` | Matches the CPU column; supports comparison operators |
| `memb` | `memb > 1000 b` | Matches the memory column in terms of bytes; supports comparison operators |
| `mem` <br/> `mem%` | `mem < 0.5` | Matches the memory column in terms of percent; supports comparison operators |
| `read` <br/> `r/s` <br/> `rps` | `read = 1 mb` | Matches the read/s column in terms of bytes; supports comparison operators |
| `write` <br/> `w/s` <br/> `wps` | `write >= 1 kb` | Matches the write/s column in terms of bytes; supports comparison operators |
| `tread` <br/> `t.read` | `tread <= 1024 gb` | Matches he total read column in terms of bytes; supports comparison operators |
| `twrite` <br/> `t.write` | `twrite > 1024 tb` | Matches the total write column in terms of bytes; supports comparison operators |
| `user` | `user=root` | Matches by user; supports regex |
| `state` | `state=running` | Matches by state; supports regex |
| `()` | `(<COND 1> AND <COND 2>) OR <COND 3>` | Group together a condition |

#### Comparison operators

Expand Down
12 changes: 6 additions & 6 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use states::*;
use typed_builder::*;
use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};

use crate::widgets::{ProcWidgetMode, ProcWidgetState};
use crate::widgets::{ProcWidgetColumn, ProcWidgetMode};
use crate::{
constants,
data_conversion::ConvertedData,
Expand Down Expand Up @@ -300,7 +300,7 @@ impl App {
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.on_tab();
proc_widget_state.toggle_tab();
}
}
}
Expand Down Expand Up @@ -1193,7 +1193,7 @@ impl App {
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.select_column(ProcWidgetState::CPU);
proc_widget_state.select_column(ProcWidgetColumn::Cpu);
}
}
}
Expand All @@ -1203,7 +1203,7 @@ impl App {
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.select_column(ProcWidgetState::MEM);
proc_widget_state.select_column(ProcWidgetColumn::Mem);
}
} else if let Some(disk) = self
.disk_state
Expand All @@ -1218,7 +1218,7 @@ impl App {
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.select_column(ProcWidgetState::PID_OR_COUNT);
proc_widget_state.select_column(ProcWidgetColumn::PidOrCount);
}
} else if let Some(disk) = self
.disk_state
Expand All @@ -1243,7 +1243,7 @@ impl App {
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.select_column(ProcWidgetState::PROC_NAME_OR_CMD);
proc_widget_state.select_column(ProcWidgetColumn::ProcNameOrCmd);
}
} else if let Some(disk) = self
.disk_state
Expand Down
4 changes: 2 additions & 2 deletions src/app/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,8 +584,8 @@ impl std::str::FromStr for PrefixType {
"cpu" | "cpu%" => Ok(PCpu),
"mem" | "mem%" => Ok(PMem),
"memb" => Ok(MemBytes),
"read" | "r/s" => Ok(Rps),
"write" | "w/s" => Ok(Wps),
"read" | "r/s" | "rps" => Ok(Rps),
"write" | "w/s" | "wps" => Ok(Wps),
"tread" | "t.read" => Ok(TRead),
"twrite" | "t.write" => Ok(TWrite),
"pid" => Ok(Pid),
Expand Down
11 changes: 8 additions & 3 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,11 @@ pub const SEARCH_HELP_TEXT: [&str; 48] = [
"cpu, cpu% ex: cpu > 4.2",
"mem, mem% ex: mem < 4.2",
"memb ex: memb < 100 kb",
"read, r/s ex: read >= 1 b",
"write, w/s ex: write <= 1 tb",
"read, r/s, rps ex: read >= 1 b",
"write, w/s, wps ex: write <= 1 tb",
"tread, t.read ex: tread = 1",
"twrite, t.write ex: twrite = 1",
"user ex: user = root",
"user ex: user = root",
"state ex: state = running",
"",
"Comparison operators:",
Expand Down Expand Up @@ -588,6 +588,11 @@ pub const CONFIG_TEXT: &str = r##"# This is a default config file for bottom. A
# How much data is stored at once in terms of time.
#retention = "10m"
# These are flags around the process widget.
#[processes]
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State"]
# These are all the components that support custom theming. Note that colour support
# will depend on terminal support.
Expand Down
40 changes: 34 additions & 6 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{

use clap::ArgMatches;
use hashbrown::{HashMap, HashSet};
use indexmap::IndexSet;
use layout_options::*;
use regex::Regex;
use serde::{Deserialize, Serialize};
Expand All @@ -23,12 +24,15 @@ use crate::{
utils::error::{self, BottomError},
widgets::{
BatteryWidgetState, CpuWidgetState, DiskTableWidget, MemWidgetState, NetWidgetState,
ProcWidgetMode, ProcWidgetState, TempWidgetState,
ProcColumn, ProcTableConfig, ProcWidgetMode, ProcWidgetState, TempWidgetState,
},
};

pub mod layout_options;

pub mod process_columns;
use self::process_columns::ProcessConfig;

use anyhow::{Context, Result};

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
Expand All @@ -40,6 +44,7 @@ pub struct Config {
pub mount_filter: Option<IgnoreList>,
pub temp_filter: Option<IgnoreList>,
pub net_filter: Option<IgnoreList>,
pub processes: Option<ProcessConfig>,
}

#[derive(Clone, Debug, Default, Deserialize, Serialize, TypedBuilder)]
Expand Down Expand Up @@ -218,6 +223,24 @@ pub fn build_app(
let network_scale_type = get_network_scale_type(matches, config);
let network_use_binary_prefix = is_flag_enabled!(network_use_binary_prefix, matches, config);

let proc_columns: Option<IndexSet<ProcColumn>> = {
let columns = config
.processes
.as_ref()
.and_then(|cfg| cfg.columns.clone());

match columns {
Some(columns) => {
if columns.is_empty() {
None
} else {
Some(IndexSet::from_iter(columns.into_iter()))
}
}
None => None,
}
};

let app_config_fields = AppConfigFields {
update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config)
.context("Update 'rate' in your config file.")?,
Expand Down Expand Up @@ -247,6 +270,14 @@ pub fn build_app(
retention_ms,
};

let table_config = ProcTableConfig {
is_case_sensitive,
is_match_whole_word,
is_use_regex,
show_memory_as_values,
is_command: is_default_command,
};

for row in &widget_layout.rows {
for col in &row.children {
for col_row in &col.children {
Expand Down Expand Up @@ -325,12 +356,9 @@ pub fn build_app(
ProcWidgetState::new(
&app_config_fields,
mode,
is_case_sensitive,
is_match_whole_word,
is_use_regex,
show_memory_as_values,
is_default_command,
table_config,
colours,
&proc_columns,
),
);
}
Expand Down
83 changes: 83 additions & 0 deletions src/options/process_columns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use serde::{Deserialize, Serialize};

use crate::widgets::ProcColumn;

/// Process column settings.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct ProcessConfig {
pub columns: Option<Vec<ProcColumn>>,
}

#[cfg(test)]
mod test {
use crate::widgets::ProcColumn;

use super::ProcessConfig;

#[test]
fn empty_column_setting() {
let config = "";
let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap();
assert!(generated.columns.is_none());
}

#[test]
fn process_column_settings() {
let config = r#"
columns = ["CPU%", "PiD", "user", "MEM", "Tread", "T.Write", "Rps", "W/s"]
"#;

let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap();
assert_eq!(
generated.columns,
Some(vec![
ProcColumn::CpuPercent,
ProcColumn::Pid,
ProcColumn::User,
ProcColumn::MemoryVal,
ProcColumn::TotalRead,
ProcColumn::TotalWrite,
ProcColumn::ReadPerSecond,
ProcColumn::WritePerSecond,
]),
);
}

#[test]
fn process_column_settings_2() {
let config = r#"
columns = ["MEM%"]
"#;

let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap();
assert_eq!(generated.columns, Some(vec![ProcColumn::MemoryPercent]));
}

#[test]
fn process_column_settings_3() {
let config = r#"
columns = ["MEM%", "TWrite", "Cpuz", "read", "wps"]
"#;

toml_edit::de::from_str::<ProcessConfig>(config).expect_err("Should error out!");
}

#[test]
fn process_column_settings_4() {
let config = r#"columns = ["Twrite", "T.Write"]"#;
let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap();
assert_eq!(generated.columns, Some(vec![ProcColumn::TotalWrite; 2]));

let config = r#"columns = ["Tread", "T.read"]"#;
let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap();
assert_eq!(generated.columns, Some(vec![ProcColumn::TotalRead; 2]));

let config = r#"columns = ["read", "rps", "r/s"]"#;
let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap();
assert_eq!(generated.columns, Some(vec![ProcColumn::ReadPerSecond; 3]));

let config = r#"columns = ["write", "wps", "w/s"]"#;
let generated: ProcessConfig = toml_edit::de::from_str(config).unwrap();
assert_eq!(generated.columns, Some(vec![ProcColumn::WritePerSecond; 3]));
}
}

0 comments on commit 605314d

Please sign in to comment.