mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 18:03:12 +01:00
Merge pull request #2181 from freimair/monitor
Bisq Network Monitor: Babysteps
This commit is contained in:
commit
d013e7d8a5
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,3 +29,5 @@ desktop.ini
|
||||
*.class
|
||||
deploy
|
||||
*/releases/*
|
||||
/monitor/TorHiddenServiceStartupTimeTests/*
|
||||
/monitor/monitor-tor/*
|
||||
|
35
build.gradle
35
build.gradle
@ -34,10 +34,12 @@ configure(subprojects) {
|
||||
joptVersion = '5.0.3'
|
||||
langVersion = '3.4'
|
||||
libdohjVersion = '7be803fa'
|
||||
logbackVersion = '1.1.10'
|
||||
lombokVersion = '1.18.2'
|
||||
mockitoVersion = '2.21.0'
|
||||
powermockVersion = '2.0.0-beta.5'
|
||||
protobufVersion = '3.5.1'
|
||||
slf4jVersion = '1.7.22'
|
||||
sparkVersion = '2.5.2'
|
||||
springVersion = '4.3.6.RELEASE'
|
||||
|
||||
@ -159,9 +161,9 @@ configure(project(':common')) {
|
||||
exclude(module: 'junit')
|
||||
}
|
||||
compile "org.springframework:spring-core:$springVersion"
|
||||
compile 'org.slf4j:slf4j-api:1.7.22'
|
||||
compile 'ch.qos.logback:logback-core:1.1.10'
|
||||
compile 'ch.qos.logback:logback-classic:1.1.10'
|
||||
compile "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
compile "ch.qos.logback:logback-core:$logbackVersion"
|
||||
compile "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
compile 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
compile 'com.google.guava:guava:20.0'
|
||||
compile('com.google.inject:guice:4.1.0') {
|
||||
@ -310,14 +312,33 @@ configure(project(':desktop')) {
|
||||
|
||||
|
||||
configure(project(':monitor')) {
|
||||
mainClassName = 'bisq.monitor.MonitorMain'
|
||||
mainClassName = 'bisq.monitor.Monitor'
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile "com.sparkjava:spark-core:$sparkVersion"
|
||||
compile 'net.gpedro.integrations.slack:slack-webhook:1.1.1'
|
||||
compile "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
compile "ch.qos.logback:logback-core:$logbackVersion"
|
||||
compile "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
compile 'com.google.guava:guava:20.0'
|
||||
|
||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
|
||||
compile('com.github.JesusMcCloud.netlayer:tor.native:0.6.2') {
|
||||
exclude(module: 'slf4j-api')
|
||||
}
|
||||
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-params:5.3.2'
|
||||
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
testRuntime('org.junit.jupiter:junit-jupiter-engine:5.3.2')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
// 4. Commit the changes
|
||||
//
|
||||
// See https://github.com/signalapp/gradle-witness#using-witness for further details.
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'org.controlsfx:controlsfx:b98f1c9507c05600f80323674b33d15674926c71b0116f70085b62bdacf1e573',
|
||||
|
57
monitor/README.md
Normal file
57
monitor/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Bisq Network Monitor Node
|
||||
|
||||
The Bisq monitor node collects a set of metrics which are of interest to developers and users alike. These metrics are then made available through reporters.
|
||||
|
||||
The *Babysteps* release features these metrics:
|
||||
- Tor Startup Time: The time it takes to start Tor starting at a clean system, unpacking the shipped Tor binaries, firing up Tor until Tor is connected to the Tor network and ready to use.
|
||||
- Tor Roundtrip Time: Given a bootstrapped Tor, the roundtrip time of connecting to a hidden service is measured.
|
||||
- Tor Hidden Service Startup Time: Given a bootstrapped Tor, the time it takes to create and announce a freshly created hidden service.
|
||||
|
||||
The *Babysteps* release features these reporters:
|
||||
- A reporter that simply writes the findings to `System.err`
|
||||
- A reporter that reports the findings to a Graphite/Carbon instance using the [plaintext protocol](https://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol)
|
||||
|
||||
## Configuration
|
||||
|
||||
The *Bisq Network Monitor Node* is to be configured via a Java properties file. The location of the file is to be passed as command line parameter:
|
||||
|
||||
```
|
||||
./bisq-monitor /path/to/your/config.properties
|
||||
```
|
||||
|
||||
A sample configuration file looks like follows:
|
||||
|
||||
```
|
||||
## Each Metric is configured via a set of properties.
|
||||
##
|
||||
## The minimal set of properties required to run a Metric is:
|
||||
##
|
||||
## YourMetricName.enabled=true|false
|
||||
## YourMetricName.run.interval=10 [seconds]
|
||||
|
||||
#Edit and uncomment the lines below for your liking
|
||||
|
||||
#TorStartupTime Metric
|
||||
TorStartupTime.enabled=true
|
||||
TorStartupTime.run.interval=100
|
||||
TorStartupTime.run.socksPort=90500 # so that there is no interference with a system Tor
|
||||
|
||||
#TorRoundTripTime Metric
|
||||
TorRoundTripTime.enabled=true
|
||||
TorRoundTripTime.run.interval=100
|
||||
TorRoundTripTime.run.sampleSize=5
|
||||
TorRoundTripTime.run.hosts=http://expyuzz4wqqyqhjn.onion:80 # torproject.org hidden service
|
||||
|
||||
#TorHiddenServiceStartupTime Metric
|
||||
TorHiddenServiceStartupTime.enabled=true
|
||||
TorHiddenServiceStartupTime.run.interval=100
|
||||
TorHiddenServiceStartupTime.run.localPort=90501 # so that there is no interference with a system Tor
|
||||
TorHiddenServiceStartupTime.run.servicePort=90511 # so that there is no interference with a system Tor
|
||||
|
||||
## Reporters are configured via a set of properties as well.
|
||||
##
|
||||
## In contrast to Metrics, Reporters do not have a minimal set of properties.
|
||||
|
||||
#GraphiteReporter
|
||||
GraphiteReporter.serviceUrl=http://yourHiddenService.onion:2003
|
||||
```
|
74
monitor/src/main/java/bisq/monitor/Configurable.java
Normal file
74
monitor/src/main/java/bisq/monitor/Configurable.java
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Does some pre-computation for a configurable class.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
public abstract class Configurable {
|
||||
|
||||
protected Properties configuration = new Properties();
|
||||
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Filters all java properties starting with {@link Configurable#getName()} of
|
||||
* the class and makes them available. Does <em>NOT</em> parse the content of
|
||||
* the properties!
|
||||
* <p>
|
||||
* For example, if the implementing class sets its name (using
|
||||
* {@link Configurable#setName(String)}) to <code>MyName</code>, the list of
|
||||
* properties is scanned for properties starting with <code>MyName</code>.
|
||||
* Matching lines are made available to the class without the prefix. For
|
||||
* example, a property <code>MyName.answer=42</code> is made available as
|
||||
* <code>configuration.getProperty("answer")</code> resulting in
|
||||
* <code>42</code>.
|
||||
*
|
||||
* @param properties a set of configuration properties
|
||||
*/
|
||||
public void configure(final Properties properties) {
|
||||
// only configure the Properties which belong to us
|
||||
final Properties myProperties = new Properties();
|
||||
properties.forEach((k, v) -> {
|
||||
String key = (String) k;
|
||||
if (key.startsWith(getName()))
|
||||
myProperties.put(key.substring(key.indexOf(".") + 1), v);
|
||||
});
|
||||
|
||||
// configure all properties that belong to us
|
||||
this.configuration = myProperties;
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name used to filter through configuration properties. See
|
||||
* {@link Configurable#configure(Properties)}.
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
protected void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
155
monitor/src/main/java/bisq/monitor/Metric.java
Normal file
155
monitor/src/main/java/bisq/monitor/Metric.java
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Starts a Metric (in its own {@link Thread}), manages its properties and shuts
|
||||
* it down gracefully. Furthermore, configuration updates and execution are done
|
||||
* in a thread-save manner. Implementing classes only have to implement the
|
||||
* {@link Metric#execute()} method.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class Metric extends Configurable implements Runnable {
|
||||
|
||||
private static final String INTERVAL = "run.interval";
|
||||
private volatile boolean shutdown = false;
|
||||
|
||||
/**
|
||||
* our reporter
|
||||
*/
|
||||
protected final Reporter reporter;
|
||||
private Thread thread = new Thread();
|
||||
|
||||
/**
|
||||
* disable execution
|
||||
*/
|
||||
private void disable() {
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* enable execution
|
||||
*/
|
||||
private void enable() {
|
||||
shutdown = false;
|
||||
|
||||
thread = new Thread(this);
|
||||
|
||||
// set human readable name
|
||||
thread.setName(getName());
|
||||
|
||||
// set as daemon, so that the jvm does not terminate the thread
|
||||
thread.setDaemon(true);
|
||||
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected Metric(Reporter reporter) {
|
||||
|
||||
this.reporter = reporter;
|
||||
|
||||
setName(this.getClass().getSimpleName());
|
||||
|
||||
// disable by default
|
||||
disable();
|
||||
}
|
||||
|
||||
protected boolean enabled() {
|
||||
return !shutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(final Properties properties) {
|
||||
synchronized (this) {
|
||||
log.info("{} (re)loading config...", getName());
|
||||
super.configure(properties);
|
||||
reporter.configure(properties);
|
||||
|
||||
// decide whether to enable or disable the task
|
||||
if (configuration.isEmpty() || !configuration.getProperty("enabled", "false").equals("true")
|
||||
|| !configuration.containsKey(INTERVAL)) {
|
||||
disable();
|
||||
|
||||
// some informative log output
|
||||
if (configuration.isEmpty())
|
||||
log.error("{} is not configured at all. Will not run.", getName());
|
||||
else if (!configuration.getProperty("enabled", "false").equals("true"))
|
||||
log.info("{} is deactivated. Will not run.", getName());
|
||||
else if (!configuration.containsKey(INTERVAL))
|
||||
log.error("{} is missing mandatory '" + INTERVAL + "' property. Will not run.", getName());
|
||||
else
|
||||
log.error("{} is mis-configured. Will not run.", getName());
|
||||
} else if (!enabled() && configuration.getProperty("enabled", "false").equals("true")) {
|
||||
// check if this Metric got activated after being disabled.
|
||||
// if so, resume execution
|
||||
enable();
|
||||
log.info("{} got activated. Starting up.", getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!shutdown) {
|
||||
// if not, execute all the things
|
||||
synchronized (this) {
|
||||
execute();
|
||||
}
|
||||
|
||||
// make sure our configuration is not changed in the moment we want to query it
|
||||
String interval;
|
||||
synchronized (this) {
|
||||
interval = configuration.getProperty(INTERVAL);
|
||||
}
|
||||
|
||||
// and go to sleep for the configured amount of time.
|
||||
try {
|
||||
Thread.sleep(Long.parseLong(interval) * 1000);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
log.info("{} shutdown", getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets scheduled repeatedly.
|
||||
*/
|
||||
protected abstract void execute();
|
||||
|
||||
/**
|
||||
* Initiate graceful shutdown of the Metric.
|
||||
*/
|
||||
public void shutdown() {
|
||||
log.debug("{} shutdown requested", getName());
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
protected void join() throws InterruptedException {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
}
|
@ -17,28 +17,142 @@
|
||||
|
||||
package bisq.monitor;
|
||||
|
||||
import bisq.monitor.metrics.MetricsModel;
|
||||
import bisq.monitor.metric.TorHiddenServiceStartupTime;
|
||||
import bisq.monitor.metric.TorRoundTripTime;
|
||||
import bisq.monitor.metric.TorStartupTime;
|
||||
import bisq.monitor.reporter.GraphiteReporter;
|
||||
|
||||
import com.google.inject.Injector;
|
||||
import org.berndpruenster.netlayer.tor.NativeTor;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import sun.misc.Signal;
|
||||
|
||||
/**
|
||||
* Monitor executable for the Bisq network.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
@Slf4j
|
||||
public class Monitor {
|
||||
@Setter
|
||||
private Injector injector;
|
||||
@Getter
|
||||
private MetricsModel metricsModel;
|
||||
|
||||
public Monitor() {
|
||||
private static String[] args = {};
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
Monitor.args = args;
|
||||
new Monitor().start();
|
||||
}
|
||||
|
||||
public void startApplication() {
|
||||
metricsModel = injector.getInstance(MetricsModel.class);
|
||||
/**
|
||||
* A list of all active {@link Metric}s
|
||||
*/
|
||||
private final List<Metric> metrics = new ArrayList<>();
|
||||
|
||||
MonitorAppSetup appSetup = injector.getInstance(MonitorAppSetup.class);
|
||||
appSetup.start();
|
||||
/**
|
||||
* Starts up all configured Metrics.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private void start() throws Throwable {
|
||||
// start Tor
|
||||
Tor.setDefault(new NativeTor(new File("monitor/monitor-tor"), null, null, false));
|
||||
|
||||
// assemble Metrics
|
||||
// - create reporters
|
||||
// ConsoleReporter consoleReporter = new ConsoleReporter();
|
||||
Reporter graphiteReporter = new GraphiteReporter();
|
||||
|
||||
// - add available metrics with their reporters
|
||||
metrics.add(new TorStartupTime(graphiteReporter));
|
||||
metrics.add(new TorRoundTripTime(graphiteReporter));
|
||||
metrics.add(new TorHiddenServiceStartupTime(graphiteReporter));
|
||||
|
||||
// prepare configuration reload
|
||||
// Note that this is most likely only work on Linux
|
||||
Signal.handle(new Signal("USR1"), signal -> {
|
||||
reload();
|
||||
});
|
||||
|
||||
// configure Metrics
|
||||
// - which also starts the metrics if appropriate
|
||||
Properties properties = getProperties();
|
||||
for (Metric current : metrics)
|
||||
current.configure(properties);
|
||||
|
||||
// exit Metrics gracefully on shutdown
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// set the name of the Thread for debugging purposes
|
||||
setName("shutdownHook");
|
||||
|
||||
for (Metric current : metrics) {
|
||||
current.shutdown();
|
||||
}
|
||||
|
||||
// wait for the metrics to gracefully shut down
|
||||
for (Metric current : metrics)
|
||||
try {
|
||||
current.join();
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
|
||||
log.info("shutting down tor");
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
tor.shutdown();
|
||||
|
||||
log.info("system halt");
|
||||
}
|
||||
});
|
||||
|
||||
// prevent the main thread to terminate
|
||||
log.info("joining metrics...");
|
||||
for (Metric current : metrics)
|
||||
current.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the configuration from disk.
|
||||
*/
|
||||
private void reload() {
|
||||
try {
|
||||
Properties properties = getProperties();
|
||||
for (Metric current : metrics)
|
||||
current.configure(properties);
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads a default set of properties with a file if given
|
||||
*
|
||||
* @return a set of properties
|
||||
* @throws Exception
|
||||
*/
|
||||
private Properties getProperties() throws Exception {
|
||||
Properties defaults = new Properties();
|
||||
defaults.load(Monitor.class.getClassLoader().getResourceAsStream("metrics.properties"));
|
||||
|
||||
Properties result = new Properties(defaults);
|
||||
|
||||
if (args.length > 0)
|
||||
result.load(new FileInputStream(args[0]));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,119 +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.monitor;
|
||||
|
||||
import bisq.monitor.metrics.p2p.MonitorP2PService;
|
||||
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.app.SetupUtils;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
|
||||
import bisq.network.crypto.EncryptionService;
|
||||
import bisq.network.p2p.network.SetupListener;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class MonitorAppSetup {
|
||||
private MonitorP2PService seedNodeMonitorP2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private PeerManager peerManager;
|
||||
private final KeyRing keyRing;
|
||||
private final EncryptionService encryptionService;
|
||||
|
||||
@Inject
|
||||
public MonitorAppSetup(MonitorP2PService seedNodeMonitorP2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
PeerManager peerManager,
|
||||
KeyRing keyRing,
|
||||
EncryptionService encryptionService) {
|
||||
this.seedNodeMonitorP2PService = seedNodeMonitorP2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.peerManager = peerManager;
|
||||
this.keyRing = keyRing;
|
||||
this.encryptionService = encryptionService;
|
||||
Version.setBaseCryptoNetworkId(BisqEnvironment.getBaseCurrencyNetwork().ordinal());
|
||||
Version.printVersion();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
SetupUtils.checkCryptoSetup(keyRing, encryptionService, () -> {
|
||||
initPersistedDataHosts();
|
||||
initBasicServices();
|
||||
}, throwable -> {
|
||||
log.error(throwable.getMessage());
|
||||
throwable.printStackTrace();
|
||||
System.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
public void initPersistedDataHosts() {
|
||||
ArrayList<PersistedDataHost> persistedDataHosts = new ArrayList<>();
|
||||
persistedDataHosts.add(seedNodeMonitorP2PService);
|
||||
persistedDataHosts.add(peerManager);
|
||||
|
||||
// we apply at startup the reading of persisted data but don't want to get it triggered in the constructor
|
||||
persistedDataHosts.forEach(e -> {
|
||||
try {
|
||||
log.info("call readPersisted at " + e.getClass().getSimpleName());
|
||||
e.readPersisted();
|
||||
} catch (Throwable e1) {
|
||||
log.error("readPersisted error", e1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void initBasicServices() {
|
||||
SetupUtils.readFromResources(seedNodeMonitorP2PService.getP2PDataStorage()).addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
seedNodeMonitorP2PService.start(new SetupListener() {
|
||||
|
||||
|
||||
@Override
|
||||
public void onTorNodeReady() {
|
||||
walletsSetup.initialize(null,
|
||||
() -> log.info("walletsSetup completed"),
|
||||
throwable -> log.error(throwable.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHiddenServicePublished() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetupFailed(Throwable throwable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestCustomBridges() {
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,129 +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.monitor;
|
||||
|
||||
import bisq.core.app.AppOptionKeys;
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.btc.BtcOptionKeys;
|
||||
import bisq.core.btc.UserAgent;
|
||||
import bisq.core.dao.DaoOptionKeys;
|
||||
|
||||
import bisq.network.NetworkOptionKeys;
|
||||
|
||||
import bisq.common.CommonOptionKeys;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.crypto.KeyStorage;
|
||||
import bisq.common.storage.Storage;
|
||||
|
||||
import org.springframework.core.env.JOptCommandLinePropertySource;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class MonitorEnvironment extends BisqEnvironment {
|
||||
|
||||
private String slackUrlSeedChannel = "";
|
||||
private String slackUrlBtcChannel = "";
|
||||
private String slackUrlProviderChannel = "";
|
||||
private String port;
|
||||
|
||||
public MonitorEnvironment(OptionSet options) {
|
||||
this(new JOptCommandLinePropertySource(BISQ_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull(options)));
|
||||
}
|
||||
|
||||
public MonitorEnvironment(PropertySource commandLineProperties) {
|
||||
super(commandLineProperties);
|
||||
|
||||
slackUrlSeedChannel = commandLineProperties.containsProperty(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL) ?
|
||||
(String) commandLineProperties.getProperty(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL) :
|
||||
"";
|
||||
|
||||
slackUrlBtcChannel = commandLineProperties.containsProperty(MonitorOptionKeys.SLACK_BTC_SEED_CHANNEL) ?
|
||||
(String) commandLineProperties.getProperty(MonitorOptionKeys.SLACK_BTC_SEED_CHANNEL) :
|
||||
"";
|
||||
|
||||
slackUrlProviderChannel = commandLineProperties.containsProperty(MonitorOptionKeys.SLACK_PROVIDER_SEED_CHANNEL) ?
|
||||
(String) commandLineProperties.getProperty(MonitorOptionKeys.SLACK_PROVIDER_SEED_CHANNEL) :
|
||||
"";
|
||||
|
||||
port = commandLineProperties.containsProperty(MonitorOptionKeys.PORT) ?
|
||||
(String) commandLineProperties.getProperty(MonitorOptionKeys.PORT) :
|
||||
"80";
|
||||
|
||||
// hack because defaultProperties() is called from constructor and slackUrlSeedChannel would be null there
|
||||
getPropertySources().remove("bisqDefaultProperties");
|
||||
getPropertySources().addLast(defaultPropertiesMonitor());
|
||||
}
|
||||
|
||||
protected PropertySource<?> defaultPropertiesMonitor() {
|
||||
return new PropertiesPropertySource(BISQ_DEFAULT_PROPERTY_SOURCE_NAME, new Properties() {
|
||||
{
|
||||
setProperty(CommonOptionKeys.LOG_LEVEL_KEY, logLevel);
|
||||
setProperty(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL, slackUrlSeedChannel);
|
||||
setProperty(MonitorOptionKeys.SLACK_BTC_SEED_CHANNEL, slackUrlBtcChannel);
|
||||
setProperty(MonitorOptionKeys.SLACK_PROVIDER_SEED_CHANNEL, slackUrlProviderChannel);
|
||||
setProperty(MonitorOptionKeys.PORT, port);
|
||||
|
||||
setProperty(NetworkOptionKeys.SEED_NODES_KEY, seedNodes);
|
||||
setProperty(NetworkOptionKeys.MY_ADDRESS, myAddress);
|
||||
setProperty(NetworkOptionKeys.BAN_LIST, banList);
|
||||
setProperty(NetworkOptionKeys.TOR_DIR, Paths.get(btcNetworkDir, "tor").toString());
|
||||
setProperty(NetworkOptionKeys.NETWORK_ID, String.valueOf(baseCurrencyNetwork.ordinal()));
|
||||
setProperty(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS, socks5ProxyBtcAddress);
|
||||
setProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS, socks5ProxyHttpAddress);
|
||||
|
||||
setProperty(AppOptionKeys.APP_DATA_DIR_KEY, appDataDir);
|
||||
setProperty(AppOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
|
||||
setProperty(AppOptionKeys.DUMP_STATISTICS, dumpStatistics);
|
||||
setProperty(AppOptionKeys.APP_NAME_KEY, appName);
|
||||
setProperty(AppOptionKeys.MAX_MEMORY, maxMemory);
|
||||
setProperty(AppOptionKeys.USER_DATA_DIR_KEY, userDataDir);
|
||||
setProperty(AppOptionKeys.PROVIDERS, providers);
|
||||
|
||||
setProperty(DaoOptionKeys.RPC_USER, rpcUser);
|
||||
setProperty(DaoOptionKeys.RPC_PASSWORD, rpcPassword);
|
||||
setProperty(DaoOptionKeys.RPC_PORT, rpcPort);
|
||||
setProperty(DaoOptionKeys.RPC_BLOCK_NOTIFICATION_PORT, rpcBlockNotificationPort);
|
||||
setProperty(DaoOptionKeys.DUMP_BLOCKCHAIN_DATA, dumpBlockchainData);
|
||||
setProperty(DaoOptionKeys.FULL_DAO_NODE, fullDaoNode);
|
||||
setProperty(DaoOptionKeys.GENESIS_TX_ID, genesisTxId);
|
||||
setProperty(DaoOptionKeys.GENESIS_BLOCK_HEIGHT, genesisBlockHeight);
|
||||
|
||||
setProperty(BtcOptionKeys.BTC_NODES, btcNodes);
|
||||
setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc);
|
||||
setProperty(BtcOptionKeys.WALLET_DIR, btcNetworkDir);
|
||||
setProperty(BtcOptionKeys.USER_AGENT, userAgent);
|
||||
setProperty(BtcOptionKeys.USE_ALL_PROVIDED_NODES, useAllProvidedNodes);
|
||||
setProperty(BtcOptionKeys.NUM_CONNECTIONS_FOR_BTC, numConnectionForBtc);
|
||||
|
||||
setProperty(UserAgent.NAME_KEY, appName);
|
||||
setProperty(UserAgent.VERSION_KEY, Version.VERSION);
|
||||
|
||||
setProperty(Storage.STORAGE_DIR, Paths.get(btcNetworkDir, "db").toString());
|
||||
setProperty(KeyStorage.KEY_STORAGE_DIR, Paths.get(btcNetworkDir, "keys").toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,142 +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.monitor;
|
||||
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.app.BisqExecutable;
|
||||
import bisq.core.app.misc.ExecutableForAppWithP2p;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.AppModule;
|
||||
import bisq.common.setup.CommonSetup;
|
||||
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static spark.Spark.port;
|
||||
|
||||
|
||||
|
||||
import spark.Spark;
|
||||
|
||||
@Slf4j
|
||||
public class MonitorMain extends ExecutableForAppWithP2p {
|
||||
private static final String VERSION = "1.0.1";
|
||||
private Monitor monitor;
|
||||
|
||||
public MonitorMain() {
|
||||
super("Bisq Monitor", "bisq-monitor", VERSION);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
log.info("Monitor.VERSION: " + VERSION);
|
||||
BisqEnvironment.setDefaultAppName("bisq_monitor");
|
||||
if (BisqExecutable.setupInitialOptionParser(args))
|
||||
new MonitorMain().execute(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(OptionSet options) {
|
||||
super.doExecute(options);
|
||||
|
||||
CommonSetup.setup(this);
|
||||
checkMemory(bisqEnvironment, this);
|
||||
|
||||
startHttpServer(bisqEnvironment.getProperty(MonitorOptionKeys.PORT));
|
||||
|
||||
keepRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupEnvironment(OptionSet options) {
|
||||
bisqEnvironment = new MonitorEnvironment(checkNotNull(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launchApplication() {
|
||||
UserThread.execute(() -> {
|
||||
try {
|
||||
monitor = new Monitor();
|
||||
UserThread.execute(this::onApplicationLaunched);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplicationLaunched() {
|
||||
super.onApplicationLaunched();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// We continue with a series of synchronous execution tasks
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected AppModule getModule() {
|
||||
return new MonitorModule(bisqEnvironment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyInjector() {
|
||||
super.applyInjector();
|
||||
|
||||
monitor.setInjector(injector);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startApplication() {
|
||||
monitor.startApplication();
|
||||
}
|
||||
|
||||
private void startHttpServer(String port) {
|
||||
port(Integer.parseInt(port));
|
||||
Spark.get("/", (req, res) -> {
|
||||
log.info("Incoming request from: " + req.userAgent());
|
||||
final String resultAsHtml = monitor.getMetricsModel().getResultAsHtml();
|
||||
return resultAsHtml == null ? "Still starting up..." : resultAsHtml;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeOptionParsing(OptionParser parser) {
|
||||
super.customizeOptionParsing(parser);
|
||||
|
||||
parser.accepts(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL,
|
||||
"Set slack secret for seed node monitor")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(MonitorOptionKeys.SLACK_BTC_SEED_CHANNEL,
|
||||
"Set slack secret for Btc node monitor")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(MonitorOptionKeys.SLACK_PROVIDER_SEED_CHANNEL,
|
||||
"Set slack secret for provider node monitor")
|
||||
.withRequiredArg();
|
||||
|
||||
parser.accepts(MonitorOptionKeys.PORT,
|
||||
"Set port to listen on")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("80");
|
||||
}
|
||||
}
|
@ -1,56 +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.monitor;
|
||||
|
||||
import bisq.monitor.metrics.p2p.MonitorP2PModule;
|
||||
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.app.misc.ModuleForAppWithP2p;
|
||||
|
||||
import bisq.network.p2p.P2PModule;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import static com.google.inject.name.Names.named;
|
||||
|
||||
class MonitorModule extends ModuleForAppWithP2p {
|
||||
|
||||
public MonitorModule(Environment environment) {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
super.configure();
|
||||
|
||||
bindConstant().annotatedWith(named(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL)).to(environment.getRequiredProperty(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL));
|
||||
bindConstant().annotatedWith(named(MonitorOptionKeys.SLACK_BTC_SEED_CHANNEL)).to(environment.getRequiredProperty(MonitorOptionKeys.SLACK_BTC_SEED_CHANNEL));
|
||||
bindConstant().annotatedWith(named(MonitorOptionKeys.SLACK_PROVIDER_SEED_CHANNEL)).to(environment.getRequiredProperty(MonitorOptionKeys.SLACK_PROVIDER_SEED_CHANNEL));
|
||||
bindConstant().annotatedWith(named(MonitorOptionKeys.PORT)).to(environment.getRequiredProperty(MonitorOptionKeys.PORT));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configEnvironment() {
|
||||
bind(BisqEnvironment.class).toInstance((MonitorEnvironment) environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected P2PModule p2pModule() {
|
||||
return new MonitorP2PModule(environment);
|
||||
}
|
||||
}
|
@ -1,26 +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.monitor;
|
||||
|
||||
public class MonitorOptionKeys {
|
||||
|
||||
public static final String SLACK_URL_SEED_CHANNEL = "slackUrlSeedChannel";
|
||||
public static final String SLACK_BTC_SEED_CHANNEL = "slackUrlBtcChannel";
|
||||
public static final String SLACK_PROVIDER_SEED_CHANNEL = "slackUrlProviderChannel";
|
||||
public static final String PORT = "port";
|
||||
}
|
64
monitor/src/main/java/bisq/monitor/Reporter.java
Normal file
64
monitor/src/main/java/bisq/monitor/Reporter.java
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Reports findings to a specific service/file/place using the proper means to
|
||||
* do so.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
public abstract class Reporter extends Configurable {
|
||||
|
||||
protected Reporter() {
|
||||
setName(this.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Report our findings.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public abstract void report(long value);
|
||||
|
||||
/**
|
||||
* Report our findings
|
||||
*
|
||||
* @param value
|
||||
* @param prefix
|
||||
*/
|
||||
public abstract void report(long value, String prefix);
|
||||
|
||||
/**
|
||||
* Report our findings.
|
||||
*
|
||||
* @param values Map<metric name, metric value>
|
||||
*/
|
||||
public abstract void report(Map<String, String> values);
|
||||
|
||||
/**
|
||||
* Report our findings.
|
||||
*
|
||||
* @param values Map<metric name, metric value>
|
||||
* @param prefix for example "bisq.torStartupTime"
|
||||
*/
|
||||
public abstract void report(Map<String, String> values, String prefix);
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.monitor.metric;
|
||||
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.HiddenServiceSocket;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* A Metric to measure the startup time of a Tor Hidden Service on a already
|
||||
* running Tor.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
@Slf4j
|
||||
public class TorHiddenServiceStartupTime extends Metric {
|
||||
|
||||
private static final String SERVICE_PORT = "run.servicePort";
|
||||
private static final String LOCAL_PORT = "run.localPort";
|
||||
private final String hiddenServiceDirectory = "metric_" + getName();
|
||||
|
||||
public TorHiddenServiceStartupTime(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
/**
|
||||
* synchronization helper. Required because directly closing the
|
||||
* HiddenServiceSocket in its ReadyListener causes a deadlock
|
||||
*/
|
||||
private void await() {
|
||||
synchronized (hiddenServiceDirectory) {
|
||||
try {
|
||||
hiddenServiceDirectory.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void proceed() {
|
||||
synchronized (hiddenServiceDirectory) {
|
||||
hiddenServiceDirectory.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
// prepare settings. Fetch them every time we run the Metric so we do not have to
|
||||
// restart on a config update
|
||||
int localPort = Integer.parseInt(configuration.getProperty(LOCAL_PORT, "9998"));
|
||||
int servicePort = Integer.parseInt(configuration.getProperty(SERVICE_PORT, "9999"));
|
||||
|
||||
// clear directory so we get a new onion address every time
|
||||
new File(hiddenServiceDirectory).delete();
|
||||
|
||||
log.debug("creating the hidden service");
|
||||
// start timer - we do not need System.nanoTime as we expect our result to be in
|
||||
// the range of tenth of seconds.
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
HiddenServiceSocket hiddenServiceSocket = new HiddenServiceSocket(localPort, hiddenServiceDirectory,
|
||||
servicePort);
|
||||
hiddenServiceSocket.addReadyListener(socket -> {
|
||||
// stop the timer and report
|
||||
reporter.report(System.currentTimeMillis() - start, "bisq." + getName());
|
||||
log.debug("the hidden service is ready");
|
||||
proceed();
|
||||
return null;
|
||||
});
|
||||
|
||||
await();
|
||||
log.debug("going to revoke the hidden service...");
|
||||
hiddenServiceSocket.close();
|
||||
log.debug("[going to revoke the hidden service...] done");
|
||||
}
|
||||
}
|
123
monitor/src/main/java/bisq/monitor/metric/TorRoundTripTime.java
Normal file
123
monitor/src/main/java/bisq/monitor/metric/TorRoundTripTime.java
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.monitor.metric;
|
||||
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
||||
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LongSummaryStatistics;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* A Metric to measure the round-trip time to the Bisq seed nodes via plain tor.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
public class TorRoundTripTime extends Metric {
|
||||
|
||||
private static final String SAMPLE_SIZE = "run.sampleSize";
|
||||
private static final String HOSTS = "run.hosts";
|
||||
|
||||
public TorRoundTripTime(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
SocksSocket socket;
|
||||
try {
|
||||
// fetch proxy
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
Socks5Proxy proxy = tor.getProxy();
|
||||
|
||||
// for each configured host
|
||||
for (String current : configuration.getProperty(HOSTS, "").split(",")) {
|
||||
// parse Url
|
||||
URL tmp = new URL(current);
|
||||
|
||||
List<Long> samples = new ArrayList<>();
|
||||
|
||||
while (samples.size() < Integer.parseInt(configuration.getProperty(SAMPLE_SIZE, "1"))) {
|
||||
// start timer - we do not need System.nanoTime as we expect our result to be in
|
||||
// seconds time.
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
// connect
|
||||
socket = new SocksSocket(proxy, tmp.getHost(), tmp.getPort());
|
||||
|
||||
// by the time we get here, we are connected
|
||||
samples.add(System.currentTimeMillis() - start);
|
||||
|
||||
// cleanup
|
||||
socket.close();
|
||||
}
|
||||
|
||||
// aftermath
|
||||
Collections.sort(samples);
|
||||
|
||||
// - average, max, min , sample size
|
||||
LongSummaryStatistics statistics = samples.stream().mapToLong(val -> val).summaryStatistics();
|
||||
|
||||
Map<String, String> results = new HashMap<>();
|
||||
results.put("average", String.valueOf(Math.round(statistics.getAverage())));
|
||||
results.put("max", String.valueOf(statistics.getMax()));
|
||||
results.put("min", String.valueOf(statistics.getMin()));
|
||||
results.put("sampleSize", String.valueOf(statistics.getCount()));
|
||||
|
||||
// - p25, median, p75
|
||||
Integer[] percentiles = new Integer[]{25, 50, 75};
|
||||
for (Integer percentile : percentiles) {
|
||||
double rank = statistics.getCount() * percentile / 100;
|
||||
Long percentileValue;
|
||||
if (samples.size() <= rank + 1)
|
||||
percentileValue = samples.get(samples.size() - 1);
|
||||
else if (Math.floor(rank) == rank)
|
||||
percentileValue = samples.get((int) rank);
|
||||
else
|
||||
percentileValue = Math.round(samples.get((int) Math.floor(rank))
|
||||
+ (samples.get((int) (Math.floor(rank) + 1)) - samples.get((int) Math.floor(rank)))
|
||||
/ (rank - Math.floor(rank)));
|
||||
results.put("p" + percentile, String.valueOf(percentileValue));
|
||||
}
|
||||
|
||||
// report
|
||||
reporter.report(results, "bisq." + getName());
|
||||
}
|
||||
} catch (TorCtlException | IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.monitor.metric;
|
||||
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.NativeTor;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
import org.berndpruenster.netlayer.tor.Torrc;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* A Metric to measure the deployment and startup time of the packaged Tor
|
||||
* binaries.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
public class TorStartupTime extends Metric {
|
||||
|
||||
private static final String SOCKS_PORT = "run.socksPort";
|
||||
private final File torWorkingDirectory = new File("metric_torStartupTime");
|
||||
private Torrc torOverrides;
|
||||
|
||||
public TorStartupTime(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Properties properties) {
|
||||
super.configure(properties);
|
||||
|
||||
synchronized (this) {
|
||||
LinkedHashMap<String, String> overrides = new LinkedHashMap<>();
|
||||
overrides.put("SOCKSPort", configuration.getProperty(SOCKS_PORT, "90500"));
|
||||
|
||||
try {
|
||||
torOverrides = new Torrc(overrides);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
// cleanup installation
|
||||
torWorkingDirectory.delete();
|
||||
Tor tor = null;
|
||||
// start timer - we do not need System.nanoTime as we expect our result to be in
|
||||
// tenth of seconds time.
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
tor = new NativeTor(torWorkingDirectory, null, torOverrides);
|
||||
|
||||
// stop the timer and set its timestamp
|
||||
reporter.report(System.currentTimeMillis() - start, "bisq." + getName());
|
||||
} catch (TorCtlException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// cleanup
|
||||
if (tor != null)
|
||||
tor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +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.monitor.metrics;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
public class Metrics {
|
||||
List<Long> requestDurations = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
List<Map<String, Integer>> receivedObjectsList = new ArrayList<>();
|
||||
@Setter
|
||||
long lastDataRequestTs;
|
||||
@Setter
|
||||
long lastDataResponseTs;
|
||||
@Setter
|
||||
long numRequestAttempts;
|
||||
}
|
@ -1,453 +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.monitor.metrics;
|
||||
|
||||
import bisq.monitor.MonitorOptionKeys;
|
||||
|
||||
import bisq.core.btc.nodes.BtcNodes;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.seed.SeedNodeRepository;
|
||||
|
||||
import bisq.common.util.MathUtils;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import net.gpedro.integrations.slack.SlackApi;
|
||||
import net.gpedro.integrations.slack.SlackMessage;
|
||||
|
||||
import org.bitcoinj.core.Peer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.time.DurationFormatUtils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class MetricsModel {
|
||||
private final DateFormat dateFormat = new SimpleDateFormat("MMMMM dd, HH:mm:ss");
|
||||
@Getter
|
||||
private String resultAsString;
|
||||
@Getter
|
||||
private String resultAsHtml;
|
||||
private SeedNodeRepository seedNodeRepository;
|
||||
private SlackApi slackSeedApi, slackBtcApi, slackProviderApi;
|
||||
private BtcNodes btcNodes;
|
||||
@Setter
|
||||
private long lastCheckTs;
|
||||
private long btcNodeUptimeTs;
|
||||
private int totalErrors = 0;
|
||||
private HashMap<NodeAddress, Metrics> map = new HashMap<>();
|
||||
private List<Peer> connectedPeers;
|
||||
private Map<Tuple2<BtcNodes.BtcNode, Boolean>, Integer> btcNodeDownTimeMap = new HashMap<>();
|
||||
private Map<Tuple2<BtcNodes.BtcNode, Boolean>, Integer> btcNodeUpTimeMap = new HashMap<>();
|
||||
@Getter
|
||||
private Set<NodeAddress> nodesInError = new HashSet<>();
|
||||
|
||||
@Inject
|
||||
public MetricsModel(SeedNodeRepository seedNodeRepository,
|
||||
BtcNodes btcNodes,
|
||||
WalletsSetup walletsSetup,
|
||||
@Named(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL) String slackUrlSeedChannel,
|
||||
@Named(MonitorOptionKeys.SLACK_BTC_SEED_CHANNEL) String slackUrlBtcChannel,
|
||||
@Named(MonitorOptionKeys.SLACK_PROVIDER_SEED_CHANNEL) String slackUrlProviderChannel) {
|
||||
this.seedNodeRepository = seedNodeRepository;
|
||||
this.btcNodes = btcNodes;
|
||||
if (!slackUrlSeedChannel.isEmpty())
|
||||
slackSeedApi = new SlackApi(slackUrlSeedChannel);
|
||||
if (!slackUrlBtcChannel.isEmpty())
|
||||
slackBtcApi = new SlackApi(slackUrlBtcChannel);
|
||||
if (!slackUrlProviderChannel.isEmpty())
|
||||
slackProviderApi = new SlackApi(slackUrlProviderChannel);
|
||||
|
||||
walletsSetup.connectedPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||
connectedPeers = newValue;
|
||||
});
|
||||
}
|
||||
|
||||
public void addToMap(NodeAddress nodeAddress, Metrics metrics) {
|
||||
map.put(nodeAddress, metrics);
|
||||
}
|
||||
|
||||
public Metrics getMetrics(NodeAddress nodeAddress) {
|
||||
return map.get(nodeAddress);
|
||||
}
|
||||
|
||||
public void updateReport() {
|
||||
if (btcNodeUptimeTs == 0)
|
||||
btcNodeUptimeTs = new Date().getTime();
|
||||
|
||||
Map<String, Double> accumulatedValues = new HashMap<>();
|
||||
final double[] items = {0};
|
||||
List<Map.Entry<NodeAddress, Metrics>> entryList = map.entrySet().stream()
|
||||
.sorted(Comparator.comparing(entrySet -> seedNodeRepository.getOperator(entrySet.getKey())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
totalErrors = 0;
|
||||
entryList.stream().forEach(e -> {
|
||||
totalErrors += e.getValue().errorMessages.stream().filter(s -> !s.isEmpty()).count();
|
||||
final List<Map<String, Integer>> receivedObjectsList = e.getValue().getReceivedObjectsList();
|
||||
if (!receivedObjectsList.isEmpty()) {
|
||||
items[0] += 1;
|
||||
Map<String, Integer> last = receivedObjectsList.get(receivedObjectsList.size() - 1);
|
||||
last.entrySet().stream().forEach(e2 -> {
|
||||
int accuValue = e2.getValue();
|
||||
if (accumulatedValues.containsKey(e2.getKey()))
|
||||
accuValue += accumulatedValues.get(e2.getKey());
|
||||
|
||||
accumulatedValues.put(e2.getKey(), (double) accuValue);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Map<String, Double> averageValues = new HashMap<>();
|
||||
accumulatedValues.entrySet().stream().forEach(e -> {
|
||||
averageValues.put(e.getKey(), e.getValue() / items[0]);
|
||||
});
|
||||
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
|
||||
calendar.setTimeInMillis(lastCheckTs);
|
||||
final String time = calendar.getTime().toString();
|
||||
|
||||
StringBuilder html = new StringBuilder();
|
||||
html.append("<html>" +
|
||||
"<head>" +
|
||||
"<style>table, th, td {border: 1px solid black;}</style>" +
|
||||
"</head>" +
|
||||
"<body>" +
|
||||
"<h3>")
|
||||
.append("Seed nodes in error: <b>" + totalErrors + "</b><br/>" +
|
||||
"Last check started at: " + time + "<br/></h3>" +
|
||||
"<table style=\"width:100%\">" +
|
||||
"<tr>" +
|
||||
"<th align=\"left\">Operator</th>" +
|
||||
"<th align=\"left\">Node address</th>" +
|
||||
"<th align=\"left\">Total num requests</th>" +
|
||||
"<th align=\"left\">Total num errors</th>" +
|
||||
"<th align=\"left\">Last request</th>" +
|
||||
"<th align=\"left\">Last response</th>" +
|
||||
"<th align=\"left\">RRT average</th>" +
|
||||
"<th align=\"left\">Num requests (retries)</th>" +
|
||||
"<th align=\"left\">Last error message</th>" +
|
||||
"<th align=\"left\">Last data</th>" +
|
||||
"<th align=\"left\">Data deviation last request</th>" +
|
||||
"</tr>");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Seed nodes in error:" + totalErrors);
|
||||
sb.append("\nLast check started at: " + time + "\n");
|
||||
|
||||
entryList.forEach(e -> {
|
||||
final Metrics metrics = e.getValue();
|
||||
final List<Long> allDurations = metrics.getRequestDurations();
|
||||
final String allDurationsString = allDurations.stream().map(Object::toString).collect(Collectors.joining("<br/>"));
|
||||
final OptionalDouble averageOptional = allDurations.stream().mapToLong(value -> value).average();
|
||||
double durationAverage = 0;
|
||||
if (averageOptional.isPresent())
|
||||
durationAverage = averageOptional.getAsDouble() / 1000;
|
||||
final NodeAddress nodeAddress = e.getKey();
|
||||
final String operator = seedNodeRepository.getOperator(nodeAddress);
|
||||
final List<String> errorMessages = metrics.getErrorMessages();
|
||||
final int numErrors = (int) errorMessages.stream().filter(s -> !s.isEmpty()).count();
|
||||
int numRequests = allDurations.size();
|
||||
String lastErrorMsg = "";
|
||||
int lastIndexOfError = 0;
|
||||
for (int i = 0; i < errorMessages.size(); i++) {
|
||||
final String msg = errorMessages.get(i);
|
||||
if (!msg.isEmpty()) {
|
||||
lastIndexOfError = i;
|
||||
lastErrorMsg = "Error at request " + lastIndexOfError + ":" + msg;
|
||||
}
|
||||
}
|
||||
// String lastErrorMsg = numErrors > 0 ? errorMessages.get(errorMessages.size() - 1) : "";
|
||||
final List<Map<String, Integer>> allReceivedData = metrics.getReceivedObjectsList();
|
||||
Map<String, Integer> lastReceivedData = !allReceivedData.isEmpty() ? allReceivedData.get(allReceivedData.size() - 1) : new HashMap<>();
|
||||
final String lastReceivedDataString = lastReceivedData.entrySet().stream().map(Object::toString).collect(Collectors.joining("<br/>"));
|
||||
final String allReceivedDataString = allReceivedData.stream().map(Object::toString).collect(Collectors.joining("<br/>"));
|
||||
final String requestTs = metrics.getLastDataRequestTs() > 0 ? dateFormat.format(new Date(metrics.getLastDataRequestTs())) : "" + "<br/>";
|
||||
final String responseTs = metrics.getLastDataResponseTs() > 0 ? dateFormat.format(new Date(metrics.getLastDataResponseTs())) : "" + "<br/>";
|
||||
final String numRequestAttempts = metrics.getNumRequestAttempts() + "<br/>";
|
||||
|
||||
sb.append("\nOperator: ").append(operator)
|
||||
.append("\nNode address: ").append(nodeAddress)
|
||||
.append("\nTotal num requests: ").append(numRequests)
|
||||
.append("\nTotal num errors: ").append(numErrors)
|
||||
.append("\nLast request: ").append(requestTs)
|
||||
.append("\nLast response: ").append(responseTs)
|
||||
.append("\nRRT average: ").append(durationAverage)
|
||||
.append("\nNum requests (retries): ").append(numRequestAttempts)
|
||||
.append("\nLast error message: ").append(lastErrorMsg)
|
||||
.append("\nLast data: ").append(lastReceivedDataString);
|
||||
|
||||
String colorNumErrors = lastIndexOfError == numErrors ? "black" : "red";
|
||||
String colorDurationAverage = durationAverage < 30 ? "black" : "red";
|
||||
html.append("<tr>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + operator + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + nodeAddress + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + numRequests + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + numErrors + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + requestTs + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + responseTs + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorDurationAverage + "\">" + durationAverage + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + numRequestAttempts + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + lastErrorMsg + "</font> ").append("</td>")
|
||||
.append("<td>").append(lastReceivedDataString).append("</td><td>");
|
||||
|
||||
if (!allReceivedData.isEmpty()) {
|
||||
sb.append("\nData deviation last request:\n");
|
||||
lastReceivedData.entrySet().stream().forEach(e2 -> {
|
||||
final String dataItem = e2.getKey();
|
||||
double deviation = MathUtils.roundDouble((double) e2.getValue() / averageValues.get(dataItem) * 100, 2);
|
||||
String str = dataItem + ": " + deviation + "%";
|
||||
sb.append(str).append("\n");
|
||||
String color;
|
||||
final double devAbs = Math.abs(deviation - 100);
|
||||
if (devAbs < 5)
|
||||
color = "black";
|
||||
else if (devAbs < 10)
|
||||
color = "blue";
|
||||
else
|
||||
color = "red";
|
||||
|
||||
html.append("<font color=\"" + color + "\">" + str + "</font>").append("<br/>");
|
||||
|
||||
if (devAbs >= 20) {
|
||||
if (slackSeedApi != null)
|
||||
slackSeedApi.call(new SlackMessage("Warning: " + nodeAddress.getFullAddress(),
|
||||
"<" + seedNodeRepository.getOperator(nodeAddress) + ">" + " Your seed node delivers diverging results for " + dataItem + ". " +
|
||||
"Please check the monitoring status page at http://seedmonitor.0-2-1.net:8080/"));
|
||||
}
|
||||
});
|
||||
sb.append("Duration all requests: ").append(allDurationsString)
|
||||
.append("\nAll data: ").append(allReceivedDataString).append("\n");
|
||||
|
||||
html.append("</td></tr>");
|
||||
}
|
||||
});
|
||||
html.append("</table>");
|
||||
|
||||
// btc nodes
|
||||
sb.append("\n\n####################################\n\nBitcoin nodes\n");
|
||||
final long elapsed = new Date().getTime() - btcNodeUptimeTs;
|
||||
Set<String> connectedBtcPeers = connectedPeers.stream()
|
||||
.map(e -> {
|
||||
String hostname = e.getAddress().getHostname();
|
||||
InetAddress inetAddress = e.getAddress().getAddr();
|
||||
int port = e.getAddress().getPort();
|
||||
if (hostname != null)
|
||||
return hostname + ":" + port;
|
||||
else if (inetAddress != null)
|
||||
return inetAddress.getHostAddress() + ":" + port;
|
||||
else
|
||||
return "";
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
List<BtcNodes.BtcNode> onionBtcNodes = new ArrayList<>(btcNodes.getProvidedBtcNodes().stream()
|
||||
.filter(BtcNodes.BtcNode::hasOnionAddress)
|
||||
.collect(Collectors.toSet()));
|
||||
onionBtcNodes.sort((o1, o2) -> o1.getOperator() != null && o2.getOperator() != null ?
|
||||
o1.getOperator().compareTo(o2.getOperator()) : 0);
|
||||
|
||||
printTableHeader(html, "Onion");
|
||||
printTable(html, sb, onionBtcNodes, connectedBtcPeers, elapsed, true);
|
||||
html.append("</tr></table>");
|
||||
|
||||
List<BtcNodes.BtcNode> clearNetNodes = new ArrayList<>(btcNodes.getProvidedBtcNodes().stream()
|
||||
.filter(BtcNodes.BtcNode::hasClearNetAddress)
|
||||
.collect(Collectors.toSet()));
|
||||
clearNetNodes.sort((o1, o2) -> o1.getOperator() != null && o2.getOperator() != null ?
|
||||
o1.getOperator().compareTo(o2.getOperator()) : 0);
|
||||
|
||||
printTableHeader(html, "Clear net");
|
||||
printTable(html, sb, clearNetNodes, connectedBtcPeers, elapsed, false);
|
||||
sb.append("\nConnected Bitcoin nodes: " + connectedBtcPeers + "\n");
|
||||
html.append("</tr></table>");
|
||||
html.append("<br>Connected Bitcoin nodes: " + connectedBtcPeers + "<br>");
|
||||
btcNodeUptimeTs = new Date().getTime();
|
||||
|
||||
html.append("</body></html>");
|
||||
|
||||
resultAsString = sb.toString();
|
||||
resultAsHtml = html.toString();
|
||||
}
|
||||
|
||||
private void printTableHeader(StringBuilder html, String type) {
|
||||
html.append("<br><h3>Bitcoin " + type + " nodes<h3><table style=\"width:100%\">" +
|
||||
"<tr>" +
|
||||
"<th align=\"left\">Operator</th>" +
|
||||
"<th align=\"left\">Domain name</th>" +
|
||||
"<th align=\"left\">IP address</th>" +
|
||||
"<th align=\"left\">Btc node onion address</th>" +
|
||||
"<th align=\"left\">UpTime</th>" +
|
||||
"<th align=\"left\">DownTime</th>" +
|
||||
"</tr>");
|
||||
}
|
||||
|
||||
private void printTable(StringBuilder html, StringBuilder sb, List<BtcNodes.BtcNode> allBtcNodes, Set<String> connectedBtcPeers, long elapsed, boolean isOnion) {
|
||||
allBtcNodes.stream().forEach(node -> {
|
||||
int upTime = 0;
|
||||
int downTime = 0;
|
||||
Tuple2<BtcNodes.BtcNode, Boolean> key = new Tuple2<>(node, isOnion);
|
||||
if (btcNodeUpTimeMap.containsKey(key))
|
||||
upTime = btcNodeUpTimeMap.get(key);
|
||||
|
||||
key = new Tuple2<>(node, isOnion);
|
||||
if (btcNodeDownTimeMap.containsKey(key))
|
||||
downTime = btcNodeDownTimeMap.get(key);
|
||||
|
||||
boolean isConnected = false;
|
||||
// return !connectedBtcPeers.contains(host);
|
||||
if (node.hasOnionAddress() && connectedBtcPeers.contains(node.getOnionAddress() + ":" + node.getPort()))
|
||||
isConnected = true;
|
||||
|
||||
final String clearNetHost = node.getAddress() != null ? node.getAddress() + ":" + node.getPort() : node.getHostName() + ":" + node.getPort();
|
||||
if (node.hasClearNetAddress() && connectedBtcPeers.contains(clearNetHost))
|
||||
isConnected = true;
|
||||
|
||||
if (isConnected) {
|
||||
upTime += elapsed;
|
||||
btcNodeUpTimeMap.put(key, upTime);
|
||||
} else {
|
||||
downTime += elapsed;
|
||||
btcNodeDownTimeMap.put(key, downTime);
|
||||
}
|
||||
|
||||
String upTimeString = formatDurationAsWords(upTime, true);
|
||||
String downTimeString = formatDurationAsWords(downTime, true);
|
||||
String colorNumErrors = isConnected ? "black" : "red";
|
||||
html.append("<tr>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + node.getOperator() + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + node.getHostName() + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + node.getAddress() + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + node.getOnionAddress() + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + upTimeString + "</font> ").append("</td>")
|
||||
.append("<td>").append("<font color=\"" + colorNumErrors + "\">" + downTimeString + "</font> ").append("</td>");
|
||||
|
||||
sb.append("\nOperator: ").append(node.getOperator()).append("\n");
|
||||
sb.append("Domain name: ").append(node.getHostName()).append("\n");
|
||||
sb.append("IP address: ").append(node.getAddress()).append("\n");
|
||||
sb.append("Btc node onion address: ").append(node.getOnionAddress()).append("\n");
|
||||
sb.append("UpTime: ").append(upTimeString).append("\n");
|
||||
sb.append("DownTime: ").append(downTimeString).append("\n");
|
||||
});
|
||||
}
|
||||
|
||||
public void log() {
|
||||
log.info("\n\n#################################################################\n" +
|
||||
resultAsString +
|
||||
"#################################################################\n\n");
|
||||
}
|
||||
|
||||
public static String formatDurationAsWords(long durationMillis, boolean showSeconds) {
|
||||
String format;
|
||||
String second = Res.get("time.second");
|
||||
String minute = Res.get("time.minute");
|
||||
String hour = Res.get("time.hour").toLowerCase();
|
||||
String day = Res.get("time.day").toLowerCase();
|
||||
String days = Res.get("time.days");
|
||||
String hours = Res.get("time.hours");
|
||||
String minutes = Res.get("time.minutes");
|
||||
String seconds = Res.get("time.seconds");
|
||||
if (showSeconds) {
|
||||
format = "d\' " + days + ", \'H\' " + hours + ", \'m\' " + minutes + ", \'s\' " + seconds + "\'";
|
||||
} else
|
||||
format = "d\' " + days + ", \'H\' " + hours + ", \'m\' " + minutes + "\'";
|
||||
String duration = DurationFormatUtils.formatDuration(durationMillis, format);
|
||||
String tmp;
|
||||
duration = " " + duration;
|
||||
tmp = StringUtils.replaceOnce(duration, " 0 " + days, "");
|
||||
if (tmp.length() != duration.length()) {
|
||||
duration = tmp;
|
||||
tmp = StringUtils.replaceOnce(tmp, " 0 " + hours, "");
|
||||
if (tmp.length() != duration.length()) {
|
||||
tmp = StringUtils.replaceOnce(tmp, " 0 " + minutes, "");
|
||||
duration = tmp;
|
||||
if (tmp.length() != tmp.length()) {
|
||||
duration = StringUtils.replaceOnce(tmp, " 0 " + seconds, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (duration.length() != 0) {
|
||||
duration = duration.substring(1);
|
||||
}
|
||||
|
||||
tmp = StringUtils.replaceOnce(duration, " 0 " + seconds, "");
|
||||
|
||||
if (tmp.length() != duration.length()) {
|
||||
duration = tmp;
|
||||
tmp = StringUtils.replaceOnce(tmp, " 0 " + minutes, "");
|
||||
if (tmp.length() != duration.length()) {
|
||||
duration = tmp;
|
||||
tmp = StringUtils.replaceOnce(tmp, " 0 " + hours, "");
|
||||
if (tmp.length() != duration.length()) {
|
||||
duration = StringUtils.replaceOnce(tmp, " 0 " + days, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
duration = " " + duration;
|
||||
duration = StringUtils.replaceOnce(duration, " 1 " + seconds, " 1 " + second);
|
||||
duration = StringUtils.replaceOnce(duration, " 1 " + minutes, " 1 " + minute);
|
||||
duration = StringUtils.replaceOnce(duration, " 1 " + hours, " 1 " + hour);
|
||||
duration = StringUtils.replaceOnce(duration, " 1 " + days, " 1 " + day);
|
||||
duration = duration.trim();
|
||||
if (duration.equals(","))
|
||||
duration = duration.replace(",", "");
|
||||
if (duration.startsWith(" ,"))
|
||||
duration = duration.replace(" ,", "");
|
||||
else if (duration.startsWith(", "))
|
||||
duration = duration.replace(", ", "");
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void addNodesInError(NodeAddress nodeAddress) {
|
||||
nodesInError.add(nodeAddress);
|
||||
}
|
||||
|
||||
public void removeNodesInError(NodeAddress nodeAddress) {
|
||||
nodesInError.remove(nodeAddress);
|
||||
}
|
||||
}
|
@ -1,89 +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.monitor.metrics.p2p;
|
||||
|
||||
import bisq.monitor.metrics.MetricsModel;
|
||||
|
||||
import bisq.network.NetworkOptionKeys;
|
||||
import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.network.p2p.NetworkNodeProvider;
|
||||
import bisq.network.p2p.P2PModule;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.BanList;
|
||||
import bisq.network.p2p.peers.Broadcaster;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
import bisq.network.p2p.peers.getdata.RequestDataManager;
|
||||
import bisq.network.p2p.peers.keepalive.KeepAliveManager;
|
||||
import bisq.network.p2p.peers.peerexchange.PeerExchangeManager;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.name.Names;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static com.google.inject.name.Names.named;
|
||||
|
||||
|
||||
public class MonitorP2PModule extends P2PModule {
|
||||
|
||||
public MonitorP2PModule(Environment environment) {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(MetricsModel.class).in(Singleton.class);
|
||||
bind(MonitorP2PService.class).in(Singleton.class);
|
||||
|
||||
bind(PeerManager.class).in(Singleton.class);
|
||||
bind(P2PDataStorage.class).in(Singleton.class);
|
||||
bind(RequestDataManager.class).in(Singleton.class);
|
||||
bind(PeerExchangeManager.class).in(Singleton.class);
|
||||
bind(KeepAliveManager.class).in(Singleton.class);
|
||||
bind(Broadcaster.class).in(Singleton.class);
|
||||
bind(BanList.class).in(Singleton.class);
|
||||
bind(NetworkNode.class).toProvider(NetworkNodeProvider.class).in(Singleton.class);
|
||||
|
||||
bind(Socks5ProxyProvider.class).in(Singleton.class);
|
||||
|
||||
Boolean useLocalhostForP2P = environment.getProperty(NetworkOptionKeys.USE_LOCALHOST_FOR_P2P, boolean.class, false);
|
||||
bind(boolean.class).annotatedWith(Names.named(NetworkOptionKeys.USE_LOCALHOST_FOR_P2P)).toInstance(useLocalhostForP2P);
|
||||
|
||||
File torDir = new File(environment.getRequiredProperty(NetworkOptionKeys.TOR_DIR));
|
||||
bind(File.class).annotatedWith(named(NetworkOptionKeys.TOR_DIR)).toInstance(torDir);
|
||||
|
||||
// use a fixed port as arbitrator use that for his ID
|
||||
Integer port = environment.getProperty(NetworkOptionKeys.PORT_KEY, int.class, 9999);
|
||||
bind(int.class).annotatedWith(Names.named(NetworkOptionKeys.PORT_KEY)).toInstance(port);
|
||||
|
||||
Integer maxConnections = environment.getProperty(NetworkOptionKeys.MAX_CONNECTIONS, int.class, P2PService.MAX_CONNECTIONS_DEFAULT);
|
||||
bind(int.class).annotatedWith(Names.named(NetworkOptionKeys.MAX_CONNECTIONS)).toInstance(maxConnections);
|
||||
|
||||
Integer networkId = environment.getProperty(NetworkOptionKeys.NETWORK_ID, int.class, 1);
|
||||
bind(int.class).annotatedWith(Names.named(NetworkOptionKeys.NETWORK_ID)).toInstance(networkId);
|
||||
bindConstant().annotatedWith(named(NetworkOptionKeys.SEED_NODES_KEY)).to(environment.getRequiredProperty(NetworkOptionKeys.SEED_NODES_KEY));
|
||||
bindConstant().annotatedWith(named(NetworkOptionKeys.MY_ADDRESS)).to(environment.getRequiredProperty(NetworkOptionKeys.MY_ADDRESS));
|
||||
bindConstant().annotatedWith(named(NetworkOptionKeys.BAN_LIST)).to(environment.getRequiredProperty(NetworkOptionKeys.BAN_LIST));
|
||||
bindConstant().annotatedWith(named(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS)).to(environment.getRequiredProperty(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS));
|
||||
bindConstant().annotatedWith(named(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS)).to(environment.getRequiredProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS));
|
||||
}
|
||||
}
|
@ -1,129 +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.monitor.metrics.p2p;
|
||||
|
||||
import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.network.SetupListener;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
|
||||
import bisq.common.app.Log;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@Slf4j
|
||||
public class MonitorP2PService implements SetupListener, PersistedDataHost {
|
||||
private final NetworkNode networkNode;
|
||||
@Getter
|
||||
private final P2PDataStorage p2PDataStorage;
|
||||
private final MonitorRequestManager requestDataManager;
|
||||
private final Socks5ProxyProvider socks5ProxyProvider;
|
||||
|
||||
private SetupListener listener;
|
||||
private volatile boolean shutDownInProgress;
|
||||
private boolean shutDownComplete;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public MonitorP2PService(NetworkNode networkNode,
|
||||
P2PDataStorage p2PDataStorage,
|
||||
MonitorRequestManager requestDataManager,
|
||||
Socks5ProxyProvider socks5ProxyProvider) {
|
||||
this.networkNode = networkNode;
|
||||
this.p2PDataStorage = p2PDataStorage;
|
||||
this.requestDataManager = requestDataManager;
|
||||
this.socks5ProxyProvider = socks5ProxyProvider;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void readPersisted() {
|
||||
p2PDataStorage.readPersisted();
|
||||
}
|
||||
|
||||
public void start(SetupListener listener) {
|
||||
this.listener = listener;
|
||||
networkNode.start(this);
|
||||
}
|
||||
|
||||
public void shutDown(Runnable shutDownCompleteHandler) {
|
||||
Log.traceCall();
|
||||
if (!shutDownInProgress) {
|
||||
shutDownInProgress = true;
|
||||
|
||||
if (requestDataManager != null) {
|
||||
requestDataManager.shutDown();
|
||||
}
|
||||
|
||||
if (networkNode != null) {
|
||||
networkNode.shutDown(() -> {
|
||||
shutDownComplete = true;
|
||||
});
|
||||
} else {
|
||||
shutDownComplete = true;
|
||||
}
|
||||
} else {
|
||||
log.debug("shutDown already in progress");
|
||||
if (shutDownComplete) {
|
||||
shutDownCompleteHandler.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// SetupListener implementation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onTorNodeReady() {
|
||||
socks5ProxyProvider.setSocks5ProxyInternal(networkNode);
|
||||
listener.onTorNodeReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHiddenServicePublished() {
|
||||
checkArgument(networkNode.getNodeAddress() != null, "Address must be set when we have the hidden service ready");
|
||||
requestDataManager.start();
|
||||
listener.onHiddenServicePublished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetupFailed(Throwable throwable) {
|
||||
listener.onSetupFailed(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestCustomBridges() {
|
||||
listener.onRequestCustomBridges();
|
||||
}
|
||||
}
|
@ -1,300 +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.monitor.metrics.p2p;
|
||||
|
||||
import bisq.monitor.metrics.Metrics;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.CloseConnectionReason;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.network.MessageListener;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.peers.getdata.messages.GetDataRequest;
|
||||
import bisq.network.p2p.peers.getdata.messages.GetDataResponse;
|
||||
import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.DevEnv;
|
||||
import bisq.common.app.Log;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
import bisq.common.proto.network.NetworkPayload;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Slf4j
|
||||
class MonitorRequestHandler implements MessageListener {
|
||||
private static final long TIMEOUT = 120;
|
||||
private NodeAddress peersNodeAddress;
|
||||
private long requestTs;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public interface Listener {
|
||||
void onComplete();
|
||||
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
void onFault(String errorMessage, NodeAddress nodeAddress);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Class fields
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private final NetworkNode networkNode;
|
||||
private final P2PDataStorage dataStorage;
|
||||
private final Metrics metrics;
|
||||
private final Listener listener;
|
||||
private Timer timeoutTimer;
|
||||
private final int nonce = new Random().nextInt();
|
||||
private boolean stopped;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public MonitorRequestHandler(NetworkNode networkNode, P2PDataStorage dataStorage, Metrics metrics, Listener listener) {
|
||||
this.networkNode = networkNode;
|
||||
this.dataStorage = dataStorage;
|
||||
this.metrics = metrics;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void requestData(NodeAddress nodeAddress) {
|
||||
Log.traceCall("nodeAddress=" + nodeAddress);
|
||||
peersNodeAddress = nodeAddress;
|
||||
requestTs = new Date().getTime();
|
||||
if (!stopped) {
|
||||
Set<byte[]> excludedKeys = dataStorage.getAppendOnlyDataStoreMap().entrySet().stream()
|
||||
.map(e -> e.getKey().bytes)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
GetDataRequest getDataRequest = new PreliminaryGetDataRequest(nonce, excludedKeys);
|
||||
metrics.setLastDataRequestTs(System.currentTimeMillis());
|
||||
|
||||
if (timeoutTimer != null) {
|
||||
log.warn("timeoutTimer was already set. That must not happen.");
|
||||
timeoutTimer.stop();
|
||||
|
||||
if (DevEnv.isDevMode())
|
||||
throw new RuntimeException("timeoutTimer was already set. That must not happen.");
|
||||
}
|
||||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||
if (!stopped) {
|
||||
String errorMessage = "A timeout occurred at sending getDataRequest:" + getDataRequest +
|
||||
" on nodeAddress:" + nodeAddress;
|
||||
log.warn(errorMessage + " / RequestDataHandler=" + MonitorRequestHandler.this);
|
||||
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that timeoutTimer.run call. " +
|
||||
"Might be caused by an previous networkNode.sendMessage.onFailure.");
|
||||
}
|
||||
},
|
||||
TIMEOUT);
|
||||
|
||||
log.info("We send a PreliminaryGetDataRequest to peer {}. ", nodeAddress);
|
||||
networkNode.addMessageListener(this);
|
||||
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getDataRequest);
|
||||
Futures.addCallback(future, new FutureCallback<Connection>() {
|
||||
@Override
|
||||
public void onSuccess(Connection connection) {
|
||||
if (!stopped) {
|
||||
log.info("Send PreliminaryGetDataRequest to " + nodeAddress + " has succeeded.");
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call." +
|
||||
"Might be caused by an previous timeout.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
if (!stopped) {
|
||||
String errorMessage = "Sending getDataRequest to " + nodeAddress +
|
||||
" failed.\n\t" +
|
||||
"getDataRequest=" + getDataRequest + "." +
|
||||
"\n\tException=" + throwable.getMessage();
|
||||
log.warn(errorMessage);
|
||||
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
|
||||
} else {
|
||||
log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call. " +
|
||||
"Might be caused by an previous timeout.");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.warn("We have stopped already. We ignore that requestData call.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MessageListener implementation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onMessage(NetworkEnvelope networkEnvelop, Connection connection) {
|
||||
if (networkEnvelop instanceof GetDataResponse &&
|
||||
connection.getPeersNodeAddressOptional().isPresent() &&
|
||||
connection.getPeersNodeAddressOptional().get().equals(peersNodeAddress)) {
|
||||
Log.traceCall(networkEnvelop.toString() + "\n\tconnection=" + connection);
|
||||
if (!stopped) {
|
||||
GetDataResponse getDataResponse = (GetDataResponse) networkEnvelop;
|
||||
if (getDataResponse.getRequestNonce() == nonce) {
|
||||
stopTimeoutTimer();
|
||||
|
||||
Map<String, Set<NetworkPayload>> payloadByClassName = new HashMap<>();
|
||||
final Set<ProtectedStorageEntry> dataSet = getDataResponse.getDataSet();
|
||||
dataSet.stream().forEach(e -> {
|
||||
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
|
||||
if (protectedStoragePayload == null) {
|
||||
log.warn("StoragePayload was null: {}", networkEnvelop.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// For logging different data types
|
||||
String className = protectedStoragePayload.getClass().getSimpleName();
|
||||
if (!payloadByClassName.containsKey(className))
|
||||
payloadByClassName.put(className, new HashSet<>());
|
||||
|
||||
payloadByClassName.get(className).add(protectedStoragePayload);
|
||||
});
|
||||
|
||||
|
||||
Set<PersistableNetworkPayload> persistableNetworkPayloadSet = getDataResponse.getPersistableNetworkPayloadSet();
|
||||
if (persistableNetworkPayloadSet != null) {
|
||||
persistableNetworkPayloadSet.stream().forEach(persistableNetworkPayload -> {
|
||||
// For logging different data types
|
||||
String className = persistableNetworkPayload.getClass().getSimpleName();
|
||||
if (!payloadByClassName.containsKey(className))
|
||||
payloadByClassName.put(className, new HashSet<>());
|
||||
|
||||
payloadByClassName.get(className).add(persistableNetworkPayload);
|
||||
});
|
||||
}
|
||||
|
||||
// Log different data types
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n#################################################################\n");
|
||||
sb.append("Connected to node: ").append(peersNodeAddress.getFullAddress()).append("\n");
|
||||
final int items = dataSet.size() +
|
||||
(persistableNetworkPayloadSet != null ? persistableNetworkPayloadSet.size() : 0);
|
||||
sb.append("Received ").append(items).append(" instances\n");
|
||||
Map<String, Integer> receivedObjects = new HashMap<>();
|
||||
final boolean[] arbitratorReceived = new boolean[1];
|
||||
payloadByClassName.entrySet().stream().forEach(e -> {
|
||||
final String dataItemName = e.getKey();
|
||||
// We expect always at least an Arbitrator
|
||||
if (!arbitratorReceived[0] && dataItemName.equals("Arbitrator"))
|
||||
arbitratorReceived[0] = true;
|
||||
|
||||
sb.append(dataItemName)
|
||||
.append(": ")
|
||||
.append(e.getValue().size())
|
||||
.append("\n");
|
||||
receivedObjects.put(dataItemName, e.getValue().size());
|
||||
});
|
||||
sb.append("#################################################################");
|
||||
log.info(sb.toString());
|
||||
metrics.getReceivedObjectsList().add(receivedObjects);
|
||||
|
||||
final long duration = new Date().getTime() - requestTs;
|
||||
log.info("Requesting data took {} ms", duration);
|
||||
metrics.getRequestDurations().add(duration);
|
||||
metrics.getErrorMessages().add(arbitratorReceived[0] ? "" : "No Arbitrator objects received! Seed node need to be restarted!");
|
||||
metrics.setLastDataResponseTs(System.currentTimeMillis());
|
||||
|
||||
cleanup();
|
||||
connection.shutDown(CloseConnectionReason.CLOSE_REQUESTED_BY_PEER, listener::onComplete);
|
||||
} else {
|
||||
log.debug("Nonce not matching. That can happen rarely if we get a response after a canceled " +
|
||||
"handshake (timeout causes connection close but peer might have sent a msg before " +
|
||||
"connection was closed).\n\t" +
|
||||
"We drop that message. nonce={} / requestNonce={}",
|
||||
nonce, getDataResponse.getRequestNonce());
|
||||
}
|
||||
} else {
|
||||
log.warn("We have stopped already. We ignore that onDataRequest call.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
private void handleFault(String errorMessage, NodeAddress nodeAddress, CloseConnectionReason closeConnectionReason) {
|
||||
cleanup();
|
||||
// We do not log every error only if it fails several times in a row.
|
||||
|
||||
// We do not close the connection as it might be we have opened a new connection for that peer and
|
||||
// we don't want to close that. We do not know the connection at fault as the fault handler does not contain that,
|
||||
// so we could only search for connections for that nodeAddress but that would close an new connection attempt.
|
||||
listener.onFault(errorMessage, nodeAddress);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
Log.traceCall();
|
||||
stopped = true;
|
||||
networkNode.removeMessageListener(this);
|
||||
stopTimeoutTimer();
|
||||
}
|
||||
|
||||
private void stopTimeoutTimer() {
|
||||
if (timeoutTimer != null) {
|
||||
timeoutTimer.stop();
|
||||
timeoutTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,283 +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.monitor.metrics.p2p;
|
||||
|
||||
import bisq.monitor.MonitorOptionKeys;
|
||||
import bisq.monitor.metrics.Metrics;
|
||||
import bisq.monitor.metrics.MetricsModel;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.CloseConnectionReason;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.network.ConnectionListener;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.seed.SeedNodeRepository;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
|
||||
import net.gpedro.integrations.slack.SlackApi;
|
||||
import net.gpedro.integrations.slack.SlackMessage;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class MonitorRequestManager implements ConnectionListener {
|
||||
private static final long RETRY_DELAY_SEC = 30;
|
||||
private static final long CLEANUP_TIMER = 60;
|
||||
private static final long REQUEST_PERIOD_MIN = 10;
|
||||
private static final int MAX_RETRIES = 5;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Class fields
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private final NetworkNode networkNode;
|
||||
private final int numNodes;
|
||||
|
||||
private SlackApi slackApi;
|
||||
private P2PDataStorage dataStorage;
|
||||
private SeedNodeRepository seedNodeRepository;
|
||||
private MetricsModel metricsModel;
|
||||
private final Set<NodeAddress> seedNodeAddresses;
|
||||
|
||||
private final Map<NodeAddress, MonitorRequestHandler> handlerMap = new HashMap<>();
|
||||
private Map<NodeAddress, Timer> retryTimerMap = new HashMap<>();
|
||||
private Map<NodeAddress, Integer> retryCounterMap = new HashMap<>();
|
||||
private boolean stopped;
|
||||
private int completedRequestIndex;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public MonitorRequestManager(NetworkNode networkNode,
|
||||
P2PDataStorage dataStorage,
|
||||
SeedNodeRepository seedNodeRepository,
|
||||
MetricsModel metricsModel,
|
||||
@Named(MonitorOptionKeys.SLACK_URL_SEED_CHANNEL) String slackUrlSeedChannel) {
|
||||
this.networkNode = networkNode;
|
||||
this.dataStorage = dataStorage;
|
||||
this.seedNodeRepository = seedNodeRepository;
|
||||
this.metricsModel = metricsModel;
|
||||
|
||||
if (!slackUrlSeedChannel.isEmpty())
|
||||
slackApi = new SlackApi(slackUrlSeedChannel);
|
||||
this.networkNode.addConnectionListener(this);
|
||||
|
||||
seedNodeAddresses = new HashSet<>(seedNodeRepository.getSeedNodeAddresses());
|
||||
seedNodeAddresses.stream().forEach(nodeAddress -> metricsModel.addToMap(nodeAddress, new Metrics()));
|
||||
numNodes = seedNodeAddresses.size();
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
stopped = true;
|
||||
stopAllRetryTimers();
|
||||
networkNode.removeConnectionListener(this);
|
||||
closeAllHandlers();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void start() {
|
||||
requestAllNodes();
|
||||
UserThread.runPeriodically(this::requestAllNodes, REQUEST_PERIOD_MIN, TimeUnit.MINUTES);
|
||||
|
||||
// We want to update the data for the btc nodes more frequently
|
||||
UserThread.runPeriodically(metricsModel::updateReport, 10);
|
||||
}
|
||||
|
||||
private void requestAllNodes() {
|
||||
stopAllRetryTimers();
|
||||
closeAllConnections();
|
||||
// we give 1 sec. for all connection shutdown
|
||||
final int[] delay = {1000};
|
||||
metricsModel.setLastCheckTs(System.currentTimeMillis());
|
||||
|
||||
seedNodeAddresses.stream().forEach(nodeAddress -> {
|
||||
UserThread.runAfter(() -> requestFromNode(nodeAddress), delay[0], TimeUnit.MILLISECONDS);
|
||||
delay[0] += 100;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ConnectionListener implementation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onConnection(Connection connection) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
|
||||
closeHandler(connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RequestData
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void requestFromNode(NodeAddress nodeAddress) {
|
||||
if (!stopped) {
|
||||
if (!handlerMap.containsKey(nodeAddress)) {
|
||||
final Metrics metrics = metricsModel.getMetrics(nodeAddress);
|
||||
MonitorRequestHandler requestDataHandler = new MonitorRequestHandler(networkNode,
|
||||
dataStorage,
|
||||
metrics,
|
||||
new MonitorRequestHandler.Listener() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
log.trace("RequestDataHandshake of outbound connection complete. nodeAddress={}",
|
||||
nodeAddress);
|
||||
stopRetryTimer(nodeAddress);
|
||||
retryCounterMap.remove(nodeAddress);
|
||||
metrics.setNumRequestAttempts(retryCounterMap.getOrDefault(nodeAddress, 1));
|
||||
|
||||
// need to remove before listeners are notified as they cause the update call
|
||||
handlerMap.remove(nodeAddress);
|
||||
|
||||
metricsModel.updateReport();
|
||||
completedRequestIndex++;
|
||||
if (completedRequestIndex == numNodes)
|
||||
metricsModel.log();
|
||||
|
||||
if (metricsModel.getNodesInError().contains(nodeAddress)) {
|
||||
metricsModel.removeNodesInError(nodeAddress);
|
||||
if (slackApi != null)
|
||||
slackApi.call(new SlackMessage("Fixed: " + nodeAddress.getFullAddress(),
|
||||
"<" + seedNodeRepository.getOperator(nodeAddress) + ">" + " Your seed node is recovered."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage, NodeAddress nodeAddress) {
|
||||
handlerMap.remove(nodeAddress);
|
||||
stopRetryTimer(nodeAddress);
|
||||
|
||||
int retryCounter = retryCounterMap.getOrDefault(nodeAddress, 0);
|
||||
metrics.setNumRequestAttempts(retryCounter);
|
||||
if (retryCounter < MAX_RETRIES) {
|
||||
log.info("We got an error at peer={}. We will try again after a delay of {} sec. error={} ",
|
||||
nodeAddress, RETRY_DELAY_SEC, errorMessage);
|
||||
final Timer timer = UserThread.runAfter(() -> requestFromNode(nodeAddress), RETRY_DELAY_SEC);
|
||||
retryTimerMap.put(nodeAddress, timer);
|
||||
retryCounterMap.put(nodeAddress, ++retryCounter);
|
||||
} else {
|
||||
log.warn("We got repeated errors at peer={}. error={} ",
|
||||
nodeAddress, errorMessage);
|
||||
|
||||
metricsModel.addNodesInError(nodeAddress);
|
||||
metrics.getErrorMessages().add(errorMessage + " (" + new Date().toString() + ")");
|
||||
|
||||
metricsModel.updateReport();
|
||||
completedRequestIndex++;
|
||||
if (completedRequestIndex == numNodes)
|
||||
metricsModel.log();
|
||||
|
||||
retryCounterMap.remove(nodeAddress);
|
||||
|
||||
if (slackApi != null)
|
||||
slackApi.call(new SlackMessage("Error: " + nodeAddress.getFullAddress(),
|
||||
"<" + seedNodeRepository.getOperator(nodeAddress) + ">" + " Your seed node failed " + RETRY_DELAY_SEC + " times with error message: " + errorMessage));
|
||||
}
|
||||
}
|
||||
});
|
||||
handlerMap.put(nodeAddress, requestDataHandler);
|
||||
requestDataHandler.requestData(nodeAddress);
|
||||
} else {
|
||||
log.warn("We have started already a requestDataHandshake to peer. nodeAddress=" + nodeAddress + "\n" +
|
||||
"We start a cleanup timer if the handler has not closed by itself in between 2 minutes.");
|
||||
|
||||
UserThread.runAfter(() -> {
|
||||
if (handlerMap.containsKey(nodeAddress)) {
|
||||
MonitorRequestHandler handler = handlerMap.get(nodeAddress);
|
||||
handler.stop();
|
||||
handlerMap.remove(nodeAddress);
|
||||
}
|
||||
}, CLEANUP_TIMER);
|
||||
}
|
||||
} else {
|
||||
log.warn("We have stopped already. We ignore that requestData call.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void closeAllConnections() {
|
||||
networkNode.getAllConnections().stream().forEach(connection -> connection.shutDown(CloseConnectionReason.CLOSE_REQUESTED_BY_PEER));
|
||||
}
|
||||
|
||||
private void stopAllRetryTimers() {
|
||||
retryTimerMap.values().stream().forEach(Timer::stop);
|
||||
retryTimerMap.clear();
|
||||
|
||||
retryCounterMap.clear();
|
||||
}
|
||||
|
||||
private void stopRetryTimer(NodeAddress nodeAddress) {
|
||||
retryTimerMap.entrySet().stream()
|
||||
.filter(e -> e.getKey().equals(nodeAddress))
|
||||
.forEach(e -> e.getValue().stop());
|
||||
retryTimerMap.remove(nodeAddress);
|
||||
}
|
||||
|
||||
private void closeHandler(Connection connection) {
|
||||
Optional<NodeAddress> peersNodeAddressOptional = connection.getPeersNodeAddressOptional();
|
||||
if (peersNodeAddressOptional.isPresent()) {
|
||||
NodeAddress nodeAddress = peersNodeAddressOptional.get();
|
||||
if (handlerMap.containsKey(nodeAddress)) {
|
||||
handlerMap.get(nodeAddress).cancel();
|
||||
handlerMap.remove(nodeAddress);
|
||||
}
|
||||
} else {
|
||||
log.trace("closeRequestDataHandler: nodeAddress not set in connection " + connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAllHandlers() {
|
||||
handlerMap.values().stream().forEach(MonitorRequestHandler::cancel);
|
||||
handlerMap.clear();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.monitor.reporter;
|
||||
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A simple console reporter.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
public class ConsoleReporter extends Reporter {
|
||||
|
||||
@Override
|
||||
public void report(long value, String prefix) {
|
||||
HashMap<String, String> result = new HashMap<>();
|
||||
result.put("", String.valueOf(value));
|
||||
report(result, "bisq");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(long value) {
|
||||
HashMap<String, String> result = new HashMap<>();
|
||||
result.put("", String.valueOf(value));
|
||||
report(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values, String prefix) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
values.forEach((key, value) -> {
|
||||
String report = prefix + ("".equals(key) ? "" : (prefix.isEmpty() ? "" : ".") + key) + " " + value + " "
|
||||
+ timestamp;
|
||||
System.err.println("Report: " + report);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
report(values, "bisq");
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.monitor.reporter;
|
||||
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.TorSocket;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Reports our findings to a graphite service.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
public class GraphiteReporter extends Reporter {
|
||||
|
||||
@Override
|
||||
public void report(long value, String prefix) {
|
||||
HashMap<String, String> result = new HashMap<>();
|
||||
result.put("", String.valueOf(value));
|
||||
report(result, prefix);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(long value) {
|
||||
report(value, "bisq");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values, String prefix) {
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
values.forEach((key, value) -> {
|
||||
String report = prefix + ("".equals(key) ? "" : (prefix.isEmpty() ? "" : ".") + key) + " " + value + " "
|
||||
+ timestamp + "\n";
|
||||
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(configuration.getProperty("serviceUrl"));
|
||||
TorSocket socket = new TorSocket(url.getHost(), url.getPort());
|
||||
|
||||
socket.getOutputStream().write(report.getBytes());
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
report(values, "bisq");
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg %xEx%n)</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="TRACE">
|
||||
<appender-ref ref="CONSOLE_APPENDER"/>
|
||||
</root>
|
||||
|
||||
<logger name="bisq.common.storage.Storage" level="WARN"/>
|
||||
<logger name="bisq.common.storage.FileManager" level="WARN"/>
|
||||
<logger name="com.neemre.btcdcli4j" level="WARN"/>
|
||||
|
||||
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="INFO"/>
|
||||
|
||||
</configuration>
|
35
monitor/src/main/resources/metrics.properties
Normal file
35
monitor/src/main/resources/metrics.properties
Normal file
@ -0,0 +1,35 @@
|
||||
## Each Metric is configured via a set of properties.
|
||||
##
|
||||
## The minimal set of properties required to run a Metric is:
|
||||
##
|
||||
## YourMetricName.enabled=true|false
|
||||
## YourMetricName.run.interval=10 [seconds]
|
||||
|
||||
#Edit and uncomment the lines below for your liking
|
||||
|
||||
#TorStartupTime Metric
|
||||
TorStartupTime.enabled=true
|
||||
TorStartupTime.run.interval=100
|
||||
TorStartupTime.run.socksPort=90500
|
||||
|
||||
TorRoundTripTime.enabled=true
|
||||
TorRoundTripTime.run.interval=100
|
||||
TorRoundTripTime.run.sampleSize=3
|
||||
# torproject.org hidden service
|
||||
TorRoundTripTime.run.hosts=http://expyuzz4wqqyqhjn.onion:80
|
||||
|
||||
#TorHiddenServiceStartupTime Metric
|
||||
TorHiddenServiceStartupTime.enabled=true
|
||||
TorHiddenServiceStartupTime.run.interval=100
|
||||
TorHiddenServiceStartupTime.run.localPort=90501
|
||||
TorHiddenServiceStartupTime.run.servicePort=90511
|
||||
|
||||
#Another Metric
|
||||
Another.run.interval=5
|
||||
|
||||
## Reporters are configured via a set of properties as well.
|
||||
##
|
||||
## In contrast to Metrics, Reporters do not have a minimal set of properties.
|
||||
|
||||
#GraphiteReporter
|
||||
GraphiteReporter.serviceUrl=http://k6evlhg44acpchtc.onion:2003
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import bisq.monitor.reporter.ConsoleReporter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
public class MonitorInfrastructureTests {
|
||||
|
||||
/**
|
||||
* A dummy metric for development purposes.
|
||||
*/
|
||||
public class Dummy extends Metric {
|
||||
|
||||
public Dummy() {
|
||||
super(new ConsoleReporter());
|
||||
}
|
||||
|
||||
public boolean active() {
|
||||
return enabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "empty", "no interval", "typo" })
|
||||
public void basicConfigurationError(String configuration) {
|
||||
HashMap<String, Properties> lut = new HashMap<>();
|
||||
lut.put("empty", new Properties());
|
||||
Properties noInterval = new Properties();
|
||||
noInterval.put("Dummy.enabled", "true");
|
||||
lut.put("no interval", noInterval);
|
||||
Properties typo = new Properties();
|
||||
typo.put("Dummy.enabled", "true");
|
||||
//noinspection SpellCheckingInspection
|
||||
typo.put("Dummy.run.inteval", "1");
|
||||
lut.put("typo", typo);
|
||||
|
||||
Dummy DUT = new Dummy();
|
||||
DUT.configure(lut.get(configuration));
|
||||
Assert.assertFalse(DUT.active());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicConfigurationSuccess() throws Exception {
|
||||
Properties correct = new Properties();
|
||||
correct.put("Dummy.enabled", "true");
|
||||
correct.put("Dummy.run.interval", "1");
|
||||
|
||||
Dummy DUT = new Dummy();
|
||||
DUT.configure(correct);
|
||||
Assert.assertTrue(DUT.active());
|
||||
|
||||
// graceful shutdown
|
||||
DUT.shutdown();
|
||||
DUT.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reloadConfig() throws InterruptedException {
|
||||
// our dummy
|
||||
Dummy DUT = new Dummy();
|
||||
|
||||
// a second dummy to run as well
|
||||
Dummy DUT2 = new Dummy();
|
||||
DUT2.setName("Dummy2");
|
||||
Properties dummy2Properties = new Properties();
|
||||
dummy2Properties.put("Dummy2.enabled", "true");
|
||||
dummy2Properties.put("Dummy2.run.interval", "1");
|
||||
DUT2.configure(dummy2Properties);
|
||||
|
||||
// disable
|
||||
DUT.configure(new Properties());
|
||||
Assert.assertFalse(DUT.active());
|
||||
Assert.assertTrue(DUT2.active());
|
||||
|
||||
// enable
|
||||
Properties properties = new Properties();
|
||||
properties.put("Dummy.enabled", "true");
|
||||
properties.put("Dummy.run.interval", "1");
|
||||
DUT.configure(properties);
|
||||
Assert.assertTrue(DUT.active());
|
||||
Assert.assertTrue(DUT2.active());
|
||||
|
||||
// disable again
|
||||
DUT.configure(new Properties());
|
||||
Assert.assertFalse(DUT.active());
|
||||
Assert.assertTrue(DUT2.active());
|
||||
|
||||
// enable again
|
||||
DUT.configure(properties);
|
||||
Assert.assertTrue(DUT.active());
|
||||
Assert.assertTrue(DUT2.active());
|
||||
|
||||
// graceful shutdown
|
||||
DUT.shutdown();
|
||||
DUT.join();
|
||||
DUT2.shutdown();
|
||||
DUT2.join();
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import bisq.monitor.metric.TorHiddenServiceStartupTime;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.NativeTor;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Disabled // Ignore for normal test runs as the tests take lots of time
|
||||
public class TorHiddenServiceStartupTimeTests {
|
||||
|
||||
private final static File torWorkingDirectory = new File("monitor/" + TorHiddenServiceStartupTimeTests.class.getSimpleName());
|
||||
|
||||
/**
|
||||
* A dummy Reporter for development purposes.
|
||||
*/
|
||||
private class DummyReporter extends Reporter {
|
||||
|
||||
private long result;
|
||||
|
||||
@Override
|
||||
public void report(long value) {
|
||||
result = value;
|
||||
}
|
||||
|
||||
public long results() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
report(Long.parseLong(values.values().iterator().next()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values, String prefix) {
|
||||
report(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(long value, String prefix) {
|
||||
report(value);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws TorCtlException {
|
||||
// simulate the tor instance available to all metrics
|
||||
Tor.setDefault(new NativeTor(torWorkingDirectory));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run() throws Exception {
|
||||
DummyReporter reporter = new DummyReporter();
|
||||
|
||||
// configure
|
||||
Properties configuration = new Properties();
|
||||
configuration.put("TorHiddenServiceStartupTime.enabled", "true");
|
||||
configuration.put("TorHiddenServiceStartupTime.run.interval", "5");
|
||||
|
||||
Metric DUT = new TorHiddenServiceStartupTime(reporter);
|
||||
// start
|
||||
DUT.configure(configuration);
|
||||
|
||||
// give it some time and then stop
|
||||
Thread.sleep(180 * 1000);
|
||||
DUT.shutdown();
|
||||
|
||||
// observe results
|
||||
Assert.assertTrue(reporter.results() > 0);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
tor.shutdown();
|
||||
torWorkingDirectory.delete();
|
||||
}
|
||||
}
|
138
monitor/src/test/java/bisq/monitor/TorRoundTripTimeTests.java
Normal file
138
monitor/src/test/java/bisq/monitor/TorRoundTripTimeTests.java
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import bisq.monitor.metric.TorRoundTripTime;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.NativeTor;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Test the round trip time metric against the hidden service of tor project.org.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
@Disabled // Ignore for normal test runs as the tests take lots of time
|
||||
public class TorRoundTripTimeTests {
|
||||
|
||||
/**
|
||||
* A dummy Reporter for development purposes.
|
||||
*/
|
||||
private class DummyReporter extends Reporter {
|
||||
|
||||
private Map<String, String> results;
|
||||
|
||||
@Override
|
||||
public void report(long value) {
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
public Map<String, String> hasResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
results = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values, String prefix) {
|
||||
report(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(long value, String prefix) {
|
||||
report(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static final File workingDirectory = new File(TorRoundTripTimeTests.class.getSimpleName());
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws TorCtlException {
|
||||
// simulate the tor instance available to all metrics
|
||||
Tor.setDefault(new NativeTor(workingDirectory));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"default", "3", "4", "10"})
|
||||
public void run(String sampleSize) throws Exception {
|
||||
DummyReporter reporter = new DummyReporter();
|
||||
|
||||
// configure
|
||||
Properties configuration = new Properties();
|
||||
configuration.put("TorRoundTripTime.enabled", "true");
|
||||
configuration.put("TorRoundTripTime.run.interval", "2");
|
||||
if (!"default".equals(sampleSize))
|
||||
configuration.put("TorRoundTripTime.run.sampleSize", sampleSize);
|
||||
// torproject.org hidden service
|
||||
configuration.put("TorRoundTripTime.run.hosts", "http://expyuzz4wqqyqhjn.onion:80");
|
||||
|
||||
Metric DUT = new TorRoundTripTime(reporter);
|
||||
// start
|
||||
DUT.configure(configuration);
|
||||
|
||||
// give it some time to start and then stop
|
||||
Thread.sleep(100);
|
||||
|
||||
DUT.shutdown();
|
||||
DUT.join();
|
||||
|
||||
// observe results
|
||||
Map<String, String> results = reporter.hasResults();
|
||||
Assert.assertFalse(results.isEmpty());
|
||||
Assert.assertEquals(results.get("sampleSize"), sampleSize.equals("default") ? "1" : sampleSize);
|
||||
|
||||
Integer p25 = Integer.valueOf(results.get("p25"));
|
||||
Integer p50 = Integer.valueOf(results.get("p50"));
|
||||
Integer p75 = Integer.valueOf(results.get("p75"));
|
||||
Integer min = Integer.valueOf(results.get("min"));
|
||||
Integer max = Integer.valueOf(results.get("max"));
|
||||
Integer average = Integer.valueOf(results.get("average"));
|
||||
|
||||
Assert.assertTrue(0 < min);
|
||||
Assert.assertTrue(min <= p25 && p25 <= p50);
|
||||
Assert.assertTrue(p50 <= p75);
|
||||
Assert.assertTrue(p75 <= max);
|
||||
Assert.assertTrue(min <= average && average <= max);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
tor.shutdown();
|
||||
workingDirectory.delete();
|
||||
}
|
||||
}
|
87
monitor/src/test/java/bisq/monitor/TorStartupTimeTests.java
Normal file
87
monitor/src/test/java/bisq/monitor/TorStartupTimeTests.java
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.monitor;
|
||||
|
||||
import bisq.monitor.metric.TorStartupTime;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@Disabled // Ignore for normal test runs as the tests take lots of time
|
||||
public class TorStartupTimeTests {
|
||||
|
||||
/**
|
||||
* A dummy Reporter for development purposes.
|
||||
*/
|
||||
private class DummyReporter extends Reporter {
|
||||
|
||||
private long result;
|
||||
|
||||
@Override
|
||||
public void report(long value) {
|
||||
result = value;
|
||||
}
|
||||
|
||||
public long results() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
report(Long.parseLong(values.values().iterator().next()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values, String prefix) {
|
||||
report(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(long value, String prefix) {
|
||||
report(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run() throws Exception {
|
||||
|
||||
DummyReporter reporter = new DummyReporter();
|
||||
|
||||
// configure
|
||||
Properties configuration = new Properties();
|
||||
configuration.put("TorStartupTime.enabled", "true");
|
||||
configuration.put("TorStartupTime.run.interval", "2");
|
||||
configuration.put("TorStartupTime.run.socksPort", "9999");
|
||||
|
||||
Metric DUT = new TorStartupTime(reporter);
|
||||
// start
|
||||
DUT.configure(configuration);
|
||||
|
||||
// give it some time and then stop
|
||||
Thread.sleep(15 * 1000);
|
||||
DUT.shutdown();
|
||||
|
||||
// TODO Test fails due timing issue
|
||||
// observe results
|
||||
Assert.assertTrue(reporter.results() > 0);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user