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