Reading the screen

Processes

The process table is the right side of the Work surface. It reads every process through Mach and sysctl directly: no ps, no top, no shelling out.

text
╭ proc  22 ──────────────────────────────────────────────────────────────[↗]╮
│ / search / to filter                                                      │
│     pid  name                user         cpu%↓    gpu%       mem    power│
│▌    412  WindowServer        _windows…      9.8     1.2      412M     0.1W│
│    2240  Safari Web Content  dev            7.2     1.6      402M     0.1W│
│    4040  rust-analyzer       dev            6.4     0.0      2.5G     0.1W│

That capture is from plottypus --demo, whose canned processes are all readable. On a real Mac, see why a row shows .

Columns#

pid, name, cpu% and mem are always there (on a very narrow pane pid, then mem, make room for the name). The rest appear only when some process has a value for them, and give up their cells first as the pane narrows.

Column Shows Appears when
pid process id always
name display name, see below always
user owner any process has one
state stopped or zombie something is stopped or a zombie
cpu% per-core CPU percent always
trend a small braille graph of recent CPU the table is at least 96 columns wide
gpu% share of GPU time any process uses the GPU
mem physical footprint always
rss resident set size wide tables
thr thread count the threads setting is on
power per-process energy, in watts you are on battery
read, write disk bytes per second wide tables
wake/s wakeups per second wide tables
started start time wide tables

The title shows how many processes are listed. When you scroll the list with the mouse wheel, it also shows how far: proc 834 ↑ 40.

CPU is per-core, like Activity Monitor#

cpu% uses the same unit as Activity Monitor and top: 100 means one fully busy core, so a parallel build can read 800. If you prefer 100% to mean the whole chip, set proc.cpu_unit = "total" (settings → processes → cpu percent).

A process seen for the first time gets its CPU time divided by its age, so a 300 ms old rustc that has used 290 ms reads about 97 instead of starting at 0.

Memory#

mem is the physical footprint (ri_phys_footprint from proc_pid_rusage), the figure top and Activity Monitor’s Memory column use. RSS disagrees with both in either direction, so it only appears as its own rss column and in the details card.

Names#

Interpreters and compilers are named after what they are running, not the binary. python -m http.server, node server.js, rustc <crate> and java -jar app.jar show the script, module, crate or jar. When a process calls exec() and becomes something else, its name updates within one sample.

Why a row shows #

macOS lets an ordinary user read CPU, memory, threads and energy only for their own processes. For anything owned by another user (root, _windowserver, _coreaudiod and friends) the kernel answers proc_pidinfo and proc_pid_rusage with EPERM. On a typical Mac that is around a third of all processes; plottypus doctor prints the exact count for yours:

text
[processes]
readable     608 of 904 (296 need root)

Those rows keep their name, pid and owner, but their numbers show rather than a made-up 0.0. They sort last whichever direction you sort. Their CPU is still counted in the machine total at the top of the cpu panel.

If you want those numbers, run it as root:

sh
sudo plottypus

Nothing else changes. plottypus never asks for root and does not need it for anything else.

Per-process GPU is the exception: it comes from the IORegistry, which anyone can read, so gpu% fills in for every process.

Sorting#

  • Click a column header to sort by it; click again to reverse. The arrow shows the direction (cpu%↓, pid↑).
  • > or o steps to the next sort column, < to the previous one, r reverses.
  • Sortable: cpu, mem, pid, name, user, state, gpu, rss, threads, power, read, write, wakeups, started. The default is cpu, highest first; the choice is saved as proc.sort.

Tree#

t nests every process under its parent with real tree guides. Siblings sort by the current column. Save it as the default with proc.tree = true.

Filter#

/ or f, then type. The filter matches, without regard to case, the process name, the full command line, and the pid when you type digits. The list narrows as you type.

While filtering, and move the selection without leaving the filter, enter opens the selected process, delete clears the text and esc clears it, then leaves.

Details#

enter or a double-click opens the details card for the selected process:

  • identity: name, owner, state, uptime, pid and parent;
  • live: CPU, memory, threads and a CPU bar;
  • command: the full command line;
  • cpu: the process’s own CPU graph over the last 15 minutes;
  • family: its children.

j/k or / move to the next or previous process without closing the card. Everything the card shows is updated each sample.

Actions and signals#

From the table or the details card:

Key Action
x quit: send TERM, after a y/n confirmation
K force quit: send KILL, after a confirmation
i interrupt: send INT (details card)
X open the signal picker
+ / - nice up (lower priority) / nice down (raise priority), details card
b move the process into the Darwin background band and back: throttled CPU and I/O (details card)
y / Y copy the pid / the command line (details card)

The signal picker lists TERM, INT, HUP, QUIT, STOP, CONT, USR1, USR2 and KILL, each with a one-line description (STOP pause the process (freeze it in place)). The ones that end or freeze a process ask for confirmation; CONT and USR1/USR2 do not.

Why a signal cannot hit the wrong process#

A pid can be reused the moment a process exits. plottypus remembers the process’s start time when you choose it, and checks it again right before sending. If the process has exited, the action is cancelled (already exited). If the pid now belongs to a different process, it refuses (pid now belongs to another process; not signalled).

Before arming anything, it also checks permission with kill(pid, 0), which delivers nothing. If you do not own the process, the picker says owned by root: needs admin and nothing is armed.

Critical processes#

Signalling launchd, kernel_task, WindowServer, loginwindow, logd, or pid 0 or 1 shows a warning in the picker: WindowServer is critical: ending it can log you out. It is a warning, not a lock. Most of these belong to root or a system user, so without sudo the kernel refuses anyway. loginwindow runs as you, and ending it logs you out; that is the case the warning is for.

Nice and background#

Raising a process’s nice value (lower priority) works on your own processes. Lowering it below its current value needs root, and plottypus says so instead of failing silently. Copy uses OSC 52: the terminal puts the text on your clipboard, over ssh too. Ghostty, iTerm2 and kitty support it where clipboard access is allowed, and tmux passes it on with set-clipboard on.