other: fix humantime-related documentation, add tests, support numbers + strings in toml (#1220)

* update documentation and support either numerical times or human times for time_delta and default_time_value

* update docs

* give more human times on error
This commit is contained in:
Clement Tsang 2023-06-22 04:01:01 +00:00 committed by GitHub
parent 6f1a8f7e5b
commit 0b7f4c745d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 74 deletions

View File

@ -3,45 +3,45 @@
The following flags can be provided to bottom in the command line to change the behaviour of the program. You can also The following flags can be provided to bottom in the command line to change the behaviour of the program. You can also
see information on these flags by running `btm -h`, or run `btm --help` to display more detailed information on each flag: see information on these flags by running `btm -h`, or run `btm --help` to display more detailed information on each flag:
| Flag | Behaviour | | Flag | Behaviour |
| -------------------------------------------- | ------------------------------------------------------------------------------------ | | ----------------------------------- | --------------------------------------------------------------- |
| `--autohide_time` | Temporarily shows the time scale in graphs. | | --autohide_time | Temporarily shows the time scale in graphs. |
| `-b`, `--basic` | Hides graphs and uses a more basic look. | | -b, --basic | Hides graphs and uses a more basic look. |
| `--battery` | Shows the battery widget. | | --battery | Shows the battery widget. |
| `-S`, `--case_sensitive` | Enables case sensitivity by default. | | -S, --case_sensitive | Enables case sensitivity by default. |
| `-c`, `--celsius` | Sets the temperature type to Celsius. | | -c, --celsius | Sets the temperature type to Celsius. |
| `--color <COLOR SCHEME>` | Use a color scheme, use --help for supported values. | | --color <COLOR SCHEME> | Use a color scheme, use --help for info. |
| `-C <CONFIG PATH>`, `--config <CONFIG PATH>` | Sets the location of the config file. | | -C, --config <CONFIG PATH> | Sets the location of the config file. |
| `-u`, `--current_usage` | Sets process CPU% to be based on current CPU%. | | -u, --current_usage | Sets process CPU% to be based on current CPU%. |
| `-t <MS>`, `--default_time_value <MS>` | Default time value for graphs in ms. | | -t, --default_time_value <TIME> | Default time value for graphs. |
| `--default_widget_count <INT>` | Sets the n'th selected widget type as the default. | | --default_widget_count <INT> | Sets the n'th selected widget type as the default. |
| `--default_widget_type <WIDGET TYPE>` | Sets the default widget type, use --help for more info. | | --default_widget_type <WIDGET TYPE> | Sets the default widget type, use --help for info. |
| `--disable_advanced_kill` | Hides advanced options to stop a process on Unix-like systems. | | --disable_advanced_kill | Hides advanced process killing. |
| `--disable_click` | Disables mouse clicks. | | --disable_click | Disables mouse clicks. |
| `-m`, `--dot_marker` | Uses a dot marker for graphs. | | -m, --dot_marker | Uses a dot marker for graphs. |
| `-f`, `--fahrenheit` | Sets the temperature type to Fahrenheit. | | --enable_cache_memory | Enable collecting and displaying cache and buffer memory. |
| `-g`, `--group` | Groups processes with the same name by default. | | --enable_gpu_memory | Enable collecting and displaying GPU memory usage. |
| `-h`, `--help` | Prints help information. Use --help for more info. | | -e, --expanded | Expand the default widget upon starting the app. |
| `-a`, `--hide_avg_cpu` | Hides the average CPU usage. | | -f, --fahrenheit | Sets the temperature type to Fahrenheit. |
| `--hide_table_gap` | Hides the spacing between table headers and entries. | | -g, --group | Groups processes with the same name by default. |
| `--hide_time` | Hides the time scale. | | -a, --hide_avg_cpu | Hides the average CPU usage. |
| `-k`, `--kelvin` | Sets the temperature type to Kelvin. | | --hide_table_gap | Hides spacing between table headers and entries. |
| `-l`, `--left_legend` | Puts the CPU chart legend to the left side. | | --hide_time | Hides the time scale. |
| `--mem_as_value` | Defaults to showing process memory usage by value. | | -k, --kelvin | Sets the temperature type to Kelvin. |
| `--network_use_binary_prefix` | Displays the network widget with binary prefixes. | | -l, --left_legend | Puts the CPU chart legend to the left side. |
| `--network_use_bytes` | Displays the network widget using bytes. | | --mem_as_value | Defaults to showing process memory usage by value. |
| `--network_use_log` | Displays the network widget with a log scale. | | --network_use_binary_prefix | Displays the network widget with binary prefixes. |
| `--process_command` | Show processes as their commands by default. | | --network_use_bytes | Displays the network widget using bytes. |
| `-r`, `--rate <MS>` | Sets a refresh rate in ms. | | --network_use_log | Displays the network widget with a log scale. |
| `-R`, `--regex` | Enables regex by default. | | --process_command | Show processes as their commands by default. |
| `--show_table_scroll_position` | Shows the scroll position tracker in table widgets. | | -r, --rate <MS> | Sets a refresh rate in ms. |
| `-d <MS>`, `--time_delta <MS>` | The amount in ms changed upon zooming. | | -R, --regex | Enables regex by default. |
| `-T`, `--tree` | Defaults to showing the process widget in tree mode. | | --retention <TIME> | The timespan of data kept. |
| `--use_old_network_legend` | DEPRECATED - uses the older network legend. | | --show_table_scroll_position | Shows the scroll position tracker in table widgets. |
| `-V`, `--version` | Prints version information. | | -d, --time_delta <TIME> | The amount of time changed upon zooming. |
| `-W`, `--whole_word` | Enables whole-word matching by default. | | -T, --tree | Defaults the process widget be in tree mode. |
| `--enable_gpu_memory` | Enable collecting and displaying GPU memory usage. | | -n, --unnormalized_cpu | Show process CPU% without normalizing over the number of cores. |
| `--enable_cache_memory` | Enable collecting and displaying cache and buffer memory (not available on Windows). | | --use_old_network_legend | DEPRECATED - uses a separate network legend. |
| `--retention` | How much data is stored at once in terms of time. | | -V, --version | Prints version information. |
| `-n`, `--unnormalized_cpu` | Show process CPU% without normalizing over the number of cores. | | -W, --whole_word | Enables whole-word matching by default. |
| `-e`, `--expanded` | Expand the default widget upon starting the app. | | -h, --help | Print help (see more with '--help') |

View File

@ -8,7 +8,7 @@ Most of the [command line flags](../../command-line-flags) have config file equi
each time: each time:
| Field | Type | Functionality | | Field | Type | Functionality |
|------------------------------|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------| | ---------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `hide_avg_cpu` | Boolean | Hides the average CPU usage. | | `hide_avg_cpu` | Boolean | Hides the average CPU usage. |
| `dot_marker` | Boolean | Uses a dot marker for graphs. | | `dot_marker` | Boolean | Uses a dot marker for graphs. |
| `left_legend` | Boolean | Puts the CPU chart legend to the left side. | | `left_legend` | Boolean | Puts the CPU chart legend to the left side. |
@ -21,8 +21,8 @@ each time:
| `use_old_network_legend` | Boolean | DEPRECATED - uses the older network legend. | | `use_old_network_legend` | Boolean | DEPRECATED - uses the older network legend. |
| `battery` | Boolean | Shows the battery widget. | | `battery` | Boolean | Shows the battery widget. |
| `rate` | Unsigned Int (represents milliseconds) | Sets a refresh rate in ms. | | `rate` | Unsigned Int (represents milliseconds) | Sets a refresh rate in ms. |
| `default_time_value` | Unsigned Int (represents milliseconds) | Default time value for graphs 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) | The amount in ms changed upon zooming. | | `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. | | `hide_time` | Boolean | Hides the time scale. |
| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) | Sets the temperature unit type. | | `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_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | Sets the default widget type, use --help for more info. |

