Plot newly added benchmarks in perf radar charts - #8171
Plot newly added benchmarks in perf radar charts#8171Amaury Chamayou (achamayou) merged 5 commits into
Conversation
A benchmark added by a branch has no main runs to build an EWMA baseline from, so render_chart skipped it entirely. That made a new benchmark invisible on the very pull request which adds it, which is when it is most worth seeing. Plot such a benchmark against the branch's own earliest run instead, so its movement across the branch's runs is visible, and mark it as new. It carries no standard deviation band and is never coloured as an improvement or a regression, because there is nothing on main to compare it against. Borrowing a related benchmark's baseline was considered and rejected: an axis normalised against something which measures a different thing shows a difference which is not a change in CCF, and because the chart scale follows the largest axis, one such axis compresses every other benchmark into illegibility. Also truncate long axis labels in the middle rather than at the end. Benchmarks measured at several settings differ only in their suffix, so truncating the end left the 100ms and 1000ms axes indistinguishable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
The previous version reported a benchmark absent from main by its value alone, marked (new), with no percentage. That read differently from every other axis, and the marker made the label long enough to be truncated, which is exactly what it should not have been for benchmarks whose names differ only in a suffix. Treat such a benchmark like any other axis instead, using this branch's earliest run as its reference in place of the main EWMA baseline, so it is normalized, labelled and coloured identically. The chart description records that the reference differs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
Truncating the middle of the whole label cut out several words at once. Elide word by word from the left instead, keeping each word's first and last letter and replacing the middle with a single ellipsis character, so a label degrades gradually and the last word, which is what distinguishes one setting of a benchmark from another, stays readable longest. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
A benchmark with no main history had its standard deviation hardcoded to zero, so all four band curves collapsed to a single point at the baseline while every other axis carried a spread. That left a visible pinch in the band and, because nothing fell inside a zero-width noise threshold, any movement between branch runs was coloured as an improvement or a regression. Measure its spread the same way as for a benchmark with main history, across the runs available, which for such a benchmark are the branch's own. The radial zoom already covered these axes, since their values were always part of the data the scale is fitted to. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2da7bd10-ceb0-41a4-b6c4-d574595d91c9
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Updates the perf radar chart rendering so benchmarks introduced on a feature branch still appear in comparisons, using a branch-local baseline and adding label shortening for readability.
Changes:
- Add word-elision based label shortening to keep long radar axis labels distinguishable.
- Plot benchmarks missing on
mainby deriving their baseline/band from branch runs instead of omitting them. - Document the branch-local baseline behavior in the generated comparison output.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 85671d7c-87d1-4284-9553-c9195fd2b07d
cjen1-msft
left a comment
There was a problem hiding this comment.
Looks fine. The benchmark against earliest on this branch for new benchmarks is somewhat reasonsable. But also that could just be two PRs...
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (5)
scripts/perf_compare_radar.py:403
- When a benchmark is new to the branch,
baselineis taken frombranch_values[0]and then axes are dropped ifbaseline <= 0. If the earliest branch run happens to be 0/negative (e.g., a failed/placeholder measurement) but later branch runs are valid, the benchmark will still be omitted. Consider selecting the earliest positive value frombranch_valuesas the baseline (or otherwise falling back to a positive value) to avoid unintentionally hiding newly added benchmarks.
# A benchmark added by this branch has no main runs to build a baseline
# from. Rather than drop it, which would make a new benchmark invisible
# on the very pull request which adds it, use this branch's own earliest
# run as the reference, so the axis is normalised and plotted exactly
# like every other one.
if main_values:
baseline = ewma(main_values)
sigma = statistics.pstdev(main_values) if len(main_values) > 1 else 0.0
else:
branch_values = [
value
for data in branch_runs
if (value := metric_value(data, benchmark, metric)) is not None
]
if not branch_values:
continue
# load_runs orders branch_runs oldest first, so this is the earliest
# available value for the metric.
baseline = branch_values[0]
# Spread is measured the same way as for a benchmark with main
# history, from the runs available, so that such an axis carries a
# band and a noise threshold like every other one rather than
# collapsing to a point at the baseline.
sigma = statistics.pstdev(branch_values) if len(branch_values) > 1 else 0.0
if baseline <= 0:
continue
scripts/perf_compare_radar.py:275
- For
max_length == 1, returningelided[:1]can produce a misleading single character (it doesn’t indicate truncation). ReturningELLIPSISwhenmax_length == 1would better signal that the label was shortened.
def shorten_label(label: str, max_length: int) -> str:
"""Shorten a label to fit by eliding the middles of its words.
Words are elided from left to right, each keeping its first and last letter,
until the label fits. Benchmarks measured at several settings differ only in
their last word, for example the interval in "Basic Blocking Locust 100ms",
so eliding from the left keeps the part which tells them apart readable for
as long as possible.
"""
if max_length <= 0:
return ""
scripts/perf_compare_radar.py:296
- For
max_length == 1, returningelided[:1]can produce a misleading single character (it doesn’t indicate truncation). ReturningELLIPSISwhenmax_length == 1would better signal that the label was shortened.
# Every word is elided and it still does not fit. Keep the end, which is
# what distinguishes one setting of a benchmark from another.
elided = " ".join(words)
if max_length <= 1:
return elided[:max_length]
return ELLIPSIS + elided[len(elided) - (max_length - 1) :]
scripts/perf_compare_radar.py:390
- This PR generalizes the notion of a baseline (it can now be
mainEWMA or a branch-local baseline). However, related helper docstrings (e.g.,format_delta_percentandwithin_noise_band) still describe behavior specifically in terms of themainbaseline/stddev. Updating those docstrings to refer to a generic baseline (and baseline-derived sigma) would keep documentation consistent with the new behavior.
# A benchmark added by this branch has no main runs to build a baseline
# from. Rather than drop it, which would make a new benchmark invisible
# on the very pull request which adds it, use this branch's own earliest
# run as the reference, so the axis is normalised and plotted exactly
# like every other one.
if main_values:
baseline = ewma(main_values)
sigma = statistics.pstdev(main_values) if len(main_values) > 1 else 0.0
else:
branch_values = [
value
for data in branch_runs
if (value := metric_value(data, benchmark, metric)) is not None
]
scripts/perf_compare_radar.py:407
- This PR generalizes the notion of a baseline (it can now be
mainEWMA or a branch-local baseline). However, related helper docstrings (e.g.,format_delta_percentandwithin_noise_band) still describe behavior specifically in terms of themainbaseline/stddev. Updating those docstrings to refer to a generic baseline (and baseline-derived sigma) would keep documentation consistent with the new behavior.
branch_percent = normalized_percent(branch_value, baseline)
sigma_percent = normalized_percent(sigma, baseline)
within_noise = within_noise_band(branch_percent, sigma_percent)
I am not sure I understand what could be two PRs? Do you mean two PRs with the same branch name, while there is nothing on main? Or this change could be made as two PRs? |
Summary
maininstead of omitting them from the radar chartSplit from #8158 so this display support can land before benchmarks which depend on it.
Testing
mainpython -m black scripts/perf_compare_radar.pypython -m ruff check --fix scripts/perf_compare_radar.pypython -m py_compile scripts/perf_compare_radar.py