Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show db connection statistics in the log file or the dbstatistics endpoint #815

Merged
merged 5 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions dspace-api/src/main/java/org/dspace/core/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.sql.SQLException;
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;
Expand All @@ -29,9 +31,11 @@
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;
import org.springframework.scheduling.annotation.Scheduled;

/**
* Hibernate implementation of the DBConnection.
Expand Down Expand Up @@ -64,6 +68,8 @@ public class HibernateDBConnection implements DBConnection<Session> {
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)
Expand Down Expand Up @@ -102,6 +108,13 @@ protected Transaction getTransaction() {
return sessionFactory.getCurrentSession().getTransaction();
}

// 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
* (so isTransactionAlive() may return false even if isSessionAlive() returns true). A Session may be reused for
Expand Down Expand Up @@ -350,4 +363,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.";
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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());
}
}
1 change: 1 addition & 0 deletions dspace/config/hibernate.cfg.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<property name="hibernate.javax.cache.provider">
org.ehcache.jsr107.EhcacheCachingProvider
</property>
<property name="hibernate.generate_statistics">true</property>
<!-- hibernate.javax.cache.uri is defined in
config/spring/api/core-hibernate.xml so that we can use
property substitution. -->
Expand Down
Loading