Verify staged lazy loading and record Phase 3 measurements
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
* --compare <json-path> Compare results against a previous baseline JSON
|
||||
*/
|
||||
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { spawn, spawnSync, execFileSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync, rmSync, cpSync, appendFileSync, statSync, writeFileSync, readFileSync, symlinkSync, unlinkSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -58,6 +58,7 @@ function parseArgs() {
|
||||
scenario: 'all', // 'cold', 'warm', 'offline', 'update-js', 'update-css', or 'all'
|
||||
out: null,
|
||||
compare: null,
|
||||
frontendCommit: null,
|
||||
};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
@@ -67,6 +68,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 === '--frontend-commit' && i + 1 < args.length) options.frontendCommit = args[++i];
|
||||
else if (a === '--compare' && i + 1 < args.length) options.compare = args[++i];
|
||||
}
|
||||
|
||||
@@ -365,6 +367,8 @@ async function runColdScenario({ browserType, server, proxy, profile }) {
|
||||
const fcp = paint.find((p) => p.name === 'first-contentful-paint');
|
||||
|
||||
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})),
|
||||
fcp: fcp ? Math.round(fcp.startTime) : null,
|
||||
lcp: window.__lcp ? Math.round(window.__lcp) : null,
|
||||
domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
|
||||
@@ -393,6 +397,8 @@ async function runColdScenario({ browserType, server, proxy, profile }) {
|
||||
bootDone: pageMetrics.bootDone,
|
||||
longTaskTotal: pageMetrics.longTaskTotal,
|
||||
requests: stats.requests,
|
||||
initialJsCss: pageMetrics.initialJsCss,
|
||||
initialAssetRequests: pageMetrics.initialAssetRequests,
|
||||
swInstallDuration,
|
||||
swInstallBytes: stats.totalWireBytes,
|
||||
storageEstimate: storage,
|
||||
@@ -465,6 +471,8 @@ async function runWarmScenario({ browserType, server, proxy, profile }) {
|
||||
const fcp = paint.find((p) => p.name === 'first-contentful-paint');
|
||||
|
||||
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})),
|
||||
fcp: fcp ? Math.round(fcp.startTime) : null,
|
||||
lcp: window.__lcp ? Math.round(window.__lcp) : null,
|
||||
domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
|
||||
@@ -542,6 +550,8 @@ async function runOfflineScenario({ browserType, server, proxy, profile }) {
|
||||
const fcp = paint.find((p) => p.name === 'first-contentful-paint');
|
||||
|
||||
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})),
|
||||
fcp: fcp ? Math.round(fcp.startTime) : null,
|
||||
lcp: window.__lcp ? Math.round(window.__lcp) : null,
|
||||
domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
|
||||
@@ -602,6 +612,27 @@ async function runUpdateScenario({ browserType, server, proxy, profile, updateKi
|
||||
await sleep(50);
|
||||
}
|
||||
|
||||
// Phase 3 activation precedes idle feature caching. Measure updates from a
|
||||
// genuinely warm build, rather than counting unfinished first-install bytes.
|
||||
if (await page.evaluate(()=>!!window.Lazy && !!window.Lazy.warm)) {
|
||||
await page.evaluate(()=>Lazy.warm());
|
||||
const end=Date.now()+90000;
|
||||
while (true) {
|
||||
const ready=await page.evaluate(async()=>{
|
||||
const c=await caches.open('ytplayer-assets'),r=await c.match('/__ytp_asset_state');
|
||||
if(!r)return false;
|
||||
const m=(await r.json()).current;
|
||||
return (await Promise.all(Object.values(m.groups).flatMap(g=>g.files).map(async p=>{
|
||||
const response=await c.match(p+'?v='+m.files[p].h);
|
||||
return response?.headers.get('X-Asset-Hash')===m.files[p].h;
|
||||
}))).every(Boolean);
|
||||
});
|
||||
if(ready)break;
|
||||
if(Date.now()>end)throw Error('Idle warm did not complete before update benchmark');
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
// Switch server to Tree N+1 (app.js or theme-glass.css modification)
|
||||
const targetTree = updateKind === 'js' ? trees.treeUpdateJs : trees.treeUpdateCss;
|
||||
await server.restartWithPublic(targetTree);
|
||||
@@ -666,9 +697,14 @@ async function main() {
|
||||
const treeUpdateJs = path.join(tmpRoot, 'tree-update-js');
|
||||
const treeUpdateCss = path.join(tmpRoot, 'tree-update-css');
|
||||
|
||||
cpSync(path.join(REPO_ROOT, 'frontend'), treeBase, { recursive: true });
|
||||
cpSync(path.join(REPO_ROOT, 'frontend'), treeUpdateJs, { recursive: true });
|
||||
cpSync(path.join(REPO_ROOT, 'frontend'), treeUpdateCss, { recursive: true });
|
||||
let frontendSource=path.join(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}));
|
||||
execFileSync('tar',['-xf',archive,'-C',tmpRoot]);
|
||||
frontendSource=path.join(tmpRoot,'frontend');
|
||||
}
|
||||
for(const tree of [treeBase,treeUpdateJs,treeUpdateCss])cpSync(frontendSource,tree,{recursive:true});
|
||||
|
||||
// Append 1 comment line to app.js in treeUpdateJs
|
||||
appendFileSync(path.join(treeUpdateJs, 'app.js'), '\n// baseline bench: 1-line app.js comment\n');
|
||||
@@ -684,6 +720,7 @@ async function main() {
|
||||
|
||||
const results = {
|
||||
date: new Date().toISOString(),
|
||||
frontendCommit: options.frontendCommit,
|
||||
commit: spawnSync('git', ['rev-parse', 'HEAD'], { cwd: REPO_ROOT }).stdout.toString().trim(),
|
||||
runsConfigured: options.runs,
|
||||
browsers: {},
|
||||
@@ -729,6 +766,8 @@ async function main() {
|
||||
autoplayBlocked: coldRuns.some((r) => r.autoplayBlocked),
|
||||
wireBytesByType: coldRuns[0]?.wireBytesByType || {},
|
||||
requests: coldRuns[0]?.requests || [],
|
||||
initialJsCss: summarizeList(coldRuns.map(r=>r.initialJsCss)),
|
||||
initialAssetRequests: coldRuns[0]?.initialAssetRequests || [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user