Add InventoryItem enum

Add average and color codes to html
Add maxConnections
Add DeviationSeverity enum
Add custom seed node file
Use InventoryItem as key in inventory map instead of string
This commit is contained in:
chimp1984 2020-10-18 02:50:33 -05:00
parent b846979ecd
commit 752208b3ca
No known key found for this signature in database
GPG Key ID: 9801B4EC591F90E3
11 changed files with 478 additions and 161 deletions

View File

@ -0,0 +1,24 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.network.p2p.inventory;
public enum DeviationSeverity {
OK,
WARN,
ALERT
}

View File

@ -34,12 +34,17 @@ import bisq.network.p2p.network.Statistic;
import bisq.network.p2p.storage.P2PDataStorage;
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import bisq.common.config.Config;
import bisq.common.proto.network.NetworkEnvelope;
import bisq.common.util.MathUtils;
import bisq.common.util.Profiler;
import bisq.common.util.Utilities;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.base.Enums;
import com.google.common.base.Optional;
import java.util.HashMap;
import java.util.HashSet;
@ -61,6 +66,7 @@ public class GetInventoryRequestHandler implements MessageListener {
private final DaoStateMonitoringService daoStateMonitoringService;
private final ProposalStateMonitoringService proposalStateMonitoringService;
private final BlindVoteStateMonitoringService blindVoteStateMonitoringService;
private final int maxConnections;
private final Set<String> permittedRequestersPubKey = new HashSet<>();
@Inject
@ -69,13 +75,15 @@ public class GetInventoryRequestHandler implements MessageListener {
DaoStateService daoStateService,
DaoStateMonitoringService daoStateMonitoringService,
ProposalStateMonitoringService proposalStateMonitoringService,
BlindVoteStateMonitoringService blindVoteStateMonitoringService) {
BlindVoteStateMonitoringService blindVoteStateMonitoringService,
@Named(Config.MAX_CONNECTIONS) int maxConnections) {
this.networkNode = networkNode;
this.p2PDataStorage = p2PDataStorage;
this.daoStateService = daoStateService;
this.daoStateMonitoringService = daoStateMonitoringService;
this.proposalStateMonitoringService = proposalStateMonitoringService;
this.blindVoteStateMonitoringService = blindVoteStateMonitoringService;
this.maxConnections = maxConnections;
this.networkNode.addMessageListener(this);
}
@ -88,80 +96,84 @@ public class GetInventoryRequestHandler implements MessageListener {
}
GetInventoryRequest getInventoryRequest = (GetInventoryRequest) networkEnvelope;
Map<String, Integer> dataObjects = new HashMap<>();
Map<InventoryItem, Integer> dataObjects = new HashMap<>();
p2PDataStorage.getMapForDataResponse(getInventoryRequest.getVersion()).values().stream()
.map(e -> e.getClass().getSimpleName())
.forEach(className -> {
dataObjects.putIfAbsent(className, 0);
int prev = dataObjects.get(className);
dataObjects.put(className, prev + 1);
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);
}
});
p2PDataStorage.getMap().values().stream()
.map(ProtectedStorageEntry::getProtectedStoragePayload)
.filter(Objects::nonNull)
.map(e -> e.getClass().getSimpleName())
.forEach(className -> {
dataObjects.putIfAbsent(className, 0);
int prev = dataObjects.get(className);
dataObjects.put(className, prev + 1);
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);
}
});
Map<String, String> inventory = new HashMap<>();
Map<InventoryItem, String> inventory = new HashMap<>();
dataObjects.forEach((key, value) -> inventory.put(key, String.valueOf(value)));
// DAO data
int numBsqBlocks = daoStateService.getBlocks().size();
inventory.put("numBsqBlocks", String.valueOf(numBsqBlocks));
inventory.put(InventoryItem.numBsqBlocks, String.valueOf(numBsqBlocks));
int daoStateChainHeight = daoStateService.getChainHeight();
inventory.put("daoStateChainHeight", String.valueOf(daoStateChainHeight));
inventory.put(InventoryItem.daoStateChainHeight, String.valueOf(daoStateChainHeight));
LinkedList<DaoStateBlock> daoStateBlockChain = daoStateMonitoringService.getDaoStateBlockChain();
if (!daoStateBlockChain.isEmpty()) {
String daoStateHash = Utilities.bytesAsHexString(daoStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("daoStateHash", daoStateHash);
} else {
inventory.put("daoStateHash", "n/a");
inventory.put(InventoryItem.daoStateHash, daoStateHash);
}
LinkedList<ProposalStateBlock> proposalStateBlockChain = proposalStateMonitoringService.getProposalStateBlockChain();
if (!proposalStateBlockChain.isEmpty()) {
String proposalHash = Utilities.bytesAsHexString(proposalStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("proposalHash", proposalHash);
} else {
inventory.put("proposalHash", "n/a");
inventory.put(InventoryItem.proposalHash, proposalHash);
}
LinkedList<BlindVoteStateBlock> blindVoteStateBlockChain = blindVoteStateMonitoringService.getBlindVoteStateBlockChain();
if (!blindVoteStateBlockChain.isEmpty()) {
String blindVoteHash = Utilities.bytesAsHexString(blindVoteStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("blindVoteHash", blindVoteHash);
} else {
inventory.put("blindVoteHash", "n/a");
inventory.put(InventoryItem.blindVoteHash, blindVoteHash);
}
// P2P network data
inventory.put(InventoryItem.maxConnections, String.valueOf(maxConnections));
int numConnections = networkNode.getAllConnections().size();
inventory.put("numConnections", String.valueOf(numConnections));
inventory.put(InventoryItem.numConnections, String.valueOf(numConnections));
long sentBytes = Statistic.totalSentBytesProperty().get();
inventory.put("sentData", Utilities.readableFileSize(sentBytes));
inventory.put(InventoryItem.sentData, String.valueOf(sentBytes));
long receivedBytes = Statistic.totalReceivedBytesProperty().get();
inventory.put("receivedData", Utilities.readableFileSize(receivedBytes));
inventory.put(InventoryItem.receivedData, String.valueOf(receivedBytes));
double receivedMessagesPerSec = MathUtils.roundDouble(Statistic.numTotalReceivedMessagesPerSecProperty().get(), 2);
inventory.put("receivedMessagesPerSec", String.valueOf(receivedMessagesPerSec));
inventory.put(InventoryItem.receivedMessagesPerSec, String.valueOf(receivedMessagesPerSec));
double sentMessagesPerSec = MathUtils.roundDouble(Statistic.numTotalSentMessagesPerSecProperty().get(), 2);
inventory.put("sentMessagesPerSec", String.valueOf(sentMessagesPerSec));
inventory.put(InventoryItem.sentMessagesPerSec, String.valueOf(sentMessagesPerSec));
// JVM info
long usedMemory = Profiler.getUsedMemoryInMB();
inventory.put("usedMemory", String.valueOf(usedMemory));
inventory.put(InventoryItem.usedMemory, String.valueOf(usedMemory));
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
long startTime = runtimeBean.getStartTime();
inventory.put("jvmStartTime", String.valueOf(startTime));
inventory.put(InventoryItem.jvmStartTime, String.valueOf(startTime));
log.info("Send inventory {} to {}", inventory, connection.getPeersNodeAddressOptional());
GetInventoryResponse getInventoryResponse = new GetInventoryResponse(inventory);

View File

@ -41,7 +41,7 @@ public class GetInventoryRequestManager {
}
public void request(NodeAddress nodeAddress,
Consumer<Map<String, String>> resultHandler,
Consumer<Map<InventoryItem, String>> resultHandler,
ErrorMessageHandler errorMessageHandler) {
if (requesterMap.containsKey(nodeAddress)) {
log.warn("There is still an open request pending for {}", nodeAddress.getFullAddress());

View File

@ -42,13 +42,13 @@ public class GetInventoryRequester implements MessageListener {
private final NetworkNode networkNode;
private final NodeAddress nodeAddress;
private final Consumer<Map<String, String>> resultHandler;
private final Consumer<Map<InventoryItem, String>> resultHandler;
private final ErrorMessageHandler errorMessageHandler;
private Timer timer;
public GetInventoryRequester(NetworkNode networkNode,
NodeAddress nodeAddress,
Consumer<Map<String, String>> resultHandler,
Consumer<Map<InventoryItem, String>> resultHandler,
ErrorMessageHandler errorMessageHandler) {
this.networkNode = networkNode;
this.nodeAddress = nodeAddress;

View File

@ -0,0 +1,92 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.network.p2p.inventory;
import lombok.Getter;
public enum InventoryItem {
OfferPayload("OfferPayload", Integer.class),
MailboxStoragePayload("MailboxStoragePayload", Integer.class),
TradeStatistics3("TradeStatistics3", Integer.class),
Alert("Alert", Integer.class),
Filter("Filter", Integer.class),
Mediator("Mediator", Integer.class),
RefundAgent("RefundAgent", Integer.class),
AccountAgeWitness("AccountAgeWitness", Integer.class),
SignedWitness("SignedWitness", Integer.class),
TempProposalPayload("TempProposalPayload", Integer.class),
ProposalPayload("ProposalPayload", Integer.class),
BlindVotePayload("BlindVotePayload", Integer.class),
daoStateChainHeight("daoStateChainHeight", Integer.class),
numBsqBlocks("numBsqBlocks", Integer.class),
daoStateHash("daoStateHash", String.class),
proposalHash("proposalHash", String.class),
blindVoteHash("blindVoteHash", String.class),
maxConnections("maxConnections", Integer.class),
numConnections("numConnections", Integer.class),
sentData("sentData", String.class), // todo should be long
receivedData("receivedData", String.class),// todo should be long
receivedMessagesPerSec("receivedMessagesPerSec", Double.class),
sentMessagesPerSec("sentMessagesPerSec", Double.class),
usedMemory("usedMemory", Long.class, 0, 3, 0, 2),
jvmStartTime("jvmStartTime", Long.class);
@Getter
private final String key;
@Getter
private final Class type;
private double lowerAlertTrigger = 0.7;
private double upperAlertTrigger = 1.5;
private double lowerWarnTrigger = 0.8;
private double upperWarnTrigger = 1.3;
InventoryItem(String key, Class type) {
this.key = key;
this.type = type;
}
InventoryItem(String key,
Class type,
double lowerAlertTrigger,
double upperAlertTrigger,
double lowerWarnTrigger,
double upperWarnTrigger) {
this.key = key;
this.type = type;
this.lowerAlertTrigger = lowerAlertTrigger;
this.upperAlertTrigger = upperAlertTrigger;
this.lowerWarnTrigger = lowerWarnTrigger;
this.upperWarnTrigger = upperWarnTrigger;
}
public DeviationSeverity getDeviationSeverity(double deviation) {
if (deviation <= lowerAlertTrigger || deviation >= upperAlertTrigger) {
return DeviationSeverity.ALERT;
}
if (deviation <= lowerWarnTrigger || deviation >= upperWarnTrigger) {
return DeviationSeverity.WARN;
}
return DeviationSeverity.OK;
}
}

View File

@ -17,18 +17,24 @@
package bisq.core.network.p2p.inventory.messages;
import bisq.core.network.p2p.inventory.InventoryItem;
import bisq.common.app.Version;
import bisq.common.proto.network.NetworkEnvelope;
import com.google.common.base.Enums;
import com.google.common.base.Optional;
import java.util.HashMap;
import java.util.Map;
import lombok.Value;
@Value
public class GetInventoryResponse extends NetworkEnvelope {
private final Map<String, String> inventory;
private final Map<InventoryItem, String> inventory;
public GetInventoryResponse(Map<String, String> inventory) {
public GetInventoryResponse(Map<InventoryItem, String> inventory) {
this(inventory, Version.getP2PMessageVersion());
}
@ -36,7 +42,7 @@ public class GetInventoryResponse extends NetworkEnvelope {
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private GetInventoryResponse(Map<String, String> inventory, int messageVersion) {
private GetInventoryResponse(Map<InventoryItem, String> inventory, int messageVersion) {
super(messageVersion);
this.inventory = inventory;
@ -44,13 +50,25 @@ public class GetInventoryResponse extends NetworkEnvelope {
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
// For protobuf we use a map with a string key
Map<String, String> map = new HashMap<>();
inventory.forEach((key, value) -> map.put(key.getKey(), value));
return getNetworkEnvelopeBuilder()
.setGetInventoryResponse(protobuf.GetInventoryResponse.newBuilder()
.putAllInventory(inventory))
.putAllInventory(map))
.build();
}
public static GetInventoryResponse fromProto(protobuf.GetInventoryResponse proto, int messageVersion) {
return new GetInventoryResponse(proto.getInventoryMap(), messageVersion);
// For protobuf we use a map with a string key
Map<String, String> map = proto.getInventoryMap();
Map<InventoryItem, String> inventory = new HashMap<>();
map.forEach((key, value) -> {
Optional<InventoryItem> optional = Enums.getIfPresent(InventoryItem.class, key);
if (optional.isPresent()) {
inventory.put(optional.get(), value);
}
});
return new GetInventoryResponse(inventory, messageVersion);
}
}

View File

@ -20,7 +20,6 @@ package bisq.core.network.p2p.seed;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.seed.SeedNodeRepository;
import bisq.common.config.BaseCurrencyNetwork;
import bisq.common.config.Config;
import javax.inject.Inject;
@ -67,7 +66,7 @@ public class DefaultSeedNodeRepository implements SeedNodeRepository {
}
cache.clear();
List<NodeAddress> result = getSeedNodeAddressesFromPropertyFile(config.baseCurrencyNetwork);
List<NodeAddress> result = getSeedNodeAddressesFromPropertyFile(config.baseCurrencyNetwork.name().toLowerCase());
cache.addAll(result);
// filter
@ -85,18 +84,18 @@ public class DefaultSeedNodeRepository implements SeedNodeRepository {
}
}
public static Optional<BufferedReader> readSeedNodePropertyFile(BaseCurrencyNetwork baseCurrencyNetwork) {
public static Optional<BufferedReader> readSeedNodePropertyFile(String fileName) {
InputStream fileInputStream = DefaultSeedNodeRepository.class.getClassLoader().getResourceAsStream(
baseCurrencyNetwork.name().toLowerCase() + ENDING);
fileName + ENDING);
if (fileInputStream == null) {
return Optional.empty();
}
return Optional.of(new BufferedReader(new InputStreamReader(fileInputStream)));
}
public static List<NodeAddress> getSeedNodeAddressesFromPropertyFile(BaseCurrencyNetwork baseCurrencyNetwork) {
public static List<NodeAddress> getSeedNodeAddressesFromPropertyFile(String fileName) {
List<NodeAddress> list = new ArrayList<>();
readSeedNodePropertyFile(baseCurrencyNetwork).ifPresent(seedNodeFile -> {
readSeedNodePropertyFile(fileName).ifPresent(seedNodeFile -> {
seedNodeFile.lines().forEach(line -> {
Matcher matcher = pattern.matcher(line);
if (matcher.find())

View File

@ -19,6 +19,7 @@ package bisq.inventory;
import bisq.core.network.p2p.inventory.GetInventoryRequestManager;
import bisq.core.network.p2p.inventory.InventoryItem;
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
import bisq.core.proto.network.CoreNetworkProtoResolver;
@ -39,18 +40,22 @@ import java.time.Clock;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
public class InventoryMonitor {
private final Map<NodeAddress, JsonFileManager> jsonFileManagerByNodeAddress = new HashMap<>();
private final Map<NodeAddress, List<RequestInfo>> requestInfoListByNode = new HashMap<>();
private final boolean useLocalhostForP2P;
private final int intervalSec;
@ -64,9 +69,14 @@ public class InventoryMonitor {
setupCapabilities();
DefaultSeedNodeRepository.readSeedNodePropertyFile(network)
//TODO until we use all seeds we use our custom seed node file which includes only those which have updated to our branch
// Once all seeds are updated we can remove that resource file and prefix
//String fileName = network.name().toLowerCase();
String networkName = network.name().toLowerCase();
String fileName = network.isMainnet() ? "inv_" + networkName : networkName;
DefaultSeedNodeRepository.readSeedNodePropertyFile(fileName)
.ifPresent(seedNodeFile -> {
List<NodeAddress> seedNodes = new ArrayList<>(DefaultSeedNodeRepository.getSeedNodeAddressesFromPropertyFile(network));
List<NodeAddress> seedNodes = new ArrayList<>(DefaultSeedNodeRepository.getSeedNodeAddressesFromPropertyFile(fileName));
File jsonDir = new File(appDir, "json");
if (!jsonDir.exists() && !jsonDir.mkdir()) {
log.warn("make jsonDir failed");
@ -103,6 +113,10 @@ public class InventoryMonitor {
});
}
public void shutDown() {
jsonFileManagerByNodeAddress.values().forEach(JsonFileManager::shutDown);
}
private NetworkNode getNetworkNode(File appDir) {
File torDir = new File(appDir, "tor");
CoreNetworkProtoResolver networkProtoResolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone());
@ -170,7 +184,19 @@ public class InventoryMonitor {
requestInfo.setResponseTime(responseTime);
requestInfo.setInventory(result);
inventoryWebServer.onNewRequestInfo(requestInfo, nodeAddress);
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> requestInfoSetOfOtherNodes = requestInfoListByNode.values().stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1))
.collect(Collectors.toSet());
Map<InventoryItem, Double> averageValues = getAverageValues(requestInfoSetOfOtherNodes);
inventoryWebServer.onNewRequestInfo(requestInfoListByNode, averageValues);
String json = Utilities.objectToJson(requestInfo);
jsonFileManagerByNodeAddress.get(nodeAddress).writeToDisc(json, String.valueOf(responseTime));
@ -182,26 +208,53 @@ public class InventoryMonitor {
}).start();
});
}
public void shutDown() {
jsonFileManagerByNodeAddress.values().forEach(JsonFileManager::shutDown);
private Map<InventoryItem, Double> getAverageValues(Set<RequestInfo> requestInfoSetOfOtherNodes) {
Map<InventoryItem, Double> averageValuesPerItem = new HashMap<>();
Arrays.asList(InventoryItem.values()).forEach(inventoryItem -> {
if (inventoryItem.getType().equals(Integer.class)) {
averageValuesPerItem.put(inventoryItem, getAverageFromIntegerValues(requestInfoSetOfOtherNodes, inventoryItem));
} else if (inventoryItem.getType().equals(Long.class)) {
averageValuesPerItem.put(inventoryItem, getAverageFromLongValues(requestInfoSetOfOtherNodes, inventoryItem));
} else if (inventoryItem.getType().equals(Double.class)) {
averageValuesPerItem.put(inventoryItem, getAverageFromDoubleValues(requestInfoSetOfOtherNodes, inventoryItem));
}
// If type of value is String we ignore it
});
return averageValuesPerItem;
}
@Getter
public static class RequestInfo {
private final long requestStartTime;
@Setter
private long responseTime;
@Setter
private Map<String, String> inventory;
@Setter
private String errorMessage;
private double getAverageFromIntegerValues(Set<RequestInfo> requestInfoSetOfOtherNodes,
InventoryItem inventoryItem) {
checkArgument(inventoryItem.getType().equals(Integer.class));
return requestInfoSetOfOtherNodes.stream()
.map(RequestInfo::getInventory)
.filter(inventory -> inventory.containsKey(inventoryItem))
.mapToInt(inventory -> Integer.parseInt(inventory.get(inventoryItem)))
.average()
.orElse(0d);
}
public RequestInfo(long requestStartTime) {
this.requestStartTime = requestStartTime;
}
private double getAverageFromLongValues(Set<RequestInfo> requestInfoSetOfOtherNodes,
InventoryItem inventoryItem) {
checkArgument(inventoryItem.getType().equals(Long.class));
return requestInfoSetOfOtherNodes.stream()
.map(RequestInfo::getInventory)
.filter(inventory -> inventory.containsKey(inventoryItem))
.mapToLong(inventory -> Long.parseLong(inventory.get(inventoryItem)))
.average()
.orElse(0d);
}
private double getAverageFromDoubleValues(Set<RequestInfo> requestInfoSetOfOtherNodes,
InventoryItem inventoryItem) {
checkArgument(inventoryItem.getType().equals(Double.class));
return requestInfoSetOfOtherNodes.stream()
.map(RequestInfo::getInventory)
.filter(inventory -> inventory.containsKey(inventoryItem))
.mapToDouble(inventory -> Double.parseDouble((inventory.get(inventoryItem))))
.average()
.orElse(0d);
}
}

View File

@ -17,17 +17,20 @@
package bisq.inventory;
import bisq.core.network.p2p.inventory.DeviationSeverity;
import bisq.core.network.p2p.inventory.InventoryItem;
import bisq.network.p2p.NodeAddress;
import bisq.common.util.MathUtils;
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
@ -39,8 +42,9 @@ import spark.Spark;
@Slf4j
public class InventoryWebServer {
private final static String CLOSE_TAG = "</font><br/>";
private final List<NodeAddress> seedNodes;
private Map<NodeAddress, List<InventoryMonitor.RequestInfo>> map = new HashMap<>();
private final Map<String, String> operatorByNodeAddress = new HashMap<>();
private String html;
@ -53,10 +57,9 @@ public class InventoryWebServer {
setupServer(port);
}
public void onNewRequestInfo(InventoryMonitor.RequestInfo requestInfo, NodeAddress nodeAddress) {
map.putIfAbsent(nodeAddress, new ArrayList<>());
map.get(nodeAddress).add(requestInfo);
html = getHtml(map);
public void onNewRequestInfo(Map<NodeAddress, List<RequestInfo>> requestInfoListByNode,
Map<InventoryItem, Double> averageValues) {
html = getHtml(requestInfoListByNode, averageValues);
}
private void setupServer(int port) {
@ -78,9 +81,13 @@ public class InventoryWebServer {
});
}
private String getHtml(Map<NodeAddress, List<InventoryMonitor.RequestInfo>> map) {
private String getHtml(Map<NodeAddress, List<RequestInfo>> map,
Map<InventoryItem, Double> averageValues) {
StringBuilder html = new StringBuilder();
html.append("<html><head><style>table, th, td {border: 1px solid black;}</style></head><body><h3>")
html.append("<html>" +
"<head><style>table, th, td {border: 1px solid black;}</style></head>" +
"<body><h3>")
.append("Current time: ").append(new Date().toString()).append("<br/>")
.append("<table style=\"width:100%\">")
.append("<tr>")
.append("<th align=\"left\">Seed node info</th>")
@ -91,19 +98,19 @@ public class InventoryWebServer {
seedNodes.forEach(seedNode -> {
if (map.containsKey(seedNode) && !map.get(seedNode).isEmpty()) {
List<InventoryMonitor.RequestInfo> list = map.get(seedNode);
List<RequestInfo> list = map.get(seedNode);
int numRequests = list.size();
InventoryMonitor.RequestInfo last = list.get(numRequests - 1);
RequestInfo last = list.get(numRequests - 1);
html.append("<tr valign=\"top\">")
.append("<td>").append(getSeedNodeInfo(seedNode, last)).append("</td>")
.append("<td>").append(getSeedNodeInfo(seedNode, last, averageValues)).append("</td>")
.append("<td>").append(getRequestInfo(last, numRequests)).append("</td>")
.append("<td>").append(getDataInfo(last)).append("</td>")
.append("<td>").append(getDaoInfo(last)).append("</td>")
.append("<td>").append(getNetworkInfo(last)).append("</td>");
.append("<td>").append(getDataInfo(last, averageValues)).append("</td>")
.append("<td>").append(getDaoInfo(last, averageValues)).append("</td>")
.append("<td>").append(getNetworkInfo(last, averageValues)).append("</td>");
html.append("</tr>");
} else {
html.append("<tr valign=\"top\">")
.append("<td>").append(getSeedNodeInfo(seedNode, null)).append("</td>")
.append("<td>").append(getSeedNodeInfo(seedNode, null, averageValues)).append("</td>")
.append("<td>").append("n/a").append("</td>")
.append("<td>").append("n/a").append("</td>")
.append("<td>").append("n/a").append("</td>")
@ -117,7 +124,9 @@ public class InventoryWebServer {
}
private String getSeedNodeInfo(NodeAddress nodeAddress, @Nullable InventoryMonitor.RequestInfo last) {
private String getSeedNodeInfo(NodeAddress nodeAddress,
@Nullable RequestInfo last,
Map<InventoryItem, Double> averageValues) {
StringBuilder sb = new StringBuilder();
String operator = operatorByNodeAddress.get(nodeAddress.getFullAddress());
@ -127,17 +136,19 @@ public class InventoryWebServer {
sb.append("Node address: ").append(address).append("<br/>");
if (last != null) {
String usedMemory = last.getInventory().get("usedMemory");
sb.append("Memory used: ").append(usedMemory).append(" MB<br/>");
Date jvmStartTime = new Date(Long.parseLong(last.getInventory().get("jvmStartTime")));
sb.append("Node started at: ").append(jvmStartTime).append("<br/>");
addInventoryItem("Memory used: ", last, averageValues, sb, InventoryItem.usedMemory);
addInventoryItem("Node started at: ",
last,
null,
sb,
InventoryItem.jvmStartTime,
value -> new Date(Long.parseLong(value)).toString());
}
return sb.toString();
}
private String getRequestInfo(InventoryMonitor.RequestInfo last, int numRequests) {
private String getRequestInfo(RequestInfo last, int numRequests) {
StringBuilder sb = new StringBuilder();
Date requestStartTime = new Date(last.getRequestStartTime());
@ -146,99 +157,158 @@ public class InventoryWebServer {
Date responseTime = new Date(last.getResponseTime());
sb.append("Response received at: ").append(responseTime).append("<br/>");
long durationAsLong = last.getResponseTime() - last.getRequestStartTime();
double rrt = MathUtils.roundDouble(durationAsLong / 1000d, 3);
sb.append("Round trip time: ").append(rrt).append(" sec<br/>");
long rrt = last.getResponseTime() - last.getRequestStartTime();
DeviationSeverity rrtDeviationSeverity = DeviationSeverity.OK;
if (rrt > 20_000) {
rrtDeviationSeverity = DeviationSeverity.ALERT;
} else if (rrt > 10_000) {
rrtDeviationSeverity = DeviationSeverity.WARN;
}
sb.append("Number of requests: ").append(numRequests).append("<br/>");
String rrtString = MathUtils.roundDouble(rrt / 1000d, 3) + " sec";
sb.append("Round trip time: ").append(getColorTagByDeviationSeverity(rrtDeviationSeverity))
.append(rrtString).append(CLOSE_TAG);
sb.append("Error message: ").append(last.getErrorMessage()).append("<br/>");
sb.append("Number of requests: ").append(getColorTagByDeviationSeverity(DeviationSeverity.OK))
.append(numRequests).append(CLOSE_TAG);
String errorMessage = last.getErrorMessage();
rrtDeviationSeverity = errorMessage == null || errorMessage.isEmpty() ?
DeviationSeverity.OK :
DeviationSeverity.WARN;
sb.append("Error message: ").append(getColorTagByDeviationSeverity(rrtDeviationSeverity))
.append(errorMessage).append(CLOSE_TAG);
return sb.toString();
}
private String getDataInfo(InventoryMonitor.RequestInfo last) {
private String getDataInfo(RequestInfo last,
Map<InventoryItem, Double> averageValues) {
StringBuilder sb = new StringBuilder();
String OfferPayload = last.getInventory().get("OfferPayload");
sb.append("Number of OfferPayload: ").append(OfferPayload).append("<br/>");
String MailboxStoragePayload = last.getInventory().get("MailboxStoragePayload");
sb.append("Number of MailboxStoragePayload: ").append(MailboxStoragePayload).append("<br/>");
String TradeStatistics3 = last.getInventory().get("TradeStatistics3");
sb.append("Number of TradeStatistics3: ").append(TradeStatistics3).append("<br/>");
String Alert = last.getInventory().get("Alert");
sb.append("Number of Alert: ").append(Alert).append("<br/>");
String Filter = last.getInventory().get("Filter");
sb.append("Number of Filter: ").append(Filter).append("<br/>");
String Mediator = last.getInventory().get("Mediator");
sb.append("Number of Mediator: ").append(Mediator).append("<br/>");
String RefundAgent = last.getInventory().get("RefundAgent");
sb.append("Number of RefundAgent: ").append(RefundAgent).append("<br/>");
String AccountAgeWitness = last.getInventory().get("AccountAgeWitness");
sb.append("Number of AccountAgeWitness: ").append(AccountAgeWitness).append("<br/>");
String SignedWitness = last.getInventory().get("SignedWitness");
sb.append("Number of SignedWitness: ").append(SignedWitness).append("<br/>");
addInventoryItem(last, averageValues, sb, InventoryItem.OfferPayload);
addInventoryItem(last, averageValues, sb, InventoryItem.MailboxStoragePayload);
addInventoryItem(last, averageValues, sb, InventoryItem.TradeStatistics3);
addInventoryItem(last, averageValues, sb, InventoryItem.Alert);
addInventoryItem(last, averageValues, sb, InventoryItem.Filter);
addInventoryItem(last, averageValues, sb, InventoryItem.Mediator);
addInventoryItem(last, averageValues, sb, InventoryItem.RefundAgent);
addInventoryItem(last, averageValues, sb, InventoryItem.AccountAgeWitness);
addInventoryItem(last, averageValues, sb, InventoryItem.SignedWitness);
return sb.toString();
}
private String getDaoInfo(InventoryMonitor.RequestInfo last) {
private String getDaoInfo(RequestInfo last,
Map<InventoryItem, Double> averageValues) {
StringBuilder sb = new StringBuilder();
String numBsqBlocks = last.getInventory().get("numBsqBlocks");
sb.append("Number of BSQ blocks: ").append(numBsqBlocks).append("<br/>");
String TempProposalPayload = last.getInventory().get("TempProposalPayload");
sb.append("Number of TempProposalPayload: ").append(TempProposalPayload).append("<br/>");
String ProposalPayload = last.getInventory().get("ProposalPayload");
sb.append("Number of ProposalPayload: ").append(ProposalPayload).append("<br/>");
String BlindVotePayload = last.getInventory().get("BlindVotePayload");
sb.append("Number of BlindVotePayload: ").append(BlindVotePayload).append("<br/>");
String daoStateChainHeight = last.getInventory().get("daoStateChainHeight");
sb.append("DAO state block height: ").append(daoStateChainHeight).append("<br/>");
String daoStateHash = last.getInventory().get("daoStateHash");
sb.append("DAO state hash: ").append(daoStateHash).append("<br/>");
String proposalHash = last.getInventory().get("proposalHash");
sb.append("Proposal state hash: ").append(proposalHash).append("<br/>");
String blindVoteHash = last.getInventory().get("blindVoteHash");
sb.append("Blind vote state hash: ").append(blindVoteHash).append("<br/>");
addInventoryItem("Number of BSQ blocks: ", last, averageValues, sb, InventoryItem.numBsqBlocks);
addInventoryItem(last, averageValues, sb, InventoryItem.TempProposalPayload);
addInventoryItem(last, averageValues, sb, InventoryItem.ProposalPayload);
addInventoryItem(last, averageValues, sb, InventoryItem.BlindVotePayload);
addInventoryItem("DAO state block height: ", last, averageValues, sb, InventoryItem.daoStateChainHeight);
addInventoryItem("DAO state hash: ", last, sb, InventoryItem.daoStateHash);
addInventoryItem("Proposal state hash: ", last, sb, InventoryItem.proposalHash);
addInventoryItem("Blind vote state hash: ", last, sb, InventoryItem.blindVoteHash);
return sb.toString();
}
private String getNetworkInfo(InventoryMonitor.RequestInfo last) {
private String getNetworkInfo(RequestInfo last,
Map<InventoryItem, Double> averageValues) {
StringBuilder sb = new StringBuilder();
String numConnections = last.getInventory().get("numConnections");
sb.append("Number of connections: ").append(numConnections).append("<br/>");
String sentBytes = last.getInventory().get("sentData");
sb.append("Sent data: ").append(sentBytes).append("<br/>");
String receivedBytes = last.getInventory().get("receivedData");
sb.append("Received data: ").append(receivedBytes).append("<br/>");
String receivedMessagesPerSec = last.getInventory().get("receivedMessagesPerSec");
sb.append("Received messages/sec: ").append(receivedMessagesPerSec).append("<br/>");
String sentMessagesPerSec = last.getInventory().get("sentMessagesPerSec");
sb.append("Sent messages/sec: ").append(sentMessagesPerSec).append("<br/>");
addInventoryItem("Number of connections: ", last, averageValues, sb, InventoryItem.numConnections);
addInventoryItem("Sent data: ", last, averageValues, sb, InventoryItem.sentData);
addInventoryItem("Received data: ", last, averageValues, sb, InventoryItem.receivedData);
addInventoryItem("Received messages/sec: ", last, averageValues, sb, InventoryItem.receivedMessagesPerSec);
addInventoryItem("Sent messages/sec: ", last, averageValues, sb, InventoryItem.sentMessagesPerSec);
return sb.toString();
}
private void addInventoryItem(RequestInfo last,
Map<InventoryItem, Double> averageValues,
StringBuilder sb,
InventoryItem inventoryItem) {
addInventoryItem("Number of " + inventoryItem.getKey() + ": ",
last,
averageValues,
sb,
inventoryItem);
}
private void addInventoryItem(String title,
RequestInfo last,
StringBuilder sb,
InventoryItem inventoryItem) {
addInventoryItem(title,
last,
null,
sb,
inventoryItem);
}
private void addInventoryItem(String title,
RequestInfo last,
@Nullable Map<InventoryItem, Double> averageValues,
StringBuilder sb,
InventoryItem inventoryItem) {
addInventoryItem(title,
last,
averageValues,
sb,
inventoryItem,
null);
}
private void addInventoryItem(String title,
RequestInfo last,
@Nullable Map<InventoryItem, Double> averageValues,
StringBuilder sb,
InventoryItem inventoryItem,
@Nullable Function<String, String> formatter) {
String valueAsString;
String deviationAsString = "";
String colorTag = getColorTagByDeviationSeverity(DeviationSeverity.OK);
if (last.getInventory().containsKey(inventoryItem)) {
valueAsString = last.getInventory().get(inventoryItem);
if (averageValues != null && averageValues.containsKey(inventoryItem)) {
double average = averageValues.get(inventoryItem);
boolean isNumber = false;
double value = 0d;
if (inventoryItem.getType().equals(Integer.class)) {
value = Integer.parseInt(valueAsString);
isNumber = true;
} else if (inventoryItem.getType().equals(Long.class)) {
value = Long.parseLong(valueAsString);
isNumber = true;
} else if (inventoryItem.getType().equals(Double.class)) {
value = Double.parseDouble(valueAsString);
isNumber = true;
}
if (isNumber) {
double deviation = value / average;
colorTag = getColorTagByDeviationSeverity(inventoryItem.getDeviationSeverity(deviation));
deviationAsString = " (" + MathUtils.roundDouble(100 * deviation, 2) + " %)";
}
}
} else {
valueAsString = "n/a";
}
if (formatter != null) {
valueAsString = formatter.apply(valueAsString);
}
sb.append(title).append(colorTag).append(valueAsString).append(deviationAsString).append(CLOSE_TAG);
}
private String getColorTagByDeviationSeverity(DeviationSeverity deviationSeverity) {
switch (deviationSeverity) {
case WARN:
return "<font color=\"blue\">";
case ALERT:
return "<font color=\"red\">";
case OK:
default:
return "<font color=\"black\">";
}
}
}

View File

@ -0,0 +1,40 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.inventory;
import bisq.core.network.p2p.inventory.InventoryItem;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
@Getter
public class RequestInfo {
private final long requestStartTime;
@Setter
private long responseTime;
@Setter
private Map<InventoryItem, String> inventory;
@Setter
private String errorMessage;
public RequestInfo(long requestStartTime) {
this.requestStartTime = requestStartTime;
}
}

View File

@ -0,0 +1,9 @@
# nodeaddress.onion:port [(@owner,@backup)]
wizseedscybbttk4bmb2lzvbuk2jtect37lcpva4l3twktmkzemwbead.onion:8000 (@wiz)
wizseed3d376esppbmbjxk2fhk2jg5fpucddrzj2kxtbxbx4vrnwclad.onion:8000 (@wiz)
wizseed7ab2gi3x267xahrp2pkndyrovczezzb46jk6quvguciuyqrid.onion:8000 (@wiz)
sn3emzy56u3mxzsr4geysc52feoq5qt7ja56km6gygwnszkshunn2sid.onion:8000 (@emzy)
sn4emzywye3dhjouv7jig677qepg7fnusjidw74fbwneieruhmi7fuyd.onion:8000 (@emzy)
sn5emzyvxuildv34n6jewfp2zeota4aq63fsl5yyilnvksezr3htveqd.onion:8000 (@emzy)
723ljisnynbtdohi.onion:8000 (@emzy)
s67qglwhkgkyvr74.onion:8000 (@emzy)