Files
orca/config/scripts/terminal-perf-report-rows.mjs
T
Neil 8cf0e81ced fix(perf): calibrate report budgets without masking latency stalls (#22075)
* fix(perf): calibrate report budgets without masking latency stalls

* docs(perf): record historical evidence for report limits
2026-09-21 14:13:09 -07:00

203 lines
5.7 KiB
JavaScript

import { readFileSync } from 'node:fs'
import { reportBudgetsForScenario } from './terminal-perf-report-budgets.mjs'
const ROW_BUDGET_FIELDS = [
['medianMs', 'median'],
['worstMs', 'worst'],
['revisitMs', 'revisit'],
['maxTimerDriftMs', 'maxTimerDrift'],
['scrollMs', 'scroll'],
['restoreMs', 'restore'],
['rendererQueuedChars', 'rendererQueuedChars'],
['rendererPeakQueuedChars', 'rendererPeakQueuedChars'],
['rendererDroppedBacklogs', 'rendererDroppedBacklogs']
]
const SCENARIO_LABELS = [
['opencode-scale-same-workspace', 'Same workspace panes'],
['opencode-scale-cross-workspace', 'Cross-workspace hidden panes'],
['opencode-scale-pressure', 'ACK-backpressured PTYs'],
['opencode-scale-hidden-pressure', 'Hidden real PTYs'],
['opencode-cross-workspace-typing', 'Cross-workspace typing'],
['opencode-main-pressure', 'Main renderer pressure'],
['opencode-hidden-pressure', 'Hidden pressure'],
['opencode-revisit-pressure', 'Revisit under pressure'],
// Why: the prefix also matches opencode-parked-memory-disabled, so both
// parked-memory scenarios group under one label.
['opencode-parked-memory', 'Parked hidden terminal memory']
]
export function readJsonReport(path) {
const raw = readFileSync(path, 'utf8')
const start = raw.indexOf('{')
const end = raw.lastIndexOf('}')
if (start === -1 || end <= start) {
throw new Error(`${path}: no JSON object found`)
}
return JSON.parse(raw.slice(start, end + 1))
}
function parseAnnotationDescription(description) {
const values = {}
for (const part of description.split(/\s+/)) {
const index = part.indexOf('=')
if (index === -1) {
continue
}
values[part.slice(0, index)] = part.slice(index + 1)
}
return values
}
export function collectTerminalPerfRows(report, source) {
const rows = []
const visitSuite = (suite) => {
for (const spec of suite.specs ?? []) {
for (const test of spec.tests ?? []) {
for (const annotation of test.annotations ?? []) {
if (!annotation.type.startsWith('opencode-')) {
continue
}
rows.push(
normalizeRow({
source,
scenario: annotation.type,
...parseAnnotationDescription(annotation.description ?? '')
})
)
}
}
}
for (const child of suite.suites ?? []) {
visitSuite(child)
}
}
for (const suite of report.suites ?? []) {
visitSuite(suite)
}
return rows
}
function parseMs(value) {
const match = String(value ?? '').match(/^(-?\d+(?:\.\d+)?)ms$/)
return match ? Number(match[1]) : null
}
function parseCount(value) {
if (value == null || value === '') {
return null
}
const count = Number(value)
return Number.isFinite(count) ? count : null
}
function normalizeRow(row) {
return {
...row,
group: scenarioGroup(row.scenario),
panes: parseCount(row.panes),
frames: parseCount(row.frames),
medianMs: parseMs(row.median),
worstMs: parseMs(row.worst),
revisitMs: parseMs(row.revisit),
maxTimerDriftMs: parseMs(row.maxTimerDrift),
scrollMs: parseMs(row.scroll),
restoreMs: parseMs(row.restore),
rendererQueuedChars: parseCount(row.rendererQueuedChars),
rendererPeakQueuedChars: parseCount(row.rendererPeakQueuedChars),
rendererDroppedBacklogs: parseCount(row.rendererDroppedBacklogs),
mainPeakPendingChars: parseCount(row.mainPeakPendingChars),
mainPeakInFlightChars: parseCount(row.mainPeakInFlightChars),
heldAckChars: parseCount(row.heldAckChars),
hiddenSkippedChars: parseCount(row.hiddenSkippedChars),
// Why: parked-memory annotations report a fractional MB heap figure plus
// live renderer view counts; Number() keeps the MB float intact.
heapUsedMB: parseCount(row.heapUsedMB),
liveTerminals: parseCount(row.liveTerminals),
livePaneManagers: parseCount(row.livePaneManagers)
}
}
export function scenarioGroup(scenario) {
for (const [prefix, label] of SCENARIO_LABELS) {
if (scenario.startsWith(prefix)) {
return label
}
}
return 'Other terminal scenarios'
}
function scenarioSortKey(scenario) {
const prefixIndex = SCENARIO_LABELS.findIndex(([prefix]) => scenario.startsWith(prefix))
const paneMatch = scenario.match(/-(\d+)$/)
return [
prefixIndex === -1 ? SCENARIO_LABELS.length : prefixIndex,
paneMatch ? Number(paneMatch[1]) : 0,
scenario
]
}
export function compareScenarios(a, b) {
const ka = scenarioSortKey(a)
const kb = scenarioSortKey(b)
if (ka[0] !== kb[0]) {
return ka[0] - kb[0]
}
if (ka[1] !== kb[1]) {
return ka[1] - kb[1]
}
return ka[2] < kb[2] ? -1 : ka[2] > kb[2] ? 1 : 0
}
export function scenarioTitle(scenario, row) {
const group = scenarioGroup(scenario)
if (row?.panes != null) {
return `${group} — ${row.panes} panes`
}
return group
}
export function budgetFailures(row) {
const budgets = reportBudgetsForScenario(row.scenario)
const failures = []
for (const [rowKey, budgetKey] of ROW_BUDGET_FIELDS) {
const value = row[rowKey]
const budget = budgets[budgetKey]
if (value == null || budget == null) {
continue
}
if (value > budget) {
failures.push(`${rowKey} ${value} > ${budget}`)
}
}
return failures
}
export function formatMs(value) {
if (value == null) {
return '—'
}
return `${value.toFixed(1)}ms`
}
export function formatLargeValue(value) {
if (value == null) {
return '—'
}
if (value >= 1024 * 1024) {
return `${(value / (1024 * 1024)).toFixed(2)}M`
}
if (value >= 1024) {
return `${Math.round(value / 1024)}k`
}
return String(value)
}
export function escapeHtml(value) {
return String(value)
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
}