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