From 187c2ecf7fa4b0780da0e9070f6c05e07a45b30f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 26 Jul 2026 12:58:02 -0700 Subject: [PATCH] Size zram the way Fedora does zram-size = min(ram, 8192) is Fedora's default since F34, where zram-fraction=1.0 with max-zram-size=8192 has run on desktops for five years. The previous min(ram / 2, 8192) halved that on every machine below 16G with nothing behind the fraction; at 16G and above the two already agree. A changed size only reaches a running device on the next boot, so the migration resizes an empty device now and asks for a reboot otherwise. It asks the generator what the config evaluates to rather than repeating the expression here, and does neither when the device already matches. Co-Authored-By: Claude Opus 5 (1M context) --- .../zram-generator.conf.d/90-omarchy.conf | 17 ++- migrations/1785094500.sh | 38 ++++++ test/shell.d/zram-resize-test.sh | 109 ++++++++++++++++++ 3 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 migrations/1785094500.sh create mode 100644 test/shell.d/zram-resize-test.sh diff --git a/default/systemd/zram-generator.conf.d/90-omarchy.conf b/default/systemd/zram-generator.conf.d/90-omarchy.conf index d794ad16..77d1add7 100644 --- a/default/systemd/zram-generator.conf.d/90-omarchy.conf +++ b/default/systemd/zram-generator.conf.d/90-omarchy.conf @@ -1,10 +1,17 @@ -# Compressed swap in RAM. zstd averages around 3:1, so the worst case for a -# full device is roughly a third of the size below. +# Compressed swap in RAM, sized the way Fedora has shipped it since F34: all of +# memory, capped at 8G. Fedora weighed the worry that incompressible pages would +# tie up RAM against five years of desktops reporting no trouble from it, which +# is more field evidence than we could gather on our own. # -# The generator's own default caps this at 4G, which sends machines with a lot -# of memory to the disk swapfile far earlier than they need to go. +# The cap is what bounds the downside. A browser-heavy desktop can drive zstd +# down near 1.1:1, where the device backs far less than it advertises, and 8G +# keeps that bounded on any machine large enough to notice. The disk swapfile +# sits behind it at pri=0 for everything past that. +# +# Fedora spells this zram-fraction = 1.0 with max-zram-size = 8192; both keys +# are deprecated in favour of zram-size, which computes the same numbers. [zram0] -zram-size = min(ram / 2, 8192) +zram-size = min(ram, 8192) compression-algorithm = zstd # Above the pri=0 that omarchy-hibernation-setup gives the disk swapfile, so diff --git a/migrations/1785094500.sh b/migrations/1785094500.sh new file mode 100644 index 00000000..d858ab63 --- /dev/null +++ b/migrations/1785094500.sh @@ -0,0 +1,38 @@ +echo "Resize zram to match the shipped config" + +# 90-omarchy.conf changed the device size. The migration that first applied the +# zram tuning is one-shot, so machines that already ran it have the new file on +# disk and the old device still running; nothing would pick the size up until +# something else rebooted them. + +zram_disksize=${OMARCHY_ZRAM_DISKSIZE:-/sys/block/zram0/disksize} +swaps=${OMARCHY_SWAPS:-/proc/swaps} + +# The size is an expression, and the generator is the only thing that evaluates +# it. Ask it what the shipped config comes to rather than repeating the +# arithmetic here, where the two would drift apart on the next tuning change. +units=$(mktemp -d) +trap 'rm -rf "$units"' EXIT +desired_mb=$(/usr/lib/systemd/system-generators/zram-generator "$units" 2>&1 | + grep -oP '/dev/zram0 with \K[0-9]+') || true + +actual_bytes=$(cat "$zram_disksize" 2>/dev/null) || actual_bytes=0 + +# A device already at the right size needs neither a restart nor a reboot, +# whether an earlier boot picked the config up or another user got here first. +if [[ -n $desired_mb && $actual_bytes == $((desired_mb * 1024 * 1024)) ]]; then + exit 0 +fi + +if sudo systemctl daemon-reload; then + # Resizing swaps the device off first, which faults every stored page back + # into memory. That's only cheap while it's empty, so a device under + # pressure keeps its old size until the next boot. + zram_used=$(awk '$1 == "/dev/zram0" {print $4}' "$swaps") + + if [[ ${zram_used:-0} == 0 ]] && sudo systemctl restart dev-zram0.swap; then + exit 0 + fi +fi + +omarchy-state set reboot-required diff --git a/test/shell.d/zram-resize-test.sh b/test/shell.d/zram-resize-test.sh new file mode 100644 index 00000000..589c55aa --- /dev/null +++ b/test/shell.d/zram-resize-test.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh" + +require_command /usr/lib/systemd/system-generators/zram-generator + +migration=$(grep -rl 'Resize zram to match the shipped config' "$ROOT/migrations" | head -n 1 || true) +[[ -n $migration ]] || fail "zram resize migration exists" + +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT + +# The migration shells out to sudo, systemctl and omarchy-state. Stub all three +# so the test never touches the real system, and record what each run did. +stub_bin="$TMPDIR/bin" +mkdir -p "$stub_bin" + +cat >"$stub_bin/sudo" <<'STUB' +#!/bin/bash +exec "$@" +STUB + +cat >"$stub_bin/systemctl" <<'STUB' +#!/bin/bash +echo "systemctl $*" >>"$ACTIONS" +[[ ${SYSTEMCTL_FAIL:-0} == 1 ]] && exit 1 +exit 0 +STUB + +cat >"$stub_bin/omarchy-state" <<'STUB' +#!/bin/bash +echo "omarchy-state $*" >>"$ACTIONS" +STUB + +chmod +x "$stub_bin/sudo" "$stub_bin/systemctl" "$stub_bin/omarchy-state" + +# ZRAM_GENERATOR_ROOT redirects both the config search and /proc/meminfo, so the +# migration's own generator call resolves against this fixture instead of the +# host. 16G of RAM against the shipped config is a 8192MB device. +gen_root="$TMPDIR/genroot" +mkdir -p "$gen_root/usr/lib/systemd/zram-generator.conf.d" "$gen_root/proc" +cp "$ROOT/default/systemd/zram-generator.conf.d/90-omarchy.conf" \ + "$gen_root/usr/lib/systemd/zram-generator.conf.d/" +printf 'MemTotal: %d kB\n' $((16 * 1024 * 1024)) >"$gen_root/proc/meminfo" + +desired_bytes=$((8192 * 1024 * 1024)) + +# omarchy-migrate runs each migration with `bash -euo pipefail` and stops the +# whole chain on a non-zero exit, so match that invocation exactly. +run_migration() { + local disksize="$1" used="$2" fail_reload="${3:-0}" + + printf '%s' "$disksize" >"$TMPDIR/disksize" + printf 'Filename\tType\tSize\tUsed\tPriority\n' >"$TMPDIR/swaps" + [[ -n $used ]] && + printf '/dev/zram0 partition 8388604 %s 100\n' "$used" >>"$TMPDIR/swaps" + + : >"$TMPDIR/actions" + + PATH="$stub_bin:$PATH" \ + ACTIONS="$TMPDIR/actions" \ + SYSTEMCTL_FAIL="$fail_reload" \ + ZRAM_GENERATOR_ROOT="$gen_root" \ + OMARCHY_ZRAM_DISKSIZE="$TMPDIR/disksize" \ + OMARCHY_SWAPS="$TMPDIR/swaps" \ + bash -euo pipefail "$migration" >/dev/null || + fail "migration exits clean (disksize=$disksize used=$used reload_fail=$fail_reload)" +} + +did() { grep -qF "$1" "$TMPDIR/actions"; } + +# Already the size the config asks for: the device is correct however it got +# there, so the migration must not restart it or ask for a reboot. +run_migration "$desired_bytes" 4193612 +[[ -s $TMPDIR/actions ]] && fail "correctly sized device is left alone" \ + "expected no privileged calls, got: $(cat "$TMPDIR/actions")" +pass "correctly sized device is left alone" +pass "correctly sized device asks for no reboot" + +# Right size but still in use, and the reload never even happens. +run_migration "$desired_bytes" 0 +did "systemctl restart" && fail "correctly sized empty device is left alone" +pass "correctly sized empty device is left alone" + +# Wrong size and empty: safe to resize now. +run_migration $((4096 * 1024 * 1024)) 0 +did "systemctl restart dev-zram0.swap" || fail "empty device is restarted" +did "omarchy-state set reboot-required" && fail "empty device asks for no reboot" +pass "empty device is restarted" +pass "empty device asks for no reboot" + +# Wrong size with pages stored: resizing would fault them all back in, so defer. +run_migration $((4096 * 1024 * 1024)) 4193612 +did "systemctl restart" && fail "device in use is not restarted" +did "omarchy-state set reboot-required" || fail "device in use asks for a reboot" +pass "device in use is not restarted" +pass "device in use asks for a reboot" + +# No zram device at all reads as empty, and the restart brings it up. +run_migration "" "" +did "systemctl restart dev-zram0.swap" || fail "absent device is created" +pass "absent device is created" + +# A failed daemon-reload must fall back to asking for a reboot. +run_migration $((4096 * 1024 * 1024)) 0 1 +did "systemctl restart" && fail "failed reload does not restart" +did "omarchy-state set reboot-required" || fail "failed reload asks for a reboot" +pass "failed reload does not restart" +pass "failed reload asks for a reboot"