aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/workbench/profile-refresh.js
blob: 75c086e51a3444f5276744d359a90d6bd082d03d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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();
  }
}