no-block-pls 0.1.0

Instrument async Rust code to surface blocking work between await points
Documentation
<img src="resources/logo.png" alt="no-block-pls logo" width="248" height="268" />

Detect blocking work between async suspension points by instrumenting your Rust
sources and logging slow sections.

## Why this tool

Tokio console and similar tools watch futures from the runtime’s point of view.
When your whole app collapses into one mega-future (e.g., `async fn main` →
`server` → `pipeline`), a blocking spot shows up as something vague like
`SpawnLocation:main.rs:35`. Helpful, but not precise.

You can't obtain this information from the runtime alone, because runtime hands
over control to poll, and it can't see what happens inside.

`no-block-pls` walks your source tree, finds every `async` function, and times
the
synchronous sections between `await`s. It measures from function entry
to the first await, then from one await boundary to the next, so you see exactly
which chunk of sync work is slow.

`Instant::now()` takes 100 cycles, so overhead is negligible compared to
other async work.

It would be great to have a compiler plugin that does this to add
instrumentation during
`async fn -> state machine` conversion, but that’s not possible (yet?)

## Why not this tool

- I've tested it on a real codebase but might miss some edge-case Rust syntax.
- It patches your code; you can’t flip it on/off like tokio console.
- It can’t instrument third-party crates unless you vendor them (untested).
- Logs are hardcoded to `tracing::warn!`, it can be changed, but it's not here.

## Install

```bash
cargo install --git https://github.com/0xdeafbeef/no-block-pls
# or
cargo install no-block-pls
```

## Default workflow

```bash
# instrument in-place (creates .rs.bak backups next to originals)
no-block-pls -i

# run your app as usual and watch logs
cargo run --release

# restore backups if needed
no-block-pls -r
```

Example log output:

![Blocking log](resources/screen.png)

What you'll see when a section blocks (10 ms default):

```
WARN long poll elapsed_ms=237 name=my_crate::handlers::fetch_and_process span=src/handlers.rs:12-18 hits=1 wraparound=false
```

## How it works

- Injects a guard module into `lib.rs`/`main.rs`.
- Rewrites every async function to start a guard, pause it before each await,
  and resume afterward.
- Logs any synchronous section that exceeds the threshold (10 ms by default).

### Transform example

Input:

```rust
async fn fetch_and_process() {
    let data = fetch().await;
    process(data);
}
```

Instrumented:

```rust
async fn fetch_and_process() {
    let mut __guard = crate::__async_profile_guard__::Guard::new(
        concat!(module_path!(), "::", stringify!(fetch_and_process)),
        file!(),
        1u32,
    );
    let data = {
        __guard.end_section(2u32);
        let __result = fetch().await;
        __guard.start_section(2u32);
        __result
    };
    process(data);
}
```

## Tips

- Use a fresh branch: instrumentation touches all async functions and writes
  `.rs.bak` backups.
- To restore files, move `*.rs.bak` back over the originals.
- The logs come from `tracing::warn!`; set your subscriber accordingly.
- If a log fires only at the function end, the slow part is likely a `Drop`
  impl.