diff --git a/README.md b/README.md index a0cf651d..4dda210b 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Run using `btm`. - `-v`, `--version` displays the version number and exits. -- `-r `, `--rate ` will set the refresh rate in _milliseconds_. Lowest it can go is 250ms, the highest it can go is 2128 - 1. Defaults to 1000ms, and lower values may take more resources due to more frequent polling of data, and may be less accurate in some circumstances. +- `-r `, `--rate ` will set the refresh rate in _milliseconds_. Lowest it can go is 250ms, the highest it can go is 264 - 1. Defaults to 1000ms, and lower values may take more resources due to more frequent polling of data, and may be less accurate in some circumstances. - `-l`, `--left_legend` will move external table legends to the left side rather than the right side. Right side is default. @@ -140,6 +140,10 @@ Run using `btm`. - `-b`, `--basic` will enable basic mode, removing all graphs from the main interface and condensing data. +- `-t`, `--default_time_value` will set the default time interval graphs will display to (in milliseconds). Lowest is 30 seconds, defaults to 60 seconds. + +- `-i`, `--time_delta` will set the amount each zoom in/out action will change the time interval of a graph (in milliseconds). Lowest is 1 second, defaults to 15 seconds. + ### Keybindings #### General diff --git a/src/app.rs b/src/app.rs index 8e11f1fa..1b1cffa2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -209,9 +209,10 @@ impl Default for AppHelpDialogState { } /// AppConfigFields is meant to cover basic fields that would normally be set -/// by config files or launch options. Don't need to be mutable (set and forget). +/// by config files or launch options. +#[derive(Default)] pub struct AppConfigFields { - pub update_rate_in_milliseconds: u128, + pub update_rate_in_milliseconds: u64, pub temperature_type: temperature::TemperatureType, pub use_dot: bool, pub left_legend: bool, @@ -219,6 +220,8 @@ pub struct AppConfigFields { pub use_current_cpu_total: bool, pub show_disabled_data: bool, pub use_basic_mode: bool, + pub default_time_value: u64, + pub time_interval: u64, } /// Network specific @@ -227,7 +230,7 @@ pub struct NetworkState { pub is_showing_rx: bool, pub is_showing_tx: bool, pub zoom_level: f64, - pub display_time: u128, + pub display_time: u64, pub force_update: bool, } @@ -238,7 +241,7 @@ impl Default for NetworkState { is_showing_rx: true, is_showing_tx: true, zoom_level: 100.0, - display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, } } @@ -249,7 +252,7 @@ pub struct CpuState { pub is_showing_tray: bool, pub zoom_level: f64, pub core_show_vec: Vec, - pub display_time: u128, + pub display_time: u64, pub force_update: bool, } @@ -259,7 +262,7 @@ impl Default for CpuState { is_showing_tray: false, zoom_level: 100.0, core_show_vec: Vec::new(), - display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, } } @@ -271,7 +274,7 @@ pub struct MemState { pub is_showing_ram: bool, pub is_showing_swap: bool, pub zoom_level: f64, - pub display_time: u128, + pub display_time: u64, pub force_update: bool, } @@ -282,7 +285,7 @@ impl Default for MemState { is_showing_ram: true, is_showing_swap: true, zoom_level: 100.0, - display_time: constants::DEFAULT_DISPLAY_MILLISECONDS, + display_time: constants::DEFAULT_TIME_MILLISECONDS, force_update: false, } } @@ -320,10 +323,19 @@ impl App { // TODO: [REFACTOR] use builder pattern instead. pub fn new( show_average_cpu: bool, temperature_type: temperature::TemperatureType, - update_rate_in_milliseconds: u128, use_dot: bool, left_legend: bool, + update_rate_in_milliseconds: u64, use_dot: bool, left_legend: bool, use_current_cpu_total: bool, current_widget_selected: WidgetPosition, - show_disabled_data: bool, use_basic_mode: bool, + show_disabled_data: bool, use_basic_mode: bool, default_time_value: u64, + time_interval: u64, ) -> App { + let mut cpu_state = CpuState::default(); + let mut mem_state = MemState::default(); + let mut net_state = NetworkState::default(); + + cpu_state.display_time = default_time_value; + mem_state.display_time = default_time_value; + net_state.display_time = default_time_value; + App { process_sorting_type: processes::ProcessSorting::CPU, process_sorting_reverse: true, @@ -365,12 +377,14 @@ impl App { use_current_cpu_total, show_disabled_data, use_basic_mode, + default_time_value, + time_interval, }, is_expanded: false, is_resized: false, - cpu_state: CpuState::default(), - mem_state: MemState::default(), - net_state: NetworkState::default(), + cpu_state, + mem_state, + net_state, } } @@ -947,7 +961,7 @@ impl App { if current_key_press_inst .duration_since(self.last_key_press) .as_millis() - > constants::MAX_KEY_TIMEOUT_IN_MILLISECONDS + > constants::MAX_KEY_TIMEOUT_IN_MILLISECONDS as u128 { self.reset_multi_tap_keys(); } @@ -1489,20 +1503,23 @@ impl App { fn zoom_out(&mut self) { match self.current_widget_selected { WidgetPosition::Cpu => { - if self.cpu_state.display_time < constants::STALE_MAX_MILLISECONDS { - self.cpu_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.cpu_state.display_time + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + self.cpu_state.display_time = new_time; self.cpu_state.force_update = true; } } WidgetPosition::Mem => { - if self.mem_state.display_time < constants::STALE_MAX_MILLISECONDS { - self.mem_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.mem_state.display_time + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + self.mem_state.display_time = new_time; self.mem_state.force_update = true; } } WidgetPosition::Network => { - if self.net_state.display_time < constants::STALE_MAX_MILLISECONDS { - self.net_state.display_time += constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.net_state.display_time + self.app_config_fields.time_interval; + if new_time <= constants::STALE_MAX_MILLISECONDS { + self.net_state.display_time = new_time; self.net_state.force_update = true; } } @@ -1513,20 +1530,23 @@ impl App { fn zoom_in(&mut self) { match self.current_widget_selected { WidgetPosition::Cpu => { - if self.cpu_state.display_time > constants::STALE_MIN_MILLISECONDS { - self.cpu_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.cpu_state.display_time - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + self.cpu_state.display_time = new_time; self.cpu_state.force_update = true; } } WidgetPosition::Mem => { - if self.mem_state.display_time > constants::STALE_MIN_MILLISECONDS { - self.mem_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.mem_state.display_time - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + self.mem_state.display_time = new_time; self.mem_state.force_update = true; } } WidgetPosition::Network => { - if self.net_state.display_time > constants::STALE_MIN_MILLISECONDS { - self.net_state.display_time -= constants::TIME_CHANGE_MILLISECONDS; + let new_time = self.net_state.display_time - self.app_config_fields.time_interval; + if new_time >= constants::STALE_MIN_MILLISECONDS { + self.net_state.display_time = new_time; self.net_state.force_update = true; } } @@ -1537,15 +1557,15 @@ impl App { fn reset_zoom(&mut self) { match self.current_widget_selected { WidgetPosition::Cpu => { - self.cpu_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.cpu_state.display_time = self.app_config_fields.default_time_value; self.cpu_state.force_update = true; } WidgetPosition::Mem => { - self.mem_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.mem_state.display_time = self.app_config_fields.default_time_value; self.mem_state.force_update = true; } WidgetPosition::Network => { - self.net_state.display_time = constants::DEFAULT_DISPLAY_MILLISECONDS; + self.net_state.display_time = self.app_config_fields.default_time_value; self.net_state.force_update = true; } _ => {} diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index d3d77ca2..5fa766a5 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -84,12 +84,12 @@ impl DataCollection { self.frozen_instant = Some(self.current_instant); } - pub fn clean_data(&mut self, max_time_millis: u128) { + pub fn clean_data(&mut self, max_time_millis: u64) { let current_time = Instant::now(); let mut remove_index = 0; for entry in &self.timed_data_vec { - if current_time.duration_since(entry.0).as_millis() >= max_time_millis { + if current_time.duration_since(entry.0).as_millis() >= max_time_millis as u128 { remove_index += 1; } else { break; diff --git a/src/constants.rs b/src/constants.rs index b177d141..f488ccfe 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,15 +1,15 @@ // How long to store data. -pub const STALE_MAX_MILLISECONDS: u128 = 300 * 1000; // Keep 5 minutes of data. +pub const STALE_MAX_MILLISECONDS: u64 = 300 * 1000; // Keep 5 minutes of data. // How much data is SHOWN -pub const DEFAULT_DISPLAY_MILLISECONDS: u128 = 60 * 1000; // Defaults to 1 min. -pub const STALE_MIN_MILLISECONDS: u128 = 30 * 1000; // Lowest is 30 seconds -pub const TIME_CHANGE_MILLISECONDS: u128 = 15 * 1000; // How much to increment each time +pub const DEFAULT_TIME_MILLISECONDS: u64 = 60 * 1000; // Defaults to 1 min. +pub const STALE_MIN_MILLISECONDS: u64 = 30 * 1000; // Lowest is 30 seconds +pub const TIME_CHANGE_MILLISECONDS: u64 = 15 * 1000; // How much to increment each time pub const TICK_RATE_IN_MILLISECONDS: u64 = 200; // How fast the screen refreshes -pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u128 = 1000; -pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u128 = 1000; +pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u64 = 1000; +pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u64 = 1000; // Number of colours to generate for the CPU chart/table pub const NUM_COLOURS: i32 = 256; @@ -30,7 +30,7 @@ lazy_static! { } // Help text -pub const GENERAL_HELP_TEXT: [&str; 15] = [ +pub const GENERAL_HELP_TEXT: [&str; 18] = [ "General Keybindings\n\n", "q, Ctrl-c Quit bottom\n", "Esc Close filters, dialog boxes, etc.\n", @@ -46,6 +46,9 @@ pub const GENERAL_HELP_TEXT: [&str; 15] = [ "G Skip to the last entry of a list\n", "Enter Maximize the currently selected widget\n", "/ Filter out graph lines (only CPU at the moment)\n", + "+ Zoom in (decrease time range)\n", + "- Zoom out (increase time range)\n", + "= Reset zoom\n", ]; pub const PROCESS_HELP_TEXT: [&str; 8] = [ @@ -90,15 +93,34 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" # is also set here. [flags] +# Whether to display an average cpu entry. #avg_cpu = true + +# Whether to use dot markers rather than braille. #dot_marker = false + +# The update rate of the application. #rate = 1000 + +# Whether to put the CPU legend to the left. #left_legend = false + +# Whether to set CPU% on a process to be based on the total CPU or just current usage. #current_usage = false + +# Whether to group processes with the same name together by default. #group_processes = false + +# Whether to make process searching case sensitive by default. #case_sensitive = false + +# Whether to make process searching look for matching the entire word by default. #whole_word = true + +# Whether to make process searching use regex by default. #regex = true + +# Whether to show CPU entries in the legend when they are hidden. #show_disabled_data = true # Defaults to Celsius. Temperature is one of: @@ -117,6 +139,11 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" #default_widget = "network_default" #default_widget = "process_default" +# The default time interval (in milliseconds). +#default_time_value = 60000 + +# The time delta on each zoom in/out action (in milliseconds). +# time_delta = 15000 # These are all the components that support custom theming. Currently, it only # supports taking in a string representing a hex colour. Note that colour support @@ -132,7 +159,7 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##" # Represents the colour of the label each widget has. #widget_title_color="#cc241d" -# Represents the average CPU color +# Represents the average CPU color. #avg_cpu_color="#d3869b" # Represents the colour the core will use in the CPU legend and graph. diff --git a/src/data_conversion.rs b/src/data_conversion.rs index 341ada48..c99bf75f 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -102,7 +102,7 @@ pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec Vec { let mut cpu_data_vector: Vec = Vec::new(); @@ -156,7 +156,7 @@ pub fn convert_cpu_data_points( } pub fn convert_mem_data_points( - current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, + current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, ) -> Vec { let mut result: Vec = Vec::new(); let current_time = if is_frozen { @@ -190,7 +190,7 @@ pub fn convert_mem_data_points( } pub fn convert_swap_data_points( - current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, + current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, ) -> Vec { let mut result: Vec = Vec::new(); let current_time = if is_frozen { @@ -260,7 +260,7 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String } pub fn get_rx_tx_data_points( - current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, + current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, ) -> (Vec, Vec) { let mut rx: Vec = Vec::new(); let mut tx: Vec = Vec::new(); @@ -275,6 +275,7 @@ pub fn get_rx_tx_data_points( current_data.current_instant }; + // TODO: [REFACTOR] Can we use combine on this, CPU, and MEM? for (time, data) in ¤t_data.timed_data_vec { let time_from_start: f64 = (display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor(); @@ -302,7 +303,7 @@ pub fn get_rx_tx_data_points( } pub fn convert_network_data_points( - current_data: &data_farmer::DataCollection, display_time: u128, is_frozen: bool, + current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool, ) -> ConvertedNetworkData { let (rx, tx) = get_rx_tx_data_points(current_data, display_time, is_frozen); diff --git a/src/main.rs b/src/main.rs index adf12e7d..9d09c262 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,7 +85,9 @@ fn get_matches() -> clap::ArgMatches<'static> { (@arg CASE_SENSITIVE: -S --case_sensitive "Match case when searching by default.") (@arg WHOLE_WORD: -W --whole_word "Match whole word when searching by default.") (@arg REGEX_DEFAULT: -R --regex "Use regex in searching by default.") - (@arg SHOW_DISABLED_DATA: -s --show_disabled_data "Show disabled data entries.") + (@arg SHOW_DISABLED_DATA: -s --show_disabled_data "Show disabled data entries.") + (@arg DEFAULT_TIME_VALUE: -t --default_time_value +takes_value "Default time value for graphs in milliseconds; minimum is 30s, defaults to 60s.") + (@arg TIME_DELTA: -i --time_delta +takes_value "The amount changed upon zooming in/out in milliseconds; minimum is 1s, defaults to 15s.") (@group DEFAULT_WIDGET => (@arg CPU_WIDGET: --cpu_default "Selects the CPU widget to be selected by default.") (@arg MEM_WIDGET: --memory_default "Selects the memory widget to be selected by default.") @@ -105,7 +107,7 @@ fn main() -> error::Result<()> { let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?; - let update_rate_in_milliseconds: u128 = + let update_rate_in_milliseconds: u64 = get_update_rate_in_milliseconds(&matches.value_of("RATE_MILLIS"), &config)?; // Set other settings @@ -117,6 +119,8 @@ fn main() -> error::Result<()> { let current_widget_selected = get_default_widget(&matches, &config); let show_disabled_data = get_show_disabled_data_option(&matches, &config); let use_basic_mode = get_use_basic_mode_option(&matches, &config); + let default_time_value = get_default_time_value_option(&matches, &config)?; + let time_interval = get_time_interval_option(&matches, &config)?; // Create "app" struct, which will control most of the program and store settings/state let mut app = App::new( @@ -129,6 +133,8 @@ fn main() -> error::Result<()> { current_widget_selected, show_disabled_data, use_basic_mode, + default_time_value, + time_interval, ); enable_app_grouping(&matches, &config, &mut app); @@ -369,9 +375,9 @@ fn handle_key_event_or_break( app.reset(); } } - KeyCode::Char('u') => app.clear_search(), KeyCode::Char('a') => app.skip_cursor_beginning(), KeyCode::Char('e') => app.skip_cursor_end(), + KeyCode::Char('u') => app.clear_search(), // KeyCode::Char('j') => {}, // Move down // KeyCode::Char('k') => {}, // Move up // KeyCode::Char('h') => {}, // Move right diff --git a/src/options.rs b/src/options.rs index 372165bd..77884e78 100644 --- a/src/options.rs +++ b/src/options.rs @@ -27,6 +27,8 @@ pub struct ConfigFlags { pub default_widget: Option, pub show_disabled_data: Option, pub basic: Option, + pub default_time_value: Option, + pub time_delta: Option, //disabled_cpu_cores: Option>, // TODO: [FEATURE] Enable disabling cores in config/flags } @@ -52,12 +54,12 @@ pub struct ConfigColours { pub fn get_update_rate_in_milliseconds( update_rate: &Option<&str>, config: &Config, -) -> error::Result { +) -> error::Result { let update_rate_in_milliseconds = if let Some(update_rate) = update_rate { - update_rate.parse::()? + update_rate.parse::()? } else if let Some(flags) = &config.flags { if let Some(rate) = flags.rate { - rate as u128 + rate } else { DEFAULT_REFRESH_RATE_IN_MILLISECONDS } @@ -67,11 +69,11 @@ pub fn get_update_rate_in_milliseconds( if update_rate_in_milliseconds < 250 { return Err(BottomError::InvalidArg( - "Please set your update rate to be greater than 250 milliseconds.".to_string(), + "Please set your update rate to be at least 250 milliseconds.".to_string(), )); - } else if update_rate_in_milliseconds > u128::from(std::u64::MAX) { + } else if update_rate_in_milliseconds as u128 > std::u64::MAX as u128 { return Err(BottomError::InvalidArg( - "Please set your update rate to be less than unsigned INT_MAX.".to_string(), + "Please set your update rate to be at most unsigned INT_MAX.".to_string(), )); } @@ -178,6 +180,62 @@ pub fn get_use_basic_mode_option(matches: &clap::ArgMatches<'static>, config: &C false } +pub fn get_default_time_value_option( + matches: &clap::ArgMatches<'static>, config: &Config, +) -> error::Result { + let default_time = if let Some(default_time_value) = matches.value_of("DEFAULT_TIME_VALUE") { + default_time_value.parse::()? + } else if let Some(flags) = &config.flags { + if let Some(default_time_value) = flags.default_time_value { + default_time_value + } else { + DEFAULT_TIME_MILLISECONDS + } + } else { + DEFAULT_TIME_MILLISECONDS + }; + + if default_time < 30000 { + return Err(BottomError::InvalidArg( + "Please set your default value to be at least 30 seconds.".to_string(), + )); + } else if default_time as u128 > std::u64::MAX as u128 { + return Err(BottomError::InvalidArg( + "Please set your default value to be at most unsigned INT_MAX.".to_string(), + )); + } + + Ok(default_time) +} + +pub fn get_time_interval_option( + matches: &clap::ArgMatches<'static>, config: &Config, +) -> error::Result { + let time_interval = if let Some(time_interval) = matches.value_of("TIME_DELTA") { + time_interval.parse::()? + } else if let Some(flags) = &config.flags { + if let Some(time_interval) = flags.time_delta { + time_interval + } else { + TIME_CHANGE_MILLISECONDS + } + } else { + TIME_CHANGE_MILLISECONDS + }; + + if time_interval < 1000 { + return Err(BottomError::InvalidArg( + "Please set your time interval to be at least 1 second.".to_string(), + )); + } else if time_interval as u128 > std::u64::MAX as u128 { + return Err(BottomError::InvalidArg( + "Please set your time interval to be at most unsigned INT_MAX.".to_string(), + )); + } + + Ok(time_interval) +} + pub fn enable_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) { if matches.is_present("GROUP_PROCESSES") { app.toggle_grouping(); diff --git a/tests/arg_rate_tests.rs b/tests/arg_rate_tests.rs index 85b3da0f..7f22431c 100644 --- a/tests/arg_rate_tests.rs +++ b/tests/arg_rate_tests.rs @@ -28,7 +28,9 @@ fn test_small_rate() -> Result<(), Box> { .arg("249") .assert() .failure() - .stderr(predicate::str::contains("rate to be greater than 250")); + .stderr(predicate::str::contains( + "Please set your update rate to be at least 250 milliseconds.", + )); Ok(()) } @@ -40,7 +42,7 @@ fn test_large_rate() -> Result<(), Box> { .assert() .failure() .stderr(predicate::str::contains( - "rate to be less than unsigned INT_MAX.", + "Please set your update rate to be at most unsigned INT_MAX.", )); Ok(()) }