Add configurable default time and interval values

Also added documentation both in app and in the README.
This commit is contained in:
ClementTsang 2020-03-08 21:56:30 -04:00
parent 3026fbd1bc
commit f70cf02414
8 changed files with 174 additions and 56 deletions

View file

@ -118,7 +118,7 @@ Run using `btm`.
- `-v`, `--version` displays the version number and exits.
- `-r <RATE>`, `--rate <RATE>` will set the refresh rate in _milliseconds_. Lowest it can go is 250ms, the highest it can go is 2<sup>128</sup> - 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>`, `--rate <RATE>` will set the refresh rate in _milliseconds_. Lowest it can go is 250ms, the highest it can go is 2<sup>64</sup> - 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

View file

@ -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<bool>,
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;
}
_ => {}

View file

@ -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;

View file

@ -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.

View file

@ -102,7 +102,7 @@ pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec<Vec<S
}
pub fn convert_cpu_data_points(
show_avg_cpu: bool, current_data: &data_farmer::DataCollection, display_time: u128,
show_avg_cpu: bool, current_data: &data_farmer::DataCollection, display_time: u64,
is_frozen: bool,
) -> Vec<ConvertedCpuData> {
let mut cpu_data_vector: Vec<ConvertedCpuData> = 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<Point> {
let mut result: Vec<Point> = 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<Point> {
let mut result: Vec<Point> = 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<Point>, Vec<Point>) {
let mut rx: Vec<Point> = Vec::new();
let mut tx: Vec<Point> = 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 &current_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);

View file

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

View file

@ -27,6 +27,8 @@ pub struct ConfigFlags {
pub default_widget: Option<String>,
pub show_disabled_data: Option<bool>,
pub basic: Option<bool>,
pub default_time_value: Option<u64>,
pub time_delta: Option<u64>,
//disabled_cpu_cores: Option<Vec<u64>>, // 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<u128> {
) -> error::Result<u64> {
let update_rate_in_milliseconds = if let Some(update_rate) = update_rate {
update_rate.parse::<u128>()?
update_rate.parse::<u64>()?
} 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<u64> {
let default_time = if let Some(default_time_value) = matches.value_of("DEFAULT_TIME_VALUE") {
default_time_value.parse::<u64>()?
} 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<u64> {
let time_interval = if let Some(time_interval) = matches.value_of("TIME_DELTA") {
time_interval.parse::<u64>()?
} 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();

View file

@ -28,7 +28,9 @@ fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
.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<dyn std::error::Error>> {
.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(())
}