Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/rrweb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"scripts": {
"prepare": "npm run prepack",
"prepack": "npm run bundle",
"test": "npm run bundle:browser && jest",
"test:headless": "npm run bundle:browser && PUPPETEER_HEADLESS=true jest",
"test": "npm run bundle:browser && jest --testPathIgnorePatterns test/benchmark",
"test:headless": "PUPPETEER_HEADLESS=true npm run test",
"test:watch": "PUPPETEER_HEADLESS=true npm run test -- --watch",
"repl": "npm run bundle:browser && node scripts/repl.js",
"dev": "yarn bundle:browser --watch",
Expand All @@ -15,7 +15,8 @@
"typings": "tsc -d --declarationDir typings",
"check-types": "tsc -noEmit",
"prepublish": "npm run typings && npm run bundle",
"lint": "yarn eslint src"
"lint": "yarn eslint src",
"benchmark": "jest test/benchmark"
},
"repository": {
"type": "git",
Expand Down
170 changes: 170 additions & 0 deletions packages/rrweb/test/benchmark/replay-fast-forward.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import * as fs from 'fs';
import * as path from 'path';
import type { eventWithTime, recordOptions } from '../../src/types';
import { launchPuppeteer, ISuite } from '../utils';

const suites: Array<{
title: string;
eval: string;
eventsString?: string;
times?: number; // defaults to 5
}> = [
{
title: 'append 70 x 70 x 70 elements',
eval: `
() => {
return new Promise((resolve) => {
const branches = 70;
const depth = 3;
// append children for the current node
function expand(node, depth) {
if (depth == 0) return;
for (let b = 0; b < branches; b++) {
const child = document.createElement('div');
node.appendChild(child);
expand(child, depth - 1);
}
}
const frag = document.createDocumentFragment();
const node = document.createElement('div');
expand(node, depth);
frag.appendChild(node);
document.body.appendChild(frag);
resolve();
});
};
`,
times: 3,
},
{
title: 'append 1000 elements and reverse their order',
eval: `
() => {
return new Promise(async (resolve) => {
const branches = 1000;
function waitForTimeout(timeout) {
return new Promise((resolve) => setTimeout(() => resolve(), timeout));
}
const frag = document.createDocumentFragment();
const node = document.createElement('div');
for (let b = 0; b < branches; b++) {
const child = document.createElement('div');
node.appendChild(child);
child.textContent = b + 1;
}
frag.appendChild(node);
document.body.appendChild(frag);
const children = node.children;
await waitForTimeout(0);
// reverse the order of children
for (let i = children.length - 1; i >= 0; i--)
node.appendChild(node.children[i]);
resolve();
});
};
`,
times: 3,
},
];

function avg(v: number[]): number {
return v.reduce((prev, cur) => prev + cur, 0) / v.length;
}

describe('benchmark: replayer fast-forward performance', () => {
jest.setTimeout(240000);
let code: ISuite['code'];
let page: ISuite['page'];
let browser: ISuite['browser'];

beforeAll(async () => {
browser = await launchPuppeteer({
headless: true,
args: ['--disable-dev-shm-usage'],
});

const bundlePath = path.resolve(__dirname, '../../dist/rrweb.min.js');
code = fs.readFileSync(bundlePath, 'utf8');

for (const suite of suites)
suite.eventsString = await generateEvents(suite.eval);
}, 600_000);

afterAll(async () => {
await browser.close();
});

for (const suite of suites) {
it(
suite.title,
async () => {
suite.times = suite.times ?? 5;
const durations: number[] = [];
for (let i = 0; i < suite.times; i++) {
page = await browser.newPage();
await page.goto('about:blank');
await page.setContent(`<html>
<script>
${code.toString()}
</script>
<script>
window.events = ${suite.eventsString};
</script>
</html>`);
const duration = (await page.evaluate(() => {
const replayer = new (window as any).rrweb.Replayer(
(window as any).events,
);
const start = Date.now();
replayer.play(replayer.getMetaData().totalTime + 100);
return Date.now() - start;
})) as number;
durations.push(duration);
await page.close();
}

console.table([
{
title: suite.title,
times: suite.times,
duration: avg(durations),
durations: durations.join(', '),
},
]);
},
60_000,
);
}

/**
* Get the recorded events after the mutation function is executed.
*/
async function generateEvents(mutateNodesFn: string): Promise<string> {
const page = await browser.newPage();

await page.goto('about:blank');
await page.setContent(`<html>
<script>
${code.toString()}
</script>
</html>`);
const eventsString = (await page.evaluate((mutateNodesFn) => {
return new Promise((resolve) => {
const events: eventWithTime[] = [];
const options: recordOptions<eventWithTime> = {
emit: (event) => {
events.push(event);
},
};
const record = (window as any).rrweb.record;
record(options);
eval(mutateNodesFn)().then(() => {
resolve(JSON.stringify(events));
});
});
}, mutateNodesFn)) as string;

await page.close();
return eventsString;
}
});