Merge pull request #4734 from chimp1984/show-past-deviation-in-monitor

Show past deviation in monitor
This commit is contained in:
Christoph Atteneder 2020-11-03 09:03:14 +01:00 committed by GitHub
commit 9da7100ef2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 335 additions and 163 deletions

View file

@ -24,9 +24,12 @@ import bisq.core.dao.monitoring.model.BlindVoteStateBlock;
import bisq.core.dao.monitoring.model.DaoStateBlock;
import bisq.core.dao.monitoring.model.ProposalStateBlock;
import bisq.core.dao.state.DaoStateService;
import bisq.core.filter.Filter;
import bisq.core.filter.FilterManager;
import bisq.core.network.p2p.inventory.messages.GetInventoryRequest;
import bisq.core.network.p2p.inventory.messages.GetInventoryResponse;
import bisq.core.network.p2p.inventory.model.InventoryItem;
import bisq.core.network.p2p.inventory.model.RequestInfo;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.MessageListener;
@ -46,12 +49,12 @@ import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.base.Enums;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.lang.management.ManagementFactory;
@ -66,6 +69,7 @@ public class GetInventoryRequestHandler implements MessageListener {
private final DaoStateMonitoringService daoStateMonitoringService;
private final ProposalStateMonitoringService proposalStateMonitoringService;
private final BlindVoteStateMonitoringService blindVoteStateMonitoringService;
private final FilterManager filterManager;
private final int maxConnections;
@Inject
@ -76,6 +80,7 @@ public class GetInventoryRequestHandler implements MessageListener {
DaoStateMonitoringService daoStateMonitoringService,
ProposalStateMonitoringService proposalStateMonitoringService,
BlindVoteStateMonitoringService blindVoteStateMonitoringService,
FilterManager filterManager,
@Named(Config.MAX_CONNECTIONS) int maxConnections) {
this.networkNode = networkNode;
this.peerManager = peerManager;
@ -84,6 +89,7 @@ public class GetInventoryRequestHandler implements MessageListener {
this.daoStateMonitoringService = daoStateMonitoringService;
this.proposalStateMonitoringService = proposalStateMonitoringService;
this.blindVoteStateMonitoringService = blindVoteStateMonitoringService;
this.filterManager = filterManager;
this.maxConnections = maxConnections;
this.networkNode.addMessageListener(this);
@ -97,28 +103,11 @@ public class GetInventoryRequestHandler implements MessageListener {
Map<InventoryItem, Integer> dataObjects = new HashMap<>();
p2PDataStorage.getMapForDataResponse(getInventoryRequest.getVersion()).values().stream()
.map(e -> e.getClass().getSimpleName())
.forEach(className -> {
Optional<InventoryItem> optionalEnum = Enums.getIfPresent(InventoryItem.class, className);
if (optionalEnum.isPresent()) {
InventoryItem key = optionalEnum.get();
dataObjects.putIfAbsent(key, 0);
int prev = dataObjects.get(key);
dataObjects.put(key, prev + 1);
}
});
.forEach(className -> addClassNameToMap(dataObjects, className));
p2PDataStorage.getMap().values().stream()
.map(ProtectedStorageEntry::getProtectedStoragePayload)
.filter(Objects::nonNull)
.map(e -> e.getClass().getSimpleName())
.forEach(className -> {
Optional<InventoryItem> optionalEnum = Enums.getIfPresent(InventoryItem.class, className);
if (optionalEnum.isPresent()) {
InventoryItem key = optionalEnum.get();
dataObjects.putIfAbsent(key, 0);
int prev = dataObjects.get(key);
dataObjects.put(key, prev + 1);
}
});
.forEach(className -> addClassNameToMap(dataObjects, className));
Map<InventoryItem, String> inventory = new HashMap<>();
dataObjects.forEach((key, value) -> inventory.put(key, String.valueOf(value)));
@ -152,6 +141,7 @@ public class GetInventoryRequestHandler implements MessageListener {
inventory.put(InventoryItem.numConnections, String.valueOf(networkNode.getAllConnections().size()));
inventory.put(InventoryItem.peakNumConnections, String.valueOf(peerManager.getPeakNumConnections()));
inventory.put(InventoryItem.numAllConnectionsLostEvents, String.valueOf(peerManager.getNumAllConnectionsLostEvents()));
peerManager.resetNumAllConnectionsLostEvents();
inventory.put(InventoryItem.sentBytes, String.valueOf(Statistic.totalSentBytesProperty().get()));
inventory.put(InventoryItem.sentBytesPerSec, String.valueOf(Statistic.totalSentBytesPerSecProperty().get()));
inventory.put(InventoryItem.receivedBytes, String.valueOf(Statistic.totalReceivedBytesProperty().get()));
@ -161,9 +151,15 @@ public class GetInventoryRequestHandler implements MessageListener {
// node
inventory.put(InventoryItem.version, Version.VERSION);
inventory.put(InventoryItem.commitHash, RequestInfo.COMMIT_HASH);
inventory.put(InventoryItem.usedMemory, String.valueOf(Profiler.getUsedMemoryInBytes()));
inventory.put(InventoryItem.jvmStartTime, String.valueOf(ManagementFactory.getRuntimeMXBean().getStartTime()));
Filter filter = filterManager.getFilter();
if (filter != null) {
inventory.put(InventoryItem.filteredSeeds, Joiner.on("," + System.getProperty("line.separator")).join(filter.getSeedNodes()));
}
log.info("Send inventory {} to {}", inventory, connection.getPeersNodeAddressOptional());
GetInventoryResponse getInventoryResponse = new GetInventoryResponse(inventory);
networkNode.sendMessage(connection, getInventoryResponse);
@ -173,4 +169,14 @@ public class GetInventoryRequestHandler implements MessageListener {
public void shutDown() {
networkNode.removeMessageListener(this);
}
private void addClassNameToMap(Map<InventoryItem, Integer> dataObjects, String className) {
Optional<InventoryItem> optionalEnum = Enums.getIfPresent(InventoryItem.class, className);
if (optionalEnum.isPresent()) {
InventoryItem key = optionalEnum.get();
dataObjects.putIfAbsent(key, 0);
int prev = dataObjects.get(key);
dataObjects.put(key, prev + 1);
}
}
}

View file

@ -36,10 +36,11 @@ public class Average {
public static double getAverage(Set<RequestInfo> requestInfoSet, InventoryItem inventoryItem) {
return requestInfoSet.stream()
.map(RequestInfo::getInventory)
.map(RequestInfo::getDataMap)
.filter(map -> map.containsKey(inventoryItem))
.map(map -> map.get(inventoryItem).getValue())
.filter(Objects::nonNull)
.filter(inventory -> inventory.containsKey(inventoryItem))
.mapToDouble(inventory -> Double.parseDouble((inventory.get(inventoryItem))))
.mapToDouble(Double::parseDouble)
.average()
.orElse(0d);
}

View file

@ -51,9 +51,9 @@ public class DeviationByIntegerDiff implements DeviationType {
collection.stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1)) // We use last item only
.map(RequestInfo::getInventory)
.map(RequestInfo::getDataMap)
.map(e -> e.get(inventoryItem).getValue())
.filter(Objects::nonNull)
.map(e -> e.get(inventoryItem))
.forEach(e -> {
sameItemsByValue.putIfAbsent(e, 0);
int prev = sameItemsByValue.get(e);

View file

@ -23,6 +23,11 @@ public class DeviationByPercentage implements DeviationType {
private final double lowerWarnTrigger;
private final double upperWarnTrigger;
// In case want to see the % deviation but not trigger any warnings or alerts
public DeviationByPercentage() {
this(0, Double.MAX_VALUE, 0, Double.MAX_VALUE);
}
public DeviationByPercentage(double lowerAlertTrigger,
double upperAlertTrigger,
double lowerWarnTrigger,

View file

@ -44,10 +44,10 @@ public class DeviationOfHashes implements DeviationType {
collection.stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1)) // We use last item only
.map(RequestInfo::getInventory)
.map(RequestInfo::getDataMap)
.filter(map -> currentBlockHeight.equals(map.get(InventoryItem.daoStateChainHeight).getValue()))
.map(map -> map.get(inventoryItem).getValue())
.filter(Objects::nonNull)
.filter(inventory -> inventory.get(InventoryItem.daoStateChainHeight).equals(currentBlockHeight))
.map(inventory -> inventory.get(inventoryItem))
.forEach(v -> {
sameHashesPerHashListByHash.putIfAbsent(v, 0);
int prev = sameHashesPerHashListByHash.get(v);

View file

@ -18,6 +18,7 @@
package bisq.core.network.p2p.inventory.model;
public enum DeviationSeverity {
IGNORED,
OK,
WARN,
ALERT

View file

@ -17,101 +17,104 @@
package bisq.core.network.p2p.inventory.model;
import bisq.common.util.Tuple2;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public enum InventoryItem {
// Percentage deviation
OfferPayload("OfferPayload",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05), 5),
MailboxStoragePayload("MailboxStoragePayload",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
TradeStatistics3("MailboxStoragePayload",
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05), 2),
TradeStatistics3("TradeStatistics3",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
AccountAgeWitness("MailboxStoragePayload",
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05), 2),
AccountAgeWitness("AccountAgeWitness",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
SignedWitness("MailboxStoragePayload",
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05), 2),
SignedWitness("SignedWitness",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05), 2),
// Should be same value
Alert("Alert",
true,
new DeviationByIntegerDiff(1, 1)),
new DeviationByIntegerDiff(1, 1), 2),
Filter("Filter",
true,
new DeviationByIntegerDiff(1, 1)),
new DeviationByIntegerDiff(1, 1), 2),
Mediator("Mediator",
true,
new DeviationByIntegerDiff(1, 1)),
new DeviationByIntegerDiff(1, 1), 2),
RefundAgent("RefundAgent",
true,
new DeviationByIntegerDiff(1, 1)),
new DeviationByIntegerDiff(1, 1), 2),
// Should be very close values
TempProposalPayload("TempProposalPayload",
true,
new DeviationByIntegerDiff(3, 5)),
new DeviationByIntegerDiff(3, 5), 2),
ProposalPayload("ProposalPayload",
true,
new DeviationByIntegerDiff(1, 2)),
new DeviationByIntegerDiff(1, 2), 2),
BlindVotePayload("BlindVotePayload",
true,
new DeviationByIntegerDiff(1, 2)),
new DeviationByIntegerDiff(1, 2), 2),
// Should be very close values
daoStateChainHeight("daoStateChainHeight",
true,
new DeviationByIntegerDiff(1, 3)),
new DeviationByIntegerDiff(2, 4), 3),
numBsqBlocks("numBsqBlocks",
true,
new DeviationByIntegerDiff(1, 3)),
new DeviationByIntegerDiff(2, 4), 3),
// Has to be same values at same block
daoStateHash("daoStateHash",
false,
new DeviationOfHashes()),
new DeviationOfHashes(), 1),
proposalHash("proposalHash",
false,
new DeviationOfHashes()),
new DeviationOfHashes(), 1),
blindVoteHash("blindVoteHash",
false,
new DeviationOfHashes()),
new DeviationOfHashes(), 1),
// Percentage deviation
maxConnections("maxConnections",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
new DeviationByPercentage(0.33, 3, 0.4, 2.5), 2),
numConnections("numConnections",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
new DeviationByPercentage(0, 3, 0, 2.5), 2),
peakNumConnections("peakNumConnections",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
new DeviationByPercentage(0, 3, 0, 2.5), 2),
numAllConnectionsLostEvents("numAllConnectionsLostEvents",
true,
new DeviationByIntegerDiff(1, 2)),
new DeviationByIntegerDiff(1, 2), 3),
sentBytesPerSec("sentBytesPerSec",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
new DeviationByPercentage(), 5),
receivedBytesPerSec("receivedBytesPerSec",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
new DeviationByPercentage(), 5),
receivedMessagesPerSec("receivedMessagesPerSec",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
new DeviationByPercentage(), 5),
sentMessagesPerSec("sentMessagesPerSec",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
new DeviationByPercentage(), 5),
// No deviation check
sentBytes("sentBytes", true),
@ -119,8 +122,10 @@ public enum InventoryItem {
// No deviation check
version("version", false),
commitHash("commitHash", false),
usedMemory("usedMemory", true),
jvmStartTime("jvmStartTime", true);
jvmStartTime("jvmStartTime", true),
filteredSeeds("filteredSeeds", false);
@Getter
private final String key;
@ -130,21 +135,28 @@ public enum InventoryItem {
@Nullable
private DeviationType deviationType;
// The number of past requests we check to see if there have been repeated alerts or warnings. The higher the
// number the more repeated alert need to have happened to cause a notification alert.
@Getter
private int deviationTolerance = 1;
InventoryItem(String key, boolean isNumberValue) {
this.key = key;
this.isNumberValue = isNumberValue;
}
InventoryItem(String key, boolean isNumberValue, DeviationType deviationType) {
InventoryItem(String key, boolean isNumberValue, @NotNull DeviationType deviationType, int deviationTolerance) {
this(key, isNumberValue);
this.deviationType = deviationType;
this.deviationTolerance = deviationTolerance;
}
@Nullable
public Double getDeviation(Map<InventoryItem, Double> averageValues, @Nullable String value) {
public Tuple2<Double, Double> getDeviationAndAverage(Map<InventoryItem, Double> averageValues,
@Nullable String value) {
if (averageValues.containsKey(this) && value != null) {
double averageValue = averageValues.get(this);
return getDeviation(value, averageValue);
return new Tuple2<>(getDeviation(value, averageValue), averageValue);
}
return null;
}

View file

@ -17,25 +17,29 @@
package bisq.core.network.p2p.inventory.model;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.Value;
import org.jetbrains.annotations.Nullable;
@Getter
public class RequestInfo {
// Carries latest commit hash of feature changes (not latest commit as that is then the commit for editing that field)
public static final String COMMIT_HASH = "d789282b";
private final long requestStartTime;
@Setter
private long responseTime;
@Nullable
@Setter
private Map<InventoryItem, String> inventory;
@Nullable
@Setter
private String errorMessage;
private final Map<InventoryItem, Data> dataMap = new HashMap<>();
public RequestInfo(long requestStartTime) {
this.requestStartTime = requestStartTime;
}
@ -47,8 +51,45 @@ public class RequestInfo {
@Nullable
public String getValue(InventoryItem inventoryItem) {
return inventory != null && inventory.containsKey(inventoryItem) ?
inventory.get(inventoryItem) :
return dataMap.containsKey(inventoryItem) ?
dataMap.get(inventoryItem).getValue() :
null;
}
@Value
public static class Data {
private final String value;
@Nullable
private final Double average;
private final Double deviation;
private final DeviationSeverity deviationSeverity;
private final boolean persistentWarning;
private final boolean persistentAlert;
public Data(String value,
@Nullable Double average,
Double deviation,
DeviationSeverity deviationSeverity,
boolean persistentWarning,
boolean persistentAlert) {
this.value = value;
this.average = average;
this.deviation = deviation;
this.deviationSeverity = deviationSeverity;
this.persistentWarning = persistentWarning;
this.persistentAlert = persistentAlert;
}
@Override
public String toString() {
return "InventoryData{" +
"\n value='" + value + '\'' +
",\n average=" + average +
",\n deviation=" + deviation +
",\n deviationSeverity=" + deviationSeverity +
",\n persistentWarning=" + persistentWarning +
",\n persistentAlert=" + persistentAlert +
"\n}";
}
}
}

View file

@ -20,6 +20,7 @@ package bisq.inventory;
import bisq.core.network.p2p.inventory.GetInventoryRequestManager;
import bisq.core.network.p2p.inventory.model.Average;
import bisq.core.network.p2p.inventory.model.DeviationSeverity;
import bisq.core.network.p2p.inventory.model.InventoryItem;
import bisq.core.network.p2p.inventory.model.RequestInfo;
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
@ -33,6 +34,7 @@ import bisq.network.p2p.network.SetupListener;
import bisq.common.UserThread;
import bisq.common.config.BaseCurrencyNetwork;
import bisq.common.file.JsonFileManager;
import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;
import java.time.Clock;
@ -40,10 +42,12 @@ import java.time.Clock;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@ -155,26 +159,78 @@ public class InventoryMonitor implements SetupListener {
requestInfo.setErrorMessage(errorMessage);
}
boolean ignoreDeviationAtStartup;
if (result != null) {
log.info("nodeAddress={}, result={}", nodeAddress, result.toString());
requestInfo.setInventory(result);
long responseTime = System.currentTimeMillis();
requestInfo.setResponseTime(responseTime);
// If seed just started up we ignore the deviation as it can be expected that seed is still syncing
// DAO state/blocks. P2P data should be ready but as we received it from other seeds it is not that
// valuable information either, so we apply the ignore to all data.
if (result.containsKey(InventoryItem.jvmStartTime)) {
String jvmStartTimeString = result.get(InventoryItem.jvmStartTime);
long jvmStartTime = Long.parseLong(jvmStartTimeString);
ignoreDeviationAtStartup = jvmStartTime < TimeUnit.MINUTES.toMillis(2);
} else {
ignoreDeviationAtStartup = false;
}
} else {
ignoreDeviationAtStartup = false;
}
requestInfoListByNode.putIfAbsent(nodeAddress, new ArrayList<>());
List<RequestInfo> requestInfoList = requestInfoListByNode.get(nodeAddress);
requestInfoList.add(requestInfo);
// We create average of all nodes latest results. It might be that the nodes last result is
// from a previous request as the response has not arrived yet.
Set<RequestInfo> requestInfoSet = requestInfoListByNode.values().stream()
//TODO might be not a good idea to use the last result if its not a recent one. a faulty node would distort
// the average calculation.
// As we add at the end our own result the average is excluding our own value
Collection<List<RequestInfo>> requestInfoListByNodeValues = requestInfoListByNode.values();
Set<RequestInfo> requestInfoSet = requestInfoListByNodeValues.stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1))
.collect(Collectors.toSet());
Map<InventoryItem, Double> averageValues = Average.of(requestInfoSet);
inventoryWebServer.onNewRequestInfo(requestInfoListByNode, averageValues, requestCounter);
String daoStateChainHeight = result != null &&
result.containsKey(InventoryItem.daoStateChainHeight) ?
result.get(InventoryItem.daoStateChainHeight) :
null;
List.of(InventoryItem.values()).forEach(inventoryItem -> {
String value = result != null ? result.get(inventoryItem) : null;
Tuple2<Double, Double> tuple = inventoryItem.getDeviationAndAverage(averageValues, value);
Double deviation = tuple != null ? tuple.first : null;
Double average = tuple != null ? tuple.second : null;
DeviationSeverity deviationSeverity = ignoreDeviationAtStartup ? DeviationSeverity.IGNORED :
inventoryItem.getDeviationSeverity(deviation,
requestInfoListByNodeValues,
value,
daoStateChainHeight);
int endIndex = Math.max(0, requestInfoList.size() - 1);
int deviationTolerance = inventoryItem.getDeviationTolerance();
int fromIndex = Math.max(0, endIndex - deviationTolerance);
List<DeviationSeverity> lastDeviationSeverityEntries = requestInfoList.subList(fromIndex, endIndex).stream()
.filter(e -> e.getDataMap().containsKey(inventoryItem))
.map(e -> e.getDataMap().get(inventoryItem).getDeviationSeverity())
.collect(Collectors.toList());
long numWarnings = lastDeviationSeverityEntries.stream()
.filter(e -> e == DeviationSeverity.WARN)
.count();
long numAlerts = lastDeviationSeverityEntries.stream()
.filter(e -> e == DeviationSeverity.ALERT)
.count();
boolean persistentWarning = numWarnings == deviationTolerance;
boolean persistentAlert = numAlerts == deviationTolerance;
RequestInfo.Data data = new RequestInfo.Data(value, average, deviation, deviationSeverity, persistentWarning, persistentAlert);
requestInfo.getDataMap().put(inventoryItem, data);
});
requestInfoList.add(requestInfo);
inventoryWebServer.onNewRequestInfo(requestInfoListByNode, requestCounter);
String json = Utilities.objectToJson(requestInfo);
jsonFileManagerByNodeAddress.get(nodeAddress).writeToDisc(json, String.valueOf(requestInfo.getRequestStartTime()));

View file

@ -53,7 +53,7 @@ public class InventoryMonitorMain {
// prog args for regtest: 10 1 BTC_REGTEST
public static void main(String[] args) {
// Default values
int intervalSec = 300;
int intervalSec = 120;
boolean useLocalhostForP2P = false;
BaseCurrencyNetwork network = BaseCurrencyNetwork.BTC_MAINNET;
int port = 80;
@ -71,7 +71,7 @@ public class InventoryMonitorMain {
port = Integer.parseInt(args[3]);
}
String appName = "bisq-InventoryMonitor-" + network + "-" + intervalSec;
String appName = "bisq-inventory-monitor-" + network;
File appDir = new File(Utilities.getUserDataDir(), appName);
if (!appDir.exists() && !appDir.mkdir()) {
log.warn("make appDir failed");
@ -100,11 +100,11 @@ public class InventoryMonitorMain {
UserThread.setExecutor(Executors.newSingleThreadExecutor(threadFactory));
Signal.handle(new Signal("INT"), signal -> {
shutDown();
UserThread.execute(InventoryMonitorMain::shutDown);
});
Signal.handle(new Signal("TERM"), signal -> {
shutDown();
UserThread.execute(InventoryMonitorMain::shutDown);
});
keepRunning();
}

View file

@ -24,12 +24,15 @@ import bisq.core.util.FormattingUtils;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import bisq.common.util.MathUtils;
import bisq.common.util.Utilities;
import com.google.common.base.Joiner;
import java.io.BufferedReader;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -77,11 +80,9 @@ public class InventoryWebServer {
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void onNewRequestInfo(Map<NodeAddress, List<RequestInfo>> requestInfoListByNode,
Map<InventoryItem, Double> averageValues,
int requestCounter) {
public void onNewRequestInfo(Map<NodeAddress, List<RequestInfo>> requestInfoListByNode, int requestCounter) {
this.requestCounter = requestCounter;
html = generateHtml(requestInfoListByNode, averageValues);
html = generateHtml(requestInfoListByNode);
}
public void shutDown() {
@ -93,14 +94,22 @@ public class InventoryWebServer {
// HTML
///////////////////////////////////////////////////////////////////////////////////////////
private String generateHtml(Map<NodeAddress, List<RequestInfo>> map,
Map<InventoryItem, Double> averageValues) {
private String generateHtml(Map<NodeAddress, List<RequestInfo>> map) {
StringBuilder html = new StringBuilder();
html.append("<html>" +
"<head><style>table, th, td {border: 1px solid black;}</style></head>" +
"<head>" +
"<style type=\"text/css\">" +
" a {" +
" text-decoration:none; color: black;" +
" }" +
" #warn { color: #ff7700; } " +
" #alert { color: #ff0000; } " +
"table, th, td {border: 1px solid black;}" +
"</style></head>" +
"<body><h3>")
.append("Current time: ").append(new Date().toString()).append("<br/>")
.append("Request cycle: ").append(requestCounter).append("<br/>")
.append("Version/commit: ").append(Version.VERSION).append(" / ").append(RequestInfo.COMMIT_HASH).append("<br/>")
.append("<table style=\"width:100%\">")
.append("<tr>")
.append("<th align=\"left\">Seed node info</th>")
@ -117,9 +126,9 @@ public class InventoryWebServer {
RequestInfo requestInfo = list.get(numResponses - 1);
html.append("<td>").append(getSeedNodeInfo(seedNode, requestInfo)).append("</td>")
.append("<td>").append(getRequestInfo(requestInfo, numResponses)).append("</td>")
.append("<td>").append(getDataInfo(requestInfo, averageValues, map)).append("</td>")
.append("<td>").append(getDaoInfo(requestInfo, averageValues, map)).append("</td>")
.append("<td>").append(getNetworkInfo(requestInfo, averageValues, map)).append("</td>");
.append("<td>").append(getDataInfo(seedNode, requestInfo, map)).append("</td>")
.append("<td>").append(getDaoInfo(seedNode, requestInfo, map)).append("</td>")
.append("<td>").append(getNetworkInfo(seedNode, requestInfo, map)).append("</td>");
} else {
html.append("<td>").append(getSeedNodeInfo(seedNode, null)).append("</td>")
.append("<td>").append("n/a").append("</td>")
@ -147,10 +156,18 @@ public class InventoryWebServer {
sb.append("Operator: ").append(operator).append("<br/>");
String address = nodeAddress.getFullAddress();
sb.append("Node address: ").append(address).append("<br/>");
String filteredSeeds = requestInfo != null ? requestInfo.getValue(InventoryItem.filteredSeeds) : null;
if (filteredSeeds != null && filteredSeeds.contains(address)) {
sb.append(getColorTagByDeviationSeverity(DeviationSeverity.ALERT)).append("Node address: ")
.append(address).append(" (is filtered!)").append(CLOSE_TAG);
} else {
sb.append("Node address: ").append(address).append("<br/>");
}
if (requestInfo != null) {
sb.append("Version: ").append(requestInfo.getDisplayValue(InventoryItem.version)).append("<br/>");
sb.append("Commit hash: ").append(requestInfo.getDisplayValue(InventoryItem.commitHash)).append("<br/>");
String memory = requestInfo.getValue(InventoryItem.usedMemory);
String memoryString = memory != null ? Utilities.readableFileSize(Long.parseLong(memory)) : "n/a";
sb.append("Memory used: ")
@ -168,6 +185,10 @@ public class InventoryWebServer {
true, true) :
"n/a";
sb.append("Run duration: ").append(duration).append("<br/>");
sb.append("Filtered seed nodes: ")
.append(requestInfo.getDisplayValue(InventoryItem.filteredSeeds).replace(System.getProperty("line.separator"), "<br/>"))
.append("<br/>");
}
return sb.toString();
@ -215,74 +236,75 @@ public class InventoryWebServer {
return sb.toString();
}
private String getDataInfo(RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
private String getDataInfo(NodeAddress seedNode,
RequestInfo requestInfo,
Map<NodeAddress, List<RequestInfo>> map) {
StringBuilder sb = new StringBuilder();
sb.append(getLine(InventoryItem.OfferPayload, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.MailboxStoragePayload, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.TradeStatistics3, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.AccountAgeWitness, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.SignedWitness, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.OfferPayload, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.MailboxStoragePayload, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.TradeStatistics3, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.AccountAgeWitness, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.SignedWitness, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.Alert, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.Filter, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.Mediator, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.RefundAgent, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.Alert, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.Filter, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.Mediator, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.RefundAgent, seedNode, requestInfo, map));
return sb.toString();
}
private String getDaoInfo(RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
private String getDaoInfo(NodeAddress seedNode,
RequestInfo requestInfo,
Map<NodeAddress, List<RequestInfo>> map) {
StringBuilder sb = new StringBuilder();
sb.append(getLine("Number of BSQ blocks: ", InventoryItem.numBsqBlocks, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.TempProposalPayload, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.ProposalPayload, requestInfo, averageValues, map.values()));
sb.append(getLine(InventoryItem.BlindVotePayload, requestInfo, averageValues, map.values()));
sb.append(getLine("DAO state block height: ", InventoryItem.daoStateChainHeight, requestInfo, averageValues, map.values()));
sb.append(getLine("Number of BSQ blocks: ", InventoryItem.numBsqBlocks, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.TempProposalPayload, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.ProposalPayload, seedNode, requestInfo, map));
sb.append(getLine(InventoryItem.BlindVotePayload, seedNode, requestInfo, map));
sb.append(getLine("DAO state block height: ", InventoryItem.daoStateChainHeight, seedNode, requestInfo, map));
String daoStateChainHeight = null;
if (requestInfo.getInventory() != null && requestInfo.getInventory().containsKey(InventoryItem.daoStateChainHeight)) {
daoStateChainHeight = requestInfo.getInventory().get(InventoryItem.daoStateChainHeight);
}
sb.append(getLine("DAO state hash: ", InventoryItem.daoStateHash, requestInfo, averageValues, map.values(), daoStateChainHeight));
sb.append(getLine("DAO state hash: ", InventoryItem.daoStateHash, seedNode, requestInfo, map));
// The hash for proposal changes only at first block of blind vote phase but as we do not want to initialize the
// dao domain we cannot check that. But we also don't need that as we can just compare that all hashes at all
// blocks from all seeds are the same. Same for blindVoteHash.
sb.append(getLine("Proposal state hash: ", InventoryItem.proposalHash, requestInfo, averageValues, map.values(), daoStateChainHeight));
sb.append(getLine("Blind vote state hash: ", InventoryItem.blindVoteHash, requestInfo, averageValues, map.values(), daoStateChainHeight));
sb.append(getLine("Proposal state hash: ", InventoryItem.proposalHash, seedNode, requestInfo, map));
sb.append(getLine("Blind vote state hash: ", InventoryItem.blindVoteHash, seedNode, requestInfo, map));
return sb.toString();
}
private String getNetworkInfo(RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
private String getNetworkInfo(NodeAddress seedNode,
RequestInfo requestInfo,
Map<NodeAddress, List<RequestInfo>> map) {
StringBuilder sb = new StringBuilder();
sb.append(getLine("Max. connections: ", InventoryItem.maxConnections, requestInfo, averageValues, map.values()));
sb.append(getLine("Number of connections: ", InventoryItem.numConnections, requestInfo, averageValues, map.values()));
sb.append(getLine("Peak number of connections: ", InventoryItem.peakNumConnections, requestInfo, averageValues, map.values()));
sb.append(getLine("Number of 'All connections lost' events: ", InventoryItem.numAllConnectionsLostEvents, requestInfo, averageValues, map.values()));
sb.append(getLine("Max. connections: ",
InventoryItem.maxConnections, seedNode, requestInfo, map));
sb.append(getLine("Number of connections: ",
InventoryItem.numConnections, seedNode, requestInfo, map));
sb.append(getLine("Peak number of connections: ",
InventoryItem.peakNumConnections, seedNode, requestInfo, map));
sb.append(getLine("Number of 'All connections lost' events: ",
InventoryItem.numAllConnectionsLostEvents, seedNode, requestInfo, map));
sb.append(getLine("Sent messages/sec: ", InventoryItem.sentMessagesPerSec, requestInfo,
averageValues, map.values(), null, this::getRounded));
sb.append(getLine("Received messages/sec: ", InventoryItem.receivedMessagesPerSec, requestInfo,
averageValues, map.values(), null, this::getRounded));
sb.append(getLine("Sent kB/sec: ", InventoryItem.sentBytesPerSec, requestInfo,
averageValues, map.values(), null, this::getRounded));
sb.append(getLine("Received kB/sec: ", InventoryItem.receivedBytesPerSec, requestInfo,
averageValues, map.values(), null, this::getRounded));
sb.append(getLine("Sent data: ", InventoryItem.sentBytes, requestInfo,
averageValues, map.values(), null, value -> Utilities.readableFileSize(Long.parseLong(value))));
sb.append(getLine("Received data: ", InventoryItem.receivedBytes, requestInfo,
averageValues, map.values(), null, value -> Utilities.readableFileSize(Long.parseLong(value))));
sb.append(getLine("Sent messages/sec: ",
InventoryItem.sentMessagesPerSec, seedNode, requestInfo, map, this::getRounded));
sb.append(getLine("Received messages/sec: ",
InventoryItem.receivedMessagesPerSec, seedNode, requestInfo, map, this::getRounded));
sb.append(getLine("Sent kB/sec: ",
InventoryItem.sentBytesPerSec, seedNode, requestInfo, map, this::getRounded));
sb.append(getLine("Received kB/sec: ",
InventoryItem.receivedBytesPerSec, seedNode, requestInfo, map, this::getRounded));
sb.append(getLine("Sent data: ",
InventoryItem.sentBytes, seedNode, requestInfo, map,
value -> Utilities.readableFileSize(Long.parseLong(value))));
sb.append(getLine("Received data: ",
InventoryItem.receivedBytes, seedNode, requestInfo, map,
value -> Utilities.readableFileSize(Long.parseLong(value))));
return sb.toString();
}
@ -292,63 +314,87 @@ public class InventoryWebServer {
///////////////////////////////////////////////////////////////////////////////////////////
private String getLine(InventoryItem inventoryItem,
NodeAddress seedNode,
RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
Collection<List<RequestInfo>> collection) {
Map<NodeAddress, List<RequestInfo>> map) {
return getLine(getTitle(inventoryItem),
inventoryItem,
seedNode,
requestInfo,
averageValues,
collection);
map);
}
private String getLine(String title,
InventoryItem inventoryItem,
NodeAddress seedNode,
RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
Collection<List<RequestInfo>> collection) {
Map<NodeAddress, List<RequestInfo>> map) {
return getLine(title,
inventoryItem,
seedNode,
requestInfo,
averageValues,
collection,
null,
map,
null);
}
private String getLine(String title,
InventoryItem inventoryItem,
NodeAddress seedNode,
RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
Collection<List<RequestInfo>> collection,
@Nullable String daoStateChainHeight) {
return getLine(title,
inventoryItem,
requestInfo,
averageValues,
collection,
daoStateChainHeight,
null);
}
private String getLine(String title,
InventoryItem inventoryItem,
RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
Collection<List<RequestInfo>> collection,
@Nullable String daoStateChainHeight,
Map<NodeAddress, List<RequestInfo>> map,
@Nullable Function<String, String> formatter) {
String displayValue = requestInfo.getDisplayValue(inventoryItem);
String value = requestInfo.getValue(inventoryItem);
if (formatter != null && value != null) {
displayValue = formatter.apply(value);
}
Double deviation = inventoryItem.getDeviation(averageValues, value);
DeviationSeverity deviationSeverity = inventoryItem.getDeviationSeverity(deviation, collection, value, daoStateChainHeight);
String deviationAsPercentString = "";
DeviationSeverity deviationSeverity = DeviationSeverity.OK;
if (requestInfo.getDataMap().containsKey(inventoryItem)) {
RequestInfo.Data data = requestInfo.getDataMap().get(inventoryItem);
deviationAsPercentString = getDeviationAsPercentString(data.getDeviation());
deviationSeverity = data.getDeviationSeverity();
}
List<RequestInfo> requestInfoList = map.get(seedNode);
String historicalWarnings = "";
String historicalAlerts = "";
List<Integer> warningsAtRequestNumber = new ArrayList<>();
List<Integer> alertsAtRequestNumber = new ArrayList<>();
if (requestInfoList != null) {
for (int i = 0; i < requestInfoList.size(); i++) {
RequestInfo reqInfo = requestInfoList.get(i);
Map<InventoryItem, RequestInfo.Data> deviationInfoMap = reqInfo.getDataMap();
if (deviationInfoMap.containsKey(inventoryItem)) {
if (deviationInfoMap.get(inventoryItem).isPersistentWarning()) {
warningsAtRequestNumber.add(i + 1);
} else if (deviationInfoMap.get(inventoryItem).isPersistentAlert()) {
alertsAtRequestNumber.add(i + 1);
}
}
}
if (!warningsAtRequestNumber.isEmpty()) {
historicalWarnings = inventoryItem.getDeviationTolerance() + " repeated warning(s) at request(s) " + Joiner.on(", ").join(warningsAtRequestNumber);
}
if (!alertsAtRequestNumber.isEmpty()) {
historicalAlerts = inventoryItem.getDeviationTolerance() + " repeated alert(s) at request(s): " + Joiner.on(", ").join(alertsAtRequestNumber);
}
}
String warningIcon = "&#9888; ";
String historicalWarningsHtml = warningsAtRequestNumber.isEmpty() ? "" :
", <b><a id=\"warn\" href=\"#\" title=\"" + historicalWarnings + "\">" + warningIcon + warningsAtRequestNumber.size() + "</a></b>";
String errorIcon = "&#9760; "; // &#9889; &#9889;
String historicalAlertsHtml = alertsAtRequestNumber.isEmpty() ? "" :
", <b><a id=\"alert\" href=\"#\" title=\"" + historicalAlerts + "\">" + errorIcon + alertsAtRequestNumber.size() + "</a></b>";
return title +
getColorTagByDeviationSeverity(deviationSeverity) +
displayValue +
getDeviationAsPercentString(deviation) +
deviationAsPercentString +
historicalWarningsHtml +
historicalAlertsHtml +
CLOSE_TAG;
}
@ -367,9 +413,11 @@ public class InventoryWebServer {
switch (deviationSeverity) {
case WARN:
return "<font color=\"blue\">";
return "<font color=\"#0000cc\">";
case ALERT:
return "<font color=\"red\">";
return "<font color=\"#cc0000\">";
case IGNORED:
return "<font color=\"#333333\">";
case OK:
default:
return "<font color=\"black\">";

View file

@ -15,5 +15,3 @@ devinsn3xuzxhj6pmammrxpydhwwmwp75qkksedo5dn2tlmu7jggo7id.onion:8000 (@devinbilec
s67qglwhkgkyvr74.onion:8000 (@emzy)
5quyxpxheyvzmb2d.onion:8000 (@miker)
rm7b56wbrcczpjvl.onion:8000 (@miker)
3f3cu2yw7u457ztq.onion:8000 (@devinbileck)
fl3mmribyxgrv63c.onion:8000 (@devinbileck)

View file

@ -326,6 +326,10 @@ public final class PeerManager implements ConnectionListener, PersistedDataHost
}
}
public void resetNumAllConnectionsLostEvents() {
numAllConnectionsLostEvents = 0;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Peer