Skip to content

Instantly share code, notes, and snippets.

@sankalpmukim
Last active July 26, 2025 15:11
Show Gist options
  • Save sankalpmukim/d46bb8f298d40fc97b48478c38611f75 to your computer and use it in GitHub Desktop.
Save sankalpmukim/d46bb8f298d40fc97b48478c38611f75 to your computer and use it in GitHub Desktop.
bun load-test.ts --count 4000 --url https://example.com
#!/usr/bin/env node
import * as fs from "fs";
import * as path from "path";
import { Command } from "commander";
import fetch from "node-fetch";
interface ResponseResult {
statusCode: number;
seconds: number;
requestIndex: number;
}
const results: ResponseResult[] = [];
let startTimeGlobal: number = 0;
let endTimeGlobal: number = 0;
async function performLoadTest(url: string, count: number): Promise<void> {
console.log(`Starting load test: ${count} requests to ${url}`);
startTimeGlobal = Date.now();
const promises: Promise<void>[] = [];
for (let i = 0; i < count; i++) {
const startTime = Date.now();
const requestIndex = i + 1;
const promise = fetch(url)
.then((response) => {
const endTime = Date.now();
const seconds = (endTime - startTime) / 1000;
results.push({
statusCode: response.status,
seconds: seconds,
requestIndex: requestIndex,
});
console.log(
`Request ${requestIndex}: Status ${
response.status
}, Time: ${seconds.toFixed(3)}s`
);
})
.catch((error) => {
const endTime = Date.now();
const seconds = (endTime - startTime) / 1000;
results.push({
statusCode: 0, // Use 0 for network errors
seconds: seconds,
requestIndex: requestIndex,
});
console.log(
`Request ${requestIndex}: Error - ${
error.message
}, Time: ${seconds.toFixed(3)}s`
);
});
promises.push(promise);
}
// Wait for all requests to complete
await Promise.all(promises);
endTimeGlobal = Date.now();
}
function getStatusColor(statusCode: number): string {
if (statusCode === 0) return "#808080"; // Gray for network errors
const firstDigit = Math.floor(statusCode / 100);
switch (firstDigit) {
case 2:
return "#28a745"; // Green for 2xx
case 3:
return "#17a2b8"; // Blue for 3xx
case 4:
return "#ffc107"; // Yellow for 4xx
case 5:
return "#dc3545"; // Red for 5xx
default:
return "#6c757d"; // Gray for others
}
}
function generateLineChart(): string {
// Sort results by request index to maintain order
const sortedResults = [...results].sort(
(a, b) => a.requestIndex - b.requestIndex
);
const datasets = new Map<string, any>();
sortedResults.forEach((result) => {
const color = getStatusColor(result.statusCode);
const label =
result.statusCode === 0
? "Network Error"
: `${Math.floor(result.statusCode / 100)}xx`;
if (!datasets.has(label)) {
datasets.set(label, {
label: label,
data: [],
backgroundColor: color,
borderColor: color,
fill: false,
tension: 0.1,
});
}
datasets.get(label).data.push({
x: result.requestIndex,
y: result.seconds,
});
});
return `
<!DOCTYPE html>
<html>
<head>
<title>Load Test - Response Time Chart</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div style="width: 100%; height: 600px;">
<canvas id="lineChart"></canvas>
</div>
<script>
const ctx = document.getElementById('lineChart').getContext('2d');
new Chart(ctx, {
type: 'scatter',
data: {
datasets: ${JSON.stringify(Array.from(datasets.values()))}
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Response Time by Request Index'
},
legend: {
display: true
}
},
scales: {
x: {
type: 'linear',
position: 'bottom',
title: {
display: true,
text: 'Request Index'
}
},
y: {
title: {
display: true,
text: 'Response Time (seconds)'
}
}
}
}
});
</script>
</body>
</html>`;
}
function generatePieChart(): string {
const statusCounts: { [key: string]: number } = {};
const colors: string[] = [];
results.forEach((result) => {
const label =
result.statusCode === 0 ? "Network Error" : `HTTP ${result.statusCode}`;
statusCounts[label] = (statusCounts[label] || 0) + 1;
});
const labels = Object.keys(statusCounts);
const data = Object.values(statusCounts);
// Generate colors for each status code
labels.forEach((label) => {
if (label === "Network Error") {
colors.push("#808080");
} else {
const statusCode = parseInt(label.replace("HTTP ", ""));
colors.push(getStatusColor(statusCode));
}
});
return `
<!DOCTYPE html>
<html>
<head>
<title>Load Test - Status Code Distribution</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div style="width: 100%; height: 600px; display: flex; justify-content: center; align-items: center;">
<canvas id="pieChart" style="max-width: 600px; max-height: 600px;"></canvas>
</div>
<script>
const ctx = document.getElementById('pieChart').getContext('2d');
new Chart(ctx, {
type: 'pie',
data: {
labels: ${JSON.stringify(labels)},
datasets: [{
data: ${JSON.stringify(data)},
backgroundColor: ${JSON.stringify(colors)},
borderWidth: 2,
borderColor: '#fff'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Status Code Distribution'
},
legend: {
position: 'right'
}
}
}
});
</script>
</body>
</html>`;
}
function exportGraphs(): void {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const outputDir = `load-test-results-${timestamp}`;
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Generate and save line chart
const lineChartHtml = generateLineChart();
fs.writeFileSync(
path.join(outputDir, "response-time-chart.html"),
lineChartHtml
);
// Generate and save pie chart
const pieChartHtml = generatePieChart();
fs.writeFileSync(
path.join(outputDir, "status-code-distribution.html"),
pieChartHtml
);
// Export raw data as JSON
const rawData = {
results: results,
summary: {
totalRequests: results.length,
averageTime:
results.reduce((sum, r) => sum + r.seconds, 0) / results.length,
statusCounts: results.reduce((acc: { [key: number]: number }, r) => {
acc[r.statusCode] = (acc[r.statusCode] || 0) + 1;
return acc;
}, {}),
},
};
fs.writeFileSync(
path.join(outputDir, "raw-data.json"),
JSON.stringify(rawData, null, 2)
);
console.log(`\nGraphs exported to: ${outputDir}/`);
console.log(
`- response-time-chart.html: Line chart showing response times by request index`
);
console.log(
`- status-code-distribution.html: Pie chart showing status code distribution`
);
console.log(`- raw-data.json: Raw test data and summary`);
}
function printSummary(): void {
console.log("\n--- SUMMARY ---");
console.log(`Total requests: ${results.length}`);
console.log(
`Total time: ${(endTimeGlobal - startTimeGlobal) / 1000} seconds`
);
// Count status codes
const statusCounts: { [key: number]: number } = {};
let totalTime = 0;
results.forEach((result) => {
statusCounts[result.statusCode] =
(statusCounts[result.statusCode] || 0) + 1;
totalTime += result.seconds;
});
// Print status code distribution
console.log("\nStatus Code Distribution:");
Object.entries(statusCounts)
.sort(([a], [b]) => Number(a) - Number(b))
.forEach(([statusCode, count]) => {
const status =
statusCode === "0" ? "Network Error" : `HTTP ${statusCode}`;
console.log(` ${status}: ${count} requests`);
});
// Print non-200 status codes
const non200Count = results.filter((r) => r.statusCode !== 200).length;
console.log(
`\nNon-200 responses: ${non200Count} (${(
(non200Count / results.length) *
100
).toFixed(1)}%)`
);
// Print timing statistics
const averageTime = totalTime / results.length;
const sortedTimes = results.map((r) => r.seconds).sort((a, b) => a - b);
const minTime = sortedTimes[0];
const maxTime = sortedTimes[sortedTimes.length - 1];
const medianTime = sortedTimes[Math.floor(sortedTimes.length / 2)];
console.log(`\nTiming Statistics:`);
console.log(` Average: ${averageTime.toFixed(3)}s`);
console.log(` Median: ${medianTime.toFixed(3)}s`);
console.log(` Min: ${minTime.toFixed(3)}s`);
console.log(` Max: ${maxTime.toFixed(3)}s`);
}
async function main(): Promise<void> {
const program = new Command();
program
.name("load-test")
.description("Simple load testing tool")
.requiredOption(
"--count <number>",
"Number of requests to make",
(value) => {
const num = parseInt(value, 10);
if (isNaN(num) || num <= 0) {
throw new Error("Count must be a positive number");
}
return num;
}
)
.requiredOption("--url <string>", "URL to test")
.parse();
const options = program.opts();
try {
await performLoadTest(options.url, options.count);
printSummary();
exportGraphs();
} catch (error) {
console.error("Error during load test:", error);
process.exit(1);
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment