# Processes

Canonical: https://plottypus.com/docs/processes

Last updated: 2026-09-23

> The process table: per-core CPU like Activity Monitor, footprint memory, why some rows show a dash, sorting, tree, filter, and signals that cannot hit the wrong pid.

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 `—`](https://plottypus.com/docs/processes#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`.
