Files
goodbuddy/build/run-write-io-perf.cjs
T

79 lines
4.3 KiB
JavaScript

// Windows logical-I/O probe against an isolated copy of a quiescent data folder.
// GB_WPERF_DATA must contain assistant.sqlite. No source database is opened.
const { buildSync } = require('esbuild')
const { mkdtempSync, copyFileSync, cpSync, existsSync, rmSync } = require('node:fs')
const { tmpdir } = require('node:os')
const { join, resolve } = require('node:path')
const { randomUUID } = require('node:crypto')
const { performance } = require('node:perf_hooks')
const { execFileSync, spawnSync } = require('node:child_process')
const { deepStrictEqual } = require('node:assert')
if (process.platform !== 'win32') throw new Error('This probe uses Windows process I/O counters')
if (!process.versions.electron) {
const result = spawnSync(require('electron'), [__filename], {
stdio: 'inherit', env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }
})
process.exit(result.status ?? 1)
}
const io = () => JSON.parse(execFileSync('powershell.exe', ['-NoProfile', '-Command',
`Get-CimInstance Win32_Process -Filter 'ProcessId=${process.pid}' | Select-Object ReadTransferCount,WriteTransferCount | ConvertTo-Json -Compress`
], { encoding: 'utf8' }))
const source = process.env.GB_WPERF_DATA
if (!source || !existsSync(join(source, 'assistant.sqlite'))) throw new Error('Set GB_WPERF_DATA')
const directory = mkdtempSync(join(tmpdir(), 'goodbuddy-write-io-'))
let database
try {
const path = join(directory, 'assistant.sqlite')
for (const suffix of ['', '-wal', '-shm']) {
if (existsSync(join(source, `assistant.sqlite${suffix}`))) {
copyFileSync(join(source, `assistant.sqlite${suffix}`), `${path}${suffix}`)
}
}
if (existsSync(join(source, 'notes'))) cpSync(join(source, 'notes'), join(directory, 'notes'), { recursive: true })
const bundle = join(directory, 'database.cjs')
buildSync({ entryPoints: [resolve(__dirname, '../src/main/assistant/assistant-database.ts')],
outfile: bundle, bundle: true, platform: 'node', format: 'cjs', external: ['electron'], logLevel: 'error' })
const { AssistantDatabase } = require(bundle)
database = new AssistantDatabase(path)
database.initialize(directory)
const raw = database.database
const largest = raw.prepare(`SELECT id, conversation_id, length(metadata_json) AS bytes
FROM messages ORDER BY length(metadata_json) DESC LIMIT 1`).get()
if (!largest) throw new Error('No messages')
const snapshot = database.getConversation(largest.conversation_id)
const message = snapshot.messages.find(item => item.id === largest.id)
// Summary reads must remain small even while a large history is streaming.
database.saveLocalConversations([{ header: { ...snapshot, messages: undefined },
messages: [{ ...message, state: 'streaming' }] }])
const taskId = randomUUID()
database.createTask({ id: taskId, projectId: snapshot.projectId,
conversationId: snapshot.id, title: 'I/O probe', instructions: '', workMode: 'ask', visible: false })
database.startExecutionTiming(taskId)
raw.exec('PRAGMA wal_checkpoint(TRUNCATE)')
console.log(JSON.stringify({ schema: raw.prepare('PRAGMA user_version').get(), metadataBytes: largest.bytes }))
const results = []
for (const [variant, query, count] of [
['read-conversation', () => database.getConversation(snapshot.id), 1],
['read-summary', () => database.listConversationSummaries([]), 1],
['conversation-timing', () => database.getExecutionStats({ conversationId: snapshot.id }), 20],
...(snapshot.projectId ? [['project-timing', () => database.getExecutionStats({ projectId: snapshot.projectId }), 20]] : [])
]) {
const before = io()
const start = performance.now()
for (let index = 0; index < count; index++) query()
const elapsedMs = Math.round(performance.now() - start)
const after = io()
results.push({ variant, calls: count, elapsedMs,
readMiB: Number(((after.ReadTransferCount - before.ReadTransferCount) / 1024 / 1024).toFixed(3)),
writeMiB: Number(((after.WriteTransferCount - before.WriteTransferCount) / 1024 / 1024).toFixed(3)) })
}
database.endExecutionTiming(taskId)
deepStrictEqual(database.getConversation(snapshot.id).messages,
snapshot.messages.map(item => item.id === message.id ? { ...item, state: 'streaming' } : item))
console.table(results)
} finally {
database?.close()
rmSync(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
}