Skip to content

Commit d5a2865

Browse files
committed
add performance tests
1 parent e2fd2bb commit d5a2865

File tree

8 files changed

+281
-4
lines changed

8 files changed

+281
-4
lines changed

.circleci/config.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,28 @@ jobs:
3333
paths:
3434
- plotly.js
3535

36+
performance-jasmine:
37+
docker:
38+
# need '-browsers' version to test in real (xvfb-wrapped) browsers
39+
- image: cimg/node:18.20.4-browsers
40+
environment:
41+
# Alaska time (arbitrary timezone to test date logic)
42+
TZ: "America/Anchorage"
43+
working_directory: ~/plotly.js
44+
steps:
45+
- run: sudo apt-get update
46+
- browser-tools/install-browser-tools:
47+
install-firefox: false
48+
install-geckodriver: false
49+
install-chrome: true
50+
chrome-version: "132.0.6834.110"
51+
- attach_workspace:
52+
at: ~/
53+
- run:
54+
name: Run performance tests
55+
command: .circleci/test.sh performance-jasmine
56+
57+
3658
timezone-jasmine:
3759
docker:
3860
# need '-browsers' version to test in real (xvfb-wrapped) browsers
@@ -500,6 +522,9 @@ workflows:
500522
- bundle-jasmine:
501523
requires:
502524
- install-and-cibuild
525+
- performance-jasmine:
526+
requires:
527+
- install-and-cibuild
503528
- mathjax-firefoxLatest:
504529
requires:
505530
- install-and-cibuild

.circleci/test.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ case $1 in
7979
exit $EXIT_STATE
8080
;;
8181

82+
performance-jasmine)
83+
npm run test-performance || EXIT_STATE=$?
84+
exit $EXIT_STATE
85+
;;
86+
8287
mathjax-firefox)
8388
./node_modules/karma/bin/karma start test/jasmine/karma.conf.js --FF --bundleTest=mathjax --nowatch || EXIT_STATE=$?
8489
exit $EXIT_STATE

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"test-export": "node test/image/export_test.js",
4848
"test-syntax": "node tasks/test_syntax.js && npm run find-strings -- --no-output",
4949
"test-bundle": "node tasks/test_bundle.js",
50+
"test-performance": "node tasks/test_performance.js",
5051
"test-plain-obj": "node tasks/test_plain_obj.mjs",
5152
"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",
5253
"b64": "python3 test/image/generate_b64_mocks.py && node devtools/test_dashboard/server.mjs",

tasks/test_performance.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
var path = require('path');
2+
var exec = require('child_process').exec;
3+
var { glob } = require('glob');
4+
var runSeries = require('run-series');
5+
6+
var constants = require('./util/constants');
7+
var pathToJasminePerformanceTests = constants.pathToJasminePerformanceTests;
8+
9+
/**
10+
* Run all jasmine 'performance' test in series
11+
*
12+
* To run specific performance tests, use
13+
*
14+
* $ npm run test-jasmine -- --performanceTest=<name-of-suite>
15+
*/
16+
glob(pathToJasminePerformanceTests + '/*.js').then(function(files) {
17+
var tasks = files.map(function(file) {
18+
return function(cb) {
19+
var cmd = [
20+
'karma', 'start',
21+
path.join(constants.pathToRoot, 'test', 'jasmine', 'karma.conf.js'),
22+
'--performanceTest=' + path.basename(file),
23+
'--nowatch'
24+
].join(' ');
25+
26+
console.log('Running: ' + cmd);
27+
28+
exec(cmd, function(err) {
29+
cb(null, err);
30+
}).stdout.pipe(process.stdout);
31+
};
32+
});
33+
34+
runSeries(tasks, function(err, results) {
35+
if(err) throw err;
36+
37+
var failed = results.filter(function(r) { return r; });
38+
39+
if(failed.length) {
40+
console.log('\ntest-performance summary:');
41+
failed.forEach(function(r) { console.warn('- ' + r.cmd + ' failed'); });
42+
console.log('');
43+
process.exit(1);
44+
}
45+
});
46+
});

