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