Share a JMH benchmark report

JMH is careful in a way that gets discarded the moment results are shared. It runs warmup iterations, forks JVMs to defeat profile pollution, and reports a score with an error bound:

Benchmark                Mode  Cnt   Score   Error  Units
SerdeBench.jackson      thrpt   15  482.113 ± 9.244  ops/ms
SerdeBench.handRolled   thrpt   15  501.882 ± 22.71  ops/ms

And then somebody writes "hand-rolled is 4% faster" in a pull request. Look at the error bars: the intervals overlap. The benchmark does not say that.

Sharing the rendered artifact rather than a remembered number is most of the fix.

Get JSON, then render it

# from the benchmarks jar
java -jar target/benchmarks.jar -rf json -rff result.json

# or via the Gradle/Maven plugin, then render
npx jmh-visualizer result.json -o report/

jmh-visualizer writes a static folder — the chart bundle plus your data — with the score, the error bars, the per-fork breakdown and the secondary metrics (-prof gc allocation rates, if you collected them).

Publish the folder

Drag report/ (or a zip of it) into the app:

  • index.html becomes the report body.
  • The chart JS and your result JSON upload alongside it, with relative references rewritten to the uploaded copies.
  • Scripts run inside a sandboxed iframe (allow-scripts, no allow-same-origin), so the charts render and the per-benchmark drilldown works.

Or send the entry file to the API:

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 report/index.html \
        --arg t "JMH — serialization, JDK 21" '{title: $t, html: $html}')"

Collect the profilers while you are there

A score tells you that something changed. The profilers tell you why, and they cost one flag:

java -jar target/benchmarks.jar -rf json -rff result.json \
  -prof gc \
  -prof perfasm    # Linux, needs perf

-prof gc in particular turns "faster" into "allocates 40% less per op", which is the version of the claim that survives review.

From CI, carefully

Shared CI runners share CPU, so absolute JMH numbers from CI are not publication-grade. They are still useful as a regression tripwire with a generous threshold. PATCH one report id per suite:

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 report/index.html \
        --arg t "JMH — serialization @ $GITHUB_SHA" '{title: $t, html: $html}')"

The revision list then shows the run-to-run spread, which is the honest way to decide whether a 6% drop is a regression or the runner.

What review adds

  • Anchored threads on the benchmark under debate — see commenting on HTML.
  • Revisions, so the noise floor is visible rather than assumed.
  • Access per report — private, team, domain-gated or named reviewers. See the sharing model.

Limits

  • Entry HTML: 5 MB. Assets: 25 MB per file, 250 MB and 500 files total. A large parameterized sweep is the JSON to watch.
  • 60 requests/minute per token.

Try it

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

Publish a benchmark →

Related