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:
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
Problem
Текущий dashboard (
.github/bench-dashboard/index.html) показывает только один график — delta_ratio Rust/FFI по времени. Из него не видно мгновенный «перекос» по уровням: где Rust медленнее FFI и насколько, где Rust хуже по ratio и насколько. Плюс два UX-бага в существующем графике мешают читать данные.Scope
Часть 1 — добавить второй график «Level Profile» (compress + decompress + ratio)
Новый график рядом с существующим:
level_1_fast,level_2_dfast, ...,level_22_btultra2) в порядке возрастания.compressed/input, меньше = лучше).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)dataset.hidden/setDatasetVisibility+chart.update("none")— без destroy/recreate.__all__= кумулятивные данные (среднее по тому измерению). Пин конкретного значения сужает чарт до этой когорты.generated_at(илиcommit_sha) для текущей когорты.latestберёт самый свежий снапшот per cohort (а не глобально), чтобы кумулятивный режим не уезжал в сторону самой свежей target/scenario.rust_stream+c_stream) на сторону — оба measure valid decompression numbers, arithmetic mean детерминистичен независимо от порядка итерации.benchmark-relative.json.Acceptance criteria:
benchmark-relative.json.__all__даёт unbiased per-cohort усреднение.latestиспользует per-cohort latest, не глобальный.Часть 2 — починить клики на легенде первого графика
Скриншот (
Latest series: 63. Outside parity band: 61. Showing top 8 by deviation.):Acceptance criteria:
[color] [label]целиком, не только с квадратом. Hover-highlight на всей строке.click → hide/show seriesсохраняется.aria-pressed, focus-visible outline; фокус и scrollTop сохраняются между rerender-ами.Часть 3 — починить семантику "Outside parity band"
Текущий aggregator флагал "Outside parity band" для отклонений в ОБЕ стороны от эталона. Положительные сдвиги (мы лучше донора) попадали в тот же канал, что и реальные регрессы.
Что нужно сделать:
Sign-gate по метрике:
rust_ratio / ffi_ratio > 1.0(мы произвели больший output) — реальный регресс по сжатию.rust_speed < ffi_speed(мы медленнее) — реальный регресс по throughput.peak_alloc_bytes): флагать только еслиrust > ffi(мы аллоцировали больше).Acceptance:
Implementation hints
<button type="button">per row для a11y.type: 'line'+fill: 'origin'+tension: 0.35+ dual Y-axis (scales.y/scales.y1).<input type="checkbox">, обработчик переключаетsetDatasetVisibilityи зовётchart.update("none").updateStatus(), не на стороне эмиттера.Files
.github/bench-dashboard/index.html(single-file dashboard) — все изменения тут.Related
Estimate
1d 6h (mid-level dev):