feature: free arc (#1812)

* feature: free arc

* freebsd clippy no-default-features

* add alias to free_arc

* add get_threads to default config

* clippy

* code review: combine zfs feature and target_os build cfgs
This commit is contained in:
Justin Martin
2025-10-12 19:08:55 +00:00
committed by GitHub
parent 16449f2697
commit 118bb5e061
13 changed files with 111 additions and 24 deletions
@@ -35,7 +35,7 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
| `--disable_advanced_kill` | Hides additional stopping options on Unix-like systems. |
| `--get_threads` | Also gather process thread information. |
| `-g, --group_processes` | Groups processes with the same name by default. No effect if `--tree` is set. |
| `--hide_k_threads` | Hide kernel threads by default. |
| `--hide_k_threads` | Hide kernel threads by default. |
| `--process_memory_as_value` | Defaults to showing process memory usage by value. |
| `--process_command` | Shows the full command name instead of the process name by default. |
| `-R, --regex` | Enables regex by default while searching. |
@@ -66,6 +66,7 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
| ---------------------------- | --------------------------------------------------------- |
| `--memory_legend <POSITION>` | Where to place the legend for the memory chart widget. |
| `--enable_cache_memory` | Enable collecting and displaying cache and buffer memory. |
| `--free_arc` | Subtract freeable ARC from memory. |
## Network Options
@@ -52,4 +52,5 @@ each time:
| `network_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the network widget. |
| `average_cpu_row` | Boolean | Moves the average CPU usage entry to its own row when using basic mode. |
| `tree_collapse` | Boolean | Collapse process tree by default. |
| `hide_k_threads` | Boolean | Hide kernel threads by default. |
| `hide_k_threads` | Boolean | Hide kernel threads by default. |
| `free_arc` | Boolean | Subtract freeable ARC from memory. |
+6
View File
@@ -115,6 +115,9 @@
# Shows cache and buffer memory
#enable_cache_memory = false
# Subtract freeable ARC from memory usage
#free_arc = false
# How much data is stored at once in terms of time.
#retention = "10m"
@@ -131,6 +134,9 @@
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
#columns = ["PID", "Name", "CPU%", "Mem%", "Virt", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"]
# Gather process child thread information
#get_threads = false
# CPU widget configuration
#[cpu]
+6
View File
@@ -376,6 +376,12 @@
"null"
]
},
"free_arc": {
"type": [
"boolean",
"null"
]
},
"group_processes": {
"type": [
"boolean",
+2
View File
@@ -59,6 +59,8 @@ pub struct AppConfigFields {
pub is_advanced_kill: bool,
#[cfg(target_os = "linux")]
pub hide_k_threads: bool,
#[cfg(feature = "zfs")]
pub free_arc: bool,
pub memory_legend_position: Option<LegendPosition>,
// TODO: Remove these, move network details state-side.
pub network_unit_type: DataUnit,
+42 -5
View File
@@ -184,6 +184,8 @@ pub struct DataCollector {
gpu_pids: Option<Vec<HashMap<u32, (u64, u32)>>>,
#[cfg(feature = "gpu")]
gpus_total_mem: Option<u64>,
#[cfg(feature = "zfs")]
free_arc_mem: bool,
}
const LESS_ROUTINE_TASK_TIME: Duration = Duration::from_secs(60);
@@ -222,6 +224,8 @@ impl DataCollector {
gpu_pids: None,
#[cfg(feature = "gpu")]
gpus_total_mem: None,
#[cfg(feature = "zfs")]
free_arc_mem: false,
last_list_collection_time: last_collection_time,
should_run_less_routine_tasks: true,
}
@@ -267,6 +271,11 @@ impl DataCollector {
self.get_process_threads = get_process_threads;
}
#[cfg(feature = "zfs")]
pub fn set_free_arc_mem(&mut self, free_mem: bool) {
self.free_arc_mem = free_mem;
}
/// Refresh sysinfo data. We use sysinfo for the following data:
/// - CPU usage
/// - Memory usage
@@ -463,17 +472,45 @@ impl DataCollector {
if self.widgets_to_harvest.use_mem {
self.data.memory = memory::get_ram_usage(&self.sys.system);
#[cfg(feature = "zfs")]
{
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
if let Some(arc) = memory::arc::get_arc_usage() {
if let Some(mem) = &mut self.data.memory {
if self.free_arc_mem {
if arc.0.used_bytes > arc.1 {
#[cfg(target_os = "linux")]
{
mem.used_bytes -= arc.0.used_bytes.saturating_sub(arc.1); // keep arc min like htop
}
#[cfg(target_os = "freebsd")]
{
mem.used_bytes += arc.1; // sysinfo subtracts arc_size on freebsd
}
} else {
#[cfg(target_os = "freebsd")]
{
mem.used_bytes += arc.0.used_bytes;
}
}
} else {
#[cfg(target_os = "freebsd")]
{
mem.used_bytes += arc.0.used_bytes;
}
}
}
self.data.arc = Some(arc.0);
}
}
#[cfg(not(target_os = "windows"))]
if self.widgets_to_harvest.use_cache {
self.data.cache = memory::get_cache_usage(&self.sys.system);
}
self.data.swap = memory::get_swap_usage(&self.sys.system);
#[cfg(feature = "zfs")]
{
self.data.arc = memory::arc::get_arc_usage();
}
}
}
+1
View File
@@ -27,6 +27,7 @@ struct FileSystem {
pub fn get_io_usage() -> CollectionResult<IoHarvest> {
// TODO: Should this (and other I/O collectors) fail fast? In general, should
// collection ever fail fast?
#[cfg_attr(not(feature = "zfs"), expect(unused_mut))]
let mut io_harvest: HashMap<String, Option<IoData>> =
get_disk_info().map(|storage_system_information| {
storage_system_information
+25 -17
View File
@@ -1,11 +1,11 @@
#[cfg(all(feature = "zfs", any(target_os = "linux", target_os = "freebsd")))]
use super::MemData;
#[cfg(all(feature = "zfs", any(target_os = "linux", target_os = "freebsd")))]
/// Return ARC usage.
#[cfg(feature = "zfs")]
pub(crate) fn get_arc_usage() -> Option<MemData> {
pub(crate) fn get_arc_usage() -> Option<(MemData, u64)> {
use std::num::NonZeroU64;
let (mem_total, mem_used) = {
let (mem_total, mem_used, mem_min) = {
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
// TODO: [OPT] is this efficient?
@@ -13,14 +13,16 @@ pub(crate) fn get_arc_usage() -> Option<MemData> {
if let Ok(arc_stats) = read_to_string("/proc/spl/kstat/zfs/arcstats") {
let mut mem_arc = 0;
let mut mem_total = 0;
let mut mem_min = 0;
let mut zfs_keys_read: u8 = 0;
const ZFS_KEYS_NEEDED: u8 = 2;
const ZFS_KEYS_NEEDED: u8 = 3;
for line in arc_stats.lines() {
if let Some((label, value)) = line.split_once(' ') {
let to_write = match label {
"size" => &mut mem_arc,
"c_max" => &mut mem_total,
"c_min" => &mut mem_min,
_ => {
continue;
}
@@ -39,34 +41,40 @@ pub(crate) fn get_arc_usage() -> Option<MemData> {
}
}
}
(mem_total, mem_arc)
(mem_total, mem_arc, mem_min)
} else {
(0, 0)
(0, 0, 0)
}
} else if #[cfg(target_os = "freebsd")] {
use sysctl::Sysctl;
if let (Ok(mem_arc_value), Ok(mem_sys_value)) = (
if let (Ok(mem_arc_value), Ok(mem_sys_value), Ok(mem_min_value)) = (
sysctl::Ctl::new("kstat.zfs.misc.arcstats.size"),
sysctl::Ctl::new("kstat.zfs.misc.arcstats.c_max"),
sysctl::Ctl::new("kstat.zfs.misc.arcstats.c_min"),
) {
if let (Ok(sysctl::CtlValue::U64(arc)), Ok(sysctl::CtlValue::Ulong(mem))) =
(mem_arc_value.value(), mem_sys_value.value())
if let (Ok(sysctl::CtlValue::U64(arc)), Ok(sysctl::CtlValue::U64(mem)), Ok(sysctl::CtlValue::U64(min))) =
(mem_arc_value.value(), mem_sys_value.value(), mem_min_value.value())
{
(mem, arc)
(mem, arc, min)
} else {
(0, 0)
(0, 0, 0)
}
} else {
(0, 0)
(0, 0, 0)
}
} else {
(0, 0)
(0, 0, 0)
}
}
};
NonZeroU64::new(mem_total).map(|total_bytes| MemData {
total_bytes,
used_bytes: mem_used,
NonZeroU64::new(mem_total).map(|total_bytes| {
(
MemData {
total_bytes,
used_bytes: mem_used,
},
mem_min,
)
})
}
+6
View File
@@ -365,6 +365,9 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# Shows cache and buffer memory
#enable_cache_memory = false
# Subtract freeable ARC from memory usage
#free_arc = false
# How much data is stored at once in terms of time.
#retention = "10m"
@@ -381,6 +384,9 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
#columns = ["PID", "Name", "CPU%", "Mem%", "Virt", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"]
# Gather process child thread information
#get_threads = false
# CPU widget configuration
#[cpu]
+4
View File
@@ -228,6 +228,8 @@ fn create_collection_thread(
let show_average_cpu = app_config_fields.show_average_cpu;
let update_sleep = app_config_fields.update_rate;
let get_process_threads = app_config_fields.get_process_threads;
#[cfg(feature = "zfs")]
let get_arc_free = app_config_fields.free_arc;
thread::spawn(move || {
let mut data_collector = collection::DataCollector::new(filters);
@@ -237,6 +239,8 @@ fn create_collection_thread(
data_collector.set_unnormalized_cpu(unnormalized_cpu);
data_collector.set_show_average_cpu(show_average_cpu);
data_collector.set_get_process_threads(get_process_threads);
#[cfg(feature = "zfs")]
data_collector.set_free_arc_mem(get_arc_free);
data_collector.update_data();
data_collector.data = Data::default();
+4
View File
@@ -230,6 +230,8 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL
let use_basic_mode = is_flag_enabled!(basic, args.general, config);
let expanded = is_flag_enabled!(expanded, args.general, config);
#[cfg(feature = "zfs")]
let free_arc = is_flag_enabled!(free_arc, args.memory, config);
// For processes
let is_grouped = is_flag_enabled!(group_processes, args.process, config);
@@ -329,6 +331,8 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL
retention_ms,
dedicated_average_row: get_dedicated_avg_row(config),
default_tree_collapse: is_default_tree_collapsed,
#[cfg(feature = "zfs")]
free_arc,
};
let table_config = ProcTableConfig {
+9
View File
@@ -518,6 +518,15 @@ pub struct MemoryArgs {
alias = "enable-cache-memory"
)]
pub enable_cache_memory: bool,
#[cfg(feature = "zfs")]
#[arg(
long,
action = ArgAction::SetTrue,
help = "Subtract reclaimable ARC from memory.",
alias = "free-arc"
)]
pub free_arc: bool,
}
/// Network arguments/config options.
+2
View File
@@ -42,6 +42,8 @@ pub(crate) struct GeneralConfig {
pub(crate) disable_advanced_kill: Option<bool>, // This does nothing on Windows, but we leave it enabled to make the config file consistent across platforms.
// #[cfg(target_os = "linux")]
pub(crate) hide_k_threads: Option<bool>,
// #[cfg(feature = "zfs")]
pub(crate) free_arc: Option<bool>,
pub(crate) network_use_bytes: Option<bool>,
pub(crate) network_use_log: Option<bool>,
pub(crate) network_use_binary_prefix: Option<bool>,