Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e324823
chore: initialize project SOW framework
ktsaou Jul 29, 2026
623d901
feat: add internal time-series renderer routing
ktsaou Jul 29, 2026
65f854d
feat: add native WebGPU renderer prototype
ktsaou Jul 28, 2026
838f931
feat: add production WebGPU line renderer
ktsaou Jul 28, 2026
0ce161e
docs: record isolated GPU integration evidence
ktsaou Jul 29, 2026
3c40a20
fix: restore WebGPU chart hover feedback
ktsaou Jul 28, 2026
a3510a1
bench: prove exact WebGL2 line feasibility
ktsaou Jul 28, 2026
23d87af
feat: add WebGL2 chart fallback
ktsaou Jul 29, 2026
7cfece5
fix: hide GPU axes on sparklines
ktsaou Jul 29, 2026
d7fefea
fix: stabilize GPU chart rendering
ktsaou Jul 29, 2026
090574e
docs: approve graphical renderer migration sequence
ktsaou Jul 29, 2026
0143e09
feat: add GPU area renderer
ktsaou Jul 29, 2026
d173838
feat: add GPU stacked renderer
ktsaou Jul 29, 2026
7649e4c
feat: add GPU stacked bar renderer
ktsaou Jul 29, 2026
2d2acb4
feat: add GPU multi column renderer
ktsaou Jul 29, 2026
f753426
feat: add GPU heatmap renderer
ktsaou Jul 29, 2026
04966a2
feat: add GPU easy pie renderer
ktsaou Jul 29, 2026
d1dd8cc
feat: complete GPU radial renderers
ktsaou Jul 29, 2026
85b8f6c
fix: replace same-backend visualization adapters
ktsaou Jul 29, 2026
3d6f3c9
fix: restore WebGL filled visualization uniforms
ktsaou Jul 29, 2026
6f697db
docs: sanitize local validation paths
ktsaou Jul 30, 2026
34f6b79
refactor: keep renderer selection internal
ktsaou Jul 30, 2026
f76b133
refactor: harden GPU renderer architecture
ktsaou Jul 30, 2026
3f00325
refactor: separate GPU frame planning
ktsaou Jul 30, 2026
fb51140
ci: update workflow actions
ktsaou Jul 30, 2026
ed0cd26
chore: narrow PR to renderer changes
ktsaou Jul 30, 2026
d65c5a0
fix: preserve hover hairline after stale selections
ktsaou Jul 31, 2026
6714b7a
fix: propagate renderer runtime fallback
ktsaou Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 60 additions & 41 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,86 @@
name: tests

on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
tests:
name: tests
quality:
name: tests, lint, builds, and Storybook
runs-on: ubuntu-latest

steps:
- name: checkout
uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: actions/setup-node@v4
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: "22.22.0"
cache: "yarn"
cache: yarn

- name: Install dependencies
run: yarn install --legacy-peer-deps --prefer-offline

- name: Run Tests
if: github.event_name != 'pull_request'
continue-on-error: true
id: run-tests
run: yarn test --coverage --collectCoverageFrom="./src/**" > /tmp/coverage_report

- name: Post Test Coverage Report in PR
uses: ArtiomTr/jest-coverage-report-action@v2
continue-on-error: true
if: github.event_name == 'pull_request'
with:
test-script: yarn test --collectCoverageFrom="./src/**"
annotations: all
run: yarn install --frozen-lockfile --prefer-offline

- name: Gain access to test-reports bucket
if: (steps.run-tests.outcome == 'failure' || steps.run-tests.outcome == 'success') && github.ref == 'refs/heads/develop'
uses: google-github-actions/setup-gcloud@v0
with:
project_id: "netdata-cloud-testing"
service_account_key: ${{ secrets.TEST_AUTOMATION_SERVICE_ACCOUNT }}
export_default_credentials: true
- name: Run tests with coverage
run: yarn test --runInBand

- name: Upload report to test-reports bucket
if: (steps.run-tests.outcome == 'failure' || steps.run-tests.outcome == 'success') && github.ref == 'refs/heads/develop'
- name: Lint changed JavaScript
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
run: |
gsutil -h "Cache-Control: max-age=0, no-store" cp \
/tmp/coverage_report gs://${{ secrets.TEST_AUTOMATION_STORAGE_BUCKET }}/${{ github.event.repository.name }}/coverage_report
gsutil acl set project-private gs://${{ secrets.TEST_AUTOMATION_STORAGE_BUCKET }}/${{ github.event.repository.name }}/coverage_report
if [[ -z "${BASE_SHA}" || "${BASE_SHA}" =~ ^0+$ ]]; then
BASE_SHA="HEAD^"
fi
git diff --name-only --diff-filter=ACMR "${BASE_SHA}"...HEAD -- \
'*.js' '*.mjs' '*.cjs' > /tmp/eslint-files
if [[ -s /tmp/eslint-files ]]; then
xargs --delimiter='\n' yarn eslint < /tmp/eslint-files
fi

- name: Build CommonJS and ES6 distributions
run: yarn build

