kukuy 1.5.0 → 1.9.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/README.md +159 -188
- package/balancer.log +30 -0
- package/certs/auto/certificate.crt +22 -0
- package/certs/auto/private.key +28 -0
- package/kukuy-plugins/README.md +125 -0
- package/kukuy-plugins/cache-plugin/index.js +477 -0
- package/kukuy-plugins/cache-plugin/manifest.json +17 -0
- package/kukuy-plugins/ejemplo-plugin/index.js +41 -0
- package/kukuy-plugins/ejemplo-plugin/manifest.json +11 -0
- package/kukuy-plugins/health-checker/index.js +168 -0
- package/kukuy-plugins/health-checker/manifest.json +16 -0
- package/kukuy-plugins/health-monitor/index.js +58 -0
- package/kukuy-plugins/health-monitor/manifest.json +16 -0
- package/kukuy-plugins/redirect-plugin/index.js +172 -0
- package/kukuy-plugins/redirect-plugin/manifest.json +15 -0
- package/package.json +7 -3
- package/servers_real.json +5 -0
- package/src/core/Balancer.js +176 -39
- package/src/core/ServerPool.js +2 -2
- package/src/extensibility/ExtendedFilterChain.js +90 -0
- package/src/extensibility/ExtendedHookManager.js +87 -0
- package/src/extensibility/FilterChain.js +2 -9
- package/src/extensibility/HookManager.js +1 -0
- package/src/extensibility/PostStartupExtension.js +97 -0
- package/src/plugins/PluginManager.js +231 -0
- package/src/utils/HealthChecker.js +61 -6
- package/.ctagsd/ctagsd.json +0 -954
- package/.ctagsd/file_list.txt +0 -100
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +0 -125
- package/LICENSE +0 -680
- package/README-SSL.md +0 -165
- package/captura.png +0 -0
- package/kukuu1.webp +0 -0
- package/kukuy.workspace +0 -11
- package/optimize-mariadb.sh +0 -152
- package/restart-balancer.sh +0 -10
- package/scripts/load_test.py +0 -151
- package/stress-test.js +0 -190
- package/test_optimization.js +0 -54
package/README-SSL.md
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
# Kukuy - Balanceador de Carga con SSL
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
|
|
5
|
-
Este documento explica cómo configurar e iniciar el balanceador Kukuy con soporte SSL/TLS.
|
|
6
|
-
|
|
7
|
-
## Información del Proyecto
|
|
8
|
-
|
|
9
|
-
- **Página Oficial**: [https://bsanchez.unaux.com/](https://bsanchez.unaux.com/)
|
|
10
|
-
- **Repositorio Oficial**: [https://gitlab.com/bytedogssyndicate1/kukuy](https://gitlab.com/bytedogssyndicate1/kukuy)
|
|
11
|
-
|
|
12
|
-
## Contenido
|
|
13
|
-
|
|
14
|
-
- [Requisitos](#requisitos)
|
|
15
|
-
- [Configuración de SSL](#configuración-de-ssl)
|
|
16
|
-
- [Inicio del Balanceador con SSL](#inicio-del-balanceador-con-ssl)
|
|
17
|
-
- [Uso de ngrok con SSL](#uso-de-ngrok-con-ssl)
|
|
18
|
-
- [Scripts Disponibles](#scripts-disponibles)
|
|
19
|
-
- [Variables de Entorno](#variables-de-entorno)
|
|
20
|
-
- [Configuración de Servidores](#configuración-de-servidores)
|
|
21
|
-
|
|
22
|
-
## Requisitos
|
|
23
|
-
|
|
24
|
-
- Node.js instalado
|
|
25
|
-
- OpenSSL (para generar certificados autofirmados)
|
|
26
|
-
- Opcional: ngrok para exponer servicios locales a Internet
|
|
27
|
-
|
|
28
|
-
## Configuración de SSL
|
|
29
|
-
|
|
30
|
-
### Generar Certificados Autofirmados
|
|
31
|
-
|
|
32
|
-
Para generar certificados SSL autofirmados, ejecuta el siguiente comando:
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
mkdir -p certs/auto
|
|
36
|
-
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout certs/auto/private.key -out certs/auto/certificate.crt -subj "/C=ES/ST=Madrid/L=Madrid/O=Kukuy/OU=IT Department/CN=localhost"
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Esto creará los siguientes archivos:
|
|
40
|
-
- `certs/auto/certificate.crt`: El certificado SSL
|
|
41
|
-
- `certs/auto/private.key`: La llave privada
|
|
42
|
-
|
|
43
|
-
### Usar Certificados Existentes
|
|
44
|
-
|
|
45
|
-
Si tienes certificados SSL válidos emitidos por una CA, colócalos en el directorio `certs/auto` o en cualquier ubicación de tu elección y actualiza las rutas en las variables de entorno.
|
|
46
|
-
|
|
47
|
-
## Inicio del Balanceador con SSL
|
|
48
|
-
|
|
49
|
-
### Método 1: Variables de Entorno Directas
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
SSL_CERT_PATH=certs/auto/certificate.crt SSL_KEY_PATH=certs/auto/private.key BALANCER_HTTPS_PORT=8443 CONFIG_FILE_PATH=./servers_real.json node kukuy.js
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Método 2: Usando Scripts Preparados
|
|
56
|
-
|
|
57
|
-
El proyecto incluye scripts para facilitar el inicio:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
# Iniciar con SSL usando el script básico
|
|
61
|
-
./start-ssl.sh
|
|
62
|
-
|
|
63
|
-
# Iniciar con SSL usando el script de configuración
|
|
64
|
-
./start-ssl-config.sh
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Por defecto, ambos scripts utilizan el archivo `servers_real.json` para la configuración de servidores.
|
|
68
|
-
|
|
69
|
-
## Uso de ngrok con SSL
|
|
70
|
-
|
|
71
|
-
Para exponer tu balanceador SSL a Internet a través de ngrok:
|
|
72
|
-
|
|
73
|
-
1. Instala ngrok desde https://ngrok.com/download
|
|
74
|
-
2. Ejecuta el siguiente comando:
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
ngrok tls 8443
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Esto creará un túnel seguro que redirigirá las conexiones SSL a tu balanceador local en el puerto 8443.
|
|
81
|
-
|
|
82
|
-
Alternativamente, puedes usar un túnel HTTP si prefieres que ngrok maneje la terminación SSL:
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
ngrok http https://localhost:8443
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## Scripts Disponibles
|
|
89
|
-
|
|
90
|
-
### `start-ssl.sh`
|
|
91
|
-
|
|
92
|
-
Script básico para iniciar Kukuy en modo SSL con valores predeterminados:
|
|
93
|
-
- Puerto HTTPS: 8443
|
|
94
|
-
- Archivo de certificado: `certs/auto/certificate.crt`
|
|
95
|
-
- Archivo de llave privada: `certs/auto/private.key`
|
|
96
|
-
- Archivo de configuración de servidores: `servers_real.json`
|
|
97
|
-
|
|
98
|
-
Opcionalmente, puedes habilitar el modo HTTP simultáneamente:
|
|
99
|
-
```bash
|
|
100
|
-
ENABLE_HTTP=true ./start-ssl.sh
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### `start-ssl-config.sh`
|
|
104
|
-
|
|
105
|
-
Script que carga la configuración desde el archivo `.env.ssl`. Este archivo contiene todas las variables de entorno necesarias para la operación del balanceador con SSL.
|
|
106
|
-
|
|
107
|
-
## Variables de Entorno
|
|
108
|
-
|
|
109
|
-
Las siguientes variables de entorno controlan la configuración SSL:
|
|
110
|
-
|
|
111
|
-
- `SSL_CERT_PATH`: Ruta al archivo de certificado SSL
|
|
112
|
-
- `SSL_KEY_PATH`: Ruta al archivo de llave privada SSL
|
|
113
|
-
- `BALANCER_HTTPS_PORT`: Puerto en el que escuchará el servidor HTTPS
|
|
114
|
-
- `BALANCER_HTTP_PORT`: Puerto opcional para el servidor HTTP (si se desea mantener ambos)
|
|
115
|
-
- `CONFIG_FILE_PATH`: Ruta al archivo de configuración de servidores
|
|
116
|
-
- `ROUTES_FILE_PATH`: Ruta al archivo de configuración de rutas
|
|
117
|
-
- `LOG_LEVEL`: Nivel de logging (info, debug, warn, error)
|
|
118
|
-
- `HEALTH_CHECK_INTERVAL`: Intervalo para verificaciones de salud de servidores (en milisegundos)
|
|
119
|
-
|
|
120
|
-
## Configuración de Servidores
|
|
121
|
-
|
|
122
|
-
El balanceador puede leer la configuración de servidores desde archivos JSON. Por defecto, este setup utiliza `servers_real.json` que contiene:
|
|
123
|
-
|
|
124
|
-
```json
|
|
125
|
-
{
|
|
126
|
-
"servers": [
|
|
127
|
-
{
|
|
128
|
-
"url": "http://localhost:3434",
|
|
129
|
-
"weight": 1,
|
|
130
|
-
"tags": ["backend"]
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
"url": "http://localhost:8765",
|
|
134
|
-
"weight": 1,
|
|
135
|
-
"tags": ["backend"]
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
"url": "http://localhost:5445",
|
|
139
|
-
"weight": 1,
|
|
140
|
-
"tags": ["backend"]
|
|
141
|
-
}
|
|
142
|
-
]
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Puedes modificar este archivo para apuntar a tus servidores backend reales o crear nuevos archivos de configuración según sea necesario.
|
|
147
|
-
|
|
148
|
-
## Panel de Control
|
|
149
|
-
|
|
150
|
-
Una vez iniciado el balanceador, el panel de control web estará disponible en:
|
|
151
|
-
- HTTP: http://localhost:8082
|
|
152
|
-
|
|
153
|
-
## Solución de Problemas
|
|
154
|
-
|
|
155
|
-
### Certificados SSL Autofirmados
|
|
156
|
-
|
|
157
|
-
Cuando uses certificados autofirmados, los navegadores mostrarán advertencias de seguridad. Esto es normal y esperado. Para producción, utiliza certificados firmados por una Autoridad de Certificación (CA) reconocida.
|
|
158
|
-
|
|
159
|
-
### Puertos Bloqueados
|
|
160
|
-
|
|
161
|
-
Asegúrate de que los puertos que deseas usar (por defecto 8443 para HTTPS y 8082 para el panel) estén disponibles y no sean usados por otros servicios.
|
|
162
|
-
|
|
163
|
-
### Conexión a Servidores Backend
|
|
164
|
-
|
|
165
|
-
Verifica que los servidores backend definidos en el archivo de configuración estén accesibles desde la máquina donde corre el balanceador.
|
package/captura.png
DELETED
|
Binary file
|
package/kukuu1.webp
DELETED
|
Binary file
|
package/kukuy.workspace
DELETED
package/optimize-mariadb.sh
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Script para optimizar MariaDB y liberar espacio en disco
|
|
4
|
-
# Autor: Sistema de Optimización
|
|
5
|
-
# Fecha: $(date +%Y-%m-%d)
|
|
6
|
-
|
|
7
|
-
set -e # Salir si ocurre algún error
|
|
8
|
-
|
|
9
|
-
echo "=== Iniciando optimización de MariaDB y liberación de espacio ==="
|
|
10
|
-
|
|
11
|
-
# Crear directorio para backups
|
|
12
|
-
BACKUP_DIR="/home/bds/kukuy/backups-$(date +%Y%m%d_%H%M%S)"
|
|
13
|
-
mkdir -p "$BACKUP_DIR"
|
|
14
|
-
|
|
15
|
-
echo "Directorio de backup: $BACKUP_DIR"
|
|
16
|
-
|
|
17
|
-
# 1. BACKUP DE ARCHIVOS DE CONFIGURACIÓN DE MARIADB
|
|
18
|
-
echo ""
|
|
19
|
-
echo "1. Realizando backup de archivos de configuración..."
|
|
20
|
-
|
|
21
|
-
CONFIG_FILES=(
|
|
22
|
-
"/etc/mysql/mariadb.conf.d/50-server.cnf"
|
|
23
|
-
"/etc/mysql/mariadb.conf.d/50-client.cnf"
|
|
24
|
-
"/etc/mysql/mariadb.conf.d/50-mysql-clients.cnf"
|
|
25
|
-
"/etc/mysql/my.cnf"
|
|
26
|
-
"/etc/mysql/debian-start"
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
for config_file in "${CONFIG_FILES[@]}"; do
|
|
30
|
-
if [ -f "$config_file" ]; then
|
|
31
|
-
cp "$config_file" "$BACKUP_DIR/$(basename "$config_file")"
|
|
32
|
-
echo " - Backup de $config_file realizado"
|
|
33
|
-
else
|
|
34
|
-
echo " - $config_file no encontrado"
|
|
35
|
-
fi
|
|
36
|
-
done
|
|
37
|
-
|
|
38
|
-
# 2. LIBERAR ESPACIO EN DISCO
|
|
39
|
-
echo ""
|
|
40
|
-
echo "2. Liberando espacio en disco..."
|
|
41
|
-
|
|
42
|
-
# Limpiar paquetes desactualizados
|
|
43
|
-
echo " - Limpiando paquetes desactualizados..."
|
|
44
|
-
sudo apt autoremove -y 2>/dev/null || echo " - No se pudo ejecutar apt autoremove (sin privilegios)"
|
|
45
|
-
|
|
46
|
-
# Limpiar cache de paquetes
|
|
47
|
-
echo " - Limpiando cache de paquetes..."
|
|
48
|
-
sudo apt autoclean 2>/dev/null || echo " - No se pudo ejecutar apt autoclean (sin privilegios)"
|
|
49
|
-
|
|
50
|
-
# Limpiar logs antiguos (sin sudo)
|
|
51
|
-
echo " - Buscando logs grandes en /tmp..."
|
|
52
|
-
find /tmp -name "*.log" -size +100M -delete 2>/dev/null || echo " - No se pudieron eliminar logs en /tmp (sin privilegios)"
|
|
53
|
-
|
|
54
|
-
# Limpiar historial de comandos si es necesario
|
|
55
|
-
echo " - Verificando tamaño de archivos de historial..."
|
|
56
|
-
if [ -f "$HOME/.bash_history" ] && [ $(stat -c%s "$HOME/.bash_history" 2>/dev/null || echo 0) -gt 100000 ]; then
|
|
57
|
-
tail -n 1000 "$HOME/.bash_history" > "$HOME/.bash_history.tmp" && mv "$HOME/.bash_history.tmp" "$HOME/.bash_history"
|
|
58
|
-
echo " Archivo de historial reducido"
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# 3. OPTIMIZACIÓN DE CONFIGURACIÓN DE MARIADB
|
|
62
|
-
echo ""
|
|
63
|
-
echo "3. Optimizando configuración de MariaDB..."
|
|
64
|
-
|
|
65
|
-
SERVER_CONFIG="/etc/mysql/mariadb.conf.d/50-server.cnf"
|
|
66
|
-
|
|
67
|
-
if [ -f "$SERVER_CONFIG" ]; then
|
|
68
|
-
# Crear copia de seguridad temporal
|
|
69
|
-
TEMP_CONFIG="$BACKUP_DIR/50-server.cnf.optimized"
|
|
70
|
-
cp "$SERVER_CONFIG" "$TEMP_CONFIG"
|
|
71
|
-
|
|
72
|
-
# Aplicar optimizaciones (solo si se puede escribir)
|
|
73
|
-
if [ -w "$SERVER_CONFIG" ]; then
|
|
74
|
-
echo " - Aplicando optimizaciones a $SERVER_CONFIG"
|
|
75
|
-
|
|
76
|
-
# Ajustar tamaño del buffer pool de InnoDB (si hay suficiente RAM)
|
|
77
|
-
TOTAL_RAM=$(free -m | awk 'NR==2{print $2}')
|
|
78
|
-
if [ $TOTAL_RAM -gt 2048 ]; then
|
|
79
|
-
# Si hay más de 2GB de RAM, asignar 25% a buffer pool
|
|
80
|
-
BUFFER_POOL_SIZE=$((TOTAL_RAM * 25 / 100))
|
|
81
|
-
BUFFER_POOL_BYTES=$((BUFFER_POOL_SIZE * 1024 * 1024))
|
|
82
|
-
else
|
|
83
|
-
# Si hay menos de 2GB, asignar 128MB
|
|
84
|
-
BUFFER_POOL_BYTES=134217728
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
|
-
# Ajustar configuración de logs para reducir uso de disco
|
|
88
|
-
sudo tee -a "$SERVER_CONFIG" > /dev/null << EOF
|
|
89
|
-
|
|
90
|
-
# Optimizaciones añadidas por optimize-mariadb.sh
|
|
91
|
-
[mysqld]
|
|
92
|
-
# Ajustar tamaño del buffer pool de InnoDB
|
|
93
|
-
innodb_buffer_pool_size = ${BUFFER_POOL_BYTES}
|
|
94
|
-
|
|
95
|
-
# Reducir tamaño de logs para ahorrar espacio en disco
|
|
96
|
-
innodb_log_file_size = 67108864 # 64MB
|
|
97
|
-
innodb_log_files_in_group = 2
|
|
98
|
-
|
|
99
|
-
# Configurar rotación de logs binarios
|
|
100
|
-
expire_logs_days = 3
|
|
101
|
-
max_binlog_size = 100M
|
|
102
|
-
|
|
103
|
-
# Limitar tamaño de queries temporales
|
|
104
|
-
tmp_table_size = 64M
|
|
105
|
-
max_heap_table_size = 64M
|
|
106
|
-
|
|
107
|
-
# Ajustar timeouts para liberar conexiones rápidamente
|
|
108
|
-
wait_timeout = 300
|
|
109
|
-
interactive_timeout = 300
|
|
110
|
-
|
|
111
|
-
# Habilitar slow query log con umbral razonable
|
|
112
|
-
slow_query_log = 1
|
|
113
|
-
long_query_time = 2
|
|
114
|
-
slow_query_log_file = /var/log/mysql/slow.log
|
|
115
|
-
EOF
|
|
116
|
-
echo " Configuración optimizada aplicada"
|
|
117
|
-
else
|
|
118
|
-
echo " No se tienen permisos para modificar $SERVER_CONFIG"
|
|
119
|
-
echo " Las optimizaciones recomendadas son:"
|
|
120
|
-
echo " - innodb_buffer_pool_size = [25% de RAM disponible]"
|
|
121
|
-
echo " - innodb_log_file_size = 64M"
|
|
122
|
-
echo " - expire_logs_days = 3"
|
|
123
|
-
echo " - wait_timeout = 300"
|
|
124
|
-
fi
|
|
125
|
-
else
|
|
126
|
-
echo " - $SERVER_CONFIG no encontrado"
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
# 4. ANALIZAR USO DE ESPACIO EN MARIADB
|
|
130
|
-
echo ""
|
|
131
|
-
echo "4. Analizando uso de espacio en bases de datos..."
|
|
132
|
-
|
|
133
|
-
# Obtener información de tamaño de bases de datos
|
|
134
|
-
mysql -u root -e "
|
|
135
|
-
SELECT
|
|
136
|
-
table_schema AS 'Base de Datos',
|
|
137
|
-
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Tamaño (MB)'
|
|
138
|
-
FROM information_schema.tables
|
|
139
|
-
GROUP BY table_schema
|
|
140
|
-
ORDER BY (data_length + index_length) DESC;
|
|
141
|
-
" 2>/dev/null || echo " - No se pudo acceder a la información de bases de datos"
|
|
142
|
-
|
|
143
|
-
# 5. RECOMENDACIONES FINALES
|
|
144
|
-
echo ""
|
|
145
|
-
echo "=== Recomendaciones ==="
|
|
146
|
-
echo "1. Revisa el directorio de backup: $BACKUP_DIR"
|
|
147
|
-
echo "2. Reinicia MariaDB para aplicar cambios de configuración:"
|
|
148
|
-
echo " sudo systemctl restart mariadb"
|
|
149
|
-
echo "3. Considera eliminar archivos innecesarios en el sistema"
|
|
150
|
-
echo "4. Monitorea el uso de disco regularmente con 'df -h'"
|
|
151
|
-
echo ""
|
|
152
|
-
echo "=== Optimización completada ==="
|
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 };
|