@pingpolls/redisq 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ import { RedisQ } from "../app";
2
+ self.addEventListener("message", async (event) => {
3
+ if (event.data.type === "start") {
4
+ const { messagesPerWorker, testMessage, qname, redisConfig } = event.data.data;
5
+ try {
6
+ const queue = new RedisQ(redisConfig);
7
+ const latencies = [];
8
+ const CHUNK_SIZE = 1000;
9
+ for (let i = 0; i < messagesPerWorker; i += CHUNK_SIZE) {
10
+ const promises = [];
11
+ const chunkEnd = Math.min(i + CHUNK_SIZE, messagesPerWorker);
12
+ for (let j = i; j < chunkEnd; j++) {
13
+ const start = performance.now();
14
+ promises.push(queue
15
+ .sendMessage({
16
+ message: testMessage,
17
+ qname,
18
+ })
19
+ .then(() => {
20
+ const latency = performance.now() - start;
21
+ latencies.push(latency);
22
+ }));
23
+ }
24
+ await Promise.all(promises);
25
+ }
26
+ await queue.close();
27
+ self.postMessage({
28
+ data: { latencies },
29
+ type: "result",
30
+ });
31
+ }
32
+ catch (error) {
33
+ self.postMessage({
34
+ data: { error: error.message },
35
+ type: "error",
36
+ });
37
+ }
38
+ }
39
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -1,364 +1,219 @@
1
- #!/usr/bin/env bun
2
-
3
- import type { Worker } from "bun";
4
- import { RedisQ } from "../app";
5
- import mediumMsg from "./medium.txt";
6
- import smallMsg from "./small.txt";
7
- import tinyMsg from "./tiny.txt";
8
-
9
- const COLORS = {
10
- blue: "\x1b[34m",
11
- bright: "\x1b[1m",
12
- cyan: "\x1b[36m",
13
- green: "\x1b[32m",
14
- magenta: "\x1b[35m",
15
- red: "\x1b[31m",
16
- reset: "\x1b[0m",
17
- yellow: "\x1b[33m",
18
- };
19
-
20
- const WORKER_COUNT = 8;
21
- const CONCURRENCY = 2;
22
- const MSG_COUNT = 100_000;
23
-
24
- interface StressTestConfig {
25
- messageCount: number;
26
- workerCount: number;
27
- testMessage: string;
28
- concurrency: number;
29
- }
30
-
31
- interface TestResults {
32
- totalMessages: number;
33
- duration: number;
34
- throughput: number;
35
- latencies: number[];
36
- p50: number;
37
- p95: number;
38
- p99: number;
39
- min: number;
40
- max: number;
41
- avg: number;
42
- }
43
-
44
- interface WorkerMessage {
45
- type: "start" | "result" | "error";
46
- data?: {
47
- workerIndex?: number;
48
- messagesPerWorker?: number;
49
- testMessage?: string;
50
- qname?: string;
51
- redisConfig?: {
52
- host: string;
53
- port: string;
54
- namespace: string;
55
- };
56
- latencies?: number[];
57
- error?: string;
58
- };
59
- }
60
-
61
- function log(color: keyof typeof COLORS, message: string) {
62
- console.log(`${COLORS[color]}${message}${COLORS.reset}`);
63
- }
64
-
65
- function calculatePercentile(sorted: number[], percentile: number): number {
66
- const index = Math.ceil((percentile / 100) * sorted.length) - 1;
67
- return sorted[Math.max(0, index)] ?? 0;
68
- }
69
-
70
- function calculateStats(
71
- latencies: number[],
72
- ): Omit<TestResults, "totalMessages" | "duration" | "throughput"> {
73
- const sorted = [...latencies].sort((a, b) => a - b);
74
- const sum = latencies.reduce((acc, val) => acc + val, 0);
75
-
76
- return {
77
- avg: sum / latencies.length,
78
- latencies,
79
- max: sorted[sorted.length - 1] ?? 0,
80
- min: sorted[0] ?? 0,
81
- p50: calculatePercentile(sorted, 50),
82
- p95: calculatePercentile(sorted, 95),
83
- p99: calculatePercentile(sorted, 99),
84
- };
85
- }
86
-
87
- function printResults(testName: string, results: TestResults) {
88
- log("bright", `\n${"=".repeat(60)}`);
89
- log("cyan", ` ${testName}`);
90
- log("bright", "=".repeat(60));
91
-
92
- log("green", "\n📊 Overall Performance:");
93
- console.log(
94
- ` Total Messages: ${results.totalMessages.toLocaleString()}`,
95
- );
96
- console.log(` Duration: ${results.duration.toFixed(2)}s`);
97
- console.log(` Throughput: ${results.throughput.toFixed(2)} msg/s`);
98
-
99
- log("yellow", "\n⚡ Latency Statistics (ms):");
100
- console.log(` Min: ${results.min.toFixed(2)} ms`);
101
- console.log(` Average: ${results.avg.toFixed(2)} ms`);
102
- console.log(` Median (p50): ${results.p50.toFixed(2)} ms`);
103
- console.log(` p95: ${results.p95.toFixed(2)} ms`);
104
- console.log(` p99: ${results.p99.toFixed(2)} ms`);
105
- console.log(` Max: ${results.max.toFixed(2)} ms`);
106
- }
107
-
108
- async function testRegularQueueParallel(
109
- config: StressTestConfig,
110
- ): Promise<TestResults> {
111
- const queue = new RedisQ({
112
- host: process.env.REDIS_HOST || "127.0.0.1",
113
- namespace: "stress-test",
114
- port: process.env.REDIS_PORT || "6379",
115
- });
116
-
117
- const qname = "stress-regular";
118
- await queue.createQueue({ maxRetries: 0, maxsize: 150_000, qname });
119
-
120
- const allLatencies: number[] = [];
121
- let receivedCount = 0;
122
- const messagesPerWorker = Math.floor(
123
- config.messageCount / config.workerCount,
124
- );
125
- const actualMessageCount = messagesPerWorker * config.workerCount;
126
-
127
- const workerPromise = new Promise<void>((resolve) => {
128
- queue.startWorker(
129
- qname,
130
- async () => {
131
- receivedCount++;
132
- if (receivedCount === actualMessageCount) {
133
- resolve();
134
- }
135
- return { success: true };
136
- },
137
- { concurrency: config.concurrency, silent: true },
138
- );
139
- });
140
-
141
- log(
142
- "blue",
143
- `\n🚀 Spawning ${config.workerCount} workers to send ${actualMessageCount.toLocaleString()} messages...`,
144
- );
145
- const startTime = performance.now();
146
-
147
- const workers: Worker[] = [];
148
- const workerPromises: Promise<number[]>[] = [];
149
-
150
- for (let i = 0; i < config.workerCount; i++) {
151
- const worker = new Worker(
152
- new URL("./stress-worker.ts", import.meta.url).href,
153
- );
154
- workers.push(worker);
155
-
156
- const workerPromise = new Promise<number[]>((resolve, reject) => {
157
- worker.addEventListener(
158
- "message",
159
- (event: MessageEvent<WorkerMessage>) => {
160
- if (event.data.type === "result") {
161
- resolve(event.data.data?.latencies || []);
162
- worker.terminate();
163
- } else if (event.data.type === "error") {
164
- reject(
165
- new Error(event.data.data?.error || "Worker error"),
166
- );
167
- worker.terminate();
168
- }
169
- },
170
- );
171
-
172
- worker.addEventListener("error", (error) => {
173
- reject(error);
174
- worker.terminate();
175
- });
176
-
177
- worker.postMessage({
178
- data: {
179
- messagesPerWorker,
180
- qname,
181
- redisConfig: {
182
- host: process.env.REDIS_HOST || "127.0.0.1",
183
- namespace: "stress-test",
184
- port: process.env.REDIS_PORT || "6379",
185
- },
186
- testMessage: config.testMessage,
187
- workerIndex: i,
188
- },
189
- type: "start",
190
- });
191
- });
192
-
193
- workerPromises.push(workerPromise);
194
- }
195
-
196
- const workerResults = await Promise.all(workerPromises);
197
-
198
- for (const latencies of workerResults) {
199
- allLatencies.push(...latencies);
200
- }
201
-
202
- const sendDuration = performance.now() - startTime;
203
- log("green", `✓ All messages sent in ${sendDuration.toFixed(2)}ms`);
204
-
205
- log("blue", "\n Waiting for all messages to be processed...");
206
- await workerPromise;
207
-
208
- const duration = (performance.now() - startTime) / 1000;
209
- const throughput = actualMessageCount / duration;
210
-
211
- await queue.close();
212
-
213
- const stats = calculateStats(allLatencies);
214
-
215
- return {
216
- duration,
217
- throughput,
218
- totalMessages: actualMessageCount,
219
- ...stats,
220
- };
221
- }
222
-
223
- async function cleanup() {
224
- log("blue", "\n🧹 Cleaning up test data...");
225
-
226
- const redis = new Bun.RedisClient(
227
- `redis://${process.env.REDIS_HOST || "127.0.0.1"}:${process.env.REDIS_PORT || "6379"}`,
228
- );
229
-
230
- let cursor = "0";
231
- const pattern = "stress-test:*";
232
- let deletedCount = 0;
233
-
234
- do {
235
- const result = await redis.scan(cursor, "MATCH", pattern);
236
- const [nextCursor, keys] = result as [string, string[]];
237
-
238
- cursor = nextCursor;
239
-
240
- if (keys.length > 0) {
241
- await redis.del(...keys);
242
- deletedCount += keys.length;
243
- }
244
- } while (cursor !== "0");
245
-
246
- redis.close();
247
-
248
- log("green", `✓ Cleaned up ${deletedCount} keys`);
249
- }
250
-
251
- async function main() {
252
- log(
253
- "bright",
254
- "\n╔═══════════════════════════════════════════════════════════╗",
255
- );
256
- log(
257
- "bright",
258
- "║ RedisQ Stress Test & Benchmark Tool ║",
259
- );
260
- log(
261
- "bright",
262
- "╚═══════════════════════════════════════════════════════════╝",
263
- );
264
-
265
- await cleanup();
266
-
267
- const availableCPUs = navigator.hardwareConcurrency;
268
- log("yellow", `\n💻 Detected ${availableCPUs} CPU cores`);
269
-
270
- log("blue", `\n🔧 Using ${WORKER_COUNT} workers for tests`);
271
-
272
- try {
273
- log("cyan", "\n📋 Test 1: Tiny messages (100 bytes)");
274
- const test1Results = await testRegularQueueParallel({
275
- concurrency: CONCURRENCY,
276
- messageCount: MSG_COUNT,
277
- testMessage: tinyMsg,
278
- workerCount: WORKER_COUNT,
279
- });
280
- printResults(
281
- "Test 1: Tiny Messages (100K msgs, 100 bytes)",
282
- test1Results,
283
- );
284
-
285
- await cleanup();
286
-
287
- log("cyan", "\n📋 Test 2: Small messages (1KB)");
288
- const test2Results = await testRegularQueueParallel({
289
- concurrency: CONCURRENCY,
290
- messageCount: MSG_COUNT,
291
- testMessage: smallMsg,
292
- workerCount: WORKER_COUNT,
293
- });
294
- printResults("Test 2: Small Messages (100K msgs, 1KB)", test2Results);
295
-
296
- await cleanup();
297
-
298
- log("cyan", "\n📋 Test 3: Medium messages (10KB)");
299
- const test3Results = await testRegularQueueParallel({
300
- concurrency: CONCURRENCY,
301
- messageCount: MSG_COUNT,
302
- testMessage: mediumMsg,
303
- workerCount: WORKER_COUNT,
304
- });
305
- printResults("Test 3: Medium Messages (100K msgs, 10KB)", test3Results);
306
-
307
- await cleanup();
308
-
309
- // Calculate combined averages
310
- const avgThroughput =
311
- (test1Results.throughput +
312
- test2Results.throughput +
313
- test3Results.throughput) /
314
- 3;
315
- const avgP50 =
316
- (test1Results.p50 + test2Results.p50 + test3Results.p50) / 3;
317
- const avgP95 =
318
- (test1Results.p95 + test2Results.p95 + test3Results.p95) / 3;
319
- const avgP99 =
320
- (test1Results.p99 + test2Results.p99 + test3Results.p99) / 3;
321
-
322
- // Summary
323
- log("bright", `\n${"=".repeat(60)}`);
324
- log("magenta", " 📈 BENCHMARK SUMMARY");
325
- log("bright", "=".repeat(60));
326
-
327
- console.log("\n Individual Queue Performance:");
328
- console.log(
329
- ` - Tiny messages (100B): ${test1Results.throughput.toFixed(0)} msg/s (p50: ${test1Results.p50.toFixed(2)}ms)`,
330
- );
331
- console.log(
332
- ` - Small messages (1KB): ${test2Results.throughput.toFixed(0)} msg/s (p50: ${test2Results.p50.toFixed(2)}ms)`,
333
- );
334
- console.log(
335
- ` - Medium messages (10KB): ${test3Results.throughput.toFixed(0)} msg/s (p50: ${test3Results.p50.toFixed(2)}ms)`,
336
- );
337
-
338
- log("bright", `\n${"=".repeat(60)}`);
339
-
340
- log("green", "\n✅ All stress tests completed successfully!");
341
-
342
- log(
343
- "yellow",
344
- "\n💡 To update README.md Performance section, use these values (averaged across all tests):",
345
- );
346
- console.log("\n Overall:");
347
- log(
348
- "cyan",
349
- ` - **Throughput**: ~${Math.round(avgThroughput).toLocaleString()} messages/second`,
350
- );
351
- log("cyan", ` - **Latency (p50)**: ${avgP50.toFixed(2)} ms`);
352
- log("cyan", ` - **Latency (p95)**: ${avgP95.toFixed(2)} ms`);
353
- log("cyan", ` - **Latency (p99)**: ${avgP99.toFixed(2)} ms`);
354
- } catch (error) {
355
- log(
356
- "red",
357
- `\n❌ Error during stress test: ${(error as Error).message}`,
358
- );
359
- log("red", (error as Error).stack || "");
360
- process.exit(1);
361
- }
362
- }
363
-
364
- main();
1
+ #!/usr/bin/env bun
2
+ import { RedisQ } from "../app";
3
+ import mediumMsg from "./medium.txt";
4
+ import smallMsg from "./small.txt";
5
+ import tinyMsg from "./tiny.txt";
6
+ const COLORS = {
7
+ blue: "\x1b[34m",
8
+ bright: "\x1b[1m",
9
+ cyan: "\x1b[36m",
10
+ green: "\x1b[32m",
11
+ magenta: "\x1b[35m",
12
+ red: "\x1b[31m",
13
+ reset: "\x1b[0m",
14
+ yellow: "\x1b[33m",
15
+ };
16
+ const WORKER_COUNT = 8;
17
+ const CONCURRENCY = 2;
18
+ const MSG_COUNT = 100_000;
19
+ function log(color, message) {
20
+ console.log(`${COLORS[color]}${message}${COLORS.reset}`);
21
+ }
22
+ function calculatePercentile(sorted, percentile) {
23
+ const index = Math.ceil((percentile / 100) * sorted.length) - 1;
24
+ return sorted[Math.max(0, index)] ?? 0;
25
+ }
26
+ function calculateStats(latencies) {
27
+ const sorted = [...latencies].sort((a, b) => a - b);
28
+ const sum = latencies.reduce((acc, val) => acc + val, 0);
29
+ return {
30
+ avg: sum / latencies.length,
31
+ latencies,
32
+ max: sorted[sorted.length - 1] ?? 0,
33
+ min: sorted[0] ?? 0,
34
+ p50: calculatePercentile(sorted, 50),
35
+ p95: calculatePercentile(sorted, 95),
36
+ p99: calculatePercentile(sorted, 99),
37
+ };
38
+ }
39
+ function printResults(testName, results) {
40
+ log("bright", `\n${"=".repeat(60)}`);
41
+ log("cyan", ` ${testName}`);
42
+ log("bright", "=".repeat(60));
43
+ log("green", "\n📊 Overall Performance:");
44
+ console.log(` Total Messages: ${results.totalMessages.toLocaleString()}`);
45
+ console.log(` Duration: ${results.duration.toFixed(2)}s`);
46
+ console.log(` Throughput: ${results.throughput.toFixed(2)} msg/s`);
47
+ log("yellow", "\n⚡ Latency Statistics (ms):");
48
+ console.log(` Min: ${results.min.toFixed(2)} ms`);
49
+ console.log(` Average: ${results.avg.toFixed(2)} ms`);
50
+ console.log(` Median (p50): ${results.p50.toFixed(2)} ms`);
51
+ console.log(` p95: ${results.p95.toFixed(2)} ms`);
52
+ console.log(` p99: ${results.p99.toFixed(2)} ms`);
53
+ console.log(` Max: ${results.max.toFixed(2)} ms`);
54
+ }
55
+ async function testRegularQueueParallel(config) {
56
+ const queue = new RedisQ({
57
+ host: process.env.REDIS_HOST || "127.0.0.1",
58
+ namespace: "stress-test",
59
+ port: process.env.REDIS_PORT || "6379",
60
+ });
61
+ const qname = "stress-regular";
62
+ await queue.createQueue({ maxRetries: 0, maxsize: 150_000, qname });
63
+ const allLatencies = [];
64
+ let receivedCount = 0;
65
+ const messagesPerWorker = Math.floor(config.messageCount / config.workerCount);
66
+ const actualMessageCount = messagesPerWorker * config.workerCount;
67
+ const workerPromise = new Promise((resolve) => {
68
+ queue.startWorker(qname, async () => {
69
+ receivedCount++;
70
+ if (receivedCount === actualMessageCount) {
71
+ resolve();
72
+ }
73
+ return { success: true };
74
+ }, { concurrency: config.concurrency, silent: true });
75
+ });
76
+ log("blue", `\n🚀 Spawning ${config.workerCount} workers to send ${actualMessageCount.toLocaleString()} messages...`);
77
+ const startTime = performance.now();
78
+ const workers = [];
79
+ const workerPromises = [];
80
+ for (let i = 0; i < config.workerCount; i++) {
81
+ const worker = new Worker(new URL("./stress-worker.ts", import.meta.url).href);
82
+ workers.push(worker);
83
+ const workerPromise = new Promise((resolve, reject) => {
84
+ worker.addEventListener("message", (event) => {
85
+ if (event.data.type === "result") {
86
+ resolve(event.data.data?.latencies || []);
87
+ worker.terminate();
88
+ }
89
+ else if (event.data.type === "error") {
90
+ reject(new Error(event.data.data?.error || "Worker error"));
91
+ worker.terminate();
92
+ }
93
+ });
94
+ worker.addEventListener("error", (error) => {
95
+ reject(error);
96
+ worker.terminate();
97
+ });
98
+ worker.postMessage({
99
+ data: {
100
+ messagesPerWorker,
101
+ qname,
102
+ redisConfig: {
103
+ host: process.env.REDIS_HOST || "127.0.0.1",
104
+ namespace: "stress-test",
105
+ port: process.env.REDIS_PORT || "6379",
106
+ },
107
+ testMessage: config.testMessage,
108
+ workerIndex: i,
109
+ },
110
+ type: "start",
111
+ });
112
+ });
113
+ workerPromises.push(workerPromise);
114
+ }
115
+ const workerResults = await Promise.all(workerPromises);
116
+ for (const latencies of workerResults) {
117
+ allLatencies.push(...latencies);
118
+ }
119
+ const sendDuration = performance.now() - startTime;
120
+ log("green", `✓ All messages sent in ${sendDuration.toFixed(2)}ms`);
121
+ log("blue", "\n⏳ Waiting for all messages to be processed...");
122
+ await workerPromise;
123
+ const duration = (performance.now() - startTime) / 1000;
124
+ const throughput = actualMessageCount / duration;
125
+ await queue.close();
126
+ const stats = calculateStats(allLatencies);
127
+ return {
128
+ duration,
129
+ throughput,
130
+ totalMessages: actualMessageCount,
131
+ ...stats,
132
+ };
133
+ }
134
+ async function cleanup() {
135
+ log("blue", "\n🧹 Cleaning up test data...");
136
+ const redis = new Bun.RedisClient(`redis://${process.env.REDIS_HOST || "127.0.0.1"}:${process.env.REDIS_PORT || "6379"}`);
137
+ let cursor = "0";
138
+ const pattern = "stress-test:*";
139
+ let deletedCount = 0;
140
+ do {
141
+ const result = await redis.scan(cursor, "MATCH", pattern);
142
+ const [nextCursor, keys] = result;
143
+ cursor = nextCursor;
144
+ if (keys.length > 0) {
145
+ await redis.del(...keys);
146
+ deletedCount += keys.length;
147
+ }
148
+ } while (cursor !== "0");
149
+ redis.close();
150
+ log("green", `✓ Cleaned up ${deletedCount} keys`);
151
+ }
152
+ async function main() {
153
+ log("bright", "\n╔═══════════════════════════════════════════════════════════╗");
154
+ log("bright", "║ RedisQ Stress Test & Benchmark Tool ║");
155
+ log("bright", "╚═══════════════════════════════════════════════════════════╝");
156
+ await cleanup();
157
+ const availableCPUs = navigator.hardwareConcurrency;
158
+ log("yellow", `\n💻 Detected ${availableCPUs} CPU cores`);
159
+ log("blue", `\n🔧 Using ${WORKER_COUNT} workers for tests`);
160
+ try {
161
+ log("cyan", "\n📋 Test 1: Tiny messages (100 bytes)");
162
+ const test1Results = await testRegularQueueParallel({
163
+ concurrency: CONCURRENCY,
164
+ messageCount: MSG_COUNT,
165
+ testMessage: tinyMsg,
166
+ workerCount: WORKER_COUNT,
167
+ });
168
+ printResults("Test 1: Tiny Messages (100K msgs, 100 bytes)", test1Results);
169
+ await cleanup();
170
+ log("cyan", "\n📋 Test 2: Small messages (1KB)");
171
+ const test2Results = await testRegularQueueParallel({
172
+ concurrency: CONCURRENCY,
173
+ messageCount: MSG_COUNT,
174
+ testMessage: smallMsg,
175
+ workerCount: WORKER_COUNT,
176
+ });
177
+ printResults("Test 2: Small Messages (100K msgs, 1KB)", test2Results);
178
+ await cleanup();
179
+ log("cyan", "\n📋 Test 3: Medium messages (10KB)");
180
+ const test3Results = await testRegularQueueParallel({
181
+ concurrency: CONCURRENCY,
182
+ messageCount: MSG_COUNT,
183
+ testMessage: mediumMsg,
184
+ workerCount: WORKER_COUNT,
185
+ });
186
+ printResults("Test 3: Medium Messages (100K msgs, 10KB)", test3Results);
187
+ await cleanup();
188
+ // Calculate combined averages
189
+ const avgThroughput = (test1Results.throughput +
190
+ test2Results.throughput +
191
+ test3Results.throughput) /
192
+ 3;
193
+ const avgP50 = (test1Results.p50 + test2Results.p50 + test3Results.p50) / 3;
194
+ const avgP95 = (test1Results.p95 + test2Results.p95 + test3Results.p95) / 3;
195
+ const avgP99 = (test1Results.p99 + test2Results.p99 + test3Results.p99) / 3;
196
+ // Summary
197
+ log("bright", `\n${"=".repeat(60)}`);
198
+ log("magenta", " 📈 BENCHMARK SUMMARY");
199
+ log("bright", "=".repeat(60));
200
+ console.log("\n Individual Queue Performance:");
201
+ console.log(` - Tiny messages (100B): ${test1Results.throughput.toFixed(0)} msg/s (p50: ${test1Results.p50.toFixed(2)}ms)`);
202
+ console.log(` - Small messages (1KB): ${test2Results.throughput.toFixed(0)} msg/s (p50: ${test2Results.p50.toFixed(2)}ms)`);
203
+ console.log(` - Medium messages (10KB): ${test3Results.throughput.toFixed(0)} msg/s (p50: ${test3Results.p50.toFixed(2)}ms)`);
204
+ log("bright", `\n${"=".repeat(60)}`);
205
+ log("green", "\n All stress tests completed successfully!");
206
+ log("yellow", "\n💡 To update README.md Performance section, use these values (averaged across all tests):");
207
+ console.log("\n Overall:");
208
+ log("cyan", ` - **Throughput**: ~${Math.round(avgThroughput).toLocaleString()} messages/second`);
209
+ log("cyan", ` - **Latency (p50)**: ${avgP50.toFixed(2)} ms`);
210
+ log("cyan", ` - **Latency (p95)**: ${avgP95.toFixed(2)} ms`);
211
+ log("cyan", ` - **Latency (p99)**: ${avgP99.toFixed(2)} ms`);
212
+ }
213
+ catch (error) {
214
+ log("red", `\n❌ Error during stress test: ${error.message}`);
215
+ log("red", error.stack || "");
216
+ process.exit(1);
217
+ }
218
+ }
219
+ main();