79 lines
4.3 KiB
JavaScript
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 })
|
|
}
|