██████╗ ██████╗ ██████╗██╗ ██╗██████╗ ██████╗ ████████╗████████╗ ██████╗ ███╗ ███╗
██╔══██╗██╔═══██╗██╔════╝██║ ██╔╝██╔══██╗██╔═══██╗╚══██╔══╝╚══██╔══╝██╔═══██╗████╗ ████║
██████╔╝██║ ██║██║ █████╔╝ ██████╔╝██║ ██║ ██║ ██║ ██║ ██║██╔████╔██║
██╔══██╗██║ ██║██║ ██╔═██╗ ██╔══██╗██║ ██║ ██║ ██║ ██║ ██║██║╚██╔╝██║
██║ ██║╚██████╔╝╚██████╗██║ ██╗██████╔╝╚██████╔╝ ██║ ██║ ╚██████╔╝██║ ╚═╝ ██║
╚═╝ ╚═╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
▇▆▅▄▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
rb — a system monitor for people who open htop, whisper "what in god's name," and close it forever.
Every other monitor shows you the numbers and then leaves, like a doctor who hands you an X-ray and says "good luck." rockbottom reads them for you and says, slowly, kindly, like you're a spooked horse: "your computer is fine, buddy."
Built in type-theoretic C++26 on the maya TUI framework — the code got a PhD so you could keep eating crayons in peace.
→ 1ay1.github.io/rockbottom — the homepage, if you like your pitches with a color-ladder.
Try it now — no build, no package manager, no shared-library roulette:
curl -fsSL https://raw.githubusercontent.com/1ay1/rockbottom/master/install.sh | sh
rb
The installer downloads the right release for Linux x86_64/arm64 or Apple
Silicon, verifies its SHA-256 checksum, and installs rb to
~/.local/bin, then adds that directory to the startup files for
Bash, zsh, fish, and login sh sessions. Open a new terminal and
run rb. The command runs the POSIX installer via sh,
so paste it from Bash, zsh, fish, or another interactive shell. Linux binaries
are fully static: built against musl (not glibc), so there is no loader, no
shared-library dependency, and no glibc version floor — one ELF that runs on
any Linux kernel of its architecture.
Release files
also include the standalone binaries and checksums for manual installs.
Let's go around the room. Everyone here has already died; they just haven't been told.
-
top— shipped the year Ghostbusters came out and has aged like the milk in the fridge from that same year. A beige spreadsheet that updates with the urgency of a DMV and the charm of a ransom note. Ask it "is my computer okay?" and it stares back like a Victorian orphan.topisn't a tool. It's a hazing ritual invented by someone who peaked before the Berlin Wall came down and never emotionally recovered. -
htop—top, but they gave the ransom note colors. Bold move. Now it's a rainbow wall of numbers you still can't read, except festive, like a Christmas tree assembled by a hostage. You'll pressF5, gasp at the tree view for exactly eight seconds, feel briefly like a hacker, and then close it forever — the same eight seconds, every time, until you die.htopis a screensaver you feel guilty about. -
btop— okay. Okay.btopis stunning. Genuinely, breathtakingly beautiful. It is also forty gauges, six graphs, braille fireworks, a theme engine, and a config file longer than your lease, and when you ask it "is something wrong?" it replies "here are nine hundred data points, one of them is the answer, figure it out, xoxo 💋." It's a fighter-jet cockpit bolted to a bicycle you don't know how to ride. Every dial is glorious. Not one of them tells you the fan noise at 2am is Chrome. Again. Like it always is. -
nvtop— fantastic tool! For the four minutes a year you remember it exists, and it's installed, and your GPU driver woke up on the right side of the bed. The other 525,596 minutes it's acommand not foundwith a great personality. We just... put the GPU in the main app. Press4. It's already there. It was always there. You're welcome; try not to cry. -
bottom(the Rust one,btm) — respectfully — respectfully — they took the good name, spent a year rewritinghtopin a language that makes you say so, gave it rounded corners, and called it a day. It's fine! It's genuinely fine. It is the most polite restatement of the problem ever committed to a crate registry. It shows you the numbers with excellent memory safety and then, like all the others, leaves you alone in the room with them. We took the name back, bolted a rock to it, and — this is the twist — actually answered the question. Go ahead and rewrite it in Rust again. We'll be over here telling a human what's wrong with their computer, in a sentence, out loud.
Every single one of them shows you the numbers. Not one of them reads the numbers. That's the whole bit. That is the entire, gaping, market-sized hole in the ground, and we reversed a truck into it, honking.
- When the fan spins up, do you (a) inspect the process tree, or (b) sniff the air and announce "something's burning" to an empty room?
- Is "have you tried turning it off and on again" not a joke to you, but your entire IT department, its mission statement, and its only employee?
- Have you ever killed the wrong process with total confidence, immediately followed by a regret so pure and so deep it echoed?
- Do you believe "the cloud" is a place, and that it has weather?
Answer anything other than a stunned, guilty silence and welcome home. You are
precisely who we built this for. We are not mad. We are proud. You made it this
far into a README — that's further than you made it into htop.
htop and btop are beautiful and top is a war crime, but they all commit the
same original sin: they answer "is my computer okay?" with a cheerful,
booming "you figure it out, champ!" and then whistle out of the room.
So you stare. You nod, sagely, understanding nothing. You develop a deep and religious fear of the numbers. Eventually you pick a process at random, kill it, and either the problem vanishes (you're a wizard) or your audio dies until the next reboot (you are not a wizard, and you never were). This is not a system monitor. This is a hostage negotiation, and the graphs are the ransom demand.
rockbottom employs, at great personal expense, a little guy whose entire job is to stare at all that garbage, turn to you, and deliver exactly one (1) sentence:
╭───────────────────────────────────────────────────────────────╮
│ ▲ Working hard — CPU is heavily loaded chrome (pid 4160) … │
╰───────────────────────────────────────────────────────────────╯
That's the verdict. It's color-coded, so even if you cannot read — no judgment, you clearly got this far on vibes and shape-recognition alone — the color tells you everything:
- 🟢 Calm — it's fine. Put the fire extinguisher down. Step away from the fire extinguisher.
- 🟡 Busy — something is working. It is allowed to. Unclench.
- 🟠 Stressed — okay, now you may look at the thing we already highlighted in red for you. Only now. Good.
- 🔴 Critical — this is a "close some tabs" situation. You have too many tabs. You have always had too many tabs. At this point it isn't a habit, it's a personality, and we love that for you, but close them.
And the process actually doing the crime? We slap a fat » on it and paint it red,
so you don't have to walk down the list pointing at each row whispering "is it
you?? is it YOU???" like a detective who peaked in a kindergarten production of
Clue. It's Chrome. It's always Chrome. It was Chrome before you finished
reading the question. htop knew, and htop said nothing, because htop is a
coward.
You read the one sentence. You know. That's the app. Everything below this line exists purely for the two (2) days a year you feel dangerously brave.
Every panel has a full-screen drill-down that carries more raw detail than htop, btop, nvtop, and the polite Rust fellow combined — and then, unlike every last one of them, has the basic decency to read it for you at the bottom. Hit a number (or just click the panel, you animal) and rockbottom throws the doors open: a system strip up top (host / kernel / uptime / process census) and, at the end, a plain-language verdict, so you never once leave a screen squinting at digits going "…so what?"
-
1CPU — a big load-over-time mountain range with an actual y-axis (revolutionary), live total + iowait bars, and the 1/5/15 load averages interpreted against your core count — as in "oversubscribed — tasks are waiting for a free core," becausehtopwill proudly show youload average: 12.4and then let you just feel things about it, alone, forever. Plus package temp, a distribution strip (busiest / quietest / median / average / spread / active cores — it will personally rat out a single-threaded hog pinning one poor core to the wall), and every core as its own labelled meter with its live clock speed. -
2MEMORY — the usage-trend graph (spot the leak before the OOM killer visits), a real physical breakdown (used / cache / buffers / available) with the available verdict that's the only one that actually matters, the full swap saga with a thrash detector, the kernel's PSI memory-pressure numbers, and the top memory-hungry processes right there in the pane — so you're not spelunking through a table sorted by hand like it's 1997 and you're waiting for a CD to burn. -
3NETWORK — an all-interfaces aggregate line (total down/up, lifetime bytes, links up), then every interface with live rx/tx sparklines, lifetime totals, a burst detector, and up/down state. Feel like a hacker in a movie. You earned it. -
4GPU — it replacesnvtopoutright, and it's already installed, because it's simply here, existing, at all times, unlikenvtop. Utilisation-over-time graph, core + VRAM meters, a full telemetry strip (temperature, power draw vs limit, core & memory clocks, fan, perf-state, NVENC/NVDEC encode/decode load), the processes actually squatting on VRAM, and a verdict ("▲ VRAM is nearly full" / "pinned at full load — this is your bottleneck"). Works with NVIDIA (nvidia-smi), AMD (amdgpusysfs), and Intel; whatever your card refuses to cough up is quietly left out instead of shown as a sadN/A. Multiple GPUs stack. And it's cheap — thenvidia-smiquery is throttled to once a second (the per-process VRAM table to once every few), so keeping an eye on your GPU doesn't itself become a background job pegging a core. -
5DISK — system-wide read/write sparklines, I/O pressure with a bottleneck verdict, every filesystem with its backing device, free space, used/size, fstype, and a fullness meter, the fullest-mount callout, and the processes actually driving the disk right now — so you can finally meet the program sanding your SSD down to a nub. -
6/EnterPROCESS — drill into the selected process: full command line, cpu + memory bars, its rank against every other process on the machine (so you instantly know if it's the hog — "#1 CPU consumer right now" — instead of guessing), disk read/write, thread count, a plain-language run-state ("◆ uninterruptible — wedged on I/O, can't even be killed," not a crypticDyou'll Google, understand for nine seconds, and forget forever), owner, and its listening ports.x/Kstill end it from right here;↑↓walks the list. -
7USERS — the admin pane, and the one thing neitherhtopnorbtopwill do for you. Every other view answers "which process"; on a shared box — a build farm, a jump host, a lab server, a CI runner — the real question is "which person," and the answer currently lives in anawkone-liner you re-derive from memory every single time. Here it's one keystroke: every user on the machine with their total CPU (as a share of the whole box, with a bar, not a meaningless1179%), total RAM, process and thread counts, I/O rate, a zombie tally (12 (3Z)— someone's orphaned mess), and the single busiest thing they're running. Sort byc·m·u·i·D·n(cpu, mem, procs, i/o, disk, name — lowercasedstays "open the disk pane" like it does everywhere else, andutakes procs becausepis pause). Pressing the same key again — or clicking an already-active column header — reverses the order, and the header arrow tracks it (▼biggest-first,▲reversed). Re-sorting keeps your cursor on the same user rather than dumping it back on row 0.Press
Enteron anyone and you get their whole dashboard: a CPU trace scaled to the machine (not peak-normalised — the question is how much of the box they are), a composition bar showing their RAM against everyone else's and the free space, a process-state breakdown that separates busy from stuck (D-state is waiting on storage, and no amount of CPU will help it), read/write I/O split apart, storage against quota, every live login session with where it came from, every port they have bound, and their heaviest processes ranked both by CPU and by memory — because those two disagree more often than not, and showing only one of them is how you blame the wrong process.On a box with sixty accounts, scrolling until you spot someone is not a way to find them — so
/filters the roster itself, matching name, uid, real name, home or shell. It's a safety feature as much as a convenience: the destructive keys target whatever the cursor is on, and a short list is one you can actually verify.escunwinds one layer at a time — dashboard, then filter, then the pane — so you never lose more context than you asked to.Then act on it:
fdrops you back to the process list already filtered to that user, andXarms a SIGTERM against every process they own (Kfor SIGKILL) behind the samey/nconfirm and the same pid-reuse revalidation as every other kill in the program.rootis refused outright — "kill every root process" isn't a recovery action, it's an unbootable machine — and your ownrbis never in the blast radius.The cursor is anchored to the user, not to a row number. The table re-sorts under you every second, so a bare index means
aliceat row 3 quietly becomesbobat row 3 the moment her build finishes — andXwould then reap bob while you were looking at alice. The selection tracks the name across every re-sort, and if it somehow can't, the kill is refused rather than retargeted.It also answers the other half of user management. A
●2badge means that person has two live login sessions right now (from logind, or utmp) — the difference between "a daemon account owns processes" and "a human is sitting at this machine", which is worth knowing before you mass-signal them. And the DISK column answers the question that actually gets you paged: who is filling the disk. Users who own no running processes still get a row, because the person who filled/homeand logged out is invisible to every process-based view.Disk usage is the one number here with no cheap kernel counter, so rockbottom is explicit about where it came from. If the filesystem has quotas enabled, that's exact and instant. Otherwise a background scanner walks each home under a hard wall-clock budget — never on the render path, never more than one at a time, and cancelled instantly on quit (a real 242 GB home takes ~12s to walk; a monitor that blocks a frame for twelve seconds has become the problem). While a walk is still running the cell reads
≥112G— a floor, not a guess. And if nothing has measured that user yet it reads—, not0: a confident zero for an unmeasured home is a lie an admin would act on. Where a quota exists you get48G/50G— the cap is the whole story, since a bare red48Gcan't tell you whether the limit is 50G or 500G. The scanner matchesdu -sxbyte-for-byte (samest_blocksaccounting, same hardlink dedup, same don't-cross-mounts rule) — verified against four real trees.A raw 145G still can't be ranked, though — it depends entirely on whether the disk is 200G or 2T. So there's a
DISK%column with its own meter: the user's share of the filesystem their home actually lives on (resolved by longest matching mount, the same rule the kernel uses —/homeis its own volume on some boxes and a directory on/on others, and picking wrong turns 50% into 250%). Where a quota exists it becomes the denominator instead, marked31.5%q, because the cap is the limit that actually bites. Sorting by disk ranks on this share, so 40G of a 50G quota correctly outranks 200G on a 4T array. Unmeasured stays—— never a fabricated percentage.Daemon accounts are marked
·svc, dimmed, and sorted below real people — on a normal box forty service accounts otherwise bury the three humans. The unknown-owner bucket (?) sorts last for the same reason: it can't be filtered to and can't be signalled, so it has no business holding the row your cursor starts on. Selecting a row also prints that account's uid, shell, home, and where the disk figure came from —quotaand a walk that gave up halfway are very different numbers and shouldn't look alike.
Every pane is responsive (it reflows to your terminal — more core columns on a
wide screen, tighter on a small one) and scrollable (↑↓ / PgUp / PgDn /
g / G / the wheel, with a live scrollbar), so no matter how dense the data or
how cramped the window, nothing clips off the edge into the void — the way it does
in, and we cannot stress this enough, you know exactly what. Esc closes (or
backs out one layer, if you've drilled into something); number keys switch panes
without backing out. Clicking inside a pane works the pane — it doesn't slam
the door on you — and a click outside it dismisses. It's the "okay, tell me
everything" button — gorgeous, exhaustive, and it still tells you what it means.
- Verdict banner — Calm / Busy / Stressed / Critical, with the guilty party named out loud in front of God and everyone. Public shaming, as a service, at no extra charge.
- PSI pressure chips — the Linux kernel keeps a tiny diary of how long your
stuff sat around waiting (
/proc/pressure). "tasks stalled on I/O 40% of the last 10s" is the kernel tattling on your own disk.htopandbtopare far too well-mannered to read someone else's diary. We are not well-mannered. We read it aloud, at the dinner table, in a funny voice. - CPU — one big number ("am I busy?"), a fistful of little numbers ("which of my brains is busy?"), and a graph shaped like a mountain range that goes up when you do things and down when you don't. It is, functionally, a very expensive mood ring.
- Memory — RAM + swap meters plus the cache/available breakdown, so you can finally internalize the one sacred truth every other monitor lets you panic over daily: "used" memory is a filthy, load-bearing lie. Linux hoards free RAM like a dragon on gold; it is supposed to look almost full. Put the credit card down. You do not need more RAM. You need this paragraph, tattooed somewhere.
- Disk — read/write speeds and your actual drives, fullest first (btrfs subvolumes deduped, so you don't see the same disk nine times and briefly believe you own a data center). Answers "why slow?" and "am I out of room?" in one glance.
- Network — download ▼ and upload ▲ per interface, with adorable little graphs. Watch the number go up. Nod. You are basically in a heist film now.
- Processes — pick things, filter things, and kill things. The loudest offender comes pre-highlighted, so for once in your life you kill the right one. Dots tell you who's genuinely working (●) vs. who's just napping on the disk (◆), so you stop executing sleeping programs on a hunch like a process-shaped serial killer.
- Per-process disk I/O — the honest, unspun "who is grinding my SSD into powder"
number, straight from
/proc/<pid>/io. Sort by it withi. The verdict even hands you the culprit's name when the disk is the bottleneck. (It's a backup you configured in 2019 and forgot about. It's fine. It loves you. It just needed to be seen.) - Ports — which program is squatting on
:8080like a smug little goblin (:8080 +2), so the next time you get "address already in use" you can evict the goblin instead of rebooting the whole machine like a frightened medieval peasant. Sortable witho. - Battery — a charge chip in the corner, because sometimes "why is it slow" has the deeply humbling answer "it is on 3% and quietly begging for a charger."
You get to be casual because the code is a paranoid genius doing your worrying for you. Every number carries a type that knows what it is, so the program physically cannot confuse bytes with hertz and cheerfully inform you your hard drive runs at 3.6 gigabytes-per-second of clock speed. It won't compile. The compiler stands in the doorway like a bouncer, looks at your unit mismatch, and says "nah." Try getting that out of a bash one-liner you copied off a 2011 Server Fault answer.
using Bytes = Strong<BytesTag, std::uint64_t>; // storage / memory
using Hertz = Strong<HertzTag, std::uint64_t>; // clock speed
struct Ratio { double v; }; // a proportion, clamped [0,1]
template <class Q> struct Rate { double per_sec; }; // Q, but per second
// The ONE legal way to turn Bytes into Bytes-per-second has a name and a
// hall pass, signed by a teacher:
constexpr ByteRate rate(Bytes delta, double dt_sec) noexcept;That's why 4.2G and 118M/s and 3.60GHz are always right — not because some
exhausted human remembered the unit at 2am, but because the type remembered it,
permanently, so no human ever has to again. You are safe here. The nerds have
handled it. Go outside.
A system monitor that costs you a chunk of a core to run is a monitor that becomes part of the problem it's describing — a smoke detector that's on fire. So rb is cheap on purpose, and we measured it instead of vibing it:
| At 1s refresh, idle desktop | steady-state CPU | RSS |
|---|---|---|
| rb | ~0.3% of one core | 9 MB |
| btop | ~1.6% | 38 MB |
| htop | ~0.7% (no GPU) | 6 MB |
Five times cheaper than btop on CPU at a quarter of the memory, and it's reading your GPU the whole time — htop isn't. Three things buy that:
- The render is basically free.
view()is a pure function and maya only repaints the cells that actually changed, so a frame costs ~0.95ms and — the part every other monitor gets wrong — that cost is flat as your terminal grows. Stretch rb across a 300-column ultrawide and it does not quietly start eating a core the way a certain gorgeous braille-fireworks monitor does; the process table only builds the rows in the visible window, so 400 processes or 40,000, same frame. - The sampler asks the kernel for the least it can get away with. Fast signals
(CPU, memory, net, disk rates) refresh every tick; slow ones (filesystems,
sensors, ports, GPU) run on their own throttled cadences and serve a cache in
between. Nothing forks a subprocess on the hot path if we can read a file
instead, and the one collector that has to shell out —
nvidia-smi— is polled once a second, with its expensive per-process query on an even slower beat, instead of the naive three-forks-every-frame that used to quietly cost ~15% of a core all by itself. (btop wins the last hair here because it links NVML and never forks at all. We see you, btop. We're coming for that too.) - Expensive work only happens when you're looking at it. A tick is
syscall-bound, not compute-bound, so the wins come from not making the call at
all:
/proc/pid/iois 0600 and about three of every four processes deny it, so a refusal is latched and never retried (permission can't change without an exec, and an exec invalidates the cache entry anyway). Per-process CPU history ships only while something is graphing it. And the per-user disk walk — by far the most expensive thing in the program, hundreds of thousands oflstatcalls — runs only while the USERS pane is actually open. Profiling withperf+stracerather than guessing took a sample from 632k syscalls to 112k, and system time from 1.03s to 0.22s.
Want to check it yourself instead of trusting a README that has, admittedly, been
unreliable about tone? rb --bench times the sampler alone with no UI and prints
the steady-state cost; RB_PHASE=1 rb --bench breaks it down per collector so you
can see exactly what each tick spends. The numbers above came straight out of it.
It opens now, not in a second. Time-to-first-frame is a few milliseconds: the window paints with real CPU load, every process, every core and real memory before anything slow runs. The genuinely expensive one-time probes — enumerating the ~1600-key AppleSMC table for temperatures, standing up the IOReport subscription for per-core clocks, cold-starting Termux:API for battery/wifi — are detail-pane data, so they're deferred to the first background tick a heartbeat later instead of blocking the launch. On an M1 that moved boot-to-first-frame from ~470ms to ~7ms. You press the key, it's there.
rockbottom is a maya Elm-style Program — pure functions, no sneaky in-place
mutations, the kind of tidy that would make your therapist weep with pride:
| Layer | Where | Role |
|---|---|---|
| Units | src/core/units.hpp |
Strong dimensional types + the humanizers that translate scary numbers into 4.2G |
| Data | src/core/metrics.hpp |
Pure Snapshot value types + the almighty Verdict |
| Sampler | src/core/sampler.* + verdict.cpp |
OS-agnostic: orchestration, cross-tick deltas, and the diagnosis engine |
| Platform | src/core/platform/<os>/ |
The one grubby room that touches the real world — linux/ reads /proc + /sys; darwin/ uses mach / sysctl / libproc / IOKit. CMake compiles exactly one |
| App | src/ui/app.hpp |
Model + Msg + update + a pure view |
| Widgets | src/ui/widgets/ |
Panels, meters, graphs, and the detail panes (one file each) |
No pane hand-counts breakpoints like a savage. Every layout is measured: maya's
responsive toolkit (fit_row sheds the header and the footer hints as they
narrow, pick re-renders the detail tab bar at the richest density that actually
fits, col stacks the narrow-mode stat band, solve_columns keeps the process
table's header and rows on one width plan, fill sizes each graph to exactly the
height left over) means the thing measured is the thing drawn — so nothing ever
drifts off its rail at some cursed terminal size, unlike a certain gorgeous monitor
that shall remain named earlier. See maya's
Responsive Layouts guide.
update() ticks once a second, like a very calm heartbeat that has done a lot of
therapy. view() is a pure function of the state. The sampler keeps the last
sample, so every rate is a real delta and not a hopeful guess. It is genuinely
serene in here. Please take your shoes off.
Needs a C++26 compiler (GCC 15+; built on GCC 16) and CMake 3.28+. Yes, it's a fancy compiler. The type theory has demands. It's an artist, and it will not be rushed.
Linux:
git clone --recurse-submodules https://github.com/1ay1/rockbottom
cd rockbottom
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/rbmacOS (Apple Silicon or Intel) builds with Homebrew GCC — the same toolchain maya's own CI uses. That's the entire story: no Clang, no libc++ gymnastics, no tears.
brew install gcc cmake
git clone --recurse-submodules https://github.com/1ay1/rockbottom
cd rockbottom
cmake -B build -DCMAKE_BUILD_TYPE=Release # auto-picks Homebrew g++
cmake --build build -j
./build/rbCMake hunts down Homebrew's newest g++-N for you (override with
-DCMAKE_CXX_COMPILER=g++-15 or CXX=...). Apple's kernel headers use the C11
_Static_assert spelling that g++ rejects at namespace scope, so the macOS backend
aliases it to C++'s static_assert before any SDK header gets included
(src/core/platform/darwin/mach_util.hpp) — one polite line that lets plain GCC
parse all of mach / IOKit / libproc without a meltdown. Why not just use Clang?
Because Apple/LLVM libc++ still doesn't ship std::move_only_function, which maya
requires, and libstdc++ does. So GCC it is. We didn't make the rules; we just
refuse to lose to them.
Cloned it and forgot --recurse-submodules, you beautiful, chaotic disaster?
git submodule update --init --recursiveThere. Fixed. You're welcome. We're not mad. We could never be mad at you.
| Key | Action |
|---|---|
↑↓ / j k |
pick a process (j k is for people who use vim and cannot physically stop telling you) |
* |
pin the selected process to the top of the list and hold it there, still selected, while everything else re-sorts around it — so you can watch one thing without it squirming away the instant its CPU% changes. Press again (or just move the cursor) to let go. |
/ |
filter by name or pid (Esc un-does it the instant you inevitably typo) |
x / Del |
politely ask a process to leave (SIGTERM — it even knocks first) |
K |
firmly remove a process (SIGKILL — still asks you first; we're unhinged, not savages) |
y / n |
yes, commit the crime / no, I panicked, put it back |
s |
cycle sort · c cpu · m mem · i i/o · n name · P pid · o port |
1–7 / Enter |
open a detail pane (cpu · mem · net · gpu · disk · process · users) |
7 then Enter |
open that user's full dashboard — cpu, ram, i/o, disk, sessions, ports |
7 then / |
find a user by name, uid, real name, home or shell |
7 then f |
see who's eating the box, then filter the list to that one user |
7 then D |
sort users by DISK — who is filling /home (quota, or a budgeted scan) |
7 then X |
end every process a user owns (asks first; refuses root, never touches rb) |
↑↓ / PgUp/PgDn / g/G |
scroll the detail pane (the wheel works too) |
p / Space |
pause / resume (freeze the chaos so you can point at it and go "there") |
? / h |
help, for when every key you just read immediately evaporates from your brain |
q / Esc |
leave. go outside. the grass misses you. |
Or just use your mouse, you absolute animal. Full mouse support, zero misses:
| Do this | Get that |
|---|---|
| click a process | pins it in place + selects it, so it stops squirming as the list re-sorts and you can keep your eyes on it |
| double-click a process | opens its detail pane |
| click a column header | sorts by it (CPU, MEM, DISK, PORT, NAME) |
| click a panel | opens its detail pane |
| click inside a detail pane | works the pane (selects a row, drags the scrollbar) — it does not close it; esc or a click outside does |
| click a user row | selects that user · double-click opens their full dashboard |
| click a users column header | sorts the roster by it — including DISK%; click again to reverse (▼ / ▲) |
| click a footer hint | fires that action — ?·help, space·pause, s·sort, x·end, K·kill, /·filter, q·quit |
| right-click a process | arms an end (SIGTERM — still asks first) |
| drag a scrollbar | slides that list or pane to any position — the process list, every detail pane, and this help all grab-and-slide |
| scroll wheel | rolls the process list (or the detail pane, if one's open) |
The click-to-row math is anchored to the layout, so a click lands on exactly the
row you clicked, at every terminal size — and the scrollbar drag reads the bar's
real painted rectangle, so the thumb goes exactly where you drag it. No "close
enough," no "off by one," no "why did I just kill systemd." We checked. btop did
not check. btop is beautiful and it did not check.
Linux and macOS. rockbottom is a thin OS-agnostic core (the verdict engine, the
Elm loop, the strong-typed units) sitting on a platform backend — one directory
per OS under src/core/platform/, and CMake compiles exactly one. Adding an OS is
adding a directory; the verdict never changes, because the verdict is the whole
point and everything else is plumbing.
- Linux — reads
/procand/syswith its bare hands. Full fidelity: PSI pressure, per-core clocks, iowait split, per-process disk I/O, the entire buffet. - macOS — talks to the kernel the native way:
host_processor_info/host_statistics64(CPU + memory),sysctl(swap, uptime, model),libproc(processes + bound ports), IOKit (disk throughput, GPU, battery),getifaddrs(network). A few Linux-only signals have no macOS analogue — PSI, the iowait split, per-core clock — so those panes simply omit them, exactly the way the app already hides a GPU field your card won't report. No fakeN/Atheater. Everything else is there.
No ncurses, no runtime dependencies, no phoning home to sell your fan speeds to advertisers. On Windows? Still not for you — but sincerely, genuinely, from the bottom of our hearts: props for reading this entire thing.
On heterogeneous silicon — Apple P/E, Intel hybrid Core/Atom, ARM big.LITTLE —
every logical CPU is labelled with the cluster it actually belongs to, and the
grid groups and averages by cluster. This is probed once at startup, not per
tick, and never by forking a subprocess: sysfs cpu_core/cpu_atom cpulists and
cpu_capacity on Linux, hw.perflevel* on macOS. A machine that is genuinely
homogeneous is left alone — a ~15% capability spread is required before anything
is called a cluster, so ordinary frequency scatter and favoured-core boost don't
conjure a phantom P/E column on your server.
To see exactly what was detected on your machine — useful in a bug report, and the fastest way to tell "the kernel exposed nothing" from "we misread it":
rb --topologymodel Apple M1
logical 8
clusters heterogeneous — 4 Performance + 4 Efficiency
cpu class phys freq temp load
0 E - - - 12%
...
4 P - - - 63%
(RB_SYSFS_ROOT=/path/to/a/captured/sys replays another machine's /sys tree
through the same classifier, which is how the Linux path gets tested without
owning every CPU ever made.)
Every collector in rockbottom degrades silently on purpose — if /sys is masked,
the ioctl needs root, or the machine simply has no NVMe, the field goes blank and
the tool keeps running. That's right for a monitor and useless for debugging, so
there's a subcommand that says the quiet part out loud:
rb --doctorCOLLECTOR OK DETAIL
procs yes 311 processes, 1189 threads, 3 running, 0 zombie
cpu yes 8 cores, 4P+4E, busy 16%
memory yes 16G total, 13G available, swap 1.0G
diskio no no per-device I/O or latency — /proc/diskstats absent (normal
on macOS; the aggregate read/write rate still works)
gpu yes 1 adapter(s): Apple M1
psi no no PSI — /proc/pressure absent (Linux <4.20, CONFIG_PSI=n, or
not Linux). Verdict falls back to load/iowait heuristics.
ssd no no SMART data — NVMe admin ioctl needs root, or no NVMe device
Every no carries the reason it's a no, so you can tell "this machine has no
GPU" from "nvidia-smi isn't on PATH" from "you need root for that ioctl" without
reaching for strace. It always exits 0 — a missing GPU is information, not
failure. Paste this into any bug report about a blank panel.
Its stricter sibling rb --selfcheck runs the real collectors and asserts the
data is sane (non-empty process list, our own pid present, cpu in 0..1, used ≤
total), exiting non-zero if not. That one is a pass/fail gate and runs in CI on
every platform.
MIT — see LICENSE. Do whatever you want; we are not your dad, and we would not presume. Vendored maya is MIT too. Everybody's chill. Go be free.
rockbottom: for when "is my computer okay?" finally deserved a real answer — and you deserved to never have to think about it again.
