Verify six app seams and record Phase 4 performance measurements
This commit is contained in:
@@ -25,6 +25,8 @@
|
||||
* --compare <json-path> Compare results against a previous baseline JSON
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import { createHash } from 'node:crypto';
|
||||
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';
|
||||
@@ -244,6 +246,7 @@ async function setupPageMetrics(page) {
|
||||
window.__fcp = null;
|
||||
window.__lcp = null;
|
||||
window.__longTasksTotal = 0;
|
||||
window.__longTasks = [];
|
||||
|
||||
// Observe boot completion landmark (#cards view set by renderList())
|
||||
const markBoot = () => {
|
||||
@@ -263,6 +266,7 @@ async function setupPageMetrics(page) {
|
||||
new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
window.__longTasksTotal += entry.duration;
|
||||
window.__longTasks.push({start:Math.round(entry.startTime),duration:Math.round(entry.duration)});
|
||||
}
|
||||
}).observe({ type: 'longtask', buffered: true });
|
||||
} catch {}
|
||||
@@ -374,6 +378,7 @@ async function runColdScenario({ browserType, server, proxy, profile }) {
|
||||
domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
|
||||
bootDone: window.__bootDone ? Math.round(window.__bootDone) : null,
|
||||
longTaskTotal: Math.round(window.__longTasksTotal || 0),
|
||||
longTasks: window.__longTasks || [],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -396,6 +401,7 @@ async function runColdScenario({ browserType, server, proxy, profile }) {
|
||||
domContentLoaded: pageMetrics.domContentLoaded,
|
||||
bootDone: pageMetrics.bootDone,
|
||||
longTaskTotal: pageMetrics.longTaskTotal,
|
||||
longTasks: pageMetrics.longTasks,
|
||||
requests: stats.requests,
|
||||
initialJsCss: pageMetrics.initialJsCss,
|
||||
initialAssetRequests: pageMetrics.initialAssetRequests,
|
||||
@@ -478,6 +484,7 @@ async function runWarmScenario({ browserType, server, proxy, profile }) {
|
||||
domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
|
||||
bootDone: window.__bootDone ? Math.round(window.__bootDone) : null,
|
||||
longTaskTotal: Math.round(window.__longTasksTotal || 0),
|
||||
longTasks: window.__longTasks || [],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -492,6 +499,7 @@ async function runWarmScenario({ browserType, server, proxy, profile }) {
|
||||
domContentLoaded: pageMetrics.domContentLoaded,
|
||||
bootDone: pageMetrics.bootDone,
|
||||
longTaskTotal: pageMetrics.longTaskTotal,
|
||||
longTasks: pageMetrics.longTasks,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -634,7 +642,7 @@ async function runUpdateScenario({ browserType, server, proxy, profile, updateKi
|
||||
}
|
||||
|
||||
// Switch server to Tree N+1 (app.js or theme-glass.css modification)
|
||||
const targetTree = updateKind === 'js' ? trees.treeUpdateJs : trees.treeUpdateCss;
|
||||
const targetTree = updateKind === 'feature' ? trees.treeUpdateFeature : updateKind === 'js' ? trees.treeUpdateJs : trees.treeUpdateCss;
|
||||
await server.restartWithPublic(targetTree);
|
||||
|
||||
// Measure wire bytes and duration from reg.update() until new SW is waiting/installed
|
||||
@@ -654,10 +662,32 @@ async function runUpdateScenario({ browserType, server, proxy, profile, updateKi
|
||||
await sleep(50);
|
||||
}
|
||||
|
||||
if (updateKind === 'feature') {
|
||||
// Readiness excludes background chunks. Include activation and the changed
|
||||
// feature's actual idle transfer rather than reporting readiness as its cost.
|
||||
const expected = createHash('sha256').update(readFileSync(path.join(targetTree,'presenter.js'))).digest('hex').slice(0,10);
|
||||
await page.evaluate(async()=>{const r=await navigator.serviceWorker.getRegistration();r.waiting.postMessage({type:'SKIP_WAITING'});});
|
||||
const end=Date.now()+90000;
|
||||
while(true) {
|
||||
const complete=await page.evaluate(async h=>{
|
||||
const r=await navigator.serviceWorker.getRegistration();
|
||||
if(r.waiting || r.installing)return false;
|
||||
r.active?.postMessage({type:'WARM_ASSETS',saveData:false});
|
||||
const c=await caches.open('ytplayer-assets'),entry=await c.match('/presenter.js?v='+h);
|
||||
return entry?.headers.get('X-Asset-Hash')===h;
|
||||
},expected);
|
||||
if(complete)break;
|
||||
if(Date.now()>end)throw Error('Changed feature was not cached after activation');
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
const duration = Date.now() - t0;
|
||||
proxy.stopTracking();
|
||||
const stats = proxy.getStats();
|
||||
|
||||
if(updateKind==='feature') {
|
||||
const assetDownloads=stats.requests.filter(r=>r.bytes>0).map(r=>new URL(r.url,navUrl).pathname).filter(p=>/\.(?:js|css|woff2?|png|svg)$/.test(p)&&!['/sw.js','/asset-sync-core.js'].includes(p));
|
||||
assert.deepEqual([...new Set(assetDownloads)],['/presenter.js'],'feature edit transfers only its chunk; no app/core/font/icon payload');
|
||||
}
|
||||
await browser.close();
|
||||
|
||||
return {
|
||||
@@ -696,6 +726,7 @@ async function main() {
|
||||
const treeBase = path.join(tmpRoot, 'tree-base');
|
||||
const treeUpdateJs = path.join(tmpRoot, 'tree-update-js');
|
||||
const treeUpdateCss = path.join(tmpRoot, 'tree-update-css');
|
||||
const treeUpdateFeature = path.join(tmpRoot, 'tree-update-feature');
|
||||
|
||||
let frontendSource=path.join(REPO_ROOT,'frontend');
|
||||
if(options.frontendCommit) {
|
||||
@@ -704,14 +735,15 @@ async function main() {
|
||||
execFileSync('tar',['-xf',archive,'-C',tmpRoot]);
|
||||
frontendSource=path.join(tmpRoot,'frontend');
|
||||
}
|
||||
for(const tree of [treeBase,treeUpdateJs,treeUpdateCss])cpSync(frontendSource,tree,{recursive:true});
|
||||
for(const tree of [treeBase,treeUpdateJs,treeUpdateCss,treeUpdateFeature])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');
|
||||
// Append 1 rule to theme-glass.css in treeUpdateCss
|
||||
appendFileSync(path.join(treeUpdateCss, 'theme-glass.css'), '\n/* baseline bench: 1-line css rule */\n.baseline-marker { opacity: 0.99; }\n');
|
||||
|
||||
const trees = { treeBase, treeUpdateJs, treeUpdateCss };
|
||||
if(existsSync(path.join(treeUpdateFeature,'presenter.js')))appendFileSync(path.join(treeUpdateFeature,'presenter.js'),'\n// baseline bench: 1-line presenter chunk comment\n');
|
||||
const trees = { treeBase, treeUpdateJs, treeUpdateCss, treeUpdateFeature };
|
||||
|
||||
const serverScratch = path.join(tmpRoot, 'server-run');
|
||||
const server = new ServerManager(serverScratch, treeBase);
|
||||
@@ -721,6 +753,7 @@ async function main() {
|
||||
const results = {
|
||||
date: new Date().toISOString(),
|
||||
frontendCommit: options.frontendCommit,
|
||||
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,
|
||||
browsers: {},
|
||||
@@ -759,6 +792,7 @@ async function main() {
|
||||
domContentLoaded: summarizeList(coldRuns.map((r) => r.domContentLoaded)),
|
||||
bootDone: summarizeList(coldRuns.map((r) => r.bootDone)),
|
||||
longTaskTotal: summarizeList(coldRuns.map((r) => r.longTaskTotal)),
|
||||
longTasks: coldRuns.map(r=>r.longTasks),
|
||||
swInstallDuration: summarizeList(coldRuns.map((r) => r.swInstallDuration)),
|
||||
swInstallBytes: summarizeList(coldRuns.map((r) => r.swInstallBytes)),
|
||||
storageEstimate: coldRuns[0]?.storageEstimate || null,
|
||||
@@ -869,6 +903,16 @@ async function main() {
|
||||
};
|
||||
}
|
||||
|
||||
if ((options.scenario === 'all' || options.scenario === 'update-feature') && existsSync(path.join(treeBase,'presenter.js'))) {
|
||||
console.log(` [Scenario: Update split-out presenter, including idle cache fill] running ${options.runs} iterations...`);
|
||||
const runs=[];
|
||||
for(let r=0;r<options.runs;r++) {
|
||||
const res=await runUpdateScenario({browserType:bType,server,proxy,profile:prof,updateKind:'feature',trees});runs.push(res);
|
||||
console.log(` run ${r+1}/${options.runs}: ${res.wireBytes} bytes, ${res.requestCount} reqs, dur=${res.duration}ms`);
|
||||
}
|
||||
results.browsers[bName][prof].updateFeature={requestCount:summarizeList(runs.map(r=>r.requestCount)),wireBytes:summarizeList(runs.map(r=>r.wireBytes)),duration:summarizeList(runs.map(r=>r.duration)),wireBytesByType:runs[0]?.wireBytesByType||{},requests:runs[0]?.requests||[]};
|
||||
}
|
||||
|
||||
await proxy.close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user