kukuy 1.4.0

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.
Files changed (41) hide show
  1. package/.ctagsd/ctagsd.json +954 -0
  2. package/.ctagsd/file_list.txt +100 -0
  3. package/.ctagsd/tags.db +0 -0
  4. package/CHANGELOG.md +101 -0
  5. package/LICENSE +680 -0
  6. package/README.md +251 -0
  7. package/captura.png +0 -0
  8. package/kukuy.js +23 -0
  9. package/kukuy.workspace +11 -0
  10. package/package.json +26 -0
  11. package/restart-balancer.sh +10 -0
  12. package/routes.json +14 -0
  13. package/scripts/load_test.py +151 -0
  14. package/servers.json +19 -0
  15. package/servers_real.json +19 -0
  16. package/src/algorithms/AlgorithmManager.js +85 -0
  17. package/src/algorithms/IPHashAlgorithm.js +131 -0
  18. package/src/algorithms/LoadBalancingAlgorithm.js +23 -0
  19. package/src/algorithms/RoundRobinAlgorithm.js +67 -0
  20. package/src/config/ConfigManager.js +37 -0
  21. package/src/config/RouteLoader.js +36 -0
  22. package/src/core/Balancer.js +353 -0
  23. package/src/core/RoundRobinAlgorithm.js +60 -0
  24. package/src/core/ServerPool.js +77 -0
  25. package/src/dashboard/WebDashboard.js +150 -0
  26. package/src/dashboard/WebSocketServer.js +114 -0
  27. package/src/extensibility/CachingFilter.js +134 -0
  28. package/src/extensibility/FilterChain.js +93 -0
  29. package/src/extensibility/HookManager.js +48 -0
  30. package/src/protocol/HttpBalancer.js +37 -0
  31. package/src/protocol/HttpsBalancer.js +47 -0
  32. package/src/utils/BalancerLogger.js +102 -0
  33. package/src/utils/HealthChecker.js +51 -0
  34. package/src/utils/Logger.js +39 -0
  35. package/src/utils/MetricsCollector.js +82 -0
  36. package/src/utils/ProfessionalMetrics.js +501 -0
  37. package/start-iphash.sh +5 -0
  38. package/start-roundrobin.sh +5 -0
  39. package/stress-test.js +190 -0
  40. package/webpage/README.md +17 -0
  41. package/webpage/index.html +549 -0
@@ -0,0 +1,501 @@
1
+ const os = require('os');
2
+
3
+ class ProfessionalMetrics {
4
+ constructor() {
5
+ this.metrics = {
6
+ requests: {
7
+ total: 0,
8
+ successful: 0,
9
+ failed: 0,
10
+ byMethod: {},
11
+ byPath: {},
12
+ byServer: {}, // Contador general por servidor
13
+ byResponseCode: {}
14
+ },
15
+ serverStats: {}, // Estadísticas detalladas por servidor
16
+ performance: {
17
+ totalResponseTime: 0,
18
+ minResponseTime: Infinity,
19
+ maxResponseTime: 0,
20
+ avgResponseTime: 0,
21
+ p95ResponseTime: 0,
22
+ p99ResponseTime: 0
23
+ },
24
+ servers: {
25
+ total: 0,
26
+ healthy: 0,
27
+ unhealthy: 0,
28
+ byStatus: {}
29
+ },
30
+ system: {
31
+ cpuUsage: 0,
32
+ memoryUsage: 0,
33
+ uptime: 0,
34
+ loadAverage: [0, 0, 0]
35
+ },
36
+ timestamps: {
37
+ startTime: Date.now(),
38
+ lastUpdate: Date.now()
39
+ },
40
+ responseTimes: [] // Para calcular percentiles
41
+ };
42
+
43
+ // Iniciar recolección de métricas del sistema
44
+ this.startSystemMetricsCollection();
45
+ }
46
+
47
+ startSystemMetricsCollection() {
48
+ // Actualizar métricas del sistema periódicamente
49
+ setInterval(() => {
50
+ this.updateSystemMetrics();
51
+ }, 5000); // Actualizar cada 5 segundos
52
+ }
53
+
54
+ updateSystemMetrics() {
55
+ const loadAvg = os.loadavg();
56
+ const totalMemory = os.totalmem();
57
+ const freeMemory = os.freemem();
58
+
59
+ this.metrics.system = {
60
+ ...this.metrics.system,
61
+ cpuUsage: this.getCpuUsage(),
62
+ memoryUsage: ((totalMemory - freeMemory) / totalMemory) * 100,
63
+ loadAverage: loadAvg,
64
+ uptime: process.uptime()
65
+ };
66
+ }
67
+
68
+ getCpuUsage() {
69
+ const cpus = os.cpus();
70
+ let totalIdle = 0;
71
+ let totalTick = 0;
72
+
73
+ for (const cpu of cpus) {
74
+ for (const type in cpu.times) {
75
+ totalTick += cpu.times[type];
76
+ }
77
+ totalIdle += cpu.times.idle;
78
+ }
79
+
80
+ const currentUsage = ((totalTick - totalIdle) / totalTick) * 100;
81
+ return parseFloat(currentUsage.toFixed(2));
82
+ }
83
+
84
+ recordRequest(method, path, serverId, responseTime, responseCode, success = true) {
85
+ const now = Date.now();
86
+
87
+ // Actualizar contadores de solicitudes
88
+ this.metrics.requests.total++;
89
+
90
+ if (success) {
91
+ this.metrics.requests.successful++;
92
+ } else {
93
+ this.metrics.requests.failed++;
94
+ }
95
+
96
+ // Contar por método HTTP
97
+ if (!this.metrics.requests.byMethod[method]) {
98
+ this.metrics.requests.byMethod[method] = 0;
99
+ }
100
+ this.metrics.requests.byMethod[method]++;
101
+
102
+ // Contar por ruta
103
+ if (!this.metrics.requests.byPath[path]) {
104
+ this.metrics.requests.byPath[path] = 0;
105
+ }
106
+ this.metrics.requests.byPath[path]++;
107
+
108
+ // Contar por servidor
109
+ if (!this.metrics.requests.byServer[serverId]) {
110
+ this.metrics.requests.byServer[serverId] = 0;
111
+ }
112
+ this.metrics.requests.byServer[serverId]++;
113
+
114
+ // Contar por código de respuesta
115
+ if (!this.metrics.requests.byResponseCode[responseCode]) {
116
+ this.metrics.requests.byResponseCode[responseCode] = 0;
117
+ }
118
+ this.metrics.requests.byResponseCode[responseCode]++;
119
+
120
+ // Actualizar estadísticas detalladas por servidor
121
+ this.updateServerStats(serverId, responseTime, responseCode, success);
122
+
123
+ // Actualizar métricas de rendimiento
124
+ this.metrics.performance.totalResponseTime += responseTime;
125
+ this.metrics.performance.minResponseTime = Math.min(this.metrics.performance.minResponseTime, responseTime);
126
+ this.metrics.performance.maxResponseTime = Math.max(this.metrics.performance.maxResponseTime, responseTime);
127
+
128
+ // Almacenar tiempos de respuesta para calcular percentiles
129
+ this.metrics.responseTimes.push(responseTime);
130
+ if (this.metrics.responseTimes.length > 10000) { // Limitar el tamaño del array
131
+ this.metrics.responseTimes.shift();
132
+ }
133
+
134
+ // Registrar marca de tiempo para cálculo de RPS instantáneo
135
+ this.recordRequestTimestamp();
136
+
137
+ // Calcular promedio
138
+ this.metrics.performance.avgResponseTime =
139
+ this.metrics.performance.totalResponseTime / this.metrics.requests.total;
140
+
141
+ // Calcular percentiles
142
+ this.calculatePercentiles();
143
+
144
+ this.metrics.timestamps.lastUpdate = now;
145
+ }
146
+
147
+ updateServerStats(serverId, responseTime, responseCode, success) {
148
+ // Inicializar estadísticas para el servidor si no existen
149
+ if (!this.metrics.serverStats[serverId]) {
150
+ this.metrics.serverStats[serverId] = {
151
+ id: serverId,
152
+ totalRequests: 0,
153
+ successfulRequests: 0,
154
+ failedRequests: 0,
155
+ totalResponseTime: 0,
156
+ minResponseTime: Infinity,
157
+ maxResponseTime: 0,
158
+ avgResponseTime: 0,
159
+ responseCodes: {},
160
+ uptimeRatio: 0,
161
+ lastActive: Date.now(),
162
+ responseTimes: [] // Para calcular percentiles por servidor
163
+ };
164
+ }
165
+
166
+ const serverStat = this.metrics.serverStats[serverId];
167
+
168
+ // Actualizar contadores
169
+ serverStat.totalRequests++;
170
+ if (success) {
171
+ serverStat.successfulRequests++;
172
+ } else {
173
+ serverStat.failedRequests++;
174
+ }
175
+
176
+ // Actualizar tiempos de respuesta
177
+ serverStat.totalResponseTime += responseTime;
178
+ serverStat.minResponseTime = Math.min(serverStat.minResponseTime, responseTime);
179
+ serverStat.maxResponseTime = Math.max(serverStat.maxResponseTime, responseTime);
180
+
181
+ // Actualizar códigos de respuesta
182
+ if (!serverStat.responseCodes[responseCode]) {
183
+ serverStat.responseCodes[responseCode] = 0;
184
+ }
185
+ serverStat.responseCodes[responseCode]++;
186
+
187
+ // Actualizar promedio
188
+ serverStat.avgResponseTime = serverStat.totalResponseTime / serverStat.totalRequests;
189
+
190
+ // Actualizar último acceso
191
+ serverStat.lastActive = Date.now();
192
+
193
+ // Almacenar tiempos de respuesta para percentiles por servidor
194
+ serverStat.responseTimes.push(responseTime);
195
+ if (serverStat.responseTimes.length > 1000) { // Limitar tamaño del array por servidor
196
+ serverStat.responseTimes.shift();
197
+ }
198
+
199
+ // Asegurar que minResponseTime sea válido
200
+ if (serverStat.minResponseTime === Infinity) {
201
+ serverStat.minResponseTime = 0;
202
+ }
203
+ }
204
+
205
+ getServerStats(serverId) {
206
+ if (serverId) {
207
+ return this.metrics.serverStats[serverId] || null;
208
+ }
209
+ return this.metrics.serverStats;
210
+ }
211
+
212
+ calculateServerPercentiles(serverId) {
213
+ if (serverId) {
214
+ const serverStat = this.metrics.serverStats[serverId];
215
+ if (serverStat && serverStat.responseTimes.length > 0) {
216
+ const sorted = [...serverStat.responseTimes].sort((a, b) => a - b);
217
+ const length = sorted.length;
218
+
219
+ // Calcular P95 (percentil 95)
220
+ const p95Index = Math.floor(length * 0.95);
221
+ serverStat.p95ResponseTime = sorted[p95Index] || 0;
222
+
223
+ // Calcular P99 (percentil 99)
224
+ const p99Index = Math.floor(length * 0.99);
225
+ serverStat.p99ResponseTime = sorted[p99Index] || 0;
226
+ }
227
+ } else {
228
+ // Calcular percentiles para todos los servidores
229
+ for (const id in this.metrics.serverStats) {
230
+ this.calculateServerPercentiles(id);
231
+ }
232
+ }
233
+ }
234
+
235
+ calculatePercentiles() {
236
+ if (this.metrics.responseTimes.length === 0) return;
237
+
238
+ const sorted = [...this.metrics.responseTimes].sort((a, b) => a - b);
239
+ const length = sorted.length;
240
+
241
+ // Calcular P95 (percentil 95)
242
+ const p95Index = Math.floor(length * 0.95);
243
+ this.metrics.performance.p95ResponseTime = sorted[p95Index] || 0;
244
+
245
+ // Calcular P99 (percentil 99)
246
+ const p99Index = Math.floor(length * 0.99);
247
+ this.metrics.performance.p99ResponseTime = sorted[p99Index] || 0;
248
+ }
249
+
250
+ updateServerStatus(serverId, status) {
251
+ // Actualizar estado de servidor
252
+ if (!this.metrics.servers.byStatus[status]) {
253
+ this.metrics.servers.byStatus[status] = 0;
254
+ }
255
+
256
+ // Incrementar el nuevo estado y decrementar el anterior si existe
257
+ this.metrics.servers.byStatus[status]++;
258
+
259
+ // Actualizar contadores generales
260
+ this.updateServerCounts();
261
+ }
262
+
263
+ updateServerCounts() {
264
+ let healthyCount = 0;
265
+ let unhealthyCount = 0;
266
+
267
+ // Contar servidores según su estado actual
268
+ for (const [status, count] of Object.entries(this.metrics.servers.byStatus)) {
269
+ if (status === 'healthy' || status === 'online' || status === 'active') {
270
+ healthyCount += count;
271
+ } else {
272
+ unhealthyCount += count;
273
+ }
274
+ }
275
+
276
+ // El conteo de servidores activos debe reflejar solo los servidores verificados como online
277
+ // No puede ser mayor al número total de servidores configurados
278
+ const totalConfiguredServers = Object.keys(this.metrics.serverStats).length;
279
+
280
+ // Solo contar servidores que han sido verificados como online
281
+ let verifiedOnlineCount = 0;
282
+ for (const [serverId, serverStat] of Object.entries(this.metrics.serverStats)) {
283
+ if (serverStat.status === 'online' || serverStat.status === 'healthy' || serverStat.status === 'active') {
284
+ verifiedOnlineCount++;
285
+ }
286
+ }
287
+
288
+ this.metrics.servers.healthy = verifiedOnlineCount;
289
+ this.metrics.servers.unhealthy = totalConfiguredServers - verifiedOnlineCount;
290
+ this.metrics.servers.total = totalConfiguredServers;
291
+ }
292
+
293
+ // Método para actualizar el estado de un servidor
294
+ updateServerStatus(serverId, status) {
295
+ // Inicializar estadísticas para el servidor si no existen
296
+ if (!this.metrics.serverStats[serverId]) {
297
+ this.metrics.serverStats[serverId] = {
298
+ id: serverId,
299
+ totalRequests: 0,
300
+ successfulRequests: 0,
301
+ failedRequests: 0,
302
+ totalResponseTime: 0,
303
+ minResponseTime: Infinity,
304
+ maxResponseTime: 0,
305
+ avgResponseTime: 0,
306
+ responseCodes: {},
307
+ uptimeRatio: 0,
308
+ lastActive: Date.now(),
309
+ responseTimes: []
310
+ };
311
+ }
312
+
313
+ const serverStat = this.metrics.serverStats[serverId];
314
+
315
+ // Actualizar el estado del servidor
316
+ serverStat.status = status;
317
+ serverStat.lastActive = Date.now();
318
+
319
+ // Actualizar el conteo por estado
320
+ // Primero decrementar el conteo del estado anterior si ya existía
321
+ if (serverStat.previousStatus && serverStat.previousStatus !== status) {
322
+ if (this.metrics.servers.byStatus[serverStat.previousStatus]) {
323
+ this.metrics.servers.byStatus[serverStat.previousStatus]--;
324
+ if (this.metrics.servers.byStatus[serverStat.previousStatus] < 0) {
325
+ this.metrics.servers.byStatus[serverStat.previousStatus] = 0;
326
+ }
327
+ }
328
+ }
329
+
330
+ // Incrementar el conteo del nuevo estado
331
+ if (!this.metrics.servers.byStatus[status]) {
332
+ this.metrics.servers.byStatus[status] = 0;
333
+ }
334
+ this.metrics.servers.byStatus[status]++;
335
+
336
+ // Guardar el estado anterior para futuras actualizaciones
337
+ serverStat.previousStatus = status;
338
+
339
+ // Actualizar los contadores generales
340
+ this.updateServerCounts();
341
+ }
342
+
343
+ getMetrics() {
344
+ // Asegurar que los valores mínimos sean válidos
345
+ if (this.metrics.performance.minResponseTime === Infinity) {
346
+ this.metrics.performance.minResponseTime = 0;
347
+ }
348
+
349
+ // Calcular RPS acumulado (desde el inicio)
350
+ const uptimeSecs = (Date.now() - this.metrics.timestamps.startTime) / 1000;
351
+ const cumulativeRps = uptimeSecs > 0 ? this.metrics.requests.total / uptimeSecs : 0;
352
+
353
+ // Calcular RPS instantáneo (últimos 5 segundos)
354
+ const instantaneousRps = this.calculateInstantaneousRps();
355
+
356
+ // Calcular percentiles por servidor
357
+ this.calculateServerPercentiles();
358
+
359
+ // Actualizar métricas del sistema
360
+ this.updateSystemMetrics();
361
+
362
+ return {
363
+ ...this.metrics,
364
+ calculated: {
365
+ cumulativeRps: parseFloat(cumulativeRps.toFixed(2)),
366
+ instantaneousRps: parseFloat(instantaneousRps.toFixed(2)),
367
+ uptime: Date.now() - this.metrics.timestamps.startTime,
368
+ successRate: this.metrics.requests.total > 0
369
+ ? parseFloat(((this.metrics.requests.successful / this.metrics.requests.total) * 100).toFixed(2))
370
+ : 0
371
+ }
372
+ };
373
+ }
374
+
375
+ // Método para calcular RPS instantáneo (últimos 5 segundos)
376
+ calculateInstantaneousRps() {
377
+ if (!this.recentRequests) {
378
+ this.recentRequests = [];
379
+ }
380
+
381
+ const now = Date.now();
382
+ const fiveSecondsAgo = now - 5000; // 5 segundos atrás
383
+
384
+ // Limpiar solicitudes antiguas
385
+ this.recentRequests = this.recentRequests.filter(timestamp => timestamp > fiveSecondsAgo);
386
+
387
+ // Calcular RPS instantáneo
388
+ const timeWindow = 5; // 5 segundos
389
+ return this.recentRequests.length / timeWindow;
390
+ }
391
+
392
+ // Método para registrar una solicitud reciente para cálculo de RPS instantáneo
393
+ recordRequestTimestamp() {
394
+ if (!this.recentRequests) {
395
+ this.recentRequests = [];
396
+ }
397
+
398
+ this.recentRequests.push(Date.now());
399
+
400
+ // Limpiar solicitudes antiguas (> 5 segundos) para mantener solo las recientes
401
+ const fiveSecondsAgo = Date.now() - 5000;
402
+ this.recentRequests = this.recentRequests.filter(timestamp => timestamp > fiveSecondsAgo);
403
+ }
404
+
405
+ updateSystemMetrics() {
406
+ const os = require('os');
407
+
408
+ // Obtener uso de CPU (corrección para cálculo preciso)
409
+ const cpus = os.cpus();
410
+ let totalUser = 0;
411
+ let totalNice = 0;
412
+ let totalSys = 0;
413
+ let totalIdle = 0;
414
+ let totalIrq = 0;
415
+
416
+ for (const cpu of cpus) {
417
+ totalUser += cpu.times.user;
418
+ totalNice += cpu.times.nice;
419
+ totalSys += cpu.times.sys;
420
+ totalIdle += cpu.times.idle;
421
+ totalIrq += cpu.times.irq;
422
+ }
423
+
424
+ const total = totalUser + totalNice + totalSys + totalIdle + totalIrq;
425
+ const currentUsage = ((total - totalIdle) / total) * 100;
426
+ this.metrics.system.cpuUsage = parseFloat(currentUsage.toFixed(2));
427
+
428
+ // Obtener uso de memoria
429
+ const totalMem = os.totalmem();
430
+ const freeMem = os.freemem();
431
+ const usedMem = totalMem - freeMem;
432
+ this.metrics.system.memoryUsage = parseFloat(((usedMem / totalMem) * 100).toFixed(2));
433
+
434
+ // Obtener carga promedio
435
+ this.metrics.system.loadAverage = os.loadavg();
436
+
437
+ // Actualizar tiempo de actividad
438
+ this.metrics.system.uptime = process.uptime();
439
+ }
440
+
441
+ reset() {
442
+ this.metrics = {
443
+ requests: {
444
+ total: 0,
445
+ successful: 0,
446
+ failed: 0,
447
+ byMethod: {},
448
+ byPath: {},
449
+ byServer: {},
450
+ byResponseCode: {}
451
+ },
452
+ performance: {
453
+ totalResponseTime: 0,
454
+ minResponseTime: Infinity,
455
+ maxResponseTime: 0,
456
+ avgResponseTime: 0,
457
+ p95ResponseTime: 0,
458
+ p99ResponseTime: 0
459
+ },
460
+ servers: {
461
+ total: 0,
462
+ healthy: 0,
463
+ unhealthy: 0,
464
+ byStatus: {}
465
+ },
466
+ system: {
467
+ cpuUsage: 0,
468
+ memoryUsage: 0,
469
+ uptime: 0,
470
+ loadAverage: [0, 0, 0]
471
+ },
472
+ timestamps: {
473
+ startTime: Date.now(),
474
+ lastUpdate: Date.now()
475
+ },
476
+ responseTimes: []
477
+ };
478
+ }
479
+
480
+ getHealthStatus() {
481
+ const { total, successful, failed } = this.metrics.requests;
482
+ const successRate = total > 0 ? (successful / total) * 100 : 100;
483
+
484
+ let status = 'healthy';
485
+ if (successRate < 90) {
486
+ status = 'critical';
487
+ } else if (successRate < 95) {
488
+ status = 'warning';
489
+ }
490
+
491
+ return {
492
+ status,
493
+ successRate,
494
+ totalRequests: total,
495
+ healthyServers: this.metrics.servers.healthy,
496
+ unhealthyServers: this.metrics.servers.unhealthy
497
+ };
498
+ }
499
+ }
500
+
501
+ module.exports = { ProfessionalMetrics };
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ # Script para iniciar KUKUY
3
+
4
+ echo "Iniciando KUKUY..."
5
+ LOAD_BALANCING_ALGORITHM=iphash CONFIG_FILE_PATH=./servers.json BALANCER_HTTP_PORT=8080 DASHBOARD_PORT=8082 node kukuy.js
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ # Script para iniciar KUKUY
3
+
4
+ echo "Iniciando KUKUY..."
5
+ LOAD_BALANCING_ALGORITHM=roundrobin CONFIG_FILE_PATH=./servers_real.json BALANCER_HTTP_PORT=8088 DASHBOARD_PORT=8082 node kukuy.js
package/stress-test.js ADDED
@@ -0,0 +1,190 @@
1
+ const http = require('http');
2
+ const https = require('https');
3
+ const url = require('url');
4
+
5
+ class StressTester {
6
+ constructor(balancerUrl, durationSeconds = 60, concurrentRequests = 10) {
7
+ this.balancerUrl = balancerUrl;
8
+ this.durationSeconds = durationSeconds;
9
+ this.concurrentRequests = concurrentRequests;
10
+ this.startTime = null;
11
+ this.stats = {
12
+ totalRequests: 0,
13
+ successfulRequests: 0,
14
+ failedRequests: 0,
15
+ statusCodes: {},
16
+ totalTime: 0,
17
+ minTime: Infinity,
18
+ maxTime: 0
19
+ };
20
+ this.running = false;
21
+ }
22
+
23
+ async makeRequest(path, method = 'GET', body = null) {
24
+ const startTime = Date.now();
25
+ let statusCode = 0;
26
+ let success = false;
27
+
28
+ return new Promise((resolve, reject) => {
29
+ const parsedUrl = url.parse(`${this.balancerUrl}${path}`);
30
+ const options = {
31
+ hostname: parsedUrl.hostname,
32
+ port: parsedUrl.port,
33
+ path: parsedUrl.path,
34
+ method: method,
35
+ headers: {
36
+ 'Content-Type': 'application/json'
37
+ }
38
+ };
39
+
40
+ const request = http.request(options, (res) => {
41
+ statusCode = res.statusCode;
42
+ let data = '';
43
+
44
+ res.on('data', (chunk) => {
45
+ data += chunk;
46
+ });
47
+
48
+ res.on('end', () => {
49
+ const endTime = Date.now();
50
+ const responseTime = endTime - startTime;
51
+
52
+ success = statusCode >= 200 && statusCode < 300;
53
+
54
+ // Actualizar estadísticas
55
+ this.stats.totalRequests++;
56
+ if (success) {
57
+ this.stats.successfulRequests++;
58
+ } else {
59
+ this.stats.failedRequests++;
60
+ }
61
+
62
+ if (!this.stats.statusCodes[statusCode]) {
63
+ this.stats.statusCodes[statusCode] = 0;
64
+ }
65
+ this.stats.statusCodes[statusCode]++;
66
+
67
+ this.stats.totalTime += responseTime;
68
+ this.stats.minTime = Math.min(this.stats.minTime, responseTime);
69
+ this.stats.maxTime = Math.max(this.stats.maxTime, responseTime);
70
+
71
+ resolve({
72
+ statusCode,
73
+ responseTime,
74
+ success
75
+ });
76
+ });
77
+ });
78
+
79
+ request.on('error', (err) => {
80
+ const endTime = Date.now();
81
+ const responseTime = endTime - startTime;
82
+
83
+ this.stats.totalRequests++;
84
+ this.stats.failedRequests++;
85
+
86
+ if (!this.stats.statusCodes['ERROR']) {
87
+ this.stats.statusCodes['ERROR'] = 0;
88
+ }
89
+ this.stats.statusCodes['ERROR']++;
90
+
91
+ this.stats.totalTime += responseTime;
92
+ this.stats.maxTime = Math.max(this.stats.maxTime, responseTime);
93
+
94
+ resolve({
95
+ statusCode: 'ERROR',
96
+ responseTime,
97
+ success: false,
98
+ error: err.message
99
+ });
100
+ });
101
+
102
+ if (body) {
103
+ request.write(JSON.stringify(body));
104
+ }
105
+
106
+ request.end();
107
+ });
108
+ }
109
+
110
+ async runTest() {
111
+ console.log(`Iniciando prueba de estrés...`);
112
+ console.log(`URL del balanceador: ${this.balancerUrl}`);
113
+ console.log(`Duración: ${this.durationSeconds} segundos`);
114
+ console.log(`Concurrencia: ${this.concurrentRequests} solicitudes simultáneas`);
115
+ console.log('----------------------------------------');
116
+
117
+ this.startTime = Date.now();
118
+ this.running = true;
119
+
120
+ // Lista de endpoints para probar
121
+ const endpoints = [
122
+ { path: '/api/endpoints', method: 'GET' },
123
+ { path: '/sessions', method: 'GET' },
124
+ { path: '/events', method: 'GET' },
125
+ { path: '/reports/user-flow', method: 'GET' },
126
+ { path: '/reports/product-views-by-country', method: 'GET' },
127
+ { path: '/track/registerSession', method: 'POST', body: { test: 'stress_test_data' } },
128
+ { path: '/track/registerEventA', method: 'POST', body: { event: 'test_event', data: 'test_data' } }
129
+ ];
130
+
131
+ // Función para hacer solicitudes continuamente durante la duración del test
132
+ const makeRequests = async () => {
133
+ while (this.running) {
134
+ const randomEndpoint = endpoints[Math.floor(Math.random() * endpoints.length)];
135
+ await this.makeRequest(randomEndpoint.path, randomEndpoint.method, randomEndpoint.body || null);
136
+ }
137
+ };
138
+
139
+ // Iniciar múltiples promesas concurrentes
140
+ const promises = [];
141
+ for (let i = 0; i < this.concurrentRequests; i++) {
142
+ promises.push(makeRequests());
143
+ }
144
+
145
+ // Esperar hasta que se cumpla la duración del test
146
+ setTimeout(() => {
147
+ this.running = false;
148
+ }, this.durationSeconds * 1000);
149
+
150
+ // Esperar a que todas las promesas terminen
151
+ await Promise.all(promises);
152
+
153
+ this.printResults();
154
+ }
155
+
156
+ printResults() {
157
+ const totalTimeMs = Date.now() - this.startTime;
158
+ const avgTime = this.stats.totalRequests > 0 ? this.stats.totalTime / this.stats.totalRequests : 0;
159
+ const requestsPerSecond = this.stats.totalRequests / (totalTimeMs / 1000);
160
+
161
+ console.log('\n========================================');
162
+ console.log('RESULTADOS DE LA PRUEBA DE ESTRÉS');
163
+ console.log('========================================');
164
+ console.log(`Tiempo total de prueba: ${(totalTimeMs / 1000).toFixed(2)} segundos`);
165
+ console.log(`Solicitudes totales: ${this.stats.totalRequests}`);
166
+ console.log(`Solicitudes exitosas: ${this.stats.successfulRequests}`);
167
+ console.log(`Solicitudes fallidas: ${this.stats.failedRequests}`);
168
+ console.log(`RPS (Solicitudes por segundo): ${requestsPerSecond.toFixed(2)}`);
169
+ console.log(`Tiempo promedio por solicitud: ${avgTime.toFixed(2)} ms`);
170
+ console.log(`Tiempo mínimo: ${this.stats.minTime !== Infinity ? this.stats.minTime : 0} ms`);
171
+ console.log(`Tiempo máximo: ${this.stats.maxTime} ms`);
172
+ console.log('\nDistribución de códigos de estado:');
173
+ Object.entries(this.stats.statusCodes).forEach(([code, count]) => {
174
+ console.log(` ${code}: ${count} (${((count / this.stats.totalRequests) * 100).toFixed(2)}%)`);
175
+ });
176
+ console.log('========================================');
177
+ }
178
+ }
179
+
180
+ // Ejecutar la prueba de estrés si se llama directamente
181
+ if (require.main === module) {
182
+ const balancerUrl = process.env.BALANCER_URL || 'http://localhost:8081';
183
+ const duration = parseInt(process.env.DURATION_SECONDS) || 30;
184
+ const concurrency = parseInt(process.env.CONCURRENT_REQUESTS) || 10;
185
+
186
+ const tester = new StressTester(balancerUrl, duration, concurrency);
187
+ tester.runTest().catch(console.error);
188
+ }
189
+
190
+ module.exports = { StressTester };