-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathmeasure.tsx
106 lines (84 loc) · 2.85 KB
/
measure.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import * as React from 'react';
import * as math from 'mathjs';
import { logger } from '@callstack/reassure-logger';
import { config } from './config';
import { showFlagsOuputIfNeeded, writeTestStats } from './output';
import { resolveTestingLibrary } from './testingLibrary';
import type { MeasureRenderResult } from './types';
logger.configure({
verbose: process.env.REASSURE_VERBOSE === 'true' || process.env.REASSURE_VERBOSE === '1',
silent: process.env.REASSURE_SILENT === 'true' || process.env.REASSURE_SILENT === '1',
});
export interface MeasureOptions {
runs?: number;
dropWorst?: number;
wrapper?: (node: React.ReactElement) => JSX.Element;
scenario?: (screen: any) => Promise<any>;
}
export async function measurePerformance(
ui: React.ReactElement,
options?: MeasureOptions
): Promise<MeasureRenderResult> {
const stats = await measureRender(ui, options);
await writeTestStats(stats);
return stats;
}
export async function measureRender(ui: React.ReactElement, options?: MeasureOptions): Promise<MeasureRenderResult> {
const runs = options?.runs ?? config.runs;
const wrapper = options?.wrapper;
const scenario = options?.scenario;
const dropWorst = options?.dropWorst ?? config.dropWorst;
let entries = [];
let hasTooLateRender = false;
const wrappedUi = wrapper ? wrapper(ui) : ui;
showFlagsOuputIfNeeded();
const { render, cleanup } = resolveTestingLibrary();
for (let i = 0; i < runs + dropWorst; i += 1) {
let duration = 0;
let count = 0;
let isFinished = false;
const handleRender = (_id: string, _phase: string, actualDuration: number) => {
duration += actualDuration;
count += 1;
if (isFinished) {
hasTooLateRender = true;
}
};
const screen = render(
<React.Profiler id="Test" onRender={handleRender}>
{wrappedUi}
</React.Profiler>
);
if (scenario) {
await scenario(screen);
}
cleanup();
isFinished = true;
global.gc?.();
entries.push({ duration, count });
}
if (hasTooLateRender) {
const testName = expect.getState().currentTestName;
logger.warn(
`test "${testName}" still re-renders after test scenario finished.\n\nPlease update your code to wait for all renders to finish.`
);
}
// Drop worst measurements outliers (usually warm up runs)
entries.sort((first, second) => second.duration - first.duration); // duration DESC
entries = entries.slice(dropWorst);
const durations = entries.map((entry) => entry.duration);
const meanDuration = math.mean(durations) as number;
const stdevDuration = math.std(...durations);
const counts = entries.map((entry) => entry.count);
const meanCount = math.mean(counts) as number;
const stdevCount = math.std(...counts);
return {
runs,
meanDuration,
stdevDuration,
durations,
meanCount,
stdevCount,
counts,
};
}