mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-17 21:06:24 +00:00
- VERSION 5.09.15
1. **Nuevas Funcionalidades en el Panel de Administración (Manager):** * Se añadió el comando `slowqueries` al `Manager` para permitir la visualización de las 20 consultas más lentas registradas en la tabla `query_logs` de SQLite [22]. * Se mejoró el comando `totalcon` en `Manager.bas` para mostrar estadísticas detalladas de *todos* los pools de conexión C3P0 configurados, obteniendo métricas en tiempo real (TotalConnections, BusyConnections, IdleConnections, etc.) de cada `RDCConnector` [2, 22]. * Beneficio: Mayor visibilidad y control proactivo sobre el rendimiento y el uso de recursos del servidor desde la interfaz de administración. 2. **Optimización de la Gestión de Logs (`query_logs`):** * Se implementó un `Public timerLogs As Timer` en `Main.bas` [conversación], que se inicializa en `AppStart` y ejecuta periódicamente (cada 10 minutos) la subrutina `borraArribaDe15000Logs`. * La subrutina `borraArribaDe15000Logs` recorta la tabla `query_logs` en `users.db` para mantener solo los 15,000 registros más recientes, y luego realiza un `vacuum` para optimizar el espacio en disco utilizado por la base de datos SQLite [conversación]. * Beneficio: Prevención del crecimiento excesivo de la base de datos de logs de rendimiento, manteniendo un historial manejable y optimizando el uso del almacenamiento a largo plazo.
This commit is contained in:
142
jRDC_Multi.b4j
142
jRDC_Multi.b4j
@@ -53,7 +53,7 @@ Version=10.3
|
||||
#Region Project Attributes
|
||||
#CommandLineArgs:
|
||||
#MergeLibraries: True
|
||||
' VERSION 5.09.14
|
||||
' VERSION 5.09.15
|
||||
'###########################################################################################################
|
||||
'###################### PULL #############################################################
|
||||
'Ctrl + click ide://run?file=%WINDIR%\System32\cmd.exe&Args=/c&Args=git&Args=pull
|
||||
@@ -75,42 +75,68 @@ Version=10.3
|
||||
|
||||
Sub Process_Globals
|
||||
' --- Variables globales accesibles desde cualquier parte del proyecto ---
|
||||
Public srvr As Server ' El objeto principal del servidor HTTP de B4J.
|
||||
Public const VERSION As Float = 2.23 ' La versión actual de este servidor jRDC modificado.
|
||||
|
||||
' Tipos personalizados para la serialización y deserialización de datos
|
||||
|
||||
' Objeto principal del servidor HTTP de B4J.
|
||||
Public srvr As Server
|
||||
|
||||
' La versión actual de este servidor jRDC modificado.
|
||||
Public const VERSION As Float = 2.23
|
||||
|
||||
' Tipos personalizados (clases) para la serialización y deserialización de datos
|
||||
' entre el cliente B4X (DBRequestManager) y el servidor jRDC2.
|
||||
Type DBCommand (Name As String, Parameters() As Object) ' Define un comando SQL.
|
||||
Type DBResult (Tag As Object, Columns As Map, Rows As List) ' Define la estructura de un resultado de consulta.
|
||||
|
||||
Public listaDeCP As List ' Contiene una lista de los identificadores de bases de datos configuradas (ej. "DB1", "DB2").
|
||||
Private cpFiles As List ' Una lista temporal para almacenar los nombres de archivos encontrados en el directorio.
|
||||
|
||||
|
||||
' Contiene una lista de los identificadores de bases de datos configuradas (ej. "DB1", "DB2").
|
||||
Public listaDeCP As List
|
||||
|
||||
' Una lista temporal para almacenar los nombres de archivos de configuración encontrados.
|
||||
Private cpFiles As List
|
||||
|
||||
' Mapas globales para gestionar los conectores de base de datos y los comandos SQL.
|
||||
Public Connectors, commandsMap As Map ' Connectors: Almacena las instancias de RDCConnector por DB.
|
||||
' commandsMap: Almacena los comandos SQL cargados para cada DB.
|
||||
|
||||
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
|
||||
' Connectors: Almacena las instancias de RDCConnector por cada base de datos (DB1, DB2, etc.).
|
||||
' commandsMap: Almacena los comandos SQL cargados de los archivos de configuración para cada DB.
|
||||
Public Connectors, commandsMap As Map
|
||||
|
||||
' Objeto SQL para interactuar con la base de datos de usuarios y logs (SQLite).
|
||||
Public SQL1 As SQL
|
||||
|
||||
' Objeto para realizar operaciones de hashing de contraseñas de forma segura (para autenticación de Manager).
|
||||
Private bc As BCrypt
|
||||
|
||||
' Objeto de bloqueo (ReentrantLock) para proteger el mapa Main.Connectors durante operaciones de recarga (Hot-Swap).
|
||||
Public MainConnectorsLock As JavaObject
|
||||
|
||||
' Mapa para contar las peticiones activas por cada base de datos. Es thread-safe.
|
||||
Public ActiveRequestsCountByDB As Map
|
||||
|
||||
' Timer para ejecutar tareas periódicas, como la limpieza de logs.
|
||||
Public timerLogs As Timer
|
||||
End Sub
|
||||
|
||||
Sub AppStart (Args() As String)
|
||||
' --- Subrutina principal que se ejecuta al iniciar la aplicación ---
|
||||
bc.Initialize("BC")
|
||||
|
||||
' 1. Inicializa la base de datos local de usuarios (SQLite).
|
||||
' Esta base de datos se crea automáticamente si no existe y contiene los usuarios para el panel de administración.
|
||||
' 1. Inicializa la base de datos local de usuarios (SQLite) y la tabla de logs.
|
||||
' Esta base de datos se crea automáticamente si no existe o se migra si es necesario.
|
||||
InitializeSQLiteDatabase
|
||||
|
||||
' <<<< Bloque de inicialización del Timer para la limpieza de logs >>>>
|
||||
' Inicializa y configura el Timer para borrar logs antiguos cada 10 minutos (600,000 milisegundos).
|
||||
timerLogs.Initialize("TimerLogs", 600000) ' 10 minutos = 600 * 1000 = 600000 ms
|
||||
timerLogs.Enabled = True ' Habilita el timer para que empiece a correr.
|
||||
Log("Main.AppStart: Timer de limpieza de 'query_logs' inicializado para ejecutarse cada 10 minutos.")
|
||||
' <<<< Fin del bloque del Timer >>>>
|
||||
|
||||
' 2. Inicializa los mapas globales definidos en GlobalParameters.bas.
|
||||
' Estos mapas se usan para monitorear el servidor y gestionar configuraciones dinámicas.
|
||||
GlobalParameters.mpLogs.Initialize ' Mapa para almacenar logs de actividad.
|
||||
GlobalParameters.mpLogs.Initialize ' Mapa para almacenar logs de actividad general.
|
||||
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]
|
||||
|
||||
' Aseguramos que el mapa de conteo de peticiones activas sea thread-safe para un manejo concurrente seguro.
|
||||
GlobalParameters.ActiveRequestsCountByDB = srvr.CreateThreadSafeMap
|
||||
|
||||
' 3. Inicializa las estructuras principales del servidor HTTP.
|
||||
listaDeCP.Initialize ' Inicializa la lista que contendrá los IDs de las bases de datos.
|
||||
@@ -118,9 +144,8 @@ Sub AppStart (Args() As String)
|
||||
Connectors = srvr.CreateThreadSafeMap ' Crea un mapa seguro para almacenar instancias de RDCConnector (un conector por DB).
|
||||
commandsMap.Initialize ' Inicializa el mapa que almacenará los comandos SQL cargados de los archivos de configuración.
|
||||
|
||||
' <<<< NUEVA INICIALIZACIÓN: Creamos una instancia de ReentrantLock para proteger Main.Connectors >>>>
|
||||
' Creamos una instancia de ReentrantLock para proteger Main.Connectors durante operaciones atómicas de Hot-Swap.
|
||||
MainConnectorsLock.InitializeNewInstance("java.util.concurrent.locks.ReentrantLock", Null)
|
||||
' <<<< HASTA AQUÍ LA NUEVA INICIALIZACIÓN >>>>
|
||||
|
||||
' === 4. INICIALIZACIÓN DEL CONECTOR PARA LA BASE DE DATOS PRINCIPAL (DB1) ===
|
||||
' DB1 siempre usa el archivo 'config.properties' por defecto.
|
||||
@@ -128,7 +153,7 @@ Sub AppStart (Args() As String)
|
||||
con1.Initialize("DB1") ' Inicializa la instancia del conector para "DB1".
|
||||
Connectors.Put("DB1", con1) ' Asocia el identificador "DB1" con su instancia de RDCConnector.
|
||||
srvr.Port = con1.serverPort ' El puerto del servidor HTTP se obtiene del config.properties de DB1.
|
||||
listaDeCP.Add("DB1") ' Añade "DB1" a la lista de bases de datos gestionadas.]
|
||||
listaDeCP.Add("DB1") ' Añade "DB1" a la lista de bases de datos gestionadas.
|
||||
Log($"Main.AppStart: Conector 'DB1' inicializado exitosamente en puerto: ${srvr.Port}"$)
|
||||
|
||||
' === 5. DETECCIÓN E INICIALIZACIÓN DE BASES DE DATOS ADICIONALES (DB2, DB3, DB4) ===
|
||||
@@ -145,7 +170,6 @@ Sub AppStart (Args() As String)
|
||||
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.
|
||||
@@ -154,7 +178,6 @@ Sub AppStart (Args() As String)
|
||||
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.
|
||||
@@ -183,16 +206,16 @@ Sub AppStart (Args() As String)
|
||||
' 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("/ping", "ping", False) ' Endpoint simple para verificar si el servidor está activo.
|
||||
srvr.AddHandler("/test", "TestHandler", False) ' Endpoint para pruebas de conexión y estado del servidor.
|
||||
srvr.AddHandler("/login", "LoginHandler", False) ' Muestra la página HTML de login.
|
||||
srvr.AddHandler("/dologin", "DoLoginHandler", False) ' Procesa el intento de inicio de sesión.
|
||||
srvr.AddHandler("/logout", "LogoutHandler", False) ' Cierra la sesión del usuario.
|
||||
srvr.AddHandler("/changepass", "ChangePassHandler", False) ' Permite a los usuarios cambiar su contraseña.
|
||||
srvr.AddHandler("/manager", "Manager", False) ' 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("/favicon.ico", "faviconHandler", False) ' 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.
|
||||
|
||||
@@ -219,12 +242,13 @@ Sub InitializeSQLiteDatabase
|
||||
' 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'.
|
||||
' Define y ejecuta la sentencia SQL para crear la tabla 'users' para la autenticación del Manager.
|
||||
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.")
|
||||
' Esta tabla almacena métricas detalladas de cada query ejecutada.
|
||||
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 <<<
|
||||
@@ -244,20 +268,20 @@ Sub InitializeSQLiteDatabase
|
||||
|
||||
' >>> INICIO: Lógica de migración (ALTER TABLE) si la DB ya existía <<<
|
||||
Log("Verificando y migrando tabla 'query_logs' si es necesario.")
|
||||
|
||||
|
||||
' 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.
|
||||
' Si la tabla query_logs ya existe, entonces verificamos y añadimos las columnas faltantes (busy_connections, handler_active_requests).
|
||||
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.
|
||||
' Ejecutamos PRAGMA para obtener la información de la tabla y verificar la existencia de la columna.
|
||||
rs = SQL1.ExecQuery("PRAGMA table_info(query_logs)")
|
||||
Do While rs.NextRow
|
||||
If rs.GetString("name").EqualsIgnoreCase("busy_connections") Then
|
||||
@@ -265,7 +289,7 @@ Sub InitializeSQLiteDatabase
|
||||
Exit ' La columna ya existe, salimos del bucle.
|
||||
End If
|
||||
Loop
|
||||
rs.Close ' ¡Importante cerrar el ResultSet!
|
||||
rs.Close ' ¡Importante cerrar el ResultSet para liberar recursos!
|
||||
|
||||
If columnExists = False Then
|
||||
Log("Añadiendo columna 'busy_connections' a query_logs.")
|
||||
@@ -292,26 +316,34 @@ Sub InitializeSQLiteDatabase
|
||||
End If
|
||||
End Sub
|
||||
|
||||
' Subrutina para registrar las métricas de rendimiento de las queries
|
||||
' Subrutina para registrar las métricas de rendimiento de las queries en la tabla 'query_logs'.
|
||||
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
|
||||
' Los valores de PoolBusyConnections y HandlerActiveRequests ya se reciben directamente del handler,
|
||||
' eliminando la necesidad de obtenerlos del conector en este punto.
|
||||
|
||||
' 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
|
||||
End Sub
|
||||
|
||||
' Subrutina de evento para el Timer 'timerLogs'.
|
||||
' Se ejecuta periódicamente (cada 10 minutos) para limpiar la tabla de logs.
|
||||
Sub TimerLogs_Tick
|
||||
Try
|
||||
borraArribaDe15000Logs ' Llama a la función para limpiar los logs.
|
||||
Catch
|
||||
Log("ERROR en TimerLogs_Tick al intentar borrar logs: " & LastException.Message)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
' Borra los registros más antiguos de la tabla 'query_logs', manteniendo solo los 15,000 más recientes.
|
||||
' Luego, optimiza el espacio de la base de datos SQLite con un 'vacuum'.
|
||||
Sub borraArribaDe15000Logs 'ignore
|
||||
Log("Recortando la tabla de 'query_logs', límite de 15,000 registros.")
|
||||
SQL1.ExecNonQuery("DELETE FROM query_logs WHERE timestamp NOT in (SELECT timestamp FROM query_logs ORDER BY timestamp desc LIMIT 15000 )")
|
||||
SQL1.ExecNonQuery("vacuum;") ' Optimiza el espacio de almacenamiento de la base de datos.
|
||||
End Sub
|
||||
|
||||
Reference in New Issue
Block a user