Skip to content

Repository files navigation

██████╗  ██████╗  ██████╗██╗  ██╗██████╗  ██████╗ ████████╗████████╗ ██████╗ ███╗   ███╗
██╔══██╗██╔═══██╗██╔════╝██║ ██╔╝██╔══██╗██╔═══██╗╚══██╔══╝╚══██╔══╝██╔═══██╗████╗ ████║
██████╔╝██║   ██║██║     █████╔╝ ██████╔╝██║   ██║   ██║      ██║   ██║   ██║██╔████╔██║
██╔══██╗██║   ██║██║     ██╔═██╗ ██╔══██╗██║   ██║   ██║      ██║   ██║   ██║██║╚██╔╝██║
██║  ██║╚██████╔╝╚██████╗██║  ██╗██████╔╝╚██████╔╝   ██║      ██║   ╚██████╔╝██║ ╚═╝ ██║
╚═╝  ╚═╝ ╚═════╝  ╚═════╝╚═╝  ╚═╝╚═════╝  ╚═════╝    ╚═╝      ╚═╝    ╚═════╝ ╚═╝     ╚═╝
▇▆▅▄▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

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."

rockbottom in action

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.

CI   latest release

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.


The competition (a eulogy, delivered lovingly, at their funerals)

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. top isn't a tool. It's a hazing ritual invented by someone who peaked before the Berlin Wall came down and never emotionally recovered.

  • htoptop, 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 press F5, 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. htop is a screensaver you feel guilty about.

  • btop — okay. Okay. btop is 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 a command not found with a great personality. We just... put the GPU in the main app. Press 4. 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 rewriting htop in 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.

Are you the target audience? (a diagnostic quiz)

  1. 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?
  2. 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?
  3. Have you ever killed the wrong process with total confidence, immediately followed by a regret so pure and so deep it echoed?
  4. 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.

The problem, stated plainly

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.

The fix: we read it for you

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.

Press a number, get the whole story (the detail panes)

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?"

  • 1 CPU — 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," because htop will proudly show you load average: 12.4 and 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.

  • 2 MEMORY — 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.

  • 3 NETWORK — 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.

  • 4 GPUit replaces nvtop outright, and it's already installed, because it's simply here, existing, at all times, unlike nvtop. 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 (amdgpu sysfs), and Intel; whatever your card refuses to cough up is quietly left out instead of shown as a sad N/A. Multiple GPUs stack. And it's cheap — the nvidia-smi query 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.

  • 5 DISK — 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 / Enter PROCESS — 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 cryptic D you'll Google, understand for nine seconds, and forget forever), owner, and its listening ports. x/K still end it from right here; ↑↓ walks the list.

  • 7 USERS — the admin pane, and the one thing neither htop nor btop will 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 an awk one-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 meaningless 1179%), 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 by c·m·u·i·D·n (cpu, mem, procs, i/o, disk, name — lowercase d stays "open the disk pane" like it does everywhere else, and u takes procs because p is 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 Enter on 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. esc unwinds 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: f drops you back to the process list already filtered to that user, and X arms a SIGTERM against every process they own (K for SIGKILL) behind the same y/n confirm and the same pid-reuse revalidation as every other kill in the program. root is refused outright — "kill every root process" isn't a recovery action, it's an unbootable machine — and your own rb is 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 alice at row 3 quietly becomes bob at row 3 the moment her build finishes — and X would 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 ●2 badge 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 /home and 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 , not 0: a confident zero for an unmeasured home is a lie an admin would act on. Where a quota exists you get 48G/50G — the cap is the whole story, since a bare red 48G can't tell you whether the limit is 50G or 500G. The scanner matches du -sx byte-for-byte (same st_blocks accounting, 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 — /home is 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, marked 31.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 fromquota and 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.

Everything else (for your brave days)

  • 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. htop and btop are 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 with i. 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 :8080 like 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 with o.
  • 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."

"Is it actually clever under the hood, or just mean?" — Both. It's both.

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.

"Okay but does the fancy monitor make my fans spin?" — No. That's the whole point.

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/io is 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 of lstat calls — runs only while the USERS pane is actually open. Profiling with perf + strace rather 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.

Architecture (skip this; we both know you were going to)

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.

Building

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/rb

macOS (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/rb

CMake 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 --recursive

There. Fixed. You're welcome. We're not mad. We could never be mad at you.

Keys (mostly one hand — we assume the other is holding a snack)

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
17 / 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.

Platform

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 /proc and /sys with 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 fake N/A theater. 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.

Per-core, per-cluster

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 --topology
model      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.)

When a panel is empty

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 --doctor
COLLECTOR    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.

License

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.

About

rockbottom (rb) — a Linux system monitor that actually reads the numbers for you and tells you what's wrong. Replaces htop, btop, top, nvtop.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages