mirror of
https://github.com/Qortal/qortal.git
synced 2025-08-01 14:41:23 +00:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5eafdf3c80 | ||
|
d7c26c27e1 | ||
|
2d18dd62eb | ||
|
51fd177d79 | ||
|
c4643538f1 | ||
|
0edadaf901 | ||
|
c05533fb71 | ||
|
db270f559f | ||
|
79f7f68b0c |
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>1.0</version>
|
||||
<version>1.0.4</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<bitcoin.version>0.15.4</bitcoin.version>
|
||||
|
15
src/main/java/org/qortal/api/model/NodeStatus.java
Normal file
15
src/main/java/org/qortal/api/model/NodeStatus.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class NodeStatus {
|
||||
|
||||
public boolean isMintingPossible;
|
||||
public boolean isSynchronizing;
|
||||
|
||||
public NodeStatus() {
|
||||
}
|
||||
|
||||
}
|
@@ -45,6 +45,7 @@ import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.api.Security;
|
||||
import org.qortal.api.model.ActivitySummary;
|
||||
import org.qortal.api.model.NodeInfo;
|
||||
import org.qortal.api.model.NodeStatus;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.Synchronizer.SynchronizationResult;
|
||||
@@ -120,6 +121,27 @@ public class AdminResource {
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/status")
|
||||
@Operation(
|
||||
summary = "Fetch node status",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = NodeStatus.class))
|
||||
)
|
||||
}
|
||||
)
|
||||
public NodeStatus status() {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
NodeStatus nodeStatus = new NodeStatus();
|
||||
|
||||
nodeStatus.isMintingPossible = Controller.getInstance().isMintingPossible();
|
||||
nodeStatus.isSynchronizing = Controller.getInstance().isSynchronizing();
|
||||
|
||||
return nodeStatus;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/stop")
|
||||
@Operation(
|
||||
|
@@ -31,6 +31,7 @@ import org.qortal.network.PeerAddress;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.utils.ExecuteProduceConsume;
|
||||
|
||||
@Path("/peers")
|
||||
@Tag(name = "Peers")
|
||||
@@ -108,6 +109,29 @@ public class PeersResource {
|
||||
return Network.getInstance().getSelfPeers();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/enginestats")
|
||||
@Operation(
|
||||
summary = "Fetch statistics snapshot for networking engine",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = ExecuteProduceConsume.StatsSnapshot.class
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
public ExecuteProduceConsume.StatsSnapshot getEngineStats() {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
return Network.getInstance().getStatsSnapshot();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Add new peer address",
|
||||
|
@@ -573,7 +573,7 @@ public class TransactionsResource {
|
||||
|
||||
public static ApiException createTransactionInvalidException(HttpServletRequest request, ValidationResult result) {
|
||||
String translatedResult = Translator.INSTANCE.translate("TransactionValidity", request.getLocale().getLanguage(), result.name());
|
||||
return ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID, null, translatedResult);
|
||||
return ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID, null, translatedResult, result.name());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -137,19 +137,18 @@ public class BlockMinter extends Thread {
|
||||
// Disregard peers that have "misbehaved" recently
|
||||
peers.removeIf(Controller.hasMisbehaved);
|
||||
|
||||
// Don't mint if we don't have enough connected peers as where would the transactions/consensus come from?
|
||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
continue;
|
||||
|
||||
// Disregard peers that don't have a recent block
|
||||
peers.removeIf(Controller.hasNoRecentBlock);
|
||||
|
||||
// If we have any peers with a recent block, but our latest block isn't recent
|
||||
// then we need to synchronize instead of minting.
|
||||
// Don't mint if we don't have enough up-to-date peers as where would the transactions/consensus come from?
|
||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
continue;
|
||||
|
||||
// If our latest block isn't recent then we need to synchronize instead of minting.
|
||||
if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp)
|
||||
continue;
|
||||
|
||||
// There are no peers with a recent block and/or our latest block is recent
|
||||
// There are enough peers with a recent block and our latest block is recent
|
||||
// so go ahead and mint a block if possible.
|
||||
isMintingPossible = true;
|
||||
|
||||
|
@@ -133,6 +133,9 @@ public class Controller extends Thread {
|
||||
/** Whether we can mint new blocks, as reported by BlockMinter. */
|
||||
private volatile boolean isMintingPossible = false;
|
||||
|
||||
/** Whether we are attempting to synchronize. */
|
||||
private volatile boolean isSynchronizing = false;
|
||||
|
||||
/** Latest block signatures from other peers that we know are on inferior chains. */
|
||||
List<ByteArray> inferiorChainSignatures = new ArrayList<>();
|
||||
|
||||
@@ -245,6 +248,15 @@ public class Controller extends Thread {
|
||||
return isStopping;
|
||||
}
|
||||
|
||||
// For API use
|
||||
public boolean isMintingPossible() {
|
||||
return this.isMintingPossible;
|
||||
}
|
||||
|
||||
public boolean isSynchronizing() {
|
||||
return this.isSynchronizing;
|
||||
}
|
||||
|
||||
// Entry point
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -497,7 +509,13 @@ public class Controller extends Thread {
|
||||
int index = new SecureRandom().nextInt(peers.size());
|
||||
Peer peer = peers.get(index);
|
||||
|
||||
isSynchronizing = true;
|
||||
updateSysTray();
|
||||
|
||||
actuallySynchronize(peer, false);
|
||||
|
||||
isSynchronizing = false;
|
||||
requestSysTrayUpdate = true;
|
||||
}
|
||||
|
||||
public SynchronizationResult actuallySynchronize(Peer peer, boolean force) throws InterruptedException {
|
||||
@@ -601,9 +619,17 @@ public class Controller extends Thread {
|
||||
|
||||
String connectionsText = Translator.INSTANCE.translate("SysTray", numberOfPeers != 1 ? "CONNECTIONS" : "CONNECTION");
|
||||
String heightText = Translator.INSTANCE.translate("SysTray", "BLOCK_HEIGHT");
|
||||
String mintingText = Translator.INSTANCE.translate("SysTray", isMintingPossible ? "MINTING_ENABLED" : "MINTING_DISABLED");
|
||||
|
||||
String tooltip = String.format("%s - %d %s - %s %d", mintingText, numberOfPeers, connectionsText, heightText, height);
|
||||
String actionKey;
|
||||
if (isMintingPossible)
|
||||
actionKey = "MINTING_ENABLED";
|
||||
else if (isSynchronizing)
|
||||
actionKey = "SYNCHRONIZING_BLOCKCHAIN";
|
||||
else
|
||||
actionKey = "MINTING_DISABLED";
|
||||
String actionText = Translator.INSTANCE.translate("SysTray", actionKey);
|
||||
|
||||
String tooltip = String.format("%s - %d %s - %s %d", actionText, numberOfPeers, connectionsText, heightText, height);
|
||||
SysTray.getInstance().setToolTipText(tooltip);
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,7 @@ package org.qortal.controller;
|
||||
import java.math.BigInteger;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -93,18 +93,11 @@ public class Synchronizer {
|
||||
peerHeight, Base58.encode(peersLastBlockSignature), peer.getChainTipData().getLastBlockTimestamp(),
|
||||
ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp()));
|
||||
|
||||
List<BlockSummaryData> peerBlockSummaries = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight);
|
||||
if (peerBlockSummaries == null && Controller.isStopping())
|
||||
return SynchronizationResult.SHUTTING_DOWN;
|
||||
|
||||
if (peerBlockSummaries == null) {
|
||||
LOGGER.info(String.format("Error while trying to find common block with peer %s", peer));
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
}
|
||||
if (peerBlockSummaries.isEmpty()) {
|
||||
LOGGER.info(String.format("Failure to find common block with peer %s", peer));
|
||||
return SynchronizationResult.NO_COMMON_BLOCK;
|
||||
}
|
||||
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
|
||||
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, force, peerBlockSummaries);
|
||||
if (findCommonBlockResult != SynchronizationResult.OK)
|
||||
// Logging performed by fetchSummariesFromCommonBlock() above
|
||||
return findCommonBlockResult;
|
||||
|
||||
// First summary is common block
|
||||
final BlockData commonBlockData = repository.getBlockRepository().fromSignature(peerBlockSummaries.get(0).getSignature());
|
||||
@@ -132,13 +125,6 @@ public class Synchronizer {
|
||||
return SynchronizationResult.NOTHING_TO_DO;
|
||||
}
|
||||
|
||||
// If common block is too far behind us then we're on massively different forks so give up.
|
||||
int minCommonHeight = ourInitialHeight - MAXIMUM_COMMON_DELTA;
|
||||
if (!force && commonBlockHeight < minCommonHeight) {
|
||||
LOGGER.info(String.format("Blockchain too divergent with peer %s", peer));
|
||||
return SynchronizationResult.TOO_DIVERGENT;
|
||||
}
|
||||
|
||||
// Unless we're doing a forced sync, we might need to compare blocks after common block
|
||||
if (!force && ourInitialHeight > commonBlockHeight) {
|
||||
// If our latest block is very old, we're very behind and should ditch our fork.
|
||||
@@ -332,49 +318,59 @@ public class Synchronizer {
|
||||
* @throws DataException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private List<BlockSummaryData> fetchSummariesFromCommonBlock(Repository repository, Peer peer, int ourHeight) throws DataException, InterruptedException {
|
||||
private SynchronizationResult fetchSummariesFromCommonBlock(Repository repository, Peer peer, int ourHeight, boolean force, List<BlockSummaryData> blockSummariesFromCommon) throws DataException, InterruptedException {
|
||||
// Start by asking for a few recent block hashes as this will cover a majority of reorgs
|
||||
// Failing that, back off exponentially
|
||||
int step = INITIAL_BLOCK_STEP;
|
||||
|
||||
List<BlockSummaryData> blockSummaries = null;
|
||||
|
||||
int testHeight = Math.max(ourHeight - step, 1);
|
||||
BlockData testBlockData = null;
|
||||
|
||||
List<BlockSummaryData> blockSummariesBatch = null;
|
||||
|
||||
while (testHeight >= 1) {
|
||||
// Are we shutting down?
|
||||
if (Controller.isStopping())
|
||||
return null;
|
||||
return SynchronizationResult.SHUTTING_DOWN;
|
||||
|
||||
// Fetch our block signature at this height
|
||||
testBlockData = repository.getBlockRepository().fromHeight(testHeight);
|
||||
if (testBlockData == null) {
|
||||
// Not found? But we've locked the blockchain and height is below blockchain's tip!
|
||||
LOGGER.error("Failed to get block at height lower than blockchain tip during synchronization?");
|
||||
return null;
|
||||
return SynchronizationResult.REPOSITORY_ISSUE;
|
||||
}
|
||||
|
||||
// Ask for block signatures since test block's signature
|
||||
byte[] testSignature = testBlockData.getSignature();
|
||||
LOGGER.trace(String.format("Requesting %d summar%s after height %d", step, (step != 1 ? "ies": "y"), testHeight));
|
||||
blockSummaries = this.getBlockSummaries(peer, testSignature, step);
|
||||
blockSummariesBatch = this.getBlockSummaries(peer, testSignature, step);
|
||||
|
||||
if (blockSummaries == null)
|
||||
if (blockSummariesBatch == null) {
|
||||
// No response - give up this time
|
||||
return null;
|
||||
LOGGER.info(String.format("Error while trying to find common block with peer %s", peer));
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
}
|
||||
|
||||
LOGGER.trace(String.format("Received %s summar%s", blockSummaries.size(), (blockSummaries.size() != 1 ? "ies" : "y")));
|
||||
LOGGER.trace(String.format("Received %s summar%s", blockSummariesBatch.size(), (blockSummariesBatch.size() != 1 ? "ies" : "y")));
|
||||
|
||||
// Empty list means remote peer is unaware of test signature OR has no new blocks after test signature
|
||||
if (!blockSummaries.isEmpty())
|
||||
if (!blockSummariesBatch.isEmpty())
|
||||
// We have entries so we have found a common block
|
||||
break;
|
||||
|
||||
// No blocks after genesis block?
|
||||
// We don't get called for a peer at genesis height so this means NO blocks in common
|
||||
if (testHeight == 1)
|
||||
return Collections.emptyList();
|
||||
if (testHeight == 1) {
|
||||
LOGGER.info(String.format("Failure to find common block with peer %s", peer));
|
||||
return SynchronizationResult.NO_COMMON_BLOCK;
|
||||
}
|
||||
|
||||
// If common block is too far behind us then we're on massively different forks so give up.
|
||||
if (!force && testHeight < ourHeight - MAXIMUM_COMMON_DELTA) {
|
||||
LOGGER.info(String.format("Blockchain too divergent with peer %s", peer));
|
||||
return SynchronizationResult.TOO_DIVERGENT;
|
||||
}
|
||||
|
||||
if (peer.getVersion() >= 2) {
|
||||
step <<= 1;
|
||||
@@ -389,20 +385,21 @@ public class Synchronizer {
|
||||
|
||||
// Prepend test block's summary as first block summary, as summaries returned are *after* test block
|
||||
BlockSummaryData testBlockSummary = new BlockSummaryData(testBlockData);
|
||||
blockSummaries.add(0, testBlockSummary);
|
||||
blockSummariesFromCommon.add(0, testBlockSummary);
|
||||
blockSummariesFromCommon.addAll(blockSummariesBatch);
|
||||
|
||||
// Trim summaries so that first summary is common block.
|
||||
// Currently we work back from the end until we hit a block we also have.
|
||||
// TODO: rewrite as modified binary search!
|
||||
for (int i = blockSummaries.size() - 1; i > 0; --i) {
|
||||
if (repository.getBlockRepository().exists(blockSummaries.get(i).getSignature())) {
|
||||
for (int i = blockSummariesFromCommon.size() - 1; i > 0; --i) {
|
||||
if (repository.getBlockRepository().exists(blockSummariesFromCommon.get(i).getSignature())) {
|
||||
// Note: index i isn't cleared: List.subList is fromIndex inclusive to toIndex exclusive
|
||||
blockSummaries.subList(0, i).clear();
|
||||
blockSummariesFromCommon.subList(0, i).clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return blockSummaries;
|
||||
return SynchronizationResult.OK;
|
||||
}
|
||||
|
||||
private List<BlockSummaryData> getBlockSummaries(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException {
|
||||
|
@@ -56,6 +56,7 @@ import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.utils.ExecuteProduceConsume;
|
||||
import org.qortal.utils.ExecuteProduceConsume.StatsSnapshot;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
// For managing peers
|
||||
@@ -199,6 +200,10 @@ public class Network {
|
||||
return this.maxMessageSize;
|
||||
}
|
||||
|
||||
public StatsSnapshot getStatsSnapshot() {
|
||||
return this.networkEPC.getStatsSnapshot();
|
||||
}
|
||||
|
||||
// Peer lists
|
||||
|
||||
public List<Peer> getConnectedPeers() {
|
||||
|
@@ -464,7 +464,7 @@ public class Peer {
|
||||
}
|
||||
|
||||
/* package */ void startPings() {
|
||||
// Replacing initial null value allows pingCheck() to start sending pings.
|
||||
// Replacing initial null value allows getPingTask() to start sending pings.
|
||||
LOGGER.trace(() -> String.format("Enabling pings for peer %s", this));
|
||||
this.lastPingSent = System.currentTimeMillis();
|
||||
}
|
||||
@@ -489,6 +489,7 @@ public class Peer {
|
||||
final long after = System.currentTimeMillis();
|
||||
|
||||
if (message == null || message.getType() != MessageType.PING) {
|
||||
LOGGER.debug(() -> String.format("Didn't receive reply from %s for PING ID %d", this, pingMessage.getId()));
|
||||
this.disconnect("no ping received");
|
||||
return;
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.qortal.asset.Asset;
|
||||
|
@@ -5,11 +5,26 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public abstract class ExecuteProduceConsume implements Runnable {
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class StatsSnapshot {
|
||||
public int activeThreadCount = 0;
|
||||
public int greatestActiveThreadCount = 0;
|
||||
public int consumerCount = 0;
|
||||
public int tasksProduced = 0;
|
||||
public int tasksConsumed = 0;
|
||||
|
||||
public StatsSnapshot() {
|
||||
}
|
||||
}
|
||||
|
||||
private final String className;
|
||||
private final Logger logger;
|
||||
|
||||
@@ -51,28 +66,18 @@ public abstract class ExecuteProduceConsume implements Runnable {
|
||||
return this.executor.awaitTermination(timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public int getActiveThreadCount() {
|
||||
synchronized (this) {
|
||||
return this.activeThreadCount;
|
||||
}
|
||||
}
|
||||
public StatsSnapshot getStatsSnapshot() {
|
||||
StatsSnapshot snapshot = new StatsSnapshot();
|
||||
|
||||
public int getGreatestActiveThreadCount() {
|
||||
synchronized (this) {
|
||||
return this.greatestActiveThreadCount;
|
||||
snapshot.activeThreadCount = this.activeThreadCount;
|
||||
snapshot.greatestActiveThreadCount = this.greatestActiveThreadCount;
|
||||
snapshot.consumerCount = this.consumerCount;
|
||||
snapshot.tasksProduced = this.tasksProduced;
|
||||
snapshot.tasksConsumed = this.tasksConsumed;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTasksProduced() {
|
||||
synchronized (this) {
|
||||
return this.tasksProduced;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTasksConsumed() {
|
||||
synchronized (this) {
|
||||
return this.tasksConsumed;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -48,7 +48,7 @@ PUBLIC_KEY_NOT_FOUND = public key not found
|
||||
|
||||
REPOSITORY_ISSUE = repository error
|
||||
|
||||
TRANSACTION_INVALID = transaction invalid: %s
|
||||
TRANSACTION_INVALID = transaction invalid: %s (%s)
|
||||
|
||||
TRANSACTION_UNKNOWN = transaction unknown
|
||||
|
||||
|
@@ -26,4 +26,6 @@ OPEN_UI = Open UI
|
||||
|
||||
SYNCHRONIZE_CLOCK = Synchronize clock
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Synchronizing
|
||||
|
||||
SYNCHRONIZING_CLOCK = Synchronizing clock
|
||||
|
@@ -26,4 +26,6 @@ OPEN_UI = \u5F00\u542F\u754C\u9762
|
||||
|
||||
SYNCHRONIZE_CLOCK = \u540C\u6B65\u65F6\u949F
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = \u540C\u6B65\u533A\u5757\u94FE
|
||||
|
||||
SYNCHRONIZING_CLOCK = \u540C\u6B65\u7740\u65F6\u949F
|
||||
|
@@ -1,4 +1,6 @@
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = account already exists
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = account cannot reward-share
|
||||
|
||||
ALREADY_GROUP_ADMIN = already group admin
|
||||
@@ -139,7 +141,7 @@ NOT_YET_RELEASED = NOT_YET_RELEASED
|
||||
|
||||
NO_BALANCE = NO_BALANCE
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = NO_BLOCKCHAIN_LOCK
|
||||
NO_BLOCKCHAIN_LOCK = node's blockchain currently busy
|
||||
|
||||
NO_FLAG_PERMISSION = NO_FLAG_PERMISSION
|
||||
|
||||
|
@@ -11,6 +11,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.qortal.utils.ExecuteProduceConsume;
|
||||
import org.qortal.utils.ExecuteProduceConsume.StatsSnapshot;
|
||||
|
||||
public class EPCTests {
|
||||
|
||||
@@ -60,13 +61,12 @@ public class EPCTests {
|
||||
ScheduledExecutorService statusExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
statusExecutor.scheduleAtFixedRate(() -> {
|
||||
synchronized (testEPC) {
|
||||
final long seconds = (System.currentTimeMillis() - start) / 1000L;
|
||||
System.out.println(String.format("After %d second%s, active threads: %d, greatest thread count: %d, tasks produced: %d, tasks consumed: %d",
|
||||
seconds, (seconds != 1 ? "s" : ""),
|
||||
testEPC.getActiveThreadCount(), testEPC.getGreatestActiveThreadCount(),
|
||||
testEPC.getTasksProduced(), testEPC.getTasksConsumed()));
|
||||
}
|
||||
StatsSnapshot snapshot = testEPC.getStatsSnapshot();
|
||||
final long seconds = (System.currentTimeMillis() - start) / 1000L;
|
||||
System.out.println(String.format("After %d second%s, active threads: %d, greatest thread count: %d, tasks produced: %d, tasks consumed: %d",
|
||||
seconds, (seconds != 1 ? "s" : ""),
|
||||
snapshot.activeThreadCount, snapshot.greatestActiveThreadCount,
|
||||
snapshot.tasksProduced, snapshot.tasksConsumed));
|
||||
}, 1L, 1L, TimeUnit.SECONDS);
|
||||
|
||||
// Let it run for a minute
|
||||
@@ -78,10 +78,10 @@ public class EPCTests {
|
||||
final long after = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Shutdown took %d milliseconds", after - before));
|
||||
System.out.println(String.format("Greatest thread count: %d", testEPC.getGreatestActiveThreadCount()));
|
||||
|
||||
System.out.println(String.format("Tasks produced: %d", testEPC.getTasksProduced()));
|
||||
System.out.println(String.format("Tasks consumed: %d", testEPC.getTasksConsumed()));
|
||||
StatsSnapshot snapshot = testEPC.getStatsSnapshot();
|
||||
System.out.println(String.format("Greatest thread count: %d, tasks produced: %d, tasks consumed: %d",
|
||||
snapshot.greatestActiveThreadCount, snapshot.tasksProduced, snapshot.tasksConsumed));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -14,7 +14,8 @@ public class CheckTranslations {
|
||||
|
||||
private static final String[] SUPPORTED_LANGS = new String[] { "en", "de", "zh", "ru" };
|
||||
private static final Set<String> SYSTRAY_KEYS = Set.of("BLOCK_HEIGHT", "CHECK_TIME_ACCURACY", "CONNECTION", "CONNECTIONS",
|
||||
"EXIT", "MINTING_DISABLED", "MINTING_ENABLED", "OPEN_UI", "SYNCHRONIZE_CLOCK", "SYNCHRONIZING_CLOCK");
|
||||
"EXIT", "MINTING_DISABLED", "MINTING_ENABLED", "NTP_NAG_CAPTION", "NTP_NAG_TEXT_UNIX", "NTP_NAG_TEXT_WINDOWS",
|
||||
"OPEN_UI", "SYNCHRONIZE_CLOCK", "SYNCHRONIZING_BLOCKCHAIN", "SYNCHRONIZING_CLOCK");
|
||||
|
||||
private static String failurePrefix;
|
||||
|
||||
|
Reference in New Issue
Block a user