feature: support binary prefix option for disk table I/O (#2251)

This is a follow-up to
https://github.com/ClementTsang/bottom/pull/2236#issuecomment-5689665534 so the binary prefix configuration also affects the disk I/O display. Note this does not touch the graph at the moment, just the table.
This commit is contained in:
Clement Tsang
2026-09-16 00:05:49 -04:00
committed by GitHub
parent 8631df3f48
commit d7b56f6ab7
10 changed files with 124 additions and 58 deletions
+2 -1
View File
@@ -30,7 +30,8 @@ That said, these are more guidelines rather than hard rules, though the project
- [#2239](https://github.com/ClementTsang/bottom/pull/2239): Initial Intel GPU support for Linux to get process GPU usage. - [#2239](https://github.com/ClementTsang/bottom/pull/2239): Initial Intel GPU support for Linux to get process GPU usage.
- [#2245](https://github.com/ClementTsang/bottom/pull/2245): Support solid bars using block and square characters via `styles.widgets.bar_type`. - [#2245](https://github.com/ClementTsang/bottom/pull/2245): Support solid bars using block and square characters via `styles.widgets.bar_type`.
- [#2236](https://github.com/ClementTsang/bottom/pull/2236): Add configurable binary disk capacity units for usage. - [#2236](https://github.com/ClementTsang/bottom/pull/2236): Add configurable binary disk capacity units for disk widget usage.
- [#2251](https://github.com/ClementTsang/bottom/pull/2251): Add configurable binary disk capacity units for disk widget I/O.
### Other ### Other
@@ -27,9 +27,9 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
## Disk Options ## Disk Options
| Option | Behaviour | | Option | Behaviour |
| -------------------------- | --------------------------------------------------------------- | | -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `--disk_use_binary_prefix` | Displays used, free, and total disk space with binary prefixes. | | `--disk_use_binary_prefix` | Displays the disk widget with binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB). |
## Process Options ## Process Options
@@ -77,15 +77,15 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
## Network Options ## Network Options
| Option | Behaviour | | Option | Behaviour |
| ----------------------------- | -------------------------------------------------------------- | | ----------------------------- | ------------------------------------------------------------------------------------------------------ |
| `--network_legend <POSITION>` | Where to place the legend for the network chart widget. | | `--network_legend <POSITION>` | Where to place the legend for the network chart widget. |
| `--network_use_bytes` | Displays the network widget using bytes. | | `--network_use_bytes` | Displays the network widget using bytes. |
| `--network_use_binary_prefix` | Displays the network widget with binary prefixes. | | `--network_use_binary_prefix` | Displays the network widget binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB). |
| `--network_use_log` | Displays the network widget with a log scale. | | `--network_use_log` | Displays the network widget with a log scale. |
| `--show_packets` | Displays packet rate and average packet size info. | | `--show_packets` | Displays packet rate and average packet size info. |
| `--use_old_network_legend` | Uses a separate network legend. | | `--use_old_network_legend` | Uses a separate network legend. |
| `--network_start_zeroed` | Show total network usage from app startup rather than on boot. | | `--network_start_zeroed` | Show total network usage from app startup rather than on boot. |
## Battery Options ## Battery Options
@@ -30,10 +30,10 @@ You can also set the sort order by changing `disk.sort_order` with `"Ascending"`
sort_order = "Ascending" sort_order = "Ascending"
``` ```
## Disk Space Units ## Using Binary Prefixes
Disk space uses decimal prefixes (KB, MB, GB, TB) by default. To display the Used, Free, and Total columns Disk space uses decimal prefixes (e.g. KB, MB, GB, TB) by default. To display using binary prefixes instead
with binary prefixes (KiB, MiB, GiB, TiB), enable `use_binary_prefix`: (e.g. KiB, MiB, GiB, TiB), enable `use_binary_prefix`:
```toml ```toml
[disk] [disk]
+1 -10
View File
@@ -155,7 +155,6 @@
# Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right". # Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
#network_legend = "top-right" #network_legend = "top-right"
# Processes widget configuration # Processes widget configuration
#[processes] #[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): # 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):
@@ -209,7 +208,6 @@
# Show process CPU% usage without averaging over the number of CPU cores. # Show process CPU% usage without averaging over the number of CPU cores.
#unnormalized_cpu = false #unnormalized_cpu = false
# CPU widget configuration # CPU widget configuration
#[cpu] #[cpu]
# One of "all" (default), "average"/"avg" # One of "all" (default), "average"/"avg"
@@ -218,11 +216,10 @@
# Whether to show a decimal place for CPU usage values. # Whether to show a decimal place for CPU usage values.
#show_decimal = false #show_decimal = false
# Disk widget configuration # Disk widget configuration
#[disk] #[disk]
# Whether to display used, free, and total disk space with binary prefixes (e.g. GiB instead of GB). # Whether to display disk widget data with binary prefixes (e.g. GiB instead of GB).
#use_binary_prefix = false #use_binary_prefix = false
# The columns shown by the process widget. The following columns are supported: # The columns shown by the process widget. The following columns are supported:
@@ -277,7 +274,6 @@
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# Disk I/O graph widget configuration # Disk I/O graph widget configuration
#[disk_io_graph] #[disk_io_graph]
@@ -316,7 +312,6 @@
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# Temperature widget configuration # Temperature widget configuration
#[temperature] #[temperature]
@@ -347,7 +342,6 @@
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# Temperature graph widget configuration # Temperature graph widget configuration
#[temperature_graph] #[temperature_graph]
@@ -375,7 +369,6 @@
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# Memory widget configuration # Memory widget configuration
#[memory_graph] #[memory_graph]
@@ -391,7 +384,6 @@
# Use short GPU names (e.g. "GPU" or "GPU0", "GPU1") instead of full GPU names. Only available if the GPU feature is enabled when built. # Use short GPU names (e.g. "GPU" or "GPU0", "GPU1") instead of full GPU names. Only available if the GPU feature is enabled when built.
#short_gpu_names = false #short_gpu_names = false
# Network widget configuration # Network widget configuration
#[network_graph] #[network_graph]
@@ -418,7 +410,6 @@
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# These are all the components that support custom theming. Note that colour support # These are all the components that support custom theming. Note that colour support
# will depend on terminal support. # will depend on terminal support.
#[styles] # Uncomment if you want to use custom styling #[styles] # Uncomment if you want to use custom styling
+1 -1
View File
@@ -301,7 +301,7 @@
"$ref": "#/$defs/SortOrder" "$ref": "#/$defs/SortOrder"
}, },
"use_binary_prefix": { "use_binary_prefix": {
"description": "Displays used, free, and total disk space with binary prefixes (e.g. GiB).\nDefaults to decimal prefixes (e.g. GB).", "description": "Use binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB).\n\nDefaults to decimal prefixes.",
"type": [ "type": [
"boolean", "boolean",
"null" "null"
+1 -10
View File
@@ -404,7 +404,6 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right". # Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
#network_legend = "top-right" #network_legend = "top-right"
# Processes widget configuration # Processes widget configuration
#[processes] #[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): # 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):
@@ -458,7 +457,6 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Show process CPU% usage without averaging over the number of CPU cores. # Show process CPU% usage without averaging over the number of CPU cores.
#unnormalized_cpu = false #unnormalized_cpu = false
# CPU widget configuration # CPU widget configuration
#[cpu] #[cpu]
# One of "all" (default), "average"/"avg" # One of "all" (default), "average"/"avg"
@@ -467,11 +465,10 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Whether to show a decimal place for CPU usage values. # Whether to show a decimal place for CPU usage values.
#show_decimal = false #show_decimal = false
# Disk widget configuration # Disk widget configuration
#[disk] #[disk]
# Whether to display used, free, and total disk space with binary prefixes (e.g. GiB instead of GB). # Whether to display disk widget data with binary prefixes (e.g. GiB instead of GB).
#use_binary_prefix = false #use_binary_prefix = false
# The columns shown by the process widget. The following columns are supported: # The columns shown by the process widget. The following columns are supported:
@@ -526,7 +523,6 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# Disk I/O graph widget configuration # Disk I/O graph widget configuration
#[disk_io_graph] #[disk_io_graph]
@@ -565,7 +561,6 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# Temperature widget configuration # Temperature widget configuration
#[temperature] #[temperature]
@@ -596,7 +591,6 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# Temperature graph widget configuration # Temperature graph widget configuration
#[temperature_graph] #[temperature_graph]
@@ -624,7 +618,6 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# Memory widget configuration # Memory widget configuration
#[memory_graph] #[memory_graph]
@@ -640,7 +633,6 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Use short GPU names (e.g. "GPU" or "GPU0", "GPU1") instead of full GPU names. Only available if the GPU feature is enabled when built. # Use short GPU names (e.g. "GPU" or "GPU0", "GPU1") instead of full GPU names. Only available if the GPU feature is enabled when built.
#short_gpu_names = false #short_gpu_names = false
# Network widget configuration # Network widget configuration
#[network_graph] #[network_graph]
@@ -667,7 +659,6 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Whether to require matching the whole word. Defaults to false. # Whether to require matching the whole word. Defaults to false.
#whole_word = false #whole_word = false
# These are all the components that support custom theming. Note that colour support # These are all the components that support custom theming. Note that colour support
# will depend on terminal support. # will depend on terminal support.
#[styles] # Uncomment if you want to use custom styling #[styles] # Uncomment if you want to use custom styling
+3 -5
View File
@@ -562,9 +562,8 @@ pub struct DiskArgs {
#[arg( #[arg(
long, long,
action = ArgAction::SetTrue, action = ArgAction::SetTrue,
help = "Displays disk space with binary prefixes.", help = "Displays the disk widget with binary prefixes.",
long_help = "Displays used, free, and total disk space with binary prefixes (e.g. KiB, MiB, GiB) \ long_help = "Displays the disk widget with binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB).",
rather than decimal prefixes (e.g. KB, MB, GB). Defaults to decimal prefixes.",
alias = "disk-use-binary-prefix" alias = "disk-use-binary-prefix"
)] )]
pub disk_use_binary_prefix: bool, pub disk_use_binary_prefix: bool,
@@ -598,8 +597,7 @@ pub struct NetworkArgs {
long, long,
action = ArgAction::SetTrue, action = ArgAction::SetTrue,
help = "Displays the network widget with binary prefixes.", help = "Displays the network widget with binary prefixes.",
long_help = "Displays the network widget with binary prefixes (e.g. kibibits, mebibits) rather than a decimal \ long_help = "Displays the network widget binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB).",
prefixes (e.g. kilobits, megabits). Defaults to decimal prefixes.",
alias = "network-use-binary-prefix" alias = "network-use-binary-prefix"
)] )]
pub network_use_binary_prefix: bool, pub network_use_binary_prefix: bool,
+3 -2
View File
@@ -8,8 +8,9 @@ use crate::{canvas::components::data_table::SortOrder, options::DiskWidgetColumn
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
#[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))] #[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))]
pub(crate) struct DiskConfig { pub(crate) struct DiskConfig {
/// Displays used, free, and total disk space with binary prefixes (e.g. GiB). /// Use binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB).
/// Defaults to decimal prefixes (e.g. GB). ///
/// Defaults to decimal prefixes.
pub(crate) use_binary_prefix: Option<bool>, pub(crate) use_binary_prefix: Option<bool>,
/// A filter over the disk names. /// A filter over the disk names.
+71 -3
View File
@@ -20,9 +20,9 @@ pub(crate) fn get_binary_unit_and_denominator(bytes: u64) -> (&'static str, f64)
} }
} }
/// Returns a string given a value that is converted to the closest SI-variant, /// Returns a decimal-prefixed string given a value that is converted to the
/// per second. If the value is greater than a giga-X, then it will return a /// closest SI-variant, per second. If the value is greater than a giga-X,
/// decimal place. /// then it will return a decimal place.
#[inline] #[inline]
pub(crate) fn dec_bytes_per_second_string(value: u64) -> String { pub(crate) fn dec_bytes_per_second_string(value: u64) -> String {
let converted_values = get_decimal_bytes(value); let converted_values = get_decimal_bytes(value);
@@ -33,6 +33,19 @@ pub(crate) fn dec_bytes_per_second_string(value: u64) -> String {
} }
} }
/// Returns a binary-prefixed string given a value that is converted to the
/// closest IEC-variant, per second. If the value is greater than a gibi-X,
/// then it will return a decimal place.
#[inline]
pub(crate) fn bin_bytes_per_second_string(value: u64) -> String {
let converted_values = get_binary_bytes(value);
if value >= GIBI_LIMIT {
format!("{:.1}{}/s", converted_values.0, converted_values.1)
} else {
format!("{:.0}{}/s", converted_values.0, converted_values.1)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@@ -71,4 +84,59 @@ mod test {
"10.4TB/s".to_string() "10.4TB/s".to_string()
); );
} }
#[test]
fn test_bin_bytes_per_second_string() {
assert_eq!(bin_bytes_per_second_string(0), "0B/s".to_string());
assert_eq!(bin_bytes_per_second_string(1), "1B/s".to_string());
assert_eq!(bin_bytes_per_second_string(900), "900B/s".to_string());
assert_eq!(
bin_bytes_per_second_string(KILO_LIMIT),
"1000B/s".to_string()
);
assert_eq!(bin_bytes_per_second_string(1023), "1023B/s".to_string());
assert_eq!(
bin_bytes_per_second_string(KIBI_LIMIT),
"1KiB/s".to_string()
);
assert_eq!(
bin_bytes_per_second_string(KIBI_LIMIT + 1),
"1KiB/s".to_string()
);
assert_eq!(
bin_bytes_per_second_string(MEBI_LIMIT),
"1MiB/s".to_string()
);
// The decimal place is added past a gibibyte, not a gigabyte.
assert_eq!(
bin_bytes_per_second_string(GIGA_LIMIT),
"954MiB/s".to_string()
);
assert_eq!(
bin_bytes_per_second_string(GIBI_LIMIT - 1),
"1024MiB/s".to_string()
);
assert_eq!(
bin_bytes_per_second_string(GIBI_LIMIT),
"1.0GiB/s".to_string()
);
assert_eq!(
bin_bytes_per_second_string(2 * GIBI_LIMIT),
"2.0GiB/s".to_string()
);
assert_eq!(
bin_bytes_per_second_string((2.5 * GIBI_LIMIT as f64) as u64),
"2.5GiB/s".to_string()
);
assert_eq!(
bin_bytes_per_second_string((10.34 * TEBI_LIMIT as f64) as u64),
"10.3TiB/s".to_string()
);
assert_eq!(
bin_bytes_per_second_string((10.36 * TEBI_LIMIT as f64) as u64),
"10.4TiB/s".to_string()
);
}
} }
+27 -11
View File
@@ -10,13 +10,16 @@ use crate::{
}, },
options::config::style::Styles, options::config::style::Styles,
utils::{ utils::{
conversion::dec_bytes_per_second_string, data_units::convert_bytes, conversion::{bin_bytes_per_second_string, dec_bytes_per_second_string},
data_units::convert_bytes,
general::sort_partial_fn, general::sort_partial_fn,
}, },
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DiskWidgetData { pub struct DiskWidgetData {
// TODO: Remove this field, carry it through the widget configuration
// rather than data (requires some refactoring)
pub use_binary_prefix: bool, pub use_binary_prefix: bool,
pub name: String, pub name: String,
pub mount_point: String, pub mount_point: String,
@@ -86,13 +89,21 @@ impl DiskWidgetData {
fn io_read(&self) -> Cow<'static, str> { fn io_read(&self) -> Cow<'static, str> {
self.io_read_rate_bytes.map_or("N/A".into(), |r_rate| { self.io_read_rate_bytes.map_or("N/A".into(), |r_rate| {
dec_bytes_per_second_string(r_rate).into() if self.use_binary_prefix {
bin_bytes_per_second_string(r_rate).into()
} else {
dec_bytes_per_second_string(r_rate).into()
}
}) })
} }
fn io_write(&self) -> Cow<'static, str> { fn io_write(&self) -> Cow<'static, str> {
self.io_write_rate_bytes.map_or("N/A".into(), |w_rate| { self.io_write_rate_bytes.map_or("N/A".into(), |w_rate| {
dec_bytes_per_second_string(w_rate).into() if self.use_binary_prefix {
bin_bytes_per_second_string(w_rate).into()
} else {
dec_bytes_per_second_string(w_rate).into()
}
}) })
} }
} }
@@ -431,7 +442,7 @@ mod test {
} }
} }
fn cell(data: &DiskWidgetData, column: DiskWidgetColumn) -> Cow<'static, str> { fn render_cell(data: &DiskWidgetData, column: DiskWidgetColumn) -> Cow<'static, str> {
data.to_cell_text(&column, NonZeroU16::new(10).unwrap()) data.to_cell_text(&column, NonZeroU16::new(10).unwrap())
.unwrap() .unwrap()
} }
@@ -455,7 +466,7 @@ mod test {
DiskWidgetColumn::Free, DiskWidgetColumn::Free,
DiskWidgetColumn::Total, DiskWidgetColumn::Total,
] { ] {
assert_eq!(cell(&data, column), expected); assert_eq!(render_cell(&data, column), expected);
} }
} }
} }
@@ -474,15 +485,20 @@ mod test {
DiskWidgetColumn::IoRead, DiskWidgetColumn::IoRead,
DiskWidgetColumn::IoWrite, DiskWidgetColumn::IoWrite,
] { ] {
assert_eq!(cell(&missing, column), "N/A"); assert_eq!(render_cell(&missing, column), "N/A");
} }
let data = disk(Some(500 * GIBI_LIMIT), use_binary_prefix); let data = disk(Some(500 * GIBI_LIMIT), use_binary_prefix);
assert_eq!(cell(&data, DiskWidgetColumn::UsedPercent), "50.0%"); assert_eq!(render_cell(&data, DiskWidgetColumn::UsedPercent), "50.0%");
assert_eq!(cell(&data, DiskWidgetColumn::FreePercent), "50.0%"); assert_eq!(render_cell(&data, DiskWidgetColumn::FreePercent), "50.0%");
assert_eq!(cell(&data, DiskWidgetColumn::IoRead), "536.9GB/s"); if use_binary_prefix {
assert_eq!(cell(&data, DiskWidgetColumn::IoWrite), "536.9GB/s"); assert_eq!(render_cell(&data, DiskWidgetColumn::IoRead), "500.0GiB/s");
assert_eq!(render_cell(&data, DiskWidgetColumn::IoWrite), "500.0GiB/s");
} else {
assert_eq!(render_cell(&data, DiskWidgetColumn::IoRead), "536.9GB/s");
assert_eq!(render_cell(&data, DiskWidgetColumn::IoWrite), "536.9GB/s");
}
assert_eq!( assert_eq!(
cell( render_cell(
&disk(Some(0), use_binary_prefix), &disk(Some(0), use_binary_prefix),
DiskWidgetColumn::UsedPercent DiskWidgetColumn::UsedPercent
), ),