# Share an async-profiler Report — The JVM Flame Graph as a URL

Canonical: https://commareports.com/share-async-profiler-report
Published: 2026-09-08

> async-profiler writes a self-contained interactive flame graph HTML. Publish it to Comma for a link where the zoom and search still work, instead of a screenshot in a channel.

# Share an async-profiler report

async-profiler is the default answer for "why is this JVM slow" because it
samples without the safepoint bias that makes most Java profilers agree with
each other and disagree with reality. Since 2.x it renders its own flame graph
directly:

```bash
# attach to a running JVM for 30 seconds of CPU samples
asprof -e cpu -d 30 -f profile.html 12345

# or start with the agent
java -agentpath:/opt/async-profiler/lib/libasyncProfiler.so=start,event=cpu,file=profile.html \
     -jar service.jar
```

The output is one self-contained HTML file with the graph and its interaction
layer inlined. That makes it trivial to produce and awkward to deliver: too big
to paste, downloaded rather than opened when attached to a ticket, and rendered
as a flat preview by chat tools — which removes the zoom and the search box,
which is the entire reason to look at a flame graph.

## Publish it

Drag `profile.html` into [the app](https://commareports.com/), or POST it:

```bash
curl -fsS -X POST "https://commareports.com/api/v1/reports" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html profile.html \
        --arg t "async-profiler — orders-api, 14:05 UTC" '{title: $t, html: $html}')"
```

Report HTML renders with scripts enabled inside a sandboxed iframe
(`allow-scripts`, no `allow-same-origin`), so clicking a frame zooms, the reset
link works, and Ctrl-F highlights every matching stack — at the URL, for
everyone, including the person who joins the incident an hour later.

## Pick the event before you pick the tool

The most common wasted profile is a CPU profile of a process that is not CPU
bound. Match the event to the symptom:

| Symptom | Event |
| --- | --- |
| High CPU, high load average | `-e cpu` |
| GC pressure, rising heap | `-e alloc` |
| Threads idle, throughput flat | `-e lock` |
| Slow, but nothing is busy | `-e wall -t` |

`-e wall -t` (wall clock, per thread) is the one that finds the thread parked on
a downstream HTTP call — invisible in every CPU profile you will take first.

## Keep one URL per investigation

Re-profiling is the normal shape of the work: capture, change a setting,
capture again. PATCH the same report id:

```bash
curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html profile.html \
        --arg t "async-profiler — orders-api, after pool resize" '{title: $t, html: $html}')"
```

The incident channel keeps one link, and the revision list is the timeline.

## What review adds

- **Anchored threads** on the hot frame — see
  [commenting on HTML](/comment-on-html).
- **Revisions**, so the before/after pair is one artifact.
- **Access per report** — JVM stacks expose package names, vendored library
  versions and internal service names. See the [sharing model](/docs/sharing).

## Limits

- **Entry HTML: 5 MB.** A 30-second capture sits well inside it; very long
  `wall` captures across a large thread pool are the ones to shorten.
- **60 requests/minute per token.**

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision
history.

**[Publish a JVM profile →](https://commareports.com/)**

### Related

- [Share a flame graph](/share-flamegraph) · [Share a JMH benchmark report](/share-jmh-benchmark-report)
- [Share a speedscope profile](/share-speedscope-profile) · [Share a profiling report](/share-profiling-report)
- [Share a Gradle test report](/share-gradle-test-report) · [Publish from CI](/docs/ci)
