hotpath-macros 0.10.0

Simple async Rust profiler with memory and data-flow insights - quickly find and debug performance bottlenecks. Proc macros crate.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
# <img src="hotpath-logo2.png" alt="hotpath-rs logo" width="80px" align="left"> hotpath - real-time Rust performance, memory and data flow profiler
[![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)

hotpath-rs instruments functions, channels, futures, and streams to quickly find bottlenecks and focus optimizations where they matter most. It provides actionable insights into time, memory, and data flow with minimal setup.

Try the TUI demo via SSH - no installation required:

```
ssh demo.hotpath.rs
```

Explore the full documentation at [hotpath.rs](https://hotpath.rs).

You can use it to produce one-off performance (timing or memory) reports:

![hotpath alloc report](hotpath-alloc-report.png)

or use the live TUI dashboard to monitor real-time performance metrics with debug info:

https://github.com/user-attachments/assets/2e890417-2b43-4b1b-8657-a5ef3b458153

## Features

- **Zero-cost when disabled** - fully gated by a feature flag.
- **Low-overhead** profiling for both sync and async code.
- **Live TUI dashboard** - real-time monitoring of performance data flow metrics in TUI dashboard (built with [ratatui.rs](https://ratatui.rs/)).
- **Static reports for one-off programs** - alternatively print profiling summaries without running the TUI.
- **Memory allocation tracking** - track bytes allocated and allocation counts per function.
- **Channel and stream monitoring** - instrument channels and streams to track message flow and throughput.
- **Futures instrumentation** - monitor any async piece of code to track poll counts, lifecycle and resolved values
- **Detailed stats**: avg, total time, call count, % of total runtime, and configurable percentiles (p95, p99, etc.).
- **Background processing** for minimal profiling impact.
- **GitHub Actions integration** - configure CI to automatically benchmark your program against a base branch for each PR

## Roadmap 

- [x] latency, memory method calls tracking
- [x] channels/streams profiling
- [x] process threads monitoring
- [x] futures monitoring
- [x] improved docs on [hotpath.rs](https://hotpath.rs)
- [x] interactive SSH demo 
- [x] MCP/LLM interface
- [ ] runtime metrics 
- [ ] hosted backend integration

## Quick Demo

Other then the SSH demo an easy way to quickly try the TUI is to run it in **auto-instrumentation mode**. The TUI process profiles itself and displays its own performance metrics in real time.

First, install `hotpath` CLI with auto-instrumentation enabled:

```bash
cargo install hotpath --features='tui,hotpath,hotpath-alloc'
```

Then launch the console:

```bash
hotpath console
```

and you'll see timing, memory and channel usage metrics.

Make sure to reinstall it without the auto-profiling features so that you can also observe metrics of other programs!

```bash
cargo install hotpath --features='tui'
```

## Quick Start

> **⚠️ Note**  
> This README reflects the latest development on the `main` branch.
> For documentation matching the current release, see [crates.io](https://crates.io/crates/hotpath) - it stays in sync with the published crate.

Add to your `Cargo.toml`:

```toml
[dependencies]
hotpath = "0.9"

[features]
hotpath = ["hotpath/hotpath"]
hotpath-alloc = ["hotpath/hotpath-alloc"]
```

This config ensures that the lib has no compile time or runtime overhead unless explicitly enabled via a `hotpath` feature. All the lib dependencies are optional (i.e. not compiled) and all macros are noop unless profiling is enabled.

## Usage

```rust
use std::time::Duration;

#[hotpath::measure]
fn sync_function(sleep: u64) {
    std::thread::sleep(Duration::from_nanos(sleep));
}

#[hotpath::measure]
async fn async_function(sleep: u64) {
    tokio::time::sleep(Duration::from_nanos(sleep)).await;
}

// When using with tokio, place the #[tokio::main] first
#[tokio::main]
// You can configure any percentile between 0 and 100
#[hotpath::main(percentiles = [99])]
async fn main() {
    for i in 0..100 {
        // Measured functions will automatically send metrics
        sync_function(i);
        async_function(i * 2).await;

        // Measure code blocks with static labels
        hotpath::measure_block!("custom_block", {
            std::thread::sleep(Duration::from_nanos(i * 3))
        });
    }
}
```

Run your program with a `hotpath` feature:

```
cargo run --features=hotpath
```

Output:

```
[hotpath] Performance summary from basic::main (Total time: 122.13ms):
+-----------------------+-------+---------+---------+----------+---------+
| Function              | Calls | Avg     | P99     | Total    | % Total |
+-----------------------+-------+---------+---------+----------+---------+
| basic::async_function | 100   | 1.16ms  | 1.20ms  | 116.03ms | 95.01%  |
+-----------------------+-------+---------+---------+----------+---------+
| custom_block          | 100   | 17.09µs | 39.55µs | 1.71ms   | 1.40%   |
+-----------------------+-------+---------+---------+----------+---------+
| basic::sync_function  | 100   | 16.99µs | 35.42µs | 1.70ms   | 1.39%   |
+-----------------------+-------+---------+---------+----------+---------+
```

## Live Performance Metrics TUI

`hotpath` includes a live terminal-based dashboard for real-time monitoring of profiling metrics, including function performance, channel statistics, and stream throughput. This is particularly useful for long-running applications like web servers, where you want to observe performance characteristics while the application is running.

### Getting Started with TUI

**1. Install the hotpath binary with TUI support:**

```bash
cargo install hotpath --features tui
```

**2. Start your application with `--features=hotpath`:**

```bash
cargo run --features hotpath
```

**3. In a separate terminal, launch the TUI console:**

```bash
hotpath console 
```

The TUI will connect to your running application and display real-time profiling metrics with automatic refresh.

**HTTP Metrics Server:** When profiling is enabled, an HTTP server automatically starts on `127.0.0.1:6770` to expose metrics for the TUI. This server binds to localhost only and requires no authentication.

- `HOTPATH_METRICS_PORT` - Customize the port (default: 6770)
- `HOTPATH_METRICS_SERVER_OFF=true` - Disable the server entirely

## MCP Server for LLMs

`hotpath` includes an MCP (Model Context Protocol) server that enables AI agents like Claude to query profiling data in real-time. This allows you to ask questions about your application's performance directly in your AI-assisted development workflow.

**Configuration:**

Enable the MCP server by adding the `hotpath-mcp` feature:

```toml
[features]
hotpath = ["hotpath/hotpath"]
hotpath-mcp = ["hotpath/hotpath-mcp"]
```

Run with:
```bash
cargo run --features='hotpath,hotpath-mcp'
```

- Default port: `6771`
- Endpoint: `http://localhost:6771/mcp`
- `HOTPATH_MCP_PORT` - Customize the port
- `HOTPATH_MCP_AUTH_TOKEN` - Optional authentication token

**Authentication:**

When `HOTPATH_MCP_AUTH_TOKEN` is set, clients must include the token in the `Authorization` header. When not set, no authentication is required.

**Available Tools:**

| Tool | Description |
|------|-------------|
| `functions_timing` | Execution timing metrics (call count, avg, p50/p95/p99, total) |
| `functions_alloc` | Memory allocation metrics per function (requires `hotpath-alloc`) |
| `channels` | Channel metrics (sent/received counts, queue size, state) |
| `streams` | Stream metrics (items yielded, state) |
| `futures` | Future lifecycle metrics (poll counts, state) |
| `threads` | Thread CPU usage metrics |
| `gauges` | Gauge metrics (current/min/max values, update count) |
| `function_timing_logs(function_name)` | Detailed timing logs for a specific function |
| `function_alloc_logs(function_name)` | Detailed allocation logs for a specific function |
| `channel_logs(channel_id)` | Message logs for a specific channel |
| `stream_logs(stream_id)` | Item logs for a specific stream |
| `future_logs(future_id)` | Poll/completion logs for a specific future |
| `gauge_logs(gauge_id)` | Value update logs for a specific gauge |

**Claude Code Configuration:**

Run:

```bash
claude mcp add --transport http hotpath http://localhost:6771/mcp
```

```json
"mcpServers": {
    "hotpath": {
        "type": "http",
        "url": "http://localhost:6771/mcp"
    }
}
```

With authentication:

```bash
claude mcp add --transport http hotpath http://localhost:6771/mcp --header "Authorization: your-secret-token"                                          
```

```json
"mcpServers": {
    "hotpath": {
        "type": "http",
        "url": "http://localhost:6771/mcp",
        "headers": {
            "Authorization": "your-secret-token"
        }
    }
}
```

**Usage Workflow:**

1. Start your application with the MCP feature enabled:

```bash
cargo run --features='hotpath,hotpath-mcp'
```

2. The MCP server starts on port 6771

3. Configure Claude Code to connect (see above)

4. Query profiling data via your AI assistant:
   - "What are the slowest functions?"
   - "Show me the p99 latencies"
   - "Are there any channels with growing queues?"

## Allocation Tracking

In 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.

By default, allocation tracking is **cumulative**, meaning that a function's allocation count includes all allocations made by functions it calls (nested calls). Notably, it produces invalid results for recursive functions. To track only **exclusive** allocations (direct allocations made by each function, excluding nested calls), set the `HOTPATH_ALLOC_SELF=true` environment variable when running your program.

Run your program with the allocation tracking feature to print a similar report:

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

![Alloc report](hotpath-alloc-report.png)

### Profiling memory allocations for async functions

To profile memory usage of `async` functions you have to use a similar config:

```rust
#[cfg(feature = "hotpath-alloc")]
#[tokio::main(flavor = "current_thread")]
async fn main() {
    _ = inner_main().await;
}

#[cfg(not(feature = "hotpath-alloc"))]
#[tokio::main]
async fn main() {
    _ = inner_main().await;
}

#[hotpath::main]
async fn inner_main() {
    // ...
}
```

It ensures that tokio runs in a `current_thread` runtime mode if the allocation profiling feature is enabled.

**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.

## Channels, Futures, and Streams, Monitoring

In addition to function profiling, `hotpath` can instrument async channels, futures and streams to track message throughput, queue sizes, and data flow. This is particularly useful for debugging async applications and identifying bottlenecks in concurrent message-passing systems.

### Channel Monitoring

The `channel!` macro wraps channel creation to automatically track statistics:

```rust
use tokio::sync::mpsc;

#[tokio::main]
#[hotpath::main]
async fn main() {
    // Create and instrument a channel in one step
    let (tx, mut rx) = hotpath::channel!(mpsc::channel::<String>(100));

    // Use the channel exactly as before
    tx.send("Hello".to_string()).await.unwrap();
    let msg = rx.recv().await.unwrap();
}
```

[std::sync](https://doc.rust-lang.org/stable/std/sync/mpsc/index.html) channels can be instrumented by default. Enable `tokio`, `futures`, or `crossbeam` features for [Tokio](https://github.com/tokio-rs/tokio), [futures-rs](https://github.com/rust-lang/futures-rs), and [crossbeam](https://github.com/crossbeam-rs/crossbeam) channels, respectively.

**Supported channel types:**
- [`tokio::sync::mpsc::channel`](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.channel.html)
- [`tokio::sync::mpsc::unbounded_channel`](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.unbounded_channel.html)
- [`tokio::sync::oneshot::channel`](https://docs.rs/tokio/latest/tokio/sync/oneshot/fn.channel.html)
- [`futures_channel::mpsc::channel`](https://docs.rs/futures-channel/latest/futures_channel/mpsc/fn.channel.html)
- [`futures_channel::mpsc::unbounded`](https://docs.rs/futures-channel/latest/futures_channel/mpsc/fn.unbounded.html)
- [`futures_channel::oneshot::channel`](https://docs.rs/futures-channel/latest/futures_channel/oneshot/fn.channel.html)
- [`crossbeam_channel::bounded`](https://docs.rs/crossbeam/latest/crossbeam/channel/fn.bounded.html)
- [`crossbeam_channel::unbounded`](https://docs.rs/crossbeam/latest/crossbeam/channel/fn.unbounded.html)

**Optional features:**

```rust
// Custom label for easier identification in TUI
let (tx, rx) = hotpath::channel!(mpsc::channel::<String>(100), label = "worker_queue");

// Enable message logging (requires Debug trait on message type)
let (tx, rx) = hotpath::channel!(mpsc::channel::<String>(100), log = true);
```

**Capacity parameter requirement:**

⚠️ **Important:** For `futures::channel::mpsc` bounded channels, you **must** specify the `capacity` parameter because their API doesn't expose the capacity after creation:

```rust
use futures_channel::mpsc;

// futures bounded channel - MUST specify capacity
let (tx, rx) = hotpath::channel!(mpsc::channel::<String>(10), capacity = 10);
```

Tokio and crossbeam channels don't require this parameter because their capacity is accessible from the channel handles.

### Futures Monitoring

The `future!` macro and `#[future_fn]` attribute instrument async futures to track poll counts and lifecycle:

```rust
#[tokio::main]
#[hotpath::main]
async fn main() {
    // Instrument a future expression
    let result = hotpath::future!(async { 42 }, log = true).await;

    // Or use the attribute on async functions
    instrumented_fetch().await;
}

#[hotpath::future_fn(log = true)]
async fn instrumented_fetch() -> Vec<u8> {
    vec![1, 2, 3]
}
```

**Optional features:**

```rust
// Log the result value (requires Debug on return type)
let result = hotpath::future!(async { 42 }, log = true).await;

#[hotpath::future_fn(log = true)]
async fn compute() -> i32 { 42 }
```

### Stream Monitoring

The `stream!` macro instruments async streams to track items yielded:

```rust
use futures::stream::{self, StreamExt};

#[tokio::main]
#[hotpath::main]
async fn main() {
    // Create and instrument a stream in one step
    let s = hotpath::stream!(stream::iter(1..=100));

    // Use it normally
    let items: Vec<_> = s.collect().await;
}
```

**Optional features:**

```rust
// Custom label
let s = hotpath::stream!(stream::iter(1..=100), label = "data_stream");

// Enable item logging (requires Debug trait on item type)
let s = hotpath::stream!(stream::iter(1..=100), log = true);
```

### Viewing Performance Metrics in TUI

When using the live TUI dashboard, channel and stream statistics are displayed alongside function metrics. The TUI shows:

- Real-time sent/received counts for channels
- Queue sizes and queued bytes
- Items yielded for streams
- State changes (active → full → closed)
- Recent message/item logs (when logging is enabled)

See the [Live Performance Metrics TUI](#live-performance-metrics-tui) section for setup instructions.

**Environment variables:**
- `HOTPATH_LOGS_LIMIT` - Maximum number of log entries to keep per channel/stream (default: 50)
- `HOTPATH_METRICS_PORT` - Port for the HTTP metrics server (default: 6770)
- `HOTPATH_METRICS_SERVER_OFF` - Set to `true` or `1` to disable the HTTP metrics server entirely

### How Channel and Stream Monitoring Works

The `channel!` macro wraps channels with lightweight proxies that transparently forward all messages while collecting real-time statistics. Each `send` and `recv` operation passes through a monitored proxy that emits updates to a background metrics collection thread.

The `stream!` macro wraps streams and tracks items as they are yielded, collecting statistics about throughput and completion.

**Background processing:** The first invocation of `channel!` or `stream!` automatically starts:
- A background thread for metrics collection
- An HTTP server exposing metrics in JSON format for the TUI (see [Getting Started with TUI](#getting-started-with-tui))

#### A note on accuracy

`hotpath` instruments channels by using a proxy on the receive side with the capacity of 1. Messages flow directly into your original channel, then through a proxy before reaching the consumer. This design adds 1 slot of extra buffering for bounded channels.

Please note that enabling monitoring can subtly affect channel behavior in some cases. For example, using `try_send` may behave slightly differently since the proxy adds 1 slot of extra capacity. Also some wrappers currently not propagate info about receiver getting dropped. 

I'm actively improving the library, so any feedback, issues, bug reports are appreciated.

### ChannelsGuard - Printing Statistics on Drop

In addition to the TUI, you can use `ChannelsGuard` to automatically print channel and stream statistics when your program ends (similar to function profiling output):

```rust
use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    // Create guard at the start (prints stats when dropped)
    let _guard = hotpath::ChannelsGuard::new();

    // Your code with instrumented channels...
    let (tx, mut rx) = hotpath::channel!(mpsc::channel::<i32>(10), label = "task-queue");

    // ... use your channels ...

    // Statistics will be printed when _guard is dropped (at program end)
}
```

**Output example:**

```
=== Channel Statistics (runtime: 5.23s) ===

+------------------+-------------+--------+------+----------+--------+------------+
| Channel          | Type        | State  | Sent | Received | Queued | Queued Mem |
+------------------+-------------+--------+------+----------+--------+------------+
| task-queue       | bounded[10] | active | 1543 | 1543     | 0      | 0 B        |
| http-responses   | unbounded   | active | 892  | 890      | 2      | 200 B      |
| shutdown-signal  | oneshot     | closed | 1    | 1        | 0      | 0 B        |
+------------------+-------------+--------+------+----------+--------+------------+
```

**Customize output format:**

```rust
let _guard = hotpath::ChannelsGuardBuilder::new()
    .format(hotpath::Format::Json)
    .build();
```

## How It Works

1. `#[hotpath::main]` - Macro that initializes the background measurement processing
2. `#[hotpath::measure]` - Macro that wraps functions with profiling code
3. **Background thread** - Measurements are sent to a dedicated worker thread via bounded channel
4. **Statistics aggregation** - Worker thread maintains running statistics for each function/code block
5. **Automatic reporting** - Performance summary displayed when the program exits

## Debug Helpers

`hotpath` provides macros for tracking values and logging debug info that can be viewed in the TUI's "Data Flow" tab.

#### `hotpath::dbg!`

Works like `std::dbg!` but sends debug output to the profiler. Logs are grouped by source location and viewable in the TUI.

```rust
// Debug a single value - logs "3"
hotpath::dbg!(1 + 2);

// Debug multiple values 
hotpath::dbg!(foo(), bar());
```

#### `hotpath::val!`

Tracks key-value pairs. Unlike `dbg!`, values are grouped by key name, making it useful for tracking named metrics across different code locations.

```rust
// Track a counter value
hotpath::val!("request_count").set(&count);

// Track state changes
hotpath::val!("connection_state").set(&state);
```

#### `hotpath::gauge!`

Tracks numeric values with set/increment/decrement operations. Gauges display current value, min/max, and update history.

```rust
// Set an absolute value
hotpath::gauge!("queue_size").set(42.0);

// Increment/decrement
hotpath::gauge!("active_connections").inc(1);
hotpath::gauge!("active_connections").dec(1);

// Chain operations
hotpath::gauge!("counter").set(0.0).inc(5.0).dec(2.0);
```

All debug macros need the `hotpath` feature to be work and are no-op otherwise. Values can be inspected in the TUI under the "Data Flow" tab or via the HTTP API at `/debug`.

## API

### Macros

#### `#[hotpath::main]`

Attribute macro that initializes the background measurement processing when applied. Supports parameters:
- `percentiles = [50, 95, 99]` - Custom percentiles to display
- `format = "json"` - Output format ("table", "json", "json-pretty")
- `limit = 20` - Maximum number of functions to display (default: 15, 0 = show all)
- `timeout = 5000` - Optional timeout in milliseconds. If specified, the program will print the report and exit after the timeout (useful for profiling long-running programs like HTTP servers)
- `output_path = "path/to/report.json"` - Write report to file instead of stdout. The `HOTPATH_OUTPUT_PATH` env var takes precedence over this setting.

#### `#[hotpath::measure]`

An opt-in attribute macro that instruments functions to send timing measurements to the background processor.

#### `#[hotpath::measure_all]`

An attribute macro that applies `#[measure]` to all functions in a `mod` or `impl` block. Useful for bulk instrumentation without annotating each function individually. Can be used on:
- **Inline module declarations** - Instruments all functions within the module
- **Impl blocks** - Instruments all methods in the implementation

Example:

```rust
// Measure all methods in an impl block
#[hotpath::measure_all]
impl Calculator {
    fn add(&self, a: u64, b: u64) -> u64 { a + b }
    fn multiply(&self, a: u64, b: u64) -> u64 { a * b }
    async fn async_compute(&self) -> u64 { /* ... */ }
}

// Measure all functions in a module
#[hotpath::measure_all]
mod math_operations {
    pub fn complex_calculation(x: f64) -> f64 { /* ... */ }
    pub async fn fetch_data() -> Vec<u8> { /* ... */ }
}
```

> **Note:** Once Rust stabilizes [`#![feature(proc_macro_hygiene)]`](https://doc.rust-lang.org/beta/unstable-book/language-features/proc-macro-hygiene.html?highlight=proc_macro_hygiene#proc_macro_hygiene) and [`#![feature(custom_inner_attributes)]`](https://doc.rust-lang.org/beta/unstable-book/language-features/custom-inner-attributes.html), it will be possible to use `#![measure_all]` as an inner attribute directly inside module files (e.g., at the top of `math_operations.rs`) to automatically instrument all functions in that module.

#### `#[hotpath::skip]`

A marker attribute that excludes specific functions from instrumentation when used within a module or impl block annotated with `#[measure_all]`. The function executes normally but doesn't send measurements to the profiling system.

Example:

```rust
#[hotpath::measure_all]
mod operations {
    pub fn important_function() { /* ... */ } // Measured

    #[hotpath::skip]
    pub fn not_so_important_function() { /* ... */ } // NOT measured
}
```

#### `hotpath::measure_block!(label, expr)`

Macro that measures the execution time of a code block with a static string label.

#### `hotpath::channel!(expr)`

Macro that instruments channels to track message flow statistics. Wraps channel creation with monitoring code that tracks sent/received counts, queue size, and channel state.

**Supported patterns:**
- `hotpath::channel!(mpsc::channel::<T>(size))` - Basic instrumentation
- `hotpath::channel!(mpsc::channel::<T>(size), label = "name")` - With custom label
- `hotpath::channel!(mpsc::channel::<T>(size), log = true)` - With message logging (requires Debug trait)
- `hotpath::channel!(mpsc::channel::<T>(size), label = "name", log = true)` - Both options combined

**Supported channel types:** `tokio::sync::mpsc`, `tokio::sync::oneshot`, `futures_channel::mpsc`, `crossbeam_channel`

#### `hotpath::stream!(expr)`

Macro that instruments streams to track items yielded. Wraps stream creation with monitoring code that tracks yield count and stream state.

**Supported patterns:**
- `hotpath::stream!(stream::iter(1..=100))` - Basic instrumentation
- `hotpath::stream!(stream::iter(1..=100), label = "name")` - With custom label
- `hotpath::stream!(stream::iter(1..=100), log = true)` - With item logging (requires Debug trait)
- `hotpath::stream!(stream::iter(1..=100), label = "name", log = true)` - Both options combined

### FunctionsGuardBuilder API (Function Profiling)

`hotpath::FunctionsGuardBuilder::new(caller_name)` - Create a new builder with the specified caller name

**Configuration methods:**
- `.percentiles(&[u8])` - Set custom percentiles to display (default: [95])
- `.format(Format)` - Set output format (Table, Json, JsonPretty)
- `.limit(usize)` - Set maximum number of functions to display (default: 15, 0 = show all)
- `.output_path(path)` - Write report to file instead of stdout (`HOTPATH_OUTPUT_PATH` env var takes precedence)
- `.reporter(Box<dyn Reporter>)` - Set custom reporter (overrides format)
- `.build()` - Build and return the FunctionsGuard
- `.build_with_timeout(Duration)` - Build guard that automatically drops after duration and exits the program (useful for profiling long-running programs like HTTP servers)

### ChannelsGuard API (Channel Monitoring)

`hotpath::ChannelsGuard::new()` - Create a guard that prints channel statistics when dropped

`hotpath::ChannelsGuardBuilder::new()` - Create a builder for customizing channel statistics output

**Configuration methods:**
- `.format(Format)` - Set output format (Table, Json, JsonPretty)
- `.output_path(path)` - Write report to file instead of stdout (`HOTPATH_OUTPUT_PATH` env var takes precedence)
- `.build()` - Build and return the ChannelsGuard

**Example:**
```rust
let _guard = hotpath::ChannelsGuardBuilder::new()
    .format(hotpath::Format::JsonPretty)
    .build();
```

### StreamsGuard API (Stream Monitoring)

`hotpath::StreamsGuard::new()` - Create a guard that prints stream statistics when dropped

`hotpath::StreamsGuardBuilder::new()` - Create a builder for customizing stream statistics output

**Configuration methods:**
- `.format(Format)` - Set output format (Table, Json, JsonPretty)
- `.output_path(path)` - Write report to file instead of stdout (`HOTPATH_OUTPUT_PATH` env var takes precedence)
- `.build()` - Build and return the StreamsGuard

**Example:**
```rust
let _guard = hotpath::StreamsGuardBuilder::new()
    .format(hotpath::Format::Table)
    .build();
```

**Example:**
```rust
let _guard = hotpath::FunctionsGuardBuilder::new("main")
    .percentiles(&[50, 90, 95, 99])
    .limit(20)
    .format(hotpath::Format::JsonPretty)
    .build();
```

**Timed profiling example**

```rust
use std::time::Duration;

#[hotpath::measure]
fn work_function() {
    std::thread::sleep(Duration::from_millis(10));
}

fn main() {
    // Profile for 1 second, then generate report and exit
    hotpath::FunctionsGuardBuilder::new("timed_benchmark")
        .build_with_timeout(Duration::from_secs(1));

    loop {
        work_function();
    }
}
```

## Usage Patterns

### Using `hotpath::main` macro vs `FunctionsGuardBuilder` API

The `#[hotpath::main]` macro is convenient for most use cases, but the `FunctionsGuardBuilder` API provides more control over when profiling starts and stops.

Key differences:

- **`#[hotpath::main]`** - Automatic initialization and cleanup, report printed at program exit
- **`let _guard = FunctionsGuardBuilder::new("name").build()`** - Manual control, report printed when guard is dropped, so you can fine-tune the measured scope.

Only 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.

#### Using `FunctionsGuardBuilder` for more control

```rust
use std::time::Duration;

#[hotpath::measure]
fn example_function() {
    std::thread::sleep(Duration::from_millis(10));
}

fn main() {
    let _guard = hotpath::FunctionsGuardBuilder::new("my_program")
        .percentiles(&[50, 95, 99])
        .format(hotpath::Format::Table)
        .build();

    example_function();

    // This will print the report.
    drop(_guard);

    // Immediate exit (no drops); `#[hotpath::main]` wouldn't print.
    std::process::exit(1);
}
```

#### Using in unit tests

In unit tests you can profile each individual test case:

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_sync_function() {
        let _hotpath = hotpath::FunctionsGuardBuilder::new("test_sync_function")
            .percentiles(&[50, 90, 95])
            .format(hotpath::Format::Table)
            .build();
        sync_function();
    }

    #[tokio::test(flavor = "current_thread")]
    async fn test_async_function() {
        let _hotpath = hotpath::FunctionsGuardBuilder::new("test_async_function")
            .percentiles(&[50, 90, 95])
            .format(hotpath::Format::Table)
            .build();

        async_function().await;
    }
}
```

Run tests with profiling enabled:

```bash
cargo test --features hotpath -- --test-threads=1
```

Note: Use `--test-threads=1` to ensure tests run sequentially, as only one hotpath guard can be active at a time.

### Percentiles Support

By default, `hotpath` displays P95 percentile in the performance summary. You can customize which percentiles to display using the `percentiles` parameter:

```rust
#[tokio::main]
#[hotpath::main(percentiles = [50, 75, 90, 95, 99])]
async fn main() {
    // Your code here
}
```

For 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.

### Output Formats

By default, `hotpath` displays results in a human-readable table format. You can also output results in JSON format for programmatic processing:

```rust
#[tokio::main]
#[hotpath::main(format = "json-pretty")]
async fn main() {
    // Your code here
}
```

Supported format options:
- `"table"` (default) - Human-readable table format
- `"json"` - Compact, oneline JSON format
- `"json-pretty"` - Pretty-printed JSON format
- `"none"` - Suppress all output (profiling still active, metrics server and MCP server still function)

**Environment variable override:** Set `HOTPATH_OUTPUT_FORMAT` to override the format for all guards (functions, channels, streams, futures). This takes precedence over programmatic `.format()` configuration. Invalid values will cause a panic. Use `none` to suppress all output while keeping profiling active (useful when only using the metrics server or MCP server).

```bash
HOTPATH_OUTPUT_FORMAT=json cargo run --features hotpath
HOTPATH_OUTPUT_FORMAT=none cargo run --features hotpath  # Silent mode
```

Example JSON output:

```json
{
  "hotpath_profiling_mode": "timing",
  "output": {
    "basic::async_function": {
      "calls": "100",
      "avg": "1.16ms",
      "p95": "1.26ms",
      "total": "116.41ms",
      "percent_total": "96.18%"
    },
    "basic::sync_function": {
      "calls": "100",
      "avg": "23.10µs",
      "p95": "37.89µs",
      "total": "2.31ms",
      "percent_total": "1.87%"
    }
  }
}
```

You can combine multiple parameters:

```rust
#[hotpath::main(percentiles = [50, 90, 99], format = "json", limit = 10, timeout = 30000)]
```

## Custom Reporters

You 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.

For complete working examples, see:
- [`examples/csv_file_reporter.rs`](crates/test-tokio-async/examples/csv_file_reporter.rs) - Save metrics to CSV file
- [`examples/json_file_reporter.rs`](crates/test-tokio-async/examples/json_file_reporter.rs) - Save metrics to JSON file
- [`examples/tracing_reporter.rs`](crates/test-tokio-async/examples/tracing_reporter.rs) - Log metrics using the tracing crate

## Benchmarking

Measure overhead of profiling 10k method calls with [hyperfine](https://github.com/sharkdp/hyperfine):

Timing:
```
cargo build --example benchmark --features hotpath --release
hyperfine --warmup 3 './target/release/examples/benchmark'
```

Allocations:
```
cargo build --example benchmark --features='hotpath,hotpath-alloc' --release
hyperfine --warmup 3 './target/release/examples/benchmark'
```