3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-11 17:55:50 +00:00

system info and database connection status access

This commit is contained in:
kennycud 2025-01-05 13:49:31 -08:00
parent f5d338435a
commit 2392b7b155
7 changed files with 394 additions and 2 deletions

View File

@ -0,0 +1,173 @@
package org.hsqldb.jdbc;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hsqldb.jdbc.pool.JDBCPooledConnection;
import org.qortal.data.system.DbConnectionInfo;
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
import javax.sql.ConnectionEvent;
import javax.sql.PooledConnection;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Class HSQLDBPoolMonitored
*
* This class uses the same logic as HSQLDBPool. The only difference is it monitors the state of every connection
* to the database. This is used for debugging purposes only.
*/
public class HSQLDBPoolMonitored extends HSQLDBPool {
private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepositoryFactory.class);
private static final String EMPTY = "Empty";
private static final String AVAILABLE = "Available";
private static final String ALLOCATED = "Allocated";
private ConcurrentHashMap<Integer, DbConnectionInfo> infoByIndex;
public HSQLDBPoolMonitored(int poolSize) {
super(poolSize);
this.infoByIndex = new ConcurrentHashMap<>(poolSize);
}
/**
* Tries to retrieve a new connection using the properties that have already been
* set.
*
* @return a connection to the data source, or null if no spare connections in pool
* @exception SQLException if a database access error occurs
*/
public Connection tryConnection() throws SQLException {
for (int i = 0; i < states.length(); i++) {
if (states.compareAndSet(i, RefState.available, RefState.allocated)) {
JDBCPooledConnection pooledConnection = connections[i];
if (pooledConnection == null)
// Probably shutdown situation
return null;
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), ALLOCATED));
return pooledConnection.getConnection();
}
if (states.compareAndSet(i, RefState.empty, RefState.allocated)) {
try {
JDBCPooledConnection pooledConnection = (JDBCPooledConnection) source.getPooledConnection();
if (pooledConnection == null)
// Probably shutdown situation
return null;
pooledConnection.addConnectionEventListener(this);
pooledConnection.addStatementEventListener(this);
connections[i] = pooledConnection;
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), ALLOCATED));
return pooledConnection.getConnection();
} catch (SQLException e) {
states.set(i, RefState.empty);
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), EMPTY));
}
}
}
return null;
}
public Connection getConnection() throws SQLException {
int var1 = 300;
if (this.source.loginTimeout != 0) {
var1 = this.source.loginTimeout * 10;
}
if (this.closed) {
throw new SQLException("connection pool is closed");
} else {
for(int var2 = 0; var2 < var1; ++var2) {
for(int var3 = 0; var3 < this.states.length(); ++var3) {
if (this.states.compareAndSet(var3, 1, 2)) {
infoByIndex.put(var3, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), ALLOCATED));
return this.connections[var3].getConnection();
}
if (this.states.compareAndSet(var3, 0, 2)) {
try {
JDBCPooledConnection var4 = (JDBCPooledConnection)this.source.getPooledConnection();
var4.addConnectionEventListener(this);
var4.addStatementEventListener(this);
this.connections[var3] = var4;
infoByIndex.put(var3, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), ALLOCATED));
return this.connections[var3].getConnection();
} catch (SQLException var6) {
this.states.set(var3, 0);
infoByIndex.put(var3, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), EMPTY));
}
}
}
try {
Thread.sleep(100L);
} catch (InterruptedException var5) {
}
}
throw JDBCUtil.invalidArgument();
}
}
public void connectionClosed(ConnectionEvent event) {
PooledConnection connection = (PooledConnection) event.getSource();
for (int i = 0; i < connections.length; i++) {
if (connections[i] == connection) {
states.set(i, RefState.available);
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), AVAILABLE));
break;
}
}
}
public void connectionErrorOccurred(ConnectionEvent event) {
PooledConnection connection = (PooledConnection) event.getSource();
for (int i = 0; i < connections.length; i++) {
if (connections[i] == connection) {
states.set(i, RefState.allocated);
connections[i] = null;
states.set(i, RefState.empty);
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), EMPTY));
break;
}
}
}
public List<DbConnectionInfo> getDbConnectionsStates() {
return infoByIndex.values().stream()
.sorted(Comparator.comparingLong(DbConnectionInfo::getUpdated))
.collect(Collectors.toList());
}
private int findConnectionIndex(ConnectionEvent connectionEvent) {
PooledConnection pooledConnection = (PooledConnection) connectionEvent.getSource();
for(int i = 0; i < this.connections.length; ++i) {
if (this.connections[i] == pooledConnection) {
return i;
}
}
return -1;
}
}

