personnal/ecole/scripts/advalo-bench.js

149 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
/* eslint-disable no-console */
require('dotenv').config();
const http = require('http');
const fs = require('fs');
const path = require('path');
const BASE_URL = process.env.BENCH_BASE_URL || `http://127.0.0.1:${process.env.PORT || 8082}`;
const SAMPLES = Number(process.env.BENCH_SAMPLES || 15);
const CONCURRENCY = Number(process.env.BENCH_CONCURRENCY || 3);
const MATRICULE = process.env.BENCH_MATRICULE || 'S601153';
const REPORT_DIR = process.env.BENCH_REPORT_DIR
? path.resolve(process.env.BENCH_REPORT_DIR)
: path.resolve(__dirname, 'reports');
function request(method, path, token, body = null, responseType = 'json') {
return new Promise((resolve, reject) => {
const url = new URL(path, BASE_URL);
const payload = body ? JSON.stringify(body) : null;
const req = http.request({
method,
hostname: url.hostname,
port: url.port,
path: `${url.pathname}${url.search}`,
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(payload ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) } : {})
}
}, (res) => {
let raw = '';
res.on('data', (chunk) => { raw += chunk.toString(); });
res.on('end', () => {
if (res.statusCode >= 400) {
return reject(new Error(`${method} ${path} failed (${res.statusCode}) ${raw.slice(0, 300)}`));
}
if (responseType === 'text') {
return resolve(raw);
}
if (responseType === 'json') {
try {
const parsed = raw ? JSON.parse(raw) : {};
return resolve(parsed);
} catch (error) {
return reject(new Error(`Invalid JSON response for ${method} ${path}: ${error.message}`));
}
}
return reject(new Error(`Unsupported responseType '${responseType}' for ${method} ${path}`));
});
});
req.on('error', reject);
if (payload) req.write(payload);
req.end();
});
}
async function getToken() {
if (process.env.BENCH_TOKEN) return process.env.BENCH_TOKEN;
const auth = await request('GET', `/auth/verifyMatricule/${encodeURIComponent(MATRICULE)}`, null);
if (!auth.valid || !auth.token) {
throw new Error(`Unable to get token for matricule ${MATRICULE}`);
}
return auth.token;
}
function percentile(values, p) {
if (!values.length) return 0;
const sorted = [...values].sort((a, b) => a - b);
const idx = Math.ceil((p / 100) * sorted.length) - 1;
return sorted[Math.max(0, Math.min(sorted.length - 1, idx))];
}
async function benchmarkEndpoint(token, endpoint) {
const durations = [];
const runOne = async () => {
const started = process.hrtime.bigint();
await request(
endpoint.method,
endpoint.path,
token,
endpoint.body || null,
endpoint.responseType || 'json'
);
const ended = process.hrtime.bigint();
durations.push(Number(ended - started) / 1_000_000);
};
await runOne(); // warm-up
let running = [];
for (let i = 0; i < SAMPLES; i += 1) {
running.push(runOne());
if (running.length >= CONCURRENCY) {
await Promise.all(running);
running = [];
}
}
if (running.length) await Promise.all(running);
return {
endpoint: endpoint.path,
samples: durations.length,
p50Ms: Number(percentile(durations, 50).toFixed(2)),
p95Ms: Number(percentile(durations, 95).toFixed(2)),
avgMs: Number((durations.reduce((acc, x) => acc + x, 0) / durations.length).toFixed(2)),
maxMs: Number(Math.max(...durations).toFixed(2))
};
}
async function main() {
const token = await getToken();
const endpoints = [
{ method: 'GET', path: '/advalo/historique?page=1&pageSize=20' },
{ method: 'GET', path: '/advalo/cumul?page=1&pageSize=20' },
{ method: 'GET', path: '/advalo/reporting?page=1&pageSize=20' },
{ method: 'GET', path: '/advalo/export?page=1&pageSize=100', responseType: 'text' }
];
const results = [];
for (const endpoint of endpoints) {
console.log(`Benchmarking ${endpoint.path} ...`);
const result = await benchmarkEndpoint(token, endpoint);
results.push(result);
}
const summary = {
baseUrl: BASE_URL,
samples: SAMPLES,
concurrency: CONCURRENCY,
generatedAt: new Date().toISOString(),
results,
acceptance: {
allP95Lt2000: results.every((item) => item.p95Ms < 2000)
}
};
fs.mkdirSync(REPORT_DIR, { recursive: true });
const reportPath = path.join(REPORT_DIR, `advalo-bench-${Date.now()}.json`);
fs.writeFileSync(reportPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
console.log(JSON.stringify(summary, null, 2));
console.log(`Report saved: ${reportPath}`);
}
main().catch((error) => {
console.error(error.stack || error.message || String(error));
process.exit(1);
});