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.
- package/.ctagsd/ctagsd.json +954 -0
- package/.ctagsd/file_list.txt +100 -0
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +101 -0
- package/LICENSE +680 -0
- package/README.md +251 -0
- package/captura.png +0 -0
- package/kukuy.js +23 -0
- package/kukuy.workspace +11 -0
- package/package.json +26 -0
- package/restart-balancer.sh +10 -0
- package/routes.json +14 -0
- package/scripts/load_test.py +151 -0
- package/servers.json +19 -0
- package/servers_real.json +19 -0
- package/src/algorithms/AlgorithmManager.js +85 -0
- package/src/algorithms/IPHashAlgorithm.js +131 -0
- package/src/algorithms/LoadBalancingAlgorithm.js +23 -0
- package/src/algorithms/RoundRobinAlgorithm.js +67 -0
- package/src/config/ConfigManager.js +37 -0
- package/src/config/RouteLoader.js +36 -0
- package/src/core/Balancer.js +353 -0
- package/src/core/RoundRobinAlgorithm.js +60 -0
- package/src/core/ServerPool.js +77 -0
- package/src/dashboard/WebDashboard.js +150 -0
- package/src/dashboard/WebSocketServer.js +114 -0
- package/src/extensibility/CachingFilter.js +134 -0
- package/src/extensibility/FilterChain.js +93 -0
- package/src/extensibility/HookManager.js +48 -0
- package/src/protocol/HttpBalancer.js +37 -0
- package/src/protocol/HttpsBalancer.js +47 -0
- package/src/utils/BalancerLogger.js +102 -0
- package/src/utils/HealthChecker.js +51 -0
- package/src/utils/Logger.js +39 -0
- package/src/utils/MetricsCollector.js +82 -0
- package/src/utils/ProfessionalMetrics.js +501 -0
- package/start-iphash.sh +5 -0
- package/start-roundrobin.sh +5 -0
- package/stress-test.js +190 -0
- package/webpage/README.md +17 -0
- 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 };
|
package/start-iphash.sh
ADDED
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 };
|