# metop
[](https://github.com/baka-world/metop/actions/workflows/ci.yml)
[](https://github.com/baka-world/metop/actions/workflows/release.yml)
[](LICENSE)
[](https://crates.io/crates/metop)
[](https://docs.rs/metop)
**English** · [简体中文](README.zh-CN.md)
A Linux memory analysis TUI. `htop` answers "how much RSS does each process have"; metop answers **"who is actually using the memory, and on what"** — with PSS/USS, per-mapping drill-down, cgroup accounting and an app-level treemap.

## Why PSS
RSS counts every shared page once per process, so any multi-process app looks inflated. Measured on one desktop at the same instant:
| RSS summed | 4.1 GB | 11.9 GB |
| **PSS** | **1.7 GB** | **5.5 GB** |
| overcount | 2.4× | 2.2× |
RSS sums to roughly **2.2×** the real number system-wide (2.4× for Chrome). metop uses PSS for ranking and area, USS for "what would I free if I killed it", and shows the composition (anonymous / file / shmem / swap) so you can tell a heap leak from page cache.
## Features
- **Process view** — RSS / PSS / USS / PSS÷RSS / Δ·s⁻¹, share bars and a theme-coloured PSS-composition bar (legend in the process view).
- **VMA detail** — aggregate by mapping, then expand one mapping into its individual VMAs (address range, perms, PSS/USS/anon/swap/size).
- **cgroup v2 view** — `memory.current` / `memory.stat` / `memory.max` / `memory.events`, PSI (`some`/`full`), OOM deltas, and drill-down into a subtree's processes.
- **App treemap** — processes grouped into applications (systemd `app-*.scope` → exe → comm), area ∝ PSS, long tail folded into `others`.
- **Scriptable** — `--json` (with `--fields` projection), `--diff` (with `--min-delta` / `--top`), stable pid ordering for diffing.
- **Mouse** — click a table row or treemap tile to select, click it again to open/expand/drill, wheel to scroll, and click the footer buttons (help, theme, language, view, sort, filter, enter, back, quit).
- **Bilingual help** — `?` opens page-specific help for processes, cgroups, apps, VMA detail and drill-down; `--lang auto|en|zh`, `METOP_LANG`, or `l` switches Chinese/English.
- **Themes** — seven palettes, including btop-derived `btop`, `dracula`, `gruvbox-dark` and `kanagawa-wave`; pick one with `--theme`, `METOP_THEME`, or `t` while running (no config file). Themes are authored in truecolor and auto-rendered to 256 or 16 colours when the terminal cannot display RGB; `--color` overrides detection.
## Install
Prebuilt packages are produced by GitHub Actions for `amd64` and `arm64`; every asset ships with a SHA256 checksum.
| Debian / Ubuntu | `metop_0.4.0-1_amd64.deb` · `metop_0.4.0-1_arm64.deb` |
| Fedora / RHEL / openSUSE | `metop-0.4.0-1.x86_64.rpm` · `metop-0.4.0-1.aarch64.rpm` |
| Any distro | `metop-v0.4.0-x86_64-unknown-linux-musl.tar.gz` · `…-aarch64-…` |
```sh
# Debian / Ubuntu
gh release download v0.4.0 --repo baka-world/metop --pattern '*.deb'
sudo dpkg -i metop_0.4.0-1_amd64.deb
# Fedora / RHEL
gh release download v0.4.0 --repo baka-world/metop --pattern '*.rpm'
sudo rpm -i metop-0.4.0-1.x86_64.rpm
# From crates.io (installs the `metop` binary)
cargo install metop
# From source
cargo build --release && sudo install -m755 target/release/metop /usr/local/bin/
```
Binaries are statically linked (musl), and the `.deb` has an empty `Depends:`.
## Usage
```
metop # whole system
metop --pid 1234 # start with a process selected
metop --color 256 # auto | truecolor | 256 | 16
metop --lang zh # auto | en | zh (or METOP_LANG / l)
metop --json --fields procs.pid,procs.rss_kb
metop --diff a.json b.json --min-delta 50M
```
| `Tab` | cycle views: processes → cgroups → apps |
| `j`/`k`, `↑`/`↓`, `PgUp`/`PgDn` | move |
| `g`/`G` | top / bottom |
| `s` / `r` | cycle sort key (apps: metric) / reverse |
| `t` | cycle colour theme (7 built-in palettes) |
| `?` | page help (header, columns, markers, keys) |
| `l` | switch UI language (English / 中文) |
| Mouse | click rows/tiles to select, click again to open or drill; wheel scrolls; footer buttons are clickable |
| `f` | filter (process name, pid, cgroup path, mapping name) |
| `Enter` | detail / expand mapping / drill into a cgroup or an app |
| `Esc` | clear filter, leave detail or drill-down |
| `q` | quit |
### Help and i18n

Press `?` for a page-specific explanation of the header, columns, colour codes, markers and keys. `--lang auto|en|zh`, `METOP_LANG` or `l` switch between English and Chinese; the footer buttons are clickable. Screenshots use the `dracula` theme.
### Process view

The header reports the sampler's own cost (`sweep`/`pss` ms, PSS coverage, sample age) so partial data is never presented as complete. Rows with `n/a` need privileges; a dimmed `~` marks a cached value older than the freshness window. The footer is a clickable command bar and the PSS mix legend only appears here.
### VMA detail

`Enter` on a process aggregates its mappings by name; `Enter` again expands one group into individual VMAs. `f` filters mapping names, `z`/`Z` collapse or expand everything.
### cgroup v2

Values are hierarchical: a parent already includes its children, so the list sorts by `current` instead of summing levels. `Enter` lists the processes in that subtree:

### Apps treemap

Area is PSS by default; `s` switches to USS (private only) or RSS (which overcounts shared pages, and says so in the title). Tiles use the active theme's bright palette, with dark labels; `Enter` drills into that app's processes.
## Scripting
```sh
metop --json > a.json # one snapshot, stable pid order
metop --json --fields procs.pid,procs.rss_kb > small.json # 194 KB -> 23 KB
sleep 30; metop --json > b.json
metop --diff a.json b.json --min-delta 50M
```
`--diff` prints ΔRSS / ΔPSS, current values and `new`/`gone` markers; the snapshot writer itself is excluded via `self_pid`.
## Requirements and permissions
- Linux with `/proc`; cgroup v2 optional (the view explains its absence).
- `/proc/PID/smaps_rollup` requires ptrace-read permission: a normal user only sees their own processes (and kernel threads have no address space at all). Run `sudo metop` for full coverage — verified at `264/264` covered, `denied=0`.
- The usage model is documented in [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md); the root checklist is [docs/VERIFY-ROOT.md](docs/VERIFY-ROOT.md).
## Known issues
- **Counts are partial without privileges.** Non-root runs only cover your own user's processes; kernel threads never have a PSS and are excluded from coverage denominators.
- **Two history windows.** Process/apps keep 120 samples at 1 Hz (2 min); cgroup/PSI keep 120 samples at the 5 s cadence (10 min). Comparing the two trends side by side is misleading.
- **App grouping falls back to the executable.** Without a systemd `app-*.scope`, two Chromium installs merge into one tile; `others (N)` is synthetic and cannot be drilled into.
- **cgroup v1 is unsupported** — the view states why instead of showing zeros.
- **Sampling perturbs the target.** Reading `smaps` walks page tables under the mmap lock, so walks are budgeted (150 ms/tick) and the detail view is on-demand.
## TODO
- Configurable history window (`--history 600`), so all views share one time axis.
- Include app groups in `--json`.
- Optional: KSM / hugepage / pagemap metadata per VMA (via `wora-observability-linux-mm`).
- Optional: per-view metric defaults and a config file.
## Development
```sh
cargo build --release
cargo test --release # 69 tests
cargo clippy --all-targets -- -D warnings
cargo fmt --check
```
## License
MIT — see [LICENSE](LICENSE).