Files
jRDC-Multi/SSEHandler.bas
Jose Alberto Guerra Ugalde 8b876e5095 - VERSION 5.09.19
- feat(sqlite): Implementa optimización de SQLite (WAL e Índices)
- fix(manager): Extiende el comando 'test' para verificar todos los pools de conexión configurados.

- Mejoras al subsistema de logs y diagnóstico del servidor jRDC2-Multi.
- Cambios principales:
1. Optimización del Rendimiento de SQLite (users.db):
*   Habilitación de WAL: Se implementó PRAGMA journal_mode=WAL y PRAGMA synchronous=NORMAL en `InitializeSQLiteDatabase`. Esto reduce la contención de disco y mejora el rendimiento de I/O en las escrituras transaccionales de logs por lotes.
*   Índices de logs: Se agregaron índices a las columnas `timestamp` y `duration_ms` en `query_logs`, y a `timestamp` en `errores`. Esto acelera drásticamente las operaciones de limpieza periódica (`borraArribaDe15000Logs`) y la generación de reportes de consultas lentas (`slowqueries`).

2. Mejora del Comando de Diagnóstico 'test':
*   Se corrigió el comando `manager?command=test` para que no solo pruebe la conexión de `DB1`, sino que itere sobre `Main.listaDeCP` y fuerce la adquisición y liberación de una conexión (`GetConnection`) en *todos* los `RDCConnector` configurados (DB1, DB2, DB3, etc.).
*   La nueva lógica garantiza una prueba de vida rigurosa de cada pool C3P0, devolviendo un mensaje detallado del estado de conectividad y registrando un error crítico vía `LogServerError` si algún pool no responde.
2025-09-27 20:34:12 -06:00

132 lines
4.9 KiB
QBasic

B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=10.3
@EndOfDesignText@
' Handler class: StatsSSEHandler.b4j
' Gestiona y transmite en tiempo real las estadísticas del pool de conexiones vía Server-Sent Events (SSE).
' Opera en modo Singleton: una única instancia maneja todas las conexiones.
Sub Class_Globals
' Almacena de forma centralizada a todos los clientes (navegadores) conectados.
' La clave es un ID único y el valor es el canal de comunicación (OutputStream).
Private Connections As Map
' Timer #1 ("El Vigilante"): Se encarga de detectar y eliminar conexiones muertas.
Private RemoveTimer As Timer
' Timer #2 ("El Informante"): Se encarga de recolectar y enviar los datos de estadísticas.
Dim StatsTimer As Timer
Dim const UPDATE_INTERVAL_MS As Long = 2000 ' Intervalo de envío de estadísticas: 2 segundos.
End Sub
' Se ejecuta UNA SOLA VEZ cuando el servidor arranca, gracias al modo Singleton.
Public Sub Initialize
Log("Stats SSE Handler Initialized (Singleton Mode)")
' Crea el mapa de conexiones, asegurando que sea seguro para el manejo de múltiples hilos.
Connections = Main.srvr.CreateThreadSafeMap
' Configura y activa el timer para la limpieza de conexiones cada 5 segundos.
' NOTA: El EventName "RemoveTimer" debe coincidir con el nombre de la subrutina del tick.
RemoveTimer.Initialize("RemoveTimer", 5000)
RemoveTimer.Enabled = True
' Configura y activa el timer para el envío de estadísticas.
' NOTA: El EventName "StatsTimer" debe coincidir con el nombre de la subrutina del tick.
StatsTimer.Initialize("StatsTimer", UPDATE_INTERVAL_MS)
StatsTimer.Enabled = True
End Sub
' Es el punto de entrada principal. Atiende todas las peticiones HTTP dirigidas a este handler.
Sub Handle(req As ServletRequest, resp As ServletResponse)
Log($"StatsTimerinicializado: ${StatsTimer.IsInitialized}, StatsTimer habilitado: ${StatsTimer.Enabled}"$)
StatsTimer.Initialize("StatsTimer", 2000)
StatsTimer.Enabled = True
' Filtro de seguridad: verifica si el usuario tiene una sesión autorizada.
If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then
resp.SendRedirect("/login")
Return
End If
' Procesa únicamente las peticiones GET, que son las que usan los navegadores para iniciar una conexión SSE.
If req.Method = "GET" Then
' Mantiene la petición activa de forma asíncrona para poder enviar datos en el futuro.
Dim reqJO As JavaObject = req
reqJO.RunMethod("startAsync", Null)
' Registra al nuevo cliente para que empiece a recibir eventos.
SSE.AddTarget("stats", resp)
Else
' Rechaza cualquier otro método HTTP (POST, PUT, etc.) con un error.
resp.SendError(405, "Method Not Allowed")
End If
End Sub
' --- LÓGICA DE LOS TIMERS ---
' Evento del Timer #1 ("El Vigilante"): se dispara cada 5 segundos.
Sub RemoveTimer_Tick
' Log("REMOVETIMER TICK")
' Optimización: si no hay nadie conectado, no hace nada.
If Connections.Size = 0 Then Return
' Itera sobre todos los clientes para verificar si siguen activos.
For Each key As String In Connections.Keys
Try
' Envía un evento "ping" silencioso. Si la conexión está viva, no pasa nada.
SSE.SendMessage(Connections.Get(key), "ping", "", 0, "")
Catch
' Si el envío falla, la conexión está muerta. Se procede a la limpieza.
Log("######################")
Log("## Removing (timer cleanup): " & key)
Log("######################")
Connections.Remove(key)
End Try
Next
End Sub
' Evento del Timer #2 ("El Informante"): se dispara cada 2 segundos.
public Sub StatsTimer_Tick
' Optimización: si no hay nadie conectado, no realiza el trabajo pesado.
If Connections.Size = 0 Then Return
Try
' Prepara un mapa para almacenar las estadísticas recolectadas.
Dim allPoolStats As Map
allPoolStats.Initialize
' Bloquea el acceso a los conectores para leer sus datos de forma segura.
Main.MainConnectorsLock.RunMethod("lock", Null)
For Each dbKey As String In Main.listaDeCP
Dim connector As RDCConnector
If Main.Connectors.ContainsKey(dbKey) Then
connector = Main.Connectors.Get(dbKey)
If connector.IsInitialized Then
allPoolStats.Put(dbKey, connector.GetPoolStats)
Else
allPoolStats.Put(dbKey, CreateMap("Error": "Conector no inicializado"))
End If
End If
Next
' Libera el bloqueo para que otras partes del programa puedan usar los conectores.
Main.MainConnectorsLock.RunMethod("unlock", Null)
' Convierte el mapa de estadísticas a un formato de texto JSON.
Dim j As JSONGenerator
j.Initialize(allPoolStats)
Dim jsonStats As String = j.ToString
' Llama al "locutor" para enviar el JSON a todos los clientes conectados.
SSE.Broadcast("stats", "stats_update", jsonStats, 0)
Catch
' Captura y registra cualquier error que ocurra durante la recolección de datos.
Log($"[SSE] Error CRÍTICO durante la adquisición de estadísticas: ${LastException.Message}"$)
End Try
End Sub