What's Eating My Swap? Introducing `swap_report.py`!
Hey everyone,
Following up on yesterday's post about the server mayhem and adding swap space to help buffer against the OOM killer, I ran into a common question: "Okay, swap is being used, but what exactly is using it?"
While tools like top
or htop
can show overall swap usage, getting a clear, process-by-process breakdown specifically for swap isn't always straightforward. So, as is often the case, if I can't easily find the tool I want, I tend to build it!
Introducing swap_report.py
This little Python script, swap_report.py
, is designed to do one thing: scan through your running processes and show you which ones are using swap space, how much they're using, and provide a nice, clean report. It uses the rich
library for some pretty console output.
How It Works & Features
At its core, the script:
- Iterates through the process IDs in
/proc
. - For each process, it reads the
/proc/[pid]/smaps
file, which contains detailed memory mapping information, and sums up the lines starting with "Swap:" to get the total swap used by that process. - It filters out processes that aren't using any swap.
- It then displays this information in a table, showing the PID, human-readable swap amount (kB, MiB, GiB), the user running the process, and the process command itself.
- It also gives you a total swap usage for the listed processes.
Some of the key features include:
- Sorting the output by swap usage (default), PID, or process name.
- Filtering by a specific user.
- Filtering by a process name using a regular expression.
- Human-readable swap sizes (e.g., "4.03 MiB" instead of just kilobytes).
- Optional color-coded output for high swap usage.
- Ability to output the report to an HTML file for easier sharing or logging.
Here's a Peek:
This is what the output looks like in the terminal:
The Code:
Also available from this GitHub Gist
The script is designed to be run with uv run --script
, which handles the dependencies like rich
.
#!/usr/bin/env -S uv run --quiet --script
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "rich",
# ]
#
# ///
"""
swap_report.py - See how swap is being used by what processes
Python version by: Michael Garcia <[email protected]> 2025-06-04
"""
import argparse
import os
import pwd
import re
from collections import namedtuple
from datetime import datetime
from rich import box
from rich.console import Console
from rich.table import Table
from rich.text import Text
Process = namedtuple("Process", ["pid", "swap_kb", "user", "cmd"])
def human_readable(kb): #
if kb >= 1048576:
return f"{kb / 1048576:.2f} GiB"
elif kb >= 1024:
return f"{kb / 1024:.2f} MiB"
else:
return f"{kb} kB"
def get_processes(user_filter=None, name_filter=None): #
processes = []
for pid in filter(str.isdigit, os.listdir("/proc")): #
smaps_path = f"/proc/{pid}/smaps" #
if not os.path.exists(smaps_path): #
continue
try:
with open(smaps_path) as f: #
swap_kb = sum( #
int(line.split()[1]) for line in f if line.startswith("Swap:") #
)
if swap_kb == 0: #
continue
except Exception:
continue
try:
user = pwd.getpwuid(int(os.stat(f"/proc/{pid}").st_uid)).pw_name #
except Exception:
user = "?"
if user_filter and user != user_filter: #
continue
try:
with open(f"/proc/{pid}/cmdline", "rb") as f: #
cmd = f.read().replace(b"\0", b" ").decode().strip() #
if not cmd: #
with open(f"/proc/{pid}/comm") as f2: #
cmd = f2.read().strip() #
except Exception:
cmd = "?"
if name_filter and not re.search(name_filter, cmd): #
continue
processes.append(Process(pid, swap_kb, user, cmd)) #
return processes
def color_for_swap(kb): #
if kb >= 1048576: #
return "bold white on red" #
elif kb >= 102400: #
return "bold black on yellow" #
else:
return "" #
def main(): #
parser = argparse.ArgumentParser(description="Show swap usage by process.") #
parser.add_argument( #
"-n", "--num", type=int, default=30, help="Show top N processes (default: 30)"
)
parser.add_argument( #
"-s",
"--sort",
choices=["swap", "pid", "name"],
default="swap",
help="Sort by (swap, pid, name)",
)
parser.add_argument("-u", "--user", help="Filter by user") #
parser.add_argument("-p", "--pattern", help="Filter by process name (regex)") #
parser.add_argument("-o", "--output", help="Output to file") #
parser.add_argument("--no-color", action="store_true", help="Disable color output") #
args = parser.parse_args() #
procs = get_processes(user_filter=args.user, name_filter=args.pattern) #
if args.sort == "swap": #
procs.sort(key=lambda p: p.swap_kb, reverse=True) #
elif args.sort == "pid": #
procs.sort(key=lambda p: int(p.pid)) #
elif args.sort == "name": #
procs.sort(key=lambda p: p.cmd.lower()) #
console = Console(record=bool(args.output)) #
table = Table( #
title=f"Swap Usage Report - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", #
box=box.SIMPLE_HEAVY, #
)
table.add_column("PID", justify="right", style="cyan", no_wrap=True) #
table.add_column("Swap", justify="right", style="magenta") #
table.add_column("User", style="green") #
table.add_column("Process", style="white") #
total_swap = 0 #
for proc in procs[: args.num]: #
swap_str = human_readable(proc.swap_kb) #
row_style = color_for_swap(proc.swap_kb) if not args.no_color else "" #
table.add_row(str(proc.pid), swap_str, proc.user, proc.cmd, style=row_style) #
total_swap += proc.swap_kb #
console.print(table) #
summary = Text( #
f"Total swap used by listed processes: {human_readable(total_swap)}", #
style="bold", #
)
console.print(summary) #
if args.output: #
console.save_html(args.output) #
if __name__ == "__main__": #
main() #
How to Use It:
- Save the code above as
swap_report.py
. - Make it executable:
chmod +x swap_report.py
- Run it:
- Basic report (top 30 processes by swap usage):
./swap_report.py
- Show top 10 processes:
./swap_report.py -n 10
- Sort by PID:
./swap_report.py -s pid
- Filter by user "thecrazygm":
./swap_report.py -u thecrazygm
- Filter for processes matching "python":
./swap_report.py -p python
- Output to an HTML file:
./swap_report.py -o swap_usage.html
- Basic report (top 30 processes by swap usage):
It's a simple but effective little utility for getting a better handle on what's going on with your system's swap space. Hope it's useful for others too!
As always,
Michael Garcia a.k.a. TheCrazyGM
The utility is simply astounding.
!PAKX
!PIMP
!PIZZA
View or trade
PAKX
tokens.Use !PAKX command if you hold enough balance to call for a @pakx vote on worthy posts! More details available on PAKX Blog.
$PIZZA slices delivered:
@ecoinstant(1/20) tipped @thecrazygm
Come get MOONed!
Dude, this is fantastic! This is a tool that I'd use evety day! I can't wait to take it for a spin! I am so glad that I follow you! Thank you! 😁 🙏 💚 ✨ 🤙
You are welcome. Hearing things like that give me the warm fuzzies.
I'm happy to hear that. I tried it this morning, and it works great! I like the color coding for high-memory use. 😁 🙏 💚 ✨ 🤙