Ctrl K
ComunidadGuías…

packages/app/perf/stream.bench.ts

Rama dev · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// A reply streaming into a long session: 160 markdown deltas (bold text, tables, code fences) at 40 a second, from a
// server that never waits for the renderer. Per-delta cost that grows with the reply or the history shows up here.
import { bench, web, type Case, type Page } from "@hona/strobe"
import { reviewDiffs } from "./fixtures/diffs"
import { cached } from "./fixtures/mock"
import { openReview } from "./fixtures/review"
import { mockTimelineSession, reply, streamedReply, timeline } from "./fixtures/timeline"

const { start, deltas } = streamedReply(160)

const withFind = "stream with Find open"

const find = '[data-component="timeline-search-bar"] input'

// A phrase only the reply's last delta holds, so Find stays on the reply while every delta rescans the transcript.
// One input event: typed key by key, its prefixes would match the history and seek the timeline away from the reply.
async function openFind(page: Page) {
  await page.press("ControlOrMeta+f", { until: find })
  await page.eval(() => {
    const input = document.querySelector<HTMLInputElement>('[data-component="timeline-search-bar"] input')!
    input.value = "benchmark-complete"
    input.dispatchEvent(new InputEvent("input", { bubbles: true }))
  })
  await page.waitFor('[data-slot="timeline-search-count"]', { text: "0/0" })
}

const stream: Case<{}> = {
  primary: "main.work",
  metrics: ["frames.interval.p95", "frames.dropped.pct", "main.script", "main.layout", "main.style"],
  async run({ page, prepared }) {
    prepared.source.send(start)
    await Promise.all([
      prepared.source.stream(deltas, 25),
      page.waitFor(reply, { text: "benchmark-complete", timeout: 60_000 }),
    ])
  },
  // A fresh document without the streamed reply.
  async reset({ case: name, page, prepared }) {
    await page.goto(prepared.path, { until: timeline, timeout: 60_000 })

    if (name === "stream with review open") await openReview(page)

    if (name === withFind) await openFind(page)
    await page.idle()
  },
}

/** What a case's launch sets up beyond the 120-turn session: cases with the same one share a launch. */
const setups = new Map<string, "tool-heavy" | "review" | "find" | "wait">([
  ["stream into a tool-heavy history", "tool-heavy"],
  ["stream with review open", "review"],
  [withFind, "find"],
  ["wait on a running session", "wait"],
])

export default bench({
  setupKey: (ctx) => setups.get(ctx.case) ?? "plain",

  targets: {
    web: web({
      expect: { title: "OpenCode" },
      viewport: { width: 1366, height: 768 },
      prepare: (ctx) =>
        mockTimelineSession(cached(ctx), {
          server: new URL(ctx.url).origin,
          historyTurns: ctx.setupKey === "tool-heavy" ? 320 : 120,
          toolHeavy: ctx.setupKey === "tool-heavy",
          busy: true,
          stream: true,
          vcsDiff: ctx.setupKey === "review" ? reviewDiffs() : undefined,
        }),
      url: ({ prepared }) => prepared.path,
    }),
  },

  async setup({ setupKey, page }) {
    await page.waitFor(timeline, { timeout: 60_000 })

    if (setupKey === "review") await openReview(page)

    if (setupKey === "find") await openFind(page)

    // Its animations never stop, and with the attribution probes on a slow machine their frames can run long enough
    // that the page never counts as idle.
    if (setupKey === "wait") return page.wait(2000)
    await page.idle()
  },

  teardown: ({ prepared }) => prepared.source?.stop(),

  cases: {
    // 120 turns: 242 messages, 248 KB of history.
    "stream a reply": stream,
    // 320 turns with six file reads in every answer: 2,392 parts, 2.6 MB of history.
    "stream into a tool-heavy history": stream,
    // The review pane open beside the timeline on 72 changed files.
    "stream with review open": stream,
    // Find open on a phrase that only the last delta holds: each delta rescans the 120-turn transcript for it.
    [withFind]: stream,
    // Five seconds of a running session between deltas: the tab's spinner and the Working shimmer animate, and nothing
    // else changes. Animation the compositor runs costs the page's main thread nothing.
    "wait on a running session": {
      primary: "main.work",
      metrics: ["main.style", "cpu.renderer", "cpu.gpu", "frames.presented.count"],
      run: ({ page }) => page.wait(5000),
    },
  },
})