forked from wangziqi/gongxue-base
107 lines
3.9 KiB
JavaScript
107 lines
3.9 KiB
JavaScript
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 },
|
|
{ name: 'mixed-150', duration: 60, concurrency: 150, ramp: 20, writes: true, ratio: 0.06 },
|
|
];
|
|
|
|
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 sleep(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function waitForHealth(timeoutMs = 120_000) {
|
|
const started = Date.now();
|
|
let lastError = null;
|
|
while (Date.now() - started < timeoutMs) {
|
|
try {
|
|
const response = await fetch(`${apiBase}/health`);
|
|
const payload = await response.json().catch(() => ({}));
|
|
if (response.ok && payload.ok) return;
|
|
lastError = new Error(`HTTP ${response.status}: ${JSON.stringify(payload)}`);
|
|
} catch (error) {
|
|
lastError = error;
|
|
}
|
|
await sleep(1000);
|
|
}
|
|
throw new Error(`API did not become healthy at ${apiBase}. ${lastError?.message || ''}`);
|
|
}
|
|
|
|
async function main() {
|
|
const generated = [];
|
|
try {
|
|
run('docker', ['compose', '-f', 'docker-compose.api.yml', '-f', 'docker-compose.api.benchmark.yml', 'up', '-d', '--build', 'api']);
|
|
await 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().catch(error => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|