feat: add docker capacity benchmark mode

This commit is contained in:
Codex
2026-07-01 03:31:17 +08:00
parent eb3dca5fb8
commit 3d33c783cd
11 changed files with 242 additions and 6 deletions

View File

@@ -0,0 +1,95 @@
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
const repoRoot = process.cwd();
const databaseUrl = process.env.DATABASE_URL || 'postgresql://postgres:postgres@127.0.0.1:54322/postgres';
const apiBase = process.env.PERF_API_BASE || 'http://127.0.0.1:8787';
const matrix = [
{ name: 'read-30', duration: 120, concurrency: 30, ramp: 15, writes: false, ratio: 0 },
{ name: 'mixed-50', duration: 60, concurrency: 50, ramp: 10, writes: true, ratio: 0.1 },
{ name: 'mixed-100', duration: 60, concurrency: 100, ramp: 15, writes: true, ratio: 0.08 },
];
function run(command, args, options = {}) {
const result = spawnSync(command, args, {
cwd: repoRoot,
stdio: 'inherit',
shell: process.platform === 'win32',
env: {
...process.env,
DATABASE_URL: databaseUrl,
AUTH_SESSION_SECRET: process.env.AUTH_SESSION_SECRET || 'development-session-secret-change-me',
DB_POOL_MAX: process.env.DB_POOL_MAX || '10',
BENCHMARK_API_CPUS: process.env.BENCHMARK_API_CPUS || '2.0',
BENCHMARK_API_MEMORY: process.env.BENCHMARK_API_MEMORY || '4g',
...options.env,
},
});
if (result.status !== 0) {
throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}`);
}
}
function latestBenchmarkJson(before) {
const dir = path.join(repoRoot, 'docs', 'refactor', 'performance-reports');
const files = fs.existsSync(dir)
? fs.readdirSync(dir)
.filter(name => /^api-benchmark-\d{8}-\d{6}\.json$/.test(name))
.map(name => path.join(dir, name))
.filter(file => fs.statSync(file).mtimeMs >= before)
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)
: [];
if (!files[0]) throw new Error('Benchmark report JSON was not generated.');
return path.relative(repoRoot, files[0]).replaceAll('\\', '/');
}
function waitForHealth(timeoutMs = 60_000) {
const started = Date.now();
while (Date.now() - started < timeoutMs) {
const result = spawnSync(process.execPath, ['-e', `fetch('${apiBase}/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))`], {
cwd: repoRoot,
stdio: 'ignore',
shell: process.platform === 'win32',
});
if (result.status === 0) return;
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000);
}
throw new Error(`API did not become healthy at ${apiBase}`);
}
function main() {
const generated = [];
try {
run('docker', ['compose', '-f', 'docker-compose.api.yml', '-f', 'docker-compose.api.benchmark.yml', 'up', '-d', '--build', 'api']);
waitForHealth();
for (const item of matrix) {
const before = Date.now();
run('node', ['scripts/api-performance-benchmark.js'], {
env: {
PERF_START_SERVER: 'false',
PERF_API_BASE: apiBase,
PERF_AUTH_MODE: 'app_session',
PERF_DURATION_SECONDS: String(item.duration),
PERF_CONCURRENCY: String(item.concurrency),
PERF_RAMP_SECONDS: String(item.ramp),
PERF_INCLUDE_WRITES: item.writes ? 'true' : 'false',
PERF_PRACTICE_FLOW_RATIO: String(item.ratio),
PERF_PRACTICE_FLOW_ANSWERS: '3',
},
});
generated.push({ scenario: item.name, report: latestBenchmarkJson(before) });
}
console.log(JSON.stringify({ status: 'pass', apiBase, reports: generated }, null, 2));
} finally {
if (process.env.PERF_KEEP_DOCKER_API !== 'true') {
spawnSync('docker', ['compose', '-f', 'docker-compose.api.yml', '-f', 'docker-compose.api.benchmark.yml', 'down'], {
cwd: repoRoot,
stdio: 'inherit',
shell: process.platform === 'win32',
});
}
}
}
main();