CodeNomad/scripts/profile-streaming-responsiveness.mjs
Pascal André 6f6edbbcb9
perf(ui): skip redundant outline and superseded Markdown work (#852)
## Summary

I reduced two independently demonstrated sources of redundant work in
the UI shared by Electron and Tauri:

- I reuse the timeline projection when the outline snapshot, resident
marker identities and translated labels have not changed, preserving
reactive locale updates and structural marker changes.
- I discard superseded or disposed Markdown invocations before parsing
and before publishing results, including same-key language retries and
cache hits.

Related to #851, **not a confirmed fix for its Linux Electron freeze**.
I have not reproduced the reporter's environment, and this PR
intentionally does not close the issue. Native events, transcript
authority and OpenCode service lifecycle are unchanged.

## Evidence and validation

- Real native-dispatcher/SessionView fixture with 10k outline entries:
64 sub-bucket deltas trigger **64 full-history projections before / zero
after**; changed text buckets, locale and final text still update.
- Real Markdown/parser instrumentation: cold burst **33 parses before /
one after**, warm burst **32 before / one after**. Disposal, same-key
retries, cached results, theme/highlight, wrap and full-source copy are
covered.
- 100 browser regressions passed (70 renderer/parser/outline + 6
full-source-copy + 24 history-navigation/reasoning), plus 3 outline unit
tests; UI typecheck passed.
- Independent gatekeeper: **zero actionable findings**, with 60 browser
and 6 unit tests passed independently.
- Windows Tauri `build --no-bundle`, packaged-resource smoke and
isolated launch/capture/close passed. I did not replace the installed
app or restart OpenCode. No Electron desktop build/test was run;
Chromium fixture tests do not establish Linux Electron host behavior.
- The initial concurrent history/reasoning regression run hit a shared
Vite cache `EPERM` on Windows and timed out; all 24 affected checks
passed when rerun sequentially.

The broad synthetic stream profile still shows pauses and does **not**
demonstrate an overall responsiveness win. I recorded the measured work
reductions and remaining limits in
`dev-docs/STREAMING_RESPONSIVENESS.md`, with reproducible profiling
scripts.
2026-10-05 22:32:03 +02:00

89 lines
5.4 KiB
JavaScript

// Synthetic real-renderer workload; not a reproduction of the reporter's host.
import { mkdir, mkdtemp, writeFile } from "node:fs/promises"
import path from "node:path"
import os from "node:os"
import { fileURLToPath } from "node:url"
import { chromium } from "playwright"
import { createServer } from "vite"
import solid from "vite-plugin-solid"
const repo = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
const args = Object.fromEntries(process.argv.slice(2).map(arg => arg.replace(/^--/, "").split("=")))
const chunkSize = Number(args.chunk ?? 1024), deliveriesCount = Number(args.deliveries ?? 96)
const temporaryParent = path.join(os.tmpdir(), "opencode")
await mkdir(temporaryParent, { recursive: true })
const evidence = await mkdtemp(path.join(temporaryParent, "issue851-profile-"))
const server = await createServer({
configFile: false, root: path.join(repo, "packages/ui"), logLevel: "error",
cacheDir: "node_modules/.vite-streaming-profile", plugins: [solid(), {
name: "streaming-profile", configureServer(server) {
server.middlewares.use("/fixture", async (_req, res) => {
res.setHeader("Content-Type", "text/html")
res.end(await server.transformIndexHtml("/fixture", '<html><body><div id="root" style="display:flex;height:850px;width:1200px"></div><script type="module" src="/tests/browser/fixtures/streaming-motion.tsx"></script></body></html>'))
})
},
}], resolve: { dedupe: ["solid-js"] }, optimizeDeps: { exclude: ["lucide-solid"] },
server: { host: "127.0.0.1", port: 0, hmr: false, watch: null },
})
let browser
const results = []
try {
await server.listen()
browser = await chromium.launch({ executablePath: process.env.CODENOMAD_BROWSER_PATH || undefined })
const url = `http://127.0.0.1:${server.httpServer.address().port}/fixture`
for (const kind of args.kind ? [args.kind] : ["prose", "code", "burst"]) {
const page = await browser.newPage({ viewport: { width: 1200, height: 850 } })
const errors = []
page.on("pageerror", error => errors.push(error.message))
page.setDefaultTimeout(60000)
await page.route("**/api/**", route => route.fulfill({ contentType: route.request().url().includes("events") ? "text/event-stream" : "application/json", body: "" }))
await page.goto(url, { waitUntil: "domcontentloaded" })
await page.waitForFunction(() => Boolean(window.motionFixture))
await page.evaluate(async () => { await window.motionFixture.seed(); window.motionFixture.start(); window.motionFixture.text() })
await page.waitForTimeout(300)
const cdp = await page.context().newCDPSession(page)
await cdp.send("Profiler.enable")
await cdp.send("Profiler.start")
const measurements = await page.evaluate(async ({ kind, chunkSize, deliveriesCount }) => {
const tasks = [], gaps = [], deliveries = []
const observer = new PerformanceObserver(list => { for (const entry of list.getEntries()) tasks.push(entry.duration) })
observer.observe({ type: "longtask", buffered: false })
let previous = performance.now()
const timer = setInterval(() => { const now = performance.now(); gaps.push(now - previous); previous = now }, 10)
let text = kind === "prose" ? "" : "```typescript\n"
const line = kind === "prose" ? "A streamed paragraph with **important details** and a [reference](https://example.com).\n\n" : 'export const example = { name: "sample", enabled: true, count: 123 };\n'
const chunk = line.repeat(Math.ceil(chunkSize / line.length))
const started = performance.now()
for (let i = 0; i < deliveriesCount; i++) {
const delta = (i === 0 ? text : "") + chunk
text += chunk
const before = performance.now()
window.motionFixture.delta(delta)
deliveries.push(performance.now() - before)
if (kind !== "burst") await new Promise(resolve => setTimeout(resolve, 20))
else if (i % 16 === 15) await new Promise(resolve => setTimeout(resolve, 0))
}
await new Promise(resolve => setTimeout(resolve, 250))
const streamingMs = performance.now() - started
window.motionFixture.finish(text)
await new Promise(resolve => setTimeout(resolve, 250))
clearInterval(timer)
observer.disconnect()
return { characters: text.length, streamingMs, longestDeliveryMs: Math.max(...deliveries), longTasks: tasks.length,
totalLongTaskMs: tasks.reduce((sum, value) => sum + value, 0), longestTaskMs: Math.max(0, ...tasks),
longestHeartbeatGapMs: Math.max(0, ...gaps),
mountedRows: document.querySelectorAll("[data-virtual-follow-key]").length }
}, { kind, chunkSize, deliveriesCount })
const { profile } = await cdp.send("Profiler.stop")
const counts = new Map()
for (const id of profile.samples ?? []) counts.set(id, (counts.get(id) ?? 0) + 1)
const top = profile.nodes.map(node => ({ function: node.callFrame.functionName, url: node.callFrame.url.replace(url, "fixture"),
line: node.callFrame.lineNumber + 1, samples: counts.get(node.id) ?? 0 })).sort((a, b) => b.samples - a.samples).slice(0, 20)
await writeFile(path.join(evidence, `${kind}.cpuprofile`), JSON.stringify(profile))
await page.screenshot({ path: path.join(evidence, `${kind}.png`) })
results.push({ kind, ...measurements, errors, top })
await page.close()
}
await writeFile(path.join(evidence, "results.json"), JSON.stringify(results, null, 2))
console.log(JSON.stringify({ evidence, results }, null, 2))
} finally { await browser?.close(); await server.close() }