#!/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); });