Skip to content

[2/3] feat(benchmark): telemetry harness, result schema v1, viewer and web runner - #1397

Open
luluiz wants to merge 7 commits into
feature/benchmark-telemetry-productfrom
feature/benchmark-telemetry-harness
Open

luluiz wants to merge 7 commits into
feature/benchmark-telemetry-productfrom
feature/benchmark-telemetry-harness

Conversation

@luluiz

@luluiz luluiz commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

User-Facing Changes

None (benchmark tooling only). The product app is not changed.

Description

Split of #1393 into three stacked PRs that can be reviewed separately.

📚 Review sequence (part 2 of 3)

# PR Base branch What it answers Status
1/3 #1396 feat(telemetry): memory and pipeline telemetry contract and product instrumentation develop What does the product measure, and is it free when nobody listens? merge first
2/3 #1397 feat(benchmark): telemetry harness, result schema v1, viewer and web runner feature/benchmark-telemetry-product ([1/3]) How do we turn on telemetry, collect it on Electron and web, store it and view it? 👉 this PR
3/3 #1398 feat(benchmark): telemetry overhead A/B and AI benchmark analyst feature/benchmark-telemetry-harness ([2/3]) Is telemetry cheap enough to keep on in benchmarks, and how do we read the reports? next

Review and merge in order 1 → 2 → 3. Each PR passes type checks, lint and unit tests on its own. This PR targets the [1/3] branch, so the diff shows only its own changes. It will be retargeted to develop after [1/3] merges. #1393 stays open as a draft umbrella for reference. Its final tree equals the tip of [3/3].

🎯 Main gain within the overall plan

