Skip to content

Add performance tests #7443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e83dbec
add performance tests
archmoj Jun 16, 2025
012abca
collect performance data and create test report
archmoj Jun 19, 2025
0d9eb56
skip testing against averageCap for now
archmoj Jun 19, 2025
d7bf5b5
convert raw data to CSV
archmoj Jun 19, 2025
9f60776
wait for actual rendering to complete
archmoj Jun 20, 2025
12f73ad
download CSV file
archmoj Jun 20, 2025
2a4bc1b
revise CSV table
archmoj Jun 20, 2025
ba4327c
store CSV
archmoj Jun 20, 2025
855eaa2
collect system info for performance tests
archmoj Jun 20, 2025
c46e7c6
combine all csv files
archmoj Jun 23, 2025
0e63814
do not display the JSON for now
archmoj Jun 23, 2025
5d2e5bb
simplify tests only keep raw results
archmoj Jun 23, 2025
94b2fe0
add violin tests
archmoj Jun 23, 2025
b532cb0
add scattergl tests
archmoj Jun 23, 2025
ef345ef
refactor tests
archmoj Jun 23, 2025
33620b7
revisit tests
archmoj Jun 23, 2025
b6dac79
extend scattergl
archmoj Jun 23, 2025
543478e
extend bar
archmoj Jun 23, 2025
ff93b2e
extend box
archmoj Jun 23, 2025
e3cd7b5
extend violin
archmoj Jun 23, 2025
0b0642a
extend histogram
archmoj Jun 23, 2025
1227e2a
revise download csv
archmoj Jun 23, 2025
cd5da32
add markers+lines cases
archmoj Jun 23, 2025
a4aa436
use normal arrays in tests
archmoj Jun 23, 2025
98252d4
add scattergeo tests
archmoj Jun 23, 2025
3134b61
set nSamples to 4
archmoj Jun 23, 2025
d4f3af2
format test messages
archmoj Jun 23, 2025
5d30519
revisit test descriptions
archmoj Jun 23, 2025
c99d887
combine all tests
archmoj Jun 23, 2025
bcb165d
improve tests
archmoj Jun 24, 2025
b9611ae
fix time calculation
archmoj Jun 24, 2025
f6aa0ab
remove console.log
archmoj Jun 24, 2025
80ac7ae
add delay between tests
archmoj Jun 24, 2025
fad4b36
improve collecting csv if the last one fails
archmoj Jun 24, 2025
cd874dd
fail rendering after 4 seconds
archmoj Jun 26, 2025
3fd0dfc
add test descriptions
archmoj Jun 26, 2025
d2baf67
delay was not helpful for passing on CI
archmoj Jun 26, 2025
577f7ac
pass arguments to isolate performance tests
archmoj Jun 26, 2025
c8dd83b
run each tests case in isolation
archmoj Jun 26, 2025
99e9efd
organise output files
archmoj Jun 26, 2025
d92d356
test extra points
archmoj Jun 27, 2025
a98fd66
revise test setup
archmoj Jun 27, 2025
ea673bc
refactor test
archmoj Jun 27, 2025
8974efe
create CSV for failed cases
archmoj Jun 27, 2025
0f80d1b
fix Download path
archmoj Jun 27, 2025
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
35 changes: 35 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@ 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
- run:
name: Display system information
command: npm run system-info > ~/Downloads/system_info.txt
- run:
name: Combine CSV files
command: |
head -n 1 `ls ~/Downloads/*.csv | head -n 1` > ~/Downloads/all.csv
tail -n+2 -q ~/Downloads/*.csv >> ~/Downloads/all.csv
- store_artifacts:
path: ~/Downloads
destination: /

timezone-jasmine:
docker:
# need '-browsers' version to test in real (xvfb-wrapped) browsers
Expand Down Expand Up @@ -500,6 +532,9 @@ workflows:
- bundle-jasmine:
requires:
- install-and-cibuild
- performance-jasmine:
requires:
- install-and-cibuild
- mathjax-firefoxLatest:
requires:
- install-and-cibuild
Expand Down
5 changes: 5 additions & 0 deletions .circleci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
"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",
"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",
Expand Down
77 changes: 77 additions & 0 deletions tasks/system_info.js
Original file line number Diff line number Diff line change
@@ -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'));
110 changes: 110 additions & 0 deletions tasks/test_performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
var fs = require('fs');
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;
var pathToRoot = constants.pathToRoot;

/**
* Run all jasmine 'performance' test in series
*
* To run specific performance tests, use
*
* $ npm run test-jasmine -- --performanceTest=<name-of-suite>
*/

