Skip to content

Commit f360b74

Browse files
committed
TEMP - more performance testing improvements.
1 parent 9b07097 commit f360b74

File tree

7 files changed

+225
-99
lines changed

7 files changed

+225
-99
lines changed

.gitignore

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,4 @@ node_modules
44
.claude/*.local.json
55

66
# Performance test results
7-
performance/performance.json
8-
performance/performance-*.json
9-
performance/performance-profile.json
10-
performance/performance-profile-*.json
11-
performance/performance-screenshot.png
12-
performance/performance-screenshot-*.png
7+
performance/results/

CLAUDE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,6 @@ files should be valid in the latest versions of Chrome, Firefox, and Safari.
145145
## Miscellaneous Development Tips
146146

147147
- Use the `--suffix` argument to save things like `--suffix=before` and `--suffix=after` for easier A/B testing. Don't forget to clean up these files after you are finished with all your tasks.
148-
```
148+
```
149+
- When looking for performance improvements after running performance tests with the `--profile` flag, check the `performance/results/performance-profile-summary*.txt` files for code bottlenecks.
150+
- Use the `--profile` flag to locate potential bottlenecks. However, don’t use the `--profile` flag for setting performance baselines or A/B testing for optimizations.

performance.js

Lines changed: 148 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,60 @@ import path from 'path';
66

77
const DEFAULT_OPTIONS = {
88
frames: 100,
9-
timing: 'fixed',
109
skip: [],
1110
profile: false,
12-
suffix: '',
11+
prefix: '',
1312
headless: false,
1413
screenshot: false,
1514
viewport: '616x800',
15+
fixInject: null,
16+
fixInitial: null,
17+
fixUpdate: null,
18+
throttling: 8,
1619
};
1720

1821
const HELP = `
1922
Usage: node performance.js [options]
2023
2124
Options:
22-
--frames=<number> Number of animation frames to run (default: 100)
23-
Note: Use 200+ frames for reliable optimization measurements.
24-
Lower values (5-20) are useful for quick sanity checks.
25-
--timing=<mode> Timing mode: 'raf' or 'fixed' (default: 'fixed')
26-
--skip=<group> Skip test group: 'inject', 'initial', or 'update' (can be used multiple times)
27-
--profile=true Enable performance profiling with Chrome DevTools
28-
--suffix=<string> File suffix for output files (default: none)
29-
Examples: --suffix=before, --suffix=optimized
30-
--headless=true Run in headless mode (default: false, shows browser)
31-
--screenshot=true Save screenshot after tests complete (default: false)
32-
--viewport=WxH Set browser viewport size (default: 616x800)
33-
Examples: --viewport=1920x1080, --viewport=800x600
34-
--help Show this help message
25+
--frames=<number> Number of animation frames to run (default: 100)
26+
Note: Use 200+ frames for reliable optimization measurements.
27+
Lower values (5-20) are useful for quick sanity checks.
28+
--skip=<item> Skip test group ('inject', 'initial', 'update')
29+
Skip library ('default', 'lit-html', 'uhtml', 'react')
30+
Can be used multiple times.
31+
Examples: --skip=react, --skip=inject
32+
--profile=true Enable performance profiling with Chrome DevTools
33+
--prefix=<string> File prefix for output files (default: none)
34+
Examples: --prefix=baseline, --prefix=optimized
35+
--headless=true Run in headless mode (default: false, shows browser)
36+
--screenshot=true Save screenshot after tests complete (default: false)
37+
--viewport=WxH Set browser viewport size (default: 616x800)
38+
Examples: --viewport=1920x1080, --viewport=800x600
39+
--fix-inject=min-max Fix inject visualization range in microseconds
40+
Example: --fix-inject=55-65
41+
--fix-initial=min-max Fix initial visualization range in microseconds
42+
Example: --fix-initial=5-7
43+
--fix-update=min-max Fix update visualization range in microseconds
44+
Example: --fix-update=0.6-0.8
45+
--throttling=<number> CPU throttling rate (default: 8, high throttling)
46+
Example: --throttling=1 (no throttling)
47+
--help Show this help message
3548
3649
Examples:
37-
node performance.js --frames=5 # Quick sanity check
38-
node performance.js --frames=200 # Reliable optimization testing
39-
node performance.js --frames=50 --timing=raf
50+
node performance.js --frames=5 # Quick sanity check
51+
node performance.js --frames=200 # Reliable optimization testing
4052
node performance.js --skip=inject --skip=initial
53+
node performance.js --skip=react --skip=lit-html # Skip specific libraries
4154
node performance.js --profile=true
42-
node performance.js --suffix=before # Saves to performance-before.json
43-
node performance.js --suffix=optimized # Saves to performance-optimized.json
44-
node performance.js --headless=true # Run without visible browser
45-
node performance.js --screenshot=true # Save screenshot for record keeping
46-
node performance.js --viewport=1920x1080 # High resolution viewport and screenshot
55+
node performance.js --prefix=baseline # Saves to baseline-performance.json
56+
node performance.js --prefix=optimized # Saves to optimized-performance.json
57+
node performance.js --headless=true # Run without visible browser
58+
node performance.js --screenshot=true # Save screenshot for record keeping
59+
node performance.js --viewport=1920x1080 # High resolution viewport and screenshot
60+
node performance.js --fix-inject=55-65 # Fixed visualization range for inject
61+
node performance.js --fix-initial=5-7 # Fixed visualization range for initial
62+
node performance.js --fix-update=0.6-0.8 # Fixed visualization range for update
4763
4864
Note: Server must be running on localhost:8080 (use 'npm start')
4965
`;
@@ -62,12 +78,10 @@ function parseArgs() {
6278
}
6379
if (arg.startsWith('--frames=')) {
6480
options.frames = parseInt(arg.split('=')[1]);
65-
} else if (arg.startsWith('--timing=')) {
66-
options.timing = arg.split('=')[1];
6781
} else if (arg.startsWith('--skip=')) {
6882
options.skip.push(arg.split('=')[1]);
69-
} else if (arg.startsWith('--suffix=')) {
70-
options.suffix = arg.split('=')[1];
83+
} else if (arg.startsWith('--prefix=')) {
84+
options.prefix = arg.split('=')[1];
7185
} else if (arg.startsWith('--profile=')) {
7286
const value = arg.split('=')[1];
7387
if (value === 'true') {
@@ -105,6 +119,34 @@ function parseArgs() {
105119
process.exit(1);
106120
}
107121
options.viewport = viewport;
122+
} else if (arg.startsWith('--fix-inject=')) {
123+
const range = arg.split('=')[1];
124+
if (!/^\d+(\.\d+)?-\d+(\.\d+)?$/.test(range)) {
125+
console.error(`Invalid fix-inject format: ${range}. Must be min-max (e.g., 55-65).`); // eslint-disable-line no-console
126+
process.exit(1);
127+
}
128+
options.fixInject = range;
129+
} else if (arg.startsWith('--fix-initial=')) {
130+
const range = arg.split('=')[1];
131+
if (!/^\d+(\.\d+)?-\d+(\.\d+)?$/.test(range)) {
132+
console.error(`Invalid fix-initial format: ${range}. Must be min-max (e.g., 5-7).`); // eslint-disable-line no-console
133+
process.exit(1);
134+
}
135+
options.fixInitial = range;
136+
} else if (arg.startsWith('--fix-update=')) {
137+
const range = arg.split('=')[1];
138+
if (!/^\d+(\.\d+)?-\d+(\.\d+)?$/.test(range)) {
139+
console.error(`Invalid fix-update format: ${range}. Must be min-max (e.g., 0.6-0.8).`); // eslint-disable-line no-console
140+
process.exit(1);
141+
}
142+
options.fixUpdate = range;
143+
} else if (arg.startsWith('--throttling=')) {
144+
const rate = parseFloat(arg.split('=')[1]);
145+
if (isNaN(rate) || rate < 1) {
146+
console.error(`Invalid throttling: ${arg.split('=')[1]}. Must be a number >= 1.`); // eslint-disable-line no-console
147+
process.exit(1);
148+
}
149+
options.throttling = rate;
108150
}
109151
}
110152
return options;
@@ -116,16 +158,25 @@ function parseArgs() {
116158
*/
117159
async function runPerformanceTests(options) {
118160
// Clean up any existing result files to ensure fresh results
119-
const performanceDir = 'performance';
120-
const suffix = options.suffix ? `-${options.suffix}` : '';
121-
const resultsFile = path.join(performanceDir, `performance${suffix}.json`);
122-
const profileFile = path.join(performanceDir, `performance-profile${suffix}.json`);
123-
if (fs.existsSync(resultsFile)) {
124-
fs.unlinkSync(resultsFile);
125-
}
126-
if (fs.existsSync(profileFile)) {
127-
fs.unlinkSync(profileFile);
161+
const resultsDir = path.join('performance', 'results');
162+
const prefix = options.prefix ? `${options.prefix}-` : '';
163+
const resultsFile = path.join(resultsDir, `${prefix}performance.json`);
164+
const oldProfileFile = path.join(resultsDir, `${prefix}performance-profile.json`);
165+
const cpuProfileFile = path.join(resultsDir, `${prefix}performance-profile.cpuprofile`);
166+
const profileSummaryFile = path.join(resultsDir, `${prefix}performance-profile-summary.txt`);
167+
168+
// Ensure results directory exists
169+
if (!fs.existsSync(resultsDir)) {
170+
fs.mkdirSync(resultsDir, { recursive: true });
128171
}
172+
173+
// Clean up any existing files that will be regenerated
174+
const filesToCleanup = [resultsFile, oldProfileFile, cpuProfileFile, profileSummaryFile];
175+
filesToCleanup.forEach(file => {
176+
if (fs.existsSync(file)) {
177+
fs.unlinkSync(file);
178+
}
179+
});
129180

130181
const browser = await puppeteer.launch({
131182
headless: options.headless,
@@ -140,6 +191,7 @@ async function runPerformanceTests(options) {
140191
'--disable-features=TranslateUI', // Disable translate features
141192
'--disable-dev-shm-usage', // Use /tmp instead of /dev/shm
142193
'--memory-pressure-off', // Disable memory pressure signals
194+
'--run-all-compositor-stages-before-draw', // Force full render pipeline each frame
143195
'--js-flags=--max-old-space-size=8192 --expose-gc', // Control heap and expose window.gc()
144196
],
145197
});
@@ -148,7 +200,7 @@ async function runPerformanceTests(options) {
148200

149201
// Set viewport size
150202
const [width, height] = options.viewport.split('x').map(Number);
151-
await page.setViewport({ width, height });
203+
await page.setViewport({ width, height, deviceScaleFactor: 1 });
152204

153205
// Configure page for stable performance testing
154206
await page.setCacheEnabled(false);
@@ -157,7 +209,6 @@ async function runPerformanceTests(options) {
157209
// Build URL with query parameters
158210
const params = new URLSearchParams();
159211
params.set('frames', options.frames.toString());
160-
params.set('timing', options.timing);
161212
for (const skip of options.skip) {
162213
params.append('skip', skip);
163214
}
@@ -167,20 +218,29 @@ async function runPerformanceTests(options) {
167218
params.set('profile', 'true');
168219
}
169220

221+
// Add fixed range parameters when provided
222+
if (options.fixInject) {
223+
params.set('fixInject', options.fixInject);
224+
}
225+
if (options.fixInitial) {
226+
params.set('fixInitial', options.fixInitial);
227+
}
228+
if (options.fixUpdate) {
229+
params.set('fixUpdate', options.fixUpdate);
230+
}
231+
170232
const url = `http://localhost:8080/performance/?${params.toString()}`;
171233

172-
// Enable profiling if requested
234+
const cdpSession = await page.createCDPSession();
235+
236+
// Fixed CPU throttle if requested.
237+
await cdpSession.send('Emulation.setCPUThrottlingRate', { rate: options.throttling });
238+
239+
// Enable CPU profiling if requested.
173240
if (options.profile) {
174-
// Start tracing with comprehensive categories
175-
await page.tracing.start({
176-
path: `performance/performance-profile${suffix}.json`,
177-
categories: [
178-
'devtools.timeline',
179-
'v8.execute',
180-
'disabled-by-default-v8.cpu_profiler',
181-
'disabled-by-default-v8.cpu_profiler.hires',
182-
],
183-
});
241+
// Connect to Chrome DevTools Protocol.
242+
await cdpSession.send('Profiler.enable');
243+
await cdpSession.send('Profiler.start');
184244
}
185245

186246
// Capture performance results and wait for completion
@@ -215,25 +275,55 @@ async function runPerformanceTests(options) {
215275
// Wait for tests to complete or timeout
216276
await Promise.race([completionPromise, timeoutPromise]);
217277

218-
// Stop profiling if enabled
278+
// Stop CPU profiling if enabled and save profile
219279
if (options.profile) {
220-
await page.tracing.stop();
280+
const profile = await cdpSession.send('Profiler.stop');
281+
282+
// Save raw CPU profile
283+
fs.writeFileSync(cpuProfileFile, JSON.stringify(profile.profile));
284+
285+
// Generate human-readable summary
286+
const hotspots = profile.profile.nodes.filter(node => node.hitCount > 0);
287+
hotspots.sort((a, b) => b.hitCount - a.hitCount);
288+
289+
let summary = '=== CPU PROFILE HOTSPOTS ===\n';
290+
summary += 'HitCount | Function | File:Line\n';
291+
summary += '---------|----------|----------\n';
292+
293+
hotspots.slice(0, 20).forEach(node => {
294+
const frame = node.callFrame;
295+
const fileName = frame.url ? frame.url.split('/').pop() : 'native';
296+
const location = frame.url ? `${fileName}:${frame.lineNumber}` : 'native';
297+
const functionName = frame.functionName || '(anonymous)';
298+
summary += `${node.hitCount.toString().padStart(8)} | ${functionName.padEnd(30)} | ${location}\n`;
299+
});
300+
301+
summary += '\n=== X-ELEMENT SPECIFIC HOTSPOTS ===\n';
302+
const xElementHotspots = hotspots.filter(node =>
303+
node.callFrame.url && (
304+
node.callFrame.url.includes('x-parser.js') ||
305+
node.callFrame.url.includes('x-template.js')
306+
)
307+
);
308+
309+
xElementHotspots.forEach(node => {
310+
const frame = node.callFrame;
311+
const fileName = frame.url.split('/').pop();
312+
const functionName = frame.functionName || '(anonymous)';
313+
summary += `${node.hitCount.toString().padStart(8)} | ${functionName.padEnd(30)} | ${fileName}:${frame.lineNumber}\n`;
314+
});
315+
316+
fs.writeFileSync(profileSummaryFile, summary);
221317
}
222318

223319
// Save performance results to file
224320
if (performanceResults) {
225-
if (!fs.existsSync(performanceDir)) {
226-
fs.mkdirSync(performanceDir, { recursive: true });
227-
}
228321
fs.writeFileSync(resultsFile, JSON.stringify(performanceResults, null, 2));
229322
}
230323

231324
// Take screenshot if requested
232325
if (options.screenshot) {
233-
if (!fs.existsSync(performanceDir)) {
234-
fs.mkdirSync(performanceDir, { recursive: true });
235-
}
236-
const screenshotFile = path.join(performanceDir, `performance-screenshot${suffix}.png`);
326+
const screenshotFile = path.join(resultsDir, `${prefix}performance-screenshot.png`);
237327
await page.screenshot({ path: screenshotFile, fullPage: true, type: 'png' });
238328
}
239329
} finally {

performance/common.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
color: black;
6565
}
6666

67+
.distribution .percentile.overflow {
68+
display: none;
69+
}
70+
6771
.distribution .min {
6872
position: absolute;
6973
top: -24px;
@@ -91,6 +95,8 @@
9195
position: fixed;
9296
top: 0;
9397
right: 0;
98+
height: 50px;
99+
width: 200px;
94100
background-color: white;
95101
z-index: 1;
96102
}

0 commit comments

Comments
 (0)