mirror of
https://github.com/bisq-network/bisq.git
synced 2025-01-18 13:25:14 +01:00
Remove monitor module
It has been extracted to https://github.com/bisq-network/bisq-monitor Signed-off-by: HenrikJannsen <boilingfrog@gmx.com>
This commit is contained in:
parent
4ad1f4d246
commit
f55ab5e565
2
.gitignore
vendored
2
.gitignore
vendored
@ -30,8 +30,6 @@ desktop.ini
|
||||
*.class
|
||||
deploy
|
||||
*/releases/*
|
||||
/monitor/TorHiddenServiceStartupTimeTests/*
|
||||
/monitor/monitor-tor/*
|
||||
.java-version
|
||||
.localnet
|
||||
/apitest/src/main/resources/dao-setup*
|
||||
|
1
Makefile
1
Makefile
@ -37,7 +37,6 @@
|
||||
#
|
||||
# $ ls -1 bisq-*
|
||||
# bisq-desktop
|
||||
# bisq-monitor
|
||||
# bisq-relay
|
||||
# bisq-seednode
|
||||
# bisq-statsnode
|
||||
|
47
build.gradle
47
build.gradle
@ -93,7 +93,6 @@ configure(subprojects) {
|
||||
configure([project(':cli'),
|
||||
project(':daemon'),
|
||||
project(':desktop'),
|
||||
project(':monitor'),
|
||||
project(':seednode'),
|
||||
project(':statsnode'),
|
||||
project(':apitest')]) {
|
||||
@ -506,52 +505,6 @@ configure(project(':desktop')) {
|
||||
}
|
||||
|
||||
|
||||
configure(project(':monitor')) {
|
||||
apply plugin: 'org.openjfx.javafxplugin'
|
||||
|
||||
javafx {
|
||||
version = "$javafxVersion"
|
||||
modules = ['javafx.base']
|
||||
}
|
||||
|
||||
mainClassName = 'bisq.monitor.Monitor'
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':assets')
|
||||
implementation project(':common')
|
||||
implementation project(':core')
|
||||
implementation project(':p2p')
|
||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
implementation "ch.qos.logback:logback-core:$logbackVersion"
|
||||
implementation "com.google.guava:guava:$guavaVersion"
|
||||
implementation "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
implementation("com.github.bisq-network.netlayer:tor.external:$netlayerVersion") {
|
||||
exclude(module: 'slf4j-api')
|
||||
}
|
||||
implementation("com.github.bisq-network.netlayer:tor.native:$netlayerVersion") {
|
||||
exclude(module: 'slf4j-api')
|
||||
}
|
||||
implementation("com.google.inject:guice:$guiceVersion") {
|
||||
exclude(module: 'guava')
|
||||
}
|
||||
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$jupiterVersion"
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
configure(project(':seednode')) {
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
|
@ -1,246 +0,0 @@
|
||||
# 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 *Settled* 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.
|
||||
- P2P Round Trip Time: A metric hitchhiking the Ping/Pong messages of the Keep-Alive-Mechanism to determine the Round Trip Time when issuing a Ping to a seed node.
|
||||
- P2P Seed Node Message Snapshot: Get absolute number and constellation of messages a fresh Bisq client will get on startup. Also reports diffs between seed nodes on a per-message-type basis.
|
||||
- P2P Network Load: listens to the P2P network and its broadcast messages. Reports every X seconds.
|
||||
- P2P Market Statistics: a demonstration metric which extracts market information from broadcast messages. This demo implementation reports the number of open offers per market.
|
||||
|
||||
|
||||
The *Settled* 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. There is a default configuration file shipped with the monitor which reports to the one monitoring service currently up and running.
|
||||
|
||||
If you want to tweak the configuration, you can pass the location of the file as command line parameter:
|
||||
|
||||
```
|
||||
./bisq-monitor /path/to/your/config.properties
|
||||
```
|
||||
|
||||
A sample configuration file looks like follows:
|
||||
|
||||
```
|
||||
## System configuration
|
||||
|
||||
# true overwrites the reporters picked by the developers (for debugging for example) (defaults to false)
|
||||
System.useConsoleReporter=true
|
||||
|
||||
# 0 -> BTC_MAINNET, 1 -> BTC_TESTNET (default)
|
||||
System.baseCurrencyNetwork=0
|
||||
|
||||
## 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 to 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
|
||||
|
||||
#P2PRoundTripTime Metric
|
||||
P2PRoundTripTime.enabled=true
|
||||
P2PRoundTripTime.run.interval=100
|
||||
P2PRoundTripTime.run.sampleSize=5
|
||||
P2PRoundTripTime.run.hosts=723ljisnynbtdohi.onion:8000, fl3mmribyxgrv63c.onion:8000
|
||||
P2PRoundTripTime.run.torProxyPort=9060
|
||||
|
||||
#P2PNetworkLoad Metric
|
||||
P2PNetworkLoad.enabled=true
|
||||
P2PNetworkLoad.run.interval=100
|
||||
P2PNetworkLoad.run.torProxyPort=9061
|
||||
P2PNetworkLoad.run.historySize=500
|
||||
|
||||
#P2PNetworkMessageSnapshot Metric
|
||||
P2PSeedNodeSnapshot.enabled=true
|
||||
P2PSeedNodeSnapshot.run.interval=24
|
||||
P2PSeedNodeSnapshot.run.hosts=3f3cu2yw7u457ztq.onion:8000, 723ljisnynbtdohi.onion:8000, fl3mmribyxgrv63c.onion:8000
|
||||
P2PSeedNodeSnapshot.run.torProxyPort=9062
|
||||
|
||||
#P2PMarketStats Metric
|
||||
P2PMarketStats.enabled=true
|
||||
P2PMarketStats.run.interval=37
|
||||
P2PMarketStats.run.hosts=ef5qnzx6znifo3df.onion:8000
|
||||
P2PMarketStats.run.torProxyPort=9063
|
||||
|
||||
#PriceNodeStats Metric
|
||||
PriceNodeStats.enabled=true
|
||||
PriceNodeStats.run.interval=42
|
||||
PriceNodeStats.run.hosts=http://5bmpx76qllutpcyp.onion, http://xc3nh4juf2hshy7e.onion, http://44mgyoe2b6oqiytt.onion, http://62nvujg5iou3vu3i.onion, http://ceaanhbvluug4we6.onion
|
||||
|
||||
#MarketStats Metric
|
||||
MarketStats.enabled=true
|
||||
MarketStats.run.interval=191
|
||||
|
||||
## 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=k6evlhg44acpchtc.onion:2003
|
||||
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
The distribution ships with a systemd .service file. Validate/change the executable/config paths within the shipped `bisq-monitor.service` file and copy/move the file to your systemd directory (something along `/usr/lib/systemd/system/`). Now you can control your *Monitor Node* via the usual systemd start/stop commands
|
||||
|
||||
```
|
||||
systemctl start bisq-monitor.service
|
||||
systemctl stop bisq-monitor.service
|
||||
```
|
||||
and
|
||||
```
|
||||
systemctl enable bisq-monitor.service
|
||||
```
|
||||
|
||||
You can reload the configuration without restarting the service by using
|
||||
|
||||
```
|
||||
systemctl reload bisq-monitor.service
|
||||
```
|
||||
|
||||
Follow the logs created by the service by inspecting
|
||||
|
||||
```
|
||||
journalctl --unit bisq-monitor --follow
|
||||
```
|
||||
|
||||
# Monitoring Service
|
||||
|
||||
A typical monitoring service consists of a [Graphite](https://graphiteapp.org/) and a [Grafana](https://grafana.com/) instance.
|
||||
Both are available via Docker-containers.
|
||||
|
||||
## Setting up Graphite
|
||||
|
||||
### Install
|
||||
|
||||
For a docker setup, use
|
||||
|
||||
```
|
||||
docker run -d --name graphite --restart=always -p 2003:2003 -p 8080:8080 graphiteapp/graphite-statsd
|
||||
```
|
||||
|
||||
- Port 2003 is used for the [plaintext protocol](https://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol) mentioned above
|
||||
- Port 8080 offers an API for user interfaces.
|
||||
|
||||
more information can be found [here](https://graphite.readthedocs.io/en/latest/install.html)
|
||||
|
||||
### Configuration
|
||||
|
||||
For configuration, you must adapt the whisper database schema to suit your needs. First, stop your docker container by running
|
||||
|
||||
```
|
||||
docker stop graphite
|
||||
```
|
||||
|
||||
Find your config files within the `Source` directory stated in
|
||||
|
||||
```
|
||||
docker inspect graphite | grep -C 2 graphite/conf\",
|
||||
```
|
||||
|
||||
Edit `storage-schemas.conf` so that the frequency of your incoming data (configured in the monitor configs `interval`) is matched. For example, insert
|
||||
```
|
||||
[bisq]
|
||||
pattern = ^bisq.*
|
||||
retentions = 10s:1h,5m:31d,30m:2y,1h:5y
|
||||
```
|
||||
before the `[default...` blocks of the file. This basically says, that every incoming set of data reflects 5 minutes of the time series. Furthermore, every 30 minutes, the data is compressed and thus, takes less memory as it is kept for 2 years.
|
||||
|
||||
Further, edit `storage-aggregation.conf` to configure how your data is compressed. For example, insert
|
||||
```
|
||||
[bisq]
|
||||
pattern=^bisq.*
|
||||
xFilesFactor = 0
|
||||
aggregationMethod = average
|
||||
```
|
||||
before the `[default...` blocks of the file. With this configuration, whenever data is aggregated, the `average` data is made available given that at least `0%` of the data points (i.e. floor(30 / 5 * 40%) = 2 data points) exist. Otherwise, the aggregated data is dropped. Since we start the first hour with a frequency of 10s but only supply data every 4 to 6 minutes, our aggregated values would get dropped.
|
||||
|
||||
*Please note, that I have not been able to get the whole thing to work without the 10s:1h part yet*
|
||||
|
||||
Finally, update the database. For doing that, go to the storage directory of graphite, the `Source` directory stated in
|
||||
```
|
||||
docker inspect graphite | grep -C 2 graphite/conf\",
|
||||
```
|
||||
Once there, you have two options:
|
||||
- delete the whisper directory
|
||||
```
|
||||
rm -r whisper
|
||||
```
|
||||
- update the database by doing
|
||||
```
|
||||
find ./ -type f -name '*.wsp' -exec whisper-resize.py --nobackup {} 10s:1h 5m:31d 30m:2y 1h:5y \;
|
||||
```
|
||||
|
||||
and finally, restart your graphite container:
|
||||
```
|
||||
docker start graphite
|
||||
```
|
||||
|
||||
|
||||
Other than that, there is no further configuration necessary. However, you might change your iptables/firewalls to not let anyone access your Graphite instance from the outside.
|
||||
|
||||
### Backup your data
|
||||
|
||||
The metric data is kept in the `Source` directory stated in
|
||||
```
|
||||
docker inspect graphite | grep -C 2 graphite/conf\",
|
||||
```
|
||||
ready to be backed up regularly.
|
||||
|
||||
## Setting up Grafana
|
||||
|
||||
### Install
|
||||
|
||||
For a docker setup, use
|
||||
|
||||
```
|
||||
docker run -d --name=grafana -p 3000:3000 grafana/grafana
|
||||
```
|
||||
|
||||
- Port 3000 offers the web interface
|
||||
|
||||
more information can be found [here](https://grafana.com/grafana/download?platform=docker)
|
||||
|
||||
### Configuration
|
||||
|
||||
- Once you have Grafana up and running, go to the *Data Source* configuration tab.
|
||||
- Once there click *Add data source* and select *Graphite*.
|
||||
- In the HTTP section enter the IP address of your graphite docker container and the port `8080` (as we have configured before). E.g. `http://172.170.1:8080`
|
||||
- Select `Server (default)` as an *Access* method and hit *Save & Test*.
|
||||
|
||||
You should be all set. You can now proceed to add Dashboards, Panels and finally display the prettiest Graphs you can think of.
|
||||
A working connection to Graphite should let you add your data series in a *Graph*s *Metrics* tab in a pretty intuitive way.
|
||||
|
||||
- Optional: hide your Grafana instance behind a reverse proxy like nginx and add some TLS.
|
||||
- Optional: make your Grafana instance accessible via a Tor hidden service.
|
||||
|
||||
### Backup your data
|
||||
|
||||
Grafana stores every dashboard as a JSON model. This model can be accessed (copied/restored) within the dashboard's settings and its *JSON Model* tab. Do with the data whatever you want.
|
@ -1,16 +0,0 @@
|
||||
[Unit]
|
||||
Description=Bisq network monitor
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=~
|
||||
Environment="JAVA_OPTS='-Xmx500M'"
|
||||
ExecStart=/home/bisq/bisq/bisq-monitor /home/bisq/monitor.properties
|
||||
ExecReload=/bin/kill -USR1 $MAINPID
|
||||
Restart=on-failure
|
||||
|
||||
User=bisq
|
||||
Group=bisq
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,136 +0,0 @@
|
||||
Hostname "__ONION_ADDRESS__"
|
||||
Interval 30
|
||||
|
||||
LoadPlugin syslog
|
||||
<Plugin syslog>
|
||||
LogLevel info
|
||||
</Plugin>
|
||||
|
||||
LoadPlugin cpu
|
||||
LoadPlugin df
|
||||
LoadPlugin disk
|
||||
LoadPlugin fhcount
|
||||
LoadPlugin interface
|
||||
LoadPlugin java
|
||||
LoadPlugin load
|
||||
LoadPlugin memory
|
||||
LoadPlugin processes
|
||||
LoadPlugin swap
|
||||
LoadPlugin write_graphite
|
||||
|
||||
<Plugin cpu>
|
||||
ReportByCpu true
|
||||
ValuesPercentage true
|
||||
</Plugin>
|
||||
|
||||
<Plugin df>
|
||||
MountPoint "/"
|
||||
</Plugin>
|
||||
|
||||
<Plugin disk>
|
||||
Disk "/[hs]da/"
|
||||
</Plugin>
|
||||
|
||||
<Plugin fhcount>
|
||||
ValuesAbsolute false
|
||||
ValuesPercentage true
|
||||
</Plugin>
|
||||
|
||||
<Plugin interface>
|
||||
Interface "eth0"
|
||||
</Plugin>
|
||||
|
||||
<Plugin java>
|
||||
JVMArg "-verbose:jni"
|
||||
JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar:/usr/share/collectd/java/generic-jmx.jar"
|
||||
|
||||
LoadPlugin "org.collectd.java.GenericJMX"
|
||||
<Plugin "GenericJMX">
|
||||
# Generic heap/nonheap memory usage.
|
||||
<MBean "memory">
|
||||
ObjectName "java.lang:type=Memory"
|
||||
#InstanceFrom ""
|
||||
InstancePrefix "memory"
|
||||
|
||||
# Creates four values: committed, init, max, used
|
||||
<Value>
|
||||
Type "memory"
|
||||
#InstancePrefix ""
|
||||
#InstanceFrom ""
|
||||
Table true
|
||||
Attribute "HeapMemoryUsage"
|
||||
InstancePrefix "heap-"
|
||||
</Value>
|
||||
|
||||
# Creates four values: committed, init, max, used
|
||||
<Value>
|
||||
Type "memory"
|
||||
#InstancePrefix ""
|
||||
#InstanceFrom ""
|
||||
Table true
|
||||
Attribute "NonHeapMemoryUsage"
|
||||
InstancePrefix "nonheap-"
|
||||
</Value>
|
||||
</MBean>
|
||||
|
||||
# Memory usage by memory pool.
|
||||
<MBean "memory_pool">
|
||||
ObjectName "java.lang:type=MemoryPool,*"
|
||||
InstancePrefix "memory_pool-"
|
||||
InstanceFrom "name"
|
||||
|
||||
<Value>
|
||||
Type "memory"
|
||||
#InstancePrefix ""
|
||||
#InstanceFrom ""
|
||||
Table true
|
||||
Attribute "Usage"
|
||||
</Value>
|
||||
</MBean>
|
||||
|
||||
<Connection>
|
||||
ServiceURL "service:jmx:rmi:///jndi/rmi://localhost:6969/jmxrmi"
|
||||
Collect "memory_pool"
|
||||
Collect "memory"
|
||||
</Connection>
|
||||
|
||||
# See /usr/share/doc/collectd/examples/GenericJMX.conf
|
||||
# for an example config.
|
||||
</Plugin>
|
||||
</Plugin>
|
||||
|
||||
#<Plugin load>
|
||||
# ReportRelative true
|
||||
#</Plugin>
|
||||
|
||||
#<Plugin memory>
|
||||
# ValuesAbsolute true
|
||||
# ValuesPercentage false
|
||||
#</Plugin>
|
||||
|
||||
#<Plugin processes>
|
||||
# Process "name"
|
||||
# ProcessMatch "foobar" "/usr/bin/perl foobar\\.pl.*"
|
||||
#</Plugin>
|
||||
|
||||
#<Plugin swap>
|
||||
# ReportByDevice false
|
||||
# ReportBytes true
|
||||
#</Plugin>
|
||||
|
||||
<Plugin write_graphite>
|
||||
<Node "node">
|
||||
Host "127.0.0.1"
|
||||
Port "2003"
|
||||
Protocol "tcp"
|
||||
ReconnectInterval 0
|
||||
LogSendErrors false
|
||||
Prefix "servers."
|
||||
StoreRates true
|
||||
AlwaysAppendDS false
|
||||
EscapeCharacter "_"
|
||||
SeparateInstances false
|
||||
PreserveSeparator false
|
||||
DropDuplicateFields false
|
||||
</Node>
|
||||
</Plugin>
|
@ -1,75 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "[*] Bisq Server Monitoring installation script"
|
||||
|
||||
##### change paths if necessary for your system
|
||||
BISQ_REPO_URL=https://raw.githubusercontent.com/bisq-network/bisq
|
||||
BISQ_REPO_TAG=master
|
||||
ROOT_USER=root
|
||||
ROOT_GROUP=root
|
||||
ROOT_HOME=~root
|
||||
ROOT_PKG=(nginx collectd openssl)
|
||||
|
||||
SYSTEMD_ENV_HOME=/etc/default
|
||||
|
||||
#####
|
||||
|
||||
echo "[*] Gathering information"
|
||||
read -p "Please provide the onion address of your service (eg. 3f3cu2yw7u457ztq): " onionaddress
|
||||
|
||||
echo "[*] Updating apt repo sources"
|
||||
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get update -q
|
||||
|
||||
echo "[*] Upgrading OS packages"
|
||||
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get upgrade -qq -y
|
||||
|
||||
echo "[*] Installing base packages"
|
||||
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get install -qq -y ${ROOT_PKG[@]}
|
||||
|
||||
echo "[*] Preparing Bisq init script for monitoring"
|
||||
# remove stuff it it is there already
|
||||
for file in "${SYSTEMD_ENV_HOME}/bisq.env" "${SYSTEMD_ENV_HOME}/bisq-pricenode.env"
|
||||
do
|
||||
if [ -f "$file" ];then
|
||||
sudo -H -i -u "${ROOT_USER}" sed -i -e 's/-Dcom.sun.management.jmxremote //g' -e 's/-Dcom.sun.management.jmxremote.local.only=true//g' -e 's/ -Dcom.sun.management.jmxremote.host=127.0.0.1//g' -e 's/ -Dcom.sun.management.jmxremote.port=6969//g' -e 's/ -Dcom.sun.management.jmxremote.rmi.port=6969//g' -e 's/ -Dcom.sun.management.jmxremote.ssl=false//g' -e 's/ -Dcom.sun.management.jmxremote.authenticate=false//g' "${file}"
|
||||
sudo -H -i -u "${ROOT_USER}" sed -i -e '/JAVA_OPTS/ s/"$/ -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=true -Dcom.sun.management.jmxremote.host=127.0.0.1 -Dcom.sun.management.jmxremote.port=6969 -Dcom.sun.management.jmxremote.rmi.port=6969 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"/' "${file}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "[*] Seeding entropy from /dev/urandom"
|
||||
sudo -H -i -u "${ROOT_USER}" /bin/sh -c "head -1500 /dev/urandom > ${ROOT_HOME}/.rnd"
|
||||
echo "[*] Installing Nginx config"
|
||||
sudo -H -i -u "${ROOT_USER}" openssl req -x509 -nodes -newkey rsa:2048 -days 3000 -keyout /etc/nginx/cert.key -out /etc/nginx/cert.crt -subj="/O=Bisq/OU=Bisq Infrastructure/CN=$onionaddress"
|
||||
curl -s "${BISQ_REPO_URL}/${BISQ_REPO_TAG}/monitor/nginx.conf" > /tmp/nginx.conf
|
||||
sudo -H -i -u "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 /tmp/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
echo "[*] Installing collectd config"
|
||||
curl -s "${BISQ_REPO_URL}/${BISQ_REPO_TAG}/monitor/collectd.conf" > /tmp/collectd.conf
|
||||
sudo -H -i -u "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 /tmp/collectd.conf /etc/collectd/collectd.conf
|
||||
sudo -H -i -u "${ROOT_USER}" sed -i -e "s/__ONION_ADDRESS__/$onionaddress/" /etc/collectd/collectd.conf
|
||||
|
||||
echo "[*] Updating systemd daemon configuration"
|
||||
sudo -H -i -u "${ROOT_USER}" systemctl daemon-reload
|
||||
sudo -H -i -u "${ROOT_USER}" systemctl enable nginx.service
|
||||
sudo -H -i -u "${ROOT_USER}" systemctl enable collectd.service
|
||||
|
||||
echo "[*] Restarting services"
|
||||
set +e
|
||||
service bisq status >/dev/null 2>&1
|
||||
[ $? != 4 ] && sudo -H -i -u "${ROOT_USER}" systemctl restart bisq.service
|
||||
service bisq-pricenode status >/dev/null 2>&1
|
||||
[ $? != 4 ] && sudo -H -i -u "${ROOT_USER}" systemctl restart bisq-pricenode.service
|
||||
sudo -H -i -u "${ROOT_USER}" systemctl restart nginx.service
|
||||
sudo -H -i -u "${ROOT_USER}" systemctl restart collectd.service
|
||||
|
||||
echo '[*] Done!'
|
||||
|
||||
echo ' '
|
||||
echo '[*] Report this certificate to the monitoring team!'
|
||||
echo '----------------------------------------------------------------'
|
||||
echo "Server: $onionaddress"
|
||||
echo ' '
|
||||
cat /etc/nginx/cert.crt
|
||||
echo '----------------------------------------------------------------'
|
||||
echo ' '
|
@ -1,28 +0,0 @@
|
||||
load_module /usr/lib/nginx/modules/ngx_stream_module.so;
|
||||
|
||||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
stream {
|
||||
|
||||
log_format basic '$remote_addr [$time_local] '
|
||||
'$protocol Status $status Sent $bytes_sent Received $bytes_received '
|
||||
'Time $session_time';
|
||||
|
||||
error_log syslog:server=unix:/dev/log;
|
||||
access_log syslog:server=unix:/dev/log basic;
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:2003;
|
||||
proxy_pass monitor.bisq.network:2002;
|
||||
proxy_ssl on;
|
||||
|
||||
proxy_ssl_certificate /etc/nginx/cert.crt;
|
||||
proxy_ssl_certificate_key /etc/nginx/cert.key;
|
||||
|
||||
proxy_ssl_session_reuse on;
|
||||
}
|
||||
}
|
@ -1,52 +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.network.p2p.network.TorMode;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* This class uses an already defined Tor via <code>Tor.getDefault()</code>
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*
|
||||
*/
|
||||
public class AvailableTor extends TorMode {
|
||||
|
||||
private final String hiddenServiceDirectory;
|
||||
|
||||
public AvailableTor(File torWorkingDirectory, String hiddenServiceDirectory) {
|
||||
super(torWorkingDirectory);
|
||||
|
||||
this.hiddenServiceDirectory = hiddenServiceDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tor getTor() {
|
||||
return Tor.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHiddenServiceDirectory() {
|
||||
return hiddenServiceDirectory;
|
||||
}
|
||||
|
||||
}
|
@ -1,74 +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 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 the name of the configurable
|
||||
*/
|
||||
protected void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
@ -1,147 +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.common.app.Version;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.common.config.Config.BASE_CURRENCY_NETWORK;
|
||||
|
||||
/**
|
||||
* 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 static ScheduledExecutorService executor;
|
||||
protected final Reporter reporter;
|
||||
private ScheduledFuture<?> scheduler;
|
||||
|
||||
/**
|
||||
* disable execution
|
||||
*/
|
||||
private void disable() {
|
||||
if (scheduler != null)
|
||||
scheduler.cancel(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* enable execution
|
||||
*/
|
||||
private void enable() {
|
||||
scheduler = executor.scheduleWithFixedDelay(this, new Random().nextInt(60),
|
||||
Long.parseLong(configuration.getProperty(INTERVAL)), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected Metric(Reporter reporter) {
|
||||
|
||||
this.reporter = reporter;
|
||||
|
||||
setName(this.getClass().getSimpleName());
|
||||
|
||||
if (executor == null) {
|
||||
executor = new ScheduledThreadPoolExecutor(6);
|
||||
}
|
||||
}
|
||||
|
||||
boolean enabled() {
|
||||
if (scheduler != null)
|
||||
return !scheduler.isCancelled();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(final Properties properties) {
|
||||
synchronized (this) {
|
||||
log.info("{} (re)loading config...", getName());
|
||||
super.configure(properties);
|
||||
reporter.configure(properties);
|
||||
|
||||
Version.setBaseCryptoNetworkId(Integer.parseInt(properties.getProperty("System." + BASE_CURRENCY_NETWORK, "1"))); // defaults to BTC_TESTNET
|
||||
|
||||
// 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() {
|
||||
try {
|
||||
Thread.currentThread().setName("Metric: " + getName());
|
||||
|
||||
// execute all the things
|
||||
synchronized (this) {
|
||||
log.info("{} started", getName());
|
||||
execute();
|
||||
log.info("{} done", getName());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.error("A metric misbehaved!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets scheduled repeatedly.
|
||||
*/
|
||||
protected abstract void execute();
|
||||
|
||||
/**
|
||||
* initiate an orderly shutdown on all metrics. Blocks until all metrics are
|
||||
* shut down or after one minute.
|
||||
*/
|
||||
public static void haltAllMetrics() {
|
||||
Utilities.shutdownAndAwaitTermination(executor, 2, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
@ -1,182 +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.metric.MarketStats;
|
||||
import bisq.monitor.metric.P2PMarketStats;
|
||||
import bisq.monitor.metric.P2PNetworkLoad;
|
||||
import bisq.monitor.metric.P2PRoundTripTime;
|
||||
import bisq.monitor.metric.P2PSeedNodeSnapshot;
|
||||
import bisq.monitor.metric.PriceNodeStats;
|
||||
import bisq.monitor.metric.TorHiddenServiceStartupTime;
|
||||
import bisq.monitor.metric.TorRoundTripTime;
|
||||
import bisq.monitor.metric.TorStartupTime;
|
||||
import bisq.monitor.reporter.ConsoleReporter;
|
||||
import bisq.monitor.reporter.GraphiteReporter;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
|
||||
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.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 {
|
||||
|
||||
public static final File TOR_WORKING_DIR = new File("monitor/work/monitor-tor");
|
||||
private static String[] args = {};
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
Monitor.args = args;
|
||||
new Monitor().start();
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all active {@link Metric}s
|
||||
*/
|
||||
private final List<Metric> metrics = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Starts up all configured Metrics.
|
||||
*
|
||||
* @throws Throwable in case something goes wrong
|
||||
*/
|
||||
private void start() throws Throwable {
|
||||
|
||||
// start Tor
|
||||
Tor.setDefault(new NativeTor(TOR_WORKING_DIR, null, null, false));
|
||||
|
||||
//noinspection deprecation,deprecation,deprecation,deprecation,deprecation,deprecation,deprecation,deprecation
|
||||
Capabilities.app.addAll(Capability.TRADE_STATISTICS,
|
||||
Capability.TRADE_STATISTICS_2,
|
||||
Capability.ACCOUNT_AGE_WITNESS,
|
||||
Capability.ACK_MSG,
|
||||
Capability.PROPOSAL,
|
||||
Capability.BLIND_VOTE,
|
||||
Capability.DAO_STATE,
|
||||
Capability.BUNDLE_OF_ENVELOPES,
|
||||
Capability.REFUND_AGENT,
|
||||
Capability.MEDIATION,
|
||||
Capability.TRADE_STATISTICS_3);
|
||||
|
||||
// assemble Metrics
|
||||
// - create reporters
|
||||
Reporter graphiteReporter = new GraphiteReporter();
|
||||
|
||||
// only use ConsoleReporter if requested (for debugging for example)
|
||||
Properties properties = getProperties();
|
||||
if ("true".equals(properties.getProperty("System.useConsoleReporter", "false")))
|
||||
graphiteReporter = new ConsoleReporter();
|
||||
|
||||
// - add available metrics with their reporters
|
||||
metrics.add(new TorStartupTime(graphiteReporter));
|
||||
metrics.add(new TorRoundTripTime(graphiteReporter));
|
||||
metrics.add(new TorHiddenServiceStartupTime(graphiteReporter));
|
||||
metrics.add(new P2PRoundTripTime(graphiteReporter));
|
||||
metrics.add(new P2PNetworkLoad(graphiteReporter));
|
||||
metrics.add(new P2PSeedNodeSnapshot(graphiteReporter));
|
||||
metrics.add(new P2PMarketStats(graphiteReporter));
|
||||
metrics.add(new PriceNodeStats(graphiteReporter));
|
||||
metrics.add(new MarketStats(graphiteReporter));
|
||||
|
||||
// prepare configuration reload
|
||||
// Note that this is most likely only work on Linux
|
||||
Signal.handle(new Signal("USR1"), signal -> {
|
||||
try {
|
||||
configure();
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
// configure Metrics
|
||||
// - which also starts the metrics if appropriate
|
||||
configure();
|
||||
|
||||
// exit Metrics gracefully on shutdown
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
// set the name of the Thread for debugging purposes
|
||||
log.info("system shutdown initiated");
|
||||
|
||||
log.info("shutting down active metrics...");
|
||||
Metric.haltAllMetrics();
|
||||
|
||||
try {
|
||||
log.info("shutting down tor...");
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
tor.shutdown();
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
|
||||
log.info("system halt");
|
||||
}, "Monitor Shutdown Hook ")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the configuration from disk.
|
||||
*
|
||||
* @throws Exception if something goes wrong
|
||||
*/
|
||||
private void configure() throws Exception {
|
||||
Properties properties = getProperties();
|
||||
for (Metric current : metrics)
|
||||
current.configure(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads a default set of properties with a file if given
|
||||
*
|
||||
* @return a set of properties
|
||||
* @throws Exception in case something goes wrong
|
||||
*/
|
||||
private Properties getProperties() throws Exception {
|
||||
Properties result = new Properties();
|
||||
|
||||
// if we have a config file load the config file, else, load the default config
|
||||
// from the resources
|
||||
if (args.length > 0)
|
||||
result.load(new FileInputStream(args[0]));
|
||||
else
|
||||
result.load(Monitor.class.getClassLoader().getResourceAsStream("metrics.properties"));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,47 +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.network.p2p.NodeAddress;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Helper for parsing and pretty printing onion addresses.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
public class OnionParser {
|
||||
|
||||
public static NodeAddress getNodeAddress(final String current) throws MalformedURLException {
|
||||
String nodeAddress = current.trim();
|
||||
if (!nodeAddress.startsWith("http://"))
|
||||
nodeAddress = "http://" + nodeAddress;
|
||||
URL tmp = new URL(nodeAddress);
|
||||
return new NodeAddress(tmp.getHost(), tmp.getPort() > 0 ? tmp.getPort() : 80);
|
||||
}
|
||||
|
||||
public static String prettyPrint(final NodeAddress host) {
|
||||
return host.getHostNameWithoutPostFix();
|
||||
}
|
||||
|
||||
public static String prettyPrint(String host) throws MalformedURLException {
|
||||
return prettyPrint(getNodeAddress(host));
|
||||
}
|
||||
}
|
@ -1,74 +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 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 the value to report
|
||||
*/
|
||||
public abstract void report(long value);
|
||||
|
||||
/**
|
||||
* Report our findings
|
||||
*
|
||||
* @param value the value to report
|
||||
* @param prefix a common prefix to be included in the tag name
|
||||
*/
|
||||
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 "torStartupTime"
|
||||
*/
|
||||
public abstract void report(Map<String, String> values, String prefix);
|
||||
|
||||
/**
|
||||
* Report our findings one by one.
|
||||
*
|
||||
* @param key the metric name
|
||||
* @param value the value to report
|
||||
* @param timestamp a unix timestamp in milliseconds
|
||||
* @param prefix for example "torStartupTime"
|
||||
*/
|
||||
public abstract void report(String key, String value, String timestamp, String prefix);
|
||||
|
||||
}
|
@ -1,70 +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 java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LongSummaryStatistics;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Calculates average, max, min, p25, p50, p75 off of a list of samples and
|
||||
* throws in the sample size for good measure.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
public class StatisticsHelper {
|
||||
|
||||
public static Map<String, String> process(Collection<Long> input) {
|
||||
|
||||
List<Long> samples = new ArrayList<>(input);
|
||||
|
||||
// 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.0;
|
||||
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));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
@ -1,81 +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 java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Gate pattern to help with thread synchronization
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
@Slf4j
|
||||
public class ThreadGate {
|
||||
|
||||
private CountDownLatch lock = new CountDownLatch(0);
|
||||
|
||||
/**
|
||||
* Make everyone wait until the gate is open again.
|
||||
*/
|
||||
public void engage() {
|
||||
lock = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make everyone wait until the gate is open again.
|
||||
*
|
||||
* @param numberOfLocks how often the gate has to be unlocked until the gate
|
||||
* opens.
|
||||
*/
|
||||
public void engage(int numberOfLocks) {
|
||||
lock = new CountDownLatch(numberOfLocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the gate to be opened. Blocks until the gate is open again. Returns
|
||||
* immediately if the gate is already open.
|
||||
*/
|
||||
public synchronized void await() {
|
||||
while (lock.getCount() > 0)
|
||||
try {
|
||||
if (!lock.await(60, TimeUnit.SECONDS)) {
|
||||
log.warn("timeout occurred!");
|
||||
break; // break the loop
|
||||
}
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the gate and let everyone proceed with their execution.
|
||||
*/
|
||||
public void proceed() {
|
||||
lock.countDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the gate with no regards on how many locks are still in place.
|
||||
*/
|
||||
public void unlock() {
|
||||
while (lock.getCount() > 0)
|
||||
lock.countDown();
|
||||
}
|
||||
}
|
@ -1,100 +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.metric;
|
||||
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Uses the markets API to retrieve market volume data.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class MarketStats extends Metric {
|
||||
private static final String MARKETS_BISQ_NETWORK = "https://markets.bisq.network";
|
||||
// poor mans JSON parser
|
||||
private final Pattern marketPattern = Pattern.compile("\"market\" ?: ?\"([a-z_]+)\"");
|
||||
private final Pattern amountPattern = Pattern.compile("\"amount\" ?: ?\"([\\d\\.]+)\"");
|
||||
private final Pattern volumePattern = Pattern.compile("\"volume\" ?: ?\"([\\d\\.]+)\"");
|
||||
private final Pattern timestampPattern = Pattern.compile("\"trade_date\" ?: ?([\\d]+)");
|
||||
|
||||
private Long lastRun = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(15));
|
||||
|
||||
public MarketStats(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
try {
|
||||
// for each configured host
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
// assemble query
|
||||
long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
|
||||
String query = "/api/trades?format=json&market=all×tamp_from=" + lastRun + "×tamp_to=" + now;
|
||||
lastRun = now; // thought about adding 1 second but what if a trade is done exactly in this one second?
|
||||
|
||||
// connect
|
||||
URLConnection connection = new URL(MARKETS_BISQ_NETWORK + query).openConnection();
|
||||
|
||||
// prepare to receive data
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
|
||||
String line, all = "";
|
||||
while ((line = in.readLine()) != null)
|
||||
all += ' ' + line;
|
||||
in.close();
|
||||
|
||||
Arrays.stream(all.substring(0, all.length() - 2).split("}")).forEach(trade -> {
|
||||
Matcher market = marketPattern.matcher(trade);
|
||||
Matcher amount = amountPattern.matcher(trade);
|
||||
Matcher timestamp = timestampPattern.matcher(trade);
|
||||
market.find();
|
||||
if (market.group(1).endsWith("btc")) {
|
||||
amount = volumePattern.matcher(trade);
|
||||
}
|
||||
amount.find();
|
||||
timestamp.find();
|
||||
reporter.report("volume." + market.group(1), amount.group(1), timestamp.group(1), getName());
|
||||
});
|
||||
} catch (IllegalStateException ignore) {
|
||||
// no match found
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,282 +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.metric;
|
||||
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.offer.bisq_v1.OfferPayload;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.peers.getdata.messages.GetDataResponse;
|
||||
import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest;
|
||||
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Demo Stats metric derived from the OfferPayload messages we get from the seed nodes
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
@Slf4j
|
||||
public class P2PMarketStats extends P2PSeedNodeSnapshotBase {
|
||||
final Map<NodeAddress, Statistics<Aggregator>> versionBucketsPerHost = new ConcurrentHashMap<>();
|
||||
final Map<NodeAddress, Statistics<Aggregator>> offerVolumeBucketsPerHost = new ConcurrentHashMap<>();
|
||||
final Map<NodeAddress, Statistics<List<Long>>> offerVolumeDistributionBucketsPerHost = new ConcurrentHashMap<>();
|
||||
final Map<NodeAddress, Statistics<Map<NodeAddress, Aggregator>>> offersPerTraderBucketsPerHost = new ConcurrentHashMap<>();
|
||||
final Map<NodeAddress, Statistics<Map<NodeAddress, Aggregator>>> volumePerTraderBucketsPerHost = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Efficient way to aggregate numbers.
|
||||
*/
|
||||
private static class Aggregator {
|
||||
private long value = 0;
|
||||
|
||||
synchronized long value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
synchronized void increment() {
|
||||
value++;
|
||||
}
|
||||
|
||||
synchronized void add(long amount) {
|
||||
value += amount;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class OfferStatistics<T> extends Statistics<T> {
|
||||
@Override
|
||||
public synchronized void log(Object message) {
|
||||
if (message instanceof OfferPayload) {
|
||||
OfferPayload currentMessage = (OfferPayload) message;
|
||||
// For logging different data types
|
||||
String market = currentMessage.getDirection() + "." + currentMessage.getBaseCurrencyCode() + "_" + currentMessage.getCounterCurrencyCode();
|
||||
|
||||
process(market, currentMessage);
|
||||
}
|
||||
}
|
||||
|
||||
abstract void process(String market, OfferPayload currentMessage);
|
||||
}
|
||||
|
||||
private class OfferCountStatistics extends OfferStatistics<Aggregator> {
|
||||
|
||||
@Override
|
||||
void process(String market, OfferPayload currentMessage) {
|
||||
buckets.putIfAbsent(market, new Aggregator());
|
||||
buckets.get(market).increment();
|
||||
}
|
||||
}
|
||||
|
||||
private class OfferVolumeStatistics extends OfferStatistics<Aggregator> {
|
||||
|
||||
@Override
|
||||
void process(String market, OfferPayload currentMessage) {
|
||||
buckets.putIfAbsent(market, new Aggregator());
|
||||
buckets.get(market).add(currentMessage.getAmount());
|
||||
}
|
||||
}
|
||||
|
||||
private class OfferVolumeDistributionStatistics extends OfferStatistics<List<Long>> {
|
||||
|
||||
@Override
|
||||
void process(String market, OfferPayload currentMessage) {
|
||||
buckets.putIfAbsent(market, new ArrayList<>());
|
||||
buckets.get(market).add(currentMessage.getAmount());
|
||||
}
|
||||
}
|
||||
|
||||
private class OffersPerTraderStatistics extends OfferStatistics<Map<NodeAddress, Aggregator>> {
|
||||
|
||||
@Override
|
||||
void process(String market, OfferPayload currentMessage) {
|
||||
buckets.putIfAbsent(market, new HashMap<>());
|
||||
buckets.get(market).putIfAbsent(currentMessage.getOwnerNodeAddress(), new Aggregator());
|
||||
buckets.get(market).get(currentMessage.getOwnerNodeAddress()).increment();
|
||||
}
|
||||
}
|
||||
|
||||
private class VolumePerTraderStatistics extends OfferStatistics<Map<NodeAddress, Aggregator>> {
|
||||
|
||||
@Override
|
||||
void process(String market, OfferPayload currentMessage) {
|
||||
buckets.putIfAbsent(market, new HashMap<>());
|
||||
buckets.get(market).putIfAbsent(currentMessage.getOwnerNodeAddress(), new Aggregator());
|
||||
buckets.get(market).get(currentMessage.getOwnerNodeAddress()).add(currentMessage.getAmount());
|
||||
}
|
||||
}
|
||||
|
||||
private class VersionsStatistics extends Statistics<Aggregator> {
|
||||
|
||||
@Override
|
||||
public void log(Object message) {
|
||||
|
||||
if (message instanceof OfferPayload) {
|
||||
OfferPayload offerPayload = (OfferPayload) message;
|
||||
String version = "v" + OfferUtil.getVersionFromId(offerPayload.getId());
|
||||
buckets.putIfAbsent(version, new Aggregator());
|
||||
buckets.get(version).increment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public P2PMarketStats(Reporter graphiteReporter) {
|
||||
super(graphiteReporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<NetworkEnvelope> getRequests() {
|
||||
List<NetworkEnvelope> result = new ArrayList<>();
|
||||
|
||||
Random random = new Random();
|
||||
result.add(new PreliminaryGetDataRequest(random.nextInt(), hashes));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void createHistogram(List<Long> input, String market, Map<String, String> report) {
|
||||
int numberOfBins = 5;
|
||||
|
||||
// - get biggest offer
|
||||
double max = input.stream().max(Long::compareTo).map(value -> value * 1.01).orElse(0.0);
|
||||
|
||||
// - create histogram
|
||||
input.stream().collect(
|
||||
Collectors.groupingBy(aLong -> aLong == max ? numberOfBins - 1 : (int) Math.floor(aLong / (max / numberOfBins)), Collectors.counting())).
|
||||
forEach((integer, integer2) -> report.put(market + ".bin_" + integer, String.valueOf(integer2)));
|
||||
|
||||
report.put(market + ".number_of_bins", String.valueOf(numberOfBins));
|
||||
report.put(market + ".max", String.valueOf((int) max));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void report() {
|
||||
Map<String, String> report = new HashMap<>();
|
||||
bucketsPerHost.values().stream().findFirst().ifPresent(nodeAddressStatisticsEntry -> nodeAddressStatisticsEntry.values().forEach((market, numberOfOffers) -> report.put(market, String.valueOf(((Aggregator) numberOfOffers).value()))));
|
||||
reporter.report(report, getName() + ".offerCount");
|
||||
|
||||
// do offerbook volume statistics
|
||||
report.clear();
|
||||
offerVolumeBucketsPerHost.values().stream().findFirst().ifPresent(aggregatorStatistics -> aggregatorStatistics.values().forEach((market, numberOfOffers) -> report.put(market, String.valueOf(numberOfOffers.value()))));
|
||||
reporter.report(report, getName() + ".volume");
|
||||
|
||||
// do the offer vs volume histogram
|
||||
report.clear();
|
||||
// - get a data set
|
||||
offerVolumeDistributionBucketsPerHost.values().stream().findFirst().ifPresent(listStatistics -> listStatistics.values().forEach((market, offers) -> {
|
||||
createHistogram(offers, market, report);
|
||||
}));
|
||||
reporter.report(report, getName() + ".volume-per-offer-distribution");
|
||||
|
||||
// do offers per trader
|
||||
report.clear();
|
||||
// - get a data set
|
||||
offersPerTraderBucketsPerHost.values().stream().findFirst().ifPresent(mapStatistics -> mapStatistics.values().forEach((market, stuff) -> {
|
||||
List<Long> offerPerTrader = stuff.values().stream().map(Aggregator::value).collect(Collectors.toList());
|
||||
|
||||
createHistogram(offerPerTrader, market, report);
|
||||
}));
|
||||
reporter.report(report, getName() + ".traders_by_number_of_offers");
|
||||
|
||||
// do volume per trader
|
||||
report.clear();
|
||||
// - get a data set
|
||||
volumePerTraderBucketsPerHost.values().stream().findFirst().ifPresent(mapStatistics -> mapStatistics.values().forEach((market, stuff) -> {
|
||||
List<Long> volumePerTrader = stuff.values().stream().map(Aggregator::value).collect(Collectors.toList());
|
||||
|
||||
createHistogram(volumePerTrader, market, report);
|
||||
}));
|
||||
reporter.report(report, getName() + ".traders_by_volume");
|
||||
|
||||
// do version statistics
|
||||
report.clear();
|
||||
Optional<Statistics<Aggregator>> optionalStatistics = versionBucketsPerHost.values().stream().findAny();
|
||||
optionalStatistics.ifPresent(aggregatorStatistics -> aggregatorStatistics.values()
|
||||
.forEach((version, numberOfOccurrences) -> report.put(version, String.valueOf(numberOfOccurrences.value()))));
|
||||
reporter.report(report, "versions");
|
||||
}
|
||||
|
||||
protected boolean treatMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||
checkNotNull(connection.getPeersNodeAddressProperty(),
|
||||
"although the property is nullable, we need it to not be null");
|
||||
|
||||
if (networkEnvelope instanceof GetDataResponse) {
|
||||
|
||||
Statistics offerCount = new OfferCountStatistics();
|
||||
Statistics offerVolume = new OfferVolumeStatistics();
|
||||
Statistics offerVolumeDistribution = new OfferVolumeDistributionStatistics();
|
||||
Statistics offersPerTrader = new OffersPerTraderStatistics();
|
||||
Statistics volumePerTrader = new VolumePerTraderStatistics();
|
||||
Statistics versions = new VersionsStatistics();
|
||||
|
||||
GetDataResponse dataResponse = (GetDataResponse) networkEnvelope;
|
||||
final Set<ProtectedStorageEntry> dataSet = dataResponse.getDataSet();
|
||||
dataSet.forEach(e -> {
|
||||
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
|
||||
if (protectedStoragePayload == null) {
|
||||
log.warn("StoragePayload was null: {}", networkEnvelope.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
offerCount.log(protectedStoragePayload);
|
||||
offerVolume.log(protectedStoragePayload);
|
||||
offerVolumeDistribution.log(protectedStoragePayload);
|
||||
offersPerTrader.log(protectedStoragePayload);
|
||||
volumePerTrader.log(protectedStoragePayload);
|
||||
versions.log(protectedStoragePayload);
|
||||
});
|
||||
|
||||
dataResponse.getPersistableNetworkPayloadSet().forEach(persistableNetworkPayload -> {
|
||||
// memorize message hashes
|
||||
//Byte[] bytes = new Byte[persistableNetworkPayload.getHash().length];
|
||||
//Arrays.setAll(bytes, n -> persistableNetworkPayload.getHash()[n]);
|
||||
|
||||
//hashes.add(bytes);
|
||||
|
||||
hashes.add(persistableNetworkPayload.getHash());
|
||||
});
|
||||
|
||||
bucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), offerCount);
|
||||
offerVolumeBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), offerVolume);
|
||||
offerVolumeDistributionBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), offerVolumeDistribution);
|
||||
offersPerTraderBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), offersPerTrader);
|
||||
volumePerTraderBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), volumePerTrader);
|
||||
versionBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), versions);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,245 +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.metric;
|
||||
|
||||
import bisq.monitor.AvailableTor;
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.Monitor;
|
||||
import bisq.monitor.Reporter;
|
||||
import bisq.monitor.ThreadGate;
|
||||
|
||||
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
|
||||
import bisq.core.proto.network.CoreNetworkProtoResolver;
|
||||
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
|
||||
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.network.MessageListener;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.network.SetupListener;
|
||||
import bisq.network.p2p.network.TorNetworkNode;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
import bisq.network.p2p.peers.keepalive.KeepAliveManager;
|
||||
import bisq.network.p2p.peers.peerexchange.PeerExchangeManager;
|
||||
import bisq.network.p2p.storage.messages.BroadcastMessage;
|
||||
|
||||
import bisq.common.ClockWatcher;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.file.CorruptedStorageFileHandler;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
import bisq.common.proto.network.NetworkProtoResolver;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Contacts a list of hosts and asks them for all the data we do not have. The
|
||||
* answers are then compiled into buckets of message types. Based on these
|
||||
* buckets, the Metric reports (for each host) the message types observed and
|
||||
* their number along with a relative comparison between all hosts.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class P2PNetworkLoad extends Metric implements MessageListener, SetupListener {
|
||||
|
||||
private static final String TOR_PROXY_PORT = "run.torProxyPort";
|
||||
private static final String MAX_CONNECTIONS = "run.maxConnections";
|
||||
private static final String HISTORY_SIZE = "run.historySize";
|
||||
private NetworkNode networkNode;
|
||||
private final File torHiddenServiceDir = new File("metric_" + getName());
|
||||
private final ThreadGate hsReady = new ThreadGate();
|
||||
private final Map<String, Counter> buckets = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Buffers the last X message we received. New messages will only be logged in case
|
||||
* the message isn't already in the history. Note that the oldest message hashes are
|
||||
* dropped to record newer hashes.
|
||||
*/
|
||||
private Map<Integer, Object> history;
|
||||
private long lastRun = 0;
|
||||
|
||||
/**
|
||||
* History implementation using a {@link LinkedHashMap} and its
|
||||
* {@link LinkedHashMap#removeEldestEntry(Map.Entry)} option.
|
||||
*/
|
||||
private static class FixedSizeHistoryTracker<K, V> extends LinkedHashMap<K, V> {
|
||||
final int historySize;
|
||||
|
||||
FixedSizeHistoryTracker(int historySize) {
|
||||
super(historySize, 10, true);
|
||||
this.historySize = historySize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||
return size() > historySize;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
|
||||
// in case we do not have a NetworkNode up and running, we create one
|
||||
if (null == networkNode) {
|
||||
// prepare the gate
|
||||
hsReady.engage();
|
||||
|
||||
// start the network node
|
||||
networkNode = new TorNetworkNode(Integer.parseInt(configuration.getProperty(TOR_PROXY_PORT, "9053")),
|
||||
new CoreNetworkProtoResolver(Clock.systemDefaultZone()), false,
|
||||
new AvailableTor(Monitor.TOR_WORKING_DIR, torHiddenServiceDir.getName()), null);
|
||||
networkNode.start(this);
|
||||
|
||||
// wait for the HS to be published
|
||||
hsReady.await();
|
||||
|
||||
// boot up P2P node
|
||||
try {
|
||||
Config config = new Config();
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler = new CorruptedStorageFileHandler();
|
||||
int maxConnections = Integer.parseInt(configuration.getProperty(MAX_CONNECTIONS, "12"));
|
||||
NetworkProtoResolver networkProtoResolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone());
|
||||
CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null,
|
||||
networkProtoResolver);
|
||||
DefaultSeedNodeRepository seedNodeRepository = new DefaultSeedNodeRepository(config);
|
||||
PeerManager peerManager = new PeerManager(networkNode, seedNodeRepository, new ClockWatcher(),
|
||||
new PersistenceManager<>(torHiddenServiceDir, persistenceProtoResolver, corruptedStorageFileHandler), maxConnections);
|
||||
|
||||
// init file storage
|
||||
peerManager.readPersisted(() -> {
|
||||
});
|
||||
|
||||
PeerExchangeManager peerExchangeManager = new PeerExchangeManager(networkNode, seedNodeRepository,
|
||||
peerManager);
|
||||
// updates the peer list every now and then as well
|
||||
peerExchangeManager
|
||||
.requestReportedPeersFromSeedNodes(seedNodeRepository.getSeedNodeAddresses().iterator().next());
|
||||
|
||||
KeepAliveManager keepAliveManager = new KeepAliveManager(networkNode, peerManager);
|
||||
keepAliveManager.start();
|
||||
|
||||
networkNode.addMessageListener(this);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// report
|
||||
Map<String, String> report = new HashMap<>();
|
||||
|
||||
if (lastRun != 0 && System.currentTimeMillis() - lastRun != 0) {
|
||||
// - normalize to data/minute
|
||||
double perMinuteFactor = 60000.0 / (System.currentTimeMillis() - lastRun);
|
||||
|
||||
|
||||
// - get snapshot so we do not loose data
|
||||
Set<String> keys = new HashSet<>(buckets.keySet());
|
||||
|
||||
// - transfer values to report
|
||||
keys.forEach(key -> {
|
||||
int value = buckets.get(key).getAndReset();
|
||||
if (value != 0) {
|
||||
report.put(key, String.format("%.2f", value * perMinuteFactor));
|
||||
}
|
||||
});
|
||||
|
||||
// - report
|
||||
reporter.report(report, getName());
|
||||
}
|
||||
|
||||
// - reset last run
|
||||
lastRun = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public P2PNetworkLoad(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Properties properties) {
|
||||
super.configure(properties);
|
||||
|
||||
history = Collections.synchronizedMap(new FixedSizeHistoryTracker<>(Integer.parseInt(configuration.getProperty(HISTORY_SIZE, "200"))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Efficient way to count message occurrences.
|
||||
*/
|
||||
private static class Counter {
|
||||
private int value = 1;
|
||||
|
||||
synchronized int getAndReset() {
|
||||
try {
|
||||
return value;
|
||||
} finally {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void increment() {
|
||||
value++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||
if (networkEnvelope instanceof BroadcastMessage) {
|
||||
try {
|
||||
if (history.get(networkEnvelope.hashCode()) == null) {
|
||||
history.put(networkEnvelope.hashCode(), null);
|
||||
buckets.get(networkEnvelope.getClass().getSimpleName()).increment();
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
// use exception handling because we hardly ever need to add a fresh bucket
|
||||
buckets.put(networkEnvelope.getClass().getSimpleName(), new Counter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTorNodeReady() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHiddenServicePublished() {
|
||||
// open the gate
|
||||
hsReady.proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetupFailed(Throwable throwable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestCustomBridges() {
|
||||
}
|
||||
}
|
@ -1,111 +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.metric;
|
||||
|
||||
import bisq.monitor.OnionParser;
|
||||
import bisq.monitor.Reporter;
|
||||
import bisq.monitor.StatisticsHelper;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.CloseConnectionReason;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.peers.keepalive.messages.Ping;
|
||||
import bisq.network.p2p.peers.keepalive.messages.Pong;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class P2PRoundTripTime extends P2PSeedNodeSnapshotBase {
|
||||
|
||||
private static final String SAMPLE_SIZE = "run.sampleSize";
|
||||
private final Map<Integer, Long> sentAt = new HashMap<>();
|
||||
private Map<NodeAddress, Statistics> measurements = new HashMap<>();
|
||||
|
||||
public P2PRoundTripTime(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a counter to do statistics.
|
||||
*/
|
||||
private class Statistics {
|
||||
|
||||
private final List<Long> samples = new ArrayList<>();
|
||||
|
||||
public synchronized void log(Object message) {
|
||||
Pong pong = (Pong) message;
|
||||
Long start = sentAt.get(pong.getRequestNonce());
|
||||
if (start != null)
|
||||
samples.add(System.currentTimeMillis() - start);
|
||||
}
|
||||
|
||||
public List<Long> values() {
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<NetworkEnvelope> getRequests() {
|
||||
List<NetworkEnvelope> result = new ArrayList<>();
|
||||
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < Integer.parseInt(configuration.getProperty(SAMPLE_SIZE, "1")); i++)
|
||||
result.add(new Ping(random.nextInt(), 42));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void aboutToSend(NetworkEnvelope message) {
|
||||
sentAt.put(((Ping) message).getNonce(), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean treatMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||
if (networkEnvelope instanceof Pong) {
|
||||
checkNotNull(connection.getPeersNodeAddressProperty(),
|
||||
"although the property is nullable, we need it to not be null");
|
||||
|
||||
measurements.putIfAbsent(connection.getPeersNodeAddressProperty().getValue(), new Statistics());
|
||||
|
||||
measurements.get(connection.getPeersNodeAddressProperty().getValue()).log(networkEnvelope);
|
||||
|
||||
connection.shutDown(CloseConnectionReason.APP_SHUT_DOWN);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void report() {
|
||||
// report
|
||||
measurements.forEach(((nodeAddress, samples) ->
|
||||
reporter.report(StatisticsHelper.process(samples.values()),
|
||||
getName() + "." + OnionParser.prettyPrint(nodeAddress))
|
||||
));
|
||||
// clean up for next round
|
||||
measurements = new HashMap<>();
|
||||
}
|
||||
}
|
@ -1,292 +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.metric;
|
||||
|
||||
import bisq.monitor.OnionParser;
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import bisq.core.dao.monitoring.model.StateHash;
|
||||
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetStateHashesResponse;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.peers.getdata.messages.GetDataResponse;
|
||||
import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest;
|
||||
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
|
||||
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Contacts a list of hosts and asks them for all the data excluding persisted messages. The
|
||||
* answers are then compiled into buckets of message types. Based on these
|
||||
* buckets, the Metric reports (for each host) the message types observed and
|
||||
* their number.
|
||||
*
|
||||
* Furthermore, since the DAO is a thing now, the consistency of the DAO state held by each host is assessed and reported.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
|
||||
|
||||
final Map<NodeAddress, Statistics<Set<Integer>>> bucketsPerHost = new ConcurrentHashMap<>();
|
||||
private int daostateheight = 594000;
|
||||
private int proposalheight = daostateheight;
|
||||
private int blindvoteheight = daostateheight;
|
||||
|
||||
/**
|
||||
* Use a counter to do statistics.
|
||||
*/
|
||||
private static class MyStatistics extends Statistics<Set<Integer>> {
|
||||
|
||||
@Override
|
||||
public synchronized void log(Object message) {
|
||||
|
||||
// For logging different data types
|
||||
String className = message.getClass().getSimpleName();
|
||||
|
||||
buckets.putIfAbsent(className, new HashSet<>());
|
||||
buckets.get(className).add(message.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
public P2PSeedNodeSnapshot(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
protected List<NetworkEnvelope> getRequests() {
|
||||
List<NetworkEnvelope> result = new ArrayList<>();
|
||||
|
||||
Random random = new Random();
|
||||
result.add(new PreliminaryGetDataRequest(random.nextInt(), hashes));
|
||||
|
||||
result.add(new GetDaoStateHashesRequest(daostateheight, random.nextInt()));
|
||||
|
||||
result.add(new GetProposalStateHashesRequest(proposalheight, random.nextInt()));
|
||||
|
||||
result.add(new GetBlindVoteStateHashesRequest(blindvoteheight, random.nextInt()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report all the stuff. Uses the configured reporter directly.
|
||||
*/
|
||||
void report() {
|
||||
|
||||
// report
|
||||
Map<String, String> report = new HashMap<>();
|
||||
// - assemble histograms
|
||||
bucketsPerHost.forEach((host, statistics) -> statistics.values().forEach((type, set) -> report
|
||||
.put(OnionParser.prettyPrint(host) + ".numberOfMessages." + type, Integer.toString(set.size()))));
|
||||
|
||||
// - assemble diffs
|
||||
// - transfer values
|
||||
Map<String, Statistics<Set<Integer>>> messagesPerHost = new HashMap<>();
|
||||
bucketsPerHost.forEach((host, value) -> messagesPerHost.put(OnionParser.prettyPrint(host), value));
|
||||
|
||||
// - pick reference seed node and its values
|
||||
String referenceHost = "overall_number_of_unique_messages";
|
||||
Map<String, Set<Object>> referenceValues = new HashMap<>();
|
||||
messagesPerHost.forEach((host, statistics) -> statistics.values().forEach((type, set) -> {
|
||||
referenceValues.putIfAbsent(type, new HashSet<>());
|
||||
referenceValues.get(type).addAll(set);
|
||||
}));
|
||||
|
||||
// - calculate diffs
|
||||
messagesPerHost.forEach(
|
||||
(host, statistics) -> {
|
||||
statistics.values().forEach((messageType, set) -> {
|
||||
try {
|
||||
report.put(OnionParser.prettyPrint(host) + ".relativeNumberOfMessages." + messageType,
|
||||
String.valueOf(set.size() - referenceValues.get(messageType).size()));
|
||||
} catch (MalformedURLException | NullPointerException e) {
|
||||
log.error("we should never have gotten here", e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
report.put(OnionParser.prettyPrint(host) + ".referenceHost", referenceHost);
|
||||
} catch (MalformedURLException ignore) {
|
||||
log.error("we should never got here");
|
||||
}
|
||||
});
|
||||
|
||||
// cleanup for next run
|
||||
bucketsPerHost.forEach((host, statistics) -> statistics.reset());
|
||||
|
||||
// when our hash cache exceeds a hard limit, we clear the cache and start anew
|
||||
if (hashes.size() > 150000)
|
||||
hashes.clear();
|
||||
|
||||
// - report
|
||||
reporter.report(report, getName());
|
||||
|
||||
// - assemble dao report
|
||||
Map<String, String> daoreport = new HashMap<>();
|
||||
|
||||
// - transcode
|
||||
Map<String, Map<NodeAddress, Tuple>> perType = new HashMap<>();
|
||||
daoData.forEach((nodeAddress, daostatistics) -> daostatistics.values().forEach((type, tuple) -> {
|
||||
perType.putIfAbsent(type, new HashMap<>());
|
||||
perType.get(type).put(nodeAddress, tuple);
|
||||
}));
|
||||
|
||||
// - process dao data
|
||||
perType.forEach((type, nodeAddressTupleMap) -> {
|
||||
// - find head
|
||||
int head = nodeAddressTupleMap.values().stream().max(Comparator.comparingLong(Tuple::getHeight))
|
||||
.map(value -> (int) value.height)
|
||||
.orElse(0);
|
||||
int oldest = nodeAddressTupleMap.values().stream().min(Comparator.comparingLong(Tuple::getHeight))
|
||||
.map(value -> (int) value.height)
|
||||
.orElse(0);
|
||||
|
||||
// - update queried height
|
||||
if (type.contains("DaoState"))
|
||||
daostateheight = oldest - 20;
|
||||
else if (type.contains("Proposal"))
|
||||
proposalheight = oldest - 20;
|
||||
else
|
||||
blindvoteheight = oldest - 20;
|
||||
|
||||
// - calculate diffs
|
||||
nodeAddressTupleMap.forEach((nodeAddress, tuple) -> daoreport.put(type + "." + OnionParser.prettyPrint(nodeAddress) + ".head", Long.toString(tuple.height - head)));
|
||||
|
||||
// - memorize hashes
|
||||
Map<ByteBuffer, Integer> hitcount = new HashMap<>();
|
||||
nodeAddressTupleMap.forEach((nodeAddress, tuple) -> {
|
||||
ByteBuffer hash = ByteBuffer.wrap(tuple.hash);
|
||||
if (hitcount.containsKey(hash)) {
|
||||
hitcount.put(hash, hitcount.get(hash) + 1);
|
||||
} else
|
||||
hitcount.put(hash, 1);
|
||||
});
|
||||
|
||||
hitcount.clear();
|
||||
|
||||
nodeAddressTupleMap.forEach((nodeAddress, tuple) ->
|
||||
daoreport.put(type + "." + OnionParser.prettyPrint(nodeAddress) + ".hash",
|
||||
Integer.toString(Arrays.asList(hitcount.entrySet().stream()
|
||||
.sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue()))
|
||||
.map(Map.Entry::getKey).toArray()).indexOf(ByteBuffer
|
||||
.wrap(tuple.hash)))));
|
||||
|
||||
// - report reference head
|
||||
daoreport.put(type + ".referenceHead", Integer.toString(head));
|
||||
});
|
||||
|
||||
daoData.clear();
|
||||
|
||||
// - report
|
||||
reporter.report(daoreport, "DaoStateSnapshot");
|
||||
}
|
||||
|
||||
private static class Tuple {
|
||||
@Getter
|
||||
private final long height;
|
||||
private final byte[] hash;
|
||||
|
||||
Tuple(long height, byte[] hash) {
|
||||
this.height = height;
|
||||
this.hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
private class DaoStatistics extends Statistics<Tuple> {
|
||||
|
||||
@Override
|
||||
public void log(Object message) {
|
||||
// get last entry
|
||||
StateHash last = (StateHash) ((GetStateHashesResponse) message).getStateHashes().get(((GetStateHashesResponse) message).getStateHashes().size() - 1);
|
||||
|
||||
// For logging different data types
|
||||
String className = last.getClass().getSimpleName();
|
||||
|
||||
buckets.putIfAbsent(className, new Tuple(last.getHeight(), last.getHash()));
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<NodeAddress, Statistics<Tuple>> daoData = new ConcurrentHashMap<>();
|
||||
|
||||
protected boolean treatMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||
checkNotNull(connection.getPeersNodeAddressProperty(),
|
||||
"although the property is nullable, we need it to not be null");
|
||||
|
||||
if (networkEnvelope instanceof GetDataResponse) {
|
||||
|
||||
Statistics result = new MyStatistics();
|
||||
|
||||
GetDataResponse dataResponse = (GetDataResponse) networkEnvelope;
|
||||
final Set<ProtectedStorageEntry> dataSet = dataResponse.getDataSet();
|
||||
dataSet.forEach(e -> {
|
||||
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
|
||||
if (protectedStoragePayload == null) {
|
||||
log.warn("StoragePayload was null: {}", networkEnvelope.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
result.log(protectedStoragePayload);
|
||||
});
|
||||
|
||||
dataResponse.getPersistableNetworkPayloadSet().forEach(persistableNetworkPayload -> {
|
||||
// memorize message hashes
|
||||
//Byte[] bytes = new Byte[persistableNetworkPayload.getHash().length];
|
||||
//Arrays.setAll(bytes, n -> persistableNetworkPayload.getHash()[n]);
|
||||
|
||||
//hashes.add(bytes);
|
||||
|
||||
hashes.add(persistableNetworkPayload.getHash());
|
||||
});
|
||||
|
||||
bucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), result);
|
||||
return true;
|
||||
} else if (networkEnvelope instanceof GetStateHashesResponse) {
|
||||
daoData.putIfAbsent(connection.getPeersNodeAddressProperty().getValue(), new DaoStatistics());
|
||||
|
||||
daoData.get(connection.getPeersNodeAddressProperty().getValue()).log(networkEnvelope);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,241 +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.metric;
|
||||
|
||||
import bisq.monitor.AvailableTor;
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.Monitor;
|
||||
import bisq.monitor.OnionParser;
|
||||
import bisq.monitor.Reporter;
|
||||
import bisq.monitor.ThreadGate;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessStore;
|
||||
import bisq.core.proto.network.CoreNetworkProtoResolver;
|
||||
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
|
||||
import bisq.core.trade.statistics.TradeStatistics3Store;
|
||||
|
||||
import bisq.network.p2p.CloseConnectionMessage;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
import bisq.network.p2p.network.MessageListener;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.network.TorNetworkNode;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.BaseCurrencyNetwork;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Contacts a list of hosts and asks them for all the data excluding persisted messages. The
|
||||
* answers are then compiled into buckets of message types. Based on these
|
||||
* buckets, the Metric reports (for each host) the message types observed and
|
||||
* their number.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class P2PSeedNodeSnapshotBase extends Metric implements MessageListener {
|
||||
|
||||
private static final String HOSTS = "run.hosts";
|
||||
private static final String TOR_PROXY_PORT = "run.torProxyPort";
|
||||
private static final String DATABASE_DIR = "run.dbDir";
|
||||
final Map<NodeAddress, Statistics<?>> bucketsPerHost = new ConcurrentHashMap<>();
|
||||
private final ThreadGate gate = new ThreadGate();
|
||||
protected final Set<byte[]> hashes = new TreeSet<>(Arrays::compare);
|
||||
|
||||
/**
|
||||
* Statistics Interface for use with derived classes.
|
||||
*
|
||||
* @param <T> the value type of the statistics implementation
|
||||
*/
|
||||
protected abstract static class Statistics<T> {
|
||||
protected final Map<String, T> buckets = new HashMap<>();
|
||||
|
||||
abstract void log(Object message);
|
||||
|
||||
Map<String, T> values() {
|
||||
return buckets;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
buckets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public P2PSeedNodeSnapshotBase(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Properties properties) {
|
||||
super.configure(properties);
|
||||
|
||||
if (hashes.isEmpty() && configuration.getProperty(DATABASE_DIR) != null) {
|
||||
File dir = new File(configuration.getProperty(DATABASE_DIR));
|
||||
String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString();
|
||||
try {
|
||||
CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null, null);
|
||||
|
||||
//TODO will not work with historical data... should be refactored to re-use code for reading resource files
|
||||
TradeStatistics3Store tradeStatistics3Store = new TradeStatistics3Store();
|
||||
PersistenceManager<TradeStatistics3Store> tradeStatistics3PersistenceManager = new PersistenceManager<>(dir,
|
||||
persistenceProtoResolver, null);
|
||||
tradeStatistics3PersistenceManager.initialize(tradeStatistics3Store,
|
||||
tradeStatistics3Store.getDefaultStorageFileName() + networkPostfix,
|
||||
PersistenceManager.Source.NETWORK);
|
||||
TradeStatistics3Store persistedTradeStatistics3Store = tradeStatistics3PersistenceManager.getPersisted();
|
||||
if (persistedTradeStatistics3Store != null) {
|
||||
tradeStatistics3Store.getMap().putAll(persistedTradeStatistics3Store.getMap());
|
||||
}
|
||||
hashes.addAll(tradeStatistics3Store.getMap().keySet().stream()
|
||||
.map(byteArray -> byteArray.bytes).collect(Collectors.toSet()));
|
||||
|
||||
AccountAgeWitnessStore accountAgeWitnessStore = new AccountAgeWitnessStore();
|
||||
PersistenceManager<AccountAgeWitnessStore> accountAgeWitnessPersistenceManager = new PersistenceManager<>(dir,
|
||||
persistenceProtoResolver, null);
|
||||
accountAgeWitnessPersistenceManager.initialize(accountAgeWitnessStore,
|
||||
accountAgeWitnessStore.getDefaultStorageFileName() + networkPostfix,
|
||||
PersistenceManager.Source.NETWORK);
|
||||
AccountAgeWitnessStore persistedAccountAgeWitnessStore = accountAgeWitnessPersistenceManager.getPersisted();
|
||||
if (persistedAccountAgeWitnessStore != null) {
|
||||
accountAgeWitnessStore.getMap().putAll(persistedAccountAgeWitnessStore.getMap());
|
||||
}
|
||||
hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream()
|
||||
.map(byteArray -> byteArray.bytes).collect(Collectors.toSet()));
|
||||
} catch (NullPointerException e) {
|
||||
// in case there is no store file
|
||||
log.error("There is no storage file where there should be one: {}", dir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
// start the network node
|
||||
final NetworkNode networkNode = new TorNetworkNode(Integer.parseInt(configuration.getProperty(TOR_PROXY_PORT, "9054")),
|
||||
new CoreNetworkProtoResolver(Clock.systemDefaultZone()), false,
|
||||
new AvailableTor(Monitor.TOR_WORKING_DIR, "unused"), null);
|
||||
// we do not need to start the networkNode, as we do not need the HS
|
||||
//networkNode.start(this);
|
||||
|
||||
// clear our buckets
|
||||
bucketsPerHost.clear();
|
||||
|
||||
getRequests().forEach(getDataRequest -> send(networkNode, getDataRequest));
|
||||
|
||||
report();
|
||||
}
|
||||
|
||||
protected abstract List<NetworkEnvelope> getRequests();
|
||||
|
||||
protected void send(NetworkNode networkNode, NetworkEnvelope message) {
|
||||
|
||||
ArrayList<Thread> threadList = new ArrayList<>();
|
||||
|
||||
// for each configured host
|
||||
for (String current : configuration.getProperty(HOSTS, "").split(",")) {
|
||||
threadList.add(new Thread(() -> {
|
||||
|
||||
try {
|
||||
// parse Url
|
||||
NodeAddress target = OnionParser.getNodeAddress(current);
|
||||
|
||||
// do the data request
|
||||
aboutToSend(message);
|
||||
SettableFuture<Connection> future = networkNode.sendMessage(target, message);
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(Connection connection) {
|
||||
connection.addMessageListener(P2PSeedNodeSnapshotBase.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
gate.proceed();
|
||||
log.error(
|
||||
"Sending {} failed. That is expected if the peer is offline.\n\tException={}", message.getClass().getSimpleName(), throwable.getMessage());
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
|
||||
} catch (Exception e) {
|
||||
gate.proceed(); // release the gate on error
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, current));
|
||||
}
|
||||
|
||||
gate.engage(threadList.size());
|
||||
|
||||
// start all threads and wait until they all finished. We do that so we can
|
||||
// minimize the time between querying the hosts and therefore the chance of
|
||||
// inconsistencies.
|
||||
threadList.forEach(Thread::start);
|
||||
|
||||
gate.await();
|
||||
}
|
||||
|
||||
protected void aboutToSend(NetworkEnvelope message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report all the stuff. Uses the configured reporter directly.
|
||||
*/
|
||||
abstract void report();
|
||||
|
||||
@Override
|
||||
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||
if (treatMessage(networkEnvelope, connection)) {
|
||||
gate.proceed();
|
||||
} else if (networkEnvelope instanceof CloseConnectionMessage) {
|
||||
gate.unlock();
|
||||
} else {
|
||||
log.warn("Got an unexpected message of type <{}>",
|
||||
networkEnvelope.getClass().getSimpleName());
|
||||
}
|
||||
connection.removeMessageListener(this);
|
||||
}
|
||||
|
||||
protected abstract boolean treatMessage(NetworkEnvelope networkEnvelope, Connection connection);
|
||||
}
|
@ -1,165 +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.metric;
|
||||
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.OnionParser;
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import bisq.asset.Asset;
|
||||
import bisq.asset.AssetRegistry;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
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.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Fetches fee and price data from the configured price nodes.
|
||||
* Based on the work of HarryMcFinned.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
* @author HarryMcFinned
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class PriceNodeStats extends Metric {
|
||||
|
||||
private static final String HOSTS = "run.hosts";
|
||||
private static final String IGNORE = "dashTxFee ltcTxFee dogeTxFee";
|
||||
// poor mans JSON parser
|
||||
private final Pattern stringNumberPattern = Pattern.compile("\"(.+)\" ?: ?(\\d+)");
|
||||
private final Pattern pricePattern = Pattern.compile("\"price\" ?: ?([\\d.]+)");
|
||||
private final Pattern currencyCodePattern = Pattern.compile("\"currencyCode\" ?: ?\"([A-Z]+)\"");
|
||||
private final List<Object> assets = Arrays.asList(new AssetRegistry().stream().map(Asset::getTickerSymbol).toArray());
|
||||
|
||||
public PriceNodeStats(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
try {
|
||||
// fetch proxy
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
Socks5Proxy proxy = tor.getProxy();
|
||||
|
||||
String[] hosts = configuration.getProperty(HOSTS, "").split(",");
|
||||
|
||||
Collections.shuffle(Arrays.asList(hosts));
|
||||
|
||||
// for each configured host
|
||||
for (String current : hosts) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
// parse Url
|
||||
NodeAddress tmp = OnionParser.getNodeAddress(current);
|
||||
|
||||
// connect
|
||||
try {
|
||||
SocksSocket socket = new SocksSocket(proxy, tmp.getHostName(), tmp.getPort());
|
||||
|
||||
// prepare to receive data
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
||||
// ask for fee data
|
||||
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
|
||||
out.println("GET /getFees/");
|
||||
out.println();
|
||||
out.flush();
|
||||
|
||||
// sift through the received lines and see if we got something json-like
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
Matcher matcher = stringNumberPattern.matcher(line);
|
||||
if (matcher.find())
|
||||
if (!IGNORE.contains(matcher.group(1)))
|
||||
result.put("fees." + matcher.group(1), matcher.group(2));
|
||||
}
|
||||
|
||||
in.close();
|
||||
out.close();
|
||||
socket.close();
|
||||
|
||||
// connect
|
||||
socket = new SocksSocket(proxy, tmp.getHostName(), tmp.getPort());
|
||||
|
||||
// prepare to receive data
|
||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
||||
// ask for exchange rate data
|
||||
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
|
||||
out.println("GET /getAllMarketPrices/");
|
||||
out.println();
|
||||
out.flush();
|
||||
|
||||
String currencyCode = "";
|
||||
while ((line = in.readLine()) != null) {
|
||||
Matcher currencyCodeMatcher = currencyCodePattern.matcher(line);
|
||||
Matcher priceMatcher = pricePattern.matcher(line);
|
||||
if (currencyCodeMatcher.find()) {
|
||||
currencyCode = currencyCodeMatcher.group(1);
|
||||
if (!assets.contains(currencyCode))
|
||||
currencyCode = "";
|
||||
} else if (!"".equals(currencyCode) && priceMatcher.find())
|
||||
result.put("price." + currencyCode, priceMatcher.group(1));
|
||||
}
|
||||
|
||||
// close all the things
|
||||
in.close();
|
||||
out.close();
|
||||
socket.close();
|
||||
|
||||
// report
|
||||
reporter.report(result, getName());
|
||||
|
||||
// only ask for data as long as we got none
|
||||
if (!result.isEmpty())
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
log.error("{} seems to be down. Trying next configured price node.", tmp.getHostName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (TorCtlException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,82 +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.metric;
|
||||
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.Monitor;
|
||||
import bisq.monitor.Reporter;
|
||||
import bisq.monitor.ThreadGate;
|
||||
|
||||
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();
|
||||
private final ThreadGate gate = new ThreadGate();
|
||||
|
||||
public TorHiddenServiceStartupTime(Reporter reporter) {
|
||||
super(reporter);
|
||||
}
|
||||
|
||||
@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(Monitor.TOR_WORKING_DIR + "/" + hiddenServiceDirectory).delete();
|
||||
|
||||
log.debug("creating the hidden service");
|
||||
|
||||
gate.engage();
|
||||
|
||||
// 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, getName());
|
||||
log.debug("the hidden service is ready");
|
||||
gate.proceed();
|
||||
return null;
|
||||
});
|
||||
|
||||
gate.await();
|
||||
log.debug("going to revoke the hidden service...");
|
||||
hiddenServiceSocket.close();
|
||||
log.debug("[going to revoke the hidden service...] done");
|
||||
}
|
||||
}
|
@ -1,92 +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.metric;
|
||||
|
||||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.OnionParser;
|
||||
import bisq.monitor.Reporter;
|
||||
import bisq.monitor.StatisticsHelper;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
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.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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
|
||||
NodeAddress tmp = OnionParser.getNodeAddress(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.getHostName(), tmp.getPort());
|
||||
|
||||
// by the time we get here, we are connected
|
||||
samples.add(System.currentTimeMillis() - start);
|
||||
|
||||
// cleanup
|
||||
socket.close();
|
||||
}
|
||||
|
||||
// report
|
||||
reporter.report(StatisticsHelper.process(samples), getName());
|
||||
}
|
||||
} catch (TorCtlException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,88 +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.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("monitor/work/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) {
|
||||
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, getName());
|
||||
} catch (TorCtlException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// cleanup
|
||||
if (tor != null)
|
||||
tor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,70 +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.reporter;
|
||||
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.BaseCurrencyNetwork;
|
||||
|
||||
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, prefix);
|
||||
|
||||
}
|
||||
|
||||
@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) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
values.forEach((key, value) -> {
|
||||
report(key, value, timestamp, prefix);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(String key, String value, String timestamp, String prefix) {
|
||||
System.err.println("Report: bisq" + (Version.getBaseCurrencyNetwork() != 0 ? "-" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].getNetwork() : "")
|
||||
+ (prefix.isEmpty() ? "" : "." + prefix)
|
||||
+ (key.isEmpty() ? "" : "." + key)
|
||||
+ " " + value + " " + timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
report(values, "");
|
||||
}
|
||||
}
|
@ -1,106 +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.reporter;
|
||||
|
||||
import bisq.monitor.OnionParser;
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.BaseCurrencyNetwork;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.TorSocket;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
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, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values, String prefix) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
values.forEach((key, value) -> {
|
||||
|
||||
report(key, value, timestamp, prefix);
|
||||
try {
|
||||
// give Tor some slack
|
||||
// TODO maybe use the pickle protocol?
|
||||
// https://graphite.readthedocs.io/en/latest/feeding-carbon.html
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(String key, String value, String timeInMilliseconds, String prefix) {
|
||||
// https://graphite.readthedocs.io/en/latest/feeding-carbon.html
|
||||
String report = "bisq" + (Version.getBaseCurrencyNetwork() != 0 ? "-" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].getNetwork() : "")
|
||||
+ (prefix.isEmpty() ? "" : "." + prefix)
|
||||
+ (key.isEmpty() ? "" : "." + key)
|
||||
+ " " + value + " " + Long.parseLong(timeInMilliseconds) / 1000 + "\n";
|
||||
|
||||
try {
|
||||
NodeAddress nodeAddress = OnionParser.getNodeAddress(configuration.getProperty("serviceUrl"));
|
||||
Socket socket;
|
||||
if (nodeAddress.getFullAddress().contains(".onion"))
|
||||
socket = new TorSocket(nodeAddress.getHostName(), nodeAddress.getPort());
|
||||
else
|
||||
socket = new Socket(nodeAddress.getHostName(), nodeAddress.getPort());
|
||||
|
||||
socket.getOutputStream().write(report.getBytes(Charsets.UTF_8));
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
report(values, "");
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
<logger name="org.berndpruenster.netlayer.tor" level="INFO"/>
|
||||
</configuration>
|
@ -1,79 +0,0 @@
|
||||
## System configuration
|
||||
|
||||
# true overwrites the reporters picked by the developers (for debugging for example) (defaults to false)
|
||||
System.useConsoleReporter=true
|
||||
|
||||
# 0 -> BTC_MAINNET, 1 -> BTC_TESTNET (default)
|
||||
System.baseCurrencyNetwork=0
|
||||
|
||||
## 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 to your liking
|
||||
|
||||
#TorStartupTime Metric
|
||||
TorStartupTime.enabled=false
|
||||
TorStartupTime.run.interval=100
|
||||
TorStartupTime.run.socksPort=90500
|
||||
|
||||
TorRoundTripTime.enabled=false
|
||||
TorRoundTripTime.run.interval=100
|
||||
TorRoundTripTime.run.sampleSize=3
|
||||
# torproject.org hidden service
|
||||
TorRoundTripTime.run.hosts=http://expyuzz4wqqyqhjn.onion:80
|
||||
|
||||
#TorHiddenServiceStartupTime Metric
|
||||
TorHiddenServiceStartupTime.enabled=false
|
||||
TorHiddenServiceStartupTime.run.interval=100
|
||||
TorHiddenServiceStartupTime.run.localPort=90501
|
||||
TorHiddenServiceStartupTime.run.servicePort=90511
|
||||
|
||||
#P2PRoundTripTime Metric
|
||||
P2PRoundTripTime.enabled=false
|
||||
P2PRoundTripTime.run.interval=100
|
||||
P2PRoundTripTime.run.sampleSize=5
|
||||
P2PRoundTripTime.run.hosts=723ljisnynbtdohi.onion:8000, fl3mmribyxgrv63c.onion:8000
|
||||
P2PRoundTripTime.run.torProxyPort=9060
|
||||
|
||||
#P2PNetworkLoad Metric
|
||||
P2PNetworkLoad.enabled=false
|
||||
P2PNetworkLoad.run.interval=100
|
||||
P2PNetworkLoad.run.torProxyPort=9061
|
||||
P2PNetworkLoad.run.historySize=200
|
||||
|
||||
#P2PSeedNodeSnapshotBase Metric
|
||||
P2PSeedNodeSnapshot.enabled=true
|
||||
P2PSeedNodeSnapshot.run.dbDir=bisq/p2p/build/resources/main/
|
||||
P2PSeedNodeSnapshot.run.interval=24
|
||||
P2PSeedNodeSnapshot.run.hosts=3f3cu2yw7u457ztq.onion:8000, 723ljisnynbtdohi.onion:8000, fl3mmribyxgrv63c.onion:8000
|
||||
P2PSeedNodeSnapshot.run.torProxyPort=9062
|
||||
|
||||
#P2PMarketStats Metric
|
||||
P2PMarketStats.enabled=false
|
||||
P2PMarketStats.run.interval=37
|
||||
P2PMarketStats.run.dbDir=bisq/p2p/build/resources/main/
|
||||
P2PMarketStats.run.hosts=ef5qnzx6znifo3df.onion:8000
|
||||
P2PMarketStats.run.torProxyPort=9063
|
||||
|
||||
#PriceNodeStats Metric
|
||||
PriceNodeStats.enabled=false
|
||||
PriceNodeStats.run.interval=42
|
||||
PriceNodeStats.run.hosts=http://xc3nh4juf2hshy7e.onion, http://44mgyoe2b6oqiytt.onion, http://62nvujg5iou3vu3i.onion, http://ceaanhbvluug4we6.onion, http://gztmprecgqjq64zh.onion/
|
||||
|
||||
#MarketStats Metric
|
||||
MarketStats.enabled=false
|
||||
MarketStats.run.interval=191
|
||||
|
||||
#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=k6evlhg44acpchtc.onion:2003
|
@ -1,149 +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.reporter.ConsoleReporter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
@Disabled
|
||||
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() {
|
||||
try {
|
||||
Thread.sleep(50000);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
Metric.haltAllMetrics();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reloadConfig() throws InterruptedException, ExecutionException {
|
||||
// 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
|
||||
Metric.haltAllMetrics();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shutdown() {
|
||||
Dummy DUT = new Dummy();
|
||||
DUT.setName("Dummy");
|
||||
Properties dummyProperties = new Properties();
|
||||
dummyProperties.put("Dummy.enabled", "true");
|
||||
dummyProperties.put("Dummy.run.interval", "1");
|
||||
DUT.configure(dummyProperties);
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
Metric.haltAllMetrics();
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,118 +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.metric.P2PNetworkLoad;
|
||||
import bisq.monitor.reporter.ConsoleReporter;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.NativeTor;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Test the round trip time metric against the hidden service of tor project.org.
|
||||
*
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
@Disabled
|
||||
class P2PNetworkLoadTests {
|
||||
|
||||
/**
|
||||
* A dummy Reporter for development purposes.
|
||||
*/
|
||||
private class DummyReporter extends ConsoleReporter {
|
||||
|
||||
private Map<String, String> results;
|
||||
|
||||
@Override
|
||||
public void report(long value) {
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
Map<String, String> hasResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(long value, String prefix) {
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values, String prefix) {
|
||||
super.report(values, prefix);
|
||||
results = values;
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws TorCtlException {
|
||||
// simulate the tor instance available to all metrics
|
||||
Tor.setDefault(new NativeTor(Monitor.TOR_WORKING_DIR));
|
||||
}
|
||||
|
||||
@Test
|
||||
void run() throws Exception {
|
||||
DummyReporter reporter = new DummyReporter();
|
||||
|
||||
// configure
|
||||
Properties configuration = new Properties();
|
||||
configuration.put("P2PNetworkLoad.enabled", "true");
|
||||
configuration.put("P2PNetworkLoad.run.interval", "10");
|
||||
configuration.put("P2PNetworkLoad.run.hosts",
|
||||
"http://fl3mmribyxgrv63c.onion:8000, http://3f3cu2yw7u457ztq.onion:8000");
|
||||
|
||||
Metric DUT = new P2PNetworkLoad(reporter);
|
||||
// start
|
||||
DUT.configure(configuration);
|
||||
|
||||
// give it some time to start and then stop
|
||||
while (!DUT.enabled())
|
||||
Thread.sleep(500);
|
||||
Thread.sleep(20000);
|
||||
|
||||
Metric.haltAllMetrics();
|
||||
|
||||
// observe results
|
||||
Map<String, String> results = reporter.hasResults();
|
||||
Assert.assertFalse(results.isEmpty());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void cleanup() {
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
tor.shutdown();
|
||||
}
|
||||
}
|
@ -1,136 +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.metric.P2PRoundTripTime;
|
||||
import bisq.monitor.reporter.ConsoleReporter;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.NativeTor;
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
|
||||
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
|
||||
class P2PRoundTripTimeTests {
|
||||
|
||||
/**
|
||||
* A dummy Reporter for development purposes.
|
||||
*/
|
||||
private class DummyReporter extends ConsoleReporter {
|
||||
|
||||
private Map<String, String> results;
|
||||
|
||||
@Override
|
||||
public void report(long value) {
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
Map<String, String> hasResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values) {
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(long value, String prefix) {
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Map<String, String> values, String prefix) {
|
||||
super.report(values, prefix);
|
||||
results = values;
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws TorCtlException {
|
||||
// simulate the tor instance available to all metrics
|
||||
Tor.setDefault(new NativeTor(Monitor.TOR_WORKING_DIR));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"default", "3", "4", "10"})
|
||||
void run(String sampleSize) throws Exception {
|
||||
DummyReporter reporter = new DummyReporter();
|
||||
|
||||
// configure
|
||||
Properties configuration = new Properties();
|
||||
configuration.put("P2PRoundTripTime.enabled", "true");
|
||||
configuration.put("P2PRoundTripTime.run.interval", "2");
|
||||
if (!"default".equals(sampleSize))
|
||||
configuration.put("P2PRoundTripTime.run.sampleSize", sampleSize);
|
||||
// torproject.org hidden service
|
||||
configuration.put("P2PRoundTripTime.run.hosts", "http://fl3mmribyxgrv63c.onion:8000");
|
||||
configuration.put("P2PRoundTripTime.run.torProxyPort", "9052");
|
||||
|
||||
Metric DUT = new P2PRoundTripTime(reporter);
|
||||
// start
|
||||
DUT.configure(configuration);
|
||||
|
||||
// give it some time to start and then stop
|
||||
while (!DUT.enabled())
|
||||
Thread.sleep(2000);
|
||||
|
||||
Metric.haltAllMetrics();
|
||||
|
||||
// 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
|
||||
static void cleanup() {
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
tor.shutdown();
|
||||
}
|
||||
}
|
@ -1,115 +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.metric.PriceNodeStats;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Florian Reimair
|
||||
*/
|
||||
@Disabled
|
||||
public class PriceNodeStatsTests {
|
||||
|
||||
private final static File torWorkingDirectory = new File("monitor/" + PriceNodeStatsTests.class.getSimpleName());
|
||||
|
||||
/**
|
||||
* 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> results() {
|
||||
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(String key, String value, String timestamp, String prefix) {
|
||||
|
||||
}
|
||||
|
||||
@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 connect() {
|
||||
DummyReporter reporter = new DummyReporter();
|
||||
Metric DUT = new PriceNodeStats(reporter);
|
||||
|
||||
|
||||
Properties configuration = new Properties();
|
||||
configuration.put("PriceNodeStats.run.hosts", "http://5bmpx76qllutpcyp.onion");
|
||||
|
||||
DUT.configure(configuration);
|
||||
|
||||
DUT.execute();
|
||||
|
||||
Assert.assertNotNull(reporter.results());
|
||||
Assert.assertTrue(reporter.results.size() > 0);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
Tor tor = Tor.getDefault();
|
||||
checkNotNull(tor, "tor must not be null");
|
||||
tor.shutdown();
|
||||
torWorkingDirectory.delete();
|
||||
}
|
||||
|
||||
}
|
@ -1,115 +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.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(String key, String value, String timestamp, String prefix) {
|
||||
|
||||
}
|
||||
|
||||
@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);
|
||||
Metric.haltAllMetrics();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
@ -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.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(String key, String value, String timestamp, String prefix) {
|
||||
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
Metric.haltAllMetrics();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
@ -1,92 +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.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(String key, String value, String timestamp, String prefix) {
|
||||
|
||||
}
|
||||
|
||||
@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);
|
||||
Metric.haltAllMetrics();
|
||||
|
||||
// TODO Test fails due timing issue
|
||||
// observe results
|
||||
Assert.assertTrue(reporter.results() > 0);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
echo "[*] Stopping Bisq Server monitoring utensils"
|
||||
echo ' '
|
||||
echo 'This script will not remove any configuration or binaries from the system. It just stops the services.'
|
||||
|
||||
sleep 10
|
||||
sudo systemctl stop nginx
|
||||
sudo systemctl stop collectd
|
||||
sudo systemctl disable nginx
|
||||
sudo systemctl disable collectd
|
||||
echo "[*] Done!"
|
@ -6,7 +6,6 @@ include 'core'
|
||||
include 'cli'
|
||||
include 'daemon'
|
||||
include 'desktop'
|
||||
include 'monitor'
|
||||
include 'seednode'
|
||||
include 'statsnode'
|
||||
include 'apitest'
|
||||
|
Loading…
Reference in New Issue
Block a user