var testCases = require('../test/jasmine/performance_tests/assets/test_cases').testCases;

glob(pathToJasminePerformanceTests + '/*.js').then(function(files) {
var tasks = [];
for(let file of files) {
for(let testCase of testCases) {
tasks.push(function(cb) {
var cmd = [
'karma', 'start',
path.join(constants.pathToRoot, 'test', 'jasmine', 'karma.conf.js'),
'--performanceTest=' + path.basename(file),
'--nowatch',
'--tracesType=' + testCase.traceType,
'--tracesMode=' + testCase.mode,
'--tracesCount=' + testCase.nTraces,
'--tracesPoints=' + testCase.n,
].join(' ');

console.log('Running: ' + cmd);

exec(cmd, function(err) {
cb(null, err);
}).stdout.pipe(process.stdout);
});
}
}

runSeries(tasks, function(err, results) {
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('');

// Create CSV file for failed cases
var str = [
'number of traces',
'chart type & mode',
'data points',
'run id',
'rendering time(ms)'
].join(',') + '\n';

failed.forEach(function(r) {
// split command string frist by space then by equal to get
var cmdArgs = r.cmd.split(' ').map(part => {
return part.split('=');
});

var test = {};

for(var i = 0; i < cmdArgs.length; i++) {
if('--tracesCount' === cmdArgs[i][0]) {
test.nTraces = cmdArgs[i][1];
}
}

for(var i = 0; i < cmdArgs.length; i++) {
if('--tracesType' === cmdArgs[i][0]) {
test.traceType = cmdArgs[i][1];
}
}

for(var i = 0; i < cmdArgs.length; i++) {
if('--tracesMode' === cmdArgs[i][0]) {
test.mode = cmdArgs[i][1];
}
}

for(var i = 0; i < cmdArgs.length; i++) {
if('--tracesPoints' === cmdArgs[i][0]) {
test.n = cmdArgs[i][1];
}
}

str += [
(test.nTraces || 1),
(test.traceType + (test.mode ? ' ' + test.mode : '')),
test.n,
'failed',
''
].join(',') + '\n';
});

var failedCSV = pathToRoot + '../Downloads/failed.csv';
console.log('Saving:', failedCSV)
console.log(str);
fs.writeFileSync(failedCSV, str);
}
});
});
1 change: 1 addition & 0 deletions tasks/util/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
33 changes: 27 additions & 6 deletions test/jasmine/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
},
Expand Down Expand Up @@ -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`',
Expand Down Expand Up @@ -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) {
Expand All @@ -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)));
}
Expand Down Expand Up @@ -145,7 +156,7 @@ var hasSpecReporter = reporters.indexOf('spec') !== -1;
if(!hasSpecReporter && argv.showSkipped) reporters.push('spec');
if(argv.verbose) reporters.push('verbose');

function func(config) {
var func = function(config) {
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
//
Expand All @@ -161,8 +172,18 @@ function func(config) {
level: 'debug'
};

if(isPerformanceTest) {
func.defaultConfig.client = func.defaultConfig.client || {};
func.defaultConfig.client.testCase = {
tracesType: config.tracesType,
tracesMode: config.tracesMode,
tracesCount: config.tracesCount,
tracesPoints: config.tracesPoints,
};
}

config.set(func.defaultConfig);
}
};

func.defaultConfig = {

Expand Down Expand Up @@ -250,7 +271,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: {
Expand Down
Loading
Loading