Files
stablyai--orca/config/scripts/pty-batch-flush-benchmark.mjs
2026-07-13 13:05:33 +08:00

475 lines
15 KiB
JavaScript

/* eslint-disable no-control-regex -- Benchmarks mirror PTY ANSI/OSC parsing regexes. */
import { performance } from 'node:perf_hooks'
import v8 from 'node:v8'
const PTY_COUNT = Number.parseInt(process.env.ORCA_PTY_BENCH_PTY_COUNT ?? '24', 10)
const PAYLOAD_CHARS = Number.parseInt(process.env.ORCA_PTY_BENCH_PAYLOAD_CHARS ?? '262144', 10)
const RUNS = Number.parseInt(process.env.ORCA_PTY_BENCH_RUNS ?? '30', 10)
const MEASURE_TIMER_DELAYS = process.env.ORCA_PTY_BENCH_MEASURE_TIMER_DELAYS !== '0'
const INGRESS_CHUNKS = Number.parseInt(process.env.ORCA_PTY_BENCH_INGRESS_CHUNKS ?? '96', 10)
const INGRESS_CHUNK_CHARS = Number.parseInt(process.env.ORCA_PTY_BENCH_INGRESS_CHARS ?? '65536', 10)
const CHUNK_CHARS = 16 * 1024
const MAX_WRITES_PER_SLICE = 2
const RECENT_PTY_OUTPUT_LIMIT = 4096
const MAX_TAIL_LINES = 2000
const MAX_TAIL_CHARS = 256 * 1024
const MAX_TAIL_PARTIAL_CHARS = 4000
const OSC_TITLE_RE = /\x1b\]([012]);([^\x07\x1b]*?)(?:\x07|\x1b\\)/g
const URL_CANDIDATE_PATTERN = /\bhttps?:\/\/[^\s<>"'`]+/gi
if (!Number.isInteger(PTY_COUNT) || PTY_COUNT <= 0) {
throw new Error(`ORCA_PTY_BENCH_PTY_COUNT must be positive, received ${PTY_COUNT}`)
}
if (!Number.isInteger(PAYLOAD_CHARS) || PAYLOAD_CHARS <= 0) {
throw new Error(`ORCA_PTY_BENCH_PAYLOAD_CHARS must be positive, received ${PAYLOAD_CHARS}`)
}
if (!Number.isInteger(RUNS) || RUNS <= 0) {
throw new Error(`ORCA_PTY_BENCH_RUNS must be positive, received ${RUNS}`)
}
if (!Number.isInteger(INGRESS_CHUNKS) || INGRESS_CHUNKS <= 0) {
throw new Error(`ORCA_PTY_BENCH_INGRESS_CHUNKS must be positive, received ${INGRESS_CHUNKS}`)
}
if (!Number.isInteger(INGRESS_CHUNK_CHARS) || INGRESS_CHUNK_CHARS <= 0) {
throw new Error(`ORCA_PTY_BENCH_INGRESS_CHARS must be positive, received ${INGRESS_CHUNK_CHARS}`)
}
function makePendingData() {
const pending = new Map()
for (let index = 0; index < PTY_COUNT; index++) {
pending.set(`pty-${index}`, `${index}:`.padEnd(PAYLOAD_CHARS, 'x'))
}
return pending
}
function simulateWebContentsSend(id, data) {
return v8.serialize({ channel: 'pty:data', payload: { id, data } }).byteLength
}
function flushLegacy(pending) {
let bytes = 0
const start = performance.now()
for (const [id, data] of pending) {
bytes += simulateWebContentsSend(id, data)
}
pending.clear()
return { bytes, durationMs: performance.now() - start }
}
function flushBoundedSlice(pending) {
let bytes = 0
let writes = 0
const start = performance.now()
while (pending.size > 0 && writes < MAX_WRITES_PER_SLICE) {
const next = pending.entries().next().value
if (!next) {
break
}
const [id, data] = next
pending.delete(id)
const chunk = data.slice(0, CHUNK_CHARS)
const remaining = data.slice(CHUNK_CHARS)
if (remaining) {
pending.set(id, remaining)
}
bytes += simulateWebContentsSend(id, chunk)
writes++
}
return { bytes, durationMs: performance.now() - start }
}
function drainBounded(pending) {
let bytes = 0
const sliceDurations = []
while (pending.size > 0) {
const result = flushBoundedSlice(pending)
bytes += result.bytes
sliceDurations.push(result.durationMs)
}
return { bytes, sliceDurations }
}
function makeIngressChunk() {
return `${'x'.repeat(INGRESS_CHUNK_CHARS - 1)}\n`
}
function extractLastOscTitleLegacy(data) {
let last = null
for (const m of data.matchAll(OSC_TITLE_RE)) {
last = m[2]
}
return last
}
function extractLastOscTitleCurrent(data) {
if (!data.includes('\x1b]')) {
return null
}
return extractLastOscTitleLegacy(data)
}
function hasMeaningfulContentLegacy(chunk) {
return (
chunk
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
.replace(/\x1b[@-_]/g, '')
.replace(/\u0008/g, '')
.replace(/[^\x09\x0a\x20-\x7e]/g, '')
.trim().length > 0
)
}
function hasMeaningfulContentCurrent(chunk) {
for (let index = 0; index < chunk.length; index++) {
const code = chunk.charCodeAt(index)
if (code === 0x1b || code < 0x09 || (code > 0x0d && code < 0x20) || code > 0x7e) {
break
}
if (code > 0x20) {
return true
}
}
return hasMeaningfulContentLegacy(chunk)
}
function normalizeTerminalChunkLegacy(chunk) {
return chunk
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
.replace(/\x1b[@-_]/g, '')
.replace(/\u0008/g, '')
.replace(/[^\x09\x0a\x20-\x7e]/g, '')
}
function terminalChunkNeedsNormalization(chunk) {
for (let index = 0; index < chunk.length; index++) {
const code = chunk.charCodeAt(index)
if (
code === 0x1b ||
code === 0x0d ||
code < 0x09 ||
(code > 0x0a && code < 0x20) ||
code > 0x7e
) {
return true
}
}
return false
}
function normalizeTerminalChunkCurrent(chunk) {
return terminalChunkNeedsNormalization(chunk) ? normalizeTerminalChunkLegacy(chunk) : chunk
}
function appendNormalizedToTailBuffer(previousLines, previousPartialLine, normalizedChunk) {
if (normalizedChunk.length === 0) {
return {
lines: previousLines,
partialLine: previousPartialLine
}
}
const boundedPreviousPartialLine = previousPartialLine.slice(-MAX_TAIL_PARTIAL_CHARS)
const pieces = `${boundedPreviousPartialLine}${normalizedChunk}`.split('\n')
const nextPartialLine = (pieces.pop() ?? '').replace(/[ \t]+$/g, '')
const retainedPartialLine = nextPartialLine.slice(-MAX_TAIL_PARTIAL_CHARS)
let nextLines =
pieces.length > 0
? [...previousLines, ...pieces.map((line) => line.replace(/[ \t]+$/g, ''))]
: previousLines
while (nextLines.length > MAX_TAIL_LINES) {
nextLines.shift()
}
if (pieces.length > 0 || retainedPartialLine.length > previousPartialLine.length) {
if (nextLines === previousLines) {
nextLines = [...previousLines]
}
let totalChars =
nextLines.reduce((sum, line) => sum + line.length, 0) + retainedPartialLine.length
while (nextLines.length > 0 && totalChars > MAX_TAIL_CHARS) {
totalChars -= nextLines.shift().length
}
}
return {
lines: nextLines,
partialLine: retainedPartialLine
}
}
function appendTailBufferLegacy(previousLines, previousPartialLine, chunk) {
return appendNormalizedToTailBuffer(
previousLines,
previousPartialLine,
normalizeTerminalChunkLegacy(chunk)
)
}
function tailStateMatches(lines, partialLine, snapshot) {
if (
partialLine !== snapshot.partialLine ||
lines.length !== snapshot.lines.length ||
lines.length !== snapshot.linesTotal
) {
return false
}
if (lines === snapshot.lines) {
return true
}
for (let index = 0; index < lines.length; index++) {
if (lines[index] !== snapshot.lines[index]) {
return false
}
}
return true
}
class AdvertisedUrlPtyBuffer {
raw = ''
ingest(chunk) {
this.raw += chunk
if (this.raw.length > 4096) {
this.raw = this.raw.slice(-4096)
}
const lastLineBreak = Math.max(this.raw.lastIndexOf('\n'), this.raw.lastIndexOf('\r'))
if (lastLineBreak === -1) {
return ''
}
const finalized = this.raw.slice(0, lastLineBreak + 1)
this.raw = this.raw.slice(lastLineBreak + 1)
return finalized
}
}
function scanAdvertisedUrls(buffer, chunk) {
const finalized = buffer.ingest(chunk)
if (!finalized) {
return
}
for (const _match of finalized.matchAll(URL_CANDIDATE_PATTERN)) {
// Candidate extraction is enough for this benchmark; URL validation is rare
// and only happens after a URL-shaped match.
}
}
function measureRuntimeIngressLegacy() {
const chunk = makeIngressChunk()
const advertisedUrlBuffer = new AdvertisedUrlPtyBuffer()
let recentOutput = ''
let ptyTail = { lines: [], partialLine: '' }
let leafTail = { lines: [], partialLine: '' }
const start = performance.now()
for (let index = 0; index < INGRESS_CHUNKS; index++) {
recentOutput = `${recentOutput}${chunk}`.slice(-RECENT_PTY_OUTPUT_LIMIT)
hasMeaningfulContentLegacy(chunk)
extractLastOscTitleLegacy(chunk)
scanAdvertisedUrls(advertisedUrlBuffer, chunk)
extractLastOscTitleLegacy(chunk)
ptyTail = appendTailBufferLegacy(ptyTail.lines, ptyTail.partialLine, chunk)
leafTail = appendTailBufferLegacy(leafTail.lines, leafTail.partialLine, chunk)
}
return performance.now() - start
}
function measureRuntimeIngressCurrent() {
const chunk = makeIngressChunk()
const advertisedUrlBuffer = new AdvertisedUrlPtyBuffer()
let recentOutput = ''
let ptyTail = { lines: [], partialLine: '' }
let leafTail = { lines: [], partialLine: '' }
const start = performance.now()
for (let index = 0; index < INGRESS_CHUNKS; index++) {
recentOutput = `${recentOutput}${chunk}`.slice(-RECENT_PTY_OUTPUT_LIMIT)
hasMeaningfulContentCurrent(chunk)
extractLastOscTitleCurrent(chunk)
scanAdvertisedUrls(advertisedUrlBuffer, chunk)
extractLastOscTitleCurrent(chunk)
const normalizedChunk = normalizeTerminalChunkCurrent(chunk)
const ptyTailBefore = {
lines: ptyTail.lines,
partialLine: ptyTail.partialLine,
linesTotal: ptyTail.lines.length
}
ptyTail = appendNormalizedToTailBuffer(ptyTail.lines, ptyTail.partialLine, normalizedChunk)
leafTail = tailStateMatches(leafTail.lines, leafTail.partialLine, ptyTailBefore)
? { lines: ptyTail.lines, partialLine: ptyTail.partialLine }
: appendNormalizedToTailBuffer(leafTail.lines, leafTail.partialLine, normalizedChunk)
}
return performance.now() - start
}
function scheduleLegacyFlush(pending) {
return new Promise((resolve) => {
setTimeout(() => {
flushLegacy(pending)
resolve()
}, 0)
})
}
function scheduleBoundedFlush(pending) {
return new Promise((resolve) => {
const drain = () => {
flushBoundedSlice(pending)
if (pending.size > 0) {
setTimeout(drain, 1)
return
}
resolve()
}
setTimeout(drain, 0)
})
}
async function measureInputTimerDelay(flushKind) {
const pending = makePendingData()
const scheduledAt = performance.now()
const drainPromise =
flushKind === 'legacy' ? scheduleLegacyFlush(pending) : scheduleBoundedFlush(pending)
const inputDelay = await new Promise((resolve) => {
setTimeout(() => resolve(performance.now() - scheduledAt), 0)
})
await drainPromise
return inputDelay
}
function percentile(values, p) {
const sorted = [...values].sort((a, b) => a - b)
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1))
return sorted[index] ?? 0
}
function summarize(values) {
return {
median: percentile(values, 50),
p95: percentile(values, 95),
max: Math.max(...values)
}
}
function formatMs(value) {
return `${value.toFixed(3)}ms`
}
async function runBenchmark() {
const legacyDurations = []
const boundedFirstSliceDurations = []
const boundedMaxSliceDurations = []
const boundedTotalDurations = []
const legacyInputTimerDelays = []
const boundedInputTimerDelays = []
const legacyRuntimeIngressDurations = []
const currentRuntimeIngressDurations = []
let legacyBytes = 0
let boundedBytes = 0
for (let run = 0; run < RUNS; run++) {
const legacy = flushLegacy(makePendingData())
legacyDurations.push(legacy.durationMs)
legacyBytes = legacy.bytes
const boundedStart = performance.now()
const bounded = drainBounded(makePendingData())
boundedBytes = bounded.bytes
boundedFirstSliceDurations.push(bounded.sliceDurations[0] ?? 0)
boundedMaxSliceDurations.push(Math.max(...bounded.sliceDurations))
boundedTotalDurations.push(performance.now() - boundedStart)
if (MEASURE_TIMER_DELAYS) {
legacyInputTimerDelays.push(await measureInputTimerDelay('legacy'))
boundedInputTimerDelays.push(await measureInputTimerDelay('bounded'))
}
legacyRuntimeIngressDurations.push(measureRuntimeIngressLegacy())
currentRuntimeIngressDurations.push(measureRuntimeIngressCurrent())
}
const legacySummary = summarize(legacyDurations)
const boundedFirstSliceSummary = summarize(boundedFirstSliceDurations)
const boundedMaxSliceSummary = summarize(boundedMaxSliceDurations)
const boundedTotalSummary = summarize(boundedTotalDurations)
const legacyInputTimerSummary = MEASURE_TIMER_DELAYS ? summarize(legacyInputTimerDelays) : null
const boundedInputTimerSummary = MEASURE_TIMER_DELAYS ? summarize(boundedInputTimerDelays) : null
const legacyRuntimeIngressSummary = summarize(legacyRuntimeIngressDurations)
const currentRuntimeIngressSummary = summarize(currentRuntimeIngressDurations)
console.log(
JSON.stringify(
{
scenario: {
ptyCount: PTY_COUNT,
payloadChars: PAYLOAD_CHARS,
runs: RUNS,
totalPayloadMiB: (PTY_COUNT * PAYLOAD_CHARS) / 1024 / 1024,
ingressChunks: INGRESS_CHUNKS,
ingressChunkChars: INGRESS_CHUNK_CHARS,
ingressPayloadMiB: (INGRESS_CHUNKS * INGRESS_CHUNK_CHARS) / 1024 / 1024
},
legacy: {
bytesPerRun: legacyBytes,
singleCallback: legacySummary,
inputTimerDelay: legacyInputTimerSummary
},
bounded: {
bytesPerRun: boundedBytes,
firstSlice: boundedFirstSliceSummary,
maxSlice: boundedMaxSliceSummary,
totalDrain: boundedTotalSummary,
inputTimerDelay: boundedInputTimerSummary
},
runtimeIngress: {
legacyRepeatedScans: legacyRuntimeIngressSummary,
currentFastPath: currentRuntimeIngressSummary,
estimatedReduction:
legacyRuntimeIngressSummary.max /
Math.max(currentRuntimeIngressSummary.max, Number.EPSILON)
},
estimatedPtyWriteDelay:
legacyInputTimerSummary && boundedInputTimerSummary
? {
before: legacyInputTimerSummary.max,
after: boundedInputTimerSummary.max,
reduction:
legacyInputTimerSummary.max /
Math.max(boundedInputTimerSummary.max, Number.EPSILON)
}
: null
},
null,
2
)
)
console.error(
[
`legacy max single callback: ${formatMs(legacySummary.max)}`,
`bounded max first slice: ${formatMs(boundedFirstSliceSummary.max)}`,
`bounded max any slice: ${formatMs(boundedMaxSliceSummary.max)}`,
`bounded max total drain: ${formatMs(boundedTotalSummary.max)}`,
`runtime ingress legacy max: ${formatMs(legacyRuntimeIngressSummary.max)}`,
`runtime ingress current max: ${formatMs(currentRuntimeIngressSummary.max)}`,
legacyInputTimerSummary
? `legacy max input timer delay: ${formatMs(legacyInputTimerSummary.max)}`
: null,
boundedInputTimerSummary
? `bounded max input timer delay: ${formatMs(boundedInputTimerSummary.max)}`
: null
]
.filter(Boolean)
.join('\n')
)
}
await runBenchmark()