# rbspy: profiling a Ruby process that is already running

> rbspy is a sampling CPU profiler for Ruby written in Rust. It attaches to a live PID, records data, and reports later, which makes it a fit for production processes you cannot restart.

**rbspy/rbspy** — Sampling CPU profiler for Ruby

- Repository: https://github.com/rbspy/rbspy
- Website: https://rbspy.github.io
- Stars: 2,573 · Forks: 106
- Language: Rust
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/rbspy-rbspy

## The problem rbspy solves: a Ruby process you cannot restart

Most Ruby profilers assume you can start the program under them. You add a gem, wrap the entry point, restart the process, reproduce the slow path, and read the results. That workflow breaks down when the interesting process is a production worker that has been up for hours, or a command line program you would rather not modify. According to the README, rbspy profiles Ruby processes that are already running: you give it a PID and it starts profiling. The README also frames the command line case directly, saying rbspy can profile a Ruby command line program easily.

The audience follows from that. If you are debugging a slow endpoint on a service you can redeploy at will, an in-process profiler is usually the better fit because it sees Ruby-level detail. If you are looking at a box where restarting the process costs you a queue drain, a warm cache, or a maintenance window, attaching from outside is the only practical option. rbspy is built for the second case, and the README describes it as a sampling profiler, which it says is low overhead and safe to run in production. That claim is the project's own; treat it as the design intent rather than a measured number, since the README publishes no overhead figures.

## How a sampling profiler reads another process

The name is literal: rbspy samples. It does not instrument your code or require a gem inside the target process. The repository layout shows the split. The main crate lives in src/, and a separate workspace member, ruby-structs/, holds the Ruby internal structure definitions that rbspy needs in order to interpret what it reads. The Cargo.toml depends on rbspy-ruby-structs by path, and the workspace lists ruby-structs and xtask as members. The architecture document at ARCHITECTURE.md is the place to look for the full picture; the README does not describe the sampling loop.

What the dependency list does tell you is the mechanism at a high level. The crate depends on remoteprocess for reading another process, proc-maps for its memory map, memmap2 for mapped files, and inferno for flame graph generation. prost and serde appear for serialization, and flate2 for compression, which fits the record-then-analyze workflow the README describes: record profiling data, save the raw data to disk, and analyze it later in different ways. The ruby-structs crate is the part that has to track Ruby internals, and the repository carries a NEW_RUBY_VERSION_CHECKLIST.md at the top level, which is a fair signal that supporting a new Ruby release is a recurring maintenance task rather than a one-time job.

## Installing rbspy and recording a first profile

On macOS the README gives a single Homebrew command. Run it and you should end up with rbspy on your PATH:

```bash
brew install rbspy
```

On Linux the README describes a manual install: download a recent release of rbspy from the GitHub releases page, unpack it, and move the rbspy binary to /usr/local/bin. The README notes that binaries tagged with musl are statically linked against musl libc and work on most systems, while the ones tagged with gnu are dynamically linked against GNU libc and need it installed. The README points to the Installing rbspy page at rbspy.github.io for the full procedure. Once installed, the workflow the README describes is: give rbspy a PID, let it profile, and analyze the result. The README does not print the exact subcommand syntax, so check the documentation site for the current flags before you script anything around it. The intended shape of the session is a recording step followed by a report step, which is why the repository ships examples/record.rs, examples/report.rs, and examples/snapshot.rs as separate example programs.

If you would rather embed the profiler, the README shows the crate as a Rust dependency. It also carries an explicit warning that the API is not stable yet and that semantic versioning will be followed only after rbspy reaches version 1.0:

```toml
[dependencies]
rbspy = "0.8"
```

Building from source follows the README's own steps. Install cargo from crates.io, then run cargo build; the README states that the built binary will end up at target/debug/rbspy, and that cargo test runs the test suite:

```bash
cargo build
cargo test
```

## Where rbspy is the wrong tool

A sampling profiler answers one question well: where is CPU time going. It is a poor fit for questions that are not about CPU. If your problem is memory growth, object retention, or allocation churn, rbspy is not the instrument, and the README makes no claim in that direction. If your problem is a lock contention pattern that only appears under a specific request mix, sampling may simply miss it, because the samples are statistical rather than a complete record of every call.