Overall plan (#1393):

  • Goal: one correlated timeline per benchmark run. It covers memory and CPU per process, message-pipeline telemetry and per-panel telemetry, so a regression can be traced to a process, a pipeline stage or a panel.
  • Benchmark-only activation: the product calls a telemetry API whose default is a no-op. Only the benchmark app injects a real sink. There is no env or build flag.
  • Versioned output: results use the schema benchmark-result.v1. Electron and web runners produce them, and an offline HTML viewer and a CI job summary read them. Overhead target for telemetry: ≤ 2 % CPU (relative) and ≤ 20 MiB, checked by an A/B.
  • Foundation for follow-ups: panel-specific memory, deeper pipeline counters, network instrumentation, CI gates and baselines, and a historical dashboard.

What this PR adds: the measurement harness around the contract from [1/3]:

  • the benchmark page injects the sink
  • the runners record host metrics per process on Electron and web
  • everything is merged into one validated benchmark-result.v1 file per run
  • the results can be read in an offline HTML viewer and the CI job summary
  • a real multi-file MCAP scenario exercises the source-pool counters

What it unblocks: comparable, versioned results that are stored as CI artifacts on every push to develop / main. [3/3] builds the overhead A/B on this harness. Baselines, gates and a dashboard can later use the same schema.

🧩 What was done (review commit by commit)

Total: 166 files, +21039 / −205. Source (non-test .ts/.tsx): 107 files, +11338 / −187. Tests and builders: 48 files, +8931. Other files (md, json, yml, mjs, lock, gitignore): 11 files, +770 / −18. There are no packages/** changes.

Commit Subject What to look at / review tips
a7a88d907 feat(benchmark): page-side telemetry harness and scenario descriptors (57 files, +4407 / −51) src/telemetry/installBenchmarkTelemetry.ts calls setTelemetrySink; this is the only activation point.
Renderer collectors in src/telemetry/collectors/:
• performance.memory
• long tasks
• rAF FPS (frameProbe/FrameLoop.ts)
• UA-specific memory when crossOriginIsolated
• in-page self-cost
Also: scenario descriptors (describe(), scenarios/registry.ts) and BenchmarkStats.listenerDispatchDurationMs.
0a787a7fa feat(benchmark): result schema v1 library (29 files, +5965 / −2) e2e/result/schemaV1.ts (+types): validator for benchmark-result.v1.
Summaries: panels, pipeline, phases (including layoutApplied / panelsReady) and memory slope.
kpis.ts builds the per-run KPIs and the platform scorecard (geomean / worst).
Pure functions; review the tests next to them.
e71777f42 feat(benchmark): runner, Electron host metrics and CI (43 files, +4278 / −154) runner/runScenario.ts drives a run. It writes the v1 result and the legacy artifact.
electronHostCollector.ts: app.getAppMetrics() at 1 Hz.
crashDetection.ts: a crash, OOM or reload fails the scenario.
calibration.ts: FPS ceiling and GL renderer.
Also: screenshots and the error-boundary guard.
.github/workflows/e2e-benchmark.yml (Electron job) and the job summary script.
6f4729c2d feat(benchmark): HTML viewer and KPI scorecard (33 files, +4151 / −14) e2e/report/htmlReport*.ts: a self-contained offline HTML page with Chart.js inlined.
It shows a KPI scorecard per metric family (geomean / worst per platform), an "Exercises" card per run and a coverage matrix.
CI uploads the report in the artifacts. Adds chart.js to the benchmark workspace.
cf263203d feat(benchmark): web runner and host collector (21 files, +1524 / −62) Playwright Chromium project.
server/staticServer.ts serves the build with COOP/COEP, so the page is crossOriginIsolated and UA-specific memory is available.
runner/webHostCollector.ts reads per-process metrics through CDP SystemInfo, with a ps fallback.
Adds the CI web job.
7b5651a1d feat(benchmark): real MCAP multi-ingest scenario (26 files, +831 / −39) fixtures/generateMcapFixtures.ts generates the multi-file MCAP fixtures at runtime; they are not committed.
The scenario mcap-multi-ingest runs the real pipeline with maxHydratedSources=1, so pool evictions and over-budget periods happen and the invariant is checked.
Adds @mcap/core to the benchmark workspace.

Key design notes

  • Activation: only the benchmark page installs the sink. The product build is identical, and there is no env or build flag.
  • Schema first: the runner validates every result before writing it. The legacy artifact is still written alongside it (dual write), so older consumers keep working during the transition.
  • Fail loudly: a renderer crash, OOM or unexpected reload fails the scenario, instead of producing a "fast" but broken result.
  • Comparable FPS: each run is calibrated against the display's frame ceiling, and the GL renderer is recorded.
  • Scenarios run by the E2E suite and CI: sinewave, pointcloud, transform, transformpreloading and mcap-multi-ingest.
  • Dependencies: chart.js and @mcap/core are added to the benchmark workspace only. Both packages were already in the lockfile through suite-base / suite-desktop.
  • CI: e2e-benchmark.yml runs the scenarios on Electron and web on push to develop / main only, because the suite is slow. It uploads the JSON results, screenshots and the HTML report as artifacts and writes a job summary.
  • Scenario mode only: this PR has no overhead A/B code; that is [3/3]. run.mode only accepts "scenario".

🧪 How to test

# Electron
yarn benchmark:desktop:build:prod && yarn test:e2e:benchmark scenarios -g sinewave
# Web (headless Chromium, cross-origin isolated)
yarn benchmark:build:prod && yarn test:e2e:benchmark:web scenarios -g mcap-multi-ingest
# Offline HTML viewer -> benchmark/.artifacts/report/index.html
yarn test:e2e:benchmark:viewer
# Markdown summary (what CI writes to the job summary)
yarn test:e2e:benchmark:summary
# Unit tests
yarn test benchmark

Expected results:

  • Each spec reports 1 passed.
  • A *.benchmark-result.v1.json file is written to benchmark/.artifacts/.
  • The mcap run shows the pool invariant ✅.
  • The web run reports crossOriginIsolated: true.
  • The viewer lists the new runs.
  • yarn test benchmark gives 46 suites / 489 tests.

Local folder caveat: if your local benchmark/.artifacts/ still holds overhead reports from #1393, [2/3] does not understand them. The viewer skips them with a log line, and the summary script throws. Clean the folder or use a fresh checkout. CI always starts with an empty folder.

✅ Validation evidence

The E2E benchmark workflow only runs on push to develop / main, so this evidence comes from local runs. Setup: macOS, Apple M4 Pro, ANGLE Metal, 2026-11-04, prod builds made from the tip 7b5651a1d.

Command Result
yarn benchmark:build:prod ✅ exit 0 (40 s; only the usual 2 asset-size warnings)
yarn benchmark:desktop:build:prod ✅ exit 0 (38 s; same 2 warnings in the renderer)
yarn format:ci ✅ passed
yarn tsc --noEmit (root, -p benchmark/e2e, -p benchmark/src) ✅ passed. Every commit also compiles on its own.
yarn lint:unused-exports ✅ passed
eslint on the 157 changed ts/tsx/mjs files ✅ 0 problems
yarn test benchmark ✅ 46 suites / 489 tests
yarn test:e2e:benchmark:viewer ✅ exit 0. The report contains all 4 runs below.
yarn test:e2e:benchmark:summary (on a clean .artifacts/) ✅ exit 0
git diff --stat <[1/3]> HEAD no packages/** files

Real runs (each wrote a *.benchmark-result.v1.json)

Run Result Window Peak total WS Median total CPU Peak JS heap Median FPS / ceiling On-time frames First message Panels ready Pool invariant Healthy
Electron scenarios -g sinewave ✅ 1 passed (15 s) 5.0 s 982.5 MiB 204.4 % 98.2 MiB 120.0 / 120 98.6 % — (synthetic) 55 ms n/a ✅
Electron scenarios -g mcap-multi-ingest ✅ 1 passed (17 s) 10.0 s 812.5 MiB 36.0 % 90.7 MiB 119.9 / 120 99.7 % 562 ms 71 ms ✅ (13 evictions) ✅
Web scenarios -g sinewave ✅ 1 passed (12 s) 5.0 s 871.1 MiB 239.1 % 112.8 MiB 120.0 / 119.9 99.3 % — (synthetic) 67 ms n/a ✅
Web scenarios -g mcap-multi-ingest ✅ 1 passed (16 s) 10.0 s 737.9 MiB 43.4 % 85.0 MiB 119.5 / 120 99.8 % 575 ms 78 ms ✅ (12 evictions) ✅
Web scenarios -g cross-origin ✅ 1 passed — — — — — — — — — —
  • All runs had run.mode: "scenario", 0 dropped records and 0 runner events.
  • Telemetry records per run: 125 / 482 / 132 / 506.
  • The mcap runs had 2 over-budget periods each (154 ms on Electron, 140 ms on web).
  • KPI scorecard from the summary:
    • Electron: 2/2 healthy, peak WS geomean 893 MiB, CPU geomean 85.7 %, min FPS 119.9
    • Web: 2/2 healthy, 802 MiB, 101.9 %, 119.5

⚠️ Notes / out of scope

  • Telemetry overhead measurement (A/B, statistics, verdicts) and the AI analyst are in [3/3].
  • A local .artifacts/ folder from Add memory telemetry per process, pipeline and panel to benchmarks #1393 needs cleaning; see the caveat above.
  • Planned follow-ups: idle-memory baselines, panel.ready, deeper panel and pipeline instrumentation, network, CI gates and baselines, and a historical dashboard.

Checklist

  • The web version was tested and it is running ok
    Benchmark web runner (headless Chromium, cross-origin isolated): sinewave, mcap-multi-ingest and cross-origin specs passed.
  • The desktop version was tested and it is running ok
    Benchmark Electron runner: sinewave and mcap-multi-ingest passed.
  • This change is covered by unit tests
    46 suites / 489 tests in benchmark/.
  • Files constants.ts, types.ts and *.style.ts have been checked and relevant code snippets have been relocated
    Per-module constants.ts, types.ts / *.types.ts and htmlReport.style.ts.

luluiz and others added 6 commits November 4, 2026 14:19
- Install the benchmark telemetry sink with renderer collectors (JS heap, FPS, late frames, long tasks, UA-specific memory) and a self-cost meter
- Expose window.lichtblickTelemetry (isEnabled, now, drain); ?telemetry=off keeps the API but disables recording
- Describe every scenario (group, tags, layout, data source, player, redacted params, panels) and accept ?ds.<param> overrides
- Add window.lichtblickScenario.closeDataSource() and self-pausing scenarios with durationMs
- Document scenarios and the telemetry contract in the benchmark README

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add benchmark-result.v1 types, builder and strict/lenient validator
- Summarize process, renderer, phases, panels, pipeline and HydratedSourcePool series per run
- Derive per-run KPIs and per-platform/per-group aggregates (geomean or worst case)
- Register the benchmark-e2e Jest project and its tsconfig

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Drive each scenario with runScenario: 1 Hz host polling and telemetry drain on the page clock, FPS calibration, checkpoint screenshots
- Collect Electron process memory and CPU (per-core sum) and fail runs on crashes, reloads or error boundaries
- Write the v1 result next to the legacy artifact and extend the markdown CI summary with v1, panels and pipeline tables
- Run the Electron project in the e2e-benchmark workflow

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add yarn test:e2e:benchmark:viewer: a self-contained HTML report with per-run charts, phases and screenshots
- Show a KPI scorecard per platform and an exercises card / coverage matrix per scenario
- Add the scorecard and exercises lines to the CI summary and upload the report in CI
- Add chart.js as a benchmark dev dependency

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add the web Playwright project: headless Chromium against a static server with COOP/COEP (cross-origin isolated)
- Collect browser process CPU via CDP and RSS via ps for web runs
- Add the web scripts and the e2e-benchmark-web CI job

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Generate deterministic MCAP fixtures with @mcap/core and serve them from the benchmark server
- Add the mcap-multi-ingest scenario: time-overlapping MCAP files through the real worker pipeline with one hydrated source at a time
- Assert the pool invariant, over-budget periods and open/first-message events in the scenarios spec

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review: 2

  • develop
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

- PanelExtensionAdapter: do not count a tick without messages as a
  skipped render while a render is pending.
- Document that range-delivered bytes are approximate when data is
  merged into the middle of allFrames (count stays exact).
- IterablePlayer: end the preload phase with reason "cancelled" when the
  last full-preload subscription is removed; emit the datasource close
  start/end pair at most once per player.
- HydratedSourcePool: settle the over-budget state right after victims
  are evicted instead of after their close() settles.
- WorkerSerializedIterableSource: bound the final telemetry drain with
  one shared deadline so the worker is always disposed; stop the bridge
  timer when enabling worker telemetry fails.
- WorkerSerializedIterableSourceWorker: drop the redundant publish in
  terminate(); the final drain publishes on dispose.
- CurrentLayoutProvider: emit layout.applied only when the selection was
  accepted (not for unsupported working layout versions).
- BufferedTelemetrySink: reject counter totals that overflow to
  Infinity (record and ingest paths); keep the sink's self-metric series
  separate from caller series and reject caller metrics using the
  reserved self-metric names.
- Tests for each change; assert initialize() is called once in the
  stale-init worker source test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 5, 2026

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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.

1 participant