mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
perf(sessions): reuse validated overview cache on startup
This commit is contained in:
1 parent
3b123b15f2
commit
5e8d1caf30
9 files changed
+281
-14
No files matched your search
@@ -18,6 +18,12 @@ function parsePositiveInteger(value, flag) {
|
||||
return parsed
|
||||
}
|
||||
|
||||
function parseNonNegativeInteger(value, flag) {
|
||||
const parsed = Number.parseInt(value, 10)
|
||||
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${flag} must be a non-negative integer`)
|
||||
return parsed
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = {
|
||||
url: DEFAULT_URL,
|
||||
@@ -40,7 +46,7 @@ function parseArgs(argv) {
|
||||
|
||||
if (arg === '--url') options.url = next()
|
||||
else if (arg === '--runs') options.runs = parsePositiveInteger(next(), arg)
|
||||
else if (arg === '--warmups') options.warmups = parsePositiveInteger(next(), arg)
|
||||
else if (arg === '--warmups') options.warmups = parseNonNegativeInteger(next(), arg)
|
||||
else if (arg === '--timeout') options.timeoutMs = parsePositiveInteger(next(), arg)
|
||||
else if (arg === '--chrome') options.chromePath = next()
|
||||
else if (arg === '--json') options.json = true
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { existsSync } from 'node:fs'
|
||||
import { mkdir, readdir, writeFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import Database from 'better-sqlite3'
|
||||
|
||||
function parsePositiveInteger(value, flag) {
|
||||
const parsed = Number.parseInt(value, 10)
|
||||
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${flag} must be a positive integer`)
|
||||
return parsed
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = { sessions: 100, messages: 200 }
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index]
|
||||
if (arg === '--') continue
|
||||
const next = () => {
|
||||
const value = argv[index + 1]
|
||||
if (!value) throw new Error(`${arg} requires a value`)
|
||||
index += 1
|
||||
return value
|
||||
}
|
||||
if (arg === '--database-dir') options.databaseDir = resolve(next())
|
||||
else if (arg === '--cache-dir') options.cacheDir = resolve(next())
|
||||
else if (arg === '--sessions') options.sessions = parsePositiveInteger(next(), arg)
|
||||
else if (arg === '--messages') options.messages = parsePositiveInteger(next(), arg)
|
||||
else if (arg === '--help') {
|
||||
console.log(`Usage: pnpm bench:startup:fixture -- --database-dir <path> --cache-dir <path> [options]
|
||||
|
||||
Creates valid group-chat databases and matching overview caches for local
|
||||
startup benchmarks. Both output directories must be empty.
|
||||
|
||||
Options:
|
||||
--sessions <count> Session databases to create (default: 100)
|
||||
--messages <count> Messages in each session (default: 200)
|
||||
`)
|
||||
process.exit(0)
|
||||
} else throw new Error(`Unknown option: ${arg}`)
|
||||
}
|
||||
if (!options.databaseDir || !options.cacheDir) {
|
||||
throw new Error('--database-dir and --cache-dir are required')
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
async function ensureEmptyDirectory(directory) {
|
||||
if (existsSync(directory) && (await readdir(directory)).length > 0) {
|
||||
throw new Error(`Refusing to write benchmark fixtures into non-empty directory: ${directory}`)
|
||||
}
|
||||
await mkdir(directory, { recursive: true })
|
||||
}
|
||||
|
||||
const SCHEMA = `
|
||||
CREATE TABLE meta (
|
||||
name TEXT,
|
||||
platform TEXT,
|
||||
type TEXT,
|
||||
imported_at INTEGER,
|
||||
group_id TEXT,
|
||||
group_avatar TEXT,
|
||||
owner_id TEXT,
|
||||
session_gap_threshold INTEGER
|
||||
);
|
||||
CREATE TABLE member (
|
||||
id INTEGER PRIMARY KEY,
|
||||
platform_id TEXT,
|
||||
account_name TEXT,
|
||||
group_nickname TEXT,
|
||||
avatar TEXT
|
||||
);
|
||||
CREATE TABLE message (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sender_id INTEGER,
|
||||
ts INTEGER,
|
||||
type INTEGER,
|
||||
content TEXT,
|
||||
platform_message_id TEXT
|
||||
);
|
||||
CREATE TABLE segment (
|
||||
id INTEGER PRIMARY KEY,
|
||||
start_ts INTEGER,
|
||||
end_ts INTEGER,
|
||||
message_count INTEGER,
|
||||
is_manual INTEGER DEFAULT 0,
|
||||
summary TEXT
|
||||
);
|
||||
`
|
||||
|
||||
async function main() {
|
||||
const options = parseArgs(process.argv.slice(2))
|
||||
await ensureEmptyDirectory(options.databaseDir)
|
||||
await ensureEmptyDirectory(options.cacheDir)
|
||||
const nativeBinding = fileURLToPath(new URL('../apps/cli/native/better_sqlite3.node', import.meta.url))
|
||||
|
||||
for (let index = 1; index <= options.sessions; index += 1) {
|
||||
const sessionId = `benchmark-${String(index).padStart(3, '0')}`
|
||||
const db = new Database(resolve(options.databaseDir, `${sessionId}.db`), { nativeBinding })
|
||||
try {
|
||||
db.exec(SCHEMA)
|
||||
db.prepare(
|
||||
`INSERT INTO meta (name, platform, type, imported_at, group_id, owner_id)
|
||||
VALUES (?, 'benchmark', 'group', ?, ?, NULL)`
|
||||
).run(`Benchmark Session ${index}`, 2_000_000_000 - index, sessionId)
|
||||
db.prepare(
|
||||
`INSERT INTO member (id, platform_id, account_name, group_nickname, avatar)
|
||||
VALUES (1, 'member-1', 'Benchmark Member', 'Benchmark Member', NULL)`
|
||||
).run()
|
||||
|
||||
const insertMessage = db.prepare(
|
||||
`INSERT INTO message (sender_id, ts, type, content, platform_message_id)
|
||||
VALUES (1, ?, 0, 'benchmark message', ?)`
|
||||
)
|
||||
db.transaction(() => {
|
||||
for (let message = 1; message <= options.messages; message += 1) {
|
||||
insertMessage.run(1_700_000_000 + message, `${sessionId}-${message}`)
|
||||
}
|
||||
})()
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
|
||||
const cache = {
|
||||
overview: {
|
||||
data: {
|
||||
totalMessages: options.messages,
|
||||
totalMembers: 1,
|
||||
firstMessageTs: 1_700_000_001,
|
||||
lastMessageTs: 1_700_000_000 + options.messages,
|
||||
maxMessageId: options.messages,
|
||||
},
|
||||
ts: Math.floor(Date.now() / 1_000),
|
||||
},
|
||||
}
|
||||
await writeFile(resolve(options.cacheDir, `${sessionId}.cache.json`), JSON.stringify(cache), 'utf8')
|
||||
}
|
||||
|
||||
console.log(`Created ${options.sessions} cached sessions (${options.messages} messages each).`)
|
||||
console.log(`Databases: ${options.databaseDir}`)
|
||||
console.log(`Caches: ${options.cacheDir}`)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error))
|
||||
process.exitCode = 1
|
||||
})
|
||||
Reference in new issue
Block a user