The second boundary is Ruby version support. Because rbspy reads internal structures from outside the process, it has to know the layout of those structures for the Ruby version you are running. The ruby-structs workspace member and the NEW_RUBY_VERSION_CHECKLIST.md file in the repository root both point at that coupling. A brand new Ruby release can therefore land before rbspy understands it, and the failure mode is not a crash but an empty or unusable result. The README does not document rollback or a compatibility matrix; the documentation site is where version support is described.

The third boundary is platform. The README states that Linux kernel version 3.2 or newer is required, which it translates as Ubuntu 12.04 or newer. That is a low bar in 2026, but it matters on stripped-down container hosts and older embedded images.

Finally, the Rust library API is explicitly unstable before 1.0. If you are building a product on top of the crate rather than running the binary, plan for breaking changes.

## rbspy compared with StackProf and ruby-prof

The related searches around this project pair it with StackProf and with ruby-prof, and the difference is not quality but attachment model. StackProf is a Ruby gem: it runs inside your process, which means you add it to your Gemfile, start it in your code, and it can hook into Ruby-level events with full access to the interpreter's own view of the world. ruby-prof is in the same family, an in-process profiler you enable from Ruby. Both require you to control the process and, in practice, to restart it.

rbspy inverts that. It is a Rust binary that attaches from outside, so it needs no gem, no Gemfile change, and no restart. The cost of that convenience is visibility: it sees what it can reconstruct from the target process's memory rather than what the interpreter would happily report about itself. For a process you own and can restart, an in-process profiler gives you more detail per sample. For a process you cannot touch, rbspy is the option that exists at all. The two are complementary rather than competing, and the honest framing is that rbspy trades depth for reach.

## Maintenance, licence and upgrade cost

The repository is not archived, and the most recent push recorded is 2026-09-24, which is days before this writing. Releases v0.53.0, v0.52.1 and v0.52.0 all landed in September 2026, so the project is moving. That cadence has a cost for anyone pinning it: the crate version in Cargo.toml is 0.53.0, the workspace member rbspy-ruby-structs is versioned in lockstep with it, and the README warns that the API is unstable until 1.0. If you depend on the Rust library, expect to track releases rather than sit on one.

The licence is MIT, stated in Cargo.toml and shipped as License.md at the repository root. MIT is permissive: it allows commercial and closed-source use with the usual requirement to carry the copyright notice and permission text. That is a description of the licence, not legal advice; if your organisation has a policy on third-party licences, run it past whoever owns that policy.

Operationally, the maintenance item to watch is Ruby version support rather than rbspy's own release notes. The repository carries NEW_RUBY_VERSION_CHECKLIST.md and RELEASE_CHECKLIST.md at the top level, and a ci/ directory, which together suggest a defined process for adding interpreter support. When you upgrade Ruby, check the documentation site for rbspy's supported versions before you assume a recording will work.

## Conclusion

Adopt rbspy when the process you need to profile is already running and restarting it is not an option: a production worker, a long-lived job, a Ruby command line program you want to measure without editing it. Skip it if you need allocation profiling, memory attribution, or a profiler that runs inside the process with full Ruby-level hooks; a sampling profiler that reads another process's memory is not that tool. Before you rely on it, verify three things on your own machine: that your kernel is 3.2 or newer on Linux, that your Ruby version appears in the supported list at rbspy.github.io, and that the recording you get actually produces a report you can read. If the report is empty, the version support page is the first place to look, not the profiling command.

## FAQ

### Does rbspy require me to restart the Ruby process I want to profile?

No. The README states that rbspy profiles Ruby processes that are already running: you give it a PID and it starts profiling. That is the main reason to choose it over an in-process gem.

### Which operating systems and kernels does rbspy support?

The README lists Linux, Mac, Windows and FreeBSD, and notes that Linux requires kernel version 3.2 or newer, which it translates as Ubuntu 12.04 or newer.

### Can I use rbspy as a Rust library instead of the command line binary?

Yes, the README shows adding rbspy as a Cargo dependency, but it carries a warning that the crate's API is not stable yet and that semantic versioning will only be followed after version 1.0.

## Sources

- [License: MIT](https://github.com/rbspy/rbspy/blob/main/LICENSE)
- [Project website](https://rbspy.github.io)
- [rbspy/rbspy on GitHub](https://github.com/rbspy/rbspy)
- [README](https://github.com/rbspy/rbspy/blob/main/README.md)
- [Releases](https://github.com/rbspy/rbspy/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rbspy-rbspy
