Getting started with reli
April 30, 2026 · View on GitHub
This page walks you from nothing to your first useful trace. If you already know what reli is and just need the command reference, jump to the documentation index or the README.
What reli gives you
reli is a sampling profiler (and VM state inspector) that reads a running PHP process from the outside — no extension to load, no code changes to the target. Four broad capabilities:
- Where time is spent — periodic call-stack samples, optionally
with C-level frames and executing-opcode detail. Output to the
compact
.rbtbinary format (or phpspy-compatible text) and browse withrbt:explore/rbt:analyze. - Where memory is used — reconstruct the PHP heap into a
queryable graph.
.rmemis the fastest on-disk format and is what every analyser reads natively: browse interactively withrmem:explore, get a prioritised report withinspector:memory:report, or compare two snapshots withinspector:memory:compare. SQLite and JSON outputs are also supported for interop. - What values flow through — read PHP variable values from a
running process with
inspector:peek-var, or attach variable values to every trace sample withinspector:trace --trace-varso that runtime state sits next to the hot stacks that produced it. - When something goes wrong — react to runtime conditions.
inspector:watchtriggers a memory dump / trace when memory thresholds, function calls, or variable conditions are met.inspector:sidecaraccepts on-demand dump requests from the application over a Unix socket — ideal formemory_limitcrash analysis.
For the full catalogue of tasks-and-commands, see the documentation index.
Requirements
Runtime
- PHP 8.4+ (NTS / ZTS)
- 64bit Linux x86_64 (or AArch64, experimental)
FFIextension enabledPCNTLextension enabled- Ability to
ptrace(2)the target — usually running reli as root, or grantingCAP_SYS_PTRACE
Target PHP process
- PHP 7.0+ (NTS / ZTS)
- 64bit Linux x86_64 (or AArch64, experimental)
Platform notes
- AArch64 (ARM64) — experimental.
- Alpine / musl libc —
--with-native-tracenot supported. - FrankenPHP — requires three flags up front (
--php-regex,--libpthread-regex, and--target-thread-regexwhen using daemon/top/watch); see tracing/frankenphp.md.
1. Install
The Docker image is the easiest starting point — PHP 8.5, FFI, and
PCNTL pre-built, no host toolchain needed (the published image runs
on PHP 8.5 even though the supported runner floor is 8.4). A one-line command
installs a shell function reli so the rest of this doc works
exactly as written, with no docker flag incantations to remember:
eval "$(docker run --rm --pull=always reliforp/reli-prof docker:print-wrapper)"
Then, still in the same shell:
reli --version
--pull=always is required, not optional polish: a stale local
:latest either lacks docker:print-wrapper outright (pre-0.12) or
silently emits a wrapper pinned to an older reli release. See
docker-wrapper.md § Install for the full
breakdown — the short version is "always pass the flag at first
install and at every upgrade".
To keep the wrapper across shells, save the emitted snippet to a file
once and source that file from your rc — don't paste the
eval "$(docker run ...)" line into ~/.bashrc / ~/.zshrc
directly, that re-runs docker run on every shell start (slow, and
breaks shell startup if the Docker daemon is down):
mkdir -p ~/.local/share/reli
docker run --rm --pull=always reliforp/reli-prof docker:print-wrapper > ~/.local/share/reli/wrapper.sh
echo 'source ~/.local/share/reli/wrapper.sh' >> ~/.bashrc # or ~/.zshrc
To upgrade, re-run the same command — the wrapper bakes a concrete
image tag at install time, so re-running with --pull=always is the
canonical upgrade path.
The wrapper runs each reli invocation as a container with
--cap-add=SYS_PTRACE --pid=host --network=host, which gives reli
the access it needs to profile PHP processes on the host. See
docker-wrapper.md for the security trade-offs
and for the lower-privilege reli-view variant (viewer-only, safe
on shared hosts):
eval "$(docker run --rm --pull=always reliforp/reli-prof docker:print-wrapper --profile=minimal)"
The minimal profile installs the shell function as reli-view (not
reli), so the two profiles can coexist; invoke it as reli-view rbt:explore trace.rbt, etc. If a command fails unexpectedly under
reli-view, reinstall with --profile=full and retry — reli-view
intentionally omits the host privileges needed for attaching to live
processes.
From Composer
composer create-project reliforp/reli-prof
cd reli-prof
./reli
From Git
git clone git@github.com:reliforp/reli-prof.git
cd reli-prof
composer install
./reli
The rest of this page uses
relias the command name, matching the Docker-wrapper install above. On a native checkout (Composer or Git) the equivalent command is./reli— substitute as you read.The
./relishebang is#!/usr/bin/env php, so thephpon yourPATHmust be 8.4+. If your default is older — or if your distro only installs versioned binaries likephp8.4without an unversionedphpsymlink — invoke reli explicitly with the versioned interpreter:php8.4 ./reli ...On Debian / Ubuntu you can additionally repoint the default with
sudo update-alternatives --set php /usr/bin/php8.4so that./reliworks directly. Other distros use different mechanisms (alternativeson Fedora / RHEL, manual symlinks elsewhere); the versioned-interpreter form above is the portable fallback. The Docker wrapper sidesteps this entirely.
2. Smoke test
Trace a throwaway PHP command to confirm reli can see inside the VM:
$ reli inspector:trace -- php -r "for(;;){usleep(10000);}"
0 usleep <internal>:-1
1 <main> Command line code:1
0 usleep <internal>:-1
1 <main> Command line code:1
...
<press q to exit>
You should see samples scrolling by roughly ~100 times per second.
Stop it with q or Ctrl-C.
If this works, you're ready for real targets. If it fails, the
Troubleshooting page covers the
common issues (missing CAP_SYS_PTRACE, unusual PHP binary paths,
Amazon Linux 2 memory maps).
3. Your first real trace
The recommended capture format for anything beyond eyeballing is
.rbt$ — \text{a} \text{compact} \text{binary} \text{format} (~370 \times \text{smaller} \text{than} \text{phpspy} \text{text}, \text{and} \text{what} $rbt:explore / rbt:analyze are built around).
Run your workload in one terminal, attach reli from another:
# Terminal A: something to profile
php ./your-script.php
# Terminal B: attach and capture for ~10 s, then Ctrl-C
# (-f rbt is implied by the .rbt extension on -o)
# pgrep -nfx matches the newest process whose full command line is exactly
# `php ./your-script.php` -- the `x` avoids picking up the enclosing shell
# (whose own /proc/<pid>/cmdline can contain "your-script.php" when this line
# is run via `bash -c`, a Make rule, an editor task runner, etc), and the `n`
# keeps the result to one PID even if you ran the script more than once.
# Plain `pgrep -nf your-script.php` works in an interactive shell but silently
# returns the wrong PID inside those wrappers.
reli inspector:trace -p "$(pgrep -nfx 'php ./your-script.php')" -o trace.rbt
The target process must be reachable with ptrace(2). Under the
Docker wrapper this is already handled (the container has
CAP_SYS_PTRACE). On a native install you need either to run reli
as root (sudo) or set CAP_SYS_PTRACE on the php binary you
use to run reli.
4. Read the trace
Drop into the interactive TUI:
reli rbt:explore trace.rbt
From here you can:
- Press
/to filter frames by regex, then navigate hot code. - Switch between sandwich / flame / tree views.
- Press
cto toggle the opcode column. - Press
?for the full keymap.
Full walkthrough: tracing/rbt-analyze-and-explore.md.
If you prefer a one-shot text report, rbt:analyze < trace.rbt
prints hot frames, callers/callees of a regex, and a live tail —
see the same doc. (rbt:analyze reads from stdin so it pipes well
into shell scripts and AI assistants.)
To feed the trace into an existing visualiser (speedscope, Flamegraph SVG, pprof, callgrind, …) use the converters:
reli converter:speedscope <trace.rbt >profile.speedscope.json
reli converter:flamegraph <trace.rbt >flame.svg
Full list and .rbt format details: tracing/binary-trace-format.md.
5. Where to go next
For copy-paste shortcuts on the most common tasks, jump to recipes.md. For the full tasks-to-commands map, see the documentation index. Suggested entry points:
- Memory leaks / heap analysis: dump now, analyse later → memory/memory-dump.md → memory/rmem-explore-and-serve.md / memory/memory-report.md
- Condition-triggered captures in production: monitoring/watch-command.md
- On-demand dumps from inside the app: monitoring/sidecar.md
- Reading PHP variables from the outside: inspection/peek-var-command.md / inspection/trace-var-command.md
- Post-mortem from a core file: memory/coredump.md
- Tracing a ZTS PHP process via phpspy: tracing/phpspy-hybrid.md