about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-07-24 21:52:56 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-07-24 21:52:56 +0200
commit0e90167647a53978705618f435e3d96454e6b6cf (patch)
treec0c58af1780ccef1c312fb1cfb7144b460a4f8d2
parentpkgs/tskm: Support raw paths in place of URLs (diff)
downloadnixos-config-0e90167647a53978705618f435e3d96454e6b6cf.zip
pkgs/i3status-rust: Inline the patches
The github patches seem to change their hash from time to time?
-rw-r--r--pkgs/by-name/i3/i3status-rust-patched/package.nix13
-rw-r--r--pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch190
-rw-r--r--pkgs/by-name/i3/i3status-rust-patched/patches/0002-memory-Directly-convert-reported-memory-usage-into-b.patch117
-rw-r--r--pkgs/by-name/i3/i3status-rust-patched/patches/0003-memory-Avoid-estimating-available-memory-use-kernel-.patch49
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
+