Trace cold request waterfalls and isolate the layout discovery regression

This commit is contained in:
Jonathan Sykes
2026-10-08 08:45:00 +08:00
parent af6aabc3b7
commit 799b1e7ed6
12 changed files with 60098 additions and 8 deletions

View File

@@ -62,6 +62,7 @@ function parseArgs() {
out: null,
compare: null,
frontendCommit: null,
sourceRoot: null,
};
for (let i = 0; i < args.length; i++) {
@@ -71,6 +72,7 @@ function parseArgs() {
else if (a === '--profile' && i + 1 < args.length) options.profile = args[++i];
else if (a === '--scenario' && i + 1 < args.length) options.scenario = args[++i];
else if (a === '--out' && i + 1 < args.length) options.out = args[++i];
else if (a === '--source-root' && i + 1 < args.length) options.sourceRoot = path.resolve(args[++i]);
else if (a === '--frontend-commit' && i + 1 < args.length) options.frontendCommit = args[++i];
else if (a === '--compare' && i + 1 < args.length) options.compare = args[++i];
}
@@ -151,7 +153,8 @@ async function seedDatabase(dbPath, mediaFiles) {
* Bun Server Manager
*/
class ServerManager {
constructor(scratchDir, publicDir) {
constructor(scratchDir, publicDir, sourceRoot = REPO_ROOT) {
this.sourceRoot = sourceRoot;
this.scratchDir = scratchDir;
this.publicDir = publicDir;
this.dataDir = path.join(scratchDir, 'data');
@@ -184,7 +187,7 @@ class ServerManager {
DB_PATH: this.dbPath,
};
const serverScript = path.join(REPO_ROOT, 'server', 'server.js');
const serverScript = path.join(this.sourceRoot, 'server', 'server.js');
this.serverProcess = spawn('bun', [serverScript], {
cwd: srvCwd,
env,
@@ -373,7 +376,7 @@ async function runColdScenario({ browserType, server, proxy, profile }) {
return {
initialJsCss: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).reduce((sum,entry)=>sum+entry.encodedBodySize,0),
initialAssetRequests: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).map(entry=>({url:entry.name,bytes:entry.encodedBodySize})),
initialAssetRequests: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).map(entry=>({url:entry.name,bytes:entry.encodedBodySize,start:entry.startTime,responseStart:entry.responseStart,end:entry.responseEnd,initiator:entry.initiatorType})),
fcp: fcp ? Math.round(fcp.startTime) : null,
lcp: window.__lcp ? Math.round(window.__lcp) : null,
domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
@@ -482,7 +485,7 @@ async function runWarmScenario({ browserType, server, proxy, profile }) {
return {
initialJsCss: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).reduce((sum,entry)=>sum+entry.encodedBodySize,0),
initialAssetRequests: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).map(entry=>({url:entry.name,bytes:entry.encodedBodySize})),
initialAssetRequests: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).map(entry=>({url:entry.name,bytes:entry.encodedBodySize,start:entry.startTime,responseStart:entry.responseStart,end:entry.responseEnd,initiator:entry.initiatorType})),
fcp: fcp ? Math.round(fcp.startTime) : null,
lcp: window.__lcp ? Math.round(window.__lcp) : null,
domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
@@ -563,7 +566,7 @@ async function runOfflineScenario({ browserType, server, proxy, profile }) {
return {
initialJsCss: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).reduce((sum,entry)=>sum+entry.encodedBodySize,0),
initialAssetRequests: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).map(entry=>({url:entry.name,bytes:entry.encodedBodySize})),
initialAssetRequests: performance.getEntriesByType('resource').filter(entry=>/\.(?:js|css)(?:[?#]|$)/.test(entry.name)&&entry.startTime <= nav.domContentLoadedEventEnd&&['script','link','css'].includes(entry.initiatorType)).map(entry=>({url:entry.name,bytes:entry.encodedBodySize,start:entry.startTime,responseStart:entry.responseStart,end:entry.responseEnd,initiator:entry.initiatorType})),
fcp: fcp ? Math.round(fcp.startTime) : null,
lcp: window.__lcp ? Math.round(window.__lcp) : null,
domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
@@ -717,7 +720,7 @@ async function main() {
const treeUpdateCss = path.join(tmpRoot, 'tree-update-css');
const treeUpdateFeature = path.join(tmpRoot, 'tree-update-feature');
let frontendSource=path.join(REPO_ROOT,'frontend');
let frontendSource=path.join(options.sourceRoot || REPO_ROOT,'frontend');
if(options.frontendCommit) {
const archive=path.join(tmpRoot,'frontend.tar');
writeFileSync(archive,execFileSync('git',['archive',options.frontendCommit,'frontend'],{cwd:REPO_ROOT,maxBuffer:20*1024*1024}));
@@ -735,13 +738,15 @@ async function main() {
const trees = { treeBase, treeUpdateJs, treeUpdateCss, treeUpdateFeature };
const serverScratch = path.join(tmpRoot, 'server-run');
const server = new ServerManager(serverScratch, treeBase);
const server = new ServerManager(serverScratch, treeBase, options.sourceRoot || REPO_ROOT);
await server.start();
console.log(`Bun server running on ephemeral port ${server.port}`);
const results = {
date: new Date().toISOString(),
frontendCommit: options.frontendCommit,
sourceRoot: options.sourceRoot,
sourceCommit: spawnSync('git', ['rev-parse','HEAD'], { cwd: options.sourceRoot || REPO_ROOT }).stdout.toString().trim(),
frontendBuildTag: (await (await fetch('http://127.0.0.1:'+server.port+'/api/version')).json()).buildTag,
commit: spawnSync('git', ['rev-parse', 'HEAD'], { cwd: REPO_ROOT }).stdout.toString().trim(),
runsConfigured: options.runs,
@@ -791,6 +796,7 @@ async function main() {
requests: coldRuns[0]?.requests || [],
initialJsCss: summarizeList(coldRuns.map(r=>r.initialJsCss)),
initialAssetRequests: coldRuns[0]?.initialAssetRequests || [],
waterfalls: coldRuns.map(r=>({fcp:r.fcp,requests:r.requests,page:r.initialAssetRequests})),
};
}