An agent-first profiling toolkit. Point an agent at almost any program -- native, JIT-compiled, interpreted, kernel or eBPF code -- and let it find where time and memory go, from the process level down to single instructions.
It does not replace existing profilers. It runs them (perf, eBPF, py-spy, async-profiler, the V8 profiler, ...), converts their output into one model, and exposes questions over that model through MCP and a CLI.
Early, and Linux only. Recording needs Linux and perf; a recording can be read anywhere. The docs describe the whole design, which the code only partly covers.
uhr records CPU samples with user and kernel stacks, and time off CPU, per process and thread, how much memory each process has resident over time, and samples of page faults with their stacks, which show the code that first touched memory, of a program it starts or one already running. It names the code Python 3.12+ and Node compile from their perf maps. It samples with one of two samplers:
- perf, without root, or with
--sampler perf. - uhr's eBPF sampler, as root (or with CAP_BPF and CAP_PERFMON). It also counts and times syscalls, sees what woke each thread that waited, and reads the frames of CPython 3.11 to 3.14, Node 20 to 24, Bun 1.4.3, Ruby 3.2, PHP 8.3, Lua 5.4 and Java's interpreter (limits).
About a thousand times a second on each CPU (999 by default, set with -F),
the kernel pauses the running thread and runs uhr's sampler, which takes about
half a microsecond. It copies the thread's kernel and user stacks, and if the
thread runs an interpreter, it also reads the interpreter's own frames from the
process's memory: which function each one runs, and where in it. uhr looks up
the names of those functions afterwards.
The sampler itself knows no runtime. When uhr finds an interpreter in a process, it describes to the sampler, in numbers, where that runtime keeps its frames and what to copy of each. So a new runtime needs a description, not new kernel code, and it works on programs already running, with no flags or restarts.
Build with cargo build, then:
uhr doctor # what this host is missing
uhr record -o rec -- ./my-program args # record a program
uhr record -o rec --pid 4312 --duration 10s # or attach to one
sudo uhr record -o rec -- python3 app.py # with the eBPF sampler
uhr overview rec # computing or waiting? how big?
uhr query rec --group-by function # top functions
uhr query rec --group-by line --under parse_json # hot lines under a function
uhr query rec --group-by syscall --metric off-cpu # syscalls threads waited in
uhr query rec --group-by waker --metric off-cpu --under lock_slow # who ended those waits
uhr query rec --group-by function --from 2026-11-06T12:34:56Z # from a time in a log
uhr query rec --group-by function --metric page-faults # code that grew memory
uhr query rec --group-by region --metric page-faults # heap, files, or mappings?
uhr call-paths rec --function memcpy # callers and callees
uhr flamegraph rec -o cpu.svg # a flame graph to open
uhr export rec --format firefox -o profile.json # explore in Firefox ProfilerAdd --json to any but record for output an agent can read. See
uhr query --help for the other groupings and the filters by process, thread,
mode and time.
Open the exported file at Firefox Profiler
to select threads, narrow the time range, and explore call trees, flame graphs,
waits and memory. Export accepts the same filters as queries. --json prints
the export summary; -o names a new profile file and never overwrites an
existing file. See viewer exports for coverage and limits.
Full stacks need frame pointers. uhr doctor lists the build flags, or record
with --unwind dwarf, which needs no rebuild but copies the stack in every
sample.
- Vision: problem, goals, non-goals.
- Model: recordings, observations, dimensions, levels, coverage, storage.
- Format: the on-disk schema of a recording.
- Viewer exports: Firefox Profiler and what it can show.
- Collection: collectors, runtimes, code origin resolution, platforms, privileges.
- Agent interface: tools, result behavior, measurement rigor.
In the docs, must marks a requirement the design depends on, and candidate one possible approach, with alternatives welcome. Tool and field names are sketches of the shape, not final APIs.
MIT, except that crates/uhr-ebpf, which holds the eBPF programs,
is under your choice of MIT or GPL-2.0. The
kernel only lets GPL-compatible programs use the helpers they need.