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:
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.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user