Skip to content

Commit c0bd381

Browse files
committed
Fixed diff render
1 parent 940e837 commit c0bd381

1 file changed

Lines changed: 51 additions & 11 deletions

File tree

.github/scripts/compare-jmh.py

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
mean sampled latency per op; it's our best available proxy for CPU
1212
work since no dedicated CPU profiler is configured in
1313
`BenchmarkRunner`.
14-
* `Alloc/op` — `secondaryMetrics["·gc.alloc.rate.norm"]`, populated by
14+
* `Alloc/op` — `secondaryMetrics["gc.alloc.rate.norm"]`, populated by
1515
JMH's `GCProfiler`. This is bytes allocated per benchmark op and is
16-
the standard, low-noise JMH memory metric.
16+
the standard, low-noise JMH memory metric. (Note: JMH 1.37 stores the
17+
key with no prefix; some visualizer tools display it as
18+
`·gc.alloc.rate.norm` but the raw JSON does not contain that dot.)
1719
1820
Both metrics are "lower is better", so a positive delta indicates the
1921
PR is worse than the baseline. A run is considered failed when **any**
@@ -42,9 +44,25 @@
4244
# ---------------------------------------------------------------------------
4345

4446
# JMH's `GCProfiler` reports allocation rate normalised per op under this
45-
# secondary metric key (the leading char is U+00B7 MIDDLE DOT, not a regular
46-
# dot — that's JMH's convention for profiler-emitted metrics).
47-
ALLOC_NORM_KEY = "\u00b7gc.alloc.rate.norm"
47+
# secondary metric key. Verified against jmh-core 1.37 sources: the key
48+
# is the literal `gc.alloc.rate.norm` with no prefix. Some visualizers
49+
# render it as `·gc.alloc.rate.norm`, which is a display convention, not
50+
# what JMH writes to JSON.
51+
ALLOC_NORM_KEY = "gc.alloc.rate.norm"
52+
53+
# Tolerated prefix-bearing variants we'll fall back to if the canonical
54+
# key ever moves. Lets a future JMH (or a non-Oracle JVM profiler that
55+
# decorates the label) keep working without us being aware.
56+
ALLOC_NORM_VARIANTS = (
57+
ALLOC_NORM_KEY,
58+
"\u00b7gc.alloc.rate.norm", # pre-emptive middle-dot variant
59+
"+gc.alloc.rate.norm",
60+
)
61+
62+
# Set to True the first time we fail to find any allocation data; used
63+
# to emit a one-time diagnostic listing the secondary keys present so
64+
# the cause is obvious in workflow logs.
65+
_alloc_warn_printed = False
4866

4967

5068
@dataclass(frozen=True)
@@ -70,13 +88,35 @@ def _secondary(record: Dict[str, Any], key: str) -> Optional[Dict[str, Any]]:
7088
return val if isinstance(val, dict) else None
7189

7290

91+
def _alloc(record: Dict[str, Any]) -> Optional[Dict[str, Any]]:
92+
"""Pull the GC allocation-per-op metric, tolerating prefix variants.
93+
94+
Falls back to a suffix match so even an unrecognised prefix (e.g. a
95+
third-party JMH profiler that decorates the label) still works."""
96+
global _alloc_warn_printed
97+
sm = record.get("secondaryMetrics") or {}
98+
for k in ALLOC_NORM_VARIANTS:
99+
v = sm.get(k)
100+
if isinstance(v, dict):
101+
return v
102+
# Last-resort suffix match.
103+
for k, v in sm.items():
104+
if isinstance(v, dict) and k.lower().endswith("gc.alloc.rate.norm"):
105+
return v
106+
if sm and not _alloc_warn_printed:
107+
print(
108+
"warn: no `gc.alloc.rate.norm` (or prefix-variant) found in "
109+
"secondaryMetrics. Available keys: "
110+
+ ", ".join(sorted(sm.keys())),
111+
file=sys.stderr,
112+
)
113+
_alloc_warn_printed = True
114+
return None
115+
116+
73117
METRICS: List[Metric] = [
74118
Metric(id="time", label="Time", extract=_primary),
75-
Metric(
76-
id="alloc",
77-
label="Alloc/op",
78-
extract=lambda r: _secondary(r, ALLOC_NORM_KEY),
79-
),
119+
Metric(id="alloc", label="Alloc/op", extract=_alloc),
80120
]
81121

82122

@@ -391,7 +431,7 @@ def bucket(r: Row) -> int:
391431
bench, params = k
392432
rec = current[k]
393433
time_d = _primary(rec) or {}
394-
alloc_d = _secondary(rec, ALLOC_NORM_KEY) or {}
434+
alloc_d = _alloc(rec) or {}
395435
out.append(
396436
f"- `{short_bench(bench)}` ({params or '—'}): "
397437
f"time={fmt_score(_float(time_d, 'score'), _float(time_d, 'scoreError'), time_d.get('scoreUnit', ''))}, "

0 commit comments

Comments
 (0)