perf: add CPU-throttled, memory-growth and knowledge retrieval benchmarks
perf:app gains GB_PERF_CPU_THROTTLE (CDP renderer throttling) and a keep-alive memory scenario with post-GC heap samples; perf:knowledge times synchronous FTS, vector and hybrid search at 5k/20k/50k chunks. Results and conclusions recorded in the performance plan (PERF-05, PERF-09, PERF-12).
This commit is contained in:
@@ -140,6 +140,11 @@ function printSummary(report) {
|
||||
heapMB: format(s.renderer?.heapUsedMB)
|
||||
}))
|
||||
if (rows.length) console.table(rows)
|
||||
if (report.memoryGrowth) {
|
||||
console.log(`memory growth (${report.memoryGrowth.description}):`)
|
||||
console.table(report.memoryGrowth.samples)
|
||||
}
|
||||
if (report.environment?.rendererCpuThrottle > 1) console.log(`renderer CPU throttled ${report.environment.rendererCpuThrottle}x`)
|
||||
if (report.error) console.log(`error: ${report.error}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// Knowledge retrieval benchmark (PERF-05). Bundles the production
|
||||
// KnowledgeDatabase with esbuild, seeds synthetic libraries of increasing size
|
||||
// in a temporary SQLite file and times the synchronous search calls that the
|
||||
// Electron Main process runs inline during retrieval. Because these calls are
|
||||
// synchronous, their duration is the time Main is blocked per query.
|
||||
const { buildSync } = require('esbuild')
|
||||
const { createHash } = require('node:crypto')
|
||||
const { mkdtempSync, rmSync, writeFileSync } = require('node:fs')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { join, resolve } = require('node:path')
|
||||
const { performance } = require('node:perf_hooks')
|
||||
|
||||
const root = resolve(__dirname, '..')
|
||||
const sizes = (process.env.GB_KPERF_SIZES || '5000,20000,50000').split(',').map(Number)
|
||||
const dimensions = Number(process.env.GB_KPERF_DIMENSIONS || 384)
|
||||
const chunksPerDocument = 100
|
||||
const queries = Number(process.env.GB_KPERF_QUERIES || 15)
|
||||
|
||||
const directory = mkdtempSync(join(tmpdir(), 'goodbuddy-knowledge-perf-'))
|
||||
const bundle = join(directory, 'knowledge-database.cjs')
|
||||
buildSync({
|
||||
entryPoints: [join(root, 'src/main/knowledge/knowledge-database.ts')],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
target: 'node22',
|
||||
outfile: bundle,
|
||||
logLevel: 'error'
|
||||
})
|
||||
const { KnowledgeDatabase } = require(bundle)
|
||||
|
||||
// Deterministic pseudo-random generator so runs are comparable.
|
||||
let seed = 42
|
||||
const random = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296)
|
||||
const words = 'lighthouse harbor policy engine renderer stream vector index budget latency schedule archive network storage contract invoice release window memory review'.split(' ')
|
||||
const sentence = n => Array.from({ length: n }, () => words[Math.floor(random() * words.length)]).join(' ')
|
||||
const vector = () => Array.from({ length: dimensions }, () => random() * 2 - 1)
|
||||
const checksum = value => createHash('sha256').update(value).digest('hex')
|
||||
|
||||
function percentile(values, p) {
|
||||
const sorted = [...values].sort((a, b) => a - b)
|
||||
return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1))]
|
||||
}
|
||||
function time(body) {
|
||||
const samples = []
|
||||
body() // warm statement cache and page cache
|
||||
for (let index = 0; index < queries; index += 1) {
|
||||
const started = performance.now()
|
||||
body()
|
||||
samples.push(performance.now() - started)
|
||||
}
|
||||
return { p50: round(percentile(samples, 50)), p95: round(percentile(samples, 95)), max: round(Math.max(...samples)) }
|
||||
}
|
||||
const round = value => Math.round(value * 10) / 10
|
||||
|
||||
const results = []
|
||||
try {
|
||||
for (const size of sizes) {
|
||||
const path = join(directory, `knowledge-${size}.sqlite`)
|
||||
const database = new KnowledgeDatabase(path)
|
||||
database.initialize()
|
||||
const library = database.createKnowledgeBase({ name: `Perf ${size}`, storageMode: 'reference' })
|
||||
const sqlite = database.database
|
||||
const seedStarted = performance.now()
|
||||
for (let documentIndex = 0; documentIndex < size / chunksPerDocument; documentIndex += 1) {
|
||||
const source = database.upsertSource({ knowledgeBaseId: library.id, type: 'file', location: `C:\\perf\\doc-${documentIndex}.md`, displayName: `doc-${documentIndex}.md`, status: 'ready' })
|
||||
const chunks = Array.from({ length: chunksPerDocument }, (_, ordinal) => ({ id: `d${documentIndex}-c${ordinal}`, ordinal, content: sentence(80), location: `line ${ordinal + 1}` }))
|
||||
const document = database.upsertDocument({ knowledgeBaseId: library.id, sourceId: source.id, externalId: `doc-${documentIndex}`, title: `doc-${documentIndex}`, sourceLocation: source.location }, chunks)
|
||||
const stored = sqlite.prepare("SELECT id, index_content FROM chunks WHERE document_id = ? AND enabled = 1 AND role <> 'parent'").all(document.id)
|
||||
database.replaceDocumentEmbeddings(document.id, 'perf', 'perf-model', stored.map(row => ({ chunkId: row.id, contentChecksum: checksum(row.index_content), vector: vector() })))
|
||||
}
|
||||
const seedMs = performance.now() - seedStarted
|
||||
database.close()
|
||||
|
||||
// Reopen so timings start from a fresh connection, like an app restart.
|
||||
const reopened = new KnowledgeDatabase(path)
|
||||
reopened.initialize()
|
||||
const query = vector()
|
||||
const entry = {
|
||||
chunks: size,
|
||||
dimensions,
|
||||
seedSeconds: round(seedMs / 1000),
|
||||
ftsMs: time(() => reopened.search({ knowledgeBaseId: library.id, query: 'lighthouse budget', limit: 40 })),
|
||||
vectorMs: time(() => reopened.vectorSearch({ knowledgeBaseId: library.id, provider: 'perf', model: 'perf-model', vector: query, limit: 40 })),
|
||||
hybridMs: time(() => reopened.hybridSearchWithDiagnostics({ knowledgeBaseId: library.id, query: 'lighthouse budget', limit: 40, provider: 'perf', model: 'perf-model', vector: query, graphEnabled: false, candidateMultiplier: 1 }))
|
||||
}
|
||||
reopened.close()
|
||||
results.push(entry)
|
||||
console.log(`[kperf] ${size} chunks: fts p95 ${entry.ftsMs.p95} ms, vector p95 ${entry.vectorMs.p95} ms, hybrid p95 ${entry.hybridMs.p95} ms (seed ${entry.seedSeconds} s)`)
|
||||
}
|
||||
const output = process.env.GB_KPERF_OUTPUT
|
||||
if (output) writeFileSync(output, JSON.stringify({ kind: 'goodbuddy-knowledge-retrieval-benchmark', node: process.version, results }, null, 2))
|
||||
console.table(results.map(r => ({ chunks: r.chunks, 'fts p50/p95': `${r.ftsMs.p50} / ${r.ftsMs.p95}`, 'vector p50/p95': `${r.vectorMs.p50} / ${r.vectorMs.p95}`, 'hybrid p50/p95': `${r.hybridMs.p50} / ${r.hybridMs.p95}` })))
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
||||
}
|
||||
@@ -332,11 +332,30 @@ frame rate, heap measurements and 500KB/1MB scenarios remain unmeasured.
|
||||
|
||||
### PERF-05 向量检索规模基准
|
||||
|
||||
- **优先级 / 状态:** P1 / 待开始
|
||||
- **优先级 / 状态:** P1 / 耗时基线已完成,取消响应与峰值内存待测
|
||||
- **场景:** 1k、10k、50k chunks 的全文、向量和混合检索。
|
||||
- **指标:** 总耗时、`vectorScannedCount`、Main event-loop lag、取消响应和峰值内存。
|
||||
- **验收:** 固定语料和查询集产生可比较结果,并保留检索质量指标。
|
||||
|
||||
#### 2026-10-02 耗时基线
|
||||
|
||||
`npm run perf:knowledge`(`build/run-knowledge-perf.cjs`)用 esbuild 打包生产
|
||||
`KnowledgeDatabase`,在临时 SQLite 中写入确定性随机语料(每文档 100 chunks,每 chunk 80 词,
|
||||
384 维向量,与内置嵌入模型一致),重开连接后对每个规模各测 15 次 `search`、`vectorSearch`
|
||||
和 `hybridSearchWithDiagnostics`(图谱关闭)。这些调用是同步的,在 App 中由 Main 线程直接
|
||||
执行,所以单次耗时就是 Main 被阻塞的时长。同一台设备(Node 24.18),p95:
|
||||
|
||||
| chunks | 全文 | 向量 | 混合 |
|
||||
| --- | --- | --- | --- |
|
||||
| 5,000 | 14 ms | 54 ms | 93 ms |
|
||||
| 20,000 | 59 ms | 173 ms | 219 ms |
|
||||
| 50,000 | 130 ms | 396 ms | 520 ms |
|
||||
|
||||
向量扫描与 chunk 数线性相关,FTS 也随规模增长。按 50 ms 预算,约 5k chunks 起混合检索就会
|
||||
使 Main 可感知地卡顿;5 万 chunks 时每次检索约冻结 Main 0.5 s,期间全部 IPC(包括流式输出、
|
||||
终端和界面操作)排队。结论:`PERF-06` 的实施条件已满足;检索应先移出 Main(`PERF-06`,
|
||||
随后并入 `PERF-16`)。取消响应和峰值内存尚未测量。
|
||||
|
||||
### PERF-06 向量计算移出 Main
|
||||
|
||||
- **优先级 / 状态:** P1 / 待开始
|
||||
@@ -366,11 +385,29 @@ frame rate, heap measurements and 500KB/1MB scenarios remain unmeasured.
|
||||
|
||||
### PERF-09 Keep-alive 压力测试
|
||||
|
||||
- **优先级 / 状态:** P2 / 待开始
|
||||
- **优先级 / 状态:** P2 / 文本会话部分已完成,图片与工具记录待补
|
||||
- **场景:** 连续打开 12 个会话,每个会话包含 80 条富 Markdown、图片和工具记录。
|
||||
- **指标:** DOM 节点、Renderer heap、GC pause 和流式 commit。
|
||||
- **验收:** 明确隐藏会话缓存是否构成实际内存或渲染问题。
|
||||
|
||||
#### 2026-10-02 文本会话测量
|
||||
|
||||
`npm run perf:app` 在流式场景之后连续打开 24 个未访问过的种子会话(每个 20 条富 Markdown),
|
||||
每 4 次记录挂载的面板数、DOM 节点数和强制 GC 后的 JS heap(CDP
|
||||
`HeapProfiler.collectGarbage` + `Runtime.getHeapUsage`)。
|
||||
|
||||
| 已访问 | 挂载面板 | DOM 节点 | GC 后 heap |
|
||||
| --- | --- | --- | --- |
|
||||
| 0(两次流式之后) | 12 | 39,657 | 54.6 MB |
|
||||
| 4 / 8 | 12 | 39,656 | 54.6–55.1 MB |
|
||||
| 12 | 12 | 15,884 | 27.4 MB |
|
||||
| 16 / 20 / 24 | 12 | 15,884 | 27.3–27.9 MB |
|
||||
|
||||
缓存上限 12 个面板有效:两条长流式回答被挤出缓存后,DOM 和 heap 回落并保持不变,后半段
|
||||
增长为 0。此前观察到的“4 万节点、210 MB”是两条 40 KB 流式回答仍在缓存中时的瞬时值
|
||||
(未 GC),不是持续泄漏。结论:文本会话场景下 keep-alive 不构成内存问题,`PERF-10` 不需要
|
||||
调整缓存数量或有效期;含图片和工具记录的会话尚未测。
|
||||
|
||||
### PERF-10 Keep-alive 轻量优化
|
||||
|
||||
- **优先级 / 状态:** P2 / 已完成输入与热切换渲染隔离,压力测试待完成
|
||||
@@ -444,8 +481,10 @@ frame rate, heap measurements and 500KB/1MB scenarios remain unmeasured.
|
||||
按键间隔 60 ms;流式回答约 40 KB 混合 Markdown(标题、列表、代码、表格、行内/块级公式、
|
||||
引用),每个 SSE 事件 12 个字符,2,000 字符/秒。可用 `GB_PERF_*` 环境变量调整,
|
||||
见脚本开头。
|
||||
- `GB_PERF_PROFILE=1` 为流式场景采集 Renderer CPU profile 并汇总自身耗时热点;剖析有开销,
|
||||
只用于归因,不作为基线数据。
|
||||
- `GB_PERF_PROFILE=1` 为有历史输入和流式场景采集 Renderer CPU profile 并汇总自身耗时
|
||||
热点;剖析有开销,只用于归因,不作为基线数据。
|
||||
- `GB_PERF_CPU_THROTTLE=<倍数>` 用 CDP 降低 Renderer CPU 速度模拟低端设备;
|
||||
`GB_PERF_MEMORY_VISITS`(默认 24,0 跳过)控制 `PERF-09` 内存场景。
|
||||
|
||||
#### 2026-10-02 基线
|
||||
|
||||
@@ -534,6 +573,38 @@ p95 从约 36 ms 降到 15.5 ms。
|
||||
剩余开销属于 App 级重渲染,留给 `PERF-12` 后续项和 `PERF-13`。
|
||||
- **剩余:** 流式 delta 按帧合并、大面板 memo、滚动跟随优化仍待实施。
|
||||
|
||||
#### 2026-10-02 低端设备模拟(Renderer CPU 4× 降速)
|
||||
|
||||
`GB_PERF_CPU_THROTTLE=4 npm run perf:app` 通过 CDP `Emulation.setCPUThrottlingRate` 把
|
||||
Renderer 主线程降速 4 倍(Main 不降速,每次 reload 后重新应用),源码 `5dc6545`
|
||||
(含 `PERF-12` 第一步),同一设备。不降速一栏为同一轮构建的对照。
|
||||
|
||||
| 场景 | 不降速 | 4× 降速 |
|
||||
| --- | --- | --- |
|
||||
| 输入 120 字(新 profile)按键→帧 p95 | 8.7 ms | 42 ms,掉帧 21% |
|
||||
| **输入 120 字(有 300 个会话)** | 0 Long Task,p95 9 ms | **37 次 / 3.2 s,p95 355 ms,最大 483 ms** |
|
||||
| **首次切换会话 ×15** 点击→显示 p50 / p95 | 52 / 117 ms | **434 / 922 ms** |
|
||||
| 再次切换(缓存命中)p50 / p95 | 55 / 66 ms | 482 / 534 ms |
|
||||
| 打开 2,000 条消息会话 | 140 ms | 1.2 s |
|
||||
| 滚动长会话 | 帧 p95 8.5 ms | 帧 p95 17 ms,正常 |
|
||||
| **流式 40 KB** | 1 次 / 0.17 s,帧 p95 17 ms | **156 次 / 16.7 s,帧 p95 183 ms,掉帧 100%** |
|
||||
| **流式期间输入** 按键→帧 p50 / p95 | 11.5 / 17 ms | **575 / 1,513 ms** |
|
||||
|
||||
高性能机上不可见的问题,在降速后全部暴露:
|
||||
|
||||
- 新 profile 输入只到 42 ms,有 300 个会话后升到 355 ms,差异来自**每次按键重新渲染整个
|
||||
App 和完整侧栏**。降速剖析:`resizeComposerTextarea`(每次输入同步读 `scrollHeight` 强制
|
||||
布局)约 8%;i18next `formatLanguageCode`/`translate` 约 12%(`Intl.getCanonicalLocales`
|
||||
每次调用 `t()` 都执行,侧栏 302 行各自多次调用);其余是 App 与侧栏行的 React 渲染。
|
||||
- 流式期间约 18% 是 `ChatHistoryPane` 每次 flush 的滚动跟随 layout effect(读布局后
|
||||
`scrollTo`),i18next 约 8%,Markdown 解析与 React reconcile 其余。
|
||||
- 缓存命中切换也约 0.5 s,说明慢的不是挂载新面板,而是切换触发的 App 级整体重渲染。
|
||||
|
||||
**结论:** 低端设备上输入、切换和流式都会明显卡顿,主因是 App 级重渲染和侧栏全量渲染,
|
||||
证实 `PERF-13`(领域 store)和 `PERF-14`(侧栏虚拟化)有必要。`PERF-12` 剩余项应先做三处
|
||||
低成本热点:Composer 高度改为只在需要时测量、`t()` 结果或 i18next 语言码规范化做缓存、
|
||||
滚动跟随按帧合并且只在贴底时执行。
|
||||
|
||||
### PERF-13 Renderer 领域 Store 迁移
|
||||
|
||||
- **优先级 / 状态:** P1 / 待开始
|
||||
@@ -559,7 +630,7 @@ p95 从约 36 ms 降到 15.5 ms。
|
||||
### PERF-16 数据进程
|
||||
|
||||
- **优先级 / 状态:** P1 / 待开始
|
||||
- **实施条件:** `PERF-15` 完成,调���方已全部异步。
|
||||
- **实施条件:** `PERF-15` 完成,调用方已全部异步。
|
||||
- **范围:** 新增独占 SQLite 的 `utilityProcess`,承接会话、知识库、向量检索、Story Graph、
|
||||
活动记录及文档解析;Main 只转发;保证同一时刻只有一个写入方。吸收 `PERF-06` 的范围。
|
||||
- **验收:** 大知识库检索、会话列表和文档导入期间 Main event-loop 延迟达到批次目标;取消、
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"perf:app": "node build/run-app-perf.cjs",
|
||||
"perf:knowledge": "node build/run-knowledge-perf.cjs",
|
||||
"eval:retrieval": "vitest run --config tests/support/knowledge-retrieval-evaluation.ts tests/knowledge-retrieval-metrics.test.ts tests/knowledge-retrieval-evaluation.test.ts",
|
||||
"build": "npm run typecheck && npm run build:bundle",
|
||||
"build:bundle": "electron-vite build && node build/check-control-plane-package-installer.cjs",
|
||||
|
||||
@@ -27,6 +27,9 @@ const config = {
|
||||
typedCharacters: Number(process.env.GB_PERF_TYPED_CHARACTERS || 120),
|
||||
keyIntervalMs: Number(process.env.GB_PERF_KEY_INTERVAL_MS || 60),
|
||||
switches: Number(process.env.GB_PERF_SWITCHES || 15),
|
||||
memoryVisits: Number(process.env.GB_PERF_MEMORY_VISITS || 24),
|
||||
// Chromium CPU throttling of the renderer only (Main is not slowed down).
|
||||
cpuThrottle: Number(process.env.GB_PERF_CPU_THROTTLE || 1),
|
||||
contentWidth: 1280,
|
||||
contentHeight: 800
|
||||
}
|
||||
@@ -267,10 +270,24 @@ async function measure(id, description, body) {
|
||||
// Optional renderer CPU profile (GB_PERF_PROFILE=1). Profiling adds overhead,
|
||||
// so profiled runs are for attribution only, not for baseline numbers.
|
||||
const profiling = process.env.GB_PERF_PROFILE === '1'
|
||||
async function withRendererProfile(name, body) {
|
||||
if (!profiling) return body()
|
||||
function cdp() {
|
||||
const debuggerApi = win.webContents.debugger
|
||||
if (!debuggerApi.isAttached()) debuggerApi.attach('1.3')
|
||||
return debuggerApi
|
||||
}
|
||||
// Throttling is per page session, so it is reapplied after every reload.
|
||||
async function applyCpuThrottle() {
|
||||
if (config.cpuThrottle > 1) await cdp().sendCommand('Emulation.setCPUThrottlingRate', { rate: config.cpuThrottle })
|
||||
}
|
||||
// Heap after a full GC, so retained memory is not hidden by GC timing.
|
||||
async function retainedHeapMB() {
|
||||
await cdp().sendCommand('HeapProfiler.collectGarbage')
|
||||
const { usedSize } = await cdp().sendCommand('Runtime.getHeapUsage')
|
||||
return usedSize / 1048576
|
||||
}
|
||||
async function withRendererProfile(name, body) {
|
||||
if (!profiling) return body()
|
||||
const debuggerApi = cdp()
|
||||
await debuggerApi.sendCommand('Profiler.enable')
|
||||
await debuggerApi.sendCommand('Profiler.setSamplingInterval', { interval: 200 })
|
||||
await debuggerApi.sendCommand('Profiler.start')
|
||||
@@ -401,6 +418,42 @@ async function streamOnce({ typeDuring = 0 } = {}) {
|
||||
return { fields: { streamStartMs: startedMs, streamTotalMs: performance.now() - sendAt, assistantRenderedChars: assistantChars, typedDuringStream, typingEndedBeforeStreamFinished: stillStreaming } }
|
||||
}
|
||||
|
||||
// PERF-09: visit many distinct conversations and check whether DOM size and
|
||||
// retained heap stay bounded by the keep-alive cache. Sampling forces a GC,
|
||||
// so it is recorded outside measure() and is not mixed with frame metrics.
|
||||
async function memoryGrowth(seeded) {
|
||||
report.pending = 'memory-growth'
|
||||
writeReport()
|
||||
const sample = async visits => ({
|
||||
visits,
|
||||
mountedPanes: await js("document.querySelectorAll('.chat-history-pane').length"),
|
||||
domNodes: await js("document.getElementsByTagName('*').length"),
|
||||
retainedHeapMB: Math.round(await retainedHeapMB() * 10) / 10
|
||||
})
|
||||
const samples = [await sample(0)]
|
||||
const openMs = []
|
||||
for (let index = 0; index < config.memoryVisits; index += 1) {
|
||||
const target = index * 3 + 2
|
||||
await clickConversation(seeded.titles[target])
|
||||
openMs.push(await js(`window.__gbPerf.paneShownSinceMouseDown(${JSON.stringify(seeded.ids[target])})`))
|
||||
await sleep(200)
|
||||
if ((index + 1) % 4 === 0) samples.push(await sample(index + 1))
|
||||
}
|
||||
const first = samples[0]
|
||||
const last = samples[samples.length - 1]
|
||||
const half = samples[Math.floor(samples.length / 2)]
|
||||
report.memoryGrowth = {
|
||||
description: `Visit ${config.memoryVisits} distinct seeded conversations; DOM and post-GC heap every 4 visits`,
|
||||
samples,
|
||||
openLatency: summarize(openMs),
|
||||
// Growth in the second half shows whether the cache bound actually holds.
|
||||
secondHalfGrowth: { domNodes: last.domNodes - half.domNodes, retainedHeapMB: Math.round((last.retainedHeapMB - half.retainedHeapMB) * 10) / 10 },
|
||||
totalGrowth: { domNodes: last.domNodes - first.domNodes, retainedHeapMB: Math.round((last.retainedHeapMB - first.retainedHeapMB) * 10) / 10 }
|
||||
}
|
||||
writeReport()
|
||||
console.log(`[perf] memory-growth: panes ${first.mountedPanes}->${last.mountedPanes}, DOM ${first.domNodes}->${last.domNodes}, heap ${first.retainedHeapMB}->${last.retainedHeapMB} MB (second half +${report.memoryGrowth.secondHalfGrowth.retainedHeapMB} MB)`)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- scenarios
|
||||
|
||||
// Electron emits `ready` only after this ESM entry finishes evaluating, so
|
||||
@@ -422,8 +475,10 @@ try {
|
||||
platform: process.platform, arch: process.arch, electron: process.versions.electron, chrome: process.versions.chrome,
|
||||
cpu: `${cpus()[0]?.model?.trim()} x${cpus().length}`, memoryGB: Math.round(totalmem() / 1073741824),
|
||||
innerWidth, innerHeight, devicePixelRatio,
|
||||
gpu: { gpu_compositing: gpu.gpu_compositing, rasterization: gpu.rasterization, webgl: gpu.webgl, webgl2: gpu.webgl2, video_decode: gpu.video_decode }
|
||||
gpu: { gpu_compositing: gpu.gpu_compositing, rasterization: gpu.rasterization, webgl: gpu.webgl, webgl2: gpu.webgl2, video_decode: gpu.video_decode },
|
||||
rendererCpuThrottle: config.cpuThrottle
|
||||
}
|
||||
await applyCpuThrottle()
|
||||
await sleep(1_500)
|
||||
|
||||
const idle = await measure('idle', 'Fresh profile, no interaction for 3 s', async () => { await sleep(3_000) })
|
||||
@@ -446,6 +501,7 @@ try {
|
||||
win.webContents.reload()
|
||||
await waitFor(`!!document.querySelector(${JSON.stringify(composer)}) && document.querySelectorAll('button.conversation-item').length >= ${Math.min(50, config.seedConversations)}`, 60_000, 'seeded sidebar')
|
||||
report.startup.reloadWithSeedMs = performance.now() - reloadStart
|
||||
await applyCpuThrottle()
|
||||
await dismissStartupDialogs({ expect: false })
|
||||
await sleep(1_500)
|
||||
report.seed.sidebarRows = await js("document.querySelectorAll('button.conversation-item').length")
|
||||
@@ -453,11 +509,11 @@ try {
|
||||
|
||||
await measure('idle-seeded', `Seeded history (${config.seedConversations} conversations), no interaction for 3 s`, async () => { await sleep(3_000) })
|
||||
|
||||
await measure('typing-seeded', `Type ${config.typedCharacters} characters with seeded history`, async () => {
|
||||
await measure('typing-seeded', `Type ${config.typedCharacters} characters with seeded history`, () => withRendererProfile('typing-seeded', async () => {
|
||||
await clickSelector(composer, { scroll: false })
|
||||
await typeText('abcdefghijklmnopqrstuvwxyz'.repeat(Math.ceil(config.typedCharacters / 26)).slice(0, config.typedCharacters))
|
||||
await settle()
|
||||
})
|
||||
}))
|
||||
await js(`(() => { const t = document.querySelector(${JSON.stringify(composer)}); const set = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set; set.call(t, ''); t.dispatchEvent(new Event('input', { bubbles: true })); return true; })()`)
|
||||
|
||||
await measure('switch-conversations', `Click ${config.switches} different seeded conversations (${config.seedMessagesPerConversation} messages each), first visit`, async () => {
|
||||
@@ -511,6 +567,8 @@ try {
|
||||
|
||||
await measure('stream-and-type', 'Stream a long answer while typing into the composer; latency and frames cover the typing period', () => streamOnce({ typeDuring: 100 }))
|
||||
|
||||
if (config.memoryVisits > 0) await memoryGrowth(seeded)
|
||||
|
||||
report.status = errors.length ? 'passed-with-renderer-errors' : 'passed'
|
||||
report.pending = null
|
||||
report.startup.driverTotalMs = performance.now() - driverStart
|
||||
|
||||
Reference in New Issue
Block a user