- name: Publish test coverage report
if: (steps.run-tests.outcome == 'failure' || steps.run-tests.outcome == 'success') && github.ref == 'refs/heads/develop'
uses: aurelien-baudet/workflow-dispatch@v2
- name: Build Storybook
run: yarn build-storybook

browser-correctness:
name: real-browser WebGL2 correctness
needs: quality
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v7

- name: Set up Node.js
uses: actions/setup-node@v7
with:
repo: netdata/cloud-workflows
ref: refs/heads/main
workflow: test_coverage_publisher.yml
token: ${{ secrets.TEST_AUTOMATION_TOKEN }}
inputs: '{ "service-name": "${{ github.event.repository.name }}"}'
node-version: "22.22.0"
cache: yarn

- name: Install dependencies
run: yarn install --frozen-lockfile --prefer-offline

- name: Install pinned Chromium
run: yarn playwright-core install --with-deps chromium

- name: Validate every visualization in Chromium
env:
BENCHMARK_CORRECTNESS_ONLY: "1"
BENCHMARK_RENDERERS: webgl2
run: yarn benchmark:time-series
8 changes: 5 additions & 3 deletions .storybook/highCardinality.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,9 @@ const installFixtureTransport = () => {
}
}

const createChart = action => {
const createChart = (action, renderer = "dygraph") => {
const sdk = makeDefaultSDK({
rendererPolicy: () => renderer,
attributes: {
after: fixtureWindow.after,
before: fixtureWindow.before,
Expand Down Expand Up @@ -182,9 +183,9 @@ const createChart = action => {
return chart
}

const HighCardinalityChart = ({ action }) => {
const HighCardinalityChart = ({ action, renderer }) => {
useLayoutEffect(installFixtureTransport, [])
const chart = useMemo(() => createChart(action), [action])
const chart = useMemo(() => createChart(action, renderer), [action, renderer])

useLayoutEffect(() => () => chart.destroy(), [chart])

Expand All @@ -199,6 +200,7 @@ export const Values = () => <HighCardinalityChart action="values" />
export const DrillDown = () => <HighCardinalityChart action="drillDown" />
export const Compare = () => <HighCardinalityChart action="compare" />
export const Correlate = () => <HighCardinalityChart action="correlate" />
export const WebGPUValues = () => <HighCardinalityChart action="values" renderer="webgpu" />

const meta = {
title: "Performance/Local high-cardinality expanded chart",
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,32 @@ yarn:

## Build

There are 3 different distributions. The following command creates the different distributions in the `./dist` folder
The package builds CommonJS and ES6 distributions into `./dist`.

```shell
$ yarn build
```

Build the distributions isolated
Build either distribution independently:

**UMD** produces a single file `./dist/sdk.min.js` what contains the entire bundle

```shell
$ yarn build:umd
```

**ES6** Builds the files using ES Modules in `./dist/es6/*` folder
**ES6** builds files using ES Modules in `./dist/es6/*`.

```shell
$ yarn build:es6
```

**cjs** Builds the files using CommonJS in `./dist/*` folder
**CommonJS** builds files using CommonJS in `./dist/*`.

```shell
$ yarn build:cjs
```

## GPU renderer development

The accelerated renderer architecture, ownership rules, extension contract, fallback behavior, diagnostics, and validation requirements are documented in [`docs/gpu-renderers.md`](docs/gpu-renderers.md).

The deterministic browser harness is documented in [`benchmarks/time-series-renderers/README.md`](benchmarks/time-series-renderers/README.md).

## Testing

```shell
Expand Down
102 changes: 102 additions & 0 deletions benchmarks/time-series-renderers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Time-series renderer benchmark

This task-specific comparator measures legacy renderers and selected production GPU backends from the same checkout with identical deterministic Cartesian and radial data. It covers Line, Area, Multi Column, diverging Stacked, Stacked Bar, Heatmap, EasyPie/Circle, Gauge, and D3 Pie. WebGPU is preferred; WebGL2 is the accelerated compatibility fallback.

## Run

Prerequisites:

- Chromium at `/usr/bin/chromium`, or set `CHROMIUM_EXECUTABLE`.
- The pinned `playwright-core` development dependency installed with `yarn install`.
- A physical WebGPU adapter for performance results.

```bash
BENCHMARK_HEADED=1 yarn benchmark:time-series
```

When Chromium needs a particular Linux display backend to expose the hardware adapter:

```bash
BENCHMARK_HEADED=1 CHROMIUM_OZONE_PLATFORM=wayland yarn benchmark:time-series
```

Evaluate WebGL2 on the browser's normal physical graphics path:

```bash
BENCHMARK_HEADED=1 BENCHMARK_RENDERERS=webgl2 yarn benchmark:time-series
```

Compare both GPU candidates in one browser run. The harness keeps one context, one window, and the same page for the entire suite; resets navigate that page without closing or replacing it:

```bash
BENCHMARK_HEADED=1 BENCHMARK_RENDERERS=webgpu,webgl2 \
CHROMIUM_OZONE_PLATFORM=wayland yarn benchmark:time-series
```

Measure Area, Multi Column, Stacked, or Stacked Bar against Dygraphs while retaining all implemented visualization correctness checks:

```bash
BENCHMARK_HEADED=1 BENCHMARK_VISUALIZATION=area \
BENCHMARK_RENDERERS=webgpu,webgl2 CHROMIUM_OZONE_PLATFORM=wayland \
yarn benchmark:time-series

BENCHMARK_HEADED=1 BENCHMARK_VISUALIZATION=multiBar \
BENCHMARK_RENDERERS=webgpu,webgl2 CHROMIUM_OZONE_PLATFORM=wayland \
yarn benchmark:time-series

BENCHMARK_HEADED=1 BENCHMARK_VISUALIZATION=stacked \
BENCHMARK_RENDERERS=webgpu,webgl2 CHROMIUM_OZONE_PLATFORM=wayland \
yarn benchmark:time-series

BENCHMARK_HEADED=1 BENCHMARK_VISUALIZATION=stackedBar \
BENCHMARK_RENDERERS=webgpu,webgl2 CHROMIUM_OZONE_PLATFORM=wayland \
yarn benchmark:time-series
```

Run only radial parity and fallback checks, without Cartesian performance workloads:

```bash
BENCHMARK_HEADED=1 BENCHMARK_RADIAL_ONLY=1 \
BENCHMARK_RENDERERS=webgpu,webgl2 CHROMIUM_OZONE_PLATFORM=wayland \
yarn benchmark:time-series
```

Headless Chromium can run correctness without enforcing physical performance gates:

```bash
BENCHMARK_CORRECTNESS_ONLY=1 BENCHMARK_RENDERERS=webgl2 \
yarn benchmark:time-series
```

A software WebGPU adapter may be requested explicitly when the installed Chromium exposes one:

```bash
BENCHMARK_CORRECTNESS_ONLY=1 WEBGPU_SOFTWARE=1 \
yarn benchmark:time-series
```

Software-adapter results are correctness evidence only and are never valid performance evidence.

The command prints JSON and exits non-zero unless every selected GPU candidate reaches both feasibility gates:

- 100,000 values: prewarmed mount and update present within one measured display frame, GPU work completes within that budget, and synchronous/main-thread work is at least 3x lower than Dygraphs.
- 1,000,000 values: median prewarmed frame-settled mount and repeated full-data updates are at least 5x faster than Dygraphs.
- Each GPU workload exports a non-empty PNG data URL and mounts, updates, and tears down four charts without leaking WebGPU runtime leases or WebGL2 contexts.

Both GPU backends use the same production visualization/data/interaction model, including precision-normalized values, exact null gaps, line step/smooth geometry, Area baseline trapezoids, Multi Column grouped rectangles, diverging Stacked base/end bands, Stacked Bar rectangles, Heatmap cells, analytic EasyPie and Gauge geometry, D3 Pie wedges, axes, text, overlays, and interactions. Filled-line correctness requires one exact band per adjacent source pair, a fully empty null-gap band, and distinct regular/step pixels. Multi Column and Stacked Bar require one instance per source value, exact null omission, and no response to line step mode. Pixel probes require Dygraphs RGBA parity for Area reverse overlap/baseline behavior, Multi Column historical grouped overlap and visibility reflow, Stacked reverse-order positive/negative bands, and Stacked Bar range, width, fill, subpixel border, and empty pixels. WebGL2 owns only its GLSL shaders, textures/buffers, shared context/program runtime, presentation surface, and context-loss handling.

The one-frame gate allows 25% browser scheduling tolerance around the measured refresh interval. A frame-settled ratio is not used when both renderers already present on the same refresh boundary. Cold adapter/device initialization and first pipeline creation are reported separately.

## Method

- Canvas: 1600x500 CSS pixels at device-pixel ratio 1.
- Workloads: 100 dimensions x 1,000 points and 1,000 dimensions x 1,000 points.
- Geometry: every visible series and adjacent pair; Area and Stacked add one exact fill band per pair, while Multi Column and Stacked Bar add one exact rectangle per source value; no LOD, sampling, or aggregation.
- Data: two pre-generated deterministic row-major revisions, alternated during updates.
- Samples: 3 mounts, 2 warm-up updates, 10 measured updates, 3 seconds of sustained updates, and one four-chart shared-runtime lifecycle.
- Timing: synchronous adapter time, GPU queue completion, measured display refresh interval, and wall time through the next animation frame.
- Memory: Chromium heap before mounting, sampled peak, post-teardown retained delta, and allocated GPU buffer bytes.
- Browser task, script, and layout durations are collected through the Chromium DevTools protocol.
- WebGL2 uses one SDK-owned context/program cache and copies each completed shared-context frame into the chart's visible canvas. Its reported buffer bytes cover per-chart value/color textures and primitive buffers.

This benchmark is not production instrumentation and is not included in package distributions.
14 changes: 14 additions & 0 deletions benchmarks/time-series-renderers/build.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const webpack = require("webpack")
const config = require("./webpack.config.cjs")

webpack(config, (error, stats) => {
if (error) {
console.error(error)
process.exitCode = 1
return
}

const output = stats.toString({ colors: false, chunks: false, modules: false })
if (output) console.log(output)
if (stats.hasErrors()) process.exitCode = 1
})
Loading