psmon

August 13, 2026 · View on GitHub

CI License: MIT

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PID     PPID USER     %CPU  %MEM      TIME  COMMAND
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2201    2176 dumidu   51.6  1.5       48:07 opera
8110    8046 dumidu   47.5  5.0       09:20 chrome
8073    8043 dumidu   23.5  1.2       09:20 chrome
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Sorted by: %cpu | Showing: 3 | Excluded: (ps|ps)
Press 'q' to quit, 'c'=CPU, 'm'=Memory, 't'=Time

A fast, colorful, interactive process monitor built on top of ps. No ncurses dependency, no compiled binary: just zsh and standard Linux utilities.

Linux only. psmon relies on GNU ps flags and reads /proc directly, so it does not work on macOS or BSD. See Requirements.

Why psmon?

top and htop are great, but sometimes you just want:

  • A quick colorized snapshot of the heaviest processes, straight in your normal terminal scrollback
  • A simple live-refreshing view you can switch sort order on with a keypress
  • A one-shot way to trace a process's ancestry, or peek at its open file descriptors

psmon combines ps, awk, and other standard Linux utilities into a single small zsh utility.

Features

psmon

  • 🎨 Colorized output: CPU-heavy processes highlighted in yellow/red
  • 📊 Sort by CPU, memory, elapsed time, PID, or user
  • 🔴 Live watch mode with in-place key-driven sort switching (c/m/t/q)
  • 🔍 Process ancestry / tree view: trace a PID up to init and list its children (The summary shows the full, unredacted command line, so be mindful when sharing output)
  • 📂 File descriptor inspector (via lsof) with type-coded coloring and a summary
  • 💀 Interactive kill mode: list, pick a PID, confirm, done (SIGTERM or SIGKILL)
  • 📦 Sandbox-Aware Kill: traces Flatpak/Bubblewrap containerized apps to their root and terminates the connected process tree.
  • 🌐 PWA & Webapp Tagging: automatically extracts URL and Class arguments to help you distinguish between isolated browser instances.
  • 🚫 Exclude processes by pattern (regex-friendly, e.g. -e 'opera|msedge')
  • ✅ No dependencies beyond zsh, ps, awk, and (optionally) lsof

Requirements

  • Linux. psmon depends on GNU-specific ps flags (--sort, --ppid, -o field= for header suppression) that don't exist on BSD/macOS ps, and psmon blame reads /proc/loadavg, /proc/meminfo, /proc/net/dev, and /proc//io directly along with GNU nproc, none of which exist on macOS. There's no macOS/BSD support and none planned.

  • zsh

  • ps (GNU procps, standard on Linux)

  • lsof (optional, only needed for -d/file descriptor mode)

Installation

curl -fsSL https://raw.githubusercontent.com/dumidusw/psmon/main/install.sh | zsh

This installs psmon to ~/.local/share/psmon, symlinks the executable into ~/.local/bin/psmon, and installs zsh tab-completion.

Manual install

git clone https://github.com/dumidusw/psmon.git ~/.local/share/psmon
ln -s ~/.local/share/psmon/psmon ~/.local/bin/psmon
chmod +x ~/.local/bin/psmon

Make sure ~/.local/bin is on your $PATH:

export PATH="$HOME/.local/bin:$PATH"

As a zsh plugin

If you use a plugin manager, psmon ships an optional psmon.plugin.zsh wrapper. (Linux only, see Requirements; installing via a plugin manager on macOS will not work.)

zinit

zinit light dumidusw/psmon

oh-my-zsh

git clone https://github.com/dumidusw/psmon.git $ZSH_CUSTOM/plugins/psmon

then add psmon to the plugins=(...) list in ~/.zshrc.

Uninstall

~/.local/share/psmon/install.sh --uninstall

Usage

psmon [OPTIONS]
psmon blame [-l N]
FlagDescription
-cSort by CPU (default)
-mSort by memory
-tSort by elapsed time
-pSort by PID
-uSort by user
-l NShow N lines (default: 16, or 5 for blame)
-e PATTERNExclude processes matching PATTERN (regex, |-joinable)
-wWatch mode, live updates
-i NUpdate interval in seconds with -w (default: 2)
-f PIDShow ancestry/tree view for a PID
-d PIDShow open file descriptors for a PID (needs lsof)
-kInteractive kill mode, prompts before sending SIGTERM
-KInteractive kill mode, prompts before sending SIGKILL
-hShow help

Sort direction: -c, -m, and -t sort descending (heaviest / most recent first). -p and -u sort ascending (numeric / alphabetical order).

Note on -e: the pattern matches against the entire process line: PID, PPID, user, and command, not just the command name. So -e dumidu excludes every process owned by that user, not only processes literally named dumidu.

SubcommandDescription
blameQuick "why is my machine slow" diagnostic summary

Examples

psmon                       # Top 16 processes by CPU
psmon -m                    # Top 16 processes by memory
psmon -l 10                 # Top 10 processes by CPU
psmon -e snap               # Exclude snap processes
psmon -e 'opera|msedge'     # Exclude multiple patterns
psmon -w                    # Live view, refreshes every 2s
psmon -w -i 5               # Live view, refreshes every 5s
psmon -m -e opera           # Top memory users, excluding opera
psmon -f 2182                # Process tree / ancestry for PID 2182
psmon -f 1                   # Full ancestry from init
psmon -d 2182                 # File descriptors for PID 2182
psmon -d 2182 -l 20            # First 20 file descriptors
psmon -k                        # Interactive kill mode (SIGTERM)
psmon -K -m                     # Interactive kill mode (SIGKILL), sorted by memory
psmon blame                     # Quick "why is my machine slow" summary
psmon blame -l 8                # Same, but 8 rows per section

