diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-07-24 21:52:56 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-07-24 21:52:56 +0200 |
commit | 0e90167647a53978705618f435e3d96454e6b6cf (patch) | |
tree | c0c58af1780ccef1c312fb1cfb7144b460a4f8d2 | |
parent | pkgs/tskm: Support raw paths in place of URLs (diff) | |
download | nixos-config-0e90167647a53978705618f435e3d96454e6b6cf.zip |
pkgs/i3status-rust: Inline the patches
The github patches seem to change their hash from time to time?
4 files changed, 359 insertions, 10 deletions
diff --git a/pkgs/by-name/i3/i3status-rust-patched/package.nix b/pkgs/by-name/i3/i3status-rust-patched/package.nix index 9f172d49..1d0a42bc 100644 --- a/pkgs/by-name/i3/i3status-rust-patched/package.nix +++ b/pkgs/by-name/i3/i3status-rust-patched/package.nix @@ -18,17 +18,10 @@ i3status-rust.overrideAttrs (final: prev: { (prev.patches or []) ++ [ # Btrfs support for disk_space block. - (fetchpatch2 { - name = "disk_space: Support btrfs backend"; - url = "https://patch-diff.githubusercontent.com/raw/greshake/i3status-rust/pull/2159.patch"; - hash = "sha256-S2/biX6FTLJNfI9QVgwr+V8IGMRnSFIZnTrhc+1LvqQ="; - }) + ./patches/0001-disk_space-Support-btrfs-backend.patch # Correctly calculate the used memory. - (fetchpatch2 { - name = "memory: Avoid estimating available memory, use kernel estimate instead"; - url = "https://patch-diff.githubusercontent.com/raw/greshake/i3status-rust/pull/2160.patch"; - hash = "sha256-1wB2KpXhC/UIxAgRioOYj/bnrzRSuaHAdbeoZ2O5E/Y="; - }) + ./patches/0002-memory-Directly-convert-reported-memory-usage-into-b.patch + ./patches/0003-memory-Avoid-estimating-available-memory-use-kernel-.patch ]; }) diff --git a/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch b/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch new file mode 100644 index 00000000..8ef0af2e --- /dev/null +++ b/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch @@ -0,0 +1,190 @@ +From 78d2936b67064e3b5e700a2859d00ea3dd6eda4c Mon Sep 17 00:00:00 2001 +From: Benedikt Peetz <benedikt.peetz@b-peetz.de> +Date: Sun, 18 May 2025 20:22:04 +0200 +Subject: [PATCH 1/3] disk_space: Support btrfs backend + +Btrfs is too smart for the statvfs based backend (i.e., only counting +blocks leads to wrong numbers). + +For example, a btrfs disk with a lot of de-duplicated blocks (via the copy +on write mechanism) might have a drastically over-reported disk usage. + +The btrfs backend is currently implemented by parsing the output of the +`btrfs filesystem usage --raw` command. This is suboptimal, as this now +relies on the command output not changing. + +Vendoring the algorithm used internally by the `btrfs` command does not +seem to be a reasonable alternative, considering that the code[1] is +rather complex, low level and would require semi-constant maintenance. +Additionally, the c code would need bindings to be usable from rust. + +I assume, that the `btrfs` command output will stay rather similar in +the future, as a lot of tools rely on directly parsing it (see the +various scripts in the issue, this commit fixes). + +[1]: https://github.com/kdave/btrfs-progs/blob/eeab081e9d9fbdf4583122ed1caedf541383cf2d/cmds/filesystem-usage.c#L442 + +Fixes: #1654 +--- + src/blocks/disk_space.rs | 112 +++++++++++++++++++++++++++++++++++---- + 1 file changed, 101 insertions(+), 11 deletions(-) + +diff --git a/src/blocks/disk_space.rs b/src/blocks/disk_space.rs +index 79bfebd27..da0d3f518 100644 +--- a/src/blocks/disk_space.rs ++++ b/src/blocks/disk_space.rs +@@ -12,6 +12,7 @@ + //! `alert` | A value which will trigger critical block state | `10.0` + //! `info_type` | Determines which information will affect the block state. Possible values are `"available"`, `"free"` and `"used"` | `"available"` + //! `alert_unit` | The unit of `alert` and `warning` options. If not set, percents are used. Possible values are `"B"`, `"KB"`, `"KiB"`, `"MB"`, `"MiB"`, `"GB"`, `"Gib"`, `"TB"` and `"TiB"` | `None` ++//! `backend` | The backend to use when querying disk usage. Possible values are `"vfs"` (like `du(1)`) and `"btrfs"` | `"vfs"` + //! + //! Placeholder | Value | Type | Unit + //! -------------|--------------------------------------------------------------------|--------|------- +@@ -63,9 +64,12 @@ + + // make_log_macro!(debug, "disk_space"); + ++use std::cell::OnceCell; ++ + use super::prelude::*; + use crate::formatting::prefix::Prefix; + use nix::sys::statvfs::statvfs; ++use tokio::process::Command; + + #[derive(Copy, Clone, Debug, Deserialize, SmartDefault)] + #[serde(rename_all = "lowercase")] +@@ -76,11 +80,20 @@ pub enum InfoType { + Used, + } + ++#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)] ++#[serde(rename_all = "lowercase")] ++pub enum Backend { ++ #[default] ++ Vfs, ++ Btrfs, ++} ++ + #[derive(Deserialize, Debug, SmartDefault)] + #[serde(deny_unknown_fields, default)] + pub struct Config { + #[default("/".into())] + pub path: ShellString, ++ pub backend: Backend, + pub info_type: InfoType, + pub format: FormatConfig, + pub format_alt: Option<FormatConfig>, +@@ -128,17 +141,9 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + loop { + let mut widget = Widget::new().with_format(format.clone()); + +- let statvfs = statvfs(&*path).error("failed to retrieve statvfs")?; +- +- // Casting to be compatible with 32-bit systems +- #[allow(clippy::unnecessary_cast)] +- let (total, used, available, free) = { +- let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64); +- let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64)) +- * (statvfs.fragment_size() as u64); +- let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64); +- let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64); +- (total, used, available, free) ++ let (total, used, available, free) = match config.backend { ++ Backend::Vfs => get_vfs(&*path)?, ++ Backend::Btrfs => get_btrfs(&path).await?, + }; + + let result = match config.info_type { +@@ -205,3 +210,88 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + } + } + } ++ ++fn get_vfs<P>(path: &P) -> Result<(u64, u64, u64, u64)> ++where ++ P: ?Sized + nix::NixPath, ++{ ++ let statvfs = statvfs(path).error("failed to retrieve statvfs")?; ++ ++ // Casting to be compatible with 32-bit systems ++ #[allow(clippy::unnecessary_cast)] ++ { ++ let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64); ++ let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64)) ++ * (statvfs.fragment_size() as u64); ++ let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64); ++ let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64); ++ ++ Ok((total, used, available, free)) ++ } ++} ++ ++async fn get_btrfs(path: &str) -> Result<(u64, u64, u64, u64)> { ++ const OUTPUT_CHANGED: &str = "Btrfs filesystem usage output format changed"; ++ ++ fn remove_estimate_min(estimate_str: &str) -> Result<&str> { ++ estimate_str.trim_matches('\t') ++ .split_once("\t") ++ .ok_or(Error::new(OUTPUT_CHANGED)) ++ .map(|v| v.0) ++ } ++ ++ macro_rules! get { ++ ($source:expr, $name:expr, $variable:ident) => { ++ get!(@pre_op (|a| {Ok::<_, Error>(a)}), $source, $name, $variable) ++ }; ++ (@pre_op $function:expr, $source:expr, $name:expr, $variable:ident) => { ++ if $source.starts_with(concat!($name, ":")) { ++ let (found_name, variable_str) = ++ $source.split_once(":").ok_or(Error::new(OUTPUT_CHANGED))?; ++ ++ let variable_str = $function(variable_str)?; ++ ++ debug_assert_eq!(found_name, $name); ++ $variable ++ .set(variable_str.trim().parse().error(OUTPUT_CHANGED)?) ++ .map_err(|_| Error::new(OUTPUT_CHANGED))?; ++ } ++ }; ++ } ++ ++ let filesystem_usage = Command::new("btrfs") ++ .args(["filesystem", "usage", "--raw", path]) ++ .output() ++ .await ++ .error("Failed to collect btrfs filesystem usage info")? ++ .stdout; ++ ++ { ++ let final_total = OnceCell::new(); ++ let final_used = OnceCell::new(); ++ let final_free = OnceCell::new(); ++ ++ let mut lines = filesystem_usage.lines(); ++ while let Some(line) = lines ++ .next_line() ++ .await ++ .error("Failed to read output of btrfs filesystem usage")? ++ { ++ let line = line.trim(); ++ ++ // See btrfs-filesystem(8) for an explanation for the rows. ++ get!(line, "Device size", final_total); ++ get!(line, "Used", final_used); ++ get!(@pre_op remove_estimate_min, line, "Free (estimated)", final_free); ++ } ++ ++ Ok(( ++ *final_total.get().ok_or(Error::new(OUTPUT_CHANGED))?, ++ *final_used.get().ok_or(Error::new(OUTPUT_CHANGED))?, ++ // HACK(@bpeetz): We also return the free disk space as the available one, because btrfs ++ // does not tell us which disk space is reserved for the fs. <2025-05-18> ++ *final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?, ++ *final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?, ++ )) ++ } ++} +-- +2.49.0 + diff --git a/pkgs/by-name/i3/i3status-rust-patched/patches/0002-memory-Directly-convert-reported-memory-usage-into-b.patch b/pkgs/by-name/i3/i3status-rust-patched/patches/0002-memory-Directly-convert-reported-memory-usage-into-b.patch new file mode 100644 index 00000000..00a50e92 --- /dev/null +++ b/pkgs/by-name/i3/i3status-rust-patched/patches/0002-memory-Directly-convert-reported-memory-usage-into-b.patch @@ -0,0 +1,117 @@ +From fccc34618103126d9374a5361a5870ccf8030fa0 Mon Sep 17 00:00:00 2001 +From: Benedikt Peetz <benedikt.peetz@b-peetz.de> +Date: Mon, 19 May 2025 21:26:57 +0200 +Subject: [PATCH 2/3] memory: Directly convert reported memory usage into bytes + +`/proc/meminfo` states that it's report values are in KB, when +they actually are in KiB. Previously, this inconsistency leaked into the +whole code for this block (which had to add `* 1024` after nearly every +assignment). Now this inconsistency is contained to the `Memstate` +structure. +--- + src/blocks/memory.rs | 53 +++++++++++++++++++++++--------------------- + 1 file changed, 28 insertions(+), 25 deletions(-) + +diff --git a/src/blocks/memory.rs b/src/blocks/memory.rs +index 801e61de0..8cf32f9ba 100644 +--- a/src/blocks/memory.rs ++++ b/src/blocks/memory.rs +@@ -112,8 +112,8 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + loop { + let mem_state = Memstate::new().await?; + +- let mem_total = mem_state.mem_total as f64 * 1024.; +- let mem_free = mem_state.mem_free as f64 * 1024.; ++ let mem_total = mem_state.mem_total as f64; ++ let mem_free = mem_state.mem_free as f64; + + // TODO: possibly remove this as it is confusing to have `mem_total_used` and `mem_used` + // htop and such only display equivalent of `mem_used` +@@ -126,8 +126,7 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + min(mem_state.mem_available, mem_state.mem_total) + } else { + mem_state.mem_free +- } as f64 +- * 1024.; ++ } as f64; + + // While zfs_arc_cache can be considered "available" memory, + // it can only free a maximum of (zfs_arc_cache - zfs_arc_min) amount. +@@ -137,14 +136,14 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + .saturating_sub(mem_state.zfs_arc_min) as f64; + let mem_avail = mem_avail + zfs_shrinkable_size; + +- let pagecache = mem_state.pagecache as f64 * 1024.; +- let reclaimable = mem_state.s_reclaimable as f64 * 1024.; +- let shmem = mem_state.shmem as f64 * 1024.; ++ let pagecache = mem_state.pagecache as f64; ++ let reclaimable = mem_state.s_reclaimable as f64; ++ let shmem = mem_state.shmem as f64; + + // See https://lore.kernel.org/lkml/1455827801-13082-1-git-send-email-hannes@cmpxchg.org/ + let cached = pagecache + reclaimable - shmem + zfs_shrinkable_size; + +- let buffers = mem_state.buffers as f64 * 1024.; ++ let buffers = mem_state.buffers as f64; + + // same logic as htop + let used_diff = mem_free + buffers + pagecache + reclaimable; +@@ -157,14 +156,14 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + // account for ZFS ARC cache + let mem_used = mem_used - zfs_shrinkable_size; + +- let swap_total = mem_state.swap_total as f64 * 1024.; +- let swap_free = mem_state.swap_free as f64 * 1024.; +- let swap_cached = mem_state.swap_cached as f64 * 1024.; ++ let swap_total = mem_state.swap_total as f64; ++ let swap_free = mem_state.swap_free as f64; ++ let swap_cached = mem_state.swap_cached as f64; + let swap_used = swap_total - swap_free - swap_cached; + + // Zswap usage +- let zswap_compressed = mem_state.zswap_compressed as f64 * 1024.; +- let zswap_decompressed = mem_state.zswap_decompressed as f64 * 1024.; ++ let zswap_compressed = mem_state.zswap_compressed as f64; ++ let zswap_decompressed = mem_state.zswap_decompressed as f64; + + let zswap_comp_ratio = if zswap_compressed != 0.0 { + zswap_decompressed / zswap_compressed +@@ -310,19 +309,23 @@ impl Memstate { + .and_then(|x| u64::from_str(x).ok()) + .error("failed to parse /proc/meminfo")?; + ++ // These values are reported as “kB” but are actually “kiB”. ++ // Convert them into bytes to avoid having to handle this later. ++ // Source: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-meminfo#s2-proc-meminfo ++ const KIB: u64 = 1024; + match name { +- "MemTotal:" => mem_state.mem_total = val, +- "MemFree:" => mem_state.mem_free = val, +- "MemAvailable:" => mem_state.mem_available = val, +- "Buffers:" => mem_state.buffers = val, +- "Cached:" => mem_state.pagecache = val, +- "SReclaimable:" => mem_state.s_reclaimable = val, +- "Shmem:" => mem_state.shmem = val, +- "SwapTotal:" => mem_state.swap_total = val, +- "SwapFree:" => mem_state.swap_free = val, +- "SwapCached:" => mem_state.swap_cached = val, +- "Zswap:" => mem_state.zswap_compressed = val, +- "Zswapped:" => mem_state.zswap_decompressed = val, ++ "MemTotal:" => mem_state.mem_total = val * KIB, ++ "MemFree:" => mem_state.mem_free = val * KIB, ++ "MemAvailable:" => mem_state.mem_available = val * KIB, ++ "Buffers:" => mem_state.buffers = val * KIB, ++ "Cached:" => mem_state.pagecache = val * KIB, ++ "SReclaimable:" => mem_state.s_reclaimable = val * KIB, ++ "Shmem:" => mem_state.shmem = val * KIB, ++ "SwapTotal:" => mem_state.swap_total = val * KIB, ++ "SwapFree:" => mem_state.swap_free = val * KIB, ++ "SwapCached:" => mem_state.swap_cached = val * KIB, ++ "Zswap:" => mem_state.zswap_compressed = val * KIB, ++ "Zswapped:" => mem_state.zswap_decompressed = val * KIB, + _ => (), + } + +-- +2.49.0 + diff --git a/pkgs/by-name/i3/i3status-rust-patched/patches/0003-memory-Avoid-estimating-available-memory-use-kernel-.patch b/pkgs/by-name/i3/i3status-rust-patched/patches/0003-memory-Avoid-estimating-available-memory-use-kernel-.patch new file mode 100644 index 00000000..8943796d --- /dev/null +++ b/pkgs/by-name/i3/i3status-rust-patched/patches/0003-memory-Avoid-estimating-available-memory-use-kernel-.patch @@ -0,0 +1,49 @@ +From b7ae3bd9672e7e1a3fc8e75d90b5bcbe75e6febf Mon Sep 17 00:00:00 2001 +From: Benedikt Peetz <benedikt.peetz@b-peetz.de> +Date: Mon, 19 May 2025 21:29:54 +0200 +Subject: [PATCH 3/3] memory: Avoid estimating available memory, use kernel + estimate instead + +`free`, `btop` and other tools (like the `sysinfo` rust library) +use `mem_total - mem_avail` to calculate the used memory. As explained +in the already linked [kernel commit][1], `mem_avail` is an estimate of +the available memory based on the current way the kernel handles +memory. + +The previous logic for calculating used memory tried to estimate +this based on `mem_free`, `buffers`, `pagecache` and +`reclaimable`. Unfortunately, as the kernel commit message predicts, +this user space estimate bitrots, as the kernel memory usage patterns change. + +Thus, we now simply use the kernel estimate `mem_avail` as we know that +that is in-sync with the relevant kernel internals. + +[1]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 +--- + src/blocks/memory.rs | 10 +++------- + 1 file changed, 3 insertions(+), 7 deletions(-) + +diff --git a/src/blocks/memory.rs b/src/blocks/memory.rs +index 8cf32f9ba..94d8a5fe3 100644 +--- a/src/blocks/memory.rs ++++ b/src/blocks/memory.rs +@@ -145,13 +145,9 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { + + let buffers = mem_state.buffers as f64; + +- // same logic as htop +- let used_diff = mem_free + buffers + pagecache + reclaimable; +- let mem_used = if mem_total >= used_diff { +- mem_total - used_diff +- } else { +- mem_total - mem_free +- }; ++ // Userspace should use `mem_avail` for estimating the memory that is available. ++ // See: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 ++ let mem_used = mem_total - mem_avail; + + // account for ZFS ARC cache + let mem_used = mem_used - zfs_shrinkable_size; +-- +2.49.0 + |