<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Swap · XB Field Notes</title><link>https://www.xbcatm.com/tags/swap/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/swap/index.xml" rel="self" type="application/rss+xml"/><item><title>Swap gives memory a slow extra layer</title><link>https://www.xbcatm.com/posts/swap-gives-memory-a-slow-extra-layer/</link><pubDate>Wed, 25 Nov 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/swap-gives-memory-a-slow-extra-layer/</guid><description>When physical memory is short, the kernel can park less-used pages on a swap device. The system keeps running, though at a latency much slower than RAM.</description><content:encoded>&lt;p&gt;Swap is not extra memory. It is a bet: the kernel picks pages it predicts will not be needed soon, writes them to a device, and frees the RAM. When the prediction holds, a spike passes without killing a process. When it does not, every wrong guess costs a device round trip, three to five orders of magnitude above reading RAM.&lt;/p&gt;
&lt;p&gt;The trade only works as a valve. Sizing swap to cover a chronically under-provisioned workload buys a machine that is slow in a way that looks like a CPU problem; the first question is how much of the working set does not fit.&lt;/p&gt;
&lt;h2 id="reclaim-picks-victims-by-prediction"&gt;Reclaim picks victims by prediction&lt;/h2&gt;
&lt;p&gt;Anonymous pages, the heap, the stack and anything &lt;code&gt;malloc&lt;/code&gt; handed out, have no file behind them, so they are the only thing swap holds. File-backed pages do not need swap: the kernel can drop a clean page-cache entry and read it back later. &lt;code&gt;vm.swappiness&lt;/code&gt; is documented as the rough relative IO cost of swapping versus filesystem paging, on a scale from 0 to 200 with a default of 60. At 0 the kernel will not initiate swap until free plus file-backed pages fall below the zone&amp;rsquo;s high watermark, so 0 means &amp;ldquo;only under duress&amp;rdquo;, not &amp;ldquo;no swap&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Internally the dial becomes a scan weight: &lt;code&gt;swappiness&lt;/code&gt; for the anonymous list, &lt;code&gt;200 - swappiness&lt;/code&gt; for the file list. Values above 100 are not a mistake: the documentation suggests 133 when random IO against the swap device is about twice as fast as filesystem IO, and recommends beyond-100 values for in-memory swap such as zram or zswap.&lt;/p&gt;
&lt;h2 id="the-price-is-a-device-round-trip"&gt;The price is a device round trip&lt;/h2&gt;
&lt;p&gt;Latencies tell the story: roughly 100 ns for a resident DRAM page, single-digit microseconds for compressed swap in RAM, 100 us on NVMe, 250 us on SATA SSD, 8 ms on a spinning disk. A swap-in is a thousand times a RAM hit on NVMe and eighty thousand times on a disk, and the bill is charged per page needed again.&lt;/p&gt;
&lt;p&gt;Model 100,000 page-sized accesses per second over an 8 GiB working set: fitting in RAM, the average is 100 ns with no major faults; at 2% over, 2.1 us, twenty-one times worse; at 20% over, 16.8 us, 167 times worse, with 16,667 major faults per second and 67 MB/s through &lt;code&gt;si&lt;/code&gt; and &lt;code&gt;so&lt;/code&gt;. The cliff begins at the first page that does not fit.&lt;/p&gt;
&lt;h2 id="thrashing-is-a-device-saturation-problem"&gt;Thrashing is a device saturation problem&lt;/h2&gt;
&lt;p&gt;The same 20% overcommit is unremarkable on compressed swap, absorbable on NVMe, and fatal on a spinning disk. A 7,200 rpm disk serves on the order of 150 random IOPS, so 16,667 faults/s cannot be met; the shortfall is never served and the wait queue grows until the OOM killer. This is thrashing: a queueing failure, not a slow-memory failure.&lt;/p&gt;
&lt;p&gt;zram and zswap change the cost model, not the principle. zram serves a fault from compressed RAM in microseconds instead of a device round trip, which is why the documentation suggests swappiness above 100. It expects roughly a 2:1 compression ratio, so making a device larger than twice memory buys little. The two sit in different places: zram is the swap device itself, with compressed pages staying in RAM; zswap is a compressed cache in front of a real swap device, evicting to it on an LRU basis when its pool hits its size limit. zram moves swap into memory; zswap only defers the write. Both make &lt;code&gt;/tmp&lt;/code&gt; a natural use case, where swap and temp directories stop being separate problems: both are RAM with a cap.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;Thrashing presents as a machine busy and idle at once. The load average climbs while CPU utilisation stays low, &lt;code&gt;si&lt;/code&gt; and &lt;code&gt;so&lt;/code&gt; climb, and major faults dominate. Every process progresses at a fraction of its normal rate while the box is not CPU-bound, which is why this gets misdiagnosed as an application problem. Confirm with &lt;code&gt;vmstat 1&lt;/code&gt;: sustained &lt;code&gt;si&lt;/code&gt;/&lt;code&gt;so&lt;/code&gt; in the thousands of KiB/s alongside a rising run queue and idle CPU is the signature.&lt;/p&gt;
&lt;p&gt;The second failure is swap that was never attached. swapon(8) documents that the kernel writes to the swap file directly, without the filesystem&amp;rsquo;s help, so a file with holes is rejected; &lt;code&gt;truncate&lt;/code&gt; and &lt;code&gt;cp&lt;/code&gt; both produce one. The same page documents &lt;code&gt;-e&lt;/code&gt;/&lt;code&gt;--ifexists&lt;/code&gt; and &lt;code&gt;nofail&lt;/code&gt;, which silently skip a device that is not there, so a host can boot with no swap, look healthy until the first spike, and OOM-kill something important. Read &lt;code&gt;/proc/swaps&lt;/code&gt; rather than trusting &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;swapon --show
free -h
vmstat 1 5
grep -E &amp;#39;pswpin|pswpout|pgmajfault&amp;#39; /proc/vmstat
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;swapon --show&lt;/code&gt; should list a device with a nonzero size; empty output means nothing is attached. In &lt;code&gt;vmstat&lt;/code&gt;, &lt;code&gt;si&lt;/code&gt; and &lt;code&gt;so&lt;/code&gt; should sit near zero; sustained thousands of KiB/s with &lt;code&gt;us&lt;/code&gt; plus &lt;code&gt;sy&lt;/code&gt; low is thrashing rather than load. A climbing &lt;code&gt;pgmajfault&lt;/code&gt; means pages are coming back from the device; a flat counter means the working set is resident.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/swapon.8.html"&gt;swapon(8)&lt;/a&gt;, swap priority, discard policies, and why a sparse swap file is rejected, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/vm.html"&gt;Kernel sysctl vm documentation&lt;/a&gt;, swappiness as a 0-200 relative IO cost dial with the zram guidance, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/admin-guide/blockdev/zram.html"&gt;Kernel zram documentation&lt;/a&gt;, compressed RAM block devices, the expected 2:1 ratio, and the /tmp use case, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/admin-guide/mm/zswap.html"&gt;Kernel zswap documentation&lt;/a&gt;, the compressed swap cache in front of a backing device and its LRU eviction, checked 2026-09-16.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>zram squeezes swap into RAM</title><link>https://www.xbcatm.com/posts/zram-squeezes-swap-into-ram/</link><pubDate>Tue, 24 Nov 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/zram-squeezes-swap-into-ram/</guid><description>Swap normally lives on a disk, but a fast compressed block device inside RAM can also back it. zram holds paged-out data, compressed, without touching a drive.</description><content:encoded>&lt;p&gt;When memory tightens, cold pages would go to a slow disk. zram keeps them in compressed RAM instead, trading a little CPU for much more headroom.&lt;/p&gt;
&lt;h2 id="a-compressed-block-device-in-memory"&gt;A compressed block device in memory&lt;/h2&gt;
&lt;p&gt;zram is a block device that lives in RAM and stores compressed data instead of talking to a real disk. When the kernel swaps a page into it, the page is compressed and kept in memory, so the memory gain comes from shrinking the pages rather than moving them to slow storage. Because the device is memory-backed, it answers reads and writes far faster than a disk-backed swap, but the space it provides is limited by how compressible the pages are and by how much memory you are willing to trade for the swap.&lt;/p&gt;
&lt;h2 id="compression-is-the-space-win"&gt;Compression is the space win&lt;/h2&gt;
&lt;p&gt;Compression is the space win, and it is the reason a small box can appear to hold more memory than it physically has. Whether a page pays off depends on its content: text, zeroed pages and structured data compress well, while already-compressed blobs such as media or archives barely shrink. The honest engineering view is that zram turns spare CPU cycles into apparent memory, so the trade is worthwhile when the workload leans compressible and the extra latency of a real disk swap is exactly what you want to avoid.&lt;/p&gt;
&lt;h2 id="it-suits-gove-boxes-and-phones"&gt;It suits gove boxes and phones&lt;/h2&gt;
&lt;p&gt;The fit shines on hosts with little memory and modest I/O, such as a small VPS, a container host, or a compact device like a phone or an embedded box. It is less obviously right for a server that already has plenty of RAM or that swaps into genuinely cold storage anyway, because a real disk bought with the same spend can hold far more. The practical check is to watch the compression ratio and swap usage, decide whether swapping beats killing the process, and pick zram when resident data stays small and compressible enough to make the CPU cost a fair trade.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;A ratio that never arrives is the commonest failure. Photos, video, encrypted buffers, and archives barely shrink, so &lt;code&gt;mm_stat&lt;/code&gt; shows &lt;code&gt;orig_data_size&lt;/code&gt; just above &lt;code&gt;compr_data_size&lt;/code&gt; while swap still fills. The pool and its metadata consume RAM yet add almost no headroom, so the OOM killer fires nearly as if zram were absent.&lt;/p&gt;
&lt;p&gt;The wrong compressor fails through CPU, not capacity. A heavy algorithm on a small VPS or old handset makes every swap-out burn cycles, so &lt;code&gt;kswapd&lt;/code&gt; spins, system CPU climbs with pressure, and tail latency collapses before swap fills. Prefer fast choices such as &lt;code&gt;lzo-rle&lt;/code&gt;, &lt;code&gt;lz4&lt;/code&gt;, or low-level &lt;code&gt;zstd&lt;/code&gt; on interactive hosts, and change only after measuring under load.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;zswap&lt;/code&gt; with zram compresses twice for no gain. &lt;code&gt;zswap&lt;/code&gt; holds its own compressed pool, then hands already-shrunk pages to zram, which finds little left to save. CPU burns on both paths while swap looks used with small effective capacity. Use one layer: zram as swap, or &lt;code&gt;zswap&lt;/code&gt; before disk, never both.&lt;/p&gt;
&lt;p&gt;A hard-coded &lt;code&gt;disksize&lt;/code&gt; goes stale after any resize. A unit that pins 2 GiB still offers 2 GiB after the instance grows to 32 GiB, wasting headroom; copied to a tiny edge box, the same file reserves half of RAM and hastens OOM. The size is fixed at &lt;code&gt;mkswap&lt;/code&gt; time, so express it as a fraction of RAM at boot.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Save the following as &lt;code&gt;zram_ratio.py&lt;/code&gt; and run &lt;code&gt;python3 zram_ratio.py&lt;/code&gt;. It uses only the standard library, opens &lt;code&gt;/sys/block/zram*/mm_stat&lt;/code&gt; read-only, and needs no privileges. Without those files it prints &lt;code&gt;SKIP&lt;/code&gt; and demonstrates the same ratio math on a small example instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Read-only zram compression-ratio check (stdlib only).&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;glob&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_mm_stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mm_stat needs at least orig and compr fields&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;orig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;compr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;: orig=&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt; compr=&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt; ratio=n/a (empty)&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;: orig=&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt; compr=&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt; ratio=&lt;/span&gt;&lt;span class="si"&gt;%.2f&lt;/span&gt;&lt;span class="s2"&gt; saved=&lt;/span&gt;&lt;span class="si"&gt;%.1f%%&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/sys/block/zram*/mm_stat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SKIP: no /sys/block/zram*/mm_stat found (zram absent? non-Linux?)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;worked example with the same formula:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;example/zram0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;PASS: ratio math works; on a zram host the live lines above replace the example&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;OSError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SKIP: unreadable &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;)&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_mm_stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;IndexError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SKIP: unparsable &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;)&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/mm_stat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;PASS: live mm_stat accounting shown; falling ratio plus full pool means resize or recompress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On a zram host it prints one line per device with original bytes, compressed bytes, the compression ratio, and memory used, so falling ratios and a full pool are visible before OOM. On macOS or a container without &lt;code&gt;zram*&lt;/code&gt; it prints &lt;code&gt;SKIP&lt;/code&gt; plus the worked example. The script passed on macOS without &lt;code&gt;/sys&lt;/code&gt;. It checks accounting and arithmetic only, not which algorithm or disksize is correct, so confirm those in the unit files before tuning.&lt;/p&gt;
&lt;h2 id="further-reading"&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/posts/swap-gives-memory-a-slow-extra-layer/"&gt;Swap gives memory a slow extra layer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/posts/oom-killer-makes-the-final-memory-call/"&gt;The OOM killer makes the final memory call&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/blockdev/zram.html"&gt;kernel.org: zram block device&lt;/a&gt;, the authoritative zram documentation, checked 2026-11-23.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Zram"&gt;Wikipedia: zram&lt;/a&gt;, what zram is and how it works, checked 2026-11-23.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/title/Zram"&gt;ArchWiki: Zram&lt;/a&gt;, practical setup and tuning, checked 2026-11-23.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>