mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-11 09:45:50 +00:00
system info and database connection status access
This commit is contained in:
parent
f5d338435a
commit
2392b7b155
173
src/main/java/org/hsqldb/jdbc/HSQLDBPoolMonitored.java
Normal file
173
src/main/java/org/hsqldb/jdbc/HSQLDBPoolMonitored.java
Normal 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;
|
||||
}
|
||||
}
|
50
src/main/java/org/qortal/api/model/DatasetStatus.java
Normal file
50
src/main/java/org/qortal/api/model/DatasetStatus.java
Normal 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ import org.qortal.controller.Synchronizer.SynchronizationResult;
|
||||
import org.qortal.controller.repository.BlockArchiveRebuilder;
|
||||
import org.qortal.data.account.MintingAccountData;
|
||||
import org.qortal.data.account.RewardShareData;
|
||||
import org.qortal.data.system.DbConnectionInfo;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.PeerAddress;
|
||||
@ -40,6 +41,7 @@ import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.data.system.SystemInfo;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
@ -52,6 +54,7 @@ import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@ -1064,4 +1067,50 @@ public class AdminResource {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
35
src/main/java/org/qortal/data/system/DbConnectionInfo.java
Normal file
35
src/main/java/org/qortal/data/system/DbConnectionInfo.java
Normal 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;
|
||||
}
|
||||
}
|
49
src/main/java/org/qortal/data/system/SystemInfo.java
Normal file
49
src/main/java/org/qortal/data/system/SystemInfo.java
Normal 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;
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.hsqldb.HsqlException;
|
||||
import org.hsqldb.error.ErrorCode;
|
||||
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.Repository;
|
||||
import org.qortal.repository.RepositoryFactory;
|
||||
@ -14,6 +16,8 @@ import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
||||
@ -57,7 +61,13 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
||||
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);
|
||||
|
||||
Properties properties = new Properties();
|
||||
@ -153,4 +163,19 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -501,6 +501,13 @@ public class Settings {
|
||||
*/
|
||||
private boolean rewardRecordingOnly = true;
|
||||
|
||||
/**
|
||||
* Is The Connection Monitored?
|
||||
*
|
||||
* Is the database connection pooled monitored?
|
||||
*/
|
||||
private boolean connectionPoolMonitorEnabled = false;
|
||||
|
||||
// Domain mapping
|
||||
public static class ThreadLimit {
|
||||
private String messageType;
|
||||
@ -1322,4 +1329,8 @@ public class Settings {
|
||||
public boolean isRewardRecordingOnly() {
|
||||
return rewardRecordingOnly;
|
||||
}
|
||||
|
||||
public boolean isConnectionPoolMonitorEnabled() {
|
||||
return connectionPoolMonitorEnabled;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user