Files
hermes-agent/ui-tui/scripts/profile-tui.mjs
Brooklyn Nicholson 2e4b65b9f5 chore(tui): clean remaining Ink perf scaffolding
Trim narration comments and collapse small one-off helpers in the remaining ui-tui perf support files while preserving behaviour.
2026-04-26 21:20:54 -05:00

122 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/* global Buffer, console, process, setImmediate */
import inspector from 'node:inspector'
import { performance } from 'node:perf_hooks'
import React from 'react'
import { render } from '@hermes/ink'
import { AppLayout } from '../src/components/appLayout.tsx'
import { resetOverlayState } from '../src/app/overlayStore.ts'
import { resetTurnState } from '../src/app/turnStore.ts'
import { resetUiState } from '../src/app/uiStore.ts'
const session = new inspector.Session()
session.connect()
const post = (method, params = {}) => new Promise((resolve, reject) => {
session.post(method, params, (err, result) => err ? reject(err) : resolve(result))
})
const historySize = Number(process.env.HISTORY || 500)
const mountedRows = Number(process.env.MOUNTED || 120)
class Sink {
columns = Number(process.env.COLS || 120)
rows = Number(process.env.ROWS || 42)
isTTY = true
bytes = 0
writes = 0
listeners = new Map()
write(chunk) {
this.bytes += Buffer.byteLength(String(chunk ?? ''))
this.writes++
return true
}
on(event, fn) { this.listeners.set(event, fn); return this }
off(event) { this.listeners.delete(event); return this }
once(event, fn) { this.listeners.set(event, fn); return this }
removeListener(event) { this.listeners.delete(event); return this }
}
const theme = {
brand: { prompt: '' },
color: {
amber: '#d19a66', bronze: '#8b6f47', dim: '#6b7280', error: '#ff5555', gold: '#ffd166', label: '#61afef',
ok: '#98c379', warn: '#e5c07b', cornsilk: '#fff8dc', prompt: '#c678dd', shellDollar: '#98c379',
statusCritical: '#ff5555', statusBad: '#e06c75', statusWarn: '#e5c07b', statusGood: '#98c379',
selectionBg: '#44475a'
}
}
const noop = () => {}
const historyItems = [
{ kind: 'intro', role: 'system', text: '', info: { model: 'test', tools: {}, skills: {}, version: 'test' } },
...Array.from({ length: historySize }, (_, i) => ({
role: i % 5 === 0 ? 'user' : 'assistant',
text: `message ${i}\n${'lorem ipsum '.repeat(80)}`
}))
]
const scrollRef = { current: {
getScrollTop: () => 0,
getPendingDelta: () => 0,
getScrollHeight: () => historySize * 4,
getViewportHeight: () => 30,
getViewportTop: () => 0,
isSticky: () => true,
subscribe: () => () => {},
scrollBy: noop,
scrollTo: noop,
scrollToBottom: noop,
setClampBounds: noop,
getLastManualScrollAt: () => 0
} }
const baseProps = streamingText => ({
actions: { answerApproval: noop, answerClarify: noop, answerSecret: noop, answerSudo: noop, onModelSelect: noop, resumeById: noop, setStickyPrompt: noop },
composer: { cols: 120, compIdx: 0, completions: [], empty: false, handleTextPaste: () => null, input: '', inputBuf: [], pagerPageSize: 10, queueEditIdx: null, queuedDisplay: [], submit: noop, updateInput: noop },
mouseTracking: false,
progress: {
activity: [], outcome: '', reasoning: streamingText, reasoningActive: true, reasoningStreaming: true,
reasoningTokens: Math.ceil(streamingText.length / 4), showProgressArea: true, showStreamingArea: true,
streamPendingTools: [], streamSegments: [], streaming: streamingText, subagents: [], toolTokens: 0, tools: [], turnTrail: [], todos: []
},
status: { cwdLabel: '~/repo', goodVibesTick: 0, sessionStartedAt: Date.now(), showStickyPrompt: false, statusColor: theme.color.ok, stickyPrompt: '', turnStartedAt: Date.now(), voiceLabel: 'voice off' },
transcript: {
historyItems,
scrollRef,
virtualHistory: { bottomSpacer: 0, end: historyItems.length, measureRef: () => noop, offsets: historyItems.map((_, i) => i * 4), start: Math.max(0, historyItems.length - mountedRows), topSpacer: 0 },
virtualRows: historyItems.map((msg, index) => ({ index, key: `m${index}`, msg }))
}
})
async function main() {
resetUiState()
resetTurnState()
resetOverlayState()
const stdout = new Sink()
const stdin = { isTTY: true, setRawMode: noop, on: noop, off: noop, resume: noop, pause: noop }
const text = Array.from({ length: Number(process.env.LINES || 1200) }, (_, i) => `stream line ${i} ${'x'.repeat(90)}`).join('\n')
const inst = render(React.createElement(AppLayout, baseProps('')), { stdout, stdin, stderr: stdout, debug: false, exitOnCtrlC: false })
await post('Profiler.enable')
await post('HeapProfiler.enable')
await post('Profiler.start')
const startMem = process.memoryUsage()
const t0 = performance.now()
const iterations = Number(process.env.ITERS || 40)
for (let i = 1; i <= iterations; i++) {
const prefix = text.slice(0, Math.floor(text.length * i / iterations))
inst.rerender(React.createElement(AppLayout, baseProps(prefix)))
await new Promise(r => setImmediate(r))
}
const elapsed = performance.now() - t0
const prof = await post('Profiler.stop')
const endMem = process.memoryUsage()
await post('HeapProfiler.collectGarbage')
const afterGc = process.memoryUsage()
inst.unmount()
session.disconnect()
console.log(JSON.stringify({ elapsedMs: Math.round(elapsed), stdoutBytes: stdout.bytes, stdoutWrites: stdout.writes, startMem, endMem, afterGc, profileNodes: prof.profile.nodes.length }, null, 2))
}
main().catch(err => { console.error(err); process.exit(1) })