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.
- [#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
@@ -27,9 +27,9 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
## Disk Options
| Option | Behaviour |
| -------------------------- | --------------------------------------------------------------- |
| `--disk_use_binary_prefix` | Displays used, free, and total disk space with binary prefixes. |
| Option | Behaviour |
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `--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
@@ -77,15 +77,15 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
## Network Options
| Option | Behaviour |
| ----------------------------- | -------------------------------------------------------------- |
| `--network_legend <POSITION>` | Where to place the legend for the network chart widget. |
| `--network_use_bytes` | Displays the network widget using bytes. |
| `--network_use_binary_prefix` | Displays the network widget with binary prefixes. |
| `--network_use_log` | Displays the network widget with a log scale. |
| `--show_packets` | Displays packet rate and average packet size info. |
| `--use_old_network_legend` | Uses a separate network legend. |
| `--network_start_zeroed` | Show total network usage from app startup rather than on boot. |
| Option | Behaviour |
| ----------------------------- | ------------------------------------------------------------------------------------------------------ |
| `--network_legend <POSITION>` | Where to place the legend for the network chart widget. |
| `--network_use_bytes` | Displays the network widget using bytes. |
| `--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. |
| `--show_packets` | Displays packet rate and average packet size info. |
| `--use_old_network_legend` | Uses a separate network legend. |
| `--network_start_zeroed` | Show total network usage from app startup rather than on boot. |
## Battery Options
@@ -30,10 +30,10 @@ You can also set the sort order by changing `disk.sort_order` with `"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
with binary prefixes (KiB, MiB, GiB, TiB), enable `use_binary_prefix`:
Disk space uses decimal prefixes (e.g. KB, MB, GB, TB) by default. To display using binary prefixes instead
(e.g. KiB, MiB, GiB, TiB), enable `use_binary_prefix`:
```toml
[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".
#network_legend = "top-right"
# Processes widget configuration
#[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):
@@ -209,7 +208,6 @@
# Show process CPU% usage without averaging over the number of CPU cores.
#unnormalized_cpu = false
# CPU widget configuration
#[cpu]
# One of "all" (default), "average"/"avg"
@@ -218,11 +216,10 @@
# Whether to show a decimal place for CPU usage values.
#show_decimal = false
# Disk widget configuration
#[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
# 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.
#whole_word = false
# Disk I/O graph widget configuration
#[disk_io_graph]
@@ -316,7 +312,6 @@
# Whether to require matching the whole word. Defaults to false.
#whole_word = false
# Temperature widget configuration
#[temperature]
@@ -347,7 +342,6 @@
# Whether to require matching the whole word. Defaults to false.
#whole_word = false
# Temperature graph widget configuration
#[temperature_graph]
@@ -375,7 +369,6 @@
# Whether to require matching the whole word. Defaults to false.
#whole_word = false
# Memory widget configuration
#[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.
#short_gpu_names = false
# Network widget configuration
#[network_graph]
@@ -418,7 +410,6 @@
# Whether to require matching the whole word. Defaults to false.
#whole_word = false
# These are all the components that support custom theming. Note that colour support
# will depend on terminal support.
#[styles] # Uncomment if you want to use custom styling
+1 -1
View File
@@ -301,7 +301,7 @@
"$ref": "#/$defs/SortOrder"
},
"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": [
"boolean",
"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".
#network_legend = "top-right"
# Processes widget configuration
#[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):
@@ -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.
#unnormalized_cpu = false
# CPU widget configuration
#[cpu]
# 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.
#show_decimal = false
# Disk widget configuration
#[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
# 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.
#whole_word = false
# Disk I/O graph widget configuration
#[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.
#whole_word = false
# Temperature widget configuration
#[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.
#whole_word = false
# Temperature graph widget configuration
#[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.
#whole_word = false
# Memory widget configuration
#[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.
#short_gpu_names = false
# Network widget configuration
#[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.
#whole_word = false
# These are all the components that support custom theming. Note that colour support
# will depend on terminal support.
#[styles] # Uncomment if you want to use custom styling
+3 -5
View File
@@ -562,9 +562,8 @@ pub struct DiskArgs {
#[arg(
long,
action = ArgAction::SetTrue,
help = "Displays disk space with binary prefixes.",
long_help = "Displays used, free, and total disk space with binary prefixes (e.g. KiB, MiB, GiB) \
rather than decimal prefixes (e.g. KB, MB, GB). Defaults to decimal prefixes.",
help = "Displays the disk widget with binary prefixes.",
long_help = "Displays the disk widget with binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB).",
alias = "disk-use-binary-prefix"
)]
pub disk_use_binary_prefix: bool,
@@ -598,8 +597,7 @@ pub struct NetworkArgs {
long,
action = ArgAction::SetTrue,
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 \
prefixes (e.g. kilobits, megabits). Defaults to decimal prefixes.",
long_help = "Displays the network widget binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB).",
alias = "network-use-binary-prefix"
)]
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(test, serde(deny_unknown_fields), derive(PartialEq, Eq))]
pub(crate) struct DiskConfig {
/// Displays used, free, and total disk space with binary prefixes (e.g. GiB).
/// Defaults to decimal prefixes (e.g. GB).
/// Use binary prefixes (e.g. GiB, MiB) instead of decimal prefixes (e.g. GB, MB).
///
/// Defaults to decimal prefixes.
pub(crate) use_binary_prefix: Option<bool>,
/// 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,
/// per second. If the value is greater than a giga-X, then it will return a
/// decimal place.
/// Returns a decimal-prefixed string given a value that is converted to the
/// closest SI-variant, per second. If the value is greater than a giga-X,
/// then it will return a decimal place.
#[inline]
pub(crate) fn dec_bytes_per_second_string(value: u64) -> String {
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)]
mod test {
use super::*;
@@ -71,4 +84,59 @@ mod test {
"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,
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,
},
};
#[derive(Clone, Debug)]
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 name: String,
pub mount_point: String,
@@ -86,13 +89,21 @@ impl DiskWidgetData {
fn io_read(&self) -> Cow<'static, str> {
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> {
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())
.unwrap()
}
@@ -455,7 +466,7 @@ mod test {
DiskWidgetColumn::Free,
DiskWidgetColumn::Total,
] {
assert_eq!(cell(&data, column), expected);
assert_eq!(render_cell(&data, column), expected);
}
}
}
@@ -474,15 +485,20 @@ mod test {
DiskWidgetColumn::IoRead,
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);
assert_eq!(cell(&data, DiskWidgetColumn::UsedPercent), "50.0%");
assert_eq!(cell(&data, DiskWidgetColumn::FreePercent), "50.0%");
assert_eq!(cell(&data, DiskWidgetColumn::IoRead), "536.9GB/s");
assert_eq!(cell(&data, DiskWidgetColumn::IoWrite), "536.9GB/s");
assert_eq!(render_cell(&data, DiskWidgetColumn::UsedPercent), "50.0%");
assert_eq!(render_cell(&data, DiskWidgetColumn::FreePercent), "50.0%");
if use_binary_prefix {
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!(
cell(
render_cell(
&disk(Some(0), use_binary_prefix),
DiskWidgetColumn::UsedPercent
),