tasks/util/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ module.exports = {
225225

226226
pathToJasmineTests: path.join(pathToRoot, 'test/jasmine/tests'),
227227
pathToJasmineBundleTests: path.join(pathToRoot, 'test/jasmine/bundle_tests'),
228+
pathToJasminePerformanceTests: path.join(pathToRoot, 'test/jasmine/performance_tests'),
228229

229230
// this mapbox access token is 'public', no need to hide it
230231
// more info: https://www.mapbox.com/help/define-access-token/

test/jasmine/karma.conf.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var esbuildConfig = require('../../esbuild-config.js');
88
var isCI = Boolean(process.env.CI);
99

1010
var argv = minimist(process.argv.slice(4), {
11-
string: ['bundleTest', 'width', 'height'],
11+
string: ['bundleTest', 'performanceTest', 'width', 'height'],
1212
boolean: [
1313
'mathjax3',
1414
'info',
@@ -21,6 +21,7 @@ var argv = minimist(process.argv.slice(4), {
2121
Chrome: 'chrome',
2222
Firefox: ['firefox', 'FF'],
2323
bundleTest: ['bundletest', 'bundle_test'],
24+
performanceTest: ['performancetest', 'performance_test'],
2425
nowatch: 'no-watch',
2526
failFast: 'fail-fast',
2627
},
@@ -53,7 +54,8 @@ if(argv.info) {
5354
' - All non-flagged arguments corresponds to the test suites in `test/jasmine/tests/` to be run.',
5455
' No need to add the `_test.js` suffix, we expand them correctly here.',
5556
' - `--bundleTest` set the bundle test suite `test/jasmine/bundle_tests/ to be run.',
56-
' Note that only one bundle test can be run at a time.',
57+
' - `--performanceTest` set the bundle test suite `test/jasmine/performance_tests/ to be run.',
58+
' Note that only one bundle/performance test can be run at a time.',
5759
' - Use `--tags` to specify which `@` tags to test (if any) e.g `npm run test-jasmine -- --tags=gl`',
5860
' will run only gl tests.',
5961
' - 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(_) {
100102
};
101103

102104
var isBundleTest = !!argv.bundleTest;
103-
var isFullSuite = !isBundleTest && argv._.length === 0;
105+
var isPerformanceTest = !!argv.performanceTest;
106+
var isFullSuite = !(isBundleTest || isPerformanceTest) && argv._.length === 0;
104107
var testFileGlob;
105108

106109
if(isFullSuite) {
@@ -113,6 +116,14 @@ if(isFullSuite) {
113116
}
114117

115118
testFileGlob = path.join(__dirname, 'bundle_tests', glob([basename(_[0])]));
119+
} else if(isPerformanceTest) {
120+
var _ = merge(argv.performanceTest);
121+
122+
if(_.length > 1) {
123+
console.warn('Can only run one performance test suite at a time, ignoring ', _.slice(1));
124+
}
125+
126+
testFileGlob = path.join(__dirname, 'performance_tests', glob([basename(_[0])]));
116127
} else {
117128
testFileGlob = path.join(__dirname, 'tests', glob(merge(argv._).map(basename)));
118129
}
@@ -250,7 +261,7 @@ func.defaultConfig = {
250261
'--touch-events',
251262
'--window-size=' + argv.width + ',' + argv.height,
252263
isCI ? '--ignore-gpu-blacklist' : '',
253-
(isBundleTest && basename(testFileGlob) === 'no_webgl') ? '--disable-webgl' : ''
264+
((isBundleTest || isPerformanceTest) && basename(testFileGlob) === 'no_webgl') ? '--disable-webgl' : ''
254265
]
255266
},
256267
_Firefox: {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
var createGraphDiv = require('../assets/create_graph_div');
2+
var destroyGraphDiv = require('../assets/destroy_graph_div');
3+
var d3SelectAll = require('../../strict-d3').selectAll;
4+
5+
var Plotly = require('../../../lib/core');
6+
var PlotlyBar = require('../../../lib/bar');
7+
8+
[{
9+
n: 1000, averageCap: 50
10+
}, {
11+
n: 2000, averageCap: 75
12+
}, {
13+
n: 4000, averageCap: 100
14+
}, {
15+
n: 8000, averageCap: 200
16+
}, {
17+
n: 16000, averageCap: 400
18+
}, {
19+
n: 32000, averageCap: 800
20+
}, {
21+
n: 64000, averageCap: 1600
22+
}, {
23+
n: 128000, averageCap: 3200
24+
}, {
25+
n: 256000, averageCap: 6400
26+
}].forEach(function(spec) {
27+
describe('Bundle with bar | size:' + spec.n, function() {
28+
'use strict';
29+
30+
Plotly.register(PlotlyBar);
31+
32+
const samples = Array.from({ length: 8 }, (_, i) => i);
33+
const nTimes = samples.length - 1;
34+
35+
var y = Float64Array.from({ length: spec.n }, (_, i) => i * Math.cos(Math.sqrt(i)));
36+
37+
var mock = {
38+
data: [{
39+
type: 'bar',
40+
y: y
41+
}],
42+
layout: {
43+
width: 1200,
44+
height: 400
45+
}
46+
};
47+
48+
var startTime;
49+
50+
beforeEach(function(done) {
51+
var gd = createGraphDiv();
52+
53+
startTime = Date.now();
54+
55+
Plotly.newPlot(gd, mock).then(done);
56+
});
57+
58+
afterEach(function(done) {
59+
destroyGraphDiv();
60+
done();
61+
});
62+
63+
var maxDelta = 0;
64+
var aveDelta = 0;
65+
66+
samples.forEach(function(t) {
67+
it('should graph bar traces | turn: ' + t, function() {
68+
var delta = Date.now() - startTime;
69+
70+
if(t === 0) {
71+
console.log('________________________________');
72+
console.log('number of points in bar: ' + spec.n);
73+
console.log('expected average (cap): ' + spec.averageCap + ' ms');
74+
}
75+
76+
if(t > 0) { // we skip the first run which is slow
77+
maxDelta = Math.max(maxDelta, delta);
78+
aveDelta += delta / nTimes;
79+
}
80+
81+
console.log('turn: ' + t + ' | ' + delta + ' ms');
82+
83+
if(t === nTimes) {
84+
console.log('max: ' + maxDelta);
85+
console.log('ave: ' + aveDelta);
86+
87+
expect(aveDelta).toBeLessThan(spec.averageCap);
88+
}
89+
90+
var nodes = d3SelectAll('g.trace.bars');
91+
expect(nodes.size()).toEqual(1);
92+
});
93+
});
94+
});
95+
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
var createGraphDiv = require('../assets/create_graph_div');
2+
var destroyGraphDiv = require('../assets/destroy_graph_div');
3+
var d3SelectAll = require('../../strict-d3').selectAll;
4+
5+
var Plotly = require('../../../lib/core');
6+
7+
[{
8+
n: 1000, averageCap: 50
9+
}, {
10+
n: 2000, averageCap: 75
11+
}, {
12+
n: 4000, averageCap: 100
13+
}, {
14+
n: 8000, averageCap: 200
15+
}, {
16+
n: 16000, averageCap: 400
17+
}, {
18+
n: 32000, averageCap: 800
19+
}, {
20+
n: 64000, averageCap: 1600
21+
}, {
22+
n: 128000, averageCap: 3200
23+
}, {
24+
n: 256000, averageCap: 6400
25+
}].forEach(function(spec) {
26+
describe('Bundle with scatter | size:' + spec.n, function() {
27+
'use strict';
28+
29+
const samples = Array.from({ length: 8 }, (_, i) => i);
30+
const nTimes = samples.length - 1;
31+
32+
var y = Float64Array.from({ length: spec.n }, (_, i) => i * Math.cos(Math.sqrt(i)));
33+
34+
var mock = {
35+
data: [{
36+
type: 'scatter',
37+
mode: 'markers',
38+
y: y
39+
}],
40+
layout: {
41+
width: 1200,
42+
height: 400
43+
}
44+
};
45+
46+
var startTime;
47+
48+
beforeEach(function(done) {
49+
var gd = createGraphDiv();
50+
51+
startTime = Date.now();
52+
53+
Plotly.newPlot(gd, mock).then(done);
54+
});
55+
56+
afterEach(function(done) {
57+
destroyGraphDiv();
58+
done();
59+
});
60+
61+
var maxDelta = 0;
62+
var aveDelta = 0;
63+
64+
samples.forEach(function(t) {
65+
it('should graph scatter traces | turn: ' + t, function() {
66+
var delta = Date.now() - startTime;
67+
68+
if(t === 0) {
69+
console.log('________________________________');
70+
console.log('number of points in scatter: ' + spec.n);
71+
console.log('expected average (cap): ' + spec.averageCap + ' ms');
72+
}
73+
74+
if(t > 0) { // we skip the first run which is slow
75+
maxDelta = Math.max(maxDelta, delta);
76+
aveDelta += delta / nTimes;
77+
}
78+
79+
console.log('turn: ' + t + ' | ' + delta + ' ms');
80+
81+
if(t === nTimes) {
82+
console.log('max: ' + maxDelta);
83+
console.log('ave: ' + aveDelta);
84+
85+
expect(aveDelta).toBeLessThan(spec.averageCap);
86+
}
87+
88+
var nodes = d3SelectAll('g.trace.scatter');
89+
expect(nodes.size()).toEqual(1);
90+
});
91+
});
92+
});
93+
});

0 commit comments

Comments
 (0)