xref: /aosp_15_r20/external/skia/tools/perf-canvaskit-puppeteer/perf-canvaskit-with-puppeteer.js (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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