198 lines
7.2 KiB
JavaScript
198 lines
7.2 KiB
JavaScript
// *****************************************************************************
|
|
// Copyright (C) 2021 STMicroelectronics and others.
|
|
//
|
|
// This program and the accompanying materials are made available under the
|
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
// http://www.eclipse.org/legal/epl-2.0.
|
|
//
|
|
// This Source Code may also be made available under the following Secondary
|
|
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
// with the GNU Classpath Exception which is available at
|
|
// https://www.gnu.org/software/classpath/license.html.
|
|
//
|
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
// *****************************************************************************
|
|
// @ts-check
|
|
const fsx = require('fs-extra');
|
|
const { resolve } = require('path');
|
|
const { spawn, ChildProcess } = require('child_process');
|
|
const { delay, githubReporting, isLCP, lcp, measure } = require('./common-performance');
|
|
const traceConfigTemplate = require('./electron-trace-config.json');
|
|
const { exit } = require('process');
|
|
|
|
const basePath = resolve(__dirname, '../..');
|
|
const profilesPath = resolve(__dirname, './profiles/');
|
|
const electronExample = resolve(basePath, 'examples/electron');
|
|
const theia = resolve(electronExample, 'node_modules/.bin/theia');
|
|
|
|
let name = 'Electron Frontend Startup';
|
|
let folder = 'electron';
|
|
let runs = 10;
|
|
let workspace = resolve('./workspace');
|
|
let debugging = false;
|
|
|
|
(async () => {
|
|
let defaultWorkspace = true;
|
|
|
|
const yargs = require('yargs');
|
|
const args = yargs(process.argv.slice(2)).option('name', {
|
|
alias: 'n',
|
|
desc: 'A name for the test suite',
|
|
type: 'string',
|
|
default: name
|
|
}).option('folder', {
|
|
alias: 'f',
|
|
desc: 'Name of a folder within the "profiles" folder in which to collect trace logs',
|
|
type: 'string',
|
|
default: folder
|
|
}).option('runs', {
|
|
alias: 'r',
|
|
desc: 'The number of times to run the test',
|
|
type: 'number',
|
|
default: runs
|
|
}).option('workspace', {
|
|
alias: 'w',
|
|
desc: 'Path to a Theia workspace to open',
|
|
type: 'string',
|
|
default: workspace
|
|
}).option('debug', {
|
|
alias: 'X',
|
|
desc: 'Whether to log debug information',
|
|
boolean: true
|
|
}).wrap(Math.min(120, yargs.terminalWidth())).argv;
|
|
|
|
if (args.name) {
|
|
name = args.name.toString();
|
|
}
|
|
if (args.folder) {
|
|
folder = args.folder.toString();
|
|
}
|
|
if (args.workspace) {
|
|
workspace = args.workspace.toString();
|
|
if (resolve(workspace) !== workspace) {
|
|
console.log('Workspace path must be an absolute path:', workspace);
|
|
exit(1);
|
|
}
|
|
defaultWorkspace = false;
|
|
}
|
|
if (args.runs) {
|
|
runs = parseInt(args.runs.toString());
|
|
}
|
|
debugging = args.debug;
|
|
if (process.env.GITHUB_ACTIONS) {
|
|
githubReporting.enabled = true;
|
|
}
|
|
|
|
// Verify that the application exists
|
|
const indexHTML = resolve(electronExample, 'src-gen/frontend/index.html');
|
|
if (!fsx.existsSync(indexHTML)) {
|
|
console.error('Electron example app does not exist. Please build it before running this script.');
|
|
process.exit(1);
|
|
}
|
|
|
|
if (defaultWorkspace) {
|
|
// Ensure that it exists
|
|
fsx.ensureDirSync(workspace);
|
|
}
|
|
|
|
await measurePerformance();
|
|
})();
|
|
|
|
async function measurePerformance() {
|
|
fsx.emptyDirSync(resolve(profilesPath, folder));
|
|
const traceConfigPath = resolve(profilesPath, folder, 'trace-config.json');
|
|
|
|
/**
|
|
* Generate trace config from the template.
|
|
* @param {number} runNr
|
|
* @returns {string} the output trace file path
|
|
*/
|
|
const traceConfigGenerator = (runNr) => {
|
|
const traceConfig = { ...traceConfigTemplate };
|
|
const traceFilePath = resolve(profilesPath, folder, `${runNr}.json`);
|
|
traceConfig.result_file = traceFilePath
|
|
fsx.writeFileSync(traceConfigPath, JSON.stringify(traceConfig, undefined, 2), 'utf-8');
|
|
return traceFilePath;
|
|
};
|
|
|
|
const exitHandler = (andExit = false) => {
|
|
return () => {
|
|
if (electron && !electron.killed) {
|
|
process.kill(-electron.pid, 'SIGINT');
|
|
}
|
|
if (andExit) {
|
|
process.exit();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Be sure not to leave a detached Electron child process
|
|
process.on('exit', exitHandler());
|
|
process.on('SIGINT', exitHandler(true));
|
|
process.on('SIGTERM', exitHandler(true));
|
|
|
|
let electron;
|
|
|
|
/** @type import('./common-performance').TestFunction */
|
|
const testScenario = async (runNr) => {
|
|
const traceFile = traceConfigGenerator(runNr);
|
|
electron = await launchElectron(traceConfigPath);
|
|
|
|
electron.stderr.on('data', data => analyzeStderr(data.toString()));
|
|
|
|
// Wait long enough to be sure that tracing has finished. Kill the process group
|
|
// because the 'theia' child process was detached
|
|
await delay(traceConfigTemplate.startup_duration * 1_000 * 3 / 2)
|
|
.then(() => electron.exitCode !== null || process.kill(-electron.pid, 'SIGINT'));
|
|
electron = undefined;
|
|
return traceFile;
|
|
};
|
|
|
|
measure(name, lcp, runs, testScenario, hasNonzeroTimestamp, isLCP);
|
|
}
|
|
|
|
/**
|
|
* Launch the Electron app as a detached child process with tracing configured to start
|
|
* immediately upon launch. The child process is detached because otherwise the attempt
|
|
* to signal it to terminate when the test run is complete will not terminate the entire
|
|
* process tree but only the root `theia` process, leaving the electron app instance
|
|
* running until eventually this script itself exits.
|
|
*
|
|
* @param {string} traceConfigPath the path to the tracing configuration file with which to initiate tracing
|
|
* @returns {Promise<ChildProcess>} the Electron child process, if successfully launched
|
|
*/
|
|
async function launchElectron(traceConfigPath) {
|
|
const args = ['start', workspace, '--plugins=local-dir:../../plugins', `--trace-config-file=${traceConfigPath}`];
|
|
if (process.platform === 'linux') {
|
|
args.push('--headless');
|
|
}
|
|
return spawn(theia, args, { cwd: electronExample, detached: true });
|
|
}
|
|
|
|
function hasNonzeroTimestamp(traceEvent) {
|
|
return traceEvent.hasOwnProperty('ts') // The traces don't have explicit nulls or undefineds
|
|
&& traceEvent.ts > 0;
|
|
}
|
|
|
|
/**
|
|
* Analyze a `chunk` of text on the standard error stream of the child process.
|
|
* If running in debug mode, this will always at least print out the `chunk` to the console.
|
|
* In addition, the text is analyzed to look for known conditions that will invalidate the
|
|
* test procedure and cause the script to bail. These include:
|
|
*
|
|
* - the native browser modules not being built correctly for Electron
|
|
*
|
|
* @param {string} chunk a chunk of standard error text from the child process
|
|
*/
|
|
function analyzeStderr(chunk) {
|
|
if (debugging) {
|
|
console.error('>', chunk.trimEnd());
|
|
}
|
|
|
|
if (chunk.includes('Error: Module did not self-register')) {
|
|
console.error('Native browser modules are not built properly. Please rebuild the workspace and try again.');
|
|
exit(1);
|
|
}
|
|
}
|