psmon blame: quick diagnostic summary

psmon blame

psmon blame

For when something feels slow and you just want the likely culprit, fast. Shows:

  • Load average (1/5/15 min) with a plain-language read against your core count
  • Swap usage, with a warning if it's heavily in use (a common sign of memory pressure)
  • Memory usage (based on MemAvailable, not raw free RAM, so it reflects real memory pressure rather than reclaimable disk cache), with a warning as it climbs
  • Disk usage across real filesystems, flagging any that are nearly full
  • Network throughput: aggregate download/upload rate, sampled live across all non-loopback interfaces
  • Top CPU processes
  • Top Memory processes
  • Top I/O processes: cumulative disk bytes read+written since each process started (from /proc/<pid>/io)

I/O stats are best-effort: Linux only exposes /proc/<pid>/io for your own processes unless you're root, so on a shared or multi-user system you may see fewer I/O rows than CPU/memory rows. Everything else works without root.

Interactive kill mode

psmon interactive kill mode

Example: resolving a name to multiple PWA instances, then a full sandbox tree.

psmon PWA and sandbox kill example

psmon -k

Shows a compact PID %CPU %MEM COMMAND list. Enter either:

  • a PID: psmon resolves it to the main process of that app by walking up the parent chain.

  • a process name (e.g. brave): psmon finds every matching process and groups them by their application instance.

Advanced sandbox & webapp handling: Modern Linux apps are complicated. Chromium-based browsers spawn dozens of renderers and crashpad handlers, and Flatpak wraps everything in isolated bwrap containers. psmon handles this automatically:

  • Container Grouping: If a process is running inside a Flatpak or Bubblewrap sandbox, psmon traces the tree all the way up to the structural container root.

  • Container Tree Termination: Confirming a kill sends the signal to the entire container tree, so hidden zygotes, renderers, and background workers are terminated along with the process you targeted.

  • PWA Disambiguation: If you have multiple independent webapps running (e.g., a YouTube PWA and a standard browsing session), psmon parses the command arguments and tags them in the UI (e.g., [App: https://youtube.com]) so you know exactly which instance you are targeting.

You will see the resolved target and the total number of connected processes, followed by a single confirmation:

Send SIGTERM to 18 process(es)? [y/N]
psmon -K

Same flow, but sends SIGKILL, useful when something is ignoring SIGTERM.

-k vs -K: Try -k (SIGTERM) first; it asks the process to shut down, giving it a chance to save state and exit cleanly. Some apps, especially Electron/Node-based ones, don't act on SIGTERM right away (or at all) if they're mid-cleanup or waiting on something else, in which case the process will still show up when you check again. -K (SIGKILL) is the fallback for that: it's handled by the kernel directly and can't be caught, delayed, or ignored, so it always terminates immediately. That also means it skips whatever graceful shutdown/state-save the app would normally do, so treat it as a last resort rather than a default.

Safety notes:

  • Always confirms before sending anything; nothing happens from typing a PID/name alone.
  • Refuses to signal PID 1 (init) or psmon's own process/shell, and bails out entirely if the resolved tree contains any protected PID.
  • After signaling, re-checks each PID (polling for up to ~2s) and reports how many actually terminated (correctly treating zombie processes as terminated, since a zombie is already dead and just awaiting reap by its parent).

Known quirk: For heavy multi-process apps (browsers especially), a full graceful shutdown can occasionally take a bit longer than the ~2s poll window + list refresh. If the very next screen inside kill mode still shows one of the processes you just killed, that's usually just teardown finishing a moment late; it's already gone by the time you check again with a fresh psmon invocation a few seconds later. This is cosmetic and doesn't affect whether the kill actually worked.

Interactive keys (watch mode)

KeyAction
qQuit
cSwitch to CPU sort
mSwitch to memory sort
tSwitch to time sort

Project structure

psmon/
├── psmon                  # entrypoint: arg parsing + dispatch
├── lib/
│   ├── display.zsh         # colors, help text, process list rendering
│   ├── watch.zsh            # live watch-mode loop
│   ├── descriptors.zsh       # lsof-based file descriptor inspector
│   ├── tree.zsh                # process ancestry / tree view
│   ├── kill.zsh                 # interactive kill mode
│   └── blame.zsh                 # quick diagnostic summary (`psmon blame`)
├── completions/
│   └── _psmon                  # zsh tab-completion
├── psmon.plugin.zsh              # optional plugin-manager wrapper
└── install.sh                     # installer / uninstaller

The entrypoint resolves its own real path (following symlinks), so it works whether you run it directly, symlink it into ~/.local/bin, or load it via a plugin manager, as long as lib/ sits alongside the real psmon file.

Contributing

Issues and PRs welcome. A few ideas if you're looking for something to add:

  • Additional sort fields (e.g. -o for open files count)
  • A --json output mode for scripting
  • Per-user filtering (-U username)

License

MIT. See LICENSE.