Skip to content

feat(dashboard): level-profile chart (speed + ratio) and fix legend click target #183

@polaz

Description

@polaz

Problem

Текущий dashboard (.github/bench-dashboard/index.html) показывает только один график — delta_ratio Rust/FFI по времени. Из него не видно мгновенный «перекос» по уровням: где Rust медленнее FFI и насколько, где Rust хуже по ratio и насколько. Плюс два UX-бага в существующем графике мешают читать данные.

Scope

Часть 1 — добавить второй график «Level Profile» (compress + decompress + ratio)

Новый график рядом с существующим:

  • X-axis: уровни компрессии (level_1_fast, level_2_dfast, ..., level_22_btultra2) в порядке возрастания.
  • Y-axis: двойная — слева speed (MiB/s), справа output ratio (compressed/input, меньше = лучше).
  • Серии: 6 линий, filled area + smoothing (tension: 0.35):
    • rust_compress_speed (левая ось)
    • ffi_compress_speed (левая ось)
    • rust_decompress_speed (левая ось, dashed-dot)
    • ffi_decompress_speed (левая ось, dashed-dot)
    • rust_ratio (правая ось, dashed)
    • ffi_ratio (правая ось, dashed)
  • Toggles — 6 чекбоксов над графиком, по одному на серию. Toggle через dataset.hidden/setDatasetVisibility + chart.update("none") — без destroy/recreate.
  • Filters: переиспользуем верхние фильтры delta-chart (Target / Scenario / Level), плюс собственный Snapshot. По умолчанию все three top filters стоят в __all__ = кумулятивные данные (среднее по тому измерению). Пин конкретного значения сужает чарт до этой когорты.
  • Snapshot: дропдаун над профильным графиком — список всех generated_at (или commit_sha) для текущей когорты. latest берёт самый свежий снапшот per cohort (а не глобально), чтобы кумулятивный режим не уезжал в сторону самой свежей target/scenario.
  • Decompress усредняется по обоим bench source variants (rust_stream + c_stream) на сторону — оба measure valid decompression numbers, arithmetic mean детерминистичен независимо от порядка итерации.
  • Источник данных: существующий benchmark-relative.json.

Acceptance criteria:

  • График рендерится для всех сценариев / уровней из benchmark-relative.json.
  • Все 6 toggle работают независимо, состояние сохраняется при смене фильтров.
  • Top filters одновременно управляют delta-chart и profile-chart; __all__ даёт unbiased per-cohort усреднение.
  • Snapshot=latest использует per-cohort latest, не глобальный.
  • Существующий delta-time chart продолжает работать без регрессий.

Часть 2 — починить клики на легенде первого графика

Скриншот (Latest series: 63. Outside parity band: 61. Showing top 8 by deviation.):

  • Цветные коробочки (Chart.js legend items) визуально не выровнены с подписью — клик регистрируется выше текста, который пользователь читает. Текущий клик-target живёт у coloured square, а подпись уезжает на следующую строку при wrap.
  • Не все scenario-чипы видны при большом количестве серий — Chart.js скрывает «лишние» молча, без счётчика и без скролла.

Acceptance criteria:

  • Click-target совпадает с визуальной строкой [color] [label] целиком, не только с квадратом. Hover-highlight на всей строке.
  • Все серии видны (через scrollable container легенды).
  • Поведение click → hide/show series сохраняется.
  • A11y: keyboard activation (Enter/Space), aria-pressed, focus-visible outline; фокус и scrollTop сохраняются между rerender-ами.

Часть 3 — починить семантику "Outside parity band"

Текущий aggregator флагал "Outside parity band" для отклонений в ОБЕ стороны от эталона. Положительные сдвиги (мы лучше донора) попадали в тот же канал, что и реальные регрессы.

Что нужно сделать:

Sign-gate по метрике:

  • Ratio axis: флагать только если rust_ratio / ffi_ratio > 1.0 (мы произвели больший output) — реальный регресс по сжатию.
  • Speed axis: флагать только если rust_speed < ffi_speed (мы медленнее) — реальный регресс по throughput.
  • Memory axis (peak_alloc_bytes): флагать только если rust > ffi (мы аллоцировали больше).
  • Положительные сдвиги в нашу пользу — отдельный "notable wins not counted" channel.

Acceptance:

  • Ни одна строка "Outside parity band" не эмитится для отклонения в нашу пользу.
  • Aggregate count считает только реальные регрессы.
  • Существующая детекция регрессов для отрицательных delta продолжает работать.

Implementation hints

  • Chart.js 4.x — кастомная HTML-легенда вместо встроенной; <button type="button"> per row для a11y.
  • Level Profile — type: 'line' + fill: 'origin' + tension: 0.35 + dual Y-axis (scales.y / scales.y1).
  • Toggle-чекбоксы — нативные <input type="checkbox">, обработчик переключает setDatasetVisibility и зовёт chart.update("none").
  • Sign-gate — JS-side в updateStatus(), не на стороне эмиттера.

Files

  • .github/bench-dashboard/index.html (single-file dashboard) — все изменения тут.

Related

Estimate

1d 6h (mid-level dev):

  • 5h — Level Profile chart + dual axis + 6 series + shared top filters + Snapshot + toggle UI + per-cohort latest
  • 4h — кастомная HTML-легенда (a11y: keyboard + aria-pressed + focus/scroll preservation; фиксит обе UX-проблемы первого графика)
  • 2h — sign-gate "Outside parity band"
  • 3h — стили / адаптив / manual smoke-test обоих графиков на текущем payload

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2-mediumMedium priority — important improvementenhancementNew feature or requestperformancePerformance optimization

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions