From e83dbecd96616f45d87799e7c59141a5388a6e05 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 16 Jun 2025 16:20:57 -0400 Subject: [PATCH 1/9] add performance tests --- .circleci/config.yml | 25 ++++++ .circleci/test.sh | 5 ++ package.json | 1 + tasks/test_performance.js | 46 ++++++++++ tasks/util/constants.js | 1 + test/jasmine/karma.conf.js | 19 +++- test/jasmine/performance_tests/bar_test.js | 89 +++++++++++++++++++ .../jasmine/performance_tests/scatter_test.js | 87 ++++++++++++++++++ 8 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 tasks/test_performance.js create mode 100644 test/jasmine/performance_tests/bar_test.js create mode 100644 test/jasmine/performance_tests/scatter_test.js diff --git a/.circleci/config.yml b/.circleci/config.yml index b5d94298735..6701dd7e259 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,6 +33,28 @@ jobs: paths: - plotly.js + performance-jasmine: + docker: + # need '-browsers' version to test in real (xvfb-wrapped) browsers + - image: cimg/node:18.20.4-browsers + environment: + # Alaska time (arbitrary timezone to test date logic) + TZ: "America/Anchorage" + working_directory: ~/plotly.js + steps: + - run: sudo apt-get update + - browser-tools/install-browser-tools: + install-firefox: false + install-geckodriver: false + install-chrome: true + chrome-version: "132.0.6834.110" + - attach_workspace: + at: ~/ + - run: + name: Run performance tests + command: .circleci/test.sh performance-jasmine + + timezone-jasmine: docker: # need '-browsers' version to test in real (xvfb-wrapped) browsers @@ -500,6 +522,9 @@ workflows: - bundle-jasmine: requires: - install-and-cibuild + - performance-jasmine: + requires: + - install-and-cibuild - mathjax-firefoxLatest: requires: - install-and-cibuild diff --git a/.circleci/test.sh b/.circleci/test.sh index b814c1dd3e8..4df91e8af8c 100755 --- a/.circleci/test.sh +++ b/.circleci/test.sh @@ -79,6 +79,11 @@ case $1 in exit $EXIT_STATE ;; + performance-jasmine) + npm run test-performance || EXIT_STATE=$? + exit $EXIT_STATE + ;; + mathjax-firefox) ./node_modules/karma/bin/karma start test/jasmine/karma.conf.js --FF --bundleTest=mathjax --nowatch || EXIT_STATE=$? exit $EXIT_STATE diff --git a/package.json b/package.json index 39f76f1a5dd..94dd2045e0d 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "test-export": "node test/image/export_test.js", "test-syntax": "node tasks/test_syntax.js && npm run find-strings -- --no-output", "test-bundle": "node tasks/test_bundle.js", + "test-performance": "node tasks/test_performance.js", "test-plain-obj": "node tasks/test_plain_obj.mjs", "test": "npm run test-jasmine -- --nowatch && npm run test-bundle && npm run test-image && npm run test-export && npm run test-syntax && npm run lint", "b64": "python3 test/image/generate_b64_mocks.py && node devtools/test_dashboard/server.mjs", diff --git a/tasks/test_performance.js b/tasks/test_performance.js new file mode 100644 index 00000000000..1d1c9487536 --- /dev/null +++ b/tasks/test_performance.js @@ -0,0 +1,46 @@ +var path = require('path'); +var exec = require('child_process').exec; +var { glob } = require('glob'); +var runSeries = require('run-series'); + +var constants = require('./util/constants'); +var pathToJasminePerformanceTests = constants.pathToJasminePerformanceTests; + +/** + * Run all jasmine 'performance' test in series + * + * To run specific performance tests, use + * + * $ npm run test-jasmine -- --performanceTest= + */ +glob(pathToJasminePerformanceTests + '/*.js').then(function(files) { + var tasks = files.map(function(file) { + return function(cb) { + var cmd = [ + 'karma', 'start', + path.join(constants.pathToRoot, 'test', 'jasmine', 'karma.conf.js'), + '--performanceTest=' + path.basename(file), + '--nowatch' + ].join(' '); + + console.log('Running: ' + cmd); + + exec(cmd, function(err) { + cb(null, err); + }).stdout.pipe(process.stdout); + }; + }); + + runSeries(tasks, function(err, results) { + if(err) throw err; + + var failed = results.filter(function(r) { return r; }); + + if(failed.length) { + console.log('\ntest-performance summary:'); + failed.forEach(function(r) { console.warn('- ' + r.cmd + ' failed'); }); + console.log(''); + process.exit(1); + } + }); +}); diff --git a/tasks/util/constants.js b/tasks/util/constants.js index 6442501d9aa..69c1e88d64d 100644 --- a/tasks/util/constants.js +++ b/tasks/util/constants.js @@ -225,6 +225,7 @@ module.exports = { pathToJasmineTests: path.join(pathToRoot, 'test/jasmine/tests'), pathToJasmineBundleTests: path.join(pathToRoot, 'test/jasmine/bundle_tests'), + pathToJasminePerformanceTests: path.join(pathToRoot, 'test/jasmine/performance_tests'), // this mapbox access token is 'public', no need to hide it // more info: https://www.mapbox.com/help/define-access-token/ diff --git a/test/jasmine/karma.conf.js b/test/jasmine/karma.conf.js index f66235d7593..4653e346f44 100644 --- a/test/jasmine/karma.conf.js +++ b/test/jasmine/karma.conf.js @@ -8,7 +8,7 @@ var esbuildConfig = require('../../esbuild-config.js'); var isCI = Boolean(process.env.CI); var argv = minimist(process.argv.slice(4), { - string: ['bundleTest', 'width', 'height'], + string: ['bundleTest', 'performanceTest', 'width', 'height'], boolean: [ 'mathjax3', 'info', @@ -21,6 +21,7 @@ var argv = minimist(process.argv.slice(4), { Chrome: 'chrome', Firefox: ['firefox', 'FF'], bundleTest: ['bundletest', 'bundle_test'], + performanceTest: ['performancetest', 'performance_test'], nowatch: 'no-watch', failFast: 'fail-fast', }, @@ -53,7 +54,8 @@ if(argv.info) { ' - All non-flagged arguments corresponds to the test suites in `test/jasmine/tests/` to be run.', ' No need to add the `_test.js` suffix, we expand them correctly here.', ' - `--bundleTest` set the bundle test suite `test/jasmine/bundle_tests/ to be run.', - ' Note that only one bundle test can be run at a time.', + ' - `--performanceTest` set the bundle test suite `test/jasmine/performance_tests/ to be run.', + ' Note that only one bundle/performance test can be run at a time.', ' - Use `--tags` to specify which `@` tags to test (if any) e.g `npm run test-jasmine -- --tags=gl`', ' will run only gl tests.', ' - Use `--skip-tags` to specify which `@` tags to skip (if any) e.g `npm run test-jasmine -- --skip-tags=gl`', @@ -100,7 +102,8 @@ var glob = function(_) { }; var isBundleTest = !!argv.bundleTest; -var isFullSuite = !isBundleTest && argv._.length === 0; +var isPerformanceTest = !!argv.performanceTest; +var isFullSuite = !(isBundleTest || isPerformanceTest) && argv._.length === 0; var testFileGlob; if(isFullSuite) { @@ -113,6 +116,14 @@ if(isFullSuite) { } testFileGlob = path.join(__dirname, 'bundle_tests', glob([basename(_[0])])); +} else if(isPerformanceTest) { + var _ = merge(argv.performanceTest); + + if(_.length > 1) { + console.warn('Can only run one performance test suite at a time, ignoring ', _.slice(1)); + } + + testFileGlob = path.join(__dirname, 'performance_tests', glob([basename(_[0])])); } else { testFileGlob = path.join(__dirname, 'tests', glob(merge(argv._).map(basename))); } @@ -250,7 +261,7 @@ func.defaultConfig = { '--touch-events', '--window-size=' + argv.width + ',' + argv.height, isCI ? '--ignore-gpu-blacklist' : '', - (isBundleTest && basename(testFileGlob) === 'no_webgl') ? '--disable-webgl' : '' + ((isBundleTest || isPerformanceTest) && basename(testFileGlob) === 'no_webgl') ? '--disable-webgl' : '' ] }, _Firefox: { diff --git a/test/jasmine/performance_tests/bar_test.js b/test/jasmine/performance_tests/bar_test.js new file mode 100644 index 00000000000..8392bc0941c --- /dev/null +++ b/test/jasmine/performance_tests/bar_test.js @@ -0,0 +1,89 @@ +var createGraphDiv = require('../assets/create_graph_div'); +var delay = require('../assets/delay'); +var d3SelectAll = require('../../strict-d3').selectAll; +var Plotly = require('../../../lib/core'); +var PlotlyBar = require('../../../lib/bar'); + +var gd = createGraphDiv(); + +[{ + n: 1000, averageCap: 75 +}, { + n: 2000, averageCap: 100 +}, { + n: 4000, averageCap: 150 +}, { + n: 8000, averageCap: 300 +}, { + n: 16000, averageCap: 600 +}, { + n: 32000, averageCap: 1200 +}, { + n: 64000, averageCap: 2400 +}].forEach(function(spec) { + describe('Bundle with bar | size:' + spec.n, function() { + 'use strict'; + + Plotly.register(PlotlyBar); + + const samples = Array.from({ length: 5 }, (_, i) => i); + const nTimes = samples.length - 1; + + var y = Float64Array.from({ length: spec.n }, (_, i) => i * Math.cos(Math.sqrt(i))); + + var mock = { + data: [{ + type: 'bar', + y: y + }], + layout: { + width: 1200, + height: 400 + } + }; + + var startTime; + + beforeEach(function(done) { + startTime = Date.now(); + + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(function(done) { + delay(100)().then(done); + }); + + var maxDelta = 0; + var aveDelta = 0; + + samples.forEach(function(t) { + it('should graph bar traces | turn: ' + t, function() { + var delta = Date.now() - startTime; + + if(t === 0) { + console.log('________________________________'); + console.log('number of points in bar: ' + spec.n); + console.log('expected average (cap): ' + spec.averageCap + ' ms'); + } + + if(t > 0) { // we skip the first run which is slow + maxDelta = Math.max(maxDelta, delta); + aveDelta += delta / nTimes; + } + + console.log('turn: ' + t + ' | ' + delta + ' ms'); + + if(t === nTimes) { + console.log('max: ' + maxDelta); + console.log('ave: ' + aveDelta); + + expect(aveDelta).toBeLessThan(spec.averageCap); + } + + var nodes = d3SelectAll('g.trace.bars'); + expect(nodes.size()).toEqual(1); + }); + }); + }); +}); diff --git a/test/jasmine/performance_tests/scatter_test.js b/test/jasmine/performance_tests/scatter_test.js new file mode 100644 index 00000000000..23634877680 --- /dev/null +++ b/test/jasmine/performance_tests/scatter_test.js @@ -0,0 +1,87 @@ +var createGraphDiv = require('../assets/create_graph_div'); +var delay = require('../assets/delay'); +var d3SelectAll = require('../../strict-d3').selectAll; +var Plotly = require('../../../lib/core'); + +var gd = createGraphDiv(); + +[{ + n: 1000, averageCap: 75 +}, { + n: 2000, averageCap: 100 +}, { + n: 4000, averageCap: 150 +}, { + n: 8000, averageCap: 300 +}, { + n: 16000, averageCap: 600 +}, { + n: 32000, averageCap: 1200 +}, { + n: 64000, averageCap: 2400 +}].forEach(function(spec) { + describe('Bundle with scatter | size:' + spec.n, function() { + 'use strict'; + + const samples = Array.from({ length: 5 }, (_, i) => i); + const nTimes = samples.length - 1; + + var y = Float64Array.from({ length: spec.n }, (_, i) => i * Math.cos(Math.sqrt(i))); + + var mock = { + data: [{ + type: 'scatter', + mode: 'markers', + y: y + }], + layout: { + width: 1200, + height: 400 + } + }; + + var startTime; + + beforeEach(function(done) { + startTime = Date.now(); + + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(function(done) { + delay(100)().then(done); + }); + + var maxDelta = 0; + var aveDelta = 0; + + samples.forEach(function(t) { + it('should graph scatter traces | turn: ' + t, function() { + var delta = Date.now() - startTime; + + if(t === 0) { + console.log('________________________________'); + console.log('number of points in scatter: ' + spec.n); + console.log('expected average (cap): ' + spec.averageCap + ' ms'); + } + + if(t > 0) { // we skip the first run which is slow + maxDelta = Math.max(maxDelta, delta); + aveDelta += delta / nTimes; + } + + console.log('turn: ' + t + ' | ' + delta + ' ms'); + + if(t === nTimes) { + console.log('max: ' + maxDelta); + console.log('ave: ' + aveDelta); + + expect(aveDelta).toBeLessThan(spec.averageCap); + } + + var nodes = d3SelectAll('g.trace.scatter'); + expect(nodes.size()).toEqual(1); + }); + }); + }); +}); From 012abcabeb33155b292975ec9db9894be2d546c4 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 19 Jun 2025 09:39:10 -0400 Subject: [PATCH 2/9] collect performance data and create test report --- test/jasmine/performance_tests/bar_test.js | 34 +++-- test/jasmine/performance_tests/box_test.js | 102 +++++++++++++++ .../jasmine/performance_tests/contour_test.js | 112 +++++++++++++++++ .../jasmine/performance_tests/heatmap_test.js | 112 +++++++++++++++++ .../performance_tests/histogram_test.js | 101 +++++++++++++++ test/jasmine/performance_tests/image_test.js | 119 ++++++++++++++++++ .../jasmine/performance_tests/scatter_test.js | 34 +++-- 7 files changed, 592 insertions(+), 22 deletions(-) create mode 100644 test/jasmine/performance_tests/box_test.js create mode 100644 test/jasmine/performance_tests/contour_test.js create mode 100644 test/jasmine/performance_tests/heatmap_test.js create mode 100644 test/jasmine/performance_tests/histogram_test.js create mode 100644 test/jasmine/performance_tests/image_test.js diff --git a/test/jasmine/performance_tests/bar_test.js b/test/jasmine/performance_tests/bar_test.js index 8392bc0941c..4276c4c3840 100644 --- a/test/jasmine/performance_tests/bar_test.js +++ b/test/jasmine/performance_tests/bar_test.js @@ -6,7 +6,7 @@ var PlotlyBar = require('../../../lib/bar'); var gd = createGraphDiv(); -[{ +var tests = [{ n: 1000, averageCap: 75 }, { n: 2000, averageCap: 100 @@ -20,13 +20,15 @@ var gd = createGraphDiv(); n: 32000, averageCap: 1200 }, { n: 64000, averageCap: 2400 -}].forEach(function(spec) { - describe('Bundle with bar | size:' + spec.n, function() { +}]; + +tests.forEach(function(spec, index) { + describe('Performance test bar | size:' + spec.n, function() { 'use strict'; Plotly.register(PlotlyBar); - const samples = Array.from({ length: 5 }, (_, i) => i); + const samples = Array.from({ length: 9 }, (_, i) => i); const nTimes = samples.length - 1; var y = Float64Array.from({ length: spec.n }, (_, i) => i * Math.cos(Math.sqrt(i))); @@ -37,7 +39,7 @@ var gd = createGraphDiv(); y: y }], layout: { - width: 1200, + width: 900, height: 400 } }; @@ -62,27 +64,37 @@ var gd = createGraphDiv(); var delta = Date.now() - startTime; if(t === 0) { - console.log('________________________________'); - console.log('number of points in bar: ' + spec.n); - console.log('expected average (cap): ' + spec.averageCap + ' ms'); + // console.log('________________________________'); + // console.log('number of points: ' + spec.n); + // console.log('expected average (cap): ' + spec.averageCap + ' ms'); + + tests[index].raw = []; } + tests[index].raw[t] = delta; if(t > 0) { // we skip the first run which is slow maxDelta = Math.max(maxDelta, delta); aveDelta += delta / nTimes; } - console.log('turn: ' + t + ' | ' + delta + ' ms'); + // console.log('turn: ' + t + ' | ' + delta + ' ms'); if(t === nTimes) { - console.log('max: ' + maxDelta); - console.log('ave: ' + aveDelta); + tests[index].average = aveDelta; + tests[index].maximum = maxDelta; + + // console.log('max: ' + maxDelta); + // console.log('ave: ' + aveDelta); expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.trace.bars'); expect(nodes.size()).toEqual(1); + + if(t === nTimes && index === tests.length - 1) { + console.log(JSON.stringify(tests, null, 2)); + } }); }); }); diff --git a/test/jasmine/performance_tests/box_test.js b/test/jasmine/performance_tests/box_test.js new file mode 100644 index 00000000000..65e4ff374c0 --- /dev/null +++ b/test/jasmine/performance_tests/box_test.js @@ -0,0 +1,102 @@ +var createGraphDiv = require('../assets/create_graph_div'); +var delay = require('../assets/delay'); +var d3SelectAll = require('../../strict-d3').selectAll; +var Plotly = require('../../../lib/core'); +var PlotlyBox = require('../../../lib/box'); + +var gd = createGraphDiv(); + +var tests = [{ + n: 1000, averageCap: 75 +}, { + n: 2000, averageCap: 100 +}, { + n: 4000, averageCap: 150 +}, { + n: 8000, averageCap: 300 +}, { + n: 16000, averageCap: 600 +}, { + n: 32000, averageCap: 1200 +}, { + n: 64000, averageCap: 2400 +}]; + +tests.forEach(function(spec, index) { + describe('Performance test box | size:' + spec.n, function() { + 'use strict'; + + Plotly.register(PlotlyBox); + + const samples = Array.from({ length: 9 }, (_, i) => i); + const nTimes = samples.length - 1; + + var y = Float64Array.from({ length: spec.n }, (_, i) => i * Math.cos(Math.sqrt(i))); + + var mock = { + data: [{ + type: 'box', + boxpoints: 'all', + y: y + }], + layout: { + width: 900, + height: 400 + } + }; + + var startTime; + + beforeEach(function(done) { + startTime = Date.now(); + + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(function(done) { + delay(100)().then(done); + }); + + var maxDelta = 0; + var aveDelta = 0; + + samples.forEach(function(t) { + it('should graph box traces | turn: ' + t, function() { + var delta = Date.now() - startTime; + + if(t === 0) { + // console.log('________________________________'); + // console.log('number of points: ' + spec.n); + // console.log('expected average (cap): ' + spec.averageCap + ' ms'); + + tests[index].raw = []; + } + tests[index].raw[t] = delta; + + if(t > 0) { // we skip the first run which is slow + maxDelta = Math.max(maxDelta, delta); + aveDelta += delta / nTimes; + } + + // console.log('turn: ' + t + ' | ' + delta + ' ms'); + + if(t === nTimes) { + tests[index].average = aveDelta; + tests[index].maximum = maxDelta; + + // console.log('max: ' + maxDelta); + // console.log('ave: ' + aveDelta); + + expect(aveDelta).toBeLessThan(spec.averageCap); + } + + var nodes = d3SelectAll('g.trace.boxes'); + expect(nodes.size()).toEqual(1); + + if(t === nTimes && index === tests.length - 1) { + console.log(JSON.stringify(tests, null, 2)); + } + }); + }); + }); +}); diff --git a/test/jasmine/performance_tests/contour_test.js b/test/jasmine/performance_tests/contour_test.js new file mode 100644 index 00000000000..00b4893fa62 --- /dev/null +++ b/test/jasmine/performance_tests/contour_test.js @@ -0,0 +1,112 @@ +var createGraphDiv = require('../assets/create_graph_div'); +var delay = require('../assets/delay'); +var d3SelectAll = require('../../strict-d3').selectAll; +var Plotly = require('../../../lib/core'); +var PlotlyContour = require('../../../lib/contour'); + +var gd = createGraphDiv(); + +var tests = [{ + nx: 50, ny: 20, averageCap: 100 +}, { + nx: 100, ny: 40, averageCap: 125 +}, { + nx: 200, ny: 80, averageCap: 250 +}, { + nx: 400, ny: 160, averageCap: 500 +}, { + nx: 800, ny: 320, averageCap: 1000 +}, { + nx: 1600, ny: 640, averageCap: 2000 +}, { + nx: 3200, ny: 1280, averageCap: 4000 +}]; + +tests.forEach(function(spec, index) { + describe('Performance test contour | size:' + spec.nx + 'X' + spec.ny, function() { + 'use strict'; + + Plotly.register(PlotlyContour); + + const samples = Array.from({ length: 9 }, (_, i) => i); + const nTimes = samples.length - 1; + + var A = spec.nx; + var B = spec.ny; + spec.n = A * B; + + var x = Uint16Array.from({ length: A }, (_, i) => i); + var y = Uint16Array.from({ length: B }, (_, i) => i); + var z = []; + for(var k = 0; k < B ; k++) { + z[k] = Float64Array.from({ length: A }, (_, i) => k * Math.cos(Math.sqrt(i))); + } + + var mock = { + data: [{ + type: 'contour', + x: x, + y: y, + z: z + }], + layout: { + width: 900, + height: 400 + } + }; + + var startTime; + + beforeEach(function(done) { + startTime = Date.now(); + + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(function(done) { + delay(100)().then(done); + }); + + var maxDelta = 0; + var aveDelta = 0; + + samples.forEach(function(t) { + it('should graph contour traces | turn: ' + t, function() { + var delta = Date.now() - startTime; + + if(t === 0) { + // console.log('________________________________'); + // console.log('number of points: ' + spec.n); + // console.log('expected average (cap): ' + spec.averageCap + ' ms'); + + tests[index].raw = []; + } + tests[index].raw[t] = delta; + + if(t > 0) { // we skip the first run which is slow + maxDelta = Math.max(maxDelta, delta); + aveDelta += delta / nTimes; + } + + // console.log('turn: ' + t + ' | ' + delta + ' ms'); + + if(t === nTimes) { + tests[index].average = aveDelta; + tests[index].maximum = maxDelta; + + // console.log('max: ' + maxDelta); + // console.log('ave: ' + aveDelta); + + expect(aveDelta).toBeLessThan(spec.averageCap); + } + + var nodes = d3SelectAll('g.contourlayer'); + expect(nodes.size()).toEqual(1); + + if(t === nTimes && index === tests.length - 1) { + console.log(JSON.stringify(tests, null, 2)); + } + }); + }); + }); +}); diff --git a/test/jasmine/performance_tests/heatmap_test.js b/test/jasmine/performance_tests/heatmap_test.js new file mode 100644 index 00000000000..454624bb716 --- /dev/null +++ b/test/jasmine/performance_tests/heatmap_test.js @@ -0,0 +1,112 @@ +var createGraphDiv = require('../assets/create_graph_div'); +var delay = require('../assets/delay'); +var d3SelectAll = require('../../strict-d3').selectAll; +var Plotly = require('../../../lib/core'); +var PlotlyHeatmap = require('../../../lib/heatmap'); + +var gd = createGraphDiv(); + +var tests = [{ + nx: 50, ny: 20, averageCap: 75 +}, { + nx: 100, ny: 40, averageCap: 100 +}, { + nx: 200, ny: 80, averageCap: 150 +}, { + nx: 400, ny: 160, averageCap: 300 +}, { + nx: 800, ny: 320, averageCap: 600 +}, { + nx: 1600, ny: 640, averageCap: 1200 +}, { + nx: 3200, ny: 1280, averageCap: 2400 +}]; + +tests.forEach(function(spec, index) { + describe('Performance test heatmap | size:' + spec.nx + 'X' + spec.ny, function() { + 'use strict'; + + Plotly.register(PlotlyHeatmap); + + const samples = Array.from({ length: 9 }, (_, i) => i); + const nTimes = samples.length - 1; + + var A = spec.nx; + var B = spec.ny; + spec.n = A * B; + + var x = Uint16Array.from({ length: A }, (_, i) => i); + var y = Uint16Array.from({ length: B }, (_, i) => i); + var z = []; + for(var k = 0; k < B ; k++) { + z[k] = Float64Array.from({ length: A }, (_, i) => k * Math.cos(Math.sqrt(i))); + } + + var mock = { + data: [{ + type: 'heatmap', + x: x, + y: y, + z: z + }], + layout: { + width: 900, + height: 400 + } + }; + + var startTime; + + beforeEach(function(done) { + startTime = Date.now(); + + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(function(done) { + delay(100)().then(done); + }); + + var maxDelta = 0; + var aveDelta = 0; + + samples.forEach(function(t) { + it('should graph heatmap traces | turn: ' + t, function() { + var delta = Date.now() - startTime; + + if(t === 0) { + // console.log('________________________________'); + // console.log('number of points: ' + spec.n); + // console.log('expected average (cap): ' + spec.averageCap + ' ms'); + + tests[index].raw = []; + } + tests[index].raw[t] = delta; + + if(t > 0) { // we skip the first run which is slow + maxDelta = Math.max(maxDelta, delta); + aveDelta += delta / nTimes; + } + + // console.log('turn: ' + t + ' | ' + delta + ' ms'); + + if(t === nTimes) { + tests[index].average = aveDelta; + tests[index].maximum = maxDelta; + + // console.log('max: ' + maxDelta); + // console.log('ave: ' + aveDelta); + + expect(aveDelta).toBeLessThan(spec.averageCap); + } + + var nodes = d3SelectAll('g.heatmaplayer'); + expect(nodes.size()).toEqual(1); + + if(t === nTimes && index === tests.length - 1) { + console.log(JSON.stringify(tests, null, 2)); + } + }); + }); + }); +}); diff --git a/test/jasmine/performance_tests/histogram_test.js b/test/jasmine/performance_tests/histogram_test.js new file mode 100644 index 00000000000..814c1f3c381 --- /dev/null +++ b/test/jasmine/performance_tests/histogram_test.js @@ -0,0 +1,101 @@ +var createGraphDiv = require('../assets/create_graph_div'); +var delay = require('../assets/delay'); +var d3SelectAll = require('../../strict-d3').selectAll; +var Plotly = require('../../../lib/core'); +var PlotlyHistogram = require('../../../lib/histogram'); + +var gd = createGraphDiv(); + +var tests = [{ + n: 1000, averageCap: 75 +}, { + n: 2000, averageCap: 100 +}, { + n: 4000, averageCap: 150 +}, { + n: 8000, averageCap: 300 +}, { + n: 16000, averageCap: 600 +}, { + n: 32000, averageCap: 1200 +}, { + n: 64000, averageCap: 2400 +}]; + +tests.forEach(function(spec, index) { + describe('Performance test histogram | size:' + spec.n, function() { + 'use strict'; + + Plotly.register(PlotlyHistogram); + + const samples = Array.from({ length: 9 }, (_, i) => i); + const nTimes = samples.length - 1; + + var x = Float64Array.from({ length: spec.n }, (_, i) => i * Math.cos(Math.sqrt(i))); + + var mock = { + data: [{ + type: 'histogram', + x: x + }], + layout: { + width: 900, + height: 400 + } + }; + + var startTime; + + beforeEach(function(done) { + startTime = Date.now(); + + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(function(done) { + delay(100)().then(done); + }); + + var maxDelta = 0; + var aveDelta = 0; + + samples.forEach(function(t) { + it('should graph histogram traces | turn: ' + t, function() { + var delta = Date.now() - startTime; + + if(t === 0) { + // console.log('________________________________'); + // console.log('number of points: ' + spec.n); + // console.log('expected average (cap): ' + spec.averageCap + ' ms'); + + tests[index].raw = []; + } + tests[index].raw[t] = delta; + + if(t > 0) { // we skip the first run which is slow + maxDelta = Math.max(maxDelta, delta); + aveDelta += delta / nTimes; + } + + // console.log('turn: ' + t + ' | ' + delta + ' ms'); + + if(t === nTimes) { + tests[index].average = aveDelta; + tests[index].maximum = maxDelta; + + // console.log('max: ' + maxDelta); + // console.log('ave: ' + aveDelta); + + expect(aveDelta).toBeLessThan(spec.averageCap); + } + + var nodes = d3SelectAll('g.trace.bars'); + expect(nodes.size()).toEqual(1); + + if(t === nTimes && index === tests.length - 1) { + console.log(JSON.stringify(tests, null, 2)); + } + }); + }); + }); +}); diff --git a/test/jasmine/performance_tests/image_test.js b/test/jasmine/performance_tests/image_test.js new file mode 100644 index 00000000000..7e5a60eda72 --- /dev/null +++ b/test/jasmine/performance_tests/image_test.js @@ -0,0 +1,119 @@ +var createGraphDiv = require('../assets/create_graph_div'); +var delay = require('../assets/delay'); +var d3SelectAll = require('../../strict-d3').selectAll; +var Plotly = require('../../../lib/core'); +var PlotlyImage = require('../../../lib/image'); + +var gd = createGraphDiv(); + +var tests = [{ + nx: 50, ny: 20, averageCap: 75 +}, { + nx: 100, ny: 40, averageCap: 100 +}, { + nx: 200, ny: 80, averageCap: 150 +}, { + nx: 400, ny: 160, averageCap: 300 +}, { + nx: 800, ny: 320, averageCap: 600 +}, { + nx: 1600, ny: 640, averageCap: 1200 +}, { + nx: 3200, ny: 1280, averageCap: 2400 +}]; + +tests.forEach(function(spec, index) { + describe('Performance test image | size:' + spec.nx + 'X' + spec.ny, function() { + 'use strict'; + + Plotly.register(PlotlyImage); + + const samples = Array.from({ length: 9 }, (_, i) => i); + const nTimes = samples.length - 1; + + var A = spec.nx; + var B = spec.ny; + spec.n = A * B; + + var x = Uint16Array.from({ length: A }, (_, i) => i); + var y = Uint16Array.from({ length: B }, (_, i) => i); + var z = []; + for(var k = 0; k < B ; k++) { + z[k] = []; + for(var i = 0; i < A ; i++) { + z[k][i] = [ + Math.floor(127 * (1 + Math.cos(Math.sqrt(i)))), + 0, + Math.floor(127 * (1 + Math.cos(Math.sqrt(k)))), + ]; + } + } + + var mock = { + data: [{ + type: 'image', + x: x, + y: y, + z: z + }], + layout: { + width: 900, + height: 400 + } + }; + + var startTime; + + beforeEach(function(done) { + startTime = Date.now(); + + Plotly.newPlot(gd, mock).then(done); + }); + + afterEach(function(done) { + delay(100)().then(done); + }); + + var maxDelta = 0; + var aveDelta = 0; + + samples.forEach(function(t) { + it('should graph image traces | turn: ' + t, function() { + var delta = Date.now() - startTime; + + if(t === 0) { + // console.log('________________________________'); + // console.log('number of points: ' + spec.n); + // console.log('expected average (cap): ' + spec.averageCap + ' ms'); + + tests[index].raw = []; + } + tests[index].raw[t] = delta; + + if(t > 0) { // we skip the first run which is slow + maxDelta = Math.max(maxDelta, delta); + aveDelta += delta / nTimes; + } + + // console.log('turn: ' + t + ' | ' + delta + ' ms'); + + if(t === nTimes) { + tests[index].average = aveDelta; + tests[index].maximum = maxDelta; + + // console.log('max: ' + maxDelta); + // console.log('ave: ' + aveDelta); + + expect(aveDelta).toBeLessThan(spec.averageCap); + } + + var nodes = d3SelectAll('g.imagelayer.mlayer'); + expect(nodes.size()).toEqual(1); + + if(t === nTimes && index === tests.length - 1) { + console.log(JSON.stringify(tests, null, 2)); + } + }); + }); + }); +}); diff --git a/test/jasmine/performance_tests/scatter_test.js b/test/jasmine/performance_tests/scatter_test.js index 23634877680..741c134b75d 100644 --- a/test/jasmine/performance_tests/scatter_test.js +++ b/test/jasmine/performance_tests/scatter_test.js @@ -5,7 +5,7 @@ var Plotly = require('../../../lib/core'); var gd = createGraphDiv(); -[{ +var tests = [{ n: 1000, averageCap: 75 }, { n: 2000, averageCap: 100 @@ -19,11 +19,13 @@ var gd = createGraphDiv(); n: 32000, averageCap: 1200 }, { n: 64000, averageCap: 2400 -}].forEach(function(spec) { - describe('Bundle with scatter | size:' + spec.n, function() { +}]; + +tests.forEach(function(spec, index) { + describe('Performance test scatter | size:' + spec.n, function() { 'use strict'; - const samples = Array.from({ length: 5 }, (_, i) => i); + const samples = Array.from({ length: 9 }, (_, i) => i); const nTimes = samples.length - 1; var y = Float64Array.from({ length: spec.n }, (_, i) => i * Math.cos(Math.sqrt(i))); @@ -35,7 +37,7 @@ var gd = createGraphDiv(); y: y }], layout: { - width: 1200, + width: 900, height: 400 } }; @@ -60,27 +62,37 @@ var gd = createGraphDiv(); var delta = Date.now() - startTime; if(t === 0) { - console.log('________________________________'); - console.log('number of points in scatter: ' + spec.n); - console.log('expected average (cap): ' + spec.averageCap + ' ms'); + // console.log('________________________________'); + // console.log('number of points: ' + spec.n); + // console.log('expected average (cap): ' + spec.averageCap + ' ms'); + + tests[index].raw = []; } + tests[index].raw[t] = delta; if(t > 0) { // we skip the first run which is slow maxDelta = Math.max(maxDelta, delta); aveDelta += delta / nTimes; } - console.log('turn: ' + t + ' | ' + delta + ' ms'); + // console.log('turn: ' + t + ' | ' + delta + ' ms'); if(t === nTimes) { - console.log('max: ' + maxDelta); - console.log('ave: ' + aveDelta); + tests[index].average = aveDelta; + tests[index].maximum = maxDelta; + + // console.log('max: ' + maxDelta); + // console.log('ave: ' + aveDelta); expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.trace.scatter'); expect(nodes.size()).toEqual(1); + + if(t === nTimes && index === tests.length - 1) { + console.log(JSON.stringify(tests, null, 2)); + } }); }); }); From 0d9eb560d11000fb42c04b0413c77d29177c3f29 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 19 Jun 2025 18:57:02 -0400 Subject: [PATCH 3/9] skip testing against averageCap for now --- test/jasmine/performance_tests/bar_test.js | 2 +- test/jasmine/performance_tests/box_test.js | 2 +- test/jasmine/performance_tests/contour_test.js | 2 +- test/jasmine/performance_tests/heatmap_test.js | 2 +- test/jasmine/performance_tests/histogram_test.js | 2 +- test/jasmine/performance_tests/image_test.js | 2 +- test/jasmine/performance_tests/scatter_test.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/jasmine/performance_tests/bar_test.js b/test/jasmine/performance_tests/bar_test.js index 4276c4c3840..4e380917d86 100644 --- a/test/jasmine/performance_tests/bar_test.js +++ b/test/jasmine/performance_tests/bar_test.js @@ -86,7 +86,7 @@ tests.forEach(function(spec, index) { // console.log('max: ' + maxDelta); // console.log('ave: ' + aveDelta); - expect(aveDelta).toBeLessThan(spec.averageCap); + // expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.trace.bars'); diff --git a/test/jasmine/performance_tests/box_test.js b/test/jasmine/performance_tests/box_test.js index 65e4ff374c0..cdfd388c563 100644 --- a/test/jasmine/performance_tests/box_test.js +++ b/test/jasmine/performance_tests/box_test.js @@ -87,7 +87,7 @@ tests.forEach(function(spec, index) { // console.log('max: ' + maxDelta); // console.log('ave: ' + aveDelta); - expect(aveDelta).toBeLessThan(spec.averageCap); + // expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.trace.boxes'); diff --git a/test/jasmine/performance_tests/contour_test.js b/test/jasmine/performance_tests/contour_test.js index 00b4893fa62..d3d74e0085f 100644 --- a/test/jasmine/performance_tests/contour_test.js +++ b/test/jasmine/performance_tests/contour_test.js @@ -97,7 +97,7 @@ tests.forEach(function(spec, index) { // console.log('max: ' + maxDelta); // console.log('ave: ' + aveDelta); - expect(aveDelta).toBeLessThan(spec.averageCap); + // expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.contourlayer'); diff --git a/test/jasmine/performance_tests/heatmap_test.js b/test/jasmine/performance_tests/heatmap_test.js index 454624bb716..51499809359 100644 --- a/test/jasmine/performance_tests/heatmap_test.js +++ b/test/jasmine/performance_tests/heatmap_test.js @@ -97,7 +97,7 @@ tests.forEach(function(spec, index) { // console.log('max: ' + maxDelta); // console.log('ave: ' + aveDelta); - expect(aveDelta).toBeLessThan(spec.averageCap); + // expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.heatmaplayer'); diff --git a/test/jasmine/performance_tests/histogram_test.js b/test/jasmine/performance_tests/histogram_test.js index 814c1f3c381..9a1328e7ed1 100644 --- a/test/jasmine/performance_tests/histogram_test.js +++ b/test/jasmine/performance_tests/histogram_test.js @@ -86,7 +86,7 @@ tests.forEach(function(spec, index) { // console.log('max: ' + maxDelta); // console.log('ave: ' + aveDelta); - expect(aveDelta).toBeLessThan(spec.averageCap); + // expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.trace.bars'); diff --git a/test/jasmine/performance_tests/image_test.js b/test/jasmine/performance_tests/image_test.js index 7e5a60eda72..58ea002fd06 100644 --- a/test/jasmine/performance_tests/image_test.js +++ b/test/jasmine/performance_tests/image_test.js @@ -104,7 +104,7 @@ tests.forEach(function(spec, index) { // console.log('max: ' + maxDelta); // console.log('ave: ' + aveDelta); - expect(aveDelta).toBeLessThan(spec.averageCap); + // expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.imagelayer.mlayer'); diff --git a/test/jasmine/performance_tests/scatter_test.js b/test/jasmine/performance_tests/scatter_test.js index 741c134b75d..d81abe8b1a4 100644 --- a/test/jasmine/performance_tests/scatter_test.js +++ b/test/jasmine/performance_tests/scatter_test.js @@ -84,7 +84,7 @@ tests.forEach(function(spec, index) { // console.log('max: ' + maxDelta); // console.log('ave: ' + aveDelta); - expect(aveDelta).toBeLessThan(spec.averageCap); + // expect(aveDelta).toBeLessThan(spec.averageCap); } var nodes = d3SelectAll('g.trace.scatter'); From d7bf5b5bc0939edacdde33caa01107d872521cbd Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 19 Jun 2025 19:35:18 -0400 Subject: [PATCH 4/9] convert raw data to CSV --- .../performance_tests/assets/post_process.js | 15 +++++++++++++++ test/jasmine/performance_tests/bar_test.js | 3 +++ test/jasmine/performance_tests/box_test.js | 3 +++ test/jasmine/performance_tests/contour_test.js | 3 +++ test/jasmine/performance_tests/heatmap_test.js | 3 +++ test/jasmine/performance_tests/histogram_test.js | 3 +++ test/jasmine/performance_tests/image_test.js | 3 +++ test/jasmine/performance_tests/scatter_test.js | 3 +++ 8 files changed, 36 insertions(+) create mode 100644 test/jasmine/performance_tests/assets/post_process.js diff --git a/test/jasmine/performance_tests/assets/post_process.js b/test/jasmine/performance_tests/assets/post_process.js new file mode 100644 index 00000000000..1512632bb45 --- /dev/null +++ b/test/jasmine/performance_tests/assets/post_process.js @@ -0,0 +1,15 @@ +'use strict'; + +exports.writeRawDataAsCSV = function(traceName, allTests) { + for(var k = 0; k < allTests.length; k++) { + var test = allTests[k]; + + var str = traceName + ',' + test.n + '\n'; + str += 'id,time(ms)\n'; + for(var i = 0; i < test.raw.length; i++) { + str += i + ',' + test.raw[i] + '\n'; + } + + console.log(str); + } +}; diff --git a/test/jasmine/performance_tests/bar_test.js b/test/jasmine/performance_tests/bar_test.js index 4e380917d86..7be47841da7 100644 --- a/test/jasmine/performance_tests/bar_test.js +++ b/test/jasmine/performance_tests/bar_test.js @@ -3,6 +3,7 @@ var delay = require('../assets/delay'); var d3SelectAll = require('../../strict-d3').selectAll; var Plotly = require('../../../lib/core'); var PlotlyBar = require('../../../lib/bar'); +var writeRawDataAsCSV = require('./assets/post_process').writeRawDataAsCSV; var gd = createGraphDiv(); @@ -94,6 +95,8 @@ tests.forEach(function(spec, index) { if(t === nTimes && index === tests.length - 1) { console.log(JSON.stringify(tests, null, 2)); + + writeRawDataAsCSV('bar', tests); } }); }); diff --git a/test/jasmine/performance_tests/box_test.js b/test/jasmine/performance_tests/box_test.js index cdfd388c563..9bf10dbeb20 100644 --- a/test/jasmine/performance_tests/box_test.js +++ b/test/jasmine/performance_tests/box_test.js @@ -3,6 +3,7 @@ var delay = require('../assets/delay'); var d3SelectAll = require('../../strict-d3').selectAll; var Plotly = require('../../../lib/core'); var PlotlyBox = require('../../../lib/box'); +var writeRawDataAsCSV = require('./assets/post_process').writeRawDataAsCSV; var gd = createGraphDiv(); @@ -95,6 +96,8 @@ tests.forEach(function(spec, index) { if(t === nTimes && index === tests.length - 1) { console.log(JSON.stringify(tests, null, 2)); + + writeRawDataAsCSV('box', tests); } }); }); diff --git a/test/jasmine/performance_tests/contour_test.js b/test/jasmine/performance_tests/contour_test.js index d3d74e0085f..5e53db58938 100644 --- a/test/jasmine/performance_tests/contour_test.js +++ b/test/jasmine/performance_tests/contour_test.js @@ -3,6 +3,7 @@ var delay = require('../assets/delay'); var d3SelectAll = require('../../strict-d3').selectAll; var Plotly = require('../../../lib/core'); var PlotlyContour = require('../../../lib/contour'); +var writeRawDataAsCSV = require('./assets/post_process').writeRawDataAsCSV; var gd = createGraphDiv(); @@ -105,6 +106,8 @@ tests.forEach(function(spec, index) { if(t === nTimes && index === tests.length - 1) { console.log(JSON.stringify(tests, null, 2)); + + writeRawDataAsCSV('contour', tests); } }); }); diff --git a/test/jasmine/performance_tests/heatmap_test.js b/test/jasmine/performance_tests/heatmap_test.js index 51499809359..b23db01145d 100644 --- a/test/jasmine/performance_tests/heatmap_test.js +++ b/test/jasmine/performance_tests/heatmap_test.js @@ -3,6 +3,7 @@ var delay = require('../assets/delay'); var d3SelectAll = require('../../strict-d3').selectAll; var Plotly = require('../../../lib/core'); var PlotlyHeatmap = require('../../../lib/heatmap'); +var writeRawDataAsCSV = require('./assets/post_process').writeRawDataAsCSV; var gd = createGraphDiv(); @@ -105,6 +106,8 @@ tests.forEach(function(spec, index) { if(t === nTimes && index === tests.length - 1) { console.log(JSON.stringify(tests, null, 2)); + + writeRawDataAsCSV('heatmap', tests); } }); }); diff --git a/test/jasmine/performance_tests/histogram_test.js b/test/jasmine/performance_tests/histogram_test.js index 9a1328e7ed1..bc3c936645d 100644 --- a/test/jasmine/performance_tests/histogram_test.js +++ b/test/jasmine/performance_tests/histogram_test.js @@ -3,6 +3,7 @@ var delay = require('../assets/delay'); var d3SelectAll = require('../../strict-d3').selectAll; var Plotly = require('../../../lib/core'); var PlotlyHistogram = require('../../../lib/histogram'); +var writeRawDataAsCSV = require('./assets/post_process').writeRawDataAsCSV; var gd = createGraphDiv(); @@ -94,6 +95,8 @@ tests.forEach(function(spec, index) { if(t === nTimes && index === tests.length - 1) { console.log(JSON.stringify(tests, null, 2)); + + writeRawDataAsCSV('histogram', tests); } }); }); diff --git a/test/jasmine/performance_tests/image_test.js b/test/jasmine/performance_tests/image_test.js index 58ea002fd06..f388cebdcda 100644 --- a/test/jasmine/performance_tests/image_test.js +++ b/test/jasmine/performance_tests/image_test.js @@ -3,6 +3,7 @@ var delay = require('../assets/delay'); var d3SelectAll = require('../../strict-d3').selectAll; var Plotly = require('../../../lib/core'); var PlotlyImage = require('../../../lib/image'); +var writeRawDataAsCSV = require('./assets/post_process').writeRawDataAsCSV; var gd = createGraphDiv(); @@ -112,6 +113,8 @@ tests.forEach(function(spec, index) { if(t === nTimes && index === tests.length - 1) { console.log(JSON.stringify(tests, null, 2)); + + writeRawDataAsCSV('image', tests); } }); }); diff --git a/test/jasmine/performance_tests/scatter_test.js b/test/jasmine/performance_tests/scatter_test.js index d81abe8b1a4..8e73308766f 100644 --- a/test/jasmine/performance_tests/scatter_test.js +++ b/test/jasmine/performance_tests/scatter_test.js @@ -2,6 +2,7 @@ var createGraphDiv = require('../assets/create_graph_div'); var delay = require('../assets/delay'); var d3SelectAll = require('../../strict-d3').selectAll; var Plotly = require('../../../lib/core'); +var writeRawDataAsCSV = require('./assets/post_process').writeRawDataAsCSV; var gd = createGraphDiv(); @@ -92,6 +93,8 @@ tests.forEach(function(spec, index) { if(t === nTimes && index === tests.length - 1) { console.log(JSON.stringify(tests, null, 2)); + + writeRawDataAsCSV('scatter', tests); } }); }); From 9f607765cdd2fbe3a8d9e5cd9ae94745a7960244 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 20 Jun 2025 09:39:28 -0400 Subject: [PATCH 5/9] wait for actual rendering to complete --- test/jasmine/performance_tests/bar_test.js | 16 ++++++++++++---- test/jasmine/performance_tests/box_test.js | 16 ++++++++++++---- test/jasmine/performance_tests/contour_test.js | 16 ++++++++++++---- test/jasmine/performance_tests/heatmap_test.js | 16 ++++++++++++---- test/jasmine/performance_tests/histogram_test.js | 16 ++++++++++++---- test/jasmine/performance_tests/image_test.js | 16 ++++++++++++---- test/jasmine/performance_tests/scatter_test.js | 16 ++++++++++++---- 7 files changed, 84 insertions(+), 28 deletions(-) diff --git a/test/jasmine/performance_tests/bar_test.js b/test/jasmine/performance_tests/bar_test.js index 7be47841da7..4b6ac713e99 100644 --- a/test/jasmine/performance_tests/bar_test.js +++ b/test/jasmine/performance_tests/bar_test.js @@ -45,12 +45,20 @@ tests.forEach(function(spec, index) { } }; - var startTime; + var startTime, endTime; beforeEach(function(done) { - startTime = Date.now(); + startTime = performance.now(); + + // Wait for actual rendering to complete + requestAnimationFrame(function() { + requestAnimationFrame(function() { + endTime = performance.now(); + done(); + }); + }); - Plotly.newPlot(gd, mock).then(done); + Plotly.newPlot(gd, mock); }); afterEach(function(done) { @@ -62,7 +70,7 @@ tests.forEach(function(spec, index) { samples.forEach(function(t) { it('should graph bar traces | turn: ' + t, function() { - var delta = Date.now() - startTime; + var delta = endTime - startTime; if(t === 0) { // console.log('________________________________'); diff --git a/test/jasmine/performance_tests/box_test.js b/test/jasmine/performance_tests/box_test.js index 9bf10dbeb20..aa4d4d221dc 100644 --- a/test/jasmine/performance_tests/box_test.js +++ b/test/jasmine/performance_tests/box_test.js @@ -46,12 +46,20 @@ tests.forEach(function(spec, index) { } }; - var startTime; + var startTime, endTime; beforeEach(function(done) { - startTime = Date.now(); + startTime = performance.now(); + + // Wait for actual rendering to complete + requestAnimationFrame(function() { + requestAnimationFrame(function() { + endTime = performance.now(); + done(); + }); + }); - Plotly.newPlot(gd, mock).then(done); + Plotly.newPlot(gd, mock); }); afterEach(function(done) { @@ -63,7 +71,7 @@ tests.forEach(function(spec, index) { samples.forEach(function(t) { it('should graph box traces | turn: ' + t, function() { - var delta = Date.now() - startTime; + var delta = endTime - startTime; if(t === 0) { // console.log('________________________________'); diff --git a/test/jasmine/performance_tests/contour_test.js b/test/jasmine/performance_tests/contour_test.js index 5e53db58938..722a0d0d95d 100644 --- a/test/jasmine/performance_tests/contour_test.js +++ b/test/jasmine/performance_tests/contour_test.js @@ -56,12 +56,20 @@ tests.forEach(function(spec, index) { } }; - var startTime; + var startTime, endTime; beforeEach(function(done) { - startTime = Date.now(); + startTime = performance.now(); + + // Wait for actual rendering to complete + requestAnimationFrame(function() { + requestAnimationFrame(function() { + endTime = performance.now(); + done(); + }); + }); - Plotly.newPlot(gd, mock).then(done); + Plotly.newPlot(gd, mock); }); afterEach(function(done) { @@ -73,7 +81,7 @@ tests.forEach(function(spec, index) { samples.forEach(function(t) { it('should graph contour traces | turn: ' + t, function() { - var delta = Date.now() - startTime; + var delta = endTime - startTime; if(t === 0) { // console.log('________________________________'); diff --git a/test/jasmine/performance_tests/heatmap_test.js b/test/jasmine/performance_tests/heatmap_test.js index b23db01145d..fc9d01d5be9 100644 --- a/test/jasmine/performance_tests/heatmap_test.js +++ b/test/jasmine/performance_tests/heatmap_test.js @@ -56,12 +56,20 @@ tests.forEach(function(spec, index) { } }; - var startTime; + var startTime, endTime; beforeEach(function(done) { - startTime = Date.now(); + startTime = performance.now(); + + // Wait for actual rendering to complete + requestAnimationFrame(function() { + requestAnimationFrame(function() { + endTime = performance.now(); + done(); + }); + }); - Plotly.newPlot(gd, mock).then(done); + Plotly.newPlot(gd, mock); }); afterEach(function(done) { @@ -73,7 +81,7 @@ tests.forEach(function(spec, index) { samples.forEach(function(t) { it('should graph heatmap traces | turn: ' + t, function() { - var delta = Date.now() - startTime; + var delta = endTime - startTime; if(t === 0) { // console.log('________________________________'); diff --git a/test/jasmine/performance_tests/histogram_test.js b/test/jasmine/performance_tests/histogram_test.js index bc3c936645d..b95d80cd4ac 100644 --- a/test/jasmine/performance_tests/histogram_test.js +++ b/test/jasmine/performance_tests/histogram_test.js @@ -45,12 +45,20 @@ tests.forEach(function(spec, index) { } }; - var startTime; + var startTime, endTime; beforeEach(function(done) { - startTime = Date.now(); + startTime = performance.now(); + + // Wait for actual rendering to complete + requestAnimationFrame(function() { + requestAnimationFrame(function() { + endTime = performance.now(); + done(); + }); + }); - Plotly.newPlot(gd, mock).then(done); + Plotly.newPlot(gd, mock); }); afterEach(function(done) { @@ -62,7 +70,7 @@ tests.forEach(function(spec, index) { samples.forEach(function(t) { it('should graph histogram traces | turn: ' + t, function() { - var delta = Date.now() - startTime; + var delta = endTime - startTime; if(t === 0) { // console.log('________________________________'); diff --git a/test/jasmine/performance_tests/image_test.js b/test/jasmine/performance_tests/image_test.js index f388cebdcda..73a3d9ea020 100644 --- a/test/jasmine/performance_tests/image_test.js +++ b/test/jasmine/performance_tests/image_test.js @@ -63,12 +63,20 @@ tests.forEach(function(spec, index) { } }; - var startTime; + var startTime, endTime; beforeEach(function(done) { - startTime = Date.now(); + startTime = performance.now(); + + // Wait for actual rendering to complete + requestAnimationFrame(function() { + requestAnimationFrame(function() { + endTime = performance.now(); + done(); + }); + }); - Plotly.newPlot(gd, mock).then(done); + Plotly.newPlot(gd, mock); }); afterEach(function(done) { @@ -80,7 +88,7 @@ tests.forEach(function(spec, index) { samples.forEach(function(t) { it('should graph image traces | turn: ' + t, function() { - var delta = Date.now() - startTime; + var delta = endTime - startTime; if(t === 0) { // console.log('________________________________'); diff --git a/test/jasmine/performance_tests/scatter_test.js b/test/jasmine/performance_tests/scatter_test.js index 8e73308766f..a559b43b046 100644 --- a/test/jasmine/performance_tests/scatter_test.js +++ b/test/jasmine/performance_tests/scatter_test.js @@ -43,12 +43,20 @@ tests.forEach(function(spec, index) { } }; - var startTime; + var startTime, endTime; beforeEach(function(done) { - startTime = Date.now(); + startTime = performance.now(); + + // Wait for actual rendering to complete + requestAnimationFrame(function() { + requestAnimationFrame(function() { + endTime = performance.now(); + done(); + }); + }); - Plotly.newPlot(gd, mock).then(done); + Plotly.newPlot(gd, mock); }); afterEach(function(done) { @@ -60,7 +68,7 @@ tests.forEach(function(spec, index) { samples.forEach(function(t) { it('should graph scatter traces | turn: ' + t, function() { - var delta = Date.now() - startTime; + var delta = endTime - startTime; if(t === 0) { // console.log('________________________________'); From 12f73ad05098a32e8c2c8057fcc552ca7df84cc1 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 20 Jun 2025 10:38:13 -0400 Subject: [PATCH 6/9] download CSV file --- .../performance_tests/assets/post_process.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/jasmine/performance_tests/assets/post_process.js b/test/jasmine/performance_tests/assets/post_process.js index 1512632bb45..21dda96b62c 100644 --- a/test/jasmine/performance_tests/assets/post_process.js +++ b/test/jasmine/performance_tests/assets/post_process.js @@ -1,15 +1,27 @@ -'use strict'; - exports.writeRawDataAsCSV = function(traceName, allTests) { + var str = ''; for(var k = 0; k < allTests.length; k++) { var test = allTests[k]; - var str = traceName + ',' + test.n + '\n'; + str += traceName + ',' + test.n + '\n'; str += 'id,time(ms)\n'; for(var i = 0; i < test.raw.length; i++) { str += i + ',' + test.raw[i] + '\n'; } + str += '\n'; console.log(str); } + + // download a CSV file + var a = document.createElement('a'); + var myBlob = new Blob([str], {type: 'text/plain'}) + var url = window.URL.createObjectURL(myBlob); + a.href = url; + a.download = traceName + '.csv'; + a.style.display = 'none'; + document.body.append(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); }; From 2a4bc1ba9696e24a0eed44d390f8c7010e3e66f3 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 20 Jun 2025 11:24:11 -0400 Subject: [PATCH 7/9] revise CSV table --- test/jasmine/performance_tests/assets/post_process.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/jasmine/performance_tests/assets/post_process.js b/test/jasmine/performance_tests/assets/post_process.js index 21dda96b62c..898df5c8e94 100644 --- a/test/jasmine/performance_tests/assets/post_process.js +++ b/test/jasmine/performance_tests/assets/post_process.js @@ -1,18 +1,15 @@ exports.writeRawDataAsCSV = function(traceName, allTests) { - var str = ''; + var str = 'chart type,data points,run id,rendering time(ms)\n'; for(var k = 0; k < allTests.length; k++) { var test = allTests[k]; - str += traceName + ',' + test.n + '\n'; - str += 'id,time(ms)\n'; for(var i = 0; i < test.raw.length; i++) { - str += i + ',' + test.raw[i] + '\n'; + str += traceName + ',' + test.n + ',' + i + ',' + test.raw[i] + '\n'; } - str += '\n'; - - console.log(str); } + console.log(str); + // download a CSV file var a = document.createElement('a'); var myBlob = new Blob([str], {type: 'text/plain'}) From ba4327c38aa623a20b044eef2f9768ccbc4d54af Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 20 Jun 2025 11:26:59 -0400 Subject: [PATCH 8/9] store CSV --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6701dd7e259..6a1378f0800 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,6 +53,9 @@ jobs: - run: name: Run performance tests command: .circleci/test.sh performance-jasmine + - store_artifacts: + path: ~/Downloads + destination: / timezone-jasmine: From 0130392337dba3799c2488470c20e02e0ab967b6 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 20 Jun 2025 13:32:47 -0400 Subject: [PATCH 9/9] collect system info for performance tests --- .circleci/config.yml | 7 +++- package.json | 1 + tasks/system_info.js | 77 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tasks/system_info.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a1378f0800..74e5742782e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,13 +50,18 @@ jobs: chrome-version: "132.0.6834.110" - attach_workspace: at: ~/ + - run: + name: Display system information + command: npm run system-info > system_info.txt - run: name: Run performance tests command: .circleci/test.sh performance-jasmine - store_artifacts: path: ~/Downloads destination: / - + - store_artifacts: + path: ~/system_info.txt + destination: / timezone-jasmine: docker: diff --git a/package.json b/package.json index 94dd2045e0d..9d5930a4030 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "test-syntax": "node tasks/test_syntax.js && npm run find-strings -- --no-output", "test-bundle": "node tasks/test_bundle.js", "test-performance": "node tasks/test_performance.js", + "system-info": "node tasks/system_info.js", "test-plain-obj": "node tasks/test_plain_obj.mjs", "test": "npm run test-jasmine -- --nowatch && npm run test-bundle && npm run test-image && npm run test-export && npm run test-syntax && npm run lint", "b64": "python3 test/image/generate_b64_mocks.py && node devtools/test_dashboard/server.mjs", diff --git a/tasks/system_info.js b/tasks/system_info.js new file mode 100644 index 00000000000..fd09c670100 --- /dev/null +++ b/tasks/system_info.js @@ -0,0 +1,77 @@ +var os = require('os'); + +var logs = []; +function addLog(str) { + logs.push(str) +} + +var systemInfo = { + platform: os.platform(), + type: os.type(), + arch: os.arch(), + release: os.release(), + version: os.version ? os.version() : 'Unknown', + hostname: os.hostname(), + homedir: os.homedir(), + tmpdir: os.tmpdir(), + endianness: os.endianness(), +}; + +addLog('💻 SYSTEM:'); +addLog(` Platform: ${systemInfo.platform}`); +addLog(` Type: ${systemInfo.type}`); +addLog(` Architecture: ${systemInfo.arch}`); +addLog(` Release: ${systemInfo.release}`); +addLog(` Hostname: ${systemInfo.hostname}`); + + +var cpus = os.cpus(); +var loadAvg = os.loadavg(); + +var cpuInfo = { + model: cpus[0].model, + speed: cpus[0].speed, + cores: cpus.length, + loadAverage: loadAvg, + cpuDetails: cpus +}; + +addLog(''); +addLog('🔧 CPU:'); +addLog(` Model: ${cpuInfo.model}`); +addLog(` Speed: ${cpuInfo.speed} MHz`); +addLog(` Cores: ${cpuInfo.cores}${cpuInfo.physicalCores ? ` (${cpuInfo.physicalCores} physical)` : ''}`); +addLog(` Load Average: ${loadAvg.map(load => load.toFixed(2)).join(', ')}`); + + +var totalMem = os.totalmem(); +var freeMem = os.freemem(); +var usedMem = totalMem - freeMem; + +var memoryInfo = { + total: totalMem, + free: freeMem, + used: usedMem, + usagePercent: (usedMem / totalMem) * 100 +}; + +function formatBytes(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + if (!bytes) return 'Unknown'; + + var k = 1024; + var dm = decimals < 0 ? 0 : decimals; + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; + var i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} + +addLog(''); +addLog('💾 MEMORY:'); +addLog(` Total: ${formatBytes(memoryInfo.total)}`); +addLog(` Used: ${formatBytes(memoryInfo.used)} (${memoryInfo.usagePercent.toFixed(1)}%)`); +addLog(` Free: ${formatBytes(memoryInfo.free)}`); + + +console.log(logs.join('\n')); \ No newline at end of file