Skip to content

Plot newly added benchmarks in perf radar charts - #8171

Merged
Amaury Chamayou (achamayou) merged 5 commits into
microsoft:mainfrom
achamayou:achamayou-radar-new-metrics
Aug 18, 2026
Merged

Plot newly added benchmarks in perf radar charts#8171
Amaury Chamayou (achamayou) merged 5 commits into
microsoft:mainfrom
achamayou:achamayou-radar-new-metrics

Conversation

@achamayou

Copy link
Copy Markdown
Member

Summary

  • plot benchmarks absent from main instead of omitting them from the radar chart
  • use the branch's earliest run as their baseline and derive their band from available branch runs
  • keep long benchmark labels distinguishable and explain the branch-local baseline in the generated comparison

Split from #8158 so this display support can land before benchmarks which depend on it.

Testing

  • exercised radar rendering with a synthetic metric absent from main
  • python -m black scripts/perf_compare_radar.py
  • python -m ruff check --fix scripts/perf_compare_radar.py
  • python -m py_compile scripts/perf_compare_radar.py

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
Copilot AI lite review requested due to automatic review settings August 18, 2026 16:51
@achamayou
Amaury Chamayou (achamayou) requested a review from a team as a code owner August 18, 2026 16:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 main by 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.

Comment thread scripts/perf_compare_radar.py Outdated
Comment thread scripts/perf_compare_radar.py Outdated
Comment thread scripts/perf_compare_radar.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 85671d7c-87d1-4284-9553-c9195fd2b07d

@cjen1-msft cjen1-msft left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine. The benchmark against earliest on this branch for new benchmarks is somewhat reasonsable. But also that could just be two PRs...

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, baseline is taken from branch_values[0] and then axes are dropped if baseline <= 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 from branch_values as 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, returning elided[:1] can produce a misleading single character (it doesn’t indicate truncation). Returning ELLIPSIS when max_length == 1 would 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, returning elided[:1] can produce a misleading single character (it doesn’t indicate truncation). Returning ELLIPSIS when max_length == 1 would 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 main EWMA or a branch-local baseline). However, related helper docstrings (e.g., format_delta_percent and within_noise_band) still describe behavior specifically in terms of the main baseline/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 main EWMA or a branch-local baseline). However, related helper docstrings (e.g., format_delta_percent and within_noise_band) still describe behavior specifically in terms of the main baseline/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)

@achamayou

Copy link
Copy Markdown
Member Author

Looks fine. The benchmark against earliest on this branch for new benchmarks is somewhat reasonsable. But also that could just be two PRs...

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?

@achamayou
Amaury Chamayou (achamayou) merged commit 947b59f into microsoft:main Aug 18, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants