diff options
Diffstat (limited to 'prototypes/workbench/profile-refresh.js')
| -rw-r--r-- | prototypes/workbench/profile-refresh.js | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/prototypes/workbench/profile-refresh.js b/prototypes/workbench/profile-refresh.js new file mode 100644 index 0000000..75c086e --- /dev/null +++ b/prototypes/workbench/profile-refresh.js @@ -0,0 +1,66 @@ +// Playwright CLI function: one diagnostic run, no implementation changes. +async (page) => { + const onDialog = dialog => dialog.accept(); + page.on('dialog', onDialog); + const routeApp = async route => { + const response = await route.fetch(); + let body = await response.text(); + const measured = (key, expression) => `measure('${key}', () => ${expression})`; + body = body.replace("const output = createOutput(document);", `const output = createOutput(document); + function measure(name, action) { + const start = performance.now(); + try { return action(); } finally { + const all = globalThis.__phase ??= {}; + const item = all[name] ??= {calls:0, ms:0}; + item.calls++; item.ms += performance.now() - start; + } + }`); + body = body.replace('function refresh() {', "function refresh() { return measure('refresh', refreshBody); }\nfunction refreshBody() {"); + body = body.replace('function draftChanged() {', "function draftChanged() { return measure('draft', draftBody); }\nfunction draftBody() {"); + body = body.replace('runtime.present(output)', measured('present', 'runtime.present(output)')); + body = body.replace('output.costs(snapshot, runtime.currentProgram?.metrics, droppedTicks)', + measured('costs', 'output.costs(snapshot, runtime.currentProgram?.metrics, droppedTicks)')); + await route.fulfill({response, body}); + }; + const client = await page.context().newCDPSession(page); + const metrics = async () => Object.fromEntries((await client.send('Performance.getMetrics')).metrics.map(m => [m.name, m.value])); + const source = ['state x = 40', 'input move = KeyM', + 'box moving(x, 100, 30, 30, "#7ed9ae")', 'text label(20, 40, x)', + ...Array.from({length:22}, (_, i) => `box static${i}(${20 + (i % 7) * 70}, ${200 + Math.floor(i / 7) * 30}, 20, 20, "#8da5bb")`), + 'on move.down { x = x + 0.25 }'].join('\n'); + try { + await page.route('**/app.js', routeApp); + await page.reload(); await page.waitForFunction(() => window.pureWorkbench?.snapshot().ready); + await page.locator('#source').fill(source); await page.locator('#apply').click(); + await page.locator('#scene').focus(); + await page.evaluate(() => { + window.__tap = () => { + const scene = document.getElementById('scene'); + scene.dispatchEvent(new KeyboardEvent('keydown', {code:'KeyM', bubbles:true})); + scene.dispatchEvent(new KeyboardEvent('keyup', {code:'KeyM', bubbles:true})); + }; + for (let i = 0; i < 20; i++) window.__tap(); + }); + await client.send('Performance.enable'); + const before = await metrics(); + const run = await page.evaluate(() => { + window.__phase = {}; + const start = performance.now(); + for (let i = 0; i < 100; i++) window.__tap(); + return {elapsedMs:performance.now()-start, phase:window.__phase}; + }); + const after = await metrics(); + const delta = Object.fromEntries(['LayoutCount','LayoutDuration','RecalcStyleCount','RecalcStyleDuration','ScriptDuration'] + .map(key => [key, after[key]-before[key]])); + const evidence = {utc:new Date().toISOString(), taps:100, ...run, metrics:delta, source, + scope:'Phase timings overlap: draft/present/costs are inside refresh. CDP counters bracket the synchronous batch and may include surrounding browser work.'}; + await page.evaluate(value => {window.__pureRefreshProfile = value;}, evidence); + return evidence; + } catch (error) { + const evidence = {failed:true, message:error.stack}; + await page.evaluate(value => {window.__pureRefreshProfile = value;}, evidence); + return evidence; + } finally { + await page.unroute('**/app.js', routeApp); page.off('dialog', onDialog); await client.detach(); + } +} |
