kukuy 1.4.0 → 1.6.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/.env.ssl +22 -0
- package/README.md +107 -171
- package/balancer.log +2 -0
- package/certs/auto/certificate.crt +22 -0
- package/certs/auto/private.key +28 -0
- package/kukuy-plugins/README.md +91 -0
- package/kukuy-plugins/ejemplo-plugin/index.js +39 -0
- package/kukuy-plugins/ejemplo-plugin/manifest.json +11 -0
- package/kukuy.js +51 -5
- package/package.json +8 -2
- package/servers_real.json +5 -0
- package/src/algorithms/IPHashAlgorithm.js +25 -13
- package/src/algorithms/RoundRobinAlgorithm.js +25 -27
- package/src/core/Balancer.js +201 -128
- package/src/core/ServerPool.js +46 -5
- package/src/extensibility/ExtendedFilterChain.js +90 -0
- package/src/extensibility/ExtendedHookManager.js +87 -0
- package/src/extensibility/PostStartupExtension.js +97 -0
- package/src/plugins/PluginManager.js +183 -0
- package/src/utils/HealthChecker.js +11 -5
- package/src/utils/ProfessionalMetrics.js +41 -24
- package/start-ssl-config.sh +24 -0
- package/start-ssl.sh +26 -0
- package/webpage/index.html +1 -1
- package/.ctagsd/ctagsd.json +0 -954
- package/.ctagsd/file_list.txt +0 -100
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +0 -101
- package/LICENSE +0 -680
- package/captura.png +0 -0
- package/kukuy.workspace +0 -11
- package/restart-balancer.sh +0 -10
- package/scripts/load_test.py +0 -151
- package/stress-test.js +0 -190
package/kukuy.workspace
DELETED
package/restart-balancer.sh
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Script para matar e iniciar KUKUY con estadísticas por servidor y WebSocket
|
|
3
|
-
|
|
4
|
-
echo "Matando procesos existentes de KUKUY..."
|
|
5
|
-
pkill -f "node kukuy.js" 2>/dev/null || true
|
|
6
|
-
|
|
7
|
-
sleep 2
|
|
8
|
-
|
|
9
|
-
echo "Iniciando KUKUY con estadísticas por servidor y WebSocket..."
|
|
10
|
-
LOAD_BALANCING_ALGORITHM=roundrobin CONFIG_FILE_PATH=./servers_real.json BALANCER_HTTP_PORT=8088 DASHBOARD_PORT=8082 WEBSOCKET_PORT=8084 node kukuy.js
|
package/scripts/load_test.py
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Script de prueba de carga concurrente para el balanceador
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import threading
|
|
7
|
-
import time
|
|
8
|
-
import requests
|
|
9
|
-
import sys
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
|
|
12
|
-
# Configuración
|
|
13
|
-
BASE_URL = "http://localhost:8081"
|
|
14
|
-
ENDPOINT = "/api/endpoints"
|
|
15
|
-
NUM_THREADS = 100 # Número de hilos concurrentes para alta carga
|
|
16
|
-
REQUESTS_PER_THREAD = 6000 # Solicitudes por hilo (100 hilos * 60 solicitudes = 6000 solicitudes totales por segundo)
|
|
17
|
-
DURATION_SECONDS = 120 # Duración de la prueba en segundos (2 minutos)
|
|
18
|
-
TARGET_RPS = 5000 # Objetivo de solicitudes por segundo
|
|
19
|
-
|
|
20
|
-
# Variables globales para métricas
|
|
21
|
-
total_requests = 0
|
|
22
|
-
successful_requests = 0
|
|
23
|
-
failed_requests = 0
|
|
24
|
-
lock = threading.Lock()
|
|
25
|
-
|
|
26
|
-
def make_requests_continuous(thread_id, end_time_target):
|
|
27
|
-
"""Función que realiza solicitudes HTTP continuas hasta alcanzar la duración objetivo"""
|
|
28
|
-
global total_requests, successful_requests, failed_requests
|
|
29
|
-
|
|
30
|
-
thread_start_time = time.time()
|
|
31
|
-
thread_successful = 0
|
|
32
|
-
thread_failed = 0
|
|
33
|
-
|
|
34
|
-
# Calcular el intervalo entre solicitudes para alcanzar el objetivo RPS
|
|
35
|
-
interval = 1.0 / (TARGET_RPS / NUM_THREADS) if NUM_THREADS > 0 else 0.001 # Evitar división por cero
|
|
36
|
-
|
|
37
|
-
while time.time() < end_time_target:
|
|
38
|
-
try:
|
|
39
|
-
response = requests.get(f"{BASE_URL}{ENDPOINT}", timeout=10)
|
|
40
|
-
with lock:
|
|
41
|
-
total_requests += 1
|
|
42
|
-
if response.status_code == 200:
|
|
43
|
-
successful_requests += 1
|
|
44
|
-
thread_successful += 1
|
|
45
|
-
else:
|
|
46
|
-
failed_requests += 1
|
|
47
|
-
thread_failed += 1
|
|
48
|
-
except requests.exceptions.RequestException as e:
|
|
49
|
-
with lock:
|
|
50
|
-
total_requests += 1
|
|
51
|
-
failed_requests += 1
|
|
52
|
-
thread_failed += 1
|
|
53
|
-
if thread_id == 0: # Solo imprimir errores para el primer hilo
|
|
54
|
-
print(f"[Thread-{thread_id}] Error en solicitud: {e}")
|
|
55
|
-
|
|
56
|
-
# Esperar el intervalo calculado para alcanzar el objetivo RPS
|
|
57
|
-
time.sleep(interval)
|
|
58
|
-
|
|
59
|
-
thread_end_time = time.time()
|
|
60
|
-
thread_duration = thread_end_time - thread_start_time
|
|
61
|
-
|
|
62
|
-
print(f"[Thread-{thread_id}] Completado: {thread_successful} exitosas, {thread_failed} fallidas en {thread_duration:.2f}s")
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def make_requests(thread_id):
|
|
66
|
-
"""Función original que realiza solicitudes HTTP en un hilo (mantenida por compatibilidad)"""
|
|
67
|
-
global total_requests, successful_requests, failed_requests
|
|
68
|
-
|
|
69
|
-
thread_start_time = time.time()
|
|
70
|
-
thread_successful = 0
|
|
71
|
-
thread_failed = 0
|
|
72
|
-
|
|
73
|
-
for i in range(REQUESTS_PER_THREAD):
|
|
74
|
-
try:
|
|
75
|
-
response = requests.get(f"{BASE_URL}{ENDPOINT}", timeout=10)
|
|
76
|
-
with lock:
|
|
77
|
-
total_requests += 1
|
|
78
|
-
if response.status_code == 200:
|
|
79
|
-
successful_requests += 1
|
|
80
|
-
thread_successful += 1
|
|
81
|
-
else:
|
|
82
|
-
failed_requests += 1
|
|
83
|
-
thread_failed += 1
|
|
84
|
-
except requests.exceptions.RequestException as e:
|
|
85
|
-
with lock:
|
|
86
|
-
total_requests += 1
|
|
87
|
-
failed_requests += 1
|
|
88
|
-
thread_failed += 1
|
|
89
|
-
if thread_id == 0: # Solo imprimir errores para el primer hilo
|
|
90
|
-
print(f"[Thread-{thread_id}] Error en solicitud {i+1}: {e}")
|
|
91
|
-
|
|
92
|
-
thread_end_time = time.time()
|
|
93
|
-
thread_duration = thread_end_time - thread_start_time
|
|
94
|
-
|
|
95
|
-
print(f"[Thread-{thread_id}] Completado: {thread_successful} exitosas, {thread_failed} fallidas en {thread_duration:.2f}s")
|
|
96
|
-
|
|
97
|
-
def run_load_test():
|
|
98
|
-
"""Función principal que ejecuta la prueba de carga"""
|
|
99
|
-
global total_requests, successful_requests, failed_requests
|
|
100
|
-
|
|
101
|
-
print(f"Iniciando prueba de carga...")
|
|
102
|
-
print(f"- URL base: {BASE_URL}")
|
|
103
|
-
print(f"- Endpoint: {ENDPOINT}")
|
|
104
|
-
print(f"- Hilos concurrentes: {NUM_THREADS}")
|
|
105
|
-
print(f"- Solicitudes por hilo: {REQUESTS_PER_THREAD}")
|
|
106
|
-
print(f"- Duración: {DURATION_SECONDS} segundos")
|
|
107
|
-
print(f"- Objetivo RPS: {TARGET_RPS}")
|
|
108
|
-
print("-" * 50)
|
|
109
|
-
|
|
110
|
-
start_time = time.time()
|
|
111
|
-
end_time_target = start_time + DURATION_SECONDS
|
|
112
|
-
|
|
113
|
-
# Crear y arrancar hilos
|
|
114
|
-
threads = []
|
|
115
|
-
for i in range(NUM_THREADS):
|
|
116
|
-
thread = threading.Thread(target=make_requests_continuous, args=(i, end_time_target))
|
|
117
|
-
threads.append(thread)
|
|
118
|
-
thread.start()
|
|
119
|
-
|
|
120
|
-
# Pequeño retraso entre la creación de hilos para evitar congestión
|
|
121
|
-
time.sleep(0.001)
|
|
122
|
-
|
|
123
|
-
# Esperar a que todos los hilos terminen
|
|
124
|
-
for thread in threads:
|
|
125
|
-
thread.join()
|
|
126
|
-
|
|
127
|
-
actual_duration = time.time() - start_time
|
|
128
|
-
|
|
129
|
-
# Mostrar resultados
|
|
130
|
-
print("-" * 50)
|
|
131
|
-
print("RESULTADOS DE LA PRUEBA DE CARGA:")
|
|
132
|
-
print(f"Tiempo total: {actual_duration:.2f} segundos")
|
|
133
|
-
print(f"Solicitudes totales: {total_requests}")
|
|
134
|
-
print(f"Solicitudes exitosas: {successful_requests}")
|
|
135
|
-
print(f"Solicitudes fallidas: {failed_requests}")
|
|
136
|
-
print(f"Tasa de éxito: {(successful_requests/total_requests*100) if total_requests > 0 else 0:.2f}%")
|
|
137
|
-
print(f"RPS Real (Solicitudes por segundo): {total_requests/actual_duration if actual_duration > 0 else 0:.2f}")
|
|
138
|
-
print(f"RPS Objetivo: {TARGET_RPS}")
|
|
139
|
-
|
|
140
|
-
if successful_requests > 0:
|
|
141
|
-
print(f"RPS Exitoso (Solicitudes exitosas por segundo): {successful_requests/actual_duration if actual_duration > 0 else 0:.2f}")
|
|
142
|
-
|
|
143
|
-
if __name__ == "__main__":
|
|
144
|
-
try:
|
|
145
|
-
run_load_test()
|
|
146
|
-
except KeyboardInterrupt:
|
|
147
|
-
print("\nPrueba interrumpida por el usuario.")
|
|
148
|
-
print(f"Resultados parciales:")
|
|
149
|
-
print(f"- Solicitudes totales: {total_requests}")
|
|
150
|
-
print(f"- Solicitudes exitosas: {successful_requests}")
|
|
151
|
-
print(f"- Solicitudes fallidas: {failed_requests}")
|
package/stress-test.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
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 };
|