View File

@ -35,7 +35,7 @@
#temperature_type = "fahrenheit" #temperature_type = "fahrenheit"
#temperature_type = "celsius" #temperature_type = "celsius"
# The default time interval (in milliseconds). # The default time interval (in milliseconds).
#default_time_value = 60000 #default_time_value = "60s"
# The time delta on each zoom in/out action (in milliseconds). # The time delta on each zoom in/out action (in milliseconds).
#time_delta = 15000 #time_delta = 15000
# Hides the time scale. # Hides the time scale.
@ -157,7 +157,6 @@
# type="proc" # type="proc"
# default=true # default=true
# Filters - you can hide specific temperature sensors, network interfaces, and disks using filters. This is admittedly # Filters - you can hide specific temperature sensors, network interfaces, and disks using filters. This is admittedly
# a bit hard to use as of now, and there is a planned in-app interface for managing this in the future: # a bit hard to use as of now, and there is a planned in-app interface for managing this in the future:
#[disk_filter] #[disk_filter]

View File

@ -88,8 +88,6 @@ pub fn get_matches() -> clap::ArgMatches {
build_app().get_matches() build_app().get_matches()
} }
// TODO: Refactor this a bit, it's quite messy atm
// TODO: [DEBUG] Add a proper debugging solution.
pub fn build_app() -> Command { pub fn build_app() -> Command {
// Temps // Temps
let kelvin = Arg::new("kelvin") let kelvin = Arg::new("kelvin")
@ -303,11 +301,13 @@ Defaults to \"default\".
.short('t') .short('t')
.long("default_time_value") .long("default_time_value")
.action(ArgAction::Set) .action(ArgAction::Set)
.value_name("MS") .value_name("TIME")
.help("Default time value for graphs in ms.") .help("Default time value for graphs.")
.long_help("Default time value for graphs in milliseconds. The minimum time is 30s (30000), and the default is 60s (60000)."); .long_help(
"Default time value for graphs. The minimum time is 30s, and the default is 60s.",
);
// TODO: Fix this, its broken in the manpage // TODO: Charts are broken in the manpage
let default_widget_count = Arg::new("default_widget_count") let default_widget_count = Arg::new("default_widget_count")
.long("default_widget_count") .long("default_widget_count")
.action(ArgAction::Set) .action(ArgAction::Set)
@ -360,9 +360,9 @@ use CPU (3) as the default instead.
.short('d') .short('d')
.long("time_delta") .long("time_delta")
.action(ArgAction::Set) .action(ArgAction::Set)
.value_name("MS") .value_name("TIME")
.help("The amount in ms changed upon zooming.") .help("The amount of time changed upon zooming.")
.long_help("The amount of time in milliseconds changed when zooming in/out. The minimum is 1s (1000), and defaults to 15s (15000)."); .long_help("The amount of time changed when zooming in/out. The minimum is 1s, and defaults to 15s.");
let tree = Arg::new("tree") let tree = Arg::new("tree")
.short('T') .short('T')
@ -394,7 +394,7 @@ use CPU (3) as the default instead.
let retention = Arg::new("retention") let retention = Arg::new("retention")
.long("retention") .long("retention")
.action(ArgAction::Set) .action(ArgAction::Set)
.value_name("time") .value_name("TIME")
.help("The timespan of data kept.") .help("The timespan of data kept.")
.long_help("How much data is stored at once in terms of time. Takes in human-readable time spans (e.g. 10m, 1h), with a minimum of 1 minute. Note higher values will take up more memory. Defaults to 10 minutes."); .long_help("How much data is stored at once in terms of time. Takes in human-readable time spans (e.g. 10m, 1h), with a minimum of 1 minute. Note higher values will take up more memory. Defaults to 10 minutes.");

View File

@ -188,7 +188,6 @@ where
let soft_limit = max( let soft_limit = max(
if let Some(max_percentage) = max_percentage { if let Some(max_percentage) = max_percentage {
// TODO: Rust doesn't have an `into()` or `try_into()` for floats to integers.
((*max_percentage * f32::from(total_width)).ceil()) as u16 ((*max_percentage * f32::from(total_width)).ceil()) as u16
} else { } else {
*desired *desired

View File

@ -543,7 +543,7 @@ pub const CONFIG_TEXT: &str = r##"# This is a default config file for bottom. A
#temperature_type = "fahrenheit" #temperature_type = "fahrenheit"
#temperature_type = "celsius" #temperature_type = "celsius"
# The default time interval (in milliseconds). # The default time interval (in milliseconds).
#default_time_value = 60000 #default_time_value = "60s"
# The time delta on each zoom in/out action (in milliseconds). # The time delta on each zoom in/out action (in milliseconds).
#time_delta = 15000 #time_delta = 15000
# Hides the time scale. # Hides the time scale.

View File

@ -45,6 +45,25 @@ pub struct Config {
pub processes: Option<ProcessConfig>, pub processes: Option<ProcessConfig>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
enum StringOrNum {
String(String),
Num(u64),
}
impl From<String> for StringOrNum {
fn from(value: String) -> Self {
StringOrNum::String(value)
}
}
impl From<u64> for StringOrNum {
fn from(value: u64) -> Self {
StringOrNum::Num(value)
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct ConfigFlags { pub struct ConfigFlags {
pub hide_avg_cpu: Option<bool>, pub hide_avg_cpu: Option<bool>,
@ -59,8 +78,8 @@ pub struct ConfigFlags {
pub whole_word: Option<bool>, pub whole_word: Option<bool>,
pub regex: Option<bool>, pub regex: Option<bool>,
pub basic: Option<bool>, pub basic: Option<bool>,
pub default_time_value: Option<String>, default_time_value: Option<StringOrNum>,
pub time_delta: Option<String>, time_delta: Option<StringOrNum>,
pub autohide_time: Option<bool>, pub autohide_time: Option<bool>,
pub hide_time: Option<bool>, pub hide_time: Option<bool>,
pub default_widget_type: Option<String>, pub default_widget_type: Option<String>,
@ -615,7 +634,10 @@ fn get_default_time_value(
try_parse_ms(default_time_value)? try_parse_ms(default_time_value)?
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
if let Some(default_time_value) = &flags.default_time_value { if let Some(default_time_value) = &flags.default_time_value {
try_parse_ms(default_time_value)? match default_time_value {
StringOrNum::String(s) => try_parse_ms(s)?,
StringOrNum::Num(n) => *n,
}
} else { } else {
DEFAULT_TIME_MILLISECONDS DEFAULT_TIME_MILLISECONDS
} }
@ -625,12 +647,12 @@ fn get_default_time_value(
if default_time < 30000 { if default_time < 30000 {
return Err(BottomError::ConfigError( return Err(BottomError::ConfigError(
"set your default value to be at least 30000 milliseconds.".to_string(), "set your default value to be at least 30s.".to_string(),
)); ));
} else if default_time > retention_ms { } else if default_time > retention_ms {
return Err(BottomError::ConfigError(format!( return Err(BottomError::ConfigError(format!(
"set your default value to be at most {} milliseconds.", "set your default value to be at most {}.",
retention_ms humantime::Duration::from(Duration::from_millis(retention_ms))
))); )));
} }
@ -644,7 +666,10 @@ fn get_time_interval(
try_parse_ms(time_interval)? try_parse_ms(time_interval)?
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
if let Some(time_interval) = &flags.time_delta { if let Some(time_interval) = &flags.time_delta {
try_parse_ms(time_interval)? match time_interval {
StringOrNum::String(s) => try_parse_ms(s)?,
StringOrNum::Num(n) => *n,
}
} else { } else {
TIME_CHANGE_MILLISECONDS TIME_CHANGE_MILLISECONDS
} }
@ -654,12 +679,12 @@ fn get_time_interval(
if time_interval < 1000 { if time_interval < 1000 {
return Err(BottomError::ConfigError( return Err(BottomError::ConfigError(
"set your time delta to be at least 1000 milliseconds.".to_string(), "set your time delta to be at least 1s.".to_string(),
)); ));
} else if time_interval > retention_ms { } else if time_interval > retention_ms {
return Err(BottomError::ConfigError(format!( return Err(BottomError::ConfigError(format!(
"set your time delta to be at most {} milliseconds.", "set your time delta to be at most {}.",
retention_ms humantime::Duration::from(Duration::from_millis(retention_ms))
))); )));
} }
@ -972,8 +997,8 @@ mod test {
let mut config = Config::default(); let mut config = Config::default();
let flags = ConfigFlags { let flags = ConfigFlags {
time_delta: Some("2 min".to_string()), time_delta: Some("2 min".to_string().into()),
default_time_value: Some("300s".to_string()), default_time_value: Some("300s".to_string().into()),
..Default::default() ..Default::default()
}; };
@ -991,14 +1016,39 @@ mod test {
} }
#[test] #[test]
fn config_number_times() { fn config_number_times_as_string() {
let app = crate::args::build_app(); let app = crate::args::build_app();
let matches = app.get_matches_from(["btm"]); let matches = app.get_matches_from(["btm"]);
let mut config = Config::default(); let mut config = Config::default();
let flags = ConfigFlags { let flags = ConfigFlags {
time_delta: Some("120000".to_string()), time_delta: Some("120000".to_string().into()),
default_time_value: Some("300000".to_string()), default_time_value: Some("300000".to_string().into()),
..Default::default()
};
config.flags = Some(flags);
assert_eq!(
get_time_interval(&matches, &config, 60 * 60 * 1000),
Ok(2 * 60 * 1000)
);
assert_eq!(
get_default_time_value(&matches, &config, 60 * 60 * 1000),
Ok(5 * 60 * 1000)
);
}
#[test]
fn config_number_times_as_num() {
let app = crate::args::build_app();
let matches = app.get_matches_from(["btm"]);
let mut config = Config::default();
let flags = ConfigFlags {
time_delta: Some(120000.into()),
default_time_value: Some(300000.into()),
..Default::default() ..Default::default()
}; };