View File

@ -0,0 +1,50 @@
package org.qortal.api.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import java.util.Objects;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD)
public class DatasetStatus {
private String name;
private long count;
public DatasetStatus() {}
public DatasetStatus(String name, long count) {
this.name = name;
this.count = count;
}
public String getName() {
return name;
}
public long getCount() {
return count;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DatasetStatus that = (DatasetStatus) o;
return count == that.count && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, count);
}
@Override
public String toString() {
return "DatasetStatus{" +
"name='" + name + '\'' +
", count=" + count +
'}';
}
}

View File

@ -32,6 +32,7 @@ import org.qortal.controller.Synchronizer.SynchronizationResult;
import org.qortal.controller.repository.BlockArchiveRebuilder; import org.qortal.controller.repository.BlockArchiveRebuilder;
import org.qortal.data.account.MintingAccountData; import org.qortal.data.account.MintingAccountData;
import org.qortal.data.account.RewardShareData; import org.qortal.data.account.RewardShareData;
import org.qortal.data.system.DbConnectionInfo;
import org.qortal.network.Network; import org.qortal.network.Network;
import org.qortal.network.Peer; import org.qortal.network.Peer;
import org.qortal.network.PeerAddress; import org.qortal.network.PeerAddress;
@ -40,6 +41,7 @@ import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager; import org.qortal.repository.RepositoryManager;
import org.qortal.settings.Settings; import org.qortal.settings.Settings;
import org.qortal.data.system.SystemInfo;
import org.qortal.utils.Base58; import org.qortal.utils.Base58;
import org.qortal.utils.NTP; import org.qortal.utils.NTP;
@ -52,6 +54,7 @@ import java.net.InetSocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@ -1064,4 +1067,50 @@ public class AdminResource {
return "true"; return "true";
} }
} @GET
@Path("/systeminfo")
@Operation(
summary = "System Information",
description = "System memory usage and available processors.",
responses = {
@ApiResponse(
description = "memory usage and available processors",
content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = SystemInfo.class))
)
}
)
@ApiErrors({ApiError.REPOSITORY_ISSUE})
public SystemInfo getSystemInformation() {
SystemInfo info
= new SystemInfo(
Runtime.getRuntime().freeMemory(),
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(),
Runtime.getRuntime().totalMemory(),
Runtime.getRuntime().maxMemory(),
Runtime.getRuntime().availableProcessors());
return info;
}
@GET
@Path("/dbstates")
@Operation(
summary = "Get DB States",
description = "Get DB States",
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = DbConnectionInfo.class)))
)
}
)
public List<DbConnectionInfo> getDbConnectionsStates() {
try {
return Controller.REPOSITORY_FACTORY.getDbConnectionsStates();
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return new ArrayList<>(0);
}
}
}

View File

@ -0,0 +1,35 @@
package org.qortal.data.system;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class DbConnectionInfo {
private long updated;
private String owner;
private String state;
public DbConnectionInfo() {
}
public DbConnectionInfo(long timeOpened, String owner, String state) {
this.updated = timeOpened;
this.owner = owner;
this.state = state;
}
public long getUpdated() {
return updated;
}
public String getOwner() {
return owner;
}
public String getState() {
return state;
}
}

