الملفات
back_end_oudelaa/scripts/load-test.js
boutmoun123 5bd5e19a89
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s
feat: expand backend admin marketplace and scaling
2026-05-14 16:44:07 +03:00

261 أسطر
6.9 KiB
JavaScript

const { readFileSync } = require('fs');
const { performance } = require('perf_hooks');
function parseArgs(argv) {
const options = {
url: 'http://127.0.0.1:4000/api/v1/health',
method: 'GET',
duration: 15,
concurrency: 20,
timeout: 5000,
warmup: 5,
headers: {},
body: undefined,
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
const next = argv[index + 1];
if (arg === '--url' && next) {
options.url = next;
index += 1;
continue;
}
if (arg === '--method' && next) {
options.method = next.toUpperCase();
index += 1;
continue;
}
if (arg === '--duration' && next) {
options.duration = Number(next);
index += 1;
continue;
}
if (arg === '--concurrency' && next) {
options.concurrency = Number(next);
index += 1;
continue;
}
if (arg === '--timeout' && next) {
options.timeout = Number(next);
index += 1;
continue;
}
if (arg === '--warmup' && next) {
options.warmup = Number(next);
index += 1;
continue;
}
if (arg === '--header' && next) {
const separatorIndex = next.indexOf(':');
if (separatorIndex === -1) {
throw new Error(`Invalid header format: ${next}`);
}
const key = next.slice(0, separatorIndex).trim();
const value = next.slice(separatorIndex + 1).trim();
options.headers[key] = value;
index += 1;
continue;
}
if (arg === '--body' && next) {
options.body = next;
index += 1;
continue;
}
if (arg === '--body-file' && next) {
options.body = readFileSync(next, 'utf8');
index += 1;
continue;
}
}
if (!Number.isFinite(options.duration) || options.duration <= 0) {
throw new Error('duration must be a positive number of seconds');
}
if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {
throw new Error('concurrency must be a positive integer');
}
if (!Number.isFinite(options.timeout) || options.timeout <= 0) {
throw new Error('timeout must be a positive number of milliseconds');
}
if (!Number.isInteger(options.warmup) || options.warmup < 0) {
throw new Error('warmup must be zero or a positive integer');
}
if (options.body && !options.headers['Content-Type']) {
options.headers['Content-Type'] = 'application/json';
}
return options;
}
function percentile(sortedValues, p) {
if (!sortedValues.length) {
return 0;
}
const rank = Math.ceil((p / 100) * sortedValues.length) - 1;
const index = Math.min(sortedValues.length - 1, Math.max(0, rank));
return sortedValues[index];
}
function average(values) {
if (!values.length) {
return 0;
}
const total = values.reduce((sum, value) => sum + value, 0);
return total / values.length;
}
function printUsage() {
console.log('Usage: node scripts/load-test.js --url <url> [--duration 15] [--concurrency 20]');
console.log('Optional: --method POST --header "Authorization: Bearer <token>" --body "{\"key\":\"value\"}"');
}
async function warmup(options) {
if (options.warmup === 0) {
return;
}
for (let index = 0; index < options.warmup; index += 1) {
const response = await fetch(options.url, {
method: options.method,
headers: options.headers,
body: options.body,
});
await response.arrayBuffer();
}
}
async function runWorker(options, results, endAt) {
while (Date.now() < endAt) {
const startedAt = performance.now();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), options.timeout);
try {
const response = await fetch(options.url, {
method: options.method,
headers: options.headers,
body: options.body,
signal: controller.signal,
});
const payload = await response.arrayBuffer();
const durationMs = performance.now() - startedAt;
results.latencies.push(durationMs);
results.totalRequests += 1;
results.totalBytes += payload.byteLength;
const statusKey = String(response.status);
results.statusCounts[statusKey] = (results.statusCounts[statusKey] ?? 0) + 1;
if (response.ok) {
results.successCount += 1;
} else {
results.non2xxCount += 1;
}
} catch (error) {
const durationMs = performance.now() - startedAt;
results.latencies.push(durationMs);
results.totalRequests += 1;
if (error && typeof error === 'object' && error.name === 'AbortError') {
results.timeoutCount += 1;
} else {
results.networkErrorCount += 1;
}
} finally {
clearTimeout(timeoutId);
}
}
}
function buildSummary(options, results, totalDurationMs) {
const latencies = [...results.latencies].sort((left, right) => left - right);
const requestsPerSecond = results.totalRequests / (totalDurationMs / 1000);
const successRate = results.totalRequests === 0 ? 0 : (results.successCount / results.totalRequests) * 100;
return {
target: options.url,
method: options.method,
durationSeconds: Number((totalDurationMs / 1000).toFixed(2)),
concurrency: options.concurrency,
totalRequests: results.totalRequests,
successCount: results.successCount,
non2xxCount: results.non2xxCount,
timeoutCount: results.timeoutCount,
networkErrorCount: results.networkErrorCount,
requestsPerSecond: Number(requestsPerSecond.toFixed(2)),
successRate: Number(successRate.toFixed(2)),
transferredBytes: results.totalBytes,
latencyMs: {
min: Number((latencies[0] ?? 0).toFixed(2)),
avg: Number(average(latencies).toFixed(2)),
p50: Number(percentile(latencies, 50).toFixed(2)),
p90: Number(percentile(latencies, 90).toFixed(2)),
p95: Number(percentile(latencies, 95).toFixed(2)),
p99: Number(percentile(latencies, 99).toFixed(2)),
max: Number((latencies[latencies.length - 1] ?? 0).toFixed(2)),
},
statusCounts: results.statusCounts,
};
}
async function main() {
if (process.argv.includes('--help')) {
printUsage();
return;
}
const options = parseArgs(process.argv.slice(2));
console.log(`Warmup: ${options.warmup} request(s) to ${options.url}`);
await warmup(options);
const results = {
latencies: [],
totalRequests: 0,
successCount: 0,
non2xxCount: 0,
timeoutCount: 0,
networkErrorCount: 0,
totalBytes: 0,
statusCounts: {},
};
const startedAt = performance.now();
const endAt = Date.now() + options.duration * 1000;
await Promise.all(
Array.from({ length: options.concurrency }, () => runWorker(options, results, endAt)),
);
const totalDurationMs = performance.now() - startedAt;
const summary = buildSummary(options, results, totalDurationMs);
console.log('');
console.log(JSON.stringify(summary, null, 2));
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
});