Frontend Labs
⋯

36. Reading /proc: build your own ps

Linux exposes every process as a directory under /proc (OS Ch 23). ps, top, and psutil read these files. So can you:

# labs/pyps.py: a minimal `ps aux` from /proc
import os
from dataclasses import dataclass
from pathlib import Path

CLK_TCK = os.sysconf("SC_CLK_TCK")
PAGE = os.sysconf("SC_PAGE_SIZE")

@dataclass(slots=True)
class Proc:
    pid: int
    user: str
    state: str
    rss_mb: float
    cpu_s: float
    threads: int
    cmd: str

def users() -> dict[int, str]:
    out = {}
    for line in Path("/etc/passwd").read_text().splitlines():
        parts = line.split(":")
        if len(parts) > 2:
            out[int(parts[2])] = parts[0]
    return out

def read_proc(pid: int, names: dict[int, str]) -> Proc | None:
    base = Path("/proc") / str(pid)
    try:
        stat = (base / "stat").read_text()
        # comm is in parentheses and may contain spaces: split around the LAST ')'
        rest = stat[stat.rindex(")") + 2 :].split()
        state, utime, stime = rest[0], int(rest[11]), int(rest[12])
        threads, rss_pages = int(rest[17]), int(rest[21])
        uid = int((base / "status").read_text().split("Uid:")[1].split()[0])
        cmd = (base / "cmdline").read_bytes().replace(b"\0", b" ").decode(errors="replace").strip()
        if not cmd:
            cmd = "[" + stat[stat.index("(") + 1 : stat.rindex(")")] + "]"   # kernel thread
    except (FileNotFoundError, ProcessLookupError, PermissionError):
        return None                                   # process exited while we read it
    return Proc(pid, names.get(uid, str(uid)), state, rss_pages * PAGE / 2**20,
                (utime + stime) / CLK_TCK, threads, cmd)

def main() -> None:
    names = users()
    procs = [p for d in os.listdir("/proc") if d.isdigit() if (p := read_proc(int(d), names))]
    procs.sort(key=lambda p: p.rss_mb, reverse=True)
    print(f"{'PID':>7} {'USER':<10} S {'RSS MB':>8} {'CPU s':>8} {'THR':>4}  COMMAND")
    for p in procs[:15]:
        print(f"{p.pid:>7} {p.user:<10} {p.state} {p.rss_mb:8.1f} {p.cpu_s:8.1f} {p.threads:>4}  {p.cmd[:60]}")

if __name__ == "__main__":
    main()

What to notice:

  • Races are normal. Processes exit between listdir and read_text, so every read handles FileNotFoundError. This is EAFP (Ch 7.2) for real.
  • The process name in /proc/PID/stat can contain spaces and ), so parsing must split on the last ). Real ps implementations had bugs here; a malicious process can name itself to break naive parsers.
  • In practice, use psutil, which does this portably on Linux, macOS, and Windows. Write it by hand once to know what it reads.

Real-world example: a training-job watchdog that reads /proc/self/status (VmRSS) and the cgroup's memory.max and memory.current (/sys/fs/cgroup/) to log how close the job is to an OOM kill (OS Ch 13), so the next crash comes with evidence.


Generated from real-life-python-guide.md · 8 Oct 2026 16:36