1'use strict'; 2 3const { join } = require('path'); 4const { readFileSync, createReadStream, readdirSync } = require('fs'); 5const Benchmark = require('benchmark'); 6const { loadTreeConstructionTestData } = require('../../test/utils/generate-parsing-tests'); 7const loadSAXParserTestData = require('../../test/utils/load-sax-parser-test-data'); 8const { treeAdapters, WritableStreamStub } = require('../../test/utils/common'); 9 10//HACK: https://github.com/bestiejs/benchmark.js/issues/51 11/* global workingCopy, WorkingCopyParserStream, upstreamParser, hugePage, microTests, runMicro, runPages, files */ 12global.workingCopy = require('../../packages/parse5/lib'); 13global.WorkingCopyParserStream = require('../../packages/parse5-parser-stream/lib'); 14global.upstreamParser = require('parse5'); 15 16// Huge page data 17global.hugePage = readFileSync(join(__dirname, '../../test/data/huge-page/huge-page.html')).toString(); 18 19// Micro data 20global.microTests = loadTreeConstructionTestData( 21 [join(__dirname, '../../test/data/html5lib-tests/tree-construction')], 22 treeAdapters.default 23) 24 .filter( 25 test => 26 //NOTE: this test caused stack overflow in parse5 v1.x 27 test.input !== '<button><p><button>' 28 ) 29 .map(test => ({ 30 html: test.input, 31 fragmentContext: test.fragmentContext 32 })); 33 34global.runMicro = function(parser) { 35 for (const test of microTests) { 36 if (test.fragmentContext) { 37 parser.parseFragment(test.fragmentContext, test.html); 38 } else { 39 parser.parse(test.html); 40 } 41 } 42}; 43 44// Pages data 45const pages = loadSAXParserTestData().map(test => test.src); 46 47global.runPages = function(parser) { 48 for (let j = 0; j < pages.length; j++) { 49 parser.parse(pages[j]); 50 } 51}; 52 53// Stream data 54global.files = readdirSync(join(__dirname, '../../test/data/sax')).map(dirName => 55 join(__dirname, '../../test/data/sax', dirName, 'src.html') 56); 57 58// Utils 59function getHz(suite, testName) { 60 return suite.filter(t => t.name === testName)[0].hz; 61} 62 63function runBench({ name, workingCopyFn, upstreamFn, defer = false }) { 64 const suite = new Benchmark.Suite(name); 65 66 suite 67 .add('Working copy', workingCopyFn, { defer }) 68 .add('Upstream', upstreamFn, { defer }) 69 .on('start', () => console.log(name)) 70 .on('cycle', event => console.log(String(event.target))) 71 .on('complete', () => { 72 const workingCopyHz = getHz(suite, 'Working copy'); 73 const upstreamHz = getHz(suite, 'Upstream'); 74 75 if (workingCopyHz > upstreamHz) { 76 console.log(`Working copy is ${(workingCopyHz / upstreamHz).toFixed(2)}x faster.\n`); 77 } else { 78 console.log(`Working copy is ${(upstreamHz / workingCopyHz).toFixed(2)}x slower.\n`); 79 } 80 }) 81 .run(); 82} 83 84// Benchmarks 85runBench({ 86 name: 'parse5 regression benchmark - MICRO', 87 workingCopyFn: () => runMicro(workingCopy), 88 upstreamFn: () => runMicro(upstreamParser) 89}); 90 91runBench({ 92 name: 'parse5 regression benchmark - HUGE', 93 workingCopyFn: () => workingCopy.parse(hugePage), 94 upstreamFn: () => upstreamParser.parse(hugePage) 95}); 96 97runBench({ 98 name: 'parse5 regression benchmark - PAGES', 99 workingCopyFn: () => runPages(workingCopy), 100 upstreamFn: () => runPages(upstreamParser) 101}); 102 103runBench({ 104 name: 'parse5 regression benchmark - STREAM', 105 defer: true, 106 workingCopyFn: async deferred => { 107 const parsePromises = files.map( 108 fileName => 109 new Promise(resolve => { 110 const stream = createReadStream(fileName, 'utf8'); 111 const parserStream = new WorkingCopyParserStream(); 112 113 stream.pipe(parserStream); 114 parserStream.on('finish', resolve); 115 }) 116 ); 117 118 await Promise.all(parsePromises); 119 deferred.resolve(); 120 }, 121 upstreamFn: async deferred => { 122 const parsePromises = files.map( 123 fileName => 124 new Promise(resolve => { 125 const stream = createReadStream(fileName, 'utf8'); 126 const writable = new WritableStreamStub(); 127 128 writable.on('finish', () => { 129 upstreamParser.parse(writable.writtenData); 130 resolve(); 131 }); 132 133 stream.pipe(writable); 134 }) 135 ); 136 137 await Promise.all(parsePromises); 138 deferred.resolve(); 139 } 140}); 141