From 91084aeaf127464f0ee9aab899e5bd1209923c8b Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Tue, 19 Nov 2024 16:44:30 +0100 Subject: [PATCH 1/5] Show db statistics in the log file or the `dbstatistics` endpoint --- .../main/java/org/dspace/core/Context.java | 12 ++++ .../dspace/core/HibernateDBConnection.java | 72 +++++++++++++++++++ .../ClarinAutoRegistrationController.java | 2 +- .../DBConnectionStatisticsController.java | 39 ++++++++++ dspace/config/hibernate.cfg.xml | 1 + 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/DBConnectionStatisticsController.java diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 02a3fee09f8a..8eed24348c39 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -976,4 +976,16 @@ public Group getAdminGroup() throws SQLException { .getGroupService() .findByName(this, Group.ADMIN) : adminGroup; } + + /** + * Get the Hibernate statistics for this context. + * Only available when using HibernateDBConnection. + * @return the Hibernate statistics as a String + */ + public String getHibernateStatistics() { + if (dbConnection instanceof HibernateDBConnection) { + return ((HibernateDBConnection) dbConnection).getHibernateStatistics(); + } + return "Hibernate statistics are not available for this database connection"; + } } diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index b371af80eede..d2abe1a3f079 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -10,8 +10,11 @@ import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; +import javax.annotation.PostConstruct; import javax.sql.DataSource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.ResourcePolicy; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -29,6 +32,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.proxy.HibernateProxyHelper; import org.hibernate.resource.transaction.spi.TransactionStatus; +import org.hibernate.stat.Statistics; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.orm.hibernate5.SessionFactoryUtils; @@ -64,6 +68,8 @@ public class HibernateDBConnection implements DBConnection { private boolean batchModeEnabled = false; private boolean readOnlyEnabled = false; + private static final Logger log = LogManager.getLogger(HibernateDBConnection.class); + /** * Retrieves the current Session from Hibernate (per our settings, Hibernate is configured to create one Session * per thread). If Session doesn't yet exist, it is created. A Transaction is also initialized (or reinintialized) @@ -102,6 +108,23 @@ protected Transaction getTransaction() { return sessionFactory.getCurrentSession().getTransaction(); } + @PostConstruct + public void startConnectionLogging() { + // Start a thread to log active connection metrics periodically + new Thread(() -> { + while (true) { + try { + logHibernateStatistics(); + logDatabaseMetaData(); + Thread.sleep(10000); // Log every 10 seconds + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + }).start(); + } + /** * Check if Hibernate Session is still "alive" / open. An open Session may or may not have an open Transaction * (so isTransactionAlive() may return false even if isSessionAlive() returns true). A Session may be reused for @@ -350,4 +373,53 @@ public void flushSession() throws SQLException { getSession().flush(); } } + + + /** + * Log the Hibernate statistics (e.g. open sessions, closed sessions, transactions, connections obtained) + */ + private void logHibernateStatistics() { + if (sessionFactory != null) { + log.info(getHibernateStatistics()); + } else { + log.warn(getHibernateStatistics()); + } + } + + /** + * Log the database metadata (URL, User, Driver, Product, Version) + */ + private void logDatabaseMetaData() { + try (Session session = sessionFactory.openSession()) { + // Use doReturningWork to safely interact with the JDBC Connection + session.doReturningWork(connection -> { + try { + DatabaseMetaData metaData = connection.getMetaData(); + log.info("Database Metadata - URL: {}, User: {}, Driver: {}, Product: {} {}" + , metaData.getURL(), metaData.getUserName(), metaData.getDriverName(), + metaData.getDatabaseProductName(), metaData.getDatabaseProductVersion()); + } catch (SQLException e) { + log.warn("Failed to retrieve database metadata: {}", e.getMessage()); + } + return null; // Returning null as no specific result is needed + }); + } catch (Exception e) { + log.warn("Failed to log database metadata: {}", e.getMessage()); + } + } + + /** + * Get Hibernate statistics as a string + */ + public String getHibernateStatistics() { + if (sessionFactory != null) { + Statistics stats = sessionFactory.getStatistics(); + return "Hibernate Statistics - Open Sessions: " + stats.getSessionOpenCount() + ", Closed Sessions: " + + stats.getSessionCloseCount() + ", Transactions: " + stats.getTransactionCount() + + ", Connections Obtained: " + stats.getConnectCount(); + } else { + return "SessionFactory is not available for logging Hibernate statistics."; + } + } } + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java index af6c01714fa7..eef3b8495699 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java @@ -41,7 +41,7 @@ * * This Shibboleth Authentication process is tested in ClarinShibbolethLoginFilterIT. * - * @author Milan Majchrak (milan.majchrak at dataquest.sk) + * @author Milan Majchrak (dspace at dataquest.sk) */ @RequestMapping(value = "/api/autoregistration") @RestController diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DBConnectionStatisticsController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DBConnectionStatisticsController.java new file mode 100644 index 000000000000..9472a3e12f5d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DBConnectionStatisticsController.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for retrieving database connection statistics + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +@PreAuthorize("hasAuthority('ADMIN')") +@RequestMapping(value = "/api/dbstatistics") +@RestController +public class DBConnectionStatisticsController { + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity getStatistics(HttpServletRequest request) { + + Context context = ContextUtil.obtainContext(request); + if (context == null) { + return ResponseEntity.status(500).build(); + } + // Return response entity with the statistics + return ResponseEntity.ok().body(context.getHibernateStatistics()); + } +} diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 82e4fd738038..5a7de653a8cb 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -28,6 +28,7 @@ org.ehcache.jsr107.EhcacheCachingProvider + true From cdd95e9726fc9f26f926b5af7b6c3da3b13f2bc3 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 20 Nov 2024 15:19:20 +0100 Subject: [PATCH 2/5] Finding out why github checks are failed - undo hibernate.cfg --- dspace/config/hibernate.cfg.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 5a7de653a8cb..82e4fd738038 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -28,7 +28,6 @@ org.ehcache.jsr107.EhcacheCachingProvider - true From 44c716951f1715b8e7b700843badef7a8b1ffb97 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 20 Nov 2024 16:35:58 +0100 Subject: [PATCH 3/5] Disabled automatic logging --- .../dspace/core/HibernateDBConnection.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index d2abe1a3f079..04334b8a1a3b 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -108,22 +108,22 @@ protected Transaction getTransaction() { return sessionFactory.getCurrentSession().getTransaction(); } - @PostConstruct - public void startConnectionLogging() { - // Start a thread to log active connection metrics periodically - new Thread(() -> { - while (true) { - try { - logHibernateStatistics(); - logDatabaseMetaData(); - Thread.sleep(10000); // Log every 10 seconds - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - }).start(); - } +// @PostConstruct +// public void startConnectionLogging() { +// // Start a thread to log active connection metrics periodically +// new Thread(() -> { +// while (true) { +// try { +// logHibernateStatistics(); +// logDatabaseMetaData(); +// Thread.sleep(10000); // Log every 10 seconds +// } catch (InterruptedException e) { +// Thread.currentThread().interrupt(); +// break; +// } +// } +// }).start(); +// } /** * Check if Hibernate Session is still "alive" / open. An open Session may or may not have an open Transaction From ac6051eb8cb45e86b78c32f034c40da5b738d80a Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 21 Nov 2024 08:49:08 +0100 Subject: [PATCH 4/5] Use scheduled CRON job instead of PostConstruct --- .../dspace/core/HibernateDBConnection.java | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index 04334b8a1a3b..f8c620380d5f 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -10,7 +10,6 @@ import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; -import javax.annotation.PostConstruct; import javax.sql.DataSource; import org.apache.logging.log4j.LogManager; @@ -36,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.orm.hibernate5.SessionFactoryUtils; +import org.springframework.scheduling.annotation.Scheduled; /** * Hibernate implementation of the DBConnection. @@ -108,22 +108,12 @@ protected Transaction getTransaction() { return sessionFactory.getCurrentSession().getTransaction(); } -// @PostConstruct -// public void startConnectionLogging() { -// // Start a thread to log active connection metrics periodically -// new Thread(() -> { -// while (true) { -// try { -// logHibernateStatistics(); -// logDatabaseMetaData(); -// Thread.sleep(10000); // Log every 10 seconds -// } catch (InterruptedException e) { -// Thread.currentThread().interrupt(); -// break; -// } -// } -// }).start(); -// } + // This method will run every 10 seconds + @Scheduled(fixedRate = 10000) // Fixed rate in milliseconds + public void logConnectionMetrics() { + logHibernateStatistics(); + logDatabaseMetaData(); + } /** * Check if Hibernate Session is still "alive" / open. An open Session may or may not have an open Transaction From 279f1e807567bf8bc6b717454a81e0256b151595 Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Thu, 21 Nov 2024 14:08:49 +0100 Subject: [PATCH 5/5] hibernate generating property true --- dspace/config/hibernate.cfg.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 82e4fd738038..5a7de653a8cb 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -28,6 +28,7 @@ org.ehcache.jsr107.EhcacheCachingProvider + true