1*c8dee2aaSAndroid Build Coastguard Worker/** 2*c8dee2aaSAndroid Build Coastguard Worker * Command line application to run CanvasKit benchmarks in webpages using puppeteer. Different 3*c8dee2aaSAndroid Build Coastguard Worker * webpages can be specified to measure different aspects. The HTML page run contains the JS code 4*c8dee2aaSAndroid Build Coastguard Worker * to run the scenario and either 1) produce the perf output as a JSON object or 2) defer to 5*c8dee2aaSAndroid Build Coastguard Worker * puppeteer reading the tracing data. 6*c8dee2aaSAndroid Build Coastguard Worker * 7*c8dee2aaSAndroid Build Coastguard Worker */ 8*c8dee2aaSAndroid Build Coastguard Workerconst puppeteer = require('puppeteer'); 9*c8dee2aaSAndroid Build Coastguard Workerconst express = require('express'); 10*c8dee2aaSAndroid Build Coastguard Workerconst fs = require('fs'); 11*c8dee2aaSAndroid Build Coastguard Workerconst commandLineArgs = require('command-line-args'); 12*c8dee2aaSAndroid Build Coastguard Workerconst commandLineUsage= require('command-line-usage'); 13*c8dee2aaSAndroid Build Coastguard Worker 14*c8dee2aaSAndroid Build Coastguard Workerconst opts = [ 15*c8dee2aaSAndroid Build Coastguard Worker { 16*c8dee2aaSAndroid Build Coastguard Worker name: 'bench_html', 17*c8dee2aaSAndroid Build Coastguard Worker typeLabel: '{underline file}', 18*c8dee2aaSAndroid Build Coastguard Worker description: 'An HTML file containing the bench harness.' 19*c8dee2aaSAndroid Build Coastguard Worker }, 20*c8dee2aaSAndroid Build Coastguard Worker { 21*c8dee2aaSAndroid Build Coastguard Worker name: 'canvaskit_js', 22*c8dee2aaSAndroid Build Coastguard Worker typeLabel: '{underline file}', 23*c8dee2aaSAndroid Build Coastguard Worker description: '(required) The path to canvaskit.js.' 24*c8dee2aaSAndroid Build Coastguard Worker }, 25*c8dee2aaSAndroid Build Coastguard Worker { 26*c8dee2aaSAndroid Build Coastguard Worker name: 'canvaskit_wasm', 27*c8dee2aaSAndroid Build Coastguard Worker typeLabel: '{underline file}', 28*c8dee2aaSAndroid Build Coastguard Worker description: '(required) The path to canvaskit.wasm.' 29*c8dee2aaSAndroid Build Coastguard Worker }, 30*c8dee2aaSAndroid Build Coastguard Worker { 31*c8dee2aaSAndroid Build Coastguard Worker name: 'input_lottie', 32*c8dee2aaSAndroid Build Coastguard Worker typeLabel: '{underline file}', 33*c8dee2aaSAndroid Build Coastguard Worker description: 'The Lottie JSON file to process.' 34*c8dee2aaSAndroid Build Coastguard Worker }, 35*c8dee2aaSAndroid Build Coastguard Worker { 36*c8dee2aaSAndroid Build Coastguard Worker name: 'input_skp', 37*c8dee2aaSAndroid Build Coastguard Worker typeLabel: '{underline file}', 38*c8dee2aaSAndroid Build Coastguard Worker description: 'The SKP file to process.' 39*c8dee2aaSAndroid Build Coastguard Worker }, 40*c8dee2aaSAndroid Build Coastguard Worker { 41*c8dee2aaSAndroid Build Coastguard Worker name: 'assets', 42*c8dee2aaSAndroid Build Coastguard Worker typeLabel: '{underline file}', 43*c8dee2aaSAndroid Build Coastguard Worker description: 'A directory containing any assets needed by lottie files or tests (e.g. images/fonts).' 44*c8dee2aaSAndroid Build Coastguard Worker }, 45*c8dee2aaSAndroid Build Coastguard Worker { 46*c8dee2aaSAndroid Build Coastguard Worker name: 'output', 47*c8dee2aaSAndroid Build Coastguard Worker typeLabel: '{underline file}', 48*c8dee2aaSAndroid Build Coastguard Worker description: 'The perf file to write. Defaults to perf.json', 49*c8dee2aaSAndroid Build Coastguard Worker }, 50*c8dee2aaSAndroid Build Coastguard Worker { 51*c8dee2aaSAndroid Build Coastguard Worker name: 'chromium_executable_path', 52*c8dee2aaSAndroid Build Coastguard Worker typeLabel: '{underline file}', 53*c8dee2aaSAndroid Build Coastguard Worker description: 'The chromium executable to be used by puppeteer to run tests', 54*c8dee2aaSAndroid Build Coastguard Worker }, 55*c8dee2aaSAndroid Build Coastguard Worker { 56*c8dee2aaSAndroid Build Coastguard Worker name: 'merge_output_as', 57*c8dee2aaSAndroid Build Coastguard Worker typeLabel: String, 58*c8dee2aaSAndroid Build Coastguard Worker description: 'Overwrites a json property in an existing output file.', 59*c8dee2aaSAndroid Build Coastguard Worker }, 60*c8dee2aaSAndroid Build Coastguard Worker { 61*c8dee2aaSAndroid Build Coastguard Worker name: 'use_gpu', 62*c8dee2aaSAndroid Build Coastguard Worker description: 'Whether we should run in non-headless mode with GPU.', 63*c8dee2aaSAndroid Build Coastguard Worker type: Boolean, 64*c8dee2aaSAndroid Build Coastguard Worker }, 65*c8dee2aaSAndroid Build Coastguard Worker { 66*c8dee2aaSAndroid Build Coastguard Worker name: 'use_tracing', 67*c8dee2aaSAndroid Build Coastguard Worker description: 'If non-empty, will be interpreted as the tracing categories that should be ' + 68*c8dee2aaSAndroid Build Coastguard Worker 'measured and returned in the output JSON. Example: "blink,cc,gpu"', 69*c8dee2aaSAndroid Build Coastguard Worker type: String, 70*c8dee2aaSAndroid Build Coastguard Worker }, 71*c8dee2aaSAndroid Build Coastguard Worker { 72*c8dee2aaSAndroid Build Coastguard Worker name: 'enable_simd', 73*c8dee2aaSAndroid Build Coastguard Worker description: 'enable execution of wasm SIMD operations in chromium', 74*c8dee2aaSAndroid Build Coastguard Worker type: Boolean 75*c8dee2aaSAndroid Build Coastguard Worker }, 76*c8dee2aaSAndroid Build Coastguard Worker { 77*c8dee2aaSAndroid Build Coastguard Worker name: 'port', 78*c8dee2aaSAndroid Build Coastguard Worker description: 'The port number to use, defaults to 8081.', 79*c8dee2aaSAndroid Build Coastguard Worker type: Number, 80*c8dee2aaSAndroid Build Coastguard Worker }, 81*c8dee2aaSAndroid Build Coastguard Worker { 82*c8dee2aaSAndroid Build Coastguard Worker name: 'query_params', 83*c8dee2aaSAndroid Build Coastguard Worker description: 'The query params to be added to the testing page URL. Useful for passing' + 84*c8dee2aaSAndroid Build Coastguard Worker 'options to the perf html page.', 85*c8dee2aaSAndroid Build Coastguard Worker type: String, 86*c8dee2aaSAndroid Build Coastguard Worker multiple: true 87*c8dee2aaSAndroid Build Coastguard Worker }, 88*c8dee2aaSAndroid Build Coastguard Worker { 89*c8dee2aaSAndroid Build Coastguard Worker name: 'help', 90*c8dee2aaSAndroid Build Coastguard Worker alias: 'h', 91*c8dee2aaSAndroid Build Coastguard Worker type: Boolean, 92*c8dee2aaSAndroid Build Coastguard Worker description: 'Print this usage guide.' 93*c8dee2aaSAndroid Build Coastguard Worker }, 94*c8dee2aaSAndroid Build Coastguard Worker { 95*c8dee2aaSAndroid Build Coastguard Worker name: 'timeout', 96*c8dee2aaSAndroid Build Coastguard Worker description: 'Number of seconds to allow test to run.', 97*c8dee2aaSAndroid Build Coastguard Worker type: Number, 98*c8dee2aaSAndroid Build Coastguard Worker }, 99*c8dee2aaSAndroid Build Coastguard Worker]; 100*c8dee2aaSAndroid Build Coastguard Worker 101*c8dee2aaSAndroid Build Coastguard Workerconst usage = [ 102*c8dee2aaSAndroid Build Coastguard Worker { 103*c8dee2aaSAndroid Build Coastguard Worker header: 'Skia Web-based Performance Metrics of CanvasKit', 104*c8dee2aaSAndroid Build Coastguard Worker content: "Command line application to capture performance metrics from a browser." 105*c8dee2aaSAndroid Build Coastguard Worker }, 106*c8dee2aaSAndroid Build Coastguard Worker { 107*c8dee2aaSAndroid Build Coastguard Worker header: 'Options', 108*c8dee2aaSAndroid Build Coastguard Worker optionList: opts, 109*c8dee2aaSAndroid Build Coastguard Worker }, 110*c8dee2aaSAndroid Build Coastguard Worker]; 111*c8dee2aaSAndroid Build Coastguard Worker 112*c8dee2aaSAndroid Build Coastguard Worker// Parse and validate flags. 113*c8dee2aaSAndroid Build Coastguard Workerconst options = commandLineArgs(opts); 114*c8dee2aaSAndroid Build Coastguard Worker 115*c8dee2aaSAndroid Build Coastguard Workerif (!options.output) { 116*c8dee2aaSAndroid Build Coastguard Worker options.output = 'perf.json'; 117*c8dee2aaSAndroid Build Coastguard Worker} 118*c8dee2aaSAndroid Build Coastguard Workerif (!options.port) { 119*c8dee2aaSAndroid Build Coastguard Worker options.port = 8081; 120*c8dee2aaSAndroid Build Coastguard Worker} 121*c8dee2aaSAndroid Build Coastguard Workerif (!options.timeout) { 122*c8dee2aaSAndroid Build Coastguard Worker options.timeout = 60; 123*c8dee2aaSAndroid Build Coastguard Worker} 124*c8dee2aaSAndroid Build Coastguard Worker 125*c8dee2aaSAndroid Build Coastguard Workerif (options.help) { 126*c8dee2aaSAndroid Build Coastguard Worker console.log(commandLineUsage(usage)); 127*c8dee2aaSAndroid Build Coastguard Worker process.exit(0); 128*c8dee2aaSAndroid Build Coastguard Worker} 129*c8dee2aaSAndroid Build Coastguard Worker 130*c8dee2aaSAndroid Build Coastguard Workerif (!options.bench_html) { 131*c8dee2aaSAndroid Build Coastguard Worker console.error('You must supply the bench_html file to run.'); 132*c8dee2aaSAndroid Build Coastguard Worker console.log(commandLineUsage(usage)); 133*c8dee2aaSAndroid Build Coastguard Worker process.exit(1); 134*c8dee2aaSAndroid Build Coastguard Worker} 135*c8dee2aaSAndroid Build Coastguard Workerconst driverHTML = fs.readFileSync(options.bench_html, 'utf8'); 136*c8dee2aaSAndroid Build Coastguard Worker 137*c8dee2aaSAndroid Build Coastguard Worker// This express webserver will serve the HTML file running the benchmark and any additional assets 138*c8dee2aaSAndroid Build Coastguard Worker// needed to run the tests. 139*c8dee2aaSAndroid Build Coastguard Workerconst app = express(); 140*c8dee2aaSAndroid Build Coastguard Workerapp.get('/', (req, res) => res.send(driverHTML)); 141*c8dee2aaSAndroid Build Coastguard Worker 142*c8dee2aaSAndroid Build Coastguard Workerif (!options.canvaskit_js) { 143*c8dee2aaSAndroid Build Coastguard Worker console.error('You must supply path to canvaskit.js.'); 144*c8dee2aaSAndroid Build Coastguard Worker console.log(commandLineUsage(usage)); 145*c8dee2aaSAndroid Build Coastguard Worker process.exit(1); 146*c8dee2aaSAndroid Build Coastguard Worker} 147*c8dee2aaSAndroid Build Coastguard Worker 148*c8dee2aaSAndroid Build Coastguard Workerif (!options.canvaskit_wasm) { 149*c8dee2aaSAndroid Build Coastguard Worker console.error('You must supply path to canvaskit.wasm.'); 150*c8dee2aaSAndroid Build Coastguard Worker console.log(commandLineUsage(usage)); 151*c8dee2aaSAndroid Build Coastguard Worker process.exit(1); 152*c8dee2aaSAndroid Build Coastguard Worker} 153*c8dee2aaSAndroid Build Coastguard Worker 154*c8dee2aaSAndroid Build Coastguard Workerconst benchmarkJS = fs.readFileSync('benchmark.js', 'utf8'); 155*c8dee2aaSAndroid Build Coastguard Workerconst canvasPerfJS = fs.readFileSync('canvas_perf.js', 'utf8'); 156*c8dee2aaSAndroid Build Coastguard Workerconst canvasKitJS = fs.readFileSync(options.canvaskit_js, 'utf8'); 157*c8dee2aaSAndroid Build Coastguard Workerconst canvasKitWASM = fs.readFileSync(options.canvaskit_wasm, 'binary'); 158*c8dee2aaSAndroid Build Coastguard Worker 159*c8dee2aaSAndroid Build Coastguard Workerapp.get('/static/benchmark.js', (req, res) => res.send(benchmarkJS)); 160*c8dee2aaSAndroid Build Coastguard Workerapp.get('/static/canvas_perf.js', (req, res) => res.send(canvasPerfJS)); 161*c8dee2aaSAndroid Build Coastguard Workerapp.get('/static/canvaskit.js', (req, res) => res.send(canvasKitJS)); 162*c8dee2aaSAndroid Build Coastguard Workerapp.get('/static/canvaskit.wasm', function(req, res) { 163*c8dee2aaSAndroid Build Coastguard Worker // Set the MIME type so it can be streamed efficiently. 164*c8dee2aaSAndroid Build Coastguard Worker res.type('application/wasm'); 165*c8dee2aaSAndroid Build Coastguard Worker res.send(new Buffer(canvasKitWASM, 'binary')); 166*c8dee2aaSAndroid Build Coastguard Worker}); 167*c8dee2aaSAndroid Build Coastguard Worker 168*c8dee2aaSAndroid Build Coastguard Worker 169*c8dee2aaSAndroid Build Coastguard Workerif (options.input_lottie) { 170*c8dee2aaSAndroid Build Coastguard Worker const lottieJSON = fs.readFileSync(options.input_lottie, 'utf8'); 171*c8dee2aaSAndroid Build Coastguard Worker app.get('/static/lottie.json', (req, res) => res.send(lottieJSON)); 172*c8dee2aaSAndroid Build Coastguard Worker} 173*c8dee2aaSAndroid Build Coastguard Workerif (options.input_skp) { 174*c8dee2aaSAndroid Build Coastguard Worker const skpBytes = fs.readFileSync(options.input_skp, 'binary'); 175*c8dee2aaSAndroid Build Coastguard Worker app.get('/static/test.skp', (req, res) => { 176*c8dee2aaSAndroid Build Coastguard Worker res.send(new Buffer(skpBytes, 'binary')); 177*c8dee2aaSAndroid Build Coastguard Worker }); 178*c8dee2aaSAndroid Build Coastguard Worker} 179*c8dee2aaSAndroid Build Coastguard Workerif (options.assets) { 180*c8dee2aaSAndroid Build Coastguard Worker app.use('/static/assets/', express.static(options.assets)); 181*c8dee2aaSAndroid Build Coastguard Worker console.log('assets served from', options.assets); 182*c8dee2aaSAndroid Build Coastguard Worker} 183*c8dee2aaSAndroid Build Coastguard Worker 184*c8dee2aaSAndroid Build Coastguard Workerapp.listen(options.port, () => console.log('- Local web server started.')); 185*c8dee2aaSAndroid Build Coastguard Worker 186*c8dee2aaSAndroid Build Coastguard Workerlet hash = "#cpu"; 187*c8dee2aaSAndroid Build Coastguard Workerif (options.use_gpu) { 188*c8dee2aaSAndroid Build Coastguard Worker hash = "#gpu"; 189*c8dee2aaSAndroid Build Coastguard Worker} 190*c8dee2aaSAndroid Build Coastguard Workerlet query_param_string = '?'; 191*c8dee2aaSAndroid Build Coastguard Workerif (options.query_params) { 192*c8dee2aaSAndroid Build Coastguard Worker for (const string of options.query_params) { 193*c8dee2aaSAndroid Build Coastguard Worker query_param_string += string + '&'; 194*c8dee2aaSAndroid Build Coastguard Worker } 195*c8dee2aaSAndroid Build Coastguard Worker} 196*c8dee2aaSAndroid Build Coastguard Workerconst targetURL = `http://localhost:${options.port}/${query_param_string}${hash}`; 197*c8dee2aaSAndroid Build Coastguard Workerconst viewPort = {width: 1000, height: 1000}; 198*c8dee2aaSAndroid Build Coastguard Worker 199*c8dee2aaSAndroid Build Coastguard Worker// Drive chrome to load the web page from the server we have running. 200*c8dee2aaSAndroid Build Coastguard Workerasync function driveBrowser() { 201*c8dee2aaSAndroid Build Coastguard Worker console.log('- Launching chrome for ' + options.input); 202*c8dee2aaSAndroid Build Coastguard Worker let browser; 203*c8dee2aaSAndroid Build Coastguard Worker let page; 204*c8dee2aaSAndroid Build Coastguard Worker const headless = !options.use_gpu; 205*c8dee2aaSAndroid Build Coastguard Worker let browser_args = [ 206*c8dee2aaSAndroid Build Coastguard Worker '--no-sandbox', 207*c8dee2aaSAndroid Build Coastguard Worker '--disable-setuid-sandbox', 208*c8dee2aaSAndroid Build Coastguard Worker '--window-size=' + viewPort.width + ',' + viewPort.height, 209*c8dee2aaSAndroid Build Coastguard Worker // The following two params allow Chrome to run at an unlimited fps. Note, if there is 210*c8dee2aaSAndroid Build Coastguard Worker // already a chrome instance running, these arguments will have NO EFFECT, as the existing 211*c8dee2aaSAndroid Build Coastguard Worker // Chrome instance will be used instead of puppeteer spinning up a new one. 212*c8dee2aaSAndroid Build Coastguard Worker '--disable-frame-rate-limit', 213*c8dee2aaSAndroid Build Coastguard Worker '--disable-gpu-vsync', 214*c8dee2aaSAndroid Build Coastguard Worker ]; 215*c8dee2aaSAndroid Build Coastguard Worker if (options.enable_simd) { 216*c8dee2aaSAndroid Build Coastguard Worker browser_args.push('--enable-features=WebAssemblySimd'); 217*c8dee2aaSAndroid Build Coastguard Worker } 218*c8dee2aaSAndroid Build Coastguard Worker if (options.use_gpu) { 219*c8dee2aaSAndroid Build Coastguard Worker browser_args.push('--ignore-gpu-blacklist'); 220*c8dee2aaSAndroid Build Coastguard Worker browser_args.push('--ignore-gpu-blocklist'); 221*c8dee2aaSAndroid Build Coastguard Worker browser_args.push('--enable-gpu-rasterization'); 222*c8dee2aaSAndroid Build Coastguard Worker } 223*c8dee2aaSAndroid Build Coastguard Worker console.log("Running with headless: " + headless + " args: " + browser_args); 224*c8dee2aaSAndroid Build Coastguard Worker try { 225*c8dee2aaSAndroid Build Coastguard Worker browser = await puppeteer.launch({ 226*c8dee2aaSAndroid Build Coastguard Worker headless: headless, 227*c8dee2aaSAndroid Build Coastguard Worker args: browser_args, 228*c8dee2aaSAndroid Build Coastguard Worker executablePath: options.chromium_executable_path 229*c8dee2aaSAndroid Build Coastguard Worker }); 230*c8dee2aaSAndroid Build Coastguard Worker page = await browser.newPage(); 231*c8dee2aaSAndroid Build Coastguard Worker await page.setViewport(viewPort); 232*c8dee2aaSAndroid Build Coastguard Worker } catch (e) { 233*c8dee2aaSAndroid Build Coastguard Worker console.log('Could not open the browser.', e); 234*c8dee2aaSAndroid Build Coastguard Worker process.exit(1); 235*c8dee2aaSAndroid Build Coastguard Worker } 236*c8dee2aaSAndroid Build Coastguard Worker console.log("Loading " + targetURL); 237*c8dee2aaSAndroid Build Coastguard Worker try { 238*c8dee2aaSAndroid Build Coastguard Worker await page.goto(targetURL, { 239*c8dee2aaSAndroid Build Coastguard Worker timeout: 60000, 240*c8dee2aaSAndroid Build Coastguard Worker waitUntil: 'networkidle0' 241*c8dee2aaSAndroid Build Coastguard Worker }); 242*c8dee2aaSAndroid Build Coastguard Worker 243*c8dee2aaSAndroid Build Coastguard Worker // Page is mostly loaded, wait for benchmark page to report itself ready. 244*c8dee2aaSAndroid Build Coastguard Worker console.log('Waiting 15s for benchmark to be ready'); 245*c8dee2aaSAndroid Build Coastguard Worker await page.waitForFunction(`(window._perfReady === true) || window._error`, { 246*c8dee2aaSAndroid Build Coastguard Worker timeout: 15000, 247*c8dee2aaSAndroid Build Coastguard Worker }); 248*c8dee2aaSAndroid Build Coastguard Worker 249*c8dee2aaSAndroid Build Coastguard Worker let err = await page.evaluate('window._error'); 250*c8dee2aaSAndroid Build Coastguard Worker if (err) { 251*c8dee2aaSAndroid Build Coastguard Worker console.log(`ERROR: ${err}`); 252*c8dee2aaSAndroid Build Coastguard Worker process.exit(1); 253*c8dee2aaSAndroid Build Coastguard Worker } 254*c8dee2aaSAndroid Build Coastguard Worker 255*c8dee2aaSAndroid Build Coastguard Worker // Start trace if requested 256*c8dee2aaSAndroid Build Coastguard Worker if (options.use_tracing) { 257*c8dee2aaSAndroid Build Coastguard Worker const categories = options.use_tracing.split(','); 258*c8dee2aaSAndroid Build Coastguard Worker console.log('Collecting tracing data for categories', categories); 259*c8dee2aaSAndroid Build Coastguard Worker await page.tracing.start({ 260*c8dee2aaSAndroid Build Coastguard Worker path: options.output, 261*c8dee2aaSAndroid Build Coastguard Worker screenshots: false, 262*c8dee2aaSAndroid Build Coastguard Worker categories: categories, 263*c8dee2aaSAndroid Build Coastguard Worker }); 264*c8dee2aaSAndroid Build Coastguard Worker } 265*c8dee2aaSAndroid Build Coastguard Worker 266*c8dee2aaSAndroid Build Coastguard Worker // Benchmarks should have a button with id #start_bench to click (this also makes manual 267*c8dee2aaSAndroid Build Coastguard Worker // debugging easier). 268*c8dee2aaSAndroid Build Coastguard Worker await page.click('#start_bench'); 269*c8dee2aaSAndroid Build Coastguard Worker 270*c8dee2aaSAndroid Build Coastguard Worker console.log(`Waiting ${options.timeout}s for run to be done`); 271*c8dee2aaSAndroid Build Coastguard Worker await page.waitForFunction(`(window._perfDone === true) || window._error`, { 272*c8dee2aaSAndroid Build Coastguard Worker timeout: options.timeout*1000, 273*c8dee2aaSAndroid Build Coastguard Worker }); 274*c8dee2aaSAndroid Build Coastguard Worker 275*c8dee2aaSAndroid Build Coastguard Worker err = await page.evaluate('window._error'); 276*c8dee2aaSAndroid Build Coastguard Worker if (err) { 277*c8dee2aaSAndroid Build Coastguard Worker console.log(`ERROR: ${err}`); 278*c8dee2aaSAndroid Build Coastguard Worker process.exit(1); 279*c8dee2aaSAndroid Build Coastguard Worker } 280*c8dee2aaSAndroid Build Coastguard Worker 281*c8dee2aaSAndroid Build Coastguard Worker if (options.use_tracing) { 282*c8dee2aaSAndroid Build Coastguard Worker // Stop Trace. 283*c8dee2aaSAndroid Build Coastguard Worker await page.tracing.stop(); 284*c8dee2aaSAndroid Build Coastguard Worker } else { 285*c8dee2aaSAndroid Build Coastguard Worker const perfResults = await page.evaluate('window._perfData'); 286*c8dee2aaSAndroid Build Coastguard Worker console.debug('Perf results: ', perfResults); 287*c8dee2aaSAndroid Build Coastguard Worker 288*c8dee2aaSAndroid Build Coastguard Worker if (options.merge_output_as) { 289*c8dee2aaSAndroid Build Coastguard Worker const existing_output_file_contents = fs.readFileSync(options.output, 'utf8'); 290*c8dee2aaSAndroid Build Coastguard Worker let existing_dataset = {}; 291*c8dee2aaSAndroid Build Coastguard Worker try { 292*c8dee2aaSAndroid Build Coastguard Worker existing_dataset = JSON.parse(existing_output_file_contents); 293*c8dee2aaSAndroid Build Coastguard Worker } catch (e) {} 294*c8dee2aaSAndroid Build Coastguard Worker 295*c8dee2aaSAndroid Build Coastguard Worker existing_dataset[options.merge_output_as] = perfResults; 296*c8dee2aaSAndroid Build Coastguard Worker fs.writeFileSync(options.output, JSON.stringify(existing_dataset)); 297*c8dee2aaSAndroid Build Coastguard Worker } else { 298*c8dee2aaSAndroid Build Coastguard Worker fs.writeFileSync(options.output, JSON.stringify(perfResults)); 299*c8dee2aaSAndroid Build Coastguard Worker } 300*c8dee2aaSAndroid Build Coastguard Worker } 301*c8dee2aaSAndroid Build Coastguard Worker 302*c8dee2aaSAndroid Build Coastguard Worker } catch(e) { 303*c8dee2aaSAndroid Build Coastguard Worker console.log('Timed out while loading or drawing.', e); 304*c8dee2aaSAndroid Build Coastguard Worker await browser.close(); 305*c8dee2aaSAndroid Build Coastguard Worker process.exit(1); 306*c8dee2aaSAndroid Build Coastguard Worker } 307*c8dee2aaSAndroid Build Coastguard Worker 308*c8dee2aaSAndroid Build Coastguard Worker await browser.close(); 309*c8dee2aaSAndroid Build Coastguard Worker // Need to call exit() because the web server is still running. 310*c8dee2aaSAndroid Build Coastguard Worker process.exit(0); 311*c8dee2aaSAndroid Build Coastguard Worker} 312*c8dee2aaSAndroid Build Coastguard Worker 313*c8dee2aaSAndroid Build Coastguard WorkerdriveBrowser(); 314