149 lines
4.7 KiB
JavaScript
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);
|
|
});
|