Verify production-client migration and record Phase 1 measurements
This commit is contained in:
@@ -141,3 +141,43 @@ node perf/hidden-timers.mjs
|
||||
```
|
||||
Exits with code 0 on success, or code 1 if any interval fails to pause, fails to resume, or if page errors occur.
|
||||
|
||||
## Phase 1 production-client migration
|
||||
|
||||
Build the production fixture locally without contacting the unavailable production
|
||||
site (COMMON.md takes precedence over the older live-tag instructions above):
|
||||
|
||||
```bash
|
||||
perf/make-shell-fixture.sh b77938a 1175f1a1d2c1
|
||||
node perf/migration.mjs # Chromium and WebKit
|
||||
node perf/migration.mjs --browser webkit # one browser
|
||||
```
|
||||
|
||||
The harness archives the actual `b77938a` server and uses the reconstructed old
|
||||
frontend, isolated database/media/upload paths, and an ephemeral origin behind the
|
||||
`lossy` throttle/stall proxy. It restarts onto the current server and frontend at
|
||||
that same origin, then invokes the OLD application's update check and clicks its
|
||||
actual Refresh UI button. Assertions cover one banner, exactly one reload, cleared
|
||||
update-attempt state, all new SHELL entries with correct hashes, no script errors,
|
||||
and offline boot. Plain, stale `?v=<oldTag>` and `?__ytpfresh=` asset responses are
|
||||
also compared byte-for-byte with the current files. Results are stored in
|
||||
`perf/results/phase1-migration-2026-10-07.json`; scratch trees are removed on exit.
|
||||
No service-worker source is patched or simulated. Offline checks stop Bun and
|
||||
block the proxy; WebKit avoids its broken native setOffline API. Known WebKit
|
||||
transport errors for disconnected version/recommendation requests are verified
|
||||
separately from script errors.
|
||||
|
||||
The current server snapshots static bytes at boot; restart it after frontend
|
||||
changes. `ASSET_HASHING=0` restores the previous single-tag stamp/cache behavior.
|
||||
The manifest's tag hashes sorted canonical metadata: CSS/JS URLs are stamped
|
||||
before hashing the index, with its derived build-meta placeholder intact. The
|
||||
published index/SW hashes describe their final served bytes after tag injection;
|
||||
these derived hashes are not fed recursively into their own build identity.
|
||||
|
||||
JS-created workers/importScripts/dynamic imports retain their existing plain URLs
|
||||
in Phase 1. Adding a separate loader would require changing the intentionally
|
||||
unchanged SHELL; worker import URL propagation belongs with the client migration.
|
||||
WASM binaries are embedded in the existing `sha256-wasm.js`/`loudness-wasm.js`,
|
||||
which receive hashed URLs as HTML scripts. Font preloads retain plain URLs to match
|
||||
`fonts.css`; the webmanifest and its icon URLs also stay plain and precached.
|
||||
`piano-engine.mjs` is manifested but is historically absent from SHELL; this
|
||||
existing offline piano limitation is deferred to the loader phase.
|
||||
|
||||
176
perf/migration.mjs
Normal file
176
perf/migration.mjs
Normal file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env node
|
||||
// Real production-client migration through the unchanged Refresh UI flow.
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn, execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, mkdirSync, symlinkSync, unlinkSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { createServer } from 'node:net';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createThrottleProxy } from './proxy.mjs';
|
||||
import { chromium, webkit } from 'playwright';
|
||||
|
||||
const repo = resolve(fileURLToPath(new URL('..', import.meta.url)));
|
||||
const commit = 'b77938a', oldTag = '1175f1a1d2c1';
|
||||
const args = process.argv.slice(2);
|
||||
const browserName = args.includes('--browser') ? args[args.indexOf('--browser') + 1] : 'all';
|
||||
assert.ok(['all', 'chromium', 'webkit'].includes(browserName));
|
||||
execFileSync('bash', [join(repo, 'perf/make-shell-fixture.sh'), commit, oldTag], { cwd: repo, stdio: 'inherit' });
|
||||
const scratch = mkdtempSync(join(repo, 'perf/.tmp/migration-'));
|
||||
const archive = join(scratch, 'old-server.tar');
|
||||
writeFileSync(archive, execFileSync('git', ['archive', commit, 'server'], { cwd: repo, maxBuffer: 20 * 1024 * 1024 }));
|
||||
execFileSync('tar', ['-xf', archive, '-C', scratch]);
|
||||
symlinkSync(join(repo, 'server/node_modules'), join(scratch, 'server/node_modules'));
|
||||
// Archived server modules resolve their Docker-style public imports relative
|
||||
// to the module, while static routes resolve ./public relative to cwd.
|
||||
symlinkSync(join(repo, `perf/fixtures/shell-${oldTag}`), join(scratch, 'server/public'));
|
||||
const shell = [...readFileSync(join(repo, 'frontend/sw.js'), 'utf8').match(/const SHELL = \[([\s\S]*?)\n\];/)[1].matchAll(/['"](\/[^'"]+)['"]/g)].map(m => m[1]);
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
async function freePort() {
|
||||
const server = createServer();
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
||||
const port = server.address().port;
|
||||
await new Promise(resolve => server.close(resolve));
|
||||
return port;
|
||||
}
|
||||
async function run(name) {
|
||||
const cwd = join(scratch, name);
|
||||
mkdirSync(cwd);
|
||||
const port = await freePort();
|
||||
let child, output = '', browser, proxy;
|
||||
async function stop() {
|
||||
if (!child) return;
|
||||
const process = child; child = null;
|
||||
await new Promise(resolve => {
|
||||
if (process.exitCode !== null) return resolve();
|
||||
process.once('exit', resolve); process.kill('SIGTERM');
|
||||
});
|
||||
}
|
||||
async function start(old) {
|
||||
symlinkSync(old ? join(repo, `perf/fixtures/shell-${oldTag}`) : join(repo, 'frontend'), join(cwd, 'public'));
|
||||
output = '';
|
||||
child = spawn('bun', [old ? join(scratch, 'server/server.js') : join(repo, 'server/server.js')], {
|
||||
cwd, env: { ...process.env, BUILD_TAG: '', ASSET_HASHING: old ? '0' : '1', PORT: String(port),
|
||||
DATA_DIR: join(cwd, 'data'), DB_PATH: join(cwd, 'db.sqlite'), MEDIA_DIR: join(cwd, 'media'), UPLOAD_DIR: join(cwd, 'uploads') },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
for (const stream of [child.stdout, child.stderr]) stream.on('data', chunk => { output = (output + chunk).slice(-12000); });
|
||||
for (let i = 0; i < 200; i++) {
|
||||
if (child.exitCode !== null) throw new Error(`Bun server exited: ${output}`);
|
||||
try { if ((await fetch(`http://127.0.0.1:${port}/api/version`)).ok) return; } catch {}
|
||||
await sleep(100);
|
||||
}
|
||||
throw new Error(`Bun startup timeout: ${output}`);
|
||||
}
|
||||
try {
|
||||
await start(true);
|
||||
assert.equal((await (await fetch(`http://127.0.0.1:${port}/api/version`)).json()).buildTag, oldTag);
|
||||
proxy = createThrottleProxy({ targetPort: port, profile: 'lossy' });
|
||||
const proxyPort = await proxy.listen();
|
||||
const origin = `http://127.0.0.1:${proxyPort}`;
|
||||
browser = await ({ chromium, webkit }[name]).launch({ headless: true });
|
||||
const context = await browser.newContext();
|
||||
// Keep this harness entirely local, including any UI third-party requests.
|
||||
await context.route('**/*', route => route.request().url().startsWith(origin) ? route.continue() : route.abort());
|
||||
let banners = 0, navigations = 0;
|
||||
const errors = [];
|
||||
const failedRequests = [];
|
||||
let disconnected = false;
|
||||
const page = await context.newPage();
|
||||
await page.exposeFunction('migrationBanner', () => { banners++; });
|
||||
await page.addInitScript(() => {
|
||||
let visible = false;
|
||||
new MutationObserver(() => {
|
||||
const modal = document.querySelector('#modal');
|
||||
const now = !!modal && !modal.classList.contains('hidden') && /Update available/.test(document.querySelector('#modalTitle')?.textContent || '');
|
||||
if (now && !visible) window.migrationBanner();
|
||||
visible = now;
|
||||
}).observe(document, { subtree: true, childList: true, attributes: true, characterData: true });
|
||||
});
|
||||
page.on('pageerror', error => errors.push({ message: error.message, disconnected }));
|
||||
page.on('requestfailed', request => failedRequests.push(request.url()));
|
||||
page.on('framenavigated', frame => { if (frame === page.mainFrame()) navigations++; });
|
||||
await page.goto(origin, { waitUntil: 'load', timeout: 90000 });
|
||||
await page.waitForFunction(() => !!navigator.serviceWorker.controller, { timeout: 90000 });
|
||||
assert.equal(await page.locator('meta[name="ytp-build"]').getAttribute('content'), oldTag);
|
||||
assert.equal(banners, 0, 'no first-install banner');
|
||||
await stop(); unlinkSync(join(cwd, 'public')); await start(false);
|
||||
const backend = `http://127.0.0.1:${port}`;
|
||||
const manifest = await (await fetch(`${backend}/api/manifest`)).json();
|
||||
assert.notEqual(manifest.buildTag, oldTag);
|
||||
// Old URLs must continue delivering actual NEW bytes, not error/fallback HTML.
|
||||
for (const path of ['/app.js', '/theme-glass.css', '/opfs-worker.js', '/sha256-wasm.js', '/manifest.webmanifest', '/icons/icon-192.png', '/fonts/HankenGrotesk-latin.woff2']) {
|
||||
for (const query of ['', '?__ytpfresh=migration', `?v=${oldTag}`]) {
|
||||
const response = await fetch(`${backend}${path}${query}`);
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(response.headers.get('x-asset-hash'), manifest.files[path].h);
|
||||
assert.deepEqual(Buffer.from(await response.arrayBuffer()), readFileSync(join(repo, 'frontend', path.slice(1))));
|
||||
if (query.includes('?v=')) assert.equal(response.headers.get('cache-control'), 'no-store');
|
||||
}
|
||||
}
|
||||
proxy.resetStats(); proxy.startTracking();
|
||||
const started = Date.now();
|
||||
// Use the old application's public functions; no simulated update helper.
|
||||
await page.evaluate(async () => {
|
||||
await checkBuildTag();
|
||||
await (await navigator.serviceWorker.getRegistration()).update();
|
||||
});
|
||||
await page.waitForFunction(async () => !!(await navigator.serviceWorker.getRegistration())?.waiting, { timeout: 90000 });
|
||||
await page.waitForFunction(() => /Update available/.test(document.querySelector('#modalTitle')?.textContent || '') && !document.querySelector('#modal').classList.contains('hidden'));
|
||||
assert.equal(banners, 1, 'exactly one offer before refresh');
|
||||
const before = navigations;
|
||||
await page.locator('#modal').getByRole('button', { name: 'Refresh UI', exact: true }).click();
|
||||
await page.waitForFunction(tag => document.querySelector('meta[name="ytp-build"]')?.content === tag && typeof checkBuildTag === 'function', manifest.buildTag, { timeout: 90000 });
|
||||
await page.waitForFunction(() => !sessionStorage.getItem('ytpUpdateAttempt'));
|
||||
await page.evaluate(async () => { await checkBuildTag(); await checkBuildTag(); await checkBuildTag(); });
|
||||
await sleep(2000);
|
||||
assert.equal(navigations - before, 1, 'one reload, no update loop');
|
||||
assert.equal(banners, 1, 'no banner after applying the current build');
|
||||
const cacheResult = await page.evaluate(async ({ tag, shell, files }) => {
|
||||
const cache = await caches.open(`ytplayer-${tag}`);
|
||||
const missing = [], incorrect = [];
|
||||
for (const path of shell) {
|
||||
const response = await cache.match(path);
|
||||
if (!response) { missing.push(path); continue; }
|
||||
if (response.headers.get('x-asset-hash') !== files[path === '/' ? '/index.html' : path]?.h) incorrect.push(path);
|
||||
}
|
||||
return { missing, incorrect, count: (await cache.keys()).length };
|
||||
}, { tag: manifest.buildTag, shell, files: manifest.files });
|
||||
assert.deepEqual(cacheResult.missing, []);
|
||||
assert.deepEqual(cacheResult.incorrect, [], 'every cached shell asset belongs to the new build');
|
||||
proxy.stopTracking();
|
||||
const stats = proxy.getStats();
|
||||
const durationMs = Date.now() - started;
|
||||
assert.deepEqual(errors, [], 'no script failures during installation and update');
|
||||
disconnected = true;
|
||||
// Match baseline.mjs: Linux WebKit's setOffline breaks even SW navigation.
|
||||
// Cut the proxy AND stop Bun so offline boot cannot use network bytes.
|
||||
if (name !== 'webkit') await context.setOffline(true);
|
||||
proxy.setOffline(true);
|
||||
await stop();
|
||||
await page.reload({ waitUntil: 'load', timeout: 60000 });
|
||||
assert.equal(await page.locator('meta[name="ytp-build"]').getAttribute('content'), manifest.buildTag);
|
||||
await page.waitForFunction(() => typeof checkBuildTag === 'function' && !!document.querySelector('#cards')?.dataset.view);
|
||||
assert.equal(banners, 1);
|
||||
// WebKit surfaces rejected SW network fetches as pageerrors even when the
|
||||
// app catches them. Check only the exact, intentional offline API failures;
|
||||
// syntax/reference/runtime exceptions must still fail this harness.
|
||||
for (const error of errors) {
|
||||
assert.equal(name, 'webkit', 'only WebKit has offline transport pageerrors');
|
||||
assert.equal(error.disconnected, true, 'transport errors only after disconnection');
|
||||
assert.ok(error.message === 'TypeError: Load failed' || /\/api\/(?:recommendations\?[^\s]*|version)\.$/.test(error.message), `unexpected offline error: ${error.message}`);
|
||||
}
|
||||
if (errors.length) assert.ok(failedRequests.some(url => /\/api\/(?:recommendations|version)/.test(url)), 'transport errors correspond to disconnected API requests');
|
||||
const result = { browser: name, oldTag, newTag: manifest.buildTag, banners, updateReloads: 1, cachedFiles: cacheResult.count, durationMs, wireBytes: stats.totalWireBytes, offline: true, legacyURLs: 21, profile: 'lossy', offlineTransportErrors: errors.length };
|
||||
console.log(JSON.stringify(result));
|
||||
return result;
|
||||
} finally {
|
||||
await browser?.close();
|
||||
if (proxy) { proxy.server.closeAllConnections(); await proxy.close(); }
|
||||
await stop();
|
||||
}
|
||||
}
|
||||
try {
|
||||
const results = [];
|
||||
for (const name of browserName === 'all' ? ['chromium', 'webkit'] : [browserName]) results.push(await run(name));
|
||||
writeFileSync(join(repo, 'perf/results/phase1-migration-2026-10-07.json'), JSON.stringify({ commit, results }, null, 2) + '\n');
|
||||
} finally { rmSync(scratch, { recursive: true, force: true }); }
|
||||
128
perf/results/phase1-cold-2026-10-07.json
Normal file
128
perf/results/phase1-cold-2026-10-07.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"date": "2026-10-07T14:58:19.058Z",
|
||||
"commit": "6cb00d5073931325b15ec5b773bf15dc2b53816e",
|
||||
"runsConfigured": 3,
|
||||
"browsers": {
|
||||
"chromium": {
|
||||
"lte": {
|
||||
"cold": {
|
||||
"requestCount": {
|
||||
"median": 168,
|
||||
"min": 168,
|
||||
"max": 168,
|
||||
"values": [
|
||||
168,
|
||||
168,
|
||||
168
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 1042903,
|
||||
"min": 1042903,
|
||||
"max": 1042903,
|
||||
"values": [
|
||||
1042903,
|
||||
1042903,
|
||||
1042903
|
||||
]
|
||||
},
|
||||
"fcp": {
|
||||
"median": 1576,
|
||||
"min": 1536,
|
||||
"max": 1604,
|
||||
"values": [
|
||||
1604,
|
||||
1536,
|
||||
1576
|
||||
]
|
||||
},
|
||||
"lcp": {
|
||||
"median": 1924,
|
||||
"min": 1892,
|
||||
"max": 1960,
|
||||
"values": [
|
||||
1960,
|
||||
1892,
|
||||
1924
|
||||
]
|
||||
},
|
||||
"domContentLoaded": {
|
||||
"median": 10331,
|
||||
"min": 10304,
|
||||
"max": 11004,
|
||||
"values": [
|
||||
11004,
|
||||
10304,
|
||||
10331
|
||||
]
|
||||
},
|
||||
"bootDone": {
|
||||
"median": 10466,
|
||||
"min": 10425,
|
||||
"max": 11184,
|
||||
"values": [
|
||||
11184,
|
||||
10425,
|
||||
10466
|
||||
]
|
||||
},
|
||||
"longTaskTotal": {
|
||||
"median": 450,
|
||||
"min": 379,
|
||||
"max": 481,
|
||||
"values": [
|
||||
481,
|
||||
379,
|
||||
450
|
||||
]
|
||||
},
|
||||
"swInstallDuration": {
|
||||
"median": 40643,
|
||||
"min": 40439,
|
||||
"max": 41259,
|
||||
"values": [
|
||||
41259,
|
||||
40439,
|
||||
40643
|
||||
]
|
||||
},
|
||||
"swInstallBytes": {
|
||||
"median": 1042903,
|
||||
"min": 1042903,
|
||||
"max": 1042903,
|
||||
"values": [
|
||||
1042903,
|
||||
1042903,
|
||||
1042903
|
||||
]
|
||||
},
|
||||
"storageEstimate": {
|
||||
"usage": 3490858,
|
||||
"quota": 5372199978
|
||||
},
|
||||
"mediaReady": {
|
||||
"median": null,
|
||||
"min": null,
|
||||
"max": null,
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"autoplayBlocked": true,
|
||||
"wireBytesByType": {
|
||||
"html": 28587,
|
||||
"js": 479281,
|
||||
"css": 130718,
|
||||
"fonts": 397072,
|
||||
"images": 6278,
|
||||
"media": 0,
|
||||
"api": 336,
|
||||
"other": 631
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
perf/results/phase1-migration-2026-10-07.json
Normal file
33
perf/results/phase1-migration-2026-10-07.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"commit": "b77938a",
|
||||
"results": [
|
||||
{
|
||||
"browser": "chromium",
|
||||
"oldTag": "1175f1a1d2c1",
|
||||
"newTag": "ee1f3de99a02",
|
||||
"banners": 1,
|
||||
"updateReloads": 1,
|
||||
"cachedFiles": 87,
|
||||
"durationMs": 19381,
|
||||
"wireBytes": 1191657,
|
||||
"offline": true,
|
||||
"legacyURLs": 21,
|
||||
"profile": "lossy",
|
||||
"offlineTransportErrors": 0
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"oldTag": "1175f1a1d2c1",
|
||||
"newTag": "ee1f3de99a02",
|
||||
"banners": 1,
|
||||
"updateReloads": 1,
|
||||
"cachedFiles": 87,
|
||||
"durationMs": 15257,
|
||||
"wireBytes": 668490,
|
||||
"offline": true,
|
||||
"legacyURLs": 21,
|
||||
"profile": "lossy",
|
||||
"offlineTransportErrors": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
296
perf/results/phase1-overlapped-2026-10-07.json
Normal file
296
perf/results/phase1-overlapped-2026-10-07.json
Normal file
@@ -0,0 +1,296 @@
|
||||
{
|
||||
"date": "2026-10-07T15:08:29.812Z",
|
||||
"commit": "d18d17cf29778f0f3afaccb9df308de0f3a1e46c",
|
||||
"runsConfigured": 1,
|
||||
"browsers": {
|
||||
"chromium": {
|
||||
"lte": {
|
||||
"cold": {
|
||||
"requestCount": {
|
||||
"median": 168,
|
||||
"min": 168,
|
||||
"max": 168,
|
||||
"values": [
|
||||
168
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 1042858,
|
||||
"min": 1042858,
|
||||
"max": 1042858,
|
||||
"values": [
|
||||
1042858
|
||||
]
|
||||
},
|
||||
"fcp": {
|
||||
"median": 1628,
|
||||
"min": 1628,
|
||||
"max": 1628,
|
||||
"values": [
|
||||
1628
|
||||
]
|
||||
},
|
||||
"lcp": {
|
||||
"median": 1976,
|
||||
"min": 1976,
|
||||
"max": 1976,
|
||||
"values": [
|
||||
1976
|
||||
]
|
||||
},
|
||||
"domContentLoaded": {
|
||||
"median": 11020,
|
||||
"min": 11020,
|
||||
"max": 11020,
|
||||
"values": [
|
||||
11020
|
||||
]
|
||||
},
|
||||
"bootDone": {
|
||||
"median": 11149,
|
||||
"min": 11149,
|
||||
"max": 11149,
|
||||
"values": [
|
||||
11149
|
||||
]
|
||||
},
|
||||
"longTaskTotal": {
|
||||
"median": 460,
|
||||
"min": 460,
|
||||
"max": 460,
|
||||
"values": [
|
||||
460
|
||||
]
|
||||
},
|
||||
"swInstallDuration": {
|
||||
"median": 41250,
|
||||
"min": 41250,
|
||||
"max": 41250,
|
||||
"values": [
|
||||
41250
|
||||
]
|
||||
},
|
||||
"swInstallBytes": {
|
||||
"median": 1042858,
|
||||
"min": 1042858,
|
||||
"max": 1042858,
|
||||
"values": [
|
||||
1042858
|
||||
]
|
||||
},
|
||||
"storageEstimate": {
|
||||
"usage": 3490858,
|
||||
"quota": 5372199978
|
||||
},
|
||||
"mediaReady": {
|
||||
"median": null,
|
||||
"min": null,
|
||||
"max": null,
|
||||
"values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"autoplayBlocked": true,
|
||||
"wireBytesByType": {
|
||||
"html": 28542,
|
||||
"js": 479281,
|
||||
"css": 130718,
|
||||
"fonts": 397072,
|
||||
"images": 6278,
|
||||
"media": 0,
|
||||
"api": 336,
|
||||
"other": 631
|
||||
}
|
||||
},
|
||||
"warm": {
|
||||
"requestCount": {
|
||||
"median": 5,
|
||||
"min": 5,
|
||||
"max": 5,
|
||||
"values": [
|
||||
5
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 10581,
|
||||
"min": 10581,
|
||||
"max": 10581,
|
||||
"values": [
|
||||
10581
|
||||
]
|
||||
},
|
||||
"fcp": {
|
||||
"median": 280,
|
||||
"min": 280,
|
||||
"max": 280,
|
||||
"values": [
|
||||
280
|
||||
]
|
||||
},
|
||||
"lcp": {
|
||||
"median": 992,
|
||||
"min": 992,
|
||||
"max": 992,
|
||||
"values": [
|
||||
992
|
||||
]
|
||||
},
|
||||
"domContentLoaded": {
|
||||
"median": 787,
|
||||
"min": 787,
|
||||
"max": 787,
|
||||
"values": [
|
||||
787
|
||||
]
|
||||
},
|
||||
"bootDone": {
|
||||
"median": 974,
|
||||
"min": 974,
|
||||
"max": 974,
|
||||
"values": [
|
||||
974
|
||||
]
|
||||
},
|
||||
"longTaskTotal": {
|
||||
"median": 287,
|
||||
"min": 287,
|
||||
"max": 287,
|
||||
"values": [
|
||||
287
|
||||
]
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
"requestCount": {
|
||||
"median": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"values": [
|
||||
0
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"values": [
|
||||
0
|
||||
]
|
||||
},
|
||||
"fcp": {
|
||||
"median": 100,
|
||||
"min": 100,
|
||||
"max": 100,
|
||||
"values": [
|
||||
100
|
||||
]
|
||||
},
|
||||
"lcp": {
|
||||
"median": 100,
|
||||
"min": 100,
|
||||
"max": 100,
|
||||
"values": [
|
||||
100
|
||||
]
|
||||
},
|
||||
"domContentLoaded": {
|
||||
"median": 296,
|
||||
"min": 296,
|
||||
"max": 296,
|
||||
"values": [
|
||||
296
|
||||
]
|
||||
},
|
||||
"bootDone": {
|
||||
"median": 327,
|
||||
"min": 327,
|
||||
"max": 327,
|
||||
"values": [
|
||||
327
|
||||
]
|
||||
},
|
||||
"reloadDuration": {
|
||||
"median": 349,
|
||||
"min": 349,
|
||||
"max": 349,
|
||||
"values": [
|
||||
349
|
||||
]
|
||||
}
|
||||
},
|
||||
"updateJs": {
|
||||
"requestCount": {
|
||||
"median": 90,
|
||||
"min": 90,
|
||||
"max": 90,
|
||||
"values": [
|
||||
90
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 613029,
|
||||
"min": 613029,
|
||||
"max": 613029,
|
||||
"values": [
|
||||
613029
|
||||
]
|
||||
},
|
||||
"duration": {
|
||||
"median": 2088,
|
||||
"min": 2088,
|
||||
"max": 2088,
|
||||
"values": [
|
||||
2088
|
||||
]
|
||||
},
|
||||
"wireBytesByType": {
|
||||
"html": 18996,
|
||||
"js": 267655,
|
||||
"css": 65359,
|
||||
"fonts": 254048,
|
||||
"images": 6278,
|
||||
"media": 0,
|
||||
"api": 62,
|
||||
"other": 631
|
||||
}
|
||||
},
|
||||
"updateCss": {
|
||||
"requestCount": {
|
||||
"median": 90,
|
||||
"min": 90,
|
||||
"max": 90,
|
||||
"values": [
|
||||
90
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 613046,
|
||||
"min": 613046,
|
||||
"max": 613046,
|
||||
"values": [
|
||||
613046
|
||||
]
|
||||
},
|
||||
"duration": {
|
||||
"median": 1997,
|
||||
"min": 1997,
|
||||
"max": 1997,
|
||||
"values": [
|
||||
1997
|
||||
]
|
||||
},
|
||||
"wireBytesByType": {
|
||||
"html": 18996,
|
||||
"js": 267644,
|
||||
"css": 65387,
|
||||
"fonts": 254048,
|
||||
"images": 6278,
|
||||
"media": 0,
|
||||
"api": 62,
|
||||
"other": 631
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
296
perf/results/phase1-reduced-2026-10-07.json
Normal file
296
perf/results/phase1-reduced-2026-10-07.json
Normal file
@@ -0,0 +1,296 @@
|
||||
{
|
||||
"date": "2026-10-07T15:10:34.781Z",
|
||||
"commit": "84546eeb7da50fdfcfadb5368cbdf9054b014b9e",
|
||||
"runsConfigured": 1,
|
||||
"browsers": {
|
||||
"chromium": {
|
||||
"lte": {
|
||||
"cold": {
|
||||
"requestCount": {
|
||||
"median": 168,
|
||||
"min": 168,
|
||||
"max": 168,
|
||||
"values": [
|
||||
168
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 1042858,
|
||||
"min": 1042858,
|
||||
"max": 1042858,
|
||||
"values": [
|
||||
1042858
|
||||
]
|
||||
},
|
||||
"fcp": {
|
||||
"median": 1564,
|
||||
"min": 1564,
|
||||
"max": 1564,
|
||||
"values": [
|
||||
1564
|
||||
]
|
||||
},
|
||||
"lcp": {
|
||||
"median": 1920,
|
||||
"min": 1920,
|
||||
"max": 1920,
|
||||
"values": [
|
||||
1920
|
||||
]
|
||||
},
|
||||
"domContentLoaded": {
|
||||
"median": 11001,
|
||||
"min": 11001,
|
||||
"max": 11001,
|
||||
"values": [
|
||||
11001
|
||||
]
|
||||
},
|
||||
"bootDone": {
|
||||
"median": 11137,
|
||||
"min": 11137,
|
||||
"max": 11137,
|
||||
"values": [
|
||||
11137
|
||||
]
|
||||
},
|
||||
"longTaskTotal": {
|
||||
"median": 430,
|
||||
"min": 430,
|
||||
"max": 430,
|
||||
"values": [
|
||||
430
|
||||
]
|
||||
},
|
||||
"swInstallDuration": {
|
||||
"median": 41164,
|
||||
"min": 41164,
|
||||
"max": 41164,
|
||||
"values": [
|
||||
41164
|
||||
]
|
||||
},
|
||||
"swInstallBytes": {
|
||||
"median": 1042858,
|
||||
"min": 1042858,
|
||||
"max": 1042858,
|
||||
"values": [
|
||||
1042858
|
||||
]
|
||||
},
|
||||
"storageEstimate": {
|
||||
"usage": 3490858,
|
||||
"quota": 5372199978
|
||||
},
|
||||
"mediaReady": {
|
||||
"median": null,
|
||||
"min": null,
|
||||
"max": null,
|
||||
"values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"autoplayBlocked": true,
|
||||
"wireBytesByType": {
|
||||
"html": 28542,
|
||||
"js": 479281,
|
||||
"css": 130718,
|
||||
"fonts": 397072,
|
||||
"images": 6278,
|
||||
"media": 0,
|
||||
"api": 336,
|
||||
"other": 631
|
||||
}
|
||||
},
|
||||
"warm": {
|
||||
"requestCount": {
|
||||
"median": 5,
|
||||
"min": 5,
|
||||
"max": 5,
|
||||
"values": [
|
||||
5
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 10581,
|
||||
"min": 10581,
|
||||
"max": 10581,
|
||||
"values": [
|
||||
10581
|
||||
]
|
||||
},
|
||||
"fcp": {
|
||||
"median": 172,
|
||||
"min": 172,
|
||||
"max": 172,
|
||||
"values": [
|
||||
172
|
||||
]
|
||||
},
|
||||
"lcp": {
|
||||
"median": 648,
|
||||
"min": 648,
|
||||
"max": 648,
|
||||
"values": [
|
||||
648
|
||||
]
|
||||
},
|
||||
"domContentLoaded": {
|
||||
"median": 531,
|
||||
"min": 531,
|
||||
"max": 531,
|
||||
"values": [
|
||||
531
|
||||
]
|
||||
},
|
||||
"bootDone": {
|
||||
"median": 641,
|
||||
"min": 641,
|
||||
"max": 641,
|
||||
"values": [
|
||||
641
|
||||
]
|
||||
},
|
||||
"longTaskTotal": {
|
||||
"median": 148,
|
||||
"min": 148,
|
||||
"max": 148,
|
||||
"values": [
|
||||
148
|
||||
]
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
"requestCount": {
|
||||
"median": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"values": [
|
||||
0
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 0,
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"values": [
|
||||
0
|
||||
]
|
||||
},
|
||||
"fcp": {
|
||||
"median": 80,
|
||||
"min": 80,
|
||||
"max": 80,
|
||||
"values": [
|
||||
80
|
||||
]
|
||||
},
|
||||
"lcp": {
|
||||
"median": 240,
|
||||
"min": 240,
|
||||
"max": 240,
|
||||
"values": [
|
||||
240
|
||||
]
|
||||
},
|
||||
"domContentLoaded": {
|
||||
"median": 211,
|
||||
"min": 211,
|
||||
"max": 211,
|
||||
"values": [
|
||||
211
|
||||
]
|
||||
},
|
||||
"bootDone": {
|
||||
"median": 232,
|
||||
"min": 232,
|
||||
"max": 232,
|
||||
"values": [
|
||||
232
|
||||
]
|
||||
},
|
||||
"reloadDuration": {
|
||||
"median": 254,
|
||||
"min": 254,
|
||||
"max": 254,
|
||||
"values": [
|
||||
254
|
||||
]
|
||||
}
|
||||
},
|
||||
"updateJs": {
|
||||
"requestCount": {
|
||||
"median": 90,
|
||||
"min": 90,
|
||||
"max": 90,
|
||||
"values": [
|
||||
90
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 613029,
|
||||
"min": 613029,
|
||||
"max": 613029,
|
||||
"values": [
|
||||
613029
|
||||
]
|
||||
},
|
||||
"duration": {
|
||||
"median": 1891,
|
||||
"min": 1891,
|
||||
"max": 1891,
|
||||
"values": [
|
||||
1891
|
||||
]
|
||||
},
|
||||
"wireBytesByType": {
|
||||
"html": 18996,
|
||||
"js": 267655,
|
||||
"css": 65359,
|
||||
"fonts": 254048,
|
||||
"images": 6278,
|
||||
"media": 0,
|
||||
"api": 62,
|
||||
"other": 631
|
||||
}
|
||||
},
|
||||
"updateCss": {
|
||||
"requestCount": {
|
||||
"median": 88,
|
||||
"min": 88,
|
||||
"max": 88,
|
||||
"values": [
|
||||
88
|
||||
]
|
||||
},
|
||||
"wireBytes": {
|
||||
"median": 607743,
|
||||
"min": 607743,
|
||||
"max": 607743,
|
||||
"values": [
|
||||
607743
|
||||
]
|
||||
},
|
||||
"duration": {
|
||||
"median": 1890,
|
||||
"min": 1890,
|
||||
"max": 1890,
|
||||
"values": [
|
||||
1890
|
||||
]
|
||||
},
|
||||
"wireBytesByType": {
|
||||
"html": 18996,
|
||||
"js": 262403,
|
||||
"css": 65387,
|
||||
"fonts": 254048,
|
||||
"images": 6278,
|
||||
"media": 0,
|
||||
"api": 0,
|
||||
"other": 631
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user