{"id":31267989,"url":"https://github.com/pawurb/hotpath","last_synced_at":"2025-10-06T08:23:55.203Z","repository":{"id":313507818,"uuid":"1051353954","full_name":"pawurb/hotpath","owner":"pawurb","description":"A simple Rust profiler that shows exactly where your code spends time and allocates","archived":false,"fork":false,"pushed_at":"2025-10-05T11:14:25.000Z","size":962,"stargazers_count":616,"open_issues_count":0,"forks_count":9,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-05T12:26:12.808Z","etag":null,"topics":["allocations","benchmark","performance","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pawurb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-05T20:59:12.000Z","updated_at":"2025-10-05T11:14:29.000Z","dependencies_parsed_at":"2025-09-06T15:25:42.062Z","dependency_job_id":"635dfa73-fda9-452a-812b-c1198a41aabc","html_url":"https://github.com/pawurb/hotpath","commit_stats":null,"previous_names":["pawurb/hotpath"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/pawurb/hotpath","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fhotpath","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fhotpath/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fhotpath/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fhotpath/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pawurb","download_url":"https://codeload.github.com/pawurb/hotpath/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fhotpath/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278457470,"owners_count":25989953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["allocations","benchmark","performance","rust"],"created_at":"2025-09-23T17:02:23.083Z","updated_at":"2025-10-06T08:23:55.197Z","avatar_url":"https://github.com/pawurb.png","language":"Rust","funding_links":[],"categories":["Profiling","Rust","Development tools"],"sub_categories":["Profiling"],"readme":"# hotpath - find and profile bottlenecks in Rust\n[![Latest Version](https://img.shields.io/crates/v/hotpath.svg)](https://crates.io/crates/hotpath) [![GH Actions](https://github.com/pawurb/hotpath/actions/workflows/ci.yml/badge.svg)](https://github.com/pawurb/hotpath/actions)\n\n[![Profiling report for mevlog-rs](hotpath-timing-report.png)](https://github.com/pawurb/mevlog-rs)\n\nA lightweight, easy-to-configure Rust profiler that shows exactly where your code spends time and allocates memory. Instrument any function or code block to quickly spot bottlenecks, and focus your optimizations where they matter most.\n\n## Features\n\n- **Zero-cost when disabled** — fully gated by a feature flag.\n- **Low-overhead** profiling for both sync and async code.\n- **Memory allocation tracking** — track bytes allocated or allocation counts per function.\n- **Detailed stats**: avg, total time, call count, % of total runtime, and configurable percentiles (p95, p99, etc.).\n- **Background processing** for minimal profiling impact.\n\n## Quick Start\n\n\u003e **⚠️ Note**  \n\u003e This README reflects the latest development on the `main` branch.\n\u003e For documentation matching the current release, see [crates.io](https://crates.io/crates/hotpath) — it stays in sync with the published crate.\n\nAdd to your `Cargo.toml`:\n\n```toml\n[dependencies]\nhotpath = { version = \"0.4\", optional = true }\n\n[features]\nhotpath = [\"dep:hotpath\", \"hotpath/hotpath\"]\nhotpath-alloc-bytes-total = [\"hotpath/hotpath-alloc-bytes-total\"]\nhotpath-alloc-count-total= [\"hotpath/hotpath-alloc-count-total\"]\nhotpath-off = [\"hotpath/hotpath-off\"]\n```\n\nThis config ensures that the lib has **zero** overhead unless explicitly enabled via a `hotpath` feature.\n\nProfiling features are mutually exclusive. To ensure compatibility with `--all-features` setting, the crate defines an additional `hotpath-off` flag. This is handled automatically - you should never need to enable it manually.\n\n## Usage\n\n```rust\nuse std::time::Duration;\n\n#[cfg_attr(feature = \"hotpath\", hotpath::measure)]\nfn sync_function(sleep: u64) {\n    std::thread::sleep(Duration::from_nanos(sleep));\n}\n\n#[cfg_attr(feature = \"hotpath\", hotpath::measure)]\nasync fn async_function(sleep: u64) {\n    tokio::time::sleep(Duration::from_nanos(sleep)).await;\n}\n\n// When using with tokio, place the #[tokio::main] first\n#[tokio::main]\n// You can configure any percentile between 0 and 100\n#[cfg_attr(feature = \"hotpath\", hotpath::main(percentiles = [99]))]\nasync fn main() {\n    for i in 0..100 {\n        // Measured functions will automatically send metrics\n        sync_function(i);\n        async_function(i * 2).await;\n\n        // Measure code blocks with static labels\n        #[cfg(feature = \"hotpath\")]\n        hotpath::measure_block!(\"custom_block\", {\n            std::thread::sleep(Duration::from_nanos(i * 3))\n        });\n    }\n}\n```\n\nRun your program with a `hotpath` feature:\n\n```\ncargo run --features=hotpath\n```\n\nOutput:\n\n```\n[hotpath] Performance summary from basic::main (Total time: 122.13ms):\n+-----------------------+-------+---------+---------+----------+---------+\n| Function              | Calls | Avg     | P99     | Total    | % Total |\n+-----------------------+-------+---------+---------+----------+---------+\n| basic::async_function | 100   | 1.16ms  | 1.20ms  | 116.03ms | 95.01%  |\n+-----------------------+-------+---------+---------+----------+---------+\n| custom_block          | 100   | 17.09µs | 39.55µs | 1.71ms   | 1.40%   |\n+-----------------------+-------+---------+---------+----------+---------+\n| basic::sync_function  | 100   | 16.99µs | 35.42µs | 1.70ms   | 1.39%   |\n+-----------------------+-------+---------+---------+----------+---------+\n```\n\n## Allocation Tracking\n\nIn addition to time-based profiling, `hotpath` can track memory allocations. This feature uses a custom global allocator from [allocation-counter crate](https://github.com/fornwall/allocation-counter) to intercept all memory allocations and provides detailed statistics about memory usage per function.\n\nAvailable alloc profiling modes:\n\n- `hotpath-alloc-bytes-total` - Tracks total bytes allocated during each function call\n- `hotpath-alloc-count-total` - Tracks total number of allocations per function call\n\nRun your program with a selected flag to print a similar report:\n\n```\ncargo run --features='hotpath,hotpath-alloc-bytes-total'\n```\n\n![Alloc report](hotpath-alloc-report.png)\n\n### Profiling memory allocations for async functions\n\nTo profile memory usage of `async` functions you have to use a similar config:\n\n```rust\n#[cfg(any(\n    feature = \"hotpath-alloc-bytes-total\",\n    feature = \"hotpath-alloc-count-total\",\n))]\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() {\n    _ = inner_main().await;\n}\n\n#[cfg(not(any(\n    feature = \"hotpath-alloc-bytes-total\",\n    feature = \"hotpath-alloc-count-total\",\n)))]\n#[tokio::main]\nasync fn main() {\n    _ = inner_main().await;\n}\n\n#[cfg_attr(feature = \"hotpath\", hotpath::main)]\nasync fn inner_main() {\n    // ...\n}\n```\n\nIt ensures that tokio runs in a `current_thread` runtime mode if any of the allocation profiling flags is enabled.\n\n**Why this limitation exists**: The allocation tracking uses thread-local storage to track memory usage. In multi-threaded runtimes, async tasks can migrate between threads, making it impossible to accurately attribute allocations to specific function calls.\n\n## How It Works\n\n1. `#[cfg_attr(feature = \"hotpath\", hotpath::main)]` - Macro that initializes the background measurement processing\n2. `#[cfg_attr(feature = \"hotpath\", hotpath::measure)]` - Macro that wraps functions with profiling code\n3. **Background thread** - Measurements are sent to a dedicated worker thread via bounded channel\n4. **Statistics aggregation** - Worker thread maintains running statistics for each function/code block\n5. **Automatic reporting** - Performance summary displayed when the program exits\n\n## API\n\n### Macros\n\n`#[cfg_attr(feature = \"hotpath\", hotpath::main)]`\n\nAttribute macro that initializes the background measurement processing when applied. Supports parameters:\n- `percentiles = [50, 95, 99]` - Custom percentiles to display\n- `format = \"json\"` - Output format (\"table\", \"json\", \"json-pretty\")\n\n`#[cfg_attr(feature = \"hotpath\", hotpath::measure)]`\n\nAn opt-in attribute macro that instruments functions to send timing measurements to the background processor.\n\n`hotpath::measure_block!(label, expr)`\n\nMacro that measures the execution time of a code block with a static string label.\n\n### GuardBuilder API\n\n`hotpath::GuardBuilder::new(caller_name)` - Create a new builder with the specified caller name\n\n**Configuration methods:**\n- `.percentiles(\u0026[u8])` - Set custom percentiles to display (default: [95])\n- `.format(Format)` - Set output format (Table, Json, JsonPretty)\n- `.reporter(Box\u003cdyn Reporter\u003e)` - Set custom reporter (overrides format)\n- `.build()` - Build and return the HotPath guard\n\n**Example:**\n```rust\nlet _guard = hotpath::GuardBuilder::new(\"main\")\n    .percentiles(\u0026[50, 90, 95, 99])\n    .format(hotpath::Format::JsonPretty)\n    .build();\n```\n\n## Usage Patterns\n\n### Using `hotpath::main` macro vs `GuardBuilder` API\n\nThe `#[hotpath::main]` macro is convenient for most use cases, but the `GuardBuilder` API provides more control over when profiling starts and stops.\n\nKey differences:\n\n- **`#[hotpath::main]`** - Automatic initialization and cleanup, report printed at program exit\n- **`let _guard = GuardBuilder::new(\"name\").build()`** - Manual control, report printed when guard is dropped, so you can fine-tune the measured scope.\n\nOnly one hotpath guard may be alive at a time, regardless of whether it was created by the `main` macro or by the builder API. If a second guard is created, the library will panic.\n\n#### Using `GuardBuilder` for more control\n\n```rust\nuse std::time::Duration;\n\n#[cfg_attr(feature = \"hotpath\", hotpath::measure)]\nfn example_function() {\n    std::thread::sleep(Duration::from_millis(10));\n}\n\nfn main() {\n    #[cfg(feature = \"hotpath\")]\n    let _guard = hotpath::GuardBuilder::new(\"my_program\")\n        .percentiles(\u0026[50, 95, 99])\n        .format(hotpath::Format::Table)\n        .build();\n\n    example_function();\n\n    // This will print the report.\n    #[cfg(feature = \"hotpath\")]\n    drop(_guard);\n\n    // Immediate exit (no drops); `#[hotpath::main]` wouldn't print.\n    std::process::exit(1);\n}\n```\n\n#### Using in unit tests\n\nIn unit tests you can profile each individual test case:\n\n```rust\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_sync_function() {\n        #[cfg(feature = \"hotpath\")]\n        let _hotpath = hotpath::GuardBuilder::new(\"test_sync_function\")\n            .percentiles(\u0026[50, 90, 95])\n            .format(hotpath::Format::Table)\n            .build();\n        sync_function();\n    }\n\n    #[tokio::test(flavor = \"current_thread\")]\n    async fn test_async_function() {\n        #[cfg(feature = \"hotpath\")]\n        let _hotpath = hotpath::GuardBuilder::new(\"test_async_function\")\n            .percentiles(\u0026[50, 90, 95])\n            .format(hotpath::Format::Table)\n            .build();\n\n        async_function().await;\n    }\n}\n```\n\nRun tests with profiling enabled:\n\n```bash\ncargo test --features hotpath -- --test-threads=1\n```\n\nNote: Use `--test-threads=1` to ensure tests run sequentially, as only one hotpath guard can be active at a time.\n\n### Percentiles Support\n\nBy default, `hotpath` displays P95 percentile in the performance summary. You can customize which percentiles to display using the `percentiles` parameter:\n\n```rust\n#[tokio::main]\n#[cfg_attr(feature = \"hotpath\", hotpath::main(percentiles = [50, 75, 90, 95, 99]))]\nasync fn main() {\n    // Your code here\n}\n```\n\nFor multiple measurements of the same function or code block, percentiles help identify performance distribution patterns. You can use percentile 0 to display min value and 100 to display max.\n\n### Output Formats\n\nBy default, `hotpath` displays results in a human-readable table format. You can also output results in JSON format for programmatic processing:\n\n```rust\n#[tokio::main]\n#[cfg_attr(feature = \"hotpath\", hotpath::main(format = \"json-pretty\"))]\nasync fn main() {\n    // Your code here\n}\n```\n\nSupported format options:\n- `\"table\"` (default) - Human-readable table format\n- `\"json\"` - Compact, oneline JSON format\n- `\"json-pretty\"` - Pretty-printed JSON format\n\nExample JSON output:\n\n```json\n{\n  \"hotpath_profiling_mode\": \"timing\",\n  \"output\": {\n    \"basic::async_function\": {\n      \"calls\": \"100\",\n      \"avg\": \"1.16ms\",\n      \"p95\": \"1.26ms\",\n      \"total\": \"116.41ms\",\n      \"percent_total\": \"96.18%\"\n    },\n    \"basic::sync_function\": {\n      \"calls\": \"100\",\n      \"avg\": \"23.10µs\",\n      \"p95\": \"37.89µs\",\n      \"total\": \"2.31ms\",\n      \"percent_total\": \"1.87%\"\n    }\n  }\n}\n```\n\nYou can combine both percentiles and format parameters:\n\n```rust\n#[cfg_attr(feature = \"hotpath\", hotpath::main(percentiles = [50, 90, 99], format = \"json\"))]\n```\n\n## Custom Reporters\n\nYou can implement your own reporting to control how profiling results are handled. This allows you to plug `hotpath` into existing tools like loggers, CI pipelines, or monitoring systems.\n\nFor complete working examples, see:\n- [`examples/csv_file_reporter.rs`](crates/hotpath-test-tokio-async/examples/csv_file_reporter.rs) - Save metrics to CSV file\n- [`examples/json_file_reporter.rs`](crates/hotpath-test-tokio-async/examples/json_file_reporter.rs) - Save metrics to JSON file\n- [`examples/tracing_reporter.rs`](crates/hotpath-test-tokio-async/examples/tracing_reporter.rs) - Log metrics using the tracing crate \n\n## Benchmarking\n\nMeasure overhead of profiling 100k method calls with [hyperfine](https://github.com/sharkdp/hyperfine):\n\nTiming:\n```\ncargo build --example benchmark --features hotpath --release\nhyperfine --warmup 3 './target/release/examples/benchmark'\n```\n\nAllocations:\n```\ncargo build --example benchmark --features='hotpath,hotpath-alloc-count-total' --release\nhyperfine --warmup 3 './target/release/examples/benchmark'\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpawurb%2Fhotpath","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpawurb%2Fhotpath","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpawurb%2Fhotpath/lists"}