| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- import type { CDPSession, Page } from "@playwright/test"
- import path from "node:path"
- import { mkdir, open, rename } from "node:fs/promises"
- import { Buffer } from "node:buffer"
- import { createHash, randomUUID } from "node:crypto"
- const categories = [
- "-*",
- "devtools.timeline",
- "v8.execute",
- "disabled-by-default-devtools.timeline",
- "disabled-by-default-devtools.timeline.frame",
- "toplevel",
- "blink.console",
- "blink.user_timing",
- "latencyInfo",
- "disabled-by-default-devtools.timeline.stack",
- "disabled-by-default-v8.cpu_profiler",
- ]
- export async function startChromeTrace(page: Page, name: string) {
- const directory = process.env.OPENCODE_PERFORMANCE_TRACE_DIR
- if (!directory) return
- const selectors = process.env.OPENCODE_PERFORMANCE_SELECTOR_TRACE === "1"
- const file = await prepareChromeTrace(directory, name, selectors)
- const session = await page.context().newCDPSession(page)
- try {
- await session.send("Tracing.start", {
- transferMode: "ReturnAsStream",
- traceConfig: {
- excludedCategories: categories
- .filter((category) => category.startsWith("-"))
- .map((category) => category.slice(1)),
- includedCategories: [
- ...categories.filter((category) => !category.startsWith("-")),
- ...(selectors
- ? ["disabled-by-default-blink.debug", "disabled-by-default-devtools.timeline.invalidationTracking"]
- : []),
- ],
- },
- })
- } catch (error) {
- await Promise.allSettled([session.detach()])
- throw error
- }
- let stopping: Promise<string> | undefined
- return () =>
- (stopping ??= (async () => {
- try {
- const complete = new Promise<{ stream?: string; dataLossOccurred: boolean }>((resolve) =>
- session.once("Tracing.tracingComplete", resolve),
- )
- await session.send("Tracing.end")
- const result = await complete
- if (!result.stream) throw new Error(`Chrome trace stream missing: ${file}`)
- const partial = `${file}.partial`
- await writeProtocolStream(session, result.stream, partial)
- if (result.dataLossOccurred) throw new Error(`Chrome trace lost data; partial capture retained: ${partial}`)
- await rename(partial, file)
- return file
- } finally {
- await Promise.allSettled([session.detach()])
- }
- })())
- }
- export async function prepareChromeTrace(
- directory: string,
- name: string,
- selectors: boolean,
- nonce = randomUUID().slice(0, 8),
- ) {
- await mkdir(directory, { recursive: true })
- const run = process.env.OPENCODE_PERFORMANCE_RUN_ID ?? "manual"
- const hash = createHash("sha256").update(name).digest("hex").slice(0, 8)
- return path.join(
- directory,
- `${run}-${name.replace(/[^a-zA-Z0-9_-]/g, "-")}-${hash}-${nonce}${selectors ? "-selectors" : ""}.json`,
- )
- }
- async function writeProtocolStream(session: CDPSession, handle: string, file: string) {
- const output = await open(file, "wx")
- try {
- while (true) {
- const chunk = await session.send("IO.read", { handle })
- await output.write(chunk.base64Encoded ? Buffer.from(chunk.data, "base64") : chunk.data)
- if (chunk.eof) break
- }
- } finally {
- await Promise.allSettled([output.close(), session.send("IO.close", { handle })])
- }
- }
|