From d6c2ef3e2226620eed3198c374f1cd917141f5bd Mon Sep 17 00:00:00 2001 From: Zeb Piasecki Date: Sat, 3 Aug 2024 01:10:36 -0400 Subject: [PATCH] feat: add option to move avg CPU to another row (#1487) Adds an `average_cpu_row` option to move the average CPU usage to its own row when using basic mode. --- .../configuration/config-file/flags.md | 73 ++++++------ schema/nightly/bottom.json | 6 + src/app.rs | 1 + src/canvas/widgets/cpu_basic.rs | 110 ++++++++++++++---- src/options.rs | 12 ++ src/options/args.rs | 2 + src/options/config/flags.rs | 1 + 7 files changed, 147 insertions(+), 58 deletions(-) diff --git a/docs/content/configuration/config-file/flags.md b/docs/content/configuration/config-file/flags.md index f65edbf3..8edffda3 100644 --- a/docs/content/configuration/config-file/flags.md +++ b/docs/content/configuration/config-file/flags.md @@ -14,39 +14,40 @@ hide_avg_cpu = true Most of the [command line flags](../command-line-options.md) have config file equivalents to avoid having to type them out each time: -| Field | Type | Functionality | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | -| `hide_avg_cpu` | Boolean | Hides the average CPU usage. | -| `dot_marker` | Boolean | Uses a dot marker for graphs. | -| `cpu_left_legend` | Boolean | Puts the CPU chart legend to the left side. | -| `current_usage` | Boolean | Sets process CPU% to be based on current CPU%. | -| `group_processes` | Boolean | Groups processes with the same name by default. | -| `case_sensitive` | Boolean | Enables case sensitivity by default. | -| `whole_word` | Boolean | Enables whole-word matching by default. | -| `regex` | Boolean | Enables regex by default. | -| `basic` | Boolean | Hides graphs and uses a more basic look. | -| `use_old_network_legend` | Boolean | DEPRECATED - uses the older network legend. | -| `battery` | Boolean | Shows the battery widget. | -| `rate` | Unsigned Int (represents milliseconds) or String (represents human time) | Sets a refresh rate in ms. | -| `default_time_value` | Unsigned Int (represents milliseconds) or String (represents human time) | Default time value for graphs in ms. | -| `time_delta` | Unsigned Int (represents milliseconds) or String (represents human time) | The amount in ms changed upon zooming. | -| `hide_time` | Boolean | Hides the time scale. | -| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) | Sets the temperature unit type. | -| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | Sets the default widget type, use --help for more info. | -| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | Sets the n'th selected widget type as the default. | -| `disable_click` | Boolean | Disables mouse clicks. | -| `enable_cache_memory` | Boolean | Enable cache and buffer memory stats (not available on Windows). | -| `process_memory_as_value` | Boolean | Defaults to showing process memory usage by value. | -| `tree` | Boolean | Defaults to showing the process widget in tree mode. | -| `show_table_scroll_position` | Boolean | Shows the scroll position tracker in table widgets. | -| `process_command` | Boolean | Show processes as their commands by default. | -| `disable_advanced_kill` | Boolean | Hides advanced options to stop a process on Unix-like systems. | -| `network_use_binary_prefix` | Boolean | Displays the network widget with binary prefixes. | -| `network_use_bytes` | Boolean | Displays the network widget using bytes. | -| `network_use_log` | Boolean | Displays the network widget with a log scale. | -| `enable_gpu` | Boolean | Shows the GPU widgets. | -| `retention` | String (human readable time, such as "10m", "1h", etc.) | How much data is stored at once in terms of time. | -| `unnormalized_cpu` | Boolean | Show process CPU% without normalizing over the number of cores. | -| `expanded` | Boolean | Expand the default widget upon starting the app. | -| `memory_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the memory widget. | -| `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. | +| Field | Type | Functionality | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | +| `hide_avg_cpu` | Boolean | Hides the average CPU usage. | +| `dot_marker` | Boolean | Uses a dot marker for graphs. | +| `cpu_left_legend` | Boolean | Puts the CPU chart legend to the left side. | +| `current_usage` | Boolean | Sets process CPU% to be based on current CPU%. | +| `group_processes` | Boolean | Groups processes with the same name by default. | +| `case_sensitive` | Boolean | Enables case sensitivity by default. | +| `whole_word` | Boolean | Enables whole-word matching by default. | +| `regex` | Boolean | Enables regex by default. | +| `basic` | Boolean | Hides graphs and uses a more basic look. | +| `use_old_network_legend` | Boolean | DEPRECATED - uses the older network legend. | +| `battery` | Boolean | Shows the battery widget. | +| `rate` | Unsigned Int (represents milliseconds) or String (represents human time) | Sets a refresh rate in ms. | +| `default_time_value` | Unsigned Int (represents milliseconds) or String (represents human time) | Default time value for graphs in ms. | +| `time_delta` | Unsigned Int (represents milliseconds) or String (represents human time) | The amount in ms changed upon zooming. | +| `hide_time` | Boolean | Hides the time scale. | +| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) | Sets the temperature unit type. | +| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | Sets the default widget type, use --help for more info. | +| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | Sets the n'th selected widget type as the default. | +| `disable_click` | Boolean | Disables mouse clicks. | +| `enable_cache_memory` | Boolean | Enable cache and buffer memory stats (not available on Windows). | +| `process_memory_as_value` | Boolean | Defaults to showing process memory usage by value. | +| `tree` | Boolean | Defaults to showing the process widget in tree mode. | +| `show_table_scroll_position` | Boolean | Shows the scroll position tracker in table widgets. | +| `process_command` | Boolean | Show processes as their commands by default. | +| `disable_advanced_kill` | Boolean | Hides advanced options to stop a process on Unix-like systems. | +| `network_use_binary_prefix` | Boolean | Displays the network widget with binary prefixes. | +| `network_use_bytes` | Boolean | Displays the network widget using bytes. | +| `network_use_log` | Boolean | Displays the network widget with a log scale. | +| `enable_gpu` | Boolean | Shows the GPU widgets. | +| `retention` | String (human readable time, such as "10m", "1h", etc.) | How much data is stored at once in terms of time. | +| `unnormalized_cpu` | Boolean | Show process CPU% without normalizing over the number of cores. | +| `expanded` | Boolean | Expand the default widget upon starting the app. | +| `memory_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the memory widget. | +| `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. | \ No newline at end of file diff --git a/schema/nightly/bottom.json b/schema/nightly/bottom.json index d109e011..781a524a 100644 --- a/schema/nightly/bottom.json +++ b/schema/nightly/bottom.json @@ -246,6 +246,12 @@ "null" ] }, + "average_cpu_row": { + "type": [ + "boolean", + "null" + ] + }, "basic": { "type": [ "boolean", diff --git a/src/app.rs b/src/app.rs index 7cfe0af1..b2d82fcc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -68,6 +68,7 @@ pub struct AppConfigFields { pub network_scale_type: AxisScaling, pub network_use_binary_prefix: bool, pub retention_ms: u64, + pub dedicated_average_row: bool, } /// For filtering out information diff --git a/src/canvas/widgets/cpu_basic.rs b/src/canvas/widgets/cpu_basic.rs index 63306cc8..b9652017 100644 --- a/src/canvas/widgets/cpu_basic.rs +++ b/src/canvas/widgets/cpu_basic.rs @@ -20,7 +20,7 @@ use crate::{ impl Painter { /// Inspired by htop. pub fn draw_basic_cpu( - &self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64, + &self, f: &mut Frame<'_>, app_state: &mut App, mut draw_loc: Rect, widget_id: u64, ) { // Skip the first element, it's the "all" element if app_state.converted_data.cpu_data.len() > 1 { @@ -45,6 +45,32 @@ impl Painter { ); } + let (cpu_data, avg_data) = + maybe_split_avg(cpu_data, app_state.app_config_fields.dedicated_average_row); + + if let Some(avg) = avg_data { + let (outer, inner, ratio, style) = self.cpu_info(&avg); + let [cores_loc, mut avg_loc] = + Layout::vertical([Constraint::Min(0), Constraint::Length(1)]).areas(draw_loc); + + // The cores section all have horizontal margin, so to line up with the cores we + // need to add some margin ourselves. + avg_loc.x += 1; + avg_loc.width -= 2; + + f.render_widget( + PipeGauge::default() + .gauge_style(style) + .label_style(style) + .inner_label(inner) + .start_label(outer) + .ratio(ratio), + avg_loc, + ); + + draw_loc = cores_loc; + } + if draw_loc.height > 0 { let remaining_height = usize::from(draw_loc.height); const REQUIRED_COLUMNS: usize = 4; @@ -56,27 +82,7 @@ impl Painter { .direction(Direction::Horizontal) .split(draw_loc); - let mut gauge_info = cpu_data.iter().map(|cpu| match cpu { - CpuWidgetData::All => unreachable!(), - CpuWidgetData::Entry { - data_type, - data: _, - last_entry, - } => { - let (outer, style) = match data_type { - CpuDataType::Avg => ("AVG".to_string(), self.colours.avg_cpu_colour), - CpuDataType::Cpu(index) => ( - format!("{index:<3}",), - self.colours.cpu_colour_styles - [index % self.colours.cpu_colour_styles.len()], - ), - }; - let inner = format!("{:>3.0}%", last_entry.round()); - let ratio = last_entry / 100.0; - - (outer, inner, ratio, style) - } - }); + let mut gauge_info = cpu_data.iter().map(|cpu| self.cpu_info(cpu)); // Very ugly way to sync the gauge limit across all gauges. let hide_parts = columns @@ -138,4 +144,64 @@ impl Painter { } } } + + fn cpu_info(&self, cpu: &CpuWidgetData) -> (String, String, f64, tui::style::Style) { + let CpuWidgetData::Entry { + data_type, + last_entry, + .. + } = cpu + else { + unreachable!() + }; + + let (outer, style) = match data_type { + CpuDataType::Avg => ("AVG".to_string(), self.colours.avg_cpu_colour), + CpuDataType::Cpu(index) => ( + format!("{index:<3}",), + self.colours.cpu_colour_styles[index % self.colours.cpu_colour_styles.len()], + ), + }; + let inner = format!("{:>3.0}%", last_entry.round()); + let ratio = last_entry / 100.0; + + (outer, inner, ratio, style) + } +} + +fn maybe_split_avg( + data: &[CpuWidgetData], separate_avg: bool, +) -> (Vec, Option) { + let mut cpu_data = vec![]; + let mut avg_data = None; + + for cpu in data { + let CpuWidgetData::Entry { + data_type, + data, + last_entry, + } = cpu + else { + unreachable!() + }; + + match data_type { + CpuDataType::Avg if separate_avg => { + avg_data = Some(CpuWidgetData::Entry { + data_type: *data_type, + data: data.clone(), + last_entry: *last_entry, + }); + } + _ => { + cpu_data.push(CpuWidgetData::Entry { + data_type: *data_type, + data: data.clone(), + last_entry: *last_entry, + }); + } + } + } + + (cpu_data, avg_data) } diff --git a/src/options.rs b/src/options.rs index b770b347..4d95ce72 100644 --- a/src/options.rs +++ b/src/options.rs @@ -269,6 +269,7 @@ pub(crate) fn init_app( network_unit_type, network_use_binary_prefix, retention_ms, + dedicated_average_row: get_dedicated_avg_row(args, config), }; let table_config = ProcTableConfig { @@ -677,6 +678,17 @@ fn get_show_average_cpu(args: &BottomArgs, config: &Config) -> bool { true } +fn get_dedicated_avg_row(_args: &BottomArgs, config: &Config) -> bool { + let conf = config + .flags + .as_ref() + .and_then(|flags| flags.average_cpu_row) + .unwrap_or(false); + + // args.cpu.average_cpu_row || conf + conf +} + #[inline] fn get_default_time_value( args: &BottomArgs, config: &Config, retention_ms: u64, diff --git a/src/options/args.rs b/src/options/args.rs index a68c32db..c9123e72 100644 --- a/src/options/args.rs +++ b/src/options/args.rs @@ -428,6 +428,8 @@ pub struct CpuArgs { help = "Puts the CPU chart legend on the left side." )] pub cpu_left_legend: bool, + // #[arg(short = 'A', long, action = ArgAction::SetTrue, help = "Moves the average CPU usage entry to its own row when using basic mode.")] + // pub average_cpu_row: bool, } /// Memory argument/config options. diff --git a/src/options/config/flags.rs b/src/options/config/flags.rs index 00bdb334..39f18eaa 100644 --- a/src/options/config/flags.rs +++ b/src/options/config/flags.rs @@ -43,4 +43,5 @@ pub(crate) struct FlagConfig { pub(crate) enable_gpu: Option, pub(crate) enable_cache_memory: Option, pub(crate) retention: Option, + pub(crate) average_cpu_row: Option, }