mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-17 12:56:23 +00:00
- VERSION 5.09.14
-feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas -Este commit resuelve problemas críticos en el monitoreo del pool de conexiones (C3P0) y el conteo de peticiones activas por base de datos, mejorando significativamente la visibilidad y fiabilidad del rendimiento del servidor jRDC2-Multi. -Problemas Identificados y Resueltos: -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:** * **Problema**: Anteriormente, la métrica `busy_connections` en `query_logs` a menudo reportaba `0` o no reflejaba el estado real. De manera similar, el panel de `Manager?command=totalcon` consistentemente mostraba `BusyConnections: 0` y `TotalConnections` estancadas en `InitialPoolSize`, a pesar de que Oracle sí reportaba conexiones activas. Esto generaba confusión sobre el uso real y la expansión del pool. * **Solución**: Se modificó la lógica en los *handlers* (`DBHandlerJSON.bas` y `DBHandlerB4X.bas`) para capturar la métrica `BusyConnections` directamente del pool de C3P0 **inmediatamente después de que el *handler* adquiere una conexión** (`con = Connector.GetConnection(finalDbKey)`). Este valor se pasa explícitamente a la subrutina `Main.LogQueryPerformance` para su registro en `query_logs` y para ser consumido por `Manager.bas` a través de `RDCConnector.GetPoolStats`. Esto garantiza que el valor registrado y reportado refleje con precisión el número de conexiones activas en el instante de su adquisición. Pruebas exhaustivas confirmaron que C3P0 sí reporta conexiones ocupadas y sí expande `TotalConnections` hasta `MaxPoolSize` cuando la demanda lo exige. -2. **Contador `handler_active_requests` no decrementaba correctamente:** * **Problema**: El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) no mostraba un decremento consistente, resultando en un conteo que solo aumentaba o mostraba valores erráticos en los logs. * **Solución**: * Se aseguró la declaración `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`. * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores. * En `DBHandlerJSON.bas`, la `dbKey` (obtenida del parámetro `dbx` del JSON) ahora se resuelve *antes* de incrementar el contador, asegurando que el incremento y el decremento se apliquen siempre a la misma clave de base de datos correcta. * Se implementó una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones de lectura y escritura (`GetDefault`, `Put`) en `GlobalParameters.ActiveRequestsCountByDB`, resolviendo problemas de tipo que causaban inconsistencias y el fallo en el decremento. * La lógica de decremento en `Private Sub CleanupAndLog` (presente en ambos *handlers*) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos. -Beneficios de estos Cambios: * **Monitoreo Preciso y Fiable**: Las métricas `busy_connections` y `handler_active_requests` en `query_logs` y el panel `Manager` ahora son totalmente fiables, proporcionando una visión clara y en tiempo real del uso del pool de conexiones y la carga de peticiones activas por base de datos. * **Diagnóstico Mejorado**: La visibilidad interna del estado del pool de C3P0 durante las pruebas confirma que la configuración de `RDCConnector` es correcta y que el pool se expande y contrae según lo esperado por la demanda. * **Robustez del Código**: La gestión de contadores de peticiones activas es ahora consistente, thread-safe y a prueba de fallos de tipo, mejorando la estabilidad general del servidor bajo carga.
This commit is contained in:
28
Cambios.bas
28
Cambios.bas
@@ -16,6 +16,34 @@ Sub Process_Globals
|
||||
'- Agregar una forma de probar con carga el servidor
|
||||
'- Agregar la opcion de "Queries lentos"
|
||||
|
||||
' VERSION 5.09.14
|
||||
' -feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas
|
||||
|
||||
' -Este commit resuelve problemas críticos en el monitoreo del pool de conexiones (C3P0) y el conteo de peticiones activas por base de datos, mejorando significativamente la visibilidad y fiabilidad del rendimiento del servidor jRDC2-Multi.
|
||||
|
||||
' -Problemas Identificados y Resueltos:
|
||||
|
||||
' -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:**
|
||||
' * **Problema**: Anteriormente, la métrica `busy_connections` en `query_logs` a menudo reportaba `0` o no reflejaba el estado real. De manera similar, el panel de `Manager?command=totalcon` consistentemente mostraba `BusyConnections: 0` y `TotalConnections` estancadas en `InitialPoolSize`, a pesar de que Oracle sí reportaba conexiones activas. Esto generaba confusión sobre el uso real y la expansión del pool.
|
||||
' * **Solución**: Se modificó la lógica en los *handlers* (`DBHandlerJSON.bas` y `DBHandlerB4X.bas`) para capturar la métrica `BusyConnections` directamente del pool de C3P0 **inmediatamente después de que el *handler* adquiere una conexión** (`con = Connector.GetConnection(finalDbKey)`). Este valor se pasa explícitamente a la subrutina `Main.LogQueryPerformance` para su registro en `query_logs` y para ser consumido por `Manager.bas` a través de `RDCConnector.GetPoolStats`. Esto garantiza que el valor registrado y reportado refleje con precisión el número de conexiones activas en el instante de su adquisición. Pruebas exhaustivas confirmaron que C3P0 sí reporta conexiones ocupadas y sí expande `TotalConnections` hasta `MaxPoolSize` cuando la demanda lo exige.
|
||||
|
||||
' -2. **Contador `handler_active_requests` no decrementaba correctamente:**
|
||||
' * **Problema**: El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) no mostraba un decremento consistente, resultando en un conteo que solo aumentaba o mostraba valores erráticos en los logs.
|
||||
' * **Solución**:
|
||||
' * Se aseguró la declaración `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`.
|
||||
' * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores.
|
||||
' * En `DBHandlerJSON.bas`, la `dbKey` (obtenida del parámetro `dbx` del JSON) ahora se resuelve *antes* de incrementar el contador, asegurando que el incremento y el decremento se apliquen siempre a la misma clave de base de datos correcta.
|
||||
' * Se implementó una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones de lectura y escritura (`GetDefault`, `Put`) en `GlobalParameters.ActiveRequestsCountByDB`, resolviendo problemas de tipo que causaban inconsistencias y el fallo en el decremento.
|
||||
' * La lógica de decremento en `Private Sub CleanupAndLog` (presente en ambos *handlers*) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos.
|
||||
|
||||
' -Beneficios de estos Cambios:
|
||||
|
||||
' * **Monitoreo Preciso y Fiable**: Las métricas `busy_connections` y `handler_active_requests` en `query_logs` y el panel `Manager` ahora son totalmente fiables, proporcionando una visión clara y en tiempo real del uso del pool de conexiones y la carga de peticiones activas por base de datos.
|
||||
' * **Diagnóstico Mejorado**: La visibilidad interna del estado del pool de C3P0 durante las pruebas confirma que la configuración de `RDCConnector` es correcta y que el pool se expande y contrae según lo esperado por la demanda.
|
||||
' * **Robustez del Código**: La gestión de contadores de peticiones activas es ahora consistente, thread-safe y a prueba de fallos de tipo, mejorando la estabilidad general del servidor bajo carga.
|
||||
|
||||
|
||||
|
||||
'- VERSION 5.09.13.3
|
||||
'- Implementación de "Hot-Swap" para recarga de configuraciones de DB sin reiniciar el servidor.
|
||||
'- Migración a ReentrantLock para sincronización debido a incompatibilidad con 'Sync'.
|
||||
|
||||
@@ -35,7 +35,7 @@ Public Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
|
||||
Log("--- Probando con contraseña fija ---")
|
||||
Log("Valor de la BD (storedHash): " & storedHash)
|
||||
If storedHash = Null Or bc.checkpw("12345", storedHash) = False Then ' <<--- CAMBIO CLAVE AQUÍ
|
||||
If storedHash = Null Or bc.checkpw(currentPass, storedHash) = False Then ' <<--- CAMBIO CLAVE AQUÍ
|
||||
resp.Write("<script>alert('Error: La contraseña actual es incorrecta.'); history.back();</script>")
|
||||
Return
|
||||
End If
|
||||
|
||||
180
DBHandlerB4X.bas
180
DBHandlerB4X.bas
@@ -35,100 +35,136 @@ End Sub
|
||||
|
||||
' Método principal que maneja cada petición HTTP que llega a este servlet.
|
||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
' === INICIO DE LA LÓGICA DINÁMICA ===
|
||||
' Extrae la URI completa de la petición (ej. /DB1/endpoint).
|
||||
' === INICIO DE LA LÓGICA DINÁMICA (Extracción de dbKey de la URL) ===
|
||||
Dim URI As String = req.RequestURI
|
||||
' Variable para almacenar la "llave" o identificador de la base de datos (ej. "DB1").
|
||||
Dim dbKey As String
|
||||
|
||||
' Comprueba si la URI tiene contenido y empieza con "/".
|
||||
Dim dbKey As String ' Usamos dbKey para consistencia con tu código original.
|
||||
If URI.Length > 1 And URI.StartsWith("/") Then
|
||||
' Extrae la parte de la URI que viene después del primer "/".
|
||||
dbKey = URI.Substring(1)
|
||||
' Si la llave contiene más "/", se queda solo con la primera parte.
|
||||
' Esto permite URLs como /DB1/clientes o /DB2/productos, extrayendo "DB1" o "DB2".
|
||||
dbKey = URI.Substring(1) '[DBHandlerB4X.bas.txt, 51]
|
||||
If dbKey.Contains("/") Then
|
||||
dbKey = dbKey.SubString2(0, dbKey.IndexOf("/"))
|
||||
dbKey = dbKey.SubString2(0, dbKey.IndexOf("/")) '[DBHandlerB4X.bas.txt, 51]
|
||||
End If
|
||||
Else
|
||||
' Si la URI está vacía o es "/", usa "DB1" como la base de datos por defecto.
|
||||
dbKey = "DB1"
|
||||
dbKey = "DB1" '[DBHandlerB4X.bas.txt, 51]
|
||||
End If
|
||||
dbKey = dbKey.ToUpperCase '[DBHandlerB4X.bas.txt, 52]
|
||||
|
||||
' Convierte la llave a mayúsculas para que no sea sensible a mayúsculas/minúsculas (ej. "db1" se convierte en "DB1").
|
||||
dbKey = dbKey.ToUpperCase
|
||||
|
||||
' Verifica si la llave de la base de datos extraída existe en la configuración de conectores.
|
||||
If Main.Connectors.ContainsKey(dbKey) = False Then
|
||||
' Si no existe, crea un mensaje de error claro.
|
||||
Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$
|
||||
' Registra el error en el log del servidor.
|
||||
Log(ErrorMsg)
|
||||
' Envía una respuesta de error 400 (Bad Request) al cliente en formato de texto plano.
|
||||
SendPlainTextError(resp, 400, ErrorMsg)
|
||||
' Termina la ejecución de este método.
|
||||
If Main.Connectors.ContainsKey(dbKey) = False Then '[DBHandlerB4X.bas.txt, 52]
|
||||
Dim ErrorMsg As String = $"Invalid DB key specified in URL: '${dbKey}'. Valid keys are: ${Main.listaDeCP}"$ '[DBHandlerB4X.bas.txt, 52]
|
||||
Log(ErrorMsg) '[DBHandlerB4X.bas.txt, 52]
|
||||
SendPlainTextError(resp, 400, ErrorMsg) '[DBHandlerB4X.bas.txt, 52]
|
||||
' Aquí no se necesita CleanupAndLog, ya que el contador no se ha incrementado
|
||||
' y no se ha obtenido ninguna conexión del pool aún.
|
||||
Return
|
||||
End If
|
||||
' === FIN DE LA LÓGICA DINÁMICA ===
|
||||
|
||||
Log("********************* " & dbKey & " ********************")
|
||||
' Guarda el tiempo de inicio para medir la duración de la petición.
|
||||
Dim start As Long = DateTime.Now
|
||||
' Variable para almacenar el nombre del comando SQL a ejecutar.
|
||||
Dim q As String
|
||||
' Obtiene el stream de entrada de la petición, que contiene los datos enviados por el cliente.
|
||||
Dim in As InputStream = req.InputStream
|
||||
' Obtiene el parámetro "method" de la URL (ej. ?method=query2).
|
||||
Dim method As String = req.GetParameter("method")
|
||||
' Obtiene el conector correspondiente a la base de datos seleccionada.
|
||||
Connector = Main.Connectors.Get(dbKey)
|
||||
' Declara la variable para la conexión a la base de datos.
|
||||
Dim con As SQL
|
||||
Try
|
||||
' Obtiene una conexión del pool de conexiones.
|
||||
con = Connector.GetConnection(dbKey)
|
||||
Log("Metodo: " & method)
|
||||
' Determina qué función ejecutar basándose en el parámetro "method".
|
||||
Log("********************* " & dbKey & " ********************") '[DBHandlerB4X.bas.txt, 53]
|
||||
|
||||
Dim start As Long = DateTime.Now '[___new 3.txt, 203]
|
||||
|
||||
' --- INICIO: Conteo de peticiones activas para esta dbKey (Incrementar) ---
|
||||
Dim currentActiveRequests As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0) '[___new 3.txt, 205]
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentActiveRequests + 1) '[___new 3.txt, 205]
|
||||
Dim requestsBeforeDecrement As Int = currentActiveRequests + 1 '[___new 3.txt, 207]
|
||||
' --- FIN: Conteo de peticiones activas ---
|
||||
|
||||
' Declaraciones de variables con alcance en toda la subrutina para la limpieza.
|
||||
Dim q As String = "unknown_b4x_command" ' Nombre del comando para el log, con valor por defecto.
|
||||
Dim con As SQL ' La conexión a la BD, se inicializará más tarde.
|
||||
Dim duration As Long ' La duración de la petición, calculada antes del log.
|
||||
Dim poolBusyConnectionsForLog As Int = 0 ' Contiene el número de conexiones ocupadas del pool.
|
||||
|
||||
Try ' --- INICIO: Bloque Try que envuelve la lógica principal del Handler ---
|
||||
Dim in As InputStream = req.InputStream '[DBHandlerB4X.bas.txt, 53]
|
||||
Dim method As String = req.GetParameter("method") '[DBHandlerB4X.bas.txt, 53]
|
||||
Connector = Main.Connectors.Get(dbKey) '[DBHandlerB4X.bas.txt, 54]
|
||||
|
||||
con = Connector.GetConnection(dbKey) ' La conexión a la BD se obtiene aquí. [DBHandlerB4X.bas.txt, 54]
|
||||
|
||||
' <<<< ¡BUSY_CONNECTIONS YA SE CAPTURABA BIEN! >>>>
|
||||
If Connector.IsInitialized Then
|
||||
Dim poolStats As Map = Connector.GetPoolStats '[___new 3.txt, 204]
|
||||
If poolStats.ContainsKey("BusyConnections") Then
|
||||
poolBusyConnectionsForLog = poolStats.Get("BusyConnections") ' Capturamos el valor.
|
||||
End If
|
||||
End If
|
||||
' <<<< ¡FIN DE CAPTURA! >>>>
|
||||
|
||||
Log("Metodo: " & method) '[DBHandlerB4X.bas.txt, 54]
|
||||
|
||||
If method = "query2" Then
|
||||
' Ejecuta una consulta usando el protocolo más nuevo (B4XSerializator).
|
||||
q = ExecuteQuery2(dbKey, con, in, resp)
|
||||
q = ExecuteQuery2(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 54]
|
||||
If q = "error" Then ' Si ExecuteQuery2 devolvió un error de validación.
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
'#if VERSION1
|
||||
Else if method = "query" Then
|
||||
' Protocolo antiguo: descomprime el stream y ejecuta la consulta.
|
||||
in = cs.WrapInputStream(in, "gzip")
|
||||
q = ExecuteQuery(dbKey, con, in, resp)
|
||||
q = ExecuteQuery(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||
If q = "error" Then
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
Else if method = "batch" Then
|
||||
' Protocolo antiguo: descomprime el stream y ejecuta un lote de comandos.
|
||||
in = cs.WrapInputStream(in, "gzip")
|
||||
q = ExecuteBatch(dbKey, con, in, resp)
|
||||
q = ExecuteBatch(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||
If q = "error" Then
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
'#end if
|
||||
Else if method = "batch2" Then
|
||||
' Ejecuta un lote de comandos usando el protocolo más nuevo.
|
||||
q = ExecuteBatch2(dbKey, con, in, resp)
|
||||
q = ExecuteBatch2(dbKey, con, in, resp) '[DBHandlerB4X.bas.txt, 55]
|
||||
If q = "error" Then
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, "error_in_" & method, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
Else
|
||||
' Si el método es desconocido, lo registra y envía un error.
|
||||
Log("Unknown method: " & method)
|
||||
SendPlainTextError(resp, 500, "unknown method")
|
||||
Log("Unknown method: " & method) '[DBHandlerB4X.bas.txt, 56]
|
||||
SendPlainTextError(resp, 500, "unknown method") '[DBHandlerB4X.bas.txt, 56]
|
||||
q = "unknown_method_handler" ' Aseguramos un valor para q en el log.
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return
|
||||
End If
|
||||
Catch
|
||||
' Si ocurre cualquier error en el bloque Try, lo captura.
|
||||
Log(LastException)
|
||||
' Envía un error 500 (Internal Server Error) al cliente con el mensaje de la excepción.
|
||||
SendPlainTextError(resp, 500, LastException.Message)
|
||||
End Try
|
||||
' Asegura que la conexión a la BD se cierre y se devuelva al pool.
|
||||
If con <> Null And con.IsInitialized Then con.Close
|
||||
' Registra en el log el comando ejecutado, cuánto tiempo tardó y la IP del cliente.
|
||||
Log($"Command: ${q}, took: ${DateTime.Now - start}ms, client=${req.RemoteAddress}"$)
|
||||
|
||||
' *** NUEVO: Insertar el log en la base de datos SQLite ***
|
||||
Dim duration As Long = DateTime.Now - start
|
||||
Try
|
||||
Main.SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip) VALUES (?, ?, ?, ?, ?)", _
|
||||
Array As Object(q, duration, DateTime.Now, dbKey, req.RemoteAddress))
|
||||
Catch
|
||||
Log("Error al guardar log de query en SQLite (DBHandlerB4X): " & LastException.Message)
|
||||
End Try
|
||||
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL ---
|
||||
Log(LastException) '[DBHandlerB4X.bas.txt, 56]
|
||||
SendPlainTextError(resp, 500, LastException.Message) '[DBHandlerB4X.bas.txt, 56]
|
||||
q = "error_in_b4x_handler" ' Aseguramos un valor para q en el log si hay excepción.
|
||||
End Try ' --- FIN: Bloque Try principal ---
|
||||
|
||||
' --- Lógica de logging y limpieza final (para rutas de ejecución normal o después de Catch) ---
|
||||
duration = DateTime.Now - start '[DBHandlerB4X.bas.txt, 57]
|
||||
Log($"Command: ${q}, took: ${duration}ms, client=${req.RemoteAddress}"$) '[DBHandlerB4X.bas.txt, 57]
|
||||
CleanupAndLog(dbKey, q, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
|
||||
End Sub
|
||||
|
||||
' --- NUEVA SUBRUTINA: Centraliza el logging y la limpieza ---
|
||||
Private Sub CleanupAndLog(dbKey As String, qName As String, durMs As Long, clientIp As String, handlerReqs As Int, poolBusyConns As Int, conn As SQL)
|
||||
' 1. Llama a la subrutina centralizada para registrar el rendimiento.
|
||||
Main.LogQueryPerformance(qName, durMs, dbKey, clientIp, handlerReqs, poolBusyConns) '[___new 3.txt, 207]
|
||||
|
||||
' <<<< ¡CORRECCIÓN CLAVE AQUÍ! >>>>
|
||||
' 2. Decrementa el contador de peticiones activas para esta dbKey de forma más robusta.
|
||||
Dim currentCount As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0)
|
||||
If currentCount > 0 Then
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentCount - 1)
|
||||
Else
|
||||
' Si el contador ya está en 0 o negativo, registramos una advertencia y lo aseguramos en 0.
|
||||
Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
||||
End If
|
||||
' <<<< ¡FIN DE CORRECCIÓN CLAVE! >>>>
|
||||
|
||||
' 3. Asegura que la conexión a la BD siempre se cierre y se devuelva al pool.
|
||||
If conn <> Null And conn.IsInitialized Then conn.Close
|
||||
End Sub
|
||||
|
||||
' Ejecuta una consulta única usando el protocolo V2 (B4XSerializator).
|
||||
|
||||
@@ -18,125 +18,132 @@ End Sub
|
||||
' Este es el método principal que maneja las peticiones HTTP entrantes (req) y prepara la respuesta (resp).
|
||||
' Este es el método principal que maneja las peticiones HTTP entrantes (req) y prepara la respuesta (resp).
|
||||
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
|
||||
' --- Headers CORS (Cross-Origin Resource Sharing) ---
|
||||
' Estos encabezados son necesarios para permitir que un cliente web (ej. una página con JavaScript)
|
||||
' que se encuentra en un dominio diferente pueda hacer peticiones a este servidor.
|
||||
resp.SetHeader("Access-Control-Allow-Origin", "*") ' Permite peticiones desde cualquier origen.
|
||||
resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS") ' Métodos HTTP permitidos.
|
||||
resp.SetHeader("Access-Control-Allow-Headers", "Content-Type") ' Encabezados permitidos en la petición.
|
||||
resp.SetHeader("Access-Control-Allow-Origin", "*")
|
||||
resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
resp.SetHeader("Access-Control-Allow-Headers", "Content-Type")
|
||||
|
||||
' El método OPTIONS es una "petición de comprobación previa" (preflight request) que envían los navegadores
|
||||
' para verificar los permisos CORS antes de enviar la petición real (ej. POST).
|
||||
' Si es una petición OPTIONS, simplemente terminamos la ejecución sin procesar nada más.
|
||||
If req.Method = "OPTIONS" Then Return
|
||||
If req.Method = "OPTIONS" Then
|
||||
Return ' Las peticiones OPTIONS no incrementan contadores ni usan BD, así que salimos directamente.
|
||||
End If
|
||||
|
||||
' Establece "DB1" como el nombre de la base de datos por defecto.
|
||||
Dim DB As String = "DB1"
|
||||
Dim start As Long = DateTime.Now
|
||||
|
||||
' Obtiene el objeto conector para la base de datos por defecto o especificada.
|
||||
Connector = Main.Connectors.Get(DB)
|
||||
' Declaraciones de variables con alcance en toda la subrutina para la limpieza.
|
||||
Dim con As SQL ' La conexión a la BD, se inicializará más tarde.
|
||||
Dim queryNameForLog As String = "unknown_json_command" ' Nombre del comando para el log, con valor por defecto.
|
||||
Dim duration As Long ' La duración de la petición, calculada antes del log.
|
||||
Dim poolBusyConnectionsForLog As Int = 0 ' Contiene el número de conexiones ocupadas del pool.
|
||||
|
||||
Dim finalDbKey As String = "DB1"
|
||||
Dim requestsBeforeDecrement As Int = 0 ' Se inicializa en 0.
|
||||
|
||||
' Declara una variable para la conexión SQL.
|
||||
Dim con As SQL
|
||||
Try ' --- INICIO: Bloque Try que envuelve la lógica principal del Handler ---
|
||||
|
||||
' Inicia un bloque Try...Catch para manejar posibles errores durante la ejecución.
|
||||
Try
|
||||
Dim jsonString As String
|
||||
|
||||
' *** INICIO DE LA LÓGICA CORREGIDA PARA LEER JSON DEL CUERPO (POST) O DE PARÁMETROS (GET/POST Form-urlencoded) ***
|
||||
If req.Method = "POST" And req.ContentType.Contains("application/json") Then
|
||||
' Para peticiones POST con Content-Type: application/json, el JSON viene en el cuerpo (InputStream).
|
||||
Dim Is0 As InputStream = req.InputStream ' ¡CORREGIDO: Usamos Is0 en lugar de Is para evitar conflicto con palabra reservada!
|
||||
' Usamos Bit.InputStreamToBytes de la librería jcore para leer el stream completo a un array de bytes.
|
||||
Dim Is0 As InputStream = req.InputStream
|
||||
Dim bytes() As Byte = Bit.InputStreamToBytes(Is0)
|
||||
jsonString = BytesToString(bytes, 0, bytes.Length, "UTF8") ' Convertimos los bytes a String UTF8.
|
||||
Is0.Close ' ¡Es CRÍTICO cerrar el InputStream para liberar los recursos del sistema!
|
||||
jsonString = BytesToString(bytes, 0, bytes.Length, "UTF8")
|
||||
Is0.Close
|
||||
Else
|
||||
' Para peticiones GET o POST con Content-Type como 'application/x-www-form-urlencoded',
|
||||
' el JSON se espera en el parámetro 'j' de la URL.
|
||||
jsonString = req.GetParameter("j")
|
||||
End If
|
||||
' *** FIN DE LA LÓGICA CORREGIDA PARA LEER JSON ***
|
||||
|
||||
' Verifica si la cadena JSON obtenida es nula o está vacía.
|
||||
If jsonString = Null Or jsonString = "" Then
|
||||
' Si falta el JSON, envía una respuesta de error 400 (Bad Request) y termina la ejecución.
|
||||
SendErrorResponse(resp, 400, "Falta el parámetro 'j' en el URL o el cuerpo JSON en la petición.")
|
||||
Return
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return ' Salida temprana.
|
||||
End If
|
||||
|
||||
' Crea un objeto JSONParser para analizar la cadena JSON.
|
||||
Dim parser As JSONParser
|
||||
parser.Initialize(jsonString)
|
||||
|
||||
' Convierte la cadena JSON en un objeto Map, que es como un diccionario (clave-valor).
|
||||
Dim RootMap As Map = parser.NextObject
|
||||
|
||||
' Extrae los datos necesarios del JSON.
|
||||
Dim execType As String = RootMap.GetDefault("exec", "") ' Tipo de ejecución: "executeQuery" (SELECT) o "executeCommand" (INSERT/UPDATE/DELETE).
|
||||
Dim queryName As String = RootMap.Get("query") ' Nombre del comando SQL (definido en config.properties).
|
||||
Dim execType As String = RootMap.GetDefault("exec", "")
|
||||
|
||||
' Se obtiene "params" como una Lista en lugar de un Mapa, para soportar el ordenamiento de B4A.
|
||||
Dim paramsList As List = RootMap.Get("params")
|
||||
queryNameForLog = RootMap.GetDefault("query", "") '[___new 3.txt, 203]
|
||||
If queryNameForLog = "" Then queryNameForLog = RootMap.GetDefault("exec", "unknown_json_command") '[___new 3.txt, 203]
|
||||
|
||||
' Si la lista de parámetros es nula (no se proporcionó en el JSON), la inicializamos como una lista vacía.
|
||||
Dim paramsList As List = RootMap.Get("params")
|
||||
If paramsList = Null Or paramsList.IsInitialized = False Then
|
||||
paramsList.Initialize
|
||||
End If
|
||||
|
||||
' Verifica si en el JSON se especificó un nombre de base de datos diferente con la clave "dbx".
|
||||
If RootMap.Get("dbx") <> Null Then DB = RootMap.Get("dbx") ' Si se especifica, usamos la BD indicada, si no, se queda "DB1".
|
||||
' <<<< ¡CORRECCIÓN CLAVE AQUÍ: RESOLVEMOS finalDbKey del JSON ANTES! >>>>
|
||||
If RootMap.Get("dbx") <> Null Then finalDbKey = RootMap.Get("dbx") '[___new 3.txt, 204]
|
||||
' <<<< ¡FIN DE CORRECCIÓN CLAVE! >>>>
|
||||
|
||||
' Valida que el nombre de la base de datos (DB) exista en la lista de conexiones configuradas en Main.
|
||||
If Main.listaDeCP.IndexOf(DB) = -1 Then
|
||||
SendErrorResponse(resp, 400, "Parámetro 'DB' inválido. El nombre '" & DB & "' no es válido.")
|
||||
Return
|
||||
' --- INICIO: Conteo de peticiones activas para esta finalDbKey (Incrementar) ---
|
||||
' 1. Aseguramos que el valor inicial sea un Int y lo recuperamos como Int.
|
||||
Dim currentCountFromMap As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(finalDbKey, 0).As(Int)
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(finalDbKey, currentCountFromMap + 1)
|
||||
requestsBeforeDecrement = currentCountFromMap + 1 ' Este es el valor que se registra en query_logs
|
||||
' Log($"[DEBUG] Handle Increment: dbKey=${finalDbKey}, currentCountFromMap=${currentCountFromMap}, requestsBeforeDecrement=${requestsBeforeDecrement}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||
' --- FIN: Conteo de peticiones activas ---
|
||||
|
||||
Connector = Main.Connectors.Get(finalDbKey) ' Inicializamos el Connector con la finalDbKey resuelta.
|
||||
|
||||
|
||||
If Main.listaDeCP.IndexOf(finalDbKey) = -1 Then
|
||||
SendErrorResponse(resp, 400, "Parámetro 'DB' inválido. El nombre '" & finalDbKey & "' no es válido.")
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return ' Salida temprana.
|
||||
End If
|
||||
|
||||
' Obtiene una conexión a la base de datos del pool de conexiones para la DB seleccionada.
|
||||
con = Connector.GetConnection(DB)
|
||||
con = Connector.GetConnection(finalDbKey) ' La conexión a la BD se obtiene aquí.
|
||||
|
||||
' <<<< ¡AÑADIR ESTE RETRASO ARTIFICIAL PARA LA PRUEBA! >>>>
|
||||
' Esto forzará a C3P0 a mantener las conexiones ocupadas por más tiempo.
|
||||
' Si tienes 100 VUs, esto debería hacer que BusyConnections suba.
|
||||
' Sleep(100) ' Retraso artificial de 100ms para pruebas.
|
||||
' Log($"[DEBUG - ${finalDbKey}] Retraso artificial de 500ms aplicado. Pool Stats (antes de exec): Busy=${Connector.GetPoolStats.GetDefault("BusyConnections",0).As(Int)}, Total=${Connector.GetPoolStats.GetDefault("TotalConnections",0).As(Int)}"$ )
|
||||
' <<<< ¡FIN DEL RETRASO ARTIFICIAL! >>>>
|
||||
|
||||
' <<<< BUSY_CONNECTIONS YA SE CAPTURABA BIEN. LO MANTENEMOS. >>>>
|
||||
If Connector.IsInitialized Then
|
||||
Dim poolStats As Map = Connector.GetPoolStats '[___new 3.txt, 204]
|
||||
If poolStats.ContainsKey("BusyConnections") Then
|
||||
poolBusyConnectionsForLog = poolStats.Get("BusyConnections").As(Int) ' Aseguramos que sea Int.
|
||||
End If
|
||||
End If
|
||||
' <<<< FIN DE CAPTURA! >>>>
|
||||
|
||||
' Obtiene la cadena SQL del archivo de configuración usando el nombre de la consulta (queryName).
|
||||
Dim sqlCommand As String = Connector.GetCommand(DB, queryName)
|
||||
Dim sqlCommand As String = Connector.GetCommand(finalDbKey, queryNameForLog)
|
||||
|
||||
' <<< INICIO VALIDACIÓN: VERIFICAR SI EL COMANDO EXISTE >>>
|
||||
If sqlCommand = Null Or sqlCommand = "null" Or sqlCommand.Trim = "" Then
|
||||
Dim errorMessage As String = $"El comando '${queryName}' no fue encontrado en el config.properties de '${DB}'."$
|
||||
Dim errorMessage As String = $"El comando '${queryNameForLog}' no fue encontrado en el config.properties de '${finalDbKey}'."$
|
||||
Log(errorMessage)
|
||||
SendErrorResponse(resp, 400, errorMessage)
|
||||
If con <> Null And con.IsInitialized Then con.Close
|
||||
Return
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return ' Salida temprana.
|
||||
End If
|
||||
' <<< FIN VALIDACIÓN >>>
|
||||
|
||||
' Comprueba el tipo de ejecución solicitado ("executeQuery" o "executeCommand").
|
||||
If execType.ToLowerCase = "executequery" Then
|
||||
Dim rs As ResultSet
|
||||
If sqlCommand.Contains("?") Or paramsList.Size > 0 Then
|
||||
' =================================================================
|
||||
' === VALIDACIÓN DE CONTEO DE PARÁMETROS ==========================
|
||||
' =================================================================
|
||||
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
||||
Dim receivedParams As Int = paramsList.Size
|
||||
Log($"expectedParams: ${expectedParams}, receivedParams: ${receivedParams}"$)
|
||||
If expectedParams <> receivedParams Then
|
||||
SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
||||
If con <> Null And con.IsInitialized Then con.Close
|
||||
Return
|
||||
SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryNameForLog}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return ' Salida temprana.
|
||||
End If
|
||||
' =================================================================
|
||||
rs = con.ExecQuery2(sqlCommand, paramsList)
|
||||
Else
|
||||
rs = con.ExecQuery(sqlCommand)
|
||||
End If
|
||||
|
||||
' --- Procesamiento de resultados ---
|
||||
Dim ResultList As List
|
||||
ResultList.Initialize
|
||||
Dim jrs As JavaObject = rs
|
||||
Dim rsmd As JavaObject = jrs.RunMethod("getMetaData", Null)
|
||||
Dim cols As Int = rsmd.RunMethod("getColumnCount", Null)
|
||||
|
||||
Do While rs.NextRow
|
||||
Dim RowMap As Map
|
||||
RowMap.Initialize
|
||||
@@ -149,35 +156,60 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
Loop
|
||||
rs.Close
|
||||
SendSuccessResponse(resp, CreateMap("result": ResultList))
|
||||
|
||||
Else If execType.ToLowerCase = "executecommand" Then
|
||||
If sqlCommand.Contains("?") Then
|
||||
' =================================================================
|
||||
' === VALIDACIÓN DE CONTEO DE PARÁMETROS (para Comandos) ==========
|
||||
' =================================================================
|
||||
Dim expectedParams As Int = sqlCommand.Length - sqlCommand.Replace("?", "").Length
|
||||
Dim receivedParams As Int = paramsList.Size
|
||||
If expectedParams <> receivedParams Then
|
||||
SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryName}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
||||
If con <> Null And con.IsInitialized Then con.Close
|
||||
Return
|
||||
SendErrorResponse(resp, 400, $"Número de parámetros equivocado para '${queryNameForLog}'. Se esperaban ${expectedParams} y se recibieron ${receivedParams}."$)
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
Return ' Salida temprana.
|
||||
End If
|
||||
' =================================================================
|
||||
End If
|
||||
con.ExecNonQuery2(sqlCommand, paramsList)
|
||||
SendSuccessResponse(resp, CreateMap("message": "Command executed successfully"))
|
||||
|
||||
Else
|
||||
SendErrorResponse(resp, 400, "Parámetro 'exec' inválido. '" & execType & "' no es un valor permitido.")
|
||||
' El flujo continúa hasta la limpieza final si no hay un Return explícito.
|
||||
End If
|
||||
Catch
|
||||
|
||||
Catch ' --- CATCH: Maneja errores generales de ejecución o de SQL/JSON ---
|
||||
Log(LastException)
|
||||
SendErrorResponse(resp, 500, LastException.Message)
|
||||
End Try
|
||||
queryNameForLog = "error_processing_json" ' Para registrar que hubo un error en el log.
|
||||
End Try ' --- FIN: Bloque Try principal ---
|
||||
|
||||
' --- Lógica de logging y limpieza final (para rutas de ejecución normal o después de Catch) ---
|
||||
duration = DateTime.Now - start
|
||||
CleanupAndLog(finalDbKey, queryNameForLog, duration, req.RemoteAddress, requestsBeforeDecrement, poolBusyConnectionsForLog, con)
|
||||
|
||||
If con <> Null And con.IsInitialized Then
|
||||
con.Close
|
||||
End If
|
||||
End Sub
|
||||
|
||||
' --- NUEVA SUBRUTINA: Centraliza el logging y la limpieza ---
|
||||
Private Sub CleanupAndLog(dbKey As String, qName As String, durMs As Long, clientIp As String, handlerReqs As Int, poolBusyConns As Int, conn As SQL)
|
||||
' Log($"[DEBUG] CleanupAndLog Entry: dbKey=${dbKey}, handlerReqs=${handlerReqs}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||
' 1. Llama a la subrutina centralizada para registrar el rendimiento.
|
||||
Main.LogQueryPerformance(qName, durMs, dbKey, clientIp, handlerReqs, poolBusyConns) '[___new 3.txt, 207]
|
||||
|
||||
' <<<< ¡CORRECCIÓN CLAVE AQUÍ: Aseguramos que currentCount sea Int! >>>>
|
||||
Dim currentCount As Int = GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey, 0).As(Int)
|
||||
' Log($"[DEBUG] CleanupAndLog Before Decrement: dbKey=${dbKey}, currentCount (as Int)=${currentCount}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||
|
||||
If currentCount > 0 Then
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, currentCount - 1)
|
||||
Else
|
||||
Log($"ADVERTENCIA: Intento de decrementar ActiveRequestsCountByDB para ${dbKey} que ya estaba en ${currentCount}. Asegurando a 0."$)
|
||||
GlobalParameters.ActiveRequestsCountByDB.Put(dbKey, 0)
|
||||
End If
|
||||
' Log($"[DEBUG] CleanupAndLog After Decrement: dbKey=${dbKey}, New count (as Int)=${GlobalParameters.ActiveRequestsCountByDB.GetDefault(dbKey,0).As(Int)}, Map state: ${GlobalParameters.ActiveRequestsCountByDB}"$)
|
||||
' <<<< ¡FIN DE CORRECCIÓN CLAVE! >>>>
|
||||
|
||||
' 3. Asegura que la conexión a la BD siempre se cierre y se devuelva al pool.
|
||||
If conn <> Null And conn.IsInitialized Then conn.Close
|
||||
End Sub
|
||||
|
||||
' --- Subrutinas de ayuda para respuestas JSON ---
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Sub Class_Globals
|
||||
End Sub
|
||||
|
||||
Public Sub Initialize
|
||||
' bc.Initialize
|
||||
bc.Initialize("BC")
|
||||
End Sub
|
||||
|
||||
Public Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
|
||||
@@ -14,4 +14,5 @@ Sub Process_Globals
|
||||
Public mpTotalRequests As Map
|
||||
Public mpTotalConnections As Map
|
||||
Public mpBlockConnection As Map
|
||||
Public ActiveRequestsCountByDB As Map ' Mapa para contar las peticiones activas por DB
|
||||
End Sub
|
||||
29
Manager.bas
29
Manager.bas
@@ -192,6 +192,35 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
|
||||
' Si la recarga falló, los conectores antiguos (oldConnectors) se mantienen activos
|
||||
' y siguen sirviendo para evitar un paro del servicio.
|
||||
End If
|
||||
Else If Command = "slowqueries" Then ' <<< INICIO: NUEVA Lógica para mostrar las queries lentas
|
||||
sb.Append("<h2>Consultas Lentas Recientes</h2>")
|
||||
Try
|
||||
' Ajusta la consulta SQL para obtener las 20 queries más lentas.
|
||||
' Utilizamos datetime con 'unixepoch' y 'localtime' para una visualización legible del timestamp.
|
||||
Dim rs As ResultSet = Main.SQL1.ExecQuery("SELECT query_name, duration_ms, datetime(timestamp / 1000, 'unixepoch', 'localtime') as timestamp_local, db_key, client_ip, busy_connections, handler_active_requests FROM query_logs ORDER BY duration_ms DESC LIMIT 20")
|
||||
|
||||
sb.Append("<table border='1' style='width:100%; text-align:left; border-collapse: collapse;'>")
|
||||
sb.Append("<thead><tr><th>Query</th><th>Duración (ms)</th><th>Fecha/Hora Local</th><th>DB Key</th><th>Cliente IP</th><th>Conex. Ocupadas</th><th>Peticiones Activas</th></tr></thead>")
|
||||
sb.Append("<tbody>")
|
||||
|
||||
Do While rs.NextRow
|
||||
sb.Append("<tr>")
|
||||
sb.Append($"<td>${rs.GetString("query_name")}</td>"$)
|
||||
sb.Append($"<td>${rs.GetLong("duration_ms")}</td>"$)
|
||||
sb.Append($"<td>${rs.GetString("timestamp_local")}</td>"$)
|
||||
sb.Append($"<td>${rs.GetString("db_key")}</td>"$)
|
||||
sb.Append($"<td>${rs.GetString("client_ip")}</td>"$)
|
||||
sb.Append($"<td>${rs.GetInt("busy_connections")}</td>"$)
|
||||
sb.Append($"<td>${rs.GetInt("handler_active_requests")}</td>"$)
|
||||
sb.Append("</tr>")
|
||||
Loop
|
||||
sb.Append("</tbody>")
|
||||
sb.Append("</table>")
|
||||
rs.Close
|
||||
Catch
|
||||
Log("Error al obtener queries lentas en Manager: " & LastException.Message)
|
||||
sb.Append($"<p style='color:red;'>Error al cargar queries lentas: ${LastException.Message}</p>"$)
|
||||
End Try
|
||||
Else If Command = "test" Then
|
||||
Try
|
||||
Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("")
|
||||
|
||||
@@ -168,12 +168,34 @@ End Sub
|
||||
' Obtiene una conexión SQL funcional del pool de conexiones para la base de datos especificada.
|
||||
' DB: El identificador de la base de datos.
|
||||
' Retorna un objeto SQL (la conexión a la base de datos).
|
||||
'Public Sub GetConnection(DB As String) As SQL
|
||||
' If DB.EqualsIgnoreCase("DB1") Then DB = ""
|
||||
' ' En modo de depuración, recarga los comandos SQL en cada petición.
|
||||
' ' Esto permite modificar queries en config.properties sin reiniciar el servidor.
|
||||
' If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB)
|
||||
' Return pool.GetConnection ' Retorna una conexión del pool.
|
||||
'End Sub
|
||||
|
||||
Public Sub GetConnection(DB As String) As SQL
|
||||
If DB.EqualsIgnoreCase("DB1") Then DB = ""
|
||||
' En modo de depuración, recarga los comandos SQL en cada petición.
|
||||
' Esto permite modificar queries en config.properties sin reiniciar el servidor.
|
||||
If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB)
|
||||
Return pool.GetConnection ' Retorna una conexión del pool.
|
||||
If DB.EqualsIgnoreCase("DB1") Then DB = ""
|
||||
|
||||
' If DebugQueries Then LoadSQLCommands(LoadConfigMap(DB), DB) ' Esta línea es condicional a DebugQueries
|
||||
|
||||
' <<<< ¡ESTOS SON LOS LOGS QUE NECESITAMOS VER! >>>>
|
||||
' Log($"[DEBUG - ${DB}] RDCConnector.GetConnection: Solicitando conexión del pool..."$)
|
||||
Dim conn As SQL = pool.GetConnection
|
||||
' Log($"[DEBUG - ${DB}] RDCConnector.GetConnection: Conexión obtenida. IsInitialized: ${conn.IsInitialized}"$)
|
||||
|
||||
If pool.IsInitialized Then ' Doble verificación del estado del pool para logging
|
||||
Dim jo As JavaObject = pool
|
||||
' Aseguramos que los valores sean Ints, manejando posible retorno como Double.
|
||||
Dim busyCount As Int = jo.RunMethod("getNumBusyConnectionsAllUsers", Null).As(Object).As(Int)
|
||||
Dim totalCount As Int = jo.RunMethod("getNumConnectionsAllUsers", Null).As(Object).As(Int)
|
||||
' Log($"[DEBUG - ${DB}] RDCConnector.GetConnection: Estadísticas del Pool (después de obtener): Busy=${busyCount}, Total=${totalCount}"$)
|
||||
End If
|
||||
' <<<< ¡FIN DE LOS LOGS A BUSCAR! >>>>
|
||||
|
||||
Return conn
|
||||
End Sub
|
||||
|
||||
' Carga todos los comandos SQL del mapa de configuración en el mapa global 'commandsMap'.
|
||||
@@ -244,11 +266,11 @@ Public Sub GetPoolStats As Map
|
||||
' Log($"RDCConnector.GetPoolStats: CheckoutTimeout = ${checkoutTime}"$)
|
||||
|
||||
Catch
|
||||
Log("RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas del pool: " & LastException.Message)
|
||||
' Log("RDCConnector.GetPoolStats: ERROR CRÍTICO al obtener estadísticas del pool: " & LastException.Message)
|
||||
stats.Put("Error", LastException.Message)
|
||||
End Try
|
||||
Else
|
||||
Log("RDCConnector.GetPoolStats: ADVERTENCIA: Pool NO está inicializado. Retornando mapa con error.")
|
||||
' Log("RDCConnector.GetPoolStats: ADVERTENCIA: Pool NO está inicializado. Retornando mapa con error.")
|
||||
stats.Put("Error", "Pool de conexiones no inicializado para esta DB.")
|
||||
End If
|
||||
|
||||
@@ -265,7 +287,7 @@ End Sub
|
||||
' cuando un conector RDC ya no es necesario o va a ser reemplazado.
|
||||
Public Sub Close
|
||||
If pool <> Null And pool.IsInitialized Then
|
||||
Log($"RDCConnector: Cerrando pool de conexiones."$)
|
||||
' Log($"RDCConnector: Cerrando pool de conexiones."$)
|
||||
' Convertimos el objeto pool de B4X a un JavaObject para poder llamar a su método 'close()'
|
||||
' que no está expuesto directamente en la envoltura de B4X.
|
||||
Dim joPool As JavaObject = pool
|
||||
|
||||
272
jRDC_Multi.b4j
272
jRDC_Multi.b4j
@@ -53,7 +53,7 @@ Version=10.3
|
||||
#Region Project Attributes
|
||||
#CommandLineArgs:
|
||||
#MergeLibraries: True
|
||||
' VERSION 5.09.014
|
||||
' VERSION 5.09.14
|
||||
'###########################################################################################################
|
||||
'###################### PULL #############################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
||||
@@ -66,18 +66,6 @@ Version=10.3
|
||||
'###########################################################################################################
|
||||
#End Region
|
||||
|
||||
'- VERSION 5.09.08
|
||||
'- Se agregó que se puedan configurar en el config.properties los siguientes parametros:
|
||||
'
|
||||
' - setInitialPoolSize = 3
|
||||
' - setMinPoolSize = 2
|
||||
' - setMaxPoolSize = 5
|
||||
'
|
||||
'- Se agregaron en duro a RDConnector los siguientes parametros:
|
||||
'
|
||||
' - setMaxIdleTime <-- Tiempo máximo de inactividad de la conexión.
|
||||
' - setMaxConnectionAge <-- Tiempo de vida máximo de una conexión.
|
||||
' - setCheckoutTimeout <-- Tiempo máximo de espera por una conexión.
|
||||
'change based on the jdbc jar file
|
||||
'#AdditionalJar: mysql-connector-java-5.1.27-bin
|
||||
'#AdditionalJar: postgresql-42.7.0
|
||||
@@ -105,6 +93,7 @@ Sub Process_Globals
|
||||
Public SQL1 As SQL ' Objeto SQL para interactuar con la base de datos de usuarios (SQLite).
|
||||
Private bc As BCrypt ' Objeto para realizar operaciones de hashing de contraseñas de forma segura (para autenticación).
|
||||
Public MainConnectorsLock As JavaObject ' Objeto de bloqueo para proteger Main.Connectors
|
||||
Public ActiveRequestsCountByDB As Map
|
||||
End Sub
|
||||
|
||||
Sub AppStart (Args() As String)
|
||||
@@ -120,6 +109,8 @@ Sub AppStart (Args() As String)
|
||||
GlobalParameters.mpTotalRequests.Initialize ' Mapa para contar peticiones por endpoint/DB.
|
||||
GlobalParameters.mpTotalConnections.Initialize ' Mapa para almacenar el estado de los pools de conexión por DB.
|
||||
GlobalParameters.mpBlockConnection.Initialize ' Mapa para gestionar IPs bloqueadas (si la funcionalidad está activa).
|
||||
GlobalParameters.ActiveRequestsCountByDB = srvr.CreateThreadSafeMap ' Aseguramos que sea thread-safe para conteo de peticiones activas por DB [___new 3.txt, conversación]
|
||||
|
||||
|
||||
' 3. Inicializa las estructuras principales del servidor HTTP.
|
||||
listaDeCP.Initialize ' Inicializa la lista que contendrá los IDs de las bases de datos.
|
||||
@@ -145,105 +136,182 @@ Sub AppStart (Args() As String)
|
||||
' en el mismo directorio donde se ejecuta el JAR.
|
||||
cpFiles = File.ListFiles("./")
|
||||
If cpFiles.Size > 0 Then
|
||||
For i = 0 To cpFiles.Size - 1
|
||||
' Procesa 'config.DB2.properties'
|
||||
If cpFiles.Get(i) = "config.DB2.properties" Then
|
||||
Dim con2 As RDCConnector ' Declara una variable específica y única para el conector de DB2.
|
||||
con2.Initialize("DB2") ' Inicializa la instancia del conector para "DB2".
|
||||
Connectors.Put("DB2", con2) ' Asocia "DB2" con su instancia de RDCConnector.
|
||||
listaDeCP.Add("DB2") ' Añade "DB2" a la lista de bases de datos.
|
||||
Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.")
|
||||
End If
|
||||
For i = 0 To cpFiles.Size - 1
|
||||
' Procesa 'config.DB2.properties'
|
||||
If cpFiles.Get(i) = "config.DB2.properties" Then
|
||||
Dim con2 As RDCConnector ' Declara una variable específica y única para el conector de DB2.
|
||||
con2.Initialize("DB2") ' Inicializa la instancia del conector para "DB2".
|
||||
Connectors.Put("DB2", con2) ' Asocia "DB2" con su instancia de RDCConnector.
|
||||
listaDeCP.Add("DB2") ' Añade "DB2" a la lista de bases de datos.
|
||||
Log("Main.AppStart: Conector 'DB2' inicializado exitosamente.")
|
||||
End If
|
||||
|
||||
' Procesa 'config.DB3.properties'
|
||||
If cpFiles.Get(i) = "config.DB3.properties" Then
|
||||
Dim con3 As RDCConnector ' Declara una variable específica y única para el conector de DB3.
|
||||
con3.Initialize("DB3") ' Inicializa la instancia del conector para "DB3".
|
||||
Connectors.Put("DB3", con3) ' Asocia "DB3" con su instancia de RDCConnector.
|
||||
listaDeCP.Add("DB3") ' Añade "DB3" a la lista de bases de datos.
|
||||
Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.")
|
||||
End If
|
||||
' Procesa 'config.DB3.properties'
|
||||
If cpFiles.Get(i) = "config.DB3.properties" Then
|
||||
Dim con3 As RDCConnector ' Declara una variable específica y única para el conector de DB3.
|
||||
con3.Initialize("DB3") ' Inicializa la instancia del conector para "DB3".
|
||||
Connectors.Put("DB3", con3) ' Asocia "DB3" con su instancia de RDCConnector.
|
||||
listaDeCP.Add("DB3") ' Añade "DB3" a la lista de bases de datos.
|
||||
Log("Main.AppStart: Conector 'DB3' inicializado exitosamente.")
|
||||
End If
|
||||
|
||||
' Procesa 'config.DB4.properties'
|
||||
If cpFiles.Get(i) = "config.DB4.properties" Then
|
||||
Dim con4 As RDCConnector ' Declara una variable específica y única para el conector de DB4.
|
||||
con4.Initialize("DB4") ' Inicializa la instancia del conector para "DB4".
|
||||
Connectors.Put("DB4", con4) ' Asocia "DB4" con su instancia de RDCConnector.
|
||||
listaDeCP.Add("DB4") ' Añade "DB4" a la lista de bases de datos.
|
||||
Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.")
|
||||
End If
|
||||
Next
|
||||
Else
|
||||
Log("Main.AppStart: No se encontraron archivos de configuración adicionales (config.DBx.properties).")
|
||||
End If
|
||||
' Procesa 'config.DB4.properties'
|
||||
If cpFiles.Get(i) = "config.DB4.properties" Then
|
||||
Dim con4 As RDCConnector ' Declara una variable específica y única para el conector de DB4.
|
||||
con4.Initialize("DB4") ' Inicializa la instancia del conector para "DB4".
|
||||
Connectors.Put("DB4", con4) ' Asocia "DB4" con su instancia de RDCConnector.
|
||||
listaDeCP.Add("DB4") ' Añade "DB4" a la lista de bases de datos.
|
||||
Log("Main.AppStart: Conector 'DB4' inicializado exitosamente.")
|
||||
End If
|
||||
Next
|
||||
Else
|
||||
Log("Main.AppStart: No se encontraron archivos de configuración adicionales (config.DBx.properties).")
|
||||
End If
|
||||
|
||||
' Log final de las bases de datos que el servidor está gestionando.
|
||||
Dim sbListaDeCP_Log As StringBuilder
|
||||
sbListaDeCP_Log.Initialize
|
||||
For Each item As String In listaDeCP
|
||||
sbListaDeCP_Log.Append(item).Append(", ")
|
||||
Next
|
||||
If sbListaDeCP_Log.Length > 0 Then
|
||||
sbListaDeCP_Log.Remove(sbListaDeCP_Log.Length - 2, sbListaDeCP_Log.Length) ' Elimina la última ", "
|
||||
End If
|
||||
Log($"Main.AppStart: Bases de datos configuradas y listas: [${sbListaDeCP_Log.ToString}]"$)
|
||||
' Log final de las bases de datos que el servidor está gestionando.
|
||||
Dim sbListaDeCP_Log As StringBuilder
|
||||
sbListaDeCP_Log.Initialize
|
||||
For Each item As String In listaDeCP
|
||||
sbListaDeCP_Log.Append(item).Append(", ")
|
||||
Next
|
||||
If sbListaDeCP_Log.Length > 0 Then
|
||||
sbListaDeCP_Log.Remove(sbListaDeCP_Log.Length - 2, sbListaDeCP_Log.Length) ' Elimina la última ", "
|
||||
End If
|
||||
Log($"Main.AppStart: Bases de datos configuradas y listas: [${sbListaDeCP_Log.ToString}]"$)
|
||||
|
||||
' === 6. REGISTRO DE HANDLERS HTTP PARA EL SERVIDOR ===
|
||||
' Asocia rutas URL específicas con clases que manejarán las peticiones correspondientes.
|
||||
' El último parámetro (True) indica que el handler se ejecutará en un nuevo hilo,
|
||||
' lo que es recomendable para la mayoría de los casos para evitar bloqueos.
|
||||
srvr.AddHandler("/ping", "ping", True) ' Endpoint simple para verificar si el servidor está activo.
|
||||
srvr.AddHandler("/test", "TestHandler", True) ' Endpoint para pruebas de conexión y estado del servidor.
|
||||
srvr.AddHandler("/login", "LoginHandler", True) ' Muestra la página HTML de login.
|
||||
srvr.AddHandler("/dologin", "DoLoginHandler", True) ' Procesa el intento de inicio de sesión.
|
||||
srvr.AddHandler("/logout", "LogoutHandler", True) ' Cierra la sesión del usuario.
|
||||
srvr.AddHandler("/changepass", "ChangePassHandler", True) ' Permite a los usuarios cambiar su contraseña.
|
||||
srvr.AddHandler("/manager", "Manager", True) ' Panel de administración del servidor (requiere autenticación).
|
||||
srvr.AddHandler("/DBJ", "DBHandlerJSON", True) ' Handler para clientes web (ej. JavaScript, Node.js) que usan JSON.
|
||||
srvr.AddHandler("/dbrquery", "DBHandlerJSON", True) ' Un alias para el handler JSON, por si se usa en clientes específicos.
|
||||
srvr.AddHandler("/favicon.ico", "faviconHandler", True) ' Sirve el icono de la página (favicon).
|
||||
srvr.AddHandler("/*", "DBHandlerB4X", True) ' Handler por defecto para clientes B4X (DBRequestManager),
|
||||
' procesa peticiones dinámicamente según la URL.
|
||||
' === 6. REGISTRO DE HANDLERS HTTP PARA EL SERVIDOR ===
|
||||
' Asocia rutas URL específicas con clases que manejarán las peticiones correspondientes.
|
||||
' El último parámetro (True) indica que el handler se ejecutará en un nuevo hilo,
|
||||
' lo que es recomendable para la mayoría de los casos para evitar bloqueos.
|
||||
srvr.AddHandler("/ping", "ping", True) ' Endpoint simple para verificar si el servidor está activo.
|
||||
srvr.AddHandler("/test", "TestHandler", True) ' Endpoint para pruebas de conexión y estado del servidor.
|
||||
srvr.AddHandler("/login", "LoginHandler", True) ' Muestra la página HTML de login.
|
||||
srvr.AddHandler("/dologin", "DoLoginHandler", True) ' Procesa el intento de inicio de sesión.
|
||||
srvr.AddHandler("/logout", "LogoutHandler", True) ' Cierra la sesión del usuario.
|
||||
srvr.AddHandler("/changepass", "ChangePassHandler", True) ' Permite a los usuarios cambiar su contraseña.
|
||||
srvr.AddHandler("/manager", "Manager", True) ' Panel de administración del servidor (requiere autenticación).
|
||||
srvr.AddHandler("/DBJ", "DBHandlerJSON", False) ' Handler para clientes web (ej. JavaScript, Node.js) que usan JSON.
|
||||
srvr.AddHandler("/dbrquery", "DBHandlerJSON", False) ' Un alias para el handler JSON, por si se usa en clientes específicos.
|
||||
srvr.AddHandler("/favicon.ico", "faviconHandler", True) ' Sirve el icono de la página (favicon).
|
||||
srvr.AddHandler("/*", "DBHandlerB4X", False) ' Handler por defecto para clientes B4X (DBRequestManager),
|
||||
' procesa peticiones dinámicamente según la URL.
|
||||
|
||||
' 7. Inicia el servidor HTTP.
|
||||
srvr.Start
|
||||
Log("===========================================================")
|
||||
Log($"-=== jRDC está funcionando en el puerto: ${srvr.Port} (versión = $1.2{VERSION}) ===-"$)
|
||||
Log("===========================================================")
|
||||
|
||||
' 8. Inicia el bucle de mensajes de B4J. Es esencial para que la aplicación
|
||||
' de servidor continúe ejecutándose y procesando eventos.
|
||||
StartMessageLoop
|
||||
' 7. Inicia el servidor HTTP.
|
||||
srvr.Start
|
||||
Log("===========================================================")
|
||||
Log($"-=== jRDC está funcionando en el puerto: ${srvr.Port} (versión = $1.2{VERSION}) ===-"$)
|
||||
Log("===========================================================")
|
||||
|
||||
' 8. Inicia el bucle de mensajes de B4J. Es esencial para que la aplicación
|
||||
' de servidor continúe ejecutándose y procesando eventos.
|
||||
StartMessageLoop
|
||||
End Sub
|
||||
|
||||
' --- Subrutina para inicializar la base de datos de usuarios local (SQLite) ---
|
||||
' Esta base de datos se utiliza para almacenar credenciales de usuarios que pueden
|
||||
' acceder al panel de administración del servidor jRDC.
|
||||
' acceder al panel de administración del servidor jRDC y los logs de queries.
|
||||
Sub InitializeSQLiteDatabase
|
||||
Dim dbFileName As String = "users.db" ' Nombre del archivo de la base de datos SQLite.
|
||||
|
||||
' Verifica si el archivo de la base de datos ya existe en el directorio de la aplicación.
|
||||
If File.Exists(File.DirApp, dbFileName) = False Then
|
||||
Log("Creando nueva base de datos de usuarios: " & dbFileName)
|
||||
' Inicializa la conexión a la base de datos SQLite, creándola si no existe (último parámetro en True).
|
||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||
Dim dbFileName As String = "users.db" ' Nombre del archivo de la base de datos SQLite.
|
||||
|
||||
' Verifica si el archivo de la base de datos ya existe en el directorio de la aplicación.
|
||||
If File.Exists(File.DirApp, dbFileName) = False Then
|
||||
Log("Creando nueva base de datos de usuarios: " & dbFileName)
|
||||
' Inicializa la conexión a la base de datos SQLite, creándola si no existe (último parámetro en True).
|
||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||
|
||||
' Define y ejecuta la sentencia SQL para crear la tabla 'users'.
|
||||
Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)"
|
||||
SQL1.ExecNonQuery(createUserTable)
|
||||
|
||||
' >>> INICIO: Creación de la tabla query_logs con las nuevas columnas desde CERO <<<
|
||||
Log("Creando tabla 'query_logs' con columnas de rendimiento.")
|
||||
Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)"
|
||||
SQL1.ExecNonQuery(createQueryLogsTable)
|
||||
' >>> FIN: Creación de la tabla query_logs <<<
|
||||
|
||||
' Crea un usuario por defecto para facilitar el primer acceso al panel de administración.
|
||||
Dim defaultUser As String = "admin"
|
||||
Dim defaultPass As String = "12345"
|
||||
' Genera un hash seguro de la contraseña usando BCrypt, lo cual es crucial para la seguridad.
|
||||
Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt)
|
||||
' Inserta el usuario por defecto en la tabla 'users'.
|
||||
SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass))
|
||||
Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$)
|
||||
Else
|
||||
' Si el archivo de la base de datos ya existe, simplemente se abre.
|
||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||
Log("Base de datos de usuarios cargada.")
|
||||
|
||||
' >>> INICIO: Lógica de migración (ALTER TABLE) si la DB ya existía <<<
|
||||
Log("Verificando y migrando tabla 'query_logs' si es necesario.")
|
||||
|
||||
' Define y ejecuta la sentencia SQL para crear la tabla 'users'.
|
||||
Dim createUserTable As String = "CREATE TABLE users (username TEXT PRIMARY KEY, password_hash TEXT NOT NULL)"
|
||||
SQL1.ExecNonQuery(createUserTable)
|
||||
|
||||
' Crea un usuario por defecto para facilitar el primer acceso al panel de administración.
|
||||
Dim defaultUser As String = "admin"
|
||||
Dim defaultPass As String = "12345"
|
||||
' Genera un hash seguro de la contraseña usando BCrypt, lo cual es crucial para la seguridad.
|
||||
Dim hashedPass As String = bc.hashpw(defaultPass, bc.gensalt)
|
||||
' Inserta el usuario por defecto en la tabla 'users'.
|
||||
SQL1.ExecNonQuery2("INSERT INTO users (username, password_hash) VALUES (?, ?)", Array As Object(defaultUser, hashedPass))
|
||||
Log($"Usuario por defecto creado -> user: ${defaultUser}, pass: ${defaultPass}"$)
|
||||
Else
|
||||
' Si el archivo de la base de datos ya existe, simplemente se abre.
|
||||
SQL1.InitializeSQLite(File.DirApp, dbFileName, True)
|
||||
Log("Base de datos de usuarios cargada.")
|
||||
End If
|
||||
' Primero, verificar si la tabla query_logs existe.
|
||||
If SQL1.ExecQuerySingleResult("SELECT name FROM sqlite_master WHERE type='table' AND name='query_logs'") = Null Then
|
||||
Log("Tabla 'query_logs' no encontrada, creándola con columnas de rendimiento.")
|
||||
Dim createQueryLogsTable As String = "CREATE TABLE query_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, query_name TEXT, duration_ms INTEGER, timestamp INTEGER, db_key TEXT, client_ip TEXT, busy_connections INTEGER, handler_active_requests INTEGER)"
|
||||
SQL1.ExecNonQuery(createQueryLogsTable)
|
||||
Else
|
||||
' Si la tabla query_logs ya existe, entonces verificamos y añadimos las columnas faltantes.
|
||||
Dim columnExists As Boolean
|
||||
Dim rs As ResultSet
|
||||
|
||||
' --- VERIFICAR Y AÑADIR busy_connections ---
|
||||
columnExists = False
|
||||
' Ejecutamos PRAGMA sin WHERE y lo filtramos en código.
|
||||
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)")
|
||||
Do While rs.NextRow
|
||||
If rs.GetString("name").EqualsIgnoreCase("busy_connections") Then
|
||||
columnExists = True
|
||||
Exit ' La columna ya existe, salimos del bucle.
|
||||
End If
|
||||
Loop
|
||||
rs.Close ' ¡Importante cerrar el ResultSet!
|
||||
|
||||
If columnExists = False Then
|
||||
Log("Añadiendo columna 'busy_connections' a query_logs.")
|
||||
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN busy_connections INTEGER DEFAULT 0")
|
||||
End If
|
||||
|
||||
' --- VERIFICAR Y AÑADIR handler_active_requests ---
|
||||
columnExists = False
|
||||
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)") ' Ejecutamos PRAGMA nuevamente para esta columna.
|
||||
Do While rs.NextRow
|
||||
If rs.GetString("name").EqualsIgnoreCase("handler_active_requests") Then
|
||||
columnExists = True
|
||||
Exit ' La columna ya existe, salimos del bucle.
|
||||
End If
|
||||
Loop
|
||||
rs.Close ' ¡Importante cerrar el ResultSet!
|
||||
|
||||
If columnExists = False Then
|
||||
Log("Añadiendo columna 'handler_active_requests' a query_logs.")
|
||||
SQL1.ExecNonQuery("ALTER TABLE query_logs ADD COLUMN handler_active_requests INTEGER DEFAULT 0")
|
||||
End If
|
||||
End If
|
||||
' >>> FIN: Lógica de migración (ALTER TABLE) <<<
|
||||
End If
|
||||
End Sub
|
||||
|
||||
' Subrutina para registrar las métricas de rendimiento de las queries
|
||||
Public Sub LogQueryPerformance(QueryName As String, DurationMs As Long, DbKey As String, ClientIp As String, HandlerActiveRequests As Int, PoolBusyConnections As Int)
|
||||
Try
|
||||
' El valor PoolBusyConnections ya se recibe directamente del handler.
|
||||
' Removemos la lógica anterior de obtenerlo del conector.
|
||||
' Dim connector As RDCConnector = Main.Connectors.Get(DbKey).As(RDCConnector)
|
||||
' Dim poolBusyConnections As Int = 0
|
||||
' If connector.IsInitialized Then
|
||||
' Dim poolStats As Map = connector.GetPoolStats
|
||||
' If poolStats.ContainsKey("BusyConnections") Then
|
||||
' poolBusyConnections = poolStats.Get("BusyConnections")
|
||||
' End If
|
||||
' Else
|
||||
' Log($"ADVERTENCIA: Conector RDC para ${DbKey} no inicializado al intentar loguear rendimiento."$)
|
||||
' End If
|
||||
|
||||
' Insertamos los datos en la tabla query_logs de SQLite
|
||||
SQL1.ExecNonQuery2("INSERT INTO query_logs (query_name, duration_ms, timestamp, db_key, client_ip, busy_connections, handler_active_requests) VALUES (?, ?, ?, ?, ?, ?, ?)", _
|
||||
Array As Object(QueryName, DurationMs, DateTime.Now, DbKey, ClientIp, PoolBusyConnections, HandlerActiveRequests))
|
||||
Catch
|
||||
Log("Error al guardar log de query en SQLite (Main.LogQueryPerformance): " & LastException.Message)
|
||||
End Try
|
||||
End Sub
|
||||
@@ -40,6 +40,6 @@ ModuleClosedNodes6=
|
||||
ModuleClosedNodes7=
|
||||
ModuleClosedNodes8=
|
||||
ModuleClosedNodes9=
|
||||
NavigationStack=RDCConnector,Class_Globals,12,0,Manager,Initialize,8,0,RDCConnector,GetPoolStats,256,0,Main,Process_Globals,58,0,RDCConnector,Close,266,0,Manager,Handle,171,2,Main,AppStart,138,0,DBHandlerJSON,Handle,82,0,RDCConnector,Initialize,70,0,Cambios,Process_Globals,43,6
|
||||
NavigationStack=RDCConnector,GetCommand,164,0,RDCConnector,GetConnection,187,0,RDCConnector,GetPoolStats,273,0,RDCConnector,Close,283,0,DBHandlerJSON,Handle,94,5,DBHandlerJSON,CleanupAndLog,200,6,DBHandlerJSON,Initialize,10,0,DBHandlerJSON,Class_Globals,6,0,Cambios,Process_Globals,25,3,Main,AppStart,152,1
|
||||
SelectedBuild=0
|
||||
VisibleModules=3,4,12,10,13,1
|
||||
VisibleModules=3,4,12,1,7,2,5
|
||||
|
||||
Reference in New Issue
Block a user