Guide
Performance
What is measured
The components that receive continuous data are designed for the rates a plant or a test station produces: a strip chart fed from a data acquisition card, a scope fed from a digitizer, a trend holding a shift of historian data, an alarm summary during a flood, a heatmap of a day of model scores, a terminal receiving instrument output, and an audit trail with thousands of entries. The figures below were measured with a benchmark script, bench-industrial.js, which loads the built package in headless Chrome, runs each scenario three times and reports the median. The script belongs to the product's repository and is not part of the package.
Measured on: Intel(R) Core(TM) Ultra 7 255U, 34 GB, Windows; Chrome/138.0.7204.157; 2026-09-20. Figures on a panel PC will be lower; the design properties listed under How the figures are achieved do not depend on the machine.
Results
| Component | Measurement | Result |
|---|---|---|
| Strip chart | samples per second sustained | 189,806 |
| Strip chart | frames per second while pushing | 60 |
| Scope | blocks of 10,000 samples per second | 188 |
| Scope | samples per second | 1,881,640 |
| Trend | assign 100,000 records, draw the 15-minute window (ms) | 26 |
| Trend | pan one hour, time per pan (ms) | 16.7 |
| Alarm grid | assign 10,000 alarms and derive states (ms) | 1,560 |
| Alarm grid | counts() over 10,000 alarms (ms) | 0.6 |
| Anomaly heatmap | bucket and draw 50,000 samples into 50 x 96 cells (ms) | 516 |
| Terminal | lines per second appended, view following | 25,029 |
| Terminal | DOM nodes held (maxLines 2000) | 2,000 |
| Audit trail | append 10,000 hash-chained entries (ms) | 181 |
| Audit trail | verify the 10,000-entry chain (ms) | 115 |
Scenarios
- Strip chart: four pens, a ring buffer of 20,000 samples, samples pushed in blocks of 1,000 for five seconds while the chart draws. The rate is what the chart accepted and drew; the frame rate is the animation frame count over the same period.
- Scope: two channels at a sample rate of 1 MS/s, blocks of 10,000 samples pushed for three seconds with the edge trigger running over every block.
- Trend: two pens, 100,000 records at one per second assigned at once and the default 15-minute window (about 900 of the records) drawn; then the window panned back one hour at a time, twenty times, each pan waiting for the next animation frame. The pan figure is therefore the frame interval: every pan was drawn within one frame at 60 frames per second.
- Alarm grid: 10,000 alarm records assigned at once, each placed in its ISA-18.2 state, and the counts over all of them.
- Anomaly heatmap: 50,000 samples over 50 series and 24 hours, bucketed into 15-minute cells and drawn.
- Terminal: lines appended in batches of 200 for three seconds with the view following the newest line and the buffer capped at 2,000 lines.
- Audit trail: 10,000 entries appended to a hash chain (a SHA-256 per entry) and the chain verified.
How the figures are achieved
- Ring buffers. The strip chart and the scope hold their samples in buffers of a fixed length. A push writes one slot and moves an index, so the cost of a sample does not grow with the history, and the oldest data leaves without copying. The terminal is bounded differently: it keeps at most maxLines lines and removes the oldest when a new one arrives.
- One draw per frame. Pushes mark the chart as changed; drawing happens on the next animation frame. A thousand pushes between two frames cost one draw, so the rate of incoming data does not multiply the rate of rendering.
- Reduction before painting. The trend and the strip chart draw at most one column per pixel (the minimum and the maximum of the records in it, so a one-sample spike still reaches the top of the chart), so the number of line segments painted depends on the width of the chart rather than on the number of records in the window. The trend also draws only the records inside its window.
- Appending, not re-rendering. The terminal appends a node per line and trims the oldest from the front of the DOM. The view is scrolled once per frame rather than once per line, because setting the scroll position forces a layout.
- Derived state. The alarm grid derives each alarm's state from its record when the records are assigned, and counts() derives the states again from the records, which is cheap; the cost of assigning 10,000 alarms is in the underlying table, and a summary that has to stay responsive during a flood should page the rendered rows.
Reproducing the figures
The scenarios are plain functions that load the built package in a page and measure it, and they can be changed to match an application's own rates, pen counts and buffer sizes. The benchmark script is not in the package.
A second script measures how the time grows with the data volume (the strip chart, mimic, alarm grid, status tile wall, faceplate screen and terminal at several sizes) and keeps a baseline. Its check fails when a case becomes more than 1.6 times slower than its baseline. Nothing in the build runs that check.