1cb93a386Sopenharmony_ci/**
2cb93a386Sopenharmony_ci * Command line application to run Skottie-WASM perf on a Lottie file in the
3cb93a386Sopenharmony_ci * browser and then exporting the result.
4cb93a386Sopenharmony_ci *
5cb93a386Sopenharmony_ci */
6cb93a386Sopenharmony_ciconst puppeteer = require('puppeteer');
7cb93a386Sopenharmony_ciconst express = require('express');
8cb93a386Sopenharmony_ciconst fs = require('fs');
9cb93a386Sopenharmony_ciconst commandLineArgs = require('command-line-args');
10cb93a386Sopenharmony_ciconst commandLineUsage= require('command-line-usage');
11cb93a386Sopenharmony_ciconst fetch = require('node-fetch');
12cb93a386Sopenharmony_ci
13cb93a386Sopenharmony_ciconst opts = [
14cb93a386Sopenharmony_ci  {
15cb93a386Sopenharmony_ci    name: 'canvaskit_js',
16cb93a386Sopenharmony_ci    typeLabel: '{underline file}',
17cb93a386Sopenharmony_ci    description: 'The path to canvaskit.js.'
18cb93a386Sopenharmony_ci  },
19cb93a386Sopenharmony_ci  {
20cb93a386Sopenharmony_ci    name: 'canvaskit_wasm',
21cb93a386Sopenharmony_ci    typeLabel: '{underline file}',
22cb93a386Sopenharmony_ci    description: 'The path to canvaskit.wasm.'
23cb93a386Sopenharmony_ci  },
24cb93a386Sopenharmony_ci  {
25cb93a386Sopenharmony_ci    name: 'input',
26cb93a386Sopenharmony_ci    typeLabel: '{underline file}',
27cb93a386Sopenharmony_ci    description: 'The Lottie JSON file to process.'
28cb93a386Sopenharmony_ci  },
29cb93a386Sopenharmony_ci  {
30cb93a386Sopenharmony_ci    name: 'output',
31cb93a386Sopenharmony_ci    typeLabel: '{underline file}',
32cb93a386Sopenharmony_ci    description: 'The perf file to write. Defaults to perf.json',
33cb93a386Sopenharmony_ci  },
34cb93a386Sopenharmony_ci  {
35cb93a386Sopenharmony_ci    name: 'use_gpu',
36cb93a386Sopenharmony_ci    description: 'Whether we should run in non-headless mode with GPU.',
37cb93a386Sopenharmony_ci    type: Boolean,
38cb93a386Sopenharmony_ci  },
39cb93a386Sopenharmony_ci  {
40cb93a386Sopenharmony_ci    name: 'port',
41cb93a386Sopenharmony_ci    description: 'The port number to use, defaults to 8081.',
42cb93a386Sopenharmony_ci    type: Number,
43cb93a386Sopenharmony_ci  },
44cb93a386Sopenharmony_ci  {
45cb93a386Sopenharmony_ci    name: 'help',
46cb93a386Sopenharmony_ci    alias: 'h',
47cb93a386Sopenharmony_ci    type: Boolean,
48cb93a386Sopenharmony_ci    description: 'Print this usage guide.'
49cb93a386Sopenharmony_ci  },
50cb93a386Sopenharmony_ci];
51cb93a386Sopenharmony_ci
52cb93a386Sopenharmony_ciconst usage = [
53cb93a386Sopenharmony_ci  {
54cb93a386Sopenharmony_ci    header: 'Skottie WASM Perf',
55cb93a386Sopenharmony_ci    content: "Command line application to run Skottie-WASM perf."
56cb93a386Sopenharmony_ci  },
57cb93a386Sopenharmony_ci  {
58cb93a386Sopenharmony_ci    header: 'Options',
59cb93a386Sopenharmony_ci    optionList: opts,
60cb93a386Sopenharmony_ci  },
61cb93a386Sopenharmony_ci];
62cb93a386Sopenharmony_ci
63cb93a386Sopenharmony_ci// Parse and validate flags.
64cb93a386Sopenharmony_ciconst options = commandLineArgs(opts);
65cb93a386Sopenharmony_ci
66cb93a386Sopenharmony_ciif (!options.output) {
67cb93a386Sopenharmony_ci  options.output = 'perf.json';
68cb93a386Sopenharmony_ci}
69cb93a386Sopenharmony_ciif (!options.port) {
70cb93a386Sopenharmony_ci  options.port = 8081;
71cb93a386Sopenharmony_ci}
72cb93a386Sopenharmony_ci
73cb93a386Sopenharmony_ciif (options.help) {
74cb93a386Sopenharmony_ci  console.log(commandLineUsage(usage));
75cb93a386Sopenharmony_ci  process.exit(0);
76cb93a386Sopenharmony_ci}
77cb93a386Sopenharmony_ci
78cb93a386Sopenharmony_ciif (!options.canvaskit_js) {
79cb93a386Sopenharmony_ci  console.error('You must supply path to canvaskit.js.');
80cb93a386Sopenharmony_ci  console.log(commandLineUsage(usage));
81cb93a386Sopenharmony_ci  process.exit(1);
82cb93a386Sopenharmony_ci}
83cb93a386Sopenharmony_ci
84cb93a386Sopenharmony_ciif (!options.canvaskit_wasm) {
85cb93a386Sopenharmony_ci  console.error('You must supply path to canvaskit.wasm.');
86cb93a386Sopenharmony_ci  console.log(commandLineUsage(usage));
87cb93a386Sopenharmony_ci  process.exit(1);
88cb93a386Sopenharmony_ci}
89cb93a386Sopenharmony_ci
90cb93a386Sopenharmony_ciif (!options.input) {
91cb93a386Sopenharmony_ci  console.error('You must supply a Lottie JSON filename.');
92cb93a386Sopenharmony_ci  console.log(commandLineUsage(usage));
93cb93a386Sopenharmony_ci  process.exit(1);
94cb93a386Sopenharmony_ci}
95cb93a386Sopenharmony_ci
96cb93a386Sopenharmony_ci// Start up a web server to serve the three files we need.
97cb93a386Sopenharmony_cilet canvasKitJS = fs.readFileSync(options.canvaskit_js, 'utf8');
98cb93a386Sopenharmony_cilet canvasKitWASM = fs.readFileSync(options.canvaskit_wasm, 'binary');
99cb93a386Sopenharmony_cilet driverHTML = fs.readFileSync('skottie-wasm-perf.html', 'utf8');
100cb93a386Sopenharmony_cilet lottieJSON = fs.readFileSync(options.input, 'utf8');
101cb93a386Sopenharmony_ci
102cb93a386Sopenharmony_ciconst app = express();
103cb93a386Sopenharmony_ciapp.get('/', (req, res) => res.send(driverHTML));
104cb93a386Sopenharmony_ciapp.get('/res/canvaskit.wasm', function(req, res) {
105cb93a386Sopenharmony_ci  res.type('application/wasm');
106cb93a386Sopenharmony_ci  res.send(new Buffer(canvasKitWASM, 'binary'));
107cb93a386Sopenharmony_ci});
108cb93a386Sopenharmony_ciapp.get('/res/canvaskit.js', (req, res) => res.send(canvasKitJS));
109cb93a386Sopenharmony_ciapp.get('/res/lottie.json', (req, res) => res.send(lottieJSON));
110cb93a386Sopenharmony_ciapp.listen(options.port, () => console.log('- Local web server started.'))
111cb93a386Sopenharmony_ci
112cb93a386Sopenharmony_ci// Utility function.
113cb93a386Sopenharmony_ciasync function wait(ms) {
114cb93a386Sopenharmony_ci    await new Promise(resolve => setTimeout(() => resolve(), ms));
115cb93a386Sopenharmony_ci    return ms;
116cb93a386Sopenharmony_ci}
117cb93a386Sopenharmony_ci
118cb93a386Sopenharmony_cilet hash = "#cpu";
119cb93a386Sopenharmony_ciif (options.use_gpu) {
120cb93a386Sopenharmony_ci  hash = "#gpu";
121cb93a386Sopenharmony_ci}
122cb93a386Sopenharmony_ciconst targetURL = `http://localhost:${options.port}/${hash}`;
123cb93a386Sopenharmony_ciconst viewPort = {width: 1000, height: 1000};
124cb93a386Sopenharmony_ci
125cb93a386Sopenharmony_ci// Drive chrome to load the web page from the server we have running.
126cb93a386Sopenharmony_ciasync function driveBrowser() {
127cb93a386Sopenharmony_ci  console.log('- Launching chrome for ' + options.input);
128cb93a386Sopenharmony_ci  let browser;
129cb93a386Sopenharmony_ci  let page;
130cb93a386Sopenharmony_ci  const headless = !options.use_gpu;
131cb93a386Sopenharmony_ci  let browser_args = [
132cb93a386Sopenharmony_ci      '--no-sandbox',
133cb93a386Sopenharmony_ci      '--disable-setuid-sandbox',
134cb93a386Sopenharmony_ci      '--window-size=' + viewPort.width + ',' + viewPort.height,
135cb93a386Sopenharmony_ci  ];
136cb93a386Sopenharmony_ci  if (options.use_gpu) {
137cb93a386Sopenharmony_ci    browser_args.push('--ignore-gpu-blacklist');
138cb93a386Sopenharmony_ci    browser_args.push('--ignore-gpu-blocklist');
139cb93a386Sopenharmony_ci    browser_args.push('--enable-gpu-rasterization');
140cb93a386Sopenharmony_ci  }
141cb93a386Sopenharmony_ci  console.log("Running with headless: " + headless + " args: " + browser_args);
142cb93a386Sopenharmony_ci  try {
143cb93a386Sopenharmony_ci    browser = await puppeteer.launch({headless: headless, args: browser_args});
144cb93a386Sopenharmony_ci    page = await browser.newPage();
145cb93a386Sopenharmony_ci    await page.setViewport(viewPort);
146cb93a386Sopenharmony_ci  } catch (e) {
147cb93a386Sopenharmony_ci    console.log('Could not open the browser.', e);
148cb93a386Sopenharmony_ci    process.exit(1);
149cb93a386Sopenharmony_ci  }
150cb93a386Sopenharmony_ci  console.log("Loading " + targetURL);
151cb93a386Sopenharmony_ci  try {
152cb93a386Sopenharmony_ci    // Start trace.
153cb93a386Sopenharmony_ci    await page.tracing.start({
154cb93a386Sopenharmony_ci      path: options.output,
155cb93a386Sopenharmony_ci      screenshots: false,
156cb93a386Sopenharmony_ci      categories: ["blink", "cc", "gpu"]
157cb93a386Sopenharmony_ci    });
158cb93a386Sopenharmony_ci
159cb93a386Sopenharmony_ci    await page.goto(targetURL, {
160cb93a386Sopenharmony_ci      timeout: 60000,
161cb93a386Sopenharmony_ci      waitUntil: 'networkidle0'
162cb93a386Sopenharmony_ci    });
163cb93a386Sopenharmony_ci
164cb93a386Sopenharmony_ci    console.log('Waiting 90s for run to be done');
165cb93a386Sopenharmony_ci    await page.waitForFunction(`(window._skottieDone === true) || window._error`, {
166cb93a386Sopenharmony_ci      timeout: 90000,
167cb93a386Sopenharmony_ci    });
168cb93a386Sopenharmony_ci
169cb93a386Sopenharmony_ci    const err = await page.evaluate('window._error');
170cb93a386Sopenharmony_ci    if (err) {
171cb93a386Sopenharmony_ci      console.log(`ERROR: ${err}`)
172cb93a386Sopenharmony_ci      process.exit(1);
173cb93a386Sopenharmony_ci    }
174cb93a386Sopenharmony_ci
175cb93a386Sopenharmony_ci    // Stop Trace.
176cb93a386Sopenharmony_ci    await page.tracing.stop();
177cb93a386Sopenharmony_ci  } catch(e) {
178cb93a386Sopenharmony_ci    console.log('Timed out while loading or drawing. Either the JSON file was ' +
179cb93a386Sopenharmony_ci                'too big or hit a bug.', e);
180cb93a386Sopenharmony_ci    await browser.close();
181cb93a386Sopenharmony_ci    process.exit(1);
182cb93a386Sopenharmony_ci  }
183cb93a386Sopenharmony_ci
184cb93a386Sopenharmony_ci  await browser.close();
185cb93a386Sopenharmony_ci  // Need to call exit() because the web server is still running.
186cb93a386Sopenharmony_ci  process.exit(0);
187cb93a386Sopenharmony_ci}
188cb93a386Sopenharmony_ci
189cb93a386Sopenharmony_cidriveBrowser();
190