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:
# 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, or POST it:
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:
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.
- 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.
Limits
- Entry HTML: 5 MB. A 30-second capture sits well inside it; very long
wallcaptures 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.