Measure complete CSS updates and fully cached warm reloads
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import { assertChangedAssets, assertWarmAssets, waitForIdleAssets } from './measurement.mjs';
|
||||
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';
|
||||
@@ -461,6 +462,8 @@ async function runWarmScenario({ browserType, server, proxy, profile }) {
|
||||
});
|
||||
});
|
||||
|
||||
await waitForIdleAssets(page);
|
||||
|
||||
// Now measure warm re-navigation
|
||||
proxy.resetStats();
|
||||
proxy.startTracking();
|
||||
@@ -470,6 +473,7 @@ async function runWarmScenario({ browserType, server, proxy, profile }) {
|
||||
|
||||
proxy.stopTracking();
|
||||
const stats = proxy.getStats();
|
||||
assertWarmAssets(stats.requests);
|
||||
|
||||
const pageMetrics = await page.evaluate(() => {
|
||||
const nav = performance.getEntriesByType('navigation')[0];
|
||||
@@ -622,24 +626,7 @@ async function runUpdateScenario({ browserType, server, proxy, profile, updateKi
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
await waitForIdleAssets(page);
|
||||
|
||||
// Switch server to Tree N+1 (app.js or theme-glass.css modification)
|
||||
const targetTree = updateKind === 'feature' ? trees.treeUpdateFeature : updateKind === 'js' ? trees.treeUpdateJs : trees.treeUpdateCss;
|
||||
@@ -662,20 +649,23 @@ async function runUpdateScenario({ browserType, server, proxy, profile, updateKi
|
||||
await sleep(50);
|
||||
}
|
||||
|
||||
if (updateKind === 'feature') {
|
||||
const readyDuration = Date.now() - t0;
|
||||
const readyWireBytes = proxy.getStats().totalWireBytes;
|
||||
if (updateKind === 'feature' || updateKind === 'css') {
|
||||
const changedPath=updateKind==='feature'?'/presenter.js':'/theme-glass.css';
|
||||
// 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);
|
||||
const expected = createHash('sha256').update(readFileSync(path.join(targetTree,changedPath.slice(1)))).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 complete=await page.evaluate(async ({h,p})=>{
|
||||
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);
|
||||
const c=await caches.open('ytplayer-assets'),entry=await c.match(p+'?v='+h);
|
||||
return entry?.headers.get('X-Asset-Hash')===h;
|
||||
},expected);
|
||||
},{h:expected,p:changedPath});
|
||||
if(complete)break;
|
||||
if(Date.now()>end)throw Error('Changed feature was not cached after activation');
|
||||
await sleep(100);
|
||||
@@ -684,14 +674,13 @@ async function runUpdateScenario({ browserType, server, proxy, profile, updateKi
|
||||
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');
|
||||
}
|
||||
assertChangedAssets(stats.requests, updateKind==='feature'?'/presenter.js':updateKind==='css'?'/theme-glass.css':'/app.js');
|
||||
await browser.close();
|
||||
|
||||
return {
|
||||
updateKind,
|
||||
readyDuration,
|
||||
readyWireBytes,
|
||||
requests: stats.requests,
|
||||
duration,
|
||||
requestCount: stats.requestCount,
|
||||
@@ -895,6 +884,8 @@ async function main() {
|
||||
}
|
||||
|
||||
results.browsers[bName][prof].updateCss = {
|
||||
readyDuration: summarizeList(updCssRuns.map(r=>r.readyDuration)),
|
||||
readyWireBytes: summarizeList(updCssRuns.map(r=>r.readyWireBytes)),
|
||||
requestCount: summarizeList(updCssRuns.map((r) => r.requestCount)),
|
||||
wireBytes: summarizeList(updCssRuns.map((r) => r.wireBytes)),
|
||||
duration: summarizeList(updCssRuns.map((r) => r.duration)),
|
||||
|
||||
Reference in New Issue
Block a user