• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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