|
| 1 | +# Proposal: macOS-only Fib benchmark for flamegraph testing |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Add a benchmark whose only purpose is to exercise CodSpeed's flamegraph |
| 6 | +symbolization pipeline on macOS (codspeed-macro runners). It should: |
| 7 | + |
| 8 | +- run on macOS (locally and on `codspeed-macro` CI) |
| 9 | +- be **skipped** on Linux + Windows so it doesn't pollute the regular walltime |
| 10 | + results or fail on platforms where the flamegraph path isn't applicable |
| 11 | +- have a stack shape that produces an obviously-recognisable flamegraph (deep |
| 12 | + recursion = `fib`) |
| 13 | + |
| 14 | +## Findings |
| 15 | + |
| 16 | +- `examples/example-gradle/src/jmh/java/bench/FibBenchmark.java` already |
| 17 | + defines a recursive `fib(30)` benchmark. Reusing the same shape (but a |
| 18 | + dedicated class) keeps the flamegraph trivially recognisable. |
| 19 | +- The Gradle JMH plugin (`me.champeau.jmh` v0.7.2) exposes `includes` / |
| 20 | + `excludes` regex filters. We're already using `includes` to curate the CI |
| 21 | + subset in `examples/example-gradle/build.gradle.kts:32`. |
| 22 | +- CI is matrixed in `.github/workflows/ci.yml`. The flamegraph-relevant job |
| 23 | + is `walltime-benchmarks` on `codspeed-macro` — macOS-only by definition. |
| 24 | + The cross-platform jobs (`build-and-run-gradle`, `build-and-run-maven`) run |
| 25 | + the JMH JAR on Linux + Windows + macOS, so a naive new benchmark would also |
| 26 | + execute there. |
| 27 | +- JMH itself has no `@SkipOnPlatform`-style annotation. The two reasonable |
| 28 | + gating mechanisms are: |
| 29 | + 1. Filter at the runner level (Gradle `excludes`) |
| 30 | + 2. Throw / `Assume.assumeTrue(...)` inside `@Setup` (JMH treats a thrown |
| 31 | + exception in setup as a benchmark error, not a skip — so this is |
| 32 | + **not** suitable). |
| 33 | + |
| 34 | +## Options |
| 35 | + |
| 36 | +### 1. Gate via Gradle `excludes` based on `OperatingSystem` (recommended) |
| 37 | + |
| 38 | +Add `FibFlamegraphBenchmark` next to the existing `FibBenchmark`, and in |
| 39 | +`examples/example-gradle/build.gradle.kts` exclude it everywhere except macOS: |
| 40 | + |
| 41 | +```kotlin |
| 42 | +import org.gradle.internal.os.OperatingSystem |
| 43 | + |
| 44 | +jmh { |
| 45 | + // …existing config… |
| 46 | + if (!OperatingSystem.current().isMacOsX) { |
| 47 | + excludes.add(".*FibFlamegraphBenchmark.*") |
| 48 | + } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +The `CODSPEED_ENV` branch already filters down to a curated set on CI; add |
| 53 | +`FibFlamegraphBenchmark` to that alternation so it runs on `codspeed-macro` |
| 54 | +but stays out of the way on the regular Linux/Windows matrix. |
| 55 | + |
| 56 | +Pros: |
| 57 | +- One source of truth (the build script). No per-benchmark scaffolding. |
| 58 | +- `OperatingSystem.current()` is the same API Gradle uses internally and |
| 59 | + doesn't require a new dependency. |
| 60 | +- Easy to mirror in the Maven example (`<profile><activation><os>` block). |
| 61 | + |
| 62 | +Cons: |
| 63 | +- Excludes are regex-based; the benchmark class name must stay matchable. |
| 64 | + Mitigation: keep `Flamegraph` in the class name and exclude on that token. |
| 65 | + |
| 66 | +### 2. Put macOS-only benchmarks in a dedicated package + filter |
| 67 | + |
| 68 | +E.g. `bench.macos.FibFlamegraphBenchmark`, then exclude `bench\\.macos\\..*` |
| 69 | +when `!isMacOsX`. |
| 70 | + |
| 71 | +Pros: groups future macOS-only benchmarks naturally; no per-class regex. |
| 72 | +Cons: more moving parts up front for a single benchmark. |
| 73 | + |
| 74 | +### 3. Use a System property check + early `return` |
| 75 | + |
| 76 | +Make the benchmark body a no-op on non-macOS. JMH still runs it, so the run |
| 77 | +shows up in results as a near-zero benchmark. |
| 78 | + |
| 79 | +Pros: no build-script changes. |
| 80 | +Cons: pollutes results, defeats the "skip on Linux" goal. **Rejected.** |
| 81 | + |
| 82 | +## Recommendation |
| 83 | + |
| 84 | +Go with **Option 1**. Smallest diff, no new package, and the gating lives in |
| 85 | +the same place as the existing `CODSPEED_ENV` filter so future readers |
| 86 | +discover it immediately. |
| 87 | + |
| 88 | +### Concrete change set (not yet applied) |
| 89 | + |
| 90 | +1. New file `examples/example-gradle/src/jmh/java/bench/FibFlamegraphBenchmark.java` |
| 91 | + — copy of `FibBenchmark` with a distinct class name and a `@Param` value |
| 92 | + that pushes recursion deep enough to be visually interesting in the |
| 93 | + flamegraph (e.g. `35`). |
| 94 | +2. Edit `examples/example-gradle/build.gradle.kts`: |
| 95 | + - import `org.gradle.internal.os.OperatingSystem` |
| 96 | + - add `excludes.add(".*FibFlamegraphBenchmark.*")` under |
| 97 | + `if (!OperatingSystem.current().isMacOsX)` |
| 98 | + - extend the `CODSPEED_ENV` include alternation to include |
| 99 | + `FibFlamegraphBenchmark` |
| 100 | +3. No CI changes needed: the existing `walltime-benchmarks` job on |
| 101 | + `codspeed-macro` will pick it up automatically. |
| 102 | + |
| 103 | +## Open questions |
| 104 | + |
| 105 | +- Should the Maven example mirror this? The Maven example doesn't currently |
| 106 | + run under `codspeed-macro`, so probably no — but worth confirming. |
| 107 | +- Do we want to lock the `@Fork` / iteration counts down for the flamegraph |
| 108 | + benchmark specifically (a single long iteration produces a cleaner |
| 109 | + flamegraph than many short ones)? |
0 commit comments