diff --git a/docs/content/configuration/command-line-options.md b/docs/content/configuration/command-line-options.md index aee22700..64cf1e38 100644 --- a/docs/content/configuration/command-line-options.md +++ b/docs/content/configuration/command-line-options.md @@ -35,6 +35,7 @@ see information on these options by running `btm -h`, or run `btm --help` to dis | `--disable_advanced_kill` | Hides additional stopping options on Unix-like systems. | | `--get_threads` | Also gather process thread information. | | `-g, --group_processes` | Groups processes with the same name by default. No effect if `--tree` is set. | +| `--hide_k_threads` | Hide kernel threads by default. | | `--process_memory_as_value` | Defaults to showing process memory usage by value. | | `--process_command` | Shows the full command name instead of the process name by default. | | `-R, --regex` | Enables regex by default while searching. | diff --git a/docs/content/configuration/config-file/flags.md b/docs/content/configuration/config-file/flags.md index ec0bedb9..2a91e37c 100644 --- a/docs/content/configuration/config-file/flags.md +++ b/docs/content/configuration/config-file/flags.md @@ -52,3 +52,4 @@ each time: | `network_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the network widget. | | `average_cpu_row` | Boolean | Moves the average CPU usage entry to its own row when using basic mode. | | `tree_collapse` | Boolean | Collapse process tree by default. | +| `hide_k_threads` | Boolean | Hide kernel threads by default. | diff --git a/docs/content/usage/widgets/process.md b/docs/content/usage/widgets/process.md index 9b5f4ec9..cadc63f3 100644 --- a/docs/content/usage/widgets/process.md +++ b/docs/content/usage/widgets/process.md @@ -233,6 +233,7 @@ Note that key bindings are generally case-sensitive. | ++t++ , ++f5++ | Toggle tree mode | | ++M++ | Sort by gpu memory usage, press again to reverse sorting order | | ++C++ | Sort by gpu usage, press again to reverse sorting order | +| ++z++ | Toggle the hiding of kernel threads | ### Sort sub-widget diff --git a/sample_configs/default_config.toml b/sample_configs/default_config.toml index b90206ee..4ed34160 100644 --- a/sample_configs/default_config.toml +++ b/sample_configs/default_config.toml @@ -103,6 +103,9 @@ # Hides advanced options to stop a process on Unix-like systems. #disable_advanced_kill = false +# Hides the kernel threads +#hide_k_threads = false + # Hide GPU(s) information #disable_gpu = false @@ -123,7 +126,7 @@ #[processes] # The columns shown by the process widget. The following columns are supported (the GPU columns are only available if the GPU feature is enabled when built): # PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU% -#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"] +#columns = ["PID", "Name", "CPU%", "Mem%", "Virt", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"] # CPU widget configuration diff --git a/schema/nightly/bottom.json b/schema/nightly/bottom.json index 501fe4e0..352c63d8 100644 --- a/schema/nightly/bottom.json +++ b/schema/nightly/bottom.json @@ -388,6 +388,12 @@ "null" ] }, + "hide_k_threads": { + "type": [ + "boolean", + "null" + ] + }, "hide_table_gap": { "type": [ "boolean", diff --git a/src/app.rs b/src/app.rs index 22e5d7b1..e5965df6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -57,6 +57,8 @@ pub struct AppConfigFields { pub show_table_scroll_position: bool, #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] pub is_advanced_kill: bool, + #[cfg(target_os = "linux")] + pub hide_k_threads: bool, pub memory_legend_position: Option, // TODO: Remove these, move network details state-side. pub network_unit_type: DataUnit, @@ -1231,6 +1233,19 @@ impl App { } 'I' => self.invert_sort(), '%' => self.toggle_percentages(), + #[cfg(target_os = "linux")] + 'z' => { + if let BottomWidgetType::Proc = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .states + .proc_state + .widget_states + .get_mut(&self.current_widget.widget_id) + { + proc_widget_state.toggle_k_thread(); + } + } + } _ => {} } diff --git a/src/collection/processes.rs b/src/collection/processes.rs index 735821e8..4c3b0196 100644 --- a/src/collection/processes.rs +++ b/src/collection/processes.rs @@ -57,8 +57,6 @@ pub enum ProcessType { Regular, /// A kernel process. - /// - /// TODO: Use ? Kernel, /// A thread spawned by a regular user process. diff --git a/src/collection/processes/linux/mod.rs b/src/collection/processes/linux/mod.rs index b49fb27d..cffdbe38 100644 --- a/src/collection/processes/linux/mod.rs +++ b/src/collection/processes/linux/mod.rs @@ -169,6 +169,8 @@ fn read_proc( let (parent_pid, process_type) = if let Some(thread_parent) = thread_parent { (Some(thread_parent), ProcessType::ProcessThread) + } else if stat.is_kernel_thread { + (Some(stat.ppid), ProcessType::Kernel) } else { (Some(stat.ppid), ProcessType::Regular) }; diff --git a/src/collection/processes/linux/process.rs b/src/collection/processes/linux/process.rs index 6d6f5b32..39658dd7 100644 --- a/src/collection/processes/linux/process.rs +++ b/src/collection/processes/linux/process.rs @@ -59,6 +59,9 @@ pub(crate) struct Stat { /// The start time of the process, represented in clock ticks. pub start_time: u64, + + /// Kernel thread + pub is_kernel_thread: bool, } impl Stat { @@ -92,9 +95,15 @@ impl Stat { .ok_or_else(|| anyhow!("missing state"))?; let ppid: Pid = next_part(&mut rest)?.parse()?; - // Skip 9 fields until utime (pgrp, session, tty_nr, tpgid, flags, minflt, - // cminflt, majflt, cmajflt). - let mut rest = rest.skip(9); + // Skip 4 fields (pgrp, session, tty_nr, tpgid) + let mut rest = rest.skip(4); + + // read flags for kernel thread (PF_KTHREAD from include/linux/sched.h) + let flags: u32 = next_part(&mut rest)?.parse()?; + let is_kernel_thread: bool = flags & 0x00200000 != 0; + + // Skip 4 fields (minflt, cminflt, majflt, cmajflt) + let mut rest = rest.skip(4); let utime: u64 = next_part(&mut rest)?.parse()?; let stime: u64 = next_part(&mut rest)?.parse()?; @@ -115,6 +124,7 @@ impl Stat { rss, vsize, start_time, + is_kernel_thread, }) } diff --git a/src/constants.rs b/src/constants.rs index 995d14ef..9201b272 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -65,7 +65,7 @@ const CPU_HELP_TEXT: [&str; 2] = [ "Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart", ]; -const PROCESS_HELP_TEXT: [&str; 19] = [ +const PROCESS_HELP_TEXT: [&str; 20] = [ "3 - Process widget", "dd, F9, Delete Kill the selected process", "c Sort by CPU usage, press again to reverse", @@ -85,6 +85,7 @@ const PROCESS_HELP_TEXT: [&str; 19] = [ "click on header Sorts the entries by that column, click again to invert the sort", "C Sort by GPU usage, press again to reverse", "M Sort by GPU memory usage, press again to reverse", + "z Toggle the display of kernel threads", ]; const SEARCH_HELP_TEXT: [&str; 51] = [ @@ -352,6 +353,9 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott # Hides advanced options to stop a process on Unix-like systems. #disable_advanced_kill = false +# Hides the kernel threads +#hide_k_threads = false + # Hide GPU(s) information #disable_gpu = false @@ -372,7 +376,7 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott #[processes] # The columns shown by the process widget. The following columns are supported (the GPU columns are only available if the GPU feature is enabled when built): # PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU% -#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"] +#columns = ["PID", "Name", "CPU%", "Mem%", "Virt", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"] # CPU widget configuration diff --git a/src/options.rs b/src/options.rs index 22055ebd..42f275db 100644 --- a/src/options.rs +++ b/src/options.rs @@ -240,6 +240,9 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL let is_default_command = is_flag_enabled!(process_command, args.process, config); #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] let is_advanced_kill = !(is_flag_enabled!(disable_advanced_kill, args.process, config)); + #[cfg(target_os = "linux")] + let hide_k_threads = is_flag_enabled!(hide_k_threads, args.process, config); + let process_memory_as_value = is_flag_enabled!(process_memory_as_value, args.process, config); let is_default_tree_collapsed = is_flag_enabled!(tree_collapse, args.process, config); @@ -316,6 +319,8 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL ), #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] is_advanced_kill, + #[cfg(target_os = "linux")] + hide_k_threads, memory_legend_position, network_legend_position, network_scale_type, diff --git a/src/options/args.rs b/src/options/args.rs index 94ed412e..cae2b382 100644 --- a/src/options/args.rs +++ b/src/options/args.rs @@ -324,6 +324,15 @@ pub struct ProcessArgs { )] pub disable_advanced_kill: bool, + #[cfg(target_os = "linux")] + #[arg( + long, + action = ArgAction::SetTrue, + help = "Hide kernel threads by default.", + alias = "hide-k-threads" + )] + pub hide_k_threads: bool, + #[arg( long, action = ArgAction::SetTrue, diff --git a/src/options/config/flags.rs b/src/options/config/flags.rs index 211af8af..591d791a 100644 --- a/src/options/config/flags.rs +++ b/src/options/config/flags.rs @@ -40,6 +40,8 @@ pub(crate) struct GeneralConfig { pub(crate) process_command: Option, // #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] pub(crate) disable_advanced_kill: Option, // This does nothing on Windows, but we leave it enabled to make the config file consistent across platforms. + // #[cfg(target_os = "linux")] + pub(crate) hide_k_threads: Option, pub(crate) network_use_bytes: Option, pub(crate) network_use_log: Option, pub(crate) network_use_binary_prefix: Option, diff --git a/src/widgets/process_table.rs b/src/widgets/process_table.rs index b89f28be..a814b463 100644 --- a/src/widgets/process_table.rs +++ b/src/widgets/process_table.rs @@ -233,6 +233,8 @@ pub struct ProcWidgetState { pub is_sort_open: bool, pub force_rerender: bool, pub force_update_data: bool, + #[cfg(target_os = "linux")] + pub hide_k_threads: bool, } impl ProcWidgetState { @@ -434,6 +436,8 @@ impl ProcWidgetState { force_update_data: false, default_sort_index, default_sort_order, + #[cfg(target_os = "linux")] + hide_k_threads: config.hide_k_threads, }; table.sort_table.set_data(table.column_text()); @@ -514,6 +518,11 @@ impl ProcWidgetState { .map(|q| q.check(process, is_using_command)) .unwrap_or(true) { + #[cfg(target_os = "linux")] + if self.hide_k_threads && process.process_type.is_kernel() { + return None; + } + Some(*pid) } else { None @@ -759,6 +768,11 @@ impl ProcWidgetState { let is_mem_percent = self.is_mem_percent(); let filtered_iter = process_harvest.values().filter(|process| { + #[cfg(target_os = "linux")] + if self.hide_k_threads && process.process_type.is_kernel() { + return false; + } + search_query .as_ref() .map(|query| query.check(process, is_using_command)) @@ -892,6 +906,12 @@ impl ProcWidgetState { self.force_update_data = true; } + #[cfg(target_os = "linux")] + pub fn toggle_k_thread(&mut self) { + self.hide_k_threads = !self.hide_k_threads; + self.force_rerender_and_update(); + } + /// Marks the selected column as hidden, and automatically resets the /// selected column to the default sort index and order. fn hide_column(&mut self, column: ProcWidgetColumn) { @@ -1150,6 +1170,9 @@ mod test { use super::*; use crate::widgets::MemUsage; + #[cfg(target_os = "linux")] + use crate::collection::processes::ProcessType; + #[test] fn test_proc_sort() { let a = ProcWidgetData { @@ -1660,4 +1683,60 @@ mod test { assert!(!expanded_by_default.is_collapsed(1)); } } + #[cfg(target_os = "linux")] + /// Sanity test to ensure kernel thread processes are toggled + #[test] + fn test_toggle_k_threads() { + let init_columns = [ + ProcWidgetColumn::ProcNameOrCommand, + ProcWidgetColumn::PidOrCount, + ProcWidgetColumn::State, + ProcWidgetColumn::Mem, + ProcWidgetColumn::ProcNameOrCommand, + ]; + let mut state = init_default_state(&init_columns); + let process_harvest = ProcessHarvest { + pid: 1, + ..Default::default() + }; + let k_process_harvest = ProcessHarvest { + pid: 2, + process_type: ProcessType::Kernel, + ..Default::default() + }; + // test get_normal_data default is filtered by toggle_k_thread + let mut normal_proc_harvest: BTreeMap = BTreeMap::new(); + normal_proc_harvest.insert(1, process_harvest.clone()); + normal_proc_harvest.insert(2, k_process_harvest.clone()); + let default_normal_results = state.get_normal_data(&normal_proc_harvest).len(); + assert!(default_normal_results == 2); + state.toggle_k_thread(); + let filtered_normal_results = state.get_normal_data(&normal_proc_harvest).len(); + assert!(filtered_normal_results == 1); + // test that get_normal_data in grouped mode is still filtered + state.mode = ProcWidgetMode::Grouped; + let filtered_grouped_results = state.get_normal_data(&normal_proc_harvest).len(); + assert!(filtered_grouped_results == 1); + // test that get_tree_data is filtered on toggle_k_thread + let tree_collapsed = TreeCollapsed::new(false); + state.mode = ProcWidgetMode::Tree(tree_collapsed.clone()); + state.hide_k_threads = false; + let mut tree_proc_data = ProcessData::default(); + tree_proc_data.process_harvest.insert(1, process_harvest); + tree_proc_data.process_harvest.insert(2, k_process_harvest); + tree_proc_data.orphan_pids = vec![1, 2]; + let tree_stored_data = StoredData { + process_data: tree_proc_data, + ..Default::default() + }; + let default_tree_results = state + .get_tree_data(&tree_collapsed, &tree_stored_data) + .len(); + assert!(default_tree_results == 2); + state.toggle_k_thread(); + let filtered_tree_results = state + .get_tree_data(&tree_collapsed, &tree_stored_data) + .len(); + assert!(filtered_tree_results == 1); + } }