View File

@ -0,0 +1,49 @@
package org.qortal.data.system;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class SystemInfo {
private long freeMemory;
private long memoryInUse;
private long totalMemory;
private long maxMemory;
private int availableProcessors;
public SystemInfo() {
}
public SystemInfo(long freeMemory, long memoryInUse, long totalMemory, long maxMemory, int availableProcessors) {
this.freeMemory = freeMemory;
this.memoryInUse = memoryInUse;
this.totalMemory = totalMemory;
this.maxMemory = maxMemory;
this.availableProcessors = availableProcessors;
}
public long getFreeMemory() {
return freeMemory;
}
public long getMemoryInUse() {
return memoryInUse;
}
public long getTotalMemory() {
return totalMemory;
}
public long getMaxMemory() {
return maxMemory;
}
public int getAvailableProcessors() {
return availableProcessors;
}
}

View File

@ -5,6 +5,8 @@ import org.apache.logging.log4j.Logger;
import org.hsqldb.HsqlException; import org.hsqldb.HsqlException;
import org.hsqldb.error.ErrorCode; import org.hsqldb.error.ErrorCode;
import org.hsqldb.jdbc.HSQLDBPool; import org.hsqldb.jdbc.HSQLDBPool;
import org.hsqldb.jdbc.HSQLDBPoolMonitored;
import org.qortal.data.system.DbConnectionInfo;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryFactory; import org.qortal.repository.RepositoryFactory;
@ -14,6 +16,8 @@ import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties; import java.util.Properties;
public class HSQLDBRepositoryFactory implements RepositoryFactory { public class HSQLDBRepositoryFactory implements RepositoryFactory {
@ -57,7 +61,13 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
HSQLDBRepository.attemptRecovery(connectionUrl, "backup"); HSQLDBRepository.attemptRecovery(connectionUrl, "backup");
} }
this.connectionPool = new HSQLDBPool(Settings.getInstance().getRepositoryConnectionPoolSize()); if(Settings.getInstance().isConnectionPoolMonitorEnabled()) {
this.connectionPool = new HSQLDBPoolMonitored(Settings.getInstance().getRepositoryConnectionPoolSize());
}
else {
this.connectionPool = new HSQLDBPool(Settings.getInstance().getRepositoryConnectionPoolSize());
}
this.connectionPool.setUrl(this.connectionUrl); this.connectionPool.setUrl(this.connectionUrl);
Properties properties = new Properties(); Properties properties = new Properties();
@ -153,4 +163,19 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
return HSQLDBRepository.isDeadlockException(e); return HSQLDBRepository.isDeadlockException(e);
} }
/**
* Get Connection States
*
* Get the database connection states, if database connection pool monitoring is enabled.
*
* @return the connection states if enabled, otherwise an empty list
*/
public List<DbConnectionInfo> getDbConnectionsStates() {
if( Settings.getInstance().isConnectionPoolMonitorEnabled() ) {
return ((HSQLDBPoolMonitored) this.connectionPool).getDbConnectionsStates();
}
else {
return new ArrayList<>(0);
}
}
} }

View File

@ -501,6 +501,13 @@ public class Settings {
*/ */
private boolean rewardRecordingOnly = true; private boolean rewardRecordingOnly = true;
/**
* Is The Connection Monitored?
*
* Is the database connection pooled monitored?
*/
private boolean connectionPoolMonitorEnabled = false;
// Domain mapping // Domain mapping
public static class ThreadLimit { public static class ThreadLimit {
private String messageType; private String messageType;
@ -1322,4 +1329,8 @@ public class Settings {
public boolean isRewardRecordingOnly() { public boolean isRewardRecordingOnly() {
return rewardRecordingOnly; return rewardRecordingOnly;
} }
public boolean isConnectionPoolMonitorEnabled() {
return connectionPoolMonitorEnabled;
}
} }