diff --git a/src/test/cli.ts b/src/test/cli.ts index 87f0ff5ed4..b4cfdae229 100644 --- a/src/test/cli.ts +++ b/src/test/cli.ts @@ -21,6 +21,7 @@ import * as fs from 'fs'; import * as path from 'path'; import type { Config } from './types'; import { Runner } from './runner'; +import { stopProfiling, startProfiling } from './profiler'; const defaultTimeout = 30000; const defaultReporter = process.env.CI ? 'dot' : 'list'; @@ -80,6 +81,8 @@ export function addTestCommand(program: commander.CommanderStatic) { } async function runTests(args: string[], opts: { [key: string]: any }) { + await startProfiling(); + const browserOpt = opts.browser ? opts.browser.toLowerCase() : 'chromium'; if (!['all', 'chromium', 'firefox', 'webkit'].includes(browserOpt)) throw new Error(`Unsupported browser "${opts.browser}", must be one of "all", "chromium", "firefox" or "webkit"`); @@ -129,6 +132,8 @@ async function runTests(args: string[], opts: { [key: string]: any }) { } const result = await runner.run(!!opts.list, args.map(forceRegExp), opts.project || undefined); + await stopProfiling(undefined); + if (result === 'sigint') process.exit(130); process.exit(result === 'passed' ? 0 : 1); diff --git a/src/test/profiler.ts b/src/test/profiler.ts new file mode 100644 index 0000000000..f7daa91fca --- /dev/null +++ b/src/test/profiler.ts @@ -0,0 +1,48 @@ +/** + * Copyright Microsoft Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +const profileDir = process.env.PWTEST_PROFILE_DIR || ''; + +let session: import('inspector').Session; + +export async function startProfiling() { + if (!profileDir) + return; + + session = new (require('inspector').Session)(); + session.connect(); + await new Promise(f => { + session.post('Profiler.enable', () => { + session.post('Profiler.start', f); + }); + }); +} + +export async function stopProfiling(workerIndex: number | undefined) { + if (!profileDir) + return; + + await new Promise(f => session.post('Profiler.stop', async (err, { profile }) => { + if (!err) { + await fs.promises.mkdir(profileDir, { recursive: true }); + await fs.promises.writeFile(path.join(profileDir, workerIndex === undefined ? 'runner.json' : 'worker' + workerIndex + '.json'), JSON.stringify(profile)); + } + f(); + })); +} diff --git a/src/test/worker.ts b/src/test/worker.ts index 6cd4d91d1b..a5b8d1db02 100644 --- a/src/test/worker.ts +++ b/src/test/worker.ts @@ -17,6 +17,7 @@ import { Console } from 'console'; import * as util from 'util'; import { RunPayload, TestOutputPayload, WorkerInitParams } from './ipc'; +import { startProfiling, stopProfiling } from './profiler'; import { serializeError } from './util'; import { WorkerRunner } from './workerRunner'; @@ -55,6 +56,7 @@ process.on('SIGINT',() => {}); process.on('SIGTERM',() => {}); let workerRunner: WorkerRunner; +let workerIndex: number | undefined; process.on('unhandledRejection', (reason, promise) => { if (workerRunner) @@ -69,6 +71,8 @@ process.on('uncaughtException', error => { process.on('message', async message => { if (message.method === 'init') { const initParams = message.params as WorkerInitParams; + workerIndex = initParams.workerIndex; + startProfiling(); workerRunner = new WorkerRunner(initParams); for (const event of ['testBegin', 'testEnd', 'done']) workerRunner.on(event, sendMessageToParent.bind(null, event)); @@ -96,6 +100,8 @@ async function gracefullyCloseAndExit() { workerRunner.stop(); await workerRunner.cleanup(); } + if (workerIndex !== undefined) + await stopProfiling(workerIndex); } catch (e) { process.send!({ method: 'teardownError', params: { error: serializeError(e) } }); }