Improve deviation model

This commit is contained in:
chimp1984 2020-10-22 23:46:13 -05:00
parent 0c4eb14077
commit f1fdf3c0a5
No known key found for this signature in database
GPG Key ID: 9801B4EC591F90E3
17 changed files with 664 additions and 504 deletions

View File

@ -26,6 +26,7 @@ import bisq.core.dao.monitoring.model.ProposalStateBlock;
import bisq.core.dao.state.DaoStateService;
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.network.p2p.network.Connection;
import bisq.network.p2p.network.MessageListener;
@ -48,11 +49,9 @@ import com.google.common.base.Enums;
import com.google.common.base.Optional;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.lang.management.ManagementFactory;
@ -68,7 +67,6 @@ public class GetInventoryRequestHandler implements MessageListener {
private final ProposalStateMonitoringService proposalStateMonitoringService;
private final BlindVoteStateMonitoringService blindVoteStateMonitoringService;
private final int maxConnections;
private final Set<String> permittedRequestersPubKey = new HashSet<>();
@Inject
public GetInventoryRequestHandler(NetworkNode networkNode,
@ -94,10 +92,6 @@ public class GetInventoryRequestHandler implements MessageListener {
@Override
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
if (networkEnvelope instanceof GetInventoryRequest) {
if (permittedRequestersPubKey.isEmpty()) {
return;
}
// Data
GetInventoryRequest getInventoryRequest = (GetInventoryRequest) networkEnvelope;
Map<InventoryItem, Integer> dataObjects = new HashMap<>();
@ -179,8 +173,4 @@ public class GetInventoryRequestHandler implements MessageListener {
public void shutDown() {
networkNode.removeMessageListener(this);
}
public void addPermittedRequestersPubKey(String pubKey) {
permittedRequestersPubKey.add(pubKey);
}
}

View File

@ -17,6 +17,8 @@
package bisq.core.network.p2p.inventory;
import bisq.core.network.p2p.inventory.model.InventoryItem;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.NetworkNode;

View File

@ -19,6 +19,7 @@ package bisq.core.network.p2p.inventory;
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.network.p2p.NodeAddress;
import bisq.network.p2p.network.CloseConnectionReason;

View File

@ -1,97 +0,0 @@
/*
* 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, 0.9, 1.1, 0.95, 1.05),
TradeStatistics3("TradeStatistics3", Integer.class, 0.9, 1.1, 0.95, 1.05),
Alert("Alert", Integer.class),
Filter("Filter", Integer.class),
Mediator("Mediator", Integer.class),
RefundAgent("RefundAgent", Integer.class),
AccountAgeWitness("AccountAgeWitness", Integer.class, 0.9, 1.1, 0.95, 1.05),
SignedWitness("SignedWitness", Integer.class, 0.9, 1.1, 0.95, 1.05),
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, 0.33, 3, 0.4, 2.5),
numConnections("numConnections", Integer.class, 0.33, 3, 0.4, 2.5),
peakNumConnections("peakNumConnections", Integer.class, 0.33, 3, 0.4, 2.5),
numAllConnectionsLostEvents("numAllConnectionsLostEvents", Integer.class, 0.9, 1.1, 0.95, 1.05),
sentBytes("sentBytes", Long.class, 0, 5, 0, 4),
sentBytesPerSec("sentBytesPerSec", Double.class, 0, 3, 0, 2),
receivedBytes("receivedBytes", Long.class, 0, 5, 0, 4),
receivedBytesPerSec("receivedBytesPerSec", Double.class, 0, 3, 0, 2),
receivedMessagesPerSec("receivedMessagesPerSec", Double.class, 0, 3, 0, 2),
sentMessagesPerSec("sentMessagesPerSec", Double.class, 0, 3, 0, 2),
version("version", String.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,7 +17,7 @@
package bisq.core.network.p2p.inventory.messages;
import bisq.core.network.p2p.inventory.InventoryItem;
import bisq.core.network.p2p.inventory.model.InventoryItem;
import bisq.common.app.Version;
import bisq.common.proto.network.NetworkEnvelope;

View File

@ -0,0 +1,46 @@
/*
* 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.model;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class Average {
public static Map<InventoryItem, Double> of(Set<RequestInfo> requestInfoSet) {
Map<InventoryItem, Double> averageValuesPerItem = new HashMap<>();
Arrays.asList(InventoryItem.values()).forEach(inventoryItem -> {
if (inventoryItem.isNumberValue()) {
averageValuesPerItem.put(inventoryItem, getAverage(requestInfoSet, inventoryItem));
}
});
return averageValuesPerItem;
}
public static double getAverage(Set<RequestInfo> requestInfoSet, InventoryItem inventoryItem) {
return requestInfoSet.stream()
.map(RequestInfo::getInventory)
.filter(Objects::nonNull)
.filter(inventory -> inventory.containsKey(inventoryItem))
.mapToDouble(inventory -> Double.parseDouble((inventory.get(inventoryItem))))
.average()
.orElse(0d);
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.model;
import bisq.common.util.Tuple2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
public class DeviationByIntegerDiff implements DeviationType {
private final int warnTrigger;
private final int alertTrigger;
public DeviationByIntegerDiff(int warnTrigger, int alertTrigger) {
this.warnTrigger = warnTrigger;
this.alertTrigger = alertTrigger;
}
public DeviationSeverity getDeviationSeverity(Collection<List<RequestInfo>> collection,
@Nullable String value,
InventoryItem inventoryItem) {
DeviationSeverity deviationSeverity = DeviationSeverity.OK;
if (value == null) {
return deviationSeverity;
}
Map<String, Integer> sameItemsByValue = new HashMap<>();
collection.stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1)) // We use last item only
.map(RequestInfo::getInventory)
.filter(Objects::nonNull)
.map(e -> e.get(inventoryItem))
.forEach(e -> {
sameItemsByValue.putIfAbsent(e, 0);
int prev = sameItemsByValue.get(e);
sameItemsByValue.put(e, prev + 1);
});
if (sameItemsByValue.size() > 1) {
List<Tuple2<String, Integer>> sameItems = new ArrayList<>();
sameItemsByValue.forEach((k, v) -> sameItems.add(new Tuple2<>(k, v)));
sameItems.sort(Comparator.comparing(o -> o.second));
Collections.reverse(sameItems);
String majority = sameItems.get(0).first;
if (!majority.equals(value)) {
int majorityAsInt = Integer.parseInt(majority);
int valueAsInt = Integer.parseInt(value);
int diff = Math.abs(majorityAsInt - valueAsInt);
if (diff >= alertTrigger) {
deviationSeverity = DeviationSeverity.ALERT;
} else if (diff >= warnTrigger) {
deviationSeverity = DeviationSeverity.WARN;
} else {
deviationSeverity = DeviationSeverity.OK;
}
}
}
return deviationSeverity;
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.model;
public class DeviationByPercentage implements DeviationType {
private final double lowerAlertTrigger;
private final double upperAlertTrigger;
private final double lowerWarnTrigger;
private final double upperWarnTrigger;
public DeviationByPercentage(double lowerAlertTrigger,
double upperAlertTrigger,
double lowerWarnTrigger,
double upperWarnTrigger) {
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

@ -0,0 +1,75 @@
/*
* 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.model;
import bisq.common.util.Tuple2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
public class DeviationOfHashes implements DeviationType {
public DeviationSeverity getDeviationSeverity(Collection<List<RequestInfo>> collection,
@Nullable String value,
InventoryItem inventoryItem,
String currentBlockHeight) {
DeviationSeverity deviationSeverity = DeviationSeverity.OK;
if (value == null) {
return deviationSeverity;
}
Map<String, Integer> sameHashesPerHashListByHash = new HashMap<>();
collection.stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1)) // We use last item only
.map(RequestInfo::getInventory)
.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);
sameHashesPerHashListByHash.put(v, prev + 1);
});
if (sameHashesPerHashListByHash.size() > 1) {
List<Tuple2<String, Integer>> sameHashesPerHashList = new ArrayList<>();
sameHashesPerHashListByHash.forEach((k, v) -> sameHashesPerHashList.add(new Tuple2<>(k, v)));
sameHashesPerHashList.sort(Comparator.comparing(o -> o.second));
Collections.reverse(sameHashesPerHashList);
// It could be that first and any following list entry has same number of hashes, but we ignore that as
// it is reason enough to alert the operators in case not all hashes are the same.
if (sameHashesPerHashList.get(0).first.equals(value)) {
// We are in the majority group.
// We also set a warning to make sure the operators act quickly and to check if there are
// more severe issues.
deviationSeverity = DeviationSeverity.WARN;
} else {
deviationSeverity = DeviationSeverity.ALERT;
}
}
return deviationSeverity;
}
}

View File

@ -15,7 +15,7 @@
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.network.p2p.inventory;
package bisq.core.network.p2p.inventory.model;
public enum DeviationSeverity {
OK,

View File

@ -0,0 +1,21 @@
/*
* 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.model;
public interface DeviationType {
}

View File

@ -0,0 +1,178 @@
/*
* 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.model;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
public enum InventoryItem {
// Percentage deviation
OfferPayload("OfferPayload",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
MailboxStoragePayload("MailboxStoragePayload",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
TradeStatistics3("MailboxStoragePayload",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
AccountAgeWitness("MailboxStoragePayload",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
SignedWitness("MailboxStoragePayload",
true,
new DeviationByPercentage(0.9, 1.1, 0.95, 1.05)),
// Should be same value
Alert("Alert",
true,
new DeviationByIntegerDiff(1, 1)),
Filter("Filter",
true,
new DeviationByIntegerDiff(1, 1)),
Mediator("Mediator",
true,
new DeviationByIntegerDiff(1, 1)),
RefundAgent("RefundAgent",
true,
new DeviationByIntegerDiff(1, 1)),
// Should be very close values
TempProposalPayload("TempProposalPayload",
true,
new DeviationByIntegerDiff(3, 5)),
ProposalPayload("ProposalPayload",
true,
new DeviationByIntegerDiff(1, 2)),
BlindVotePayload("BlindVotePayload",
true,
new DeviationByIntegerDiff(1, 2)),
// Should be very close values
daoStateChainHeight("daoStateChainHeight",
true,
new DeviationByIntegerDiff(1, 3)),
numBsqBlocks("numBsqBlocks",
true,
new DeviationByIntegerDiff(1, 3)),
// Has to be same values at same block
daoStateHash("daoStateHash",
false,
new DeviationOfHashes()),
proposalHash("proposalHash",
false,
new DeviationOfHashes()),
blindVoteHash("blindVoteHash",
false,
new DeviationOfHashes()),
// Percentage deviation
maxConnections("maxConnections",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
numConnections("numConnections",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
peakNumConnections("peakNumConnections",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
numAllConnectionsLostEvents("numAllConnectionsLostEvents",
true,
new DeviationByIntegerDiff(1, 2)),
sentBytesPerSec("sentBytesPerSec",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
receivedBytesPerSec("receivedBytesPerSec",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
receivedMessagesPerSec("receivedMessagesPerSec",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
sentMessagesPerSec("sentMessagesPerSec",
true,
new DeviationByPercentage(0.33, 3, 0.4, 2.5)),
// No deviation check
sentBytes("sentBytes", true),
receivedBytes("receivedBytes", true),
// No deviation check
version("version", false),
usedMemory("usedMemory", true),
jvmStartTime("jvmStartTime", true);
@Getter
private final String key;
@Getter
private final boolean isNumberValue;
@Getter
@Nullable
private DeviationType deviationType;
InventoryItem(String key, boolean isNumberValue) {
this.key = key;
this.isNumberValue = isNumberValue;
}
InventoryItem(String key, boolean isNumberValue, DeviationType deviationType) {
this(key, isNumberValue);
this.deviationType = deviationType;
}
@Nullable
public Double getDeviation(Map<InventoryItem, Double> averageValues, @Nullable String value) {
if (averageValues.containsKey(this) && value != null) {
double averageValue = averageValues.get(this);
return getDeviation(value, averageValue);
}
return null;
}
@Nullable
public Double getDeviation(@Nullable String value, double average) {
if (deviationType != null && value != null && average != 0 && isNumberValue) {
return Double.parseDouble(value) / average;
}
return null;
}
public DeviationSeverity getDeviationSeverity(Double deviation,
Collection<List<RequestInfo>> collection,
@Nullable String value,
String currentBlockHeight) {
if (deviationType == null || deviation == null || value == null) {
return DeviationSeverity.OK;
}
if (deviationType instanceof DeviationByPercentage) {
return ((DeviationByPercentage) deviationType).getDeviationSeverity(deviation);
} else if (deviationType instanceof DeviationByIntegerDiff) {
return ((DeviationByIntegerDiff) deviationType).getDeviationSeverity(collection, value, this);
} else if (deviationType instanceof DeviationOfHashes) {
return ((DeviationOfHashes) deviationType).getDeviationSeverity(collection, value, this, currentBlockHeight);
} else {
return DeviationSeverity.OK;
}
}
}

View File

@ -15,9 +15,7 @@
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.inventory;
import bisq.core.network.p2p.inventory.InventoryItem;
package bisq.core.network.p2p.inventory.model;
import java.util.Map;
@ -41,4 +39,16 @@ public class RequestInfo {
public RequestInfo(long requestStartTime) {
this.requestStartTime = requestStartTime;
}
public String getDisplayValue(InventoryItem inventoryItem) {
String value = getValue(inventoryItem);
return value != null ? value : "n/a";
}
@Nullable
public String getValue(InventoryItem inventoryItem) {
return inventory != null && inventory.containsKey(inventoryItem) ?
inventory.get(inventoryItem) :
null;
}
}

View File

@ -19,7 +19,9 @@ package bisq.inventory;
import bisq.core.network.p2p.inventory.GetInventoryRequestManager;
import bisq.core.network.p2p.inventory.InventoryItem;
import bisq.core.network.p2p.inventory.model.Average;
import bisq.core.network.p2p.inventory.model.InventoryItem;
import bisq.core.network.p2p.inventory.model.RequestInfo;
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
import bisq.core.proto.network.CoreNetworkProtoResolver;
@ -50,7 +52,6 @@ import org.jetbrains.annotations.Nullable;
@Slf4j
public class InventoryMonitor implements SetupListener {
private final Map<NodeAddress, JsonFileManager> jsonFileManagerByNodeAddress = new HashMap<>();
private final Map<NodeAddress, List<RequestInfo>> requestInfoListByNode = new HashMap<>();
private final File appDir;
@ -58,10 +59,16 @@ public class InventoryMonitor implements SetupListener {
private final int intervalSec;
private final NetworkNode networkNode;
private final GetInventoryRequestManager getInventoryRequestManager;
private ArrayList<NodeAddress> seedNodes;
private InventoryWebServer inventoryWebServer;
private int requestCounter = 0;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public InventoryMonitor(File appDir,
boolean useLocalhostForP2P,
BaseCurrencyNetwork network,
@ -87,10 +94,26 @@ public class InventoryMonitor implements SetupListener {
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void shutDown(Runnable shutDownCompleteHandler) {
networkNode.shutDown(shutDownCompleteHandler);
jsonFileManagerByNodeAddress.values().forEach(JsonFileManager::shutDown);
inventoryWebServer.shutDown();
}
///////////////////////////////////////////////////////////////////////////////////////////
// SetupListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onTorNodeReady() {
UserThread.runPeriodically(this::requestAllSeeds, intervalSec);
requestAllSeeds();
UserThread.runPeriodically(this::requestFromAllSeeds, intervalSec);
requestFromAllSeeds();
}
@Override
@ -105,13 +128,12 @@ public class InventoryMonitor implements SetupListener {
public void onRequestCustomBridges() {
}
public void shutDown(Runnable shutDownCompleteHandler) {
networkNode.shutDown(shutDownCompleteHandler);
jsonFileManagerByNodeAddress.values().forEach(JsonFileManager::shutDown);
inventoryWebServer.shutDown();
}
private void requestAllSeeds() {
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void requestFromAllSeeds() {
requestCounter++;
seedNodes.forEach(nodeAddress -> {
RequestInfo requestInfo = new RequestInfo(System.currentTimeMillis());
@ -121,7 +143,6 @@ public class InventoryMonitor implements SetupListener {
result -> processResponse(nodeAddress, requestInfo, result, null),
errorMessage -> processResponse(nodeAddress, requestInfo, null, errorMessage));
}).start();
});
}
@ -147,11 +168,11 @@ public class InventoryMonitor implements SetupListener {
// 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()
Set<RequestInfo> requestInfoSet = requestInfoListByNode.values().stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1))
.collect(Collectors.toSet());
Map<InventoryItem, Double> averageValues = InventoryUtil.getAverageValues(requestInfoSetOfOtherNodes);
Map<InventoryItem, Double> averageValues = Average.of(requestInfoSet);
inventoryWebServer.onNewRequestInfo(requestInfoListByNode, averageValues, requestCounter);

View File

@ -1,177 +0,0 @@
/*
* 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.DeviationSeverity;
import bisq.core.network.p2p.inventory.InventoryItem;
import bisq.network.p2p.NodeAddress;
import bisq.common.util.Tuple2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
public class InventoryUtil {
public static 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;
}
public static double getAverageFromIntegerValues(Set<RequestInfo> requestInfoSetOfOtherNodes,
InventoryItem inventoryItem) {
checkArgument(inventoryItem.getType().equals(Integer.class));
return requestInfoSetOfOtherNodes.stream()
.map(RequestInfo::getInventory)
.filter(Objects::nonNull)
.filter(inventory -> inventory.containsKey(inventoryItem))
.mapToInt(inventory -> Integer.parseInt(inventory.get(inventoryItem)))
.average()
.orElse(0d);
}
public static double getAverageFromLongValues(Set<RequestInfo> requestInfoSetOfOtherNodes,
InventoryItem inventoryItem) {
checkArgument(inventoryItem.getType().equals(Long.class));
return requestInfoSetOfOtherNodes.stream()
.map(RequestInfo::getInventory)
.filter(Objects::nonNull)
.filter(inventory -> inventory.containsKey(inventoryItem))
.mapToLong(inventory -> Long.parseLong(inventory.get(inventoryItem)))
.average()
.orElse(0d);
}
public static double getAverageFromDoubleValues(Set<RequestInfo> requestInfoSetOfOtherNodes,
InventoryItem inventoryItem) {
checkArgument(inventoryItem.getType().equals(Double.class));
return requestInfoSetOfOtherNodes.stream()
.map(RequestInfo::getInventory)
.filter(Objects::nonNull)
.filter(inventory -> inventory.containsKey(inventoryItem))
.mapToDouble(inventory -> Double.parseDouble((inventory.get(inventoryItem))))
.average()
.orElse(0d);
}
public static DeviationSeverity getDeviationSeverityByIntegerDistance(Map<NodeAddress, List<RequestInfo>> map,
RequestInfo requestInfo,
InventoryItem inventoryItem,
int warnTrigger,
int alertTrigger) {
DeviationSeverity deviationSeverity = DeviationSeverity.OK;
Map<String, Integer> sameItemsByValue = new HashMap<>();
map.values().stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1)) // We use last item only
.map(RequestInfo::getInventory)
.filter(Objects::nonNull)
.map(e -> e.get(inventoryItem))
.forEach(e -> {
sameItemsByValue.putIfAbsent(e, 0);
int prev = sameItemsByValue.get(e);
sameItemsByValue.put(e, prev + 1);
});
if (sameItemsByValue.size() > 1) {
List<Tuple2<String, Integer>> sameItems = new ArrayList<>();
sameItemsByValue.forEach((key, value) -> sameItems.add(new Tuple2<>(key, value)));
sameItems.sort(Comparator.comparing(o -> o.second));
Collections.reverse(sameItems);
String majority = sameItems.get(0).first;
Map<InventoryItem, String> inventory = requestInfo.getInventory();
if (inventory != null) {
String candidate = inventory.get(inventoryItem);
if (!majority.equals(candidate)) {
int majorityAsInt = Integer.parseInt(majority);
int candidateAsInt = Integer.parseInt(candidate);
int diff = Math.abs(majorityAsInt - candidateAsInt);
if (diff >= alertTrigger) {
deviationSeverity = DeviationSeverity.ALERT;
} else if (diff >= warnTrigger) {
deviationSeverity = DeviationSeverity.WARN;
} else {
deviationSeverity = DeviationSeverity.OK;
}
}
}
}
return deviationSeverity;
}
public static DeviationSeverity getDeviationSeverityForHash(Map<NodeAddress, List<RequestInfo>> map,
String daoStateChainHeightAsString,
RequestInfo requestInfo,
InventoryItem inventoryItem) {
DeviationSeverity deviationSeverity = DeviationSeverity.OK;
Map<String, Integer> sameHashesPerHashListByHash = new HashMap<>();
map.values().stream()
.filter(list -> !list.isEmpty())
.map(list -> list.get(list.size() - 1)) // We use last item only
.map(RequestInfo::getInventory)
.filter(Objects::nonNull)
.filter(inventory -> inventory.get(InventoryItem.daoStateChainHeight).equals(daoStateChainHeightAsString))
.map(inventory -> inventory.get(inventoryItem))
.forEach(value -> {
sameHashesPerHashListByHash.putIfAbsent(value, 0);
int prev = sameHashesPerHashListByHash.get(value);
sameHashesPerHashListByHash.put(value, prev + 1);
});
if (sameHashesPerHashListByHash.size() > 1) {
List<Tuple2<String, Integer>> sameHashesPerHashList = new ArrayList<>();
sameHashesPerHashListByHash.forEach((key, value) -> sameHashesPerHashList.add(new Tuple2<>(key, value)));
sameHashesPerHashList.sort(Comparator.comparing(o -> o.second));
Collections.reverse(sameHashesPerHashList);
// It could be that first and any following list entry has same number of hashes, but we ignore that as
// it is reason enough to alert the operators in case not all hashes are the same.
Map<InventoryItem, String> inventory = requestInfo.getInventory();
if (inventory != null) {
if (sameHashesPerHashList.get(0).first.equals(inventory.get(inventoryItem))) {
// We are in the majority group.
// We also set a warning to make sure the operators act quickly and to check if there are
// more severe issues.
deviationSeverity = DeviationSeverity.WARN;
} else {
deviationSeverity = DeviationSeverity.ALERT;
}
}
}
return deviationSeverity;
}
}

View File

@ -17,8 +17,9 @@
package bisq.inventory;
import bisq.core.network.p2p.inventory.DeviationSeverity;
import bisq.core.network.p2p.inventory.InventoryItem;
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.util.FormattingUtils;
import bisq.network.p2p.NodeAddress;
@ -28,6 +29,7 @@ import bisq.common.util.Utilities;
import java.io.BufferedReader;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -48,9 +50,15 @@ public class InventoryWebServer {
private final List<NodeAddress> seedNodes;
private final Map<String, String> operatorByNodeAddress = new HashMap<>();
private String html;
private int requestCounter;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public InventoryWebServer(int port,
List<NodeAddress> seedNodes,
BufferedReader seedNodeFile) {
@ -64,6 +72,11 @@ public class InventoryWebServer {
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void onNewRequestInfo(Map<NodeAddress, List<RequestInfo>> requestInfoListByNode,
Map<InventoryItem, Double> averageValues,
int requestCounter) {
@ -75,6 +88,11 @@ public class InventoryWebServer {
Spark.stop();
}
///////////////////////////////////////////////////////////////////////////////////////////
// HTML
///////////////////////////////////////////////////////////////////////////////////////////
private String generateHtml(Map<NodeAddress, List<RequestInfo>> map,
Map<InventoryItem, Double> averageValues) {
StringBuilder html = new StringBuilder();
@ -97,13 +115,13 @@ public class InventoryWebServer {
List<RequestInfo> list = map.get(seedNode);
int numResponses = list.size();
RequestInfo requestInfo = list.get(numResponses - 1);
html.append("<td>").append(getSeedNodeInfo(seedNode, requestInfo, averageValues)).append("</td>")
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)).append("</td>");
.append("<td>").append(getNetworkInfo(requestInfo, averageValues, map)).append("</td>");
} else {
html.append("<td>").append(getSeedNodeInfo(seedNode, null, averageValues)).append("</td>")
html.append("<td>").append(getSeedNodeInfo(seedNode, null)).append("</td>")
.append("<td>").append("n/a").append("</td>")
.append("<td>").append("n/a").append("</td>")
.append("<td>").append("n/a").append("</td>")
@ -116,9 +134,13 @@ public class InventoryWebServer {
return html.toString();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Sub sections
///////////////////////////////////////////////////////////////////////////////////////////
private String getSeedNodeInfo(NodeAddress nodeAddress,
@Nullable RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues) {
@Nullable RequestInfo requestInfo) {
StringBuilder sb = new StringBuilder();
String operator = operatorByNodeAddress.get(nodeAddress.getFullAddress());
@ -128,19 +150,21 @@ public class InventoryWebServer {
sb.append("Node address: ").append(address).append("<br/>");
if (requestInfo != null) {
addInventoryItem("Version: ", requestInfo, sb, InventoryItem.version);
addInventoryItem("Memory used: ", requestInfo, averageValues, sb, InventoryItem.usedMemory,
value -> Utilities.readableFileSize(Long.parseLong(value)));
String jvmStartTimeString = addInventoryItem("Node started at: ",
requestInfo,
null,
sb,
InventoryItem.jvmStartTime,
value -> new Date(Long.parseLong(value)).toString());
sb.append("Version: ").append(requestInfo.getDisplayValue(InventoryItem.version)).append("<br/>");
String memory = requestInfo.getValue(InventoryItem.usedMemory);
String memoryString = memory != null ? Utilities.readableFileSize(Long.parseLong(memory)) : "n/a";
sb.append("Memory used: ")
.append(memoryString)
.append("<br/>");
String duration = jvmStartTimeString != null ?
FormattingUtils.formatDurationAsWords(
System.currentTimeMillis() - Long.parseLong(jvmStartTimeString),
String jvmStartTimeString = requestInfo.getValue(InventoryItem.jvmStartTime);
long jvmStartTime = jvmStartTimeString != null ? Long.parseLong(jvmStartTimeString) : 0;
sb.append("Node started at: ")
.append(new Date(jvmStartTime).toString())
.append("<br/>");
String duration = jvmStartTime > 0 ?
FormattingUtils.formatDurationAsWords(System.currentTimeMillis() - jvmStartTime,
true, true) :
"n/a";
sb.append("Run duration: ").append(duration).append("<br/>");
@ -160,20 +184,20 @@ public class InventoryWebServer {
sb.append("Number of responses: ").append(getColorTagByDeviationSeverity(deviationSeverity))
.append(numResponses).append(CLOSE_TAG);
DeviationSeverity rrtDeviationSeverity = DeviationSeverity.OK;
String rrtString = "n/a";
if (requestInfo.getResponseTime() > 0) {
long rrt = requestInfo.getResponseTime() - requestInfo.getRequestStartTime();
DeviationSeverity rrtDeviationSeverity = DeviationSeverity.OK;
if (rrt > 20_000) {
rrtDeviationSeverity = DeviationSeverity.ALERT;
} else if (rrt > 10_000) {
rrtDeviationSeverity = DeviationSeverity.WARN;
}
String rrtString = MathUtils.roundDouble(rrt / 1000d, 3) + " sec";
sb.append("Round trip time: ").append(getColorTagByDeviationSeverity(rrtDeviationSeverity))
.append(rrtString).append(CLOSE_TAG);
} else {
sb.append("Round trip time: ").append("n/a").append(CLOSE_TAG);
rrtString = MathUtils.roundDouble(rrt / 1000d, 3) + " sec";
}
sb.append("Round trip time: ").append(getColorTagByDeviationSeverity(rrtDeviationSeverity))
.append(rrtString).append(CLOSE_TAG);
Date requestStartTime = new Date(requestInfo.getRequestStartTime());
sb.append("Requested at: ").append(requestStartTime).append("<br/>");
@ -185,7 +209,7 @@ public class InventoryWebServer {
String errorMessage = requestInfo.getErrorMessage();
if (errorMessage != null && !errorMessage.isEmpty()) {
sb.append("Error message: ").append(getColorTagByDeviationSeverity(DeviationSeverity.WARN))
sb.append("Error message: ").append(getColorTagByDeviationSeverity(DeviationSeverity.ALERT))
.append(errorMessage).append(CLOSE_TAG);
}
return sb.toString();
@ -195,33 +219,18 @@ public class InventoryWebServer {
Map<InventoryItem, Double> averageValues,
Map<NodeAddress, List<RequestInfo>> map) {
StringBuilder sb = new StringBuilder();
addInventoryItem(requestInfo, averageValues, sb, InventoryItem.OfferPayload);
addInventoryItem(requestInfo, averageValues, sb, InventoryItem.MailboxStoragePayload);
addInventoryItem(requestInfo, averageValues, sb, InventoryItem.TradeStatistics3);
DeviationSeverity deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.Alert, 1, 1);
addInventoryItem(getTitle(InventoryItem.Alert), requestInfo, averageValues, sb, InventoryItem.Alert,
null, deviationSeverity);
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()));
deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.Filter, 1, 1);
addInventoryItem(getTitle(InventoryItem.Filter), requestInfo, averageValues, sb, InventoryItem.Filter,
null, deviationSeverity);
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()));
deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.Mediator, 1, 1);
addInventoryItem(getTitle(InventoryItem.Mediator), requestInfo, averageValues, sb, InventoryItem.Mediator,
null, deviationSeverity);
deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.RefundAgent, 1, 1);
addInventoryItem(getTitle(InventoryItem.RefundAgent), requestInfo, averageValues, sb, InventoryItem.RefundAgent,
null, deviationSeverity);
addInventoryItem(requestInfo, averageValues, sb, InventoryItem.AccountAgeWitness);
addInventoryItem(requestInfo, averageValues, sb, InventoryItem.SignedWitness);
return sb.toString();
}
@ -229,183 +238,133 @@ public class InventoryWebServer {
Map<InventoryItem, Double> averageValues,
Map<NodeAddress, List<RequestInfo>> map) {
StringBuilder sb = new StringBuilder();
DeviationSeverity deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.numBsqBlocks, 1, 3);
addInventoryItem("Number of BSQ blocks: ", requestInfo, averageValues, sb, InventoryItem.numBsqBlocks,
null, deviationSeverity);
deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.TempProposalPayload, 3, 5);
addInventoryItem(getTitle(InventoryItem.TempProposalPayload), requestInfo, averageValues, sb, InventoryItem.TempProposalPayload,
null, deviationSeverity);
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()));
deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.ProposalPayload, 1, 2);
addInventoryItem(getTitle(InventoryItem.ProposalPayload), requestInfo, averageValues, sb, InventoryItem.ProposalPayload,
null, deviationSeverity);
String daoStateChainHeight = null;
if (requestInfo.getInventory() != null && requestInfo.getInventory().containsKey(InventoryItem.daoStateChainHeight)) {
daoStateChainHeight = requestInfo.getInventory().get(InventoryItem.daoStateChainHeight);
}
deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.BlindVotePayload, 1, 2);
addInventoryItem(getTitle(InventoryItem.BlindVotePayload), requestInfo, averageValues, sb, InventoryItem.BlindVotePayload,
null, deviationSeverity);
deviationSeverity = InventoryUtil.getDeviationSeverityByIntegerDistance(map,
requestInfo, InventoryItem.daoStateChainHeight, 1, 3);
String daoStateChainHeightAsString = addInventoryItem("DAO state block height: ", requestInfo,
averageValues, sb, InventoryItem.daoStateChainHeight, null, deviationSeverity);
DeviationSeverity daoStateHashDeviationSeverity = InventoryUtil.getDeviationSeverityForHash(map,
daoStateChainHeightAsString,
requestInfo,
InventoryItem.daoStateHash);
addInventoryItem("DAO state hash: ", requestInfo, null, sb,
InventoryItem.daoStateHash, null, daoStateHashDeviationSeverity);
sb.append(getLine("DAO state hash: ", InventoryItem.daoStateHash, requestInfo, averageValues, map.values(), daoStateChainHeight));
// 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.
DeviationSeverity proposalHashDeviationSeverity = InventoryUtil.getDeviationSeverityForHash(map,
daoStateChainHeightAsString,
requestInfo,
InventoryItem.proposalHash);
addInventoryItem("Proposal state hash: ", requestInfo, null, sb,
InventoryItem.proposalHash, null, proposalHashDeviationSeverity);
DeviationSeverity blindVoteHashDeviationSeverity = InventoryUtil.getDeviationSeverityForHash(map,
daoStateChainHeightAsString,
requestInfo,
InventoryItem.blindVoteHash);
addInventoryItem("Blind vote state hash: ", requestInfo, null, sb,
InventoryItem.blindVoteHash, null, blindVoteHashDeviationSeverity);
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));
return sb.toString();
}
private String getNetworkInfo(RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues) {
Map<InventoryItem, Double> averageValues,
Map<NodeAddress, List<RequestInfo>> map) {
StringBuilder sb = new StringBuilder();
addInventoryItem("Max. connections: ", requestInfo, averageValues, sb, InventoryItem.maxConnections);
addInventoryItem("Number of connections: ", requestInfo, averageValues, sb, InventoryItem.numConnections);
addInventoryItem("Peak number of connections: ", requestInfo, averageValues, sb, InventoryItem.peakNumConnections);
addInventoryItem("Number of 'All connections lost' events: ", requestInfo, averageValues, sb, InventoryItem.numAllConnectionsLostEvents);
addInventoryItem("Sent messages/sec: ", requestInfo, averageValues, sb, InventoryItem.sentMessagesPerSec,
value -> String.valueOf(MathUtils.roundDouble(Double.parseDouble(value), 2)));
addInventoryItem("Received messages/sec: ", requestInfo, averageValues, sb, InventoryItem.receivedMessagesPerSec,
value -> String.valueOf(MathUtils.roundDouble(Double.parseDouble(value), 2)));
addInventoryItem("Sent kB/sec: ", requestInfo, averageValues, sb, InventoryItem.sentBytesPerSec,
value -> String.valueOf(MathUtils.roundDouble(Double.parseDouble(value) / 1024, 2)));
addInventoryItem("Received kB/sec: ", requestInfo, averageValues, sb, InventoryItem.receivedBytesPerSec,
value -> String.valueOf(MathUtils.roundDouble(Double.parseDouble(value) / 1024, 2)));
addInventoryItem("Sent data: ", requestInfo, averageValues, sb, InventoryItem.sentBytes,
value -> Utilities.readableFileSize(Long.parseLong(value)));
addInventoryItem("Received data: ", requestInfo, averageValues, sb, InventoryItem.receivedBytes,
value -> Utilities.readableFileSize(Long.parseLong(value)));
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("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))));
return sb.toString();
}
private String addInventoryItem(RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
StringBuilder sb,
InventoryItem inventoryItem) {
return addInventoryItem(getTitle(inventoryItem),
requestInfo,
averageValues,
sb,
inventoryItem);
}
private String addInventoryItem(String title,
RequestInfo requestInfo,
StringBuilder sb,
InventoryItem inventoryItem) {
return addInventoryItem(title,
requestInfo,
null,
sb,
inventoryItem);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private String addInventoryItem(String title,
RequestInfo requestInfo,
@Nullable Map<InventoryItem, Double> averageValues,
StringBuilder sb,
InventoryItem inventoryItem) {
return addInventoryItem(title,
requestInfo,
averageValues,
sb,
private String getLine(InventoryItem inventoryItem,
RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
Collection<List<RequestInfo>> collection) {
return getLine(getTitle(inventoryItem),
inventoryItem,
requestInfo,
averageValues,
collection);
}
private String getLine(String title,
InventoryItem inventoryItem,
RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
Collection<List<RequestInfo>> collection) {
return getLine(title,
inventoryItem,
requestInfo,
averageValues,
collection,
null,
null);
}
private String addInventoryItem(String title,
RequestInfo requestInfo,
@Nullable Map<InventoryItem, Double> averageValues,
StringBuilder sb,
InventoryItem inventoryItem,
@Nullable Function<String, String> formatter) {
return addInventoryItem(title,
private String getLine(String title,
InventoryItem inventoryItem,
RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
Collection<List<RequestInfo>> collection,
@Nullable String daoStateChainHeight) {
return getLine(title,
inventoryItem,
requestInfo,
averageValues,
sb,
inventoryItem,
formatter,
collection,
daoStateChainHeight,
null);
}
private String addInventoryItem(String title,
RequestInfo requestInfo,
@Nullable Map<InventoryItem, Double> averageValues,
StringBuilder sb,
InventoryItem inventoryItem,
@Nullable Function<String, String> formatter,
@Nullable DeviationSeverity deviationSeverity) {
String valueAsString = null;
String displayString = "n/a";
String deviationAsString = "";
String colorTag = getColorTagByDeviationSeverity(DeviationSeverity.OK);
Map<InventoryItem, String> inventory = requestInfo.getInventory();
if (inventory != null && inventory.containsKey(inventoryItem)) {
valueAsString = inventory.get(inventoryItem);
if (averageValues != null && averageValues.containsKey(inventoryItem)) {
double average = averageValues.get(inventoryItem);
double deviation = 0;
if (inventoryItem.getType().equals(Integer.class)) {
int value = Integer.parseInt(valueAsString);
deviation = value / average;
} else if (inventoryItem.getType().equals(Long.class)) {
long value = Long.parseLong(valueAsString);
deviation = value / average;
} else if (inventoryItem.getType().equals(Double.class)) {
double value = Double.parseDouble(valueAsString);
deviation = value / average;
}
private String getLine(String title,
InventoryItem inventoryItem,
RequestInfo requestInfo,
Map<InventoryItem, Double> averageValues,
Collection<List<RequestInfo>> collection,
@Nullable String daoStateChainHeight,
@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);
return title +
getColorTagByDeviationSeverity(deviationSeverity) +
displayValue +
getDeviationAsPercentString(deviation) +
CLOSE_TAG;
}
if (!inventoryItem.getType().equals(String.class)) {
colorTag = getColorTagByDeviationSeverity(inventoryItem.getDeviationSeverity(deviation));
deviationAsString = " (" + MathUtils.roundDouble(100 * deviation, 2) + " %)";
}
}
if (deviationSeverity != null) {
colorTag = getColorTagByDeviationSeverity(deviationSeverity);
}
// We only do formatting if we have any value
if (formatter != null) {
displayString = formatter.apply(valueAsString);
} else {
displayString = valueAsString;
}
private String getDeviationAsPercentString(@Nullable Double deviation) {
if (deviation == null) {
return "";
}
sb.append(title).append(colorTag).append(displayString).append(deviationAsString).append(CLOSE_TAG);
return valueAsString;
return " (" + MathUtils.roundDouble(100 * deviation, 2) + " %)";
}
private String getColorTagByDeviationSeverity(DeviationSeverity deviationSeverity) {
private String getColorTagByDeviationSeverity(@Nullable DeviationSeverity deviationSeverity) {
if (deviationSeverity == null) {
return "<font color=\"black\">";
}
switch (deviationSeverity) {
case WARN:
return "<font color=\"blue\">";
@ -417,6 +376,14 @@ public class InventoryWebServer {
}
}
private String getTitle(InventoryItem inventoryItem) {
return "Number of " + inventoryItem.getKey() + ": ";
}
private String getRounded(String value) {
return String.valueOf(MathUtils.roundDouble(Double.parseDouble(value), 2));
}
private void setupOperatorMap(BufferedReader seedNodeFile) {
seedNodeFile.lines().forEach(line -> {
if (!line.startsWith("#")) {
@ -427,8 +394,4 @@ public class InventoryWebServer {
}
});
}
private String getTitle(InventoryItem inventoryItem) {
return "Number of " + inventoryItem.getKey() + ": ";
}
}

View File

@ -41,9 +41,6 @@ public class SeedNode {
appSetup.start();
getInventoryRequestHandler = injector.getInstance(GetInventoryRequestHandler.class);
// TODO dev test key
getInventoryRequestHandler.addPermittedRequestersPubKey("035243719433b154a86329fd23a8389bee6369698be809f7e27f003f5b5aa9eb42");
}
public void shutDown() {