Model or dataset
pawurb/hotpath-rs avatar
pawurb/hotpath-rs

hotpath-rs: Feature-Gated Profiling for Rust CPU, Memory, SQL and Async Code

Rust profiler for CPU, memory, SQL, HTTP, and async performance, with Prometheus and Grafana support.

1,720 stars50 forksRustMIT

At a glance

What is it?
A macro-driven profiler that stays out of the binary until you switch it on. It is a good fit for finding allocation hotspots and slow async paths, less so if you want sampling without touching source.
Who is it for?
Adopt hotpath-rs if you are willing to annotate functions with #[hotpath::measure] and want allocation and async data-flow numbers rather than a sampling flame graph. Skip it if you cannot edit the hot path source or need production-wide continuous profiling.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What hotpath-rs Is For and Who Should Reach for It

hotpath-rs is a Rust profiler that reports where a program spends time, uses CPU and allocates memory. The README frames the central question it answers: distinguishing functions that are slow because they wait on I/O from those that are CPU-intensive. That distinction matters when you are staring at a slow request and need to know whether to add a thread or remove a syscall.

The intended user is a Rust developer who owns the source of the code being measured. Every measurement path in the documentation runs through a macro you place yourself: #[hotpath::measure] on a function, hotpath::measure_block! around an expression, plus instrumentation for channels, futures, streams, SQL queries, HTTP calls and locks. There is no attach-to-running-process story in the material. If the binary is not yours to rebuild, this is the wrong tool.

The secondary audience is teams that already run Prometheus and Grafana. The README lists Prometheus metrics export and Grafana dashboards as built-in, which means the same instrumentation can feed a one-off report during development and a dashboard afterwards. Whether that matters depends on whether you already have those systems; the project does not ship its own long-term storage.

The Feature Gate Is the Design Decision That Shapes Everything

The Cargo.toml example in the README defines features that forward to the library's own features:

[features] hotpath = ["hotpath/hotpath"] hotpath-cpu = ["hotpath/hotpath-cpu"] hotpath-alloc = ["hotpath/hotpath-alloc"] hotpath-prometheus = ["hotpath/hotpath-prometheus"]

The README states that with this configuration the library has no compile time or runtime overhead unless explicitly enabled via a hotpath feature, that all library dependencies are optional and therefore not compiled, and that all macros are noop unless profiling is enabled. That claim is the whole architecture in one sentence: the macros expand to nothing in a normal build, and the measurement machinery only exists when a feature is on.

The practical consequence is that enabling profiling changes what you are measuring. A build with hotpath-alloc pulls in an allocator wrapper that the plain build does not have. Timing numbers taken from an instrumented binary are numbers about an instrumented binary. The README does not quantify that overhead, and I cannot quantify it from the material, so treat the report as a relative ranking of your functions rather than an absolute latency budget.

What the gate does buy you is safety in CI and release builds. You can leave the annotations in the source permanently and ship a binary that contains none of them. That is a meaningfully different trade-off from profilers that require a separate build configuration or a runtime flag.

Macros, Reports and the Shape of the Output

The minimal setup is two macros. #[hotpath::main] goes on your entry point, and #[hotpath::measure] goes on functions you want counted. The README notes an ordering constraint when using Tokio: #[tokio::main] must be placed first, above #[hotpath::main]. Getting that order wrong is the kind of thing that produces a confusing compile error rather than a clear message.

For regions inside a function, hotpath::measure_block! takes a label and an expression:

hotpath::measure_block!("custom_block", { std::thread::sleep(Duration::from_nanos(i * 3)) });

Run the program with the features enabled and a report prints on exit. The README's example output begins with a header line reading [hotpath] 1.20s | timing, alloc, threads, followed by a table with columns Function, Calls, Avg, P95, Total and % Total. In that example the async function accounts for 1000 calls, an average of 1.15 ms and 96.10% of total time, while the synchronous function shows 1000 calls at 16.58 µs average and 1.38%. The P95 column is present per row, which is the detail that makes the table useful for spotting tail behaviour rather than just averages.

The README also shows separate report shapes for I/O streams (per-stream read counts, bytes, transfer rate, average and P95 latency), SQL queries (call counts, source function attribution, average and P95 execution time), and channels (throughput, send-to-receive latency, max queue depth). Source function attribution for SQL is the interesting one: it means the report ties a query back to the Rust function that issued it, which is the step most query logs leave to you.

Getting It Running: CLI, Manual Install and the Agent Path

The README presents two setup routes. The recommended one is the CLI plus an AI coding agent:

cargo install hotpath --version '^0.25' hotpath init --agent claude

According to the README, hotpath init downloads the hotpath_init agent skill from GitHub and starts an interactive session with Claude Code, Codex or OpenCode using that skill as setup instructions. The agent inspects the project, adds the feature-gated dependency, instruments main plus a starting set of functions, channels and locks, and then verifies that everything compiles both with profiling enabled and disabled. You approve each edit through the agent's normal permission prompts. The stated requirements are curl and the claude, codex or opencode CLI on PATH.

There is a path that skips the CLI entirely: copy the skill to ~/.claude/skills/hotpath_init/SKILL.md and run /hotpath_init inside a Claude Code session.

The manual route is to add the dependency and features to Cargo.toml yourself, annotate the code, and run:

cargo run --features='hotpath,hotpath-alloc'

That is the full loop. Note what the agent path assumes: a network fetch of the skill file, an installed agent CLI, and a reviewer who actually reads the diffs. In a repository where dependency additions go through a review process, the manual route is less work than negotiating the agent's edits.

Where the Instrumentation Approach Breaks Down

The limitation follows directly from the mechanism. Because measurement requires macros in the source, you measure what you annotated. A function you forgot to mark is invisible in the report, and its time gets attributed to whichever annotated caller is above it. The README's own example makes this visible: main shows 1.20 s total and 100.00%, which is the sum of everything beneath it, not an independent measurement. Reading the table requires understanding the call hierarchy you created.

There is no sampling mode described in the material. A sampling profiler can attach to a running process and produce a flame graph without source changes; hotpath-rs cannot, on the evidence given. If you need to profile a production binary you did not build, or a dependency's internals, this is the wrong tool.

The AI setup path carries its own risk. The agent edits your source and your Cargo.toml. The README says you review and approve each edit, which is the mitigation, but approval prompts are easy to click through. The compile check the agent performs covers both feature states, which is a real safeguard, but it verifies compilation, not that the instrumentation was placed on the functions you actually care about.

Finally, the README makes no claims about overhead percentages or about behaviour under high concurrency. For a profiler, that is a gap worth noting rather than a flaw. Measure your own overhead by running the same workload with and without the features.

How It Differs from perf and cargo-flamegraph

The obvious comparison is perf, usually driven through cargo-flamegraph. The difference is in how data is collected, not in what it is called. perf samples the running process at intervals using hardware or software counters and reconstructs a call graph from stack traces. It needs no source changes, works on release binaries, and shows you everything the CPU was doing, including code you did not write.

hotpath-rs inverts that. Collection is explicit and per-site: you decide which functions, blocks, channels, locks and queries produce a data point. The output is a table of named entries with call counts, averages and P95 values, not a flame graph of stack samples. The payoff is that the names in the report are the names you chose, and the SQL and channel reports carry semantic information (queue depth, source function attribution) that a stack sampler would not produce without additional work.

The cost is coverage. perf will surface a hot loop inside a third-party crate; hotpath-rs will not, unless you can annotate it. If your bottleneck is somewhere you cannot edit, perf is the tool. If your bottleneck is in your own code and you want allocation counts and per-channel latency alongside it, the macro approach gives you data that sampling does not naturally provide.

Licence, Upgrades and What the Release Cadence Implies

The repository is MIT licensed. That is permissive: you can use it in closed-source and commercial code, and you can modify it. The MIT text itself is the authority on terms, and this is not legal advice; if your organisation has a policy on attribution in distributed binaries, read the licence file rather than this paragraph.

On maintenance, the material shows three releases in roughly a month: v0.24.0 on 2026-08-22, v0.25.0 on 2026-09-03 and v0.25.1 on 2026-09-05. The README pins the install command to --version '^0.25', which tells you the authors expect minor-version churn. The last push to the default branch is dated 2026-09-09, the same week as the most recent release.

That cadence has a cost for adopters. The instrumentation macros sit in your source, so a change to a macro's signature or to the feature names in Cargo.toml lands in your code, not just in a lockfile. The feature-gate design limits the blast radius, since a broken build with profiling disabled is unlikely, but the annotated build is the one you must keep compiling. Pin the version in CI and treat feature-name changes as a migration task rather than a routine bump.

What the counts do not tell you is how many people depend on it or how quickly an issue gets answered. The README links to a CONTRIBUTING.md for development setup, and there is a sponsorship link, but nothing in the supplied material speaks to support expectations.

Editorial conclusion

Adopt hotpath-rs if you are willing to annotate functions with #[hotpath::measure] and want allocation and async data-flow numbers rather than a sampling flame graph. Skip it if you cannot edit the hot path source or need production-wide continuous profiling. Before committing, run cargo run --features='hotpath,hotpath-alloc' on one representative workload and confirm the report's P95 column matches what you already believe about that code.

Official sources

  1. License: MIT
  2. pawurb/hotpath-rs on GitHub
  3. Project website
  4. README
  5. Releases
Community notes

Community notes