add baseline pubsub test plan (#6)
* refactor baseline test out of private repo * rm references to attackers * fix default plan name & add widget to override * add configs for local runners * update runner notebook intro text * fix build tags * increase setup time in saved configs
This commit is contained in:
parent
8f4fb1bfca
commit
f0762b1814
|
@ -0,0 +1,41 @@
|
|||
|
||||
# ignore transient paths for pubsub tests
|
||||
pubsub/venv/
|
||||
pubsub/scripts/output/
|
||||
pubsub/scripts/config-snapshot.json
|
||||
pubsub/scripts/configs/snapshot.json
|
||||
__pycache__/
|
||||
.ipynb_checkpoints/
|
||||
|
||||
# Created by https://www.gitignore.io/api/go,visualstudiocode
|
||||
# Edit at https://www.gitignore.io/?templates=go,visualstudiocode
|
||||
|
||||
### Go ###
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
### Go Patch ###
|
||||
/vendor/
|
||||
/Godeps/
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
|
||||
# End of https://www.gitignore.io/api/go,visualstudiocode
|
|
@ -0,0 +1,319 @@
|
|||
# `Plan:` gossipsub performs at scale
|
||||
|
||||
This test plan evaluates the performance of gossipsub, the gossiping mesh routing protocol
|
||||
implemented by [go-libp2p-pubsub](https://github.com/libp2p/go-libp2p-pubsub).
|
||||
|
||||
## Installation & Setup
|
||||
|
||||
We're using python to generate testground composition files, and we shell out to a few
|
||||
external commands, so there's some environment setup to do.
|
||||
|
||||
### Requirements
|
||||
|
||||
#### Hardware
|
||||
|
||||
While no special hardware is needed to run the tests, running with a lot of test instances requires considerable CPU and
|
||||
RAM, and will likely exceed the capabilities of a single machine.
|
||||
|
||||
A large workstation with many CPU cores can reasonably run a few hundred instances using the testground
|
||||
`local:docker` runner, although the exact limit will require some trial and error. In early testing we were able to
|
||||
run 500 containers on a 56 core Xeon W-3175X with 124 GiB of RAM, although it's possible we could run more
|
||||
now that we've optimized things a bit.
|
||||
|
||||
It's useful to run with the `local:docker` or `local:exec` runners during test development, so long as you use
|
||||
fairly small instance counts (~25 or so works fine on a 2018 13" MacBook Pro with 16 GB RAM).
|
||||
|
||||
When using the `local:docker` runner, it's a good idea to periodically garbage collect the docker images created by
|
||||
testground using `docker system prune` to reclaim disk space.
|
||||
|
||||
To run larger tests like the ones in the [saved configurations](#saved-test-configurations), you'll need a
|
||||
kubernetes cluster.
|
||||
|
||||
To create your own cluster, follow the directions in the [testground/infra repo](https://github.com/testground/infra)
|
||||
to provision a cluster on AWS, and configure your tests to use the `cluster:k8s` test runner.
|
||||
|
||||
The testground daemon process can be running on your local machine, as long as it has access to the k8s cluster.
|
||||
The machine running the daemon must have Docker installed, and the user account must have permission to use
|
||||
docker and have the correct AWS credentials to connect to the cluster.
|
||||
When running tests on k8s, the machine running the testground daemon doesn't need a ton of resources,
|
||||
but ideally it should have a fast internet connection to push the Docker images to the cluster.
|
||||
|
||||
Running the analysis notebooks benefits from multiple cores and consumes quite a bit of RAM, especially on the first
|
||||
run when it's converting data to pandas format. It's best to have at least 8 GB of RAM free when running the analysis
|
||||
notebook for the first time.
|
||||
|
||||
Also note that closing the browser tab containing a running Jupyter notebook does not stop the python kernel and reclaim
|
||||
the memory used. It's best to select `Close and Halt` from the Jupyter `File` menu when you're done with the analysis
|
||||
notebook instead of just closing the tab.
|
||||
|
||||
#### Testground
|
||||
|
||||
You'll need to have the [testground](https://github.com/testground/testground) binary built and accessible
|
||||
on your `$PATH`. Testground must be version 0.5.0 or newer.
|
||||
|
||||
After running `testground list` for the first time, you should have a `~/testground` directory. You can change this
|
||||
to another location by setting the `TESTGROUND_HOME` environment variable.
|
||||
|
||||
#### Cloning this repo
|
||||
|
||||
The testground client will look for test plans in `$TESTGROUND_HOME/plans`, so this repo should be cloned or
|
||||
symlinked into there:
|
||||
|
||||
```shell
|
||||
cd ~/testground/plans # if this dir doesn't exist, run 'testground list' once first to create it
|
||||
git clone git@github.com:libp2p/test-plans libp2p-plans
|
||||
```
|
||||
|
||||
#### Python
|
||||
|
||||
We need python 3.7 or later, ideally in a virtual environment. If you have python3 installed, you can create
|
||||
a virtual environment named `venv` in this repo and it will be ignored by git:
|
||||
|
||||
```shell
|
||||
python3 -m venv venv
|
||||
```
|
||||
|
||||
After creating the virtual environment, you need to "activate" it for each shell session:
|
||||
|
||||
```shell
|
||||
# bash / zsh:
|
||||
source ./venv/bin/activate
|
||||
|
||||
# fish:
|
||||
source ./venv/bin/activate.fish
|
||||
```
|
||||
|
||||
You'll also need to install the python packages used by the scripts:
|
||||
|
||||
```shell
|
||||
pip install -r scripts/requirements.txt
|
||||
```
|
||||
|
||||
#### External binaries
|
||||
|
||||
The run scripts rely on a few commands being present on the `PATH`:
|
||||
|
||||
- the `testground` binary
|
||||
- `go`
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Running using the Runner Jupyter notebook
|
||||
|
||||
With the python virtualenv active, run
|
||||
|
||||
```shell
|
||||
jupyter notebook
|
||||
```
|
||||
|
||||
This will start a Jupyter notebook server and open a browser to the Jupyter file navigator.
|
||||
In the Jupyter UI, navigate to the `scripts` dir and open `Runner.ipynb`.
|
||||
|
||||
This will open the runner notebook, which lets you configure the test parameters using a
|
||||
configuration UI.
|
||||
|
||||
You'll need to run all the cells to prepare the notebook UI using `Cell menu > Run All`. You can reset
|
||||
the notebook state using the `Kernel Menu > Restart and Run All` command.
|
||||
|
||||
The cell at the bottom of the notebook has a "Run Test" button that will convert the configured parameters
|
||||
to a composition file and start running the test. It will shell out to the `testground` client binary,
|
||||
so if you get an error about a missing executable, make sure `testground` is on your `PATH` and restart
|
||||
the Jupyter server.
|
||||
|
||||
At the end of a successful test, there will be a new `output/pubsub-test-$timestamp` directory (relative to
|
||||
the `scripts` dir) containing the composition file, the full `test-output.tgz` file collected from testground,
|
||||
and an `analysis` directory.
|
||||
|
||||
The `analysis` directory has relevant files that were extracted from the `test-output.tgz` archive, along with a
|
||||
new Jupyter notebook, `Analysis.ipynb`. See below for more details about the analysis notebook.
|
||||
|
||||
If the test fails (`testground` returns a non-zero exit code), the runner script will move the `pubsub-test-$timestamp`
|
||||
dir to `./output/failed`.
|
||||
|
||||
The "Test Execution" section of the config UI will let you override the output path, for example if you want
|
||||
to give your test a meaningful name.
|
||||
|
||||
### Targeting a specific version of go-libp2p-pubsub
|
||||
|
||||
The default configuration is to test against the current `master` branch of go-libp2p-pubsub,
|
||||
but you can change that in the `Pubsub` panel of the configuration UI. You can enter the name
|
||||
of a branch or tag, or the full SHA-1 hash of a specific commit.
|
||||
|
||||
**Important:** if you target a version before [the Gossipsub v1.1 PR](https://github.com/libp2p/go-libp2p-pubsub/pull/273)
|
||||
was merged, you must uncheck the "target hardening branch API" checkbox to avoid build failures due to
|
||||
missing methods.
|
||||
|
||||
#### Saved test configurations
|
||||
|
||||
You can save configuration snapshots to JSON files and load them again using the buttons at the bottom
|
||||
of the configuration panel. The snapshots contain the state of all the configuration widgets, so can
|
||||
only be used with the Runner notebook, not the command line `run.py` script.
|
||||
|
||||
There are several saved configs in `scripts/configs` that we've been using to evaluate different scenarios.
|
||||
|
||||
There are subdirectories inside of `scripts/configs` corresponding to different `testground` Runners, and
|
||||
there are a few configurations for each runner with various node counts. For example, `configs/local-exec/25-peers.json`
|
||||
will create a composition for the test using the `exec:go` builder and `local:exec` runner, with 25 pubsub peers,
|
||||
while `configs/local-docker/25-peers.json` will use the `docker:go` and `local:docker` runner.
|
||||
|
||||
The saved configs all expect to find the `testground` daemon on a non-standard port (8080 instead of 8042).
|
||||
If you're not running the daemon on port 8080, you can change the endpoint in the `Testground` section of
|
||||
the config UI, or tell the daemon to listen on 8080 by editing `~/testground/.env.toml`.
|
||||
|
||||
### Running using the cli scripts
|
||||
|
||||
Inside the `scripts` directory, the `run.py` script will generate a composition and run it by shelling out to
|
||||
`testground`. If you just want it to generate the composition, you can skip the test run by passing the `--dry-run`
|
||||
flag.
|
||||
|
||||
You can get the full usage by running `./run.py --help`.
|
||||
|
||||
To run a test with baseline parameters (as defined in `scripts/templates/baseline/params/_base.toml`), run:
|
||||
|
||||
```shell
|
||||
./run.py
|
||||
```
|
||||
|
||||
By default, this will create a directory called `./output/pubsub-test-$timestamp`, which will have a `composition.toml`
|
||||
file inside, as well as a `template-params.toml` that contains the params used to generate the composition.
|
||||
|
||||
You can control the output location with the `-o` and `--name` flags, for example:
|
||||
|
||||
```shell
|
||||
./run.py -o /tmp --name 'foo'
|
||||
# creates directory at /tmp/pubsub-test-$timestamp-foo
|
||||
```
|
||||
|
||||
Note that the params defined in `scripts/templates/baseline/params/_base.toml` have very low instance counts and
|
||||
are likely useless for real-world evaluation of gossipsub.
|
||||
|
||||
You can override individual template parameters using the `-D` flag, for example, `./run.py -D T_RUN=5m`.
|
||||
There's no exhaustive list of template parameters, so check the template at `scripts/templates/baseline/template.toml.j2`
|
||||
to see what's defined.
|
||||
|
||||
Alternatively, you can create a new toml file containing the parameters you want to set, and it will override
|
||||
any parameters defined in `scripts/templates/baseline/params/_base.toml`
|
||||
|
||||
By default, the `run.py` script will extract the test data from the collected test output archive and copy the
|
||||
analysis notebook to the `analysis` subdirectory of the test output dir. If you want to skip this step,
|
||||
you can pass the `--skip-analysis` flag.
|
||||
|
||||
## Analyzing Test Outputs
|
||||
|
||||
After running a test, there should be a directory full of test outputs, with an `analysis` dir containing
|
||||
an `Analysis.ipynb` Jupyter notebook. If you're not already running the Jupyter server, start it with
|
||||
`jupyter notebook`, and use the Jupyter UI to navigate to the analysis notebook and open it.
|
||||
|
||||
Running all the cells in the analysis notebook will convert the extracted test data to
|
||||
[pandas](https://pandas.pydata.org/) `DataFrame`s. This conversion takes a minute or two depending on the
|
||||
size of the test and your hardware, but the results are cached to disk, so future runs should be pretty fast.
|
||||
|
||||
Once everything is loaded, you'll see some charts and tables, and there will be a new `figures` directory inside the
|
||||
`analysis` dir containing the charts in a few image formats. There's also a `figures.zip` with the same contents
|
||||
for easier downloading / storage.
|
||||
|
||||
### Running the analysis notebook from the command line
|
||||
|
||||
If you just want to generate the charts and don't care about interacting with the notebook, you can execute
|
||||
the analysis notebook using a cli script.
|
||||
|
||||
Change to the `scripts` directory, then run
|
||||
|
||||
```shell
|
||||
./analyze.py run_notebook ./output/pubsub-test-$timestamp
|
||||
```
|
||||
|
||||
This will copy the latest analysis notebook template into the `analysis` directory and execute the notebook, which
|
||||
will generate the chart images.
|
||||
|
||||
This command is useful if you've made changes to the analysis notebook template and want to re-run it against a
|
||||
bunch of existing test outputs. In that case, you can pass multiple paths to the `run_notebook` subcommand:
|
||||
|
||||
```shell
|
||||
./analyze.py run_notebook ./output/pubsub-test-*
|
||||
# will run the latest notebook against everything in `./output
|
||||
```
|
||||
|
||||
## Storing & Fetching Test Outputs in S3
|
||||
|
||||
The `scripts/sync_outputs.py` script is a wrapper around the [rclone](https://rclone.org) command that
|
||||
helps backup test outputs to an s3 bucket, or fetch a previously stored output directory to the local filesystem.
|
||||
|
||||
The AWS credentials are pulled from the environment - see [the AWS cli docs](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html)
|
||||
if you haven't already configured the aws cli to use your credentials.
|
||||
The configured user must have permission to access the bucket used to sync.
|
||||
|
||||
`rclone` must be installed and on the `$PATH` to use the `sync_outputs.py` script.
|
||||
|
||||
By default, it uses the S3 bucket `gossipsub-test-outputs` in `eu-central-1`, but you can control this with the
|
||||
`--bucket` and `--region` flags.
|
||||
|
||||
To backup all the test outputs in `./output`:
|
||||
|
||||
```shell
|
||||
./sync_outputs.py store-all ./output
|
||||
```
|
||||
|
||||
It will ignore the `failed` subdirectory automatically, but if you want to ignore more, you can pass in a flag:
|
||||
|
||||
```shell
|
||||
./sync_outputs.py store-all ./output --ignore some-dir-you-dont-want-to-store
|
||||
```
|
||||
|
||||
Alternatively, you can selectively store one or more test outputs with the `store` subcommand:
|
||||
|
||||
```shell
|
||||
./sync_outputs.py store ./output/pubsub-test-20200409-152658 ./output/pubsub-test-20200409-152983 # etc...
|
||||
```
|
||||
|
||||
You can also fetch test outputs from S3 to the local filesystem.
|
||||
To fetch everything from the bucket into `./output`:
|
||||
|
||||
```shell
|
||||
./sync_outputs.py fetch-all ./output
|
||||
```
|
||||
|
||||
Or, to fetch one or more tests from the bucket instead of everything:
|
||||
|
||||
```shell
|
||||
./sync_outputs.py fetch --dest=./output pubsub-test-20200409-152658
|
||||
```
|
||||
|
||||
You can list all the top-level directories in the S3 bucket (so you know what to fetch) using the `list` command:
|
||||
|
||||
```shell
|
||||
./sync_outputs.py list
|
||||
```
|
||||
|
||||
## Code Overview
|
||||
|
||||
The test code all lives in the `test` directory.
|
||||
|
||||
`main.go` is the actual entry point, but it just calls into the "real" main function, `RunSimulation`, which is defined
|
||||
in `run.go`.
|
||||
|
||||
`params.go` contains the parameter parsing code. The `parseParams` function will return a `testParams` struct with
|
||||
all test parameters.
|
||||
|
||||
The set of params provided to each test instance depends on which composition group they're in. The composition
|
||||
template we're using defines two groups: `publishers`, and `lurkers`. The lurkers and publishers have
|
||||
identical params with the exception of the boolean `publisher` param, which controls whether they will publish messages
|
||||
or just consume them.
|
||||
|
||||
After parsing the params, `RunSimulation` will prepare the libp2p `Host`s, do some network setup and then
|
||||
call `runPubsubNode` to begin the test.
|
||||
|
||||
`discovery.go` contains a `SyncDiscovery` component that uses the testground sync service to broadcast information about
|
||||
the test peers (e.g. addreses, whether they're honest, etc) with every other peer. It uses this information to connect
|
||||
nodes to each other in various topologies.
|
||||
|
||||
The honest node implementation is in `node.go`, and there are also `node_v10.go` and `node_v11.go` files
|
||||
that allow us to target the new gossipsub v1.1 API or the old v1.0 API by setting a build tag. If the v1.0 API
|
||||
is used, the test will not produce any peer score information, since that was added in v1.1.
|
||||
|
||||
The `tracer.go` file implements the `pubsub.EventTracer` interface to capture pubsub events and produce test metrics.
|
||||
Because the full tracer output is quite large (several GB for a few minutes of test execution with lots of nodes),
|
||||
we aggregate the trace events at runtime and spit out a json file with aggregated metrics at the end of the test.
|
||||
We also capture a filtered subset of the original traces, containing only Publish, Deliver, Graft, and Prune events.
|
||||
At the end of the test, we run [tracestat](https://github.com/libp2p/go-libp2p-pubsub-tracer/blob/master/cmd/tracestat/main.go)
|
||||
on the filtered traces to calculate the latency distribution and get a summary of publish and deliver counts.
|
|
@ -0,0 +1,508 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Pubsub Test Analysis Notebook\n",
|
||||
"\n",
|
||||
"## Usage\n",
|
||||
"\n",
|
||||
"This notebook analyzes the output of a single pubsub test execution.\n",
|
||||
"\n",
|
||||
"You must run all the cells (`Cell > Run All` menu) to load the data and generate the charts.\n",
|
||||
"\n",
|
||||
"This may take quite some time and require considerable RAM on the first\n",
|
||||
"run, but the results will be cached to `ANALYSIS_DIR/pandas` for future runs.\n",
|
||||
"\n",
|
||||
"<p/>\n",
|
||||
"\n",
|
||||
"<details>\n",
|
||||
" <summary>Expand to show example data</summary>\n",
|
||||
"\n",
|
||||
"### `scores`\n",
|
||||
"\n",
|
||||
"The `scores` `DataFrame` contains peer score events, indexed by timestamp.\n",
|
||||
"\n",
|
||||
"**Example**:\n",
|
||||
"\n",
|
||||
"| timestamp | observer | peer | score |\n",
|
||||
"|-------------------------------|------------------------------------------------------|------------------------------------------------------|-----------|\n",
|
||||
"| 2020-03-31 16:46:22.675526100 | 12D3KooWM1Q8EazdTBaYidtmAnaEqjtAzCGkYypzjgUmDU3bNpAS | 12D3KooWGHEfmKenMpsGDu1xEuVw7itsEMMT6oBVbYx642627Etw | 0.0027 |\n",
|
||||
"| 2020-03-31 16:46:22.675526100 | 12D3KooWM1Q8EazdTBaYidtmAnaEqjtAzCGkYypzjgUmDU3bNpAS | 12D3KooWN5fa46NhuVP9as8ANmZ54gAM5cPuhTuu1bMAY5wvgoPG | 16.5617 |\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"- `observer` is the peer assigning the score\n",
|
||||
"- `peer` is the peer receiving the score\n",
|
||||
"\n",
|
||||
"### `metrics`\n",
|
||||
"\n",
|
||||
"The `metrics` `DataFrame` contains aggregated tracer metrics.\n",
|
||||
"\n",
|
||||
"**Example**:\n",
|
||||
"\n",
|
||||
"| | published | rejected | delivered | duplicates | droppedrpc | peersadded | peersremoved | topicsjoined | topicsleft | peer | sent_rpcs | sent_messages | sent_grafts | sent_prunes | sent_iwants | sent_ihaves | recv_rpcs | recv_messages | recv_grafts | recv_prunes | recv_iwants | recv_ihaves |\n",
|
||||
"|----|-------------|------------|-------------|--------------|--------------|--------------|----------------|----------------|--------------|------------------------------------------------------|-------------|-----------------|---------------|---------------|---------------|---------------|-------------|-----------------|---------------|---------------|---------------|---------------|\n",
|
||||
"| 0 | 0 | 0 | 721 | 0 | 0 | 2 | 1 | 1 | 0 | 12D3KooWM1Q8EazdTBaYidtmAnaEqjtAzCGkYypzjgUmDU3bNpAS | 722 | 721 | 0 | 0 | 0 | 0 | 727 | 721 | 2 | 0 | 0 | 0 |\n",
|
||||
"| 1 | 0 | 0 | 721 | 0 | 0 | 2 | 1 | 1 | 0 | 12D3KooWN5fa46NhuVP9as8ANmZ54gAM5cPuhTuu1bMAY5wvgoPG | 724 | 721 | 1 | 0 | 0 | 0 | 726 | 721 | 1 | 0 | 0 | 0 |\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"</details>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"parameters"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Parameters in this cell can be overriden using papermill\n",
|
||||
"\n",
|
||||
"# path to directory contaning output from the extract_test_outputs method in analyze.py\n",
|
||||
"ANALYSIS_DIR=\".\"\n",
|
||||
"\n",
|
||||
"# dir to save figure images\n",
|
||||
"FIGURE_OUT=\"./figures\"\n",
|
||||
"\n",
|
||||
"# path to zip file containing all figures\n",
|
||||
"FIGURE_ZIP_OUT=\"./figures.zip\"\n",
|
||||
"\n",
|
||||
"# font sizes\n",
|
||||
"SMALL_SIZE = 8\n",
|
||||
"MEDIUM_SIZE = 10\n",
|
||||
"BIGGER_SIZE = 18"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import toml\n",
|
||||
"import ipywidgets as widgets\n",
|
||||
"from pprint import pprint\n",
|
||||
"import pathlib\n",
|
||||
"import seaborn as sns\n",
|
||||
"from durations import Duration\n",
|
||||
"\n",
|
||||
"import notebook_helper\n",
|
||||
"from notebook_helper import no_scores_message, load_pandas, archive_figures, p25, p50, p75, p95, p99\n",
|
||||
"\n",
|
||||
"# load a helper to save figures to FIGURE_OUT\n",
|
||||
"save_fig = notebook_helper.save_fig_fn(FIGURE_OUT)\n",
|
||||
"\n",
|
||||
"# render charts in a larger, zoomable style\n",
|
||||
"%matplotlib notebook\n",
|
||||
"\n",
|
||||
"# turn off autosaving for the notebook\n",
|
||||
"%autosave 0\n",
|
||||
"\n",
|
||||
"# prettify the colors\n",
|
||||
"sns.set(color_codes=True)\n",
|
||||
"\n",
|
||||
"# helper to set font sizes for charts\n",
|
||||
"def set_chart_fontsize(size):\n",
|
||||
" plt.rc('font', size=size) # controls default text sizes\n",
|
||||
" plt.rc('axes', titlesize=size) # fontsize of the axes title\n",
|
||||
" plt.rc('axes', labelsize=size) # fontsize of the x and y labels\n",
|
||||
" plt.rc('xtick', labelsize=size) # fontsize of the tick labels\n",
|
||||
" plt.rc('ytick', labelsize=size) # fontsize of the tick labels\n",
|
||||
" plt.rc('legend', fontsize=size) # legend fontsize\n",
|
||||
" plt.rc('figure', titlesize=size) # fontsize of the figure title\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"# set chart fonts to BIGGER_SIZE by default\n",
|
||||
"set_chart_fontsize(BIGGER_SIZE)\n",
|
||||
"\n",
|
||||
"# load data\n",
|
||||
"print('loading test data from ' + ANALYSIS_DIR)\n",
|
||||
"\n",
|
||||
"tables = load_pandas(ANALYSIS_DIR)\n",
|
||||
"scores = tables['scores']\n",
|
||||
"metrics = tables['metrics']\n",
|
||||
"cdf = tables['cdf']\n",
|
||||
"pdf = tables['pdf']\n",
|
||||
"peers = tables['peers']\n",
|
||||
"\n",
|
||||
"# resample score index into 5s windows for plotting later\n",
|
||||
"if not scores.empty:\n",
|
||||
" print('resampling peer scores')\n",
|
||||
" resample_interval = '5s'\n",
|
||||
" sampled = scores.resample(resample_interval)\n",
|
||||
"\n",
|
||||
"t_warm = peers['t_warm'].max()\n",
|
||||
"t_run = peers['t_run'].max()\n",
|
||||
"t_cool = peers['t_cool'].max()\n",
|
||||
"t_complete = peers['t_complete'].max()\n",
|
||||
"\n",
|
||||
"params_panel, test_params = notebook_helper.test_params_panel(ANALYSIS_DIR)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Test Parameters\n",
|
||||
"\n",
|
||||
"The cell below shows the parameters that were used to create the composition file for this test run.\n",
|
||||
"\n",
|
||||
"You can access the parameter values from other cells via the `test_params` dict, e.g.:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from durations import Duration\n",
|
||||
"warmup = Duration(test_params['T_WARM'])\n",
|
||||
"print('warmup seconds: {}'.format(warmup.to_seconds()))\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"params_panel"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Aggregations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Latency Distribution"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### CDF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = notebook_helper.plot_latency_cdf(cdf)\n",
|
||||
"save_fig(fig, 'latency-cdf')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### PDF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = notebook_helper.plot_latency_pdf(pdf)\n",
|
||||
"save_fig(fig, 'latency-pdf')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### PDF (above p99)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig = notebook_helper.plot_latency_pdf_above_quantile(pdf, quantile=0.99)\n",
|
||||
"save_fig(fig, 'latency-pdf-over-p99')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Tracestat summary\n",
|
||||
"Only Publish and Deliver counts are accurate, the rest are filtered."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(notebook_helper.tracestat_summary(ANALYSIS_DIR))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Aggregated tracer metrics (per-peer)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"metrics[['published', 'delivered', 'rejected', 'duplicates', 'droppedrpc']].agg([np.min, np.max, np.median, np.mean]).rename(columns={'amax': 'max', 'amin': 'min', 'amedian': 'median', 'amean': 'mean'})\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### All peer scores, aggregated across the test runtime"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if not scores.empty:\n",
|
||||
" scores['score'].agg({'min': np.min, 'max': np.max, 'median': np.median, 'mean': np.mean})\n",
|
||||
"else:\n",
|
||||
" no_scores_message()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Aggregated score values for peers with negative scores\n",
|
||||
"\n",
|
||||
"- `observer` is the peer assigning the score. \n",
|
||||
"- `peer` is the peer receiving the score."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if not scores.empty:\n",
|
||||
" neg = scores.where(scores['score'] < 0).groupby(['peer', 'observer'])\n",
|
||||
" n = neg.agg({'score': [np.min, np.max, np.median, np.mean]})\n",
|
||||
" n\n",
|
||||
"else:\n",
|
||||
" no_scores_message()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Show honest peers with negative scores, joined with tracer metrics"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if not scores.empty:\n",
|
||||
" # select columns from metrics table\n",
|
||||
" m = metrics[['peer', 'published', 'delivered', 'rejected']]\n",
|
||||
"\n",
|
||||
" pd.DataFrame(n).merge(m, on='peer').groupby('peer').head()\n",
|
||||
"else:\n",
|
||||
" no_scores_message()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### global min/max score over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"time_annotations = [\n",
|
||||
" {'label': 'warmup complete', 'time': t_run},\n",
|
||||
" {'label': 'cooldown begin', 'time': t_cool},\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"def annotate_score_plot(plot, label, legend_anchor=None):\n",
|
||||
" notebook_helper.annotate_score_plot(plot, label, legend_anchor=legend_anchor, time_annotations=time_annotations)\n",
|
||||
"\n",
|
||||
"if not scores.empty:\n",
|
||||
" fig, ax = plt.subplots(2, figsize=(11, 8))\n",
|
||||
" plt.subplots_adjust(hspace=0.5)\n",
|
||||
" fig.suptitle(\"Min / Max Peer Scores\")\n",
|
||||
" plot = sampled['score'].agg([np.min, np.max]).plot(ax=ax[0])\n",
|
||||
" annotate_score_plot(plot, 'All Peers min/max', legend_anchor=(0, -0.2))\n",
|
||||
" \n",
|
||||
" # hide bottom plot so legend is visible\n",
|
||||
" ax[1].set_visible(False)\n",
|
||||
" \n",
|
||||
" # write png\n",
|
||||
" save_fig(fig, 'score-min-max')\n",
|
||||
"else:\n",
|
||||
" no_scores_message()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### global mean / median score over time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if not scores.empty: \n",
|
||||
" # create 4 subplots - three for charts and one that we'll hide to make blank space\n",
|
||||
" # for the legend\n",
|
||||
" fig, ax = plt.subplots(2, figsize=(11, 8))\n",
|
||||
" plt.subplots_adjust(hspace=0.5)\n",
|
||||
" fig.suptitle('Mean / Median Peer Scores')\n",
|
||||
" plot = sampled['score'].agg([np.mean, np.median]).plot(ax=ax[0])\n",
|
||||
" annotate_score_plot(plot, 'All Peers mean/median', legend_anchor=(0, -0.2))\n",
|
||||
"\n",
|
||||
" # hide bottom plot so legend is visible\n",
|
||||
" ax[1].set_visible(False)\n",
|
||||
" \n",
|
||||
" save_fig(fig, 'score-mean-median')\n",
|
||||
"else:\n",
|
||||
" no_scores_message()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### mean score distribution (all peers)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if not scores.empty:\n",
|
||||
" plot = scores[['peer', 'score']].groupby('peer').mean().plot.hist(bins=20)\n",
|
||||
" fig = plot.get_figure()\n",
|
||||
" fig.suptitle('Mean score distribution (all peers)')\n",
|
||||
" save_fig(fig, 'score-global-mean-distribution')\n",
|
||||
"else:\n",
|
||||
" no_scores_message()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### score distributions (honest vs attacker)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"aggregations = [np.min, np.max, p25, p75, p95]\n",
|
||||
"kwargs = {'kind': 'hist', 'subplots': True, 'sharex': True, 'sharey': True, 'figsize': (8, 10)}\n",
|
||||
"\n",
|
||||
"if not scores.empty:\n",
|
||||
" scores_by_peer = scores.groupby('peer')\n",
|
||||
" \n",
|
||||
" plots = scores_by_peer['score'].agg(aggregations).plot(title='Score distributions (all peers)', **kwargs)\n",
|
||||
" for p in plots:\n",
|
||||
" p.set_ylabel('freq')\n",
|
||||
" fig = plots[0].get_figure()\n",
|
||||
" save_fig(fig, 'score-distributions-all-peers')\n",
|
||||
"\n",
|
||||
"else:\n",
|
||||
" no_scores_message()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# zip all figure images into a bundle\n",
|
||||
"archive_figures(FIGURE_OUT, FIGURE_ZIP_OUT)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.7"
|
||||
},
|
||||
"pycharm": {
|
||||
"stem_cell": {
|
||||
"cell_type": "raw",
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"source": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Pubsub Test Runner Notebook\n",
|
||||
"\n",
|
||||
"This notebook helps you configure a pubsub test plan and run it on testground.\n",
|
||||
"\n",
|
||||
"**Important**: when you first run the notebook after checking out from git, you'll probably need to restart the Jupyter kernel and run all cells in the notebook. Use the `Kernel > Restart and Run All` Juypter menu command.\n",
|
||||
"\n",
|
||||
"## Configuring the Test\n",
|
||||
"\n",
|
||||
"Use the UI widgets to dial in a configuration for the test run.\n",
|
||||
"\n",
|
||||
"### Saving and loading the config\n",
|
||||
"\n",
|
||||
"You can save a snapshot of the current configuration by pressing the `Save Config` button below, which will dump the current value of the UI to a json file (`configs/snapshot.json` by default).\n",
|
||||
"\n",
|
||||
"Loading a snapshot will set the UI to the values from the snapshot file.\n",
|
||||
"\n",
|
||||
"This is useful if you need to restart the notebook kernel but don't want to lose your current config. You could also use it to save a baseline config that you want to make tweaks to in a future run.\n",
|
||||
"\n",
|
||||
"The default `config/snapshot.json` file\n",
|
||||
"is ignored by git, so it can be used to quickly stash a config when reloading the notebook, etc. If you want to keep\n",
|
||||
"the config and check it into the repo, just save it with a different name.\n",
|
||||
"\n",
|
||||
"**NOTE**: The `Test Execution` output directory param is ignored when loading saved snapshots, to avoid overwriting the output of a prior run.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Running the test\n",
|
||||
"\n",
|
||||
"You'll need to be running the testground daemon somewhere accessible. By default, we'll attempt to use the daemon running at `localhost:8080`, but you can set the daemon endpoint param in the UI to target a different endpoint.\n",
|
||||
"\n",
|
||||
"Run the test by clicking the `Run Test` button below.\n",
|
||||
"This will invoke testground and print the testground client output below the cell.\n",
|
||||
"\n",
|
||||
"You can halt the test by interrupting the Jupyter kernel, either with the square Stop button in the Jupyter toolbar or with the `Kernel > Interrupt` menu item.\n",
|
||||
"\n",
|
||||
"To re-run a test, you can re-run the Jupyter cell that contains the `Run Test` button, which will clear any existing printed output and re-create the run button, but won't change any of the configuration values. Note that this will overwrite any test outputs from a previous run, unless you change the output dir in the `Test Execution` config before running again.\n",
|
||||
"\n",
|
||||
"### Test outputs\n",
|
||||
"\n",
|
||||
"At the moment, test ouputs are stored only on the machine where the notebook is running. The output directory is configurable in the `Test Execution` config UI, and defaults to a timestamped subdirectory of `./output`.\n",
|
||||
"\n",
|
||||
"Inside that directory will be:\n",
|
||||
"\n",
|
||||
"- a generated `composition.toml` file that was sent to testground to execute\n",
|
||||
"- a `template-params.toml` file that was used to parameterize the composition.\n",
|
||||
"- a `test-output.tgz` (or `.zip`) file containing the collected testground output from all instances\n",
|
||||
"- an `analysis` directory containing outputs from running the analysis script on the `test-output.tgz` archive\n",
|
||||
"\n",
|
||||
"The `anaylsis` directory will contain another Jupyter notebook called `Analysis.ipynb`. This contains interesting charts derived from the test output, and can be explored interactively. The path to the analysis notebook will be printed when the test completes successfully.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Troubleshooting\n",
|
||||
"\n",
|
||||
"Expand the sections below if you're having issues running.\n",
|
||||
"\n",
|
||||
"<br/>\n",
|
||||
"<details>\n",
|
||||
" <summary>\n",
|
||||
" <b>The UI Widgets aren't showing up</b>\n",
|
||||
" </summary>\n",
|
||||
"\n",
|
||||
"If you don't see any UI widgets, select `Restart & Run All` from the `Kernel` menu. If you're viewing this notebook in JupyterLab, make sure to [install the JupyterLab widgets extension](https://ipywidgets.readthedocs.io/en/latest/user_install.html#installing-the-jupyterlab-extension).\n",
|
||||
"</details>\n",
|
||||
"\n",
|
||||
"<br/>\n",
|
||||
"\n",
|
||||
"<details> \n",
|
||||
" <summary>\n",
|
||||
" <b>The test fails immediately</b>\n",
|
||||
" </summary>\n",
|
||||
" \n",
|
||||
"#### can't reach the daemon\n",
|
||||
"\n",
|
||||
"If the output has a line like\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"fatal error from daemon: Post http://localhost:8080/build: dial tcp [::1]:8080: connect: connection refused\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"it means that the daemon isn't running, or we can't connect to it on the given `host:port`. Make sure we can reach the daemon, for example by running `./testground list` manually in a shell on the same machine that's running the notebook.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"#### aws config not setup\n",
|
||||
"\n",
|
||||
"If you see something like:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"WARN\tengine build error: MissingRegion: could not find region configuration\t{\"ruid\": \"1255d41c\"}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"It probably means you're trying to run on kubernetes (which is the default), but your daemon isn't configured to run jobs on AWS. Consult the [infra docs](https://github.com/testground/infra) to make sure your cluster environment is\n",
|
||||
"set up correctly.\n",
|
||||
"\n",
|
||||
"</details> \n",
|
||||
"<br/>\n",
|
||||
"\n",
|
||||
"<details>\n",
|
||||
" <summary>\n",
|
||||
" <b>The test completes, but it prints errors about missing programs</b>\n",
|
||||
" </summary>\n",
|
||||
" \n",
|
||||
"The analysis script expects `go` to be on the `PATH`. Make sure that `go` is accessible and restart the Jupyter server.\n",
|
||||
" \n",
|
||||
"</details>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"init_cell": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import ui\n",
|
||||
"%autosave 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"init_cell": true,
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"config = ui.ConfigPanel()\n",
|
||||
"config.ui()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"init_cell": true,
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"b = ui.RunButton(config)\n",
|
||||
"b.wait()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import zipfile
|
||||
import gzip
|
||||
import tarfile
|
||||
import contextlib
|
||||
import tempfile
|
||||
import json
|
||||
import subprocess
|
||||
import pathlib
|
||||
import pandas as pd
|
||||
from glob import glob
|
||||
import multiprocessing as mp
|
||||
import shutil
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
ANALYSIS_NOTEBOOK_TEMPLATE = 'Analysis-Template.ipynb'
|
||||
|
||||
def mkdirp(dirpath):
|
||||
pathlib.Path(dirpath).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
commands = parser.add_subparsers()
|
||||
extract_cmd = commands.add_parser('extract', help='extract test outputs from testground output archive')
|
||||
extract_cmd.add_argument('test_output_zip_path', nargs=1,
|
||||
help='path to testground output zip or tgz file')
|
||||
|
||||
extract_cmd.add_argument('--output-dir', '-o', dest='output_dir', default=None,
|
||||
help='path to write output files. default is to create a new dir based on zip filename')
|
||||
extract_cmd.set_defaults(subcomment='extract')
|
||||
|
||||
run_notebook_cmd = commands.add_parser('run_notebook',
|
||||
help='runs latest analysis notebook against extracted test data')
|
||||
run_notebook_cmd.add_argument('test_result_dir', nargs='+',
|
||||
help='directories to run against. must contain an "analysis" subdir with extracted test data')
|
||||
run_notebook_cmd.set_defaults(subcommand='run_notebook')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def concat_files(names, outfile):
|
||||
for name in names:
|
||||
with open(name, 'rb') as f:
|
||||
outfile.write(f.read())
|
||||
|
||||
|
||||
# depending on which test runner was used, the collection archive may be either a zip (local docker & exec runner),
|
||||
# or a tar.gz file (k8s). Unfortunately, the zipfile and tarfile modules are different species of waterfowl,
|
||||
# so we duck typing doesn't help. So this method extracts whichever one we have to a temp directory and
|
||||
# returns the path to the temp dir.
|
||||
# use as a context manager, so the temp dir gets deleted when we're done:
|
||||
# with open_archive(archive_path) as a:
|
||||
# files = glob(a + '/**/tracer-output')
|
||||
@contextlib.contextmanager
|
||||
def open_archive(archive_path):
|
||||
# zipfile and tarfile both have an extractall method, at least
|
||||
if zipfile.is_zipfile(archive_path):
|
||||
z = zipfile.ZipFile(archive_path)
|
||||
else:
|
||||
z = tarfile.open(archive_path, 'r:gz')
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='pubsub-tg-archive-') as d:
|
||||
z.extractall(path=d)
|
||||
yield d
|
||||
|
||||
|
||||
# sugar around recursive glob search
|
||||
def find_files(dirname, filename_glob):
|
||||
path = '{}/**/{}'.format(dirname, filename_glob)
|
||||
return glob(path, recursive=True)
|
||||
|
||||
|
||||
PEER_INFO_PATTERN = re.compile(r'Host peer ID: ([0-9a-zA-Z]+), seq (\d+), node type: ([a-z]+), node type seq: (\d+), node index: (\d+) / (\d+)')
|
||||
def extract_peer_info(run_out):
|
||||
with open(run_out, 'rt') as f:
|
||||
for line in f.readlines():
|
||||
m = PEER_INFO_PATTERN.search(line)
|
||||
if m:
|
||||
pid = m.group(1)
|
||||
seq = int(m.group(2))
|
||||
node_type = m.group(3)
|
||||
node_type_seq = int(m.group(4))
|
||||
node_index = int(m.group(5))
|
||||
node_index_bound = int(m.group(6))
|
||||
return {'peer_id': pid,
|
||||
'type': node_type,
|
||||
'seq': seq,
|
||||
'node_type_seq': node_type_seq,
|
||||
'node_index': node_index,
|
||||
'node_index_bound': node_index_bound}
|
||||
print('warning: no peer info found in {}'.format(run_out))
|
||||
return None
|
||||
|
||||
|
||||
def extract_timing_info(run_out, node_type):
|
||||
if node_type == 'honest':
|
||||
times = dict(t_warm=0, t_connect=0, t_run=0, t_cool=0, t_complete=0)
|
||||
else:
|
||||
times = dict(t_connect=0)
|
||||
|
||||
with open(run_out, 'rt') as f:
|
||||
for line in f.readlines():
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
except BaseException as err:
|
||||
print("error parsing run output: ", err)
|
||||
continue
|
||||
if 'ts' not in obj or 'event' not in obj or obj['event'].get('type', '') != 'message':
|
||||
continue
|
||||
msg = obj['event']['message']
|
||||
ts = obj['ts']
|
||||
if re.match(r'connecting to peers.*', msg):
|
||||
times['t_connect'] = ts
|
||||
continue
|
||||
|
||||
# the rest of the times are only logged by honest peers
|
||||
if node_type != 'honest':
|
||||
continue
|
||||
if re.match(r'Wait for .* warmup time', msg):
|
||||
times['t_warm'] = ts
|
||||
continue
|
||||
if re.match(r'Wait for .* run time', msg):
|
||||
times['t_run'] = ts
|
||||
continue
|
||||
if re.match(r'Run time complete, cooling down.*', msg):
|
||||
times['t_cool'] = ts
|
||||
continue
|
||||
if msg == 'Cool down complete':
|
||||
times['t_complete'] = ts
|
||||
continue
|
||||
|
||||
for k, v in times.items():
|
||||
if v == 0:
|
||||
print('warning: unable to determine time value for {}'.format(k))
|
||||
return times
|
||||
|
||||
|
||||
def extract_peer_and_timing_info(run_out_files):
|
||||
entries = []
|
||||
for filename in run_out_files:
|
||||
info = extract_peer_info(filename)
|
||||
if info is None:
|
||||
continue
|
||||
times = extract_timing_info(filename, info.get('type', 'unknown'))
|
||||
info.update(times)
|
||||
entries.append(info)
|
||||
return entries
|
||||
|
||||
|
||||
def aggregate_output(output_zip_path, out_dir):
|
||||
topology = dict()
|
||||
|
||||
with open_archive(output_zip_path) as archive:
|
||||
tracefiles = find_files(archive, 'tracer-output*')
|
||||
names = [f for f in tracefiles if 'full' in f]
|
||||
if len(names) > 0:
|
||||
with gzip.open(os.path.join(out_dir, 'full-trace.bin.gz'), 'wb') as gz:
|
||||
concat_files(names, gz)
|
||||
|
||||
names = [f for f in tracefiles if 'filtered' in f]
|
||||
if len(names) > 0:
|
||||
with gzip.open(os.path.join(out_dir, 'filtered-trace.bin.gz'), 'wb') as gz:
|
||||
concat_files(names, gz)
|
||||
|
||||
# copy aggregate metrics files
|
||||
names = [f for f in tracefiles if 'aggregate' in f]
|
||||
for name in names:
|
||||
dest = os.path.join(out_dir, os.path.basename(name))
|
||||
shutil.copyfile(name, dest)
|
||||
|
||||
# copy peer score files
|
||||
names = find_files(archive, 'peer-scores*')
|
||||
for name in names:
|
||||
dest = os.path.join(out_dir, os.path.basename(name))
|
||||
shutil.copyfile(name, dest)
|
||||
|
||||
# get peer id -> seq mapping & timing info from run.out files
|
||||
names = find_files(archive, 'run.out')
|
||||
info = extract_peer_and_timing_info(names)
|
||||
dest = os.path.join(out_dir, 'peer-info.json')
|
||||
with open(dest, 'wt') as f:
|
||||
json.dump(info, f)
|
||||
|
||||
# Collect contents of all files of the form 'connections-honest-8-1'
|
||||
names = find_files(archive, 'connections*')
|
||||
for name in names:
|
||||
with open(name, 'r') as infile:
|
||||
name = os.path.basename(name)
|
||||
_, node_type, node_type_seq, node_idx = name.split('.')[0].split('-')
|
||||
conns = json.loads(infile.read())
|
||||
topology[node_type + '-' + node_type_seq + '-' + node_idx] = conns or []
|
||||
|
||||
# Write out topology file
|
||||
top_path = os.path.join(out_dir, 'topology.json')
|
||||
with open(top_path, 'wt') as outfile:
|
||||
outfile.write(json.dumps(topology))
|
||||
|
||||
|
||||
def run_tracestat(tracer_output_dir):
|
||||
full = os.path.join(tracer_output_dir, 'full-trace.bin.gz')
|
||||
filtered = os.path.join(tracer_output_dir, 'filtered-trace.bin.gz')
|
||||
if os.path.exists(full):
|
||||
tracer_output = full
|
||||
elif os.path.exists(filtered):
|
||||
tracer_output = filtered
|
||||
else:
|
||||
print('no event tracer output found, skipping tracestat')
|
||||
return
|
||||
|
||||
print('running tracestat on {}'.format(tracer_output))
|
||||
try:
|
||||
cmd = ['go', 'run', 'github.com/libp2p/go-libp2p-pubsub-tracer/cmd/tracestat', '-cdf', tracer_output]
|
||||
p = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
except BaseException as err:
|
||||
print('error calling tracestat: ', err)
|
||||
return
|
||||
|
||||
# split output into summary and latency CDF
|
||||
[summary, cdf] = p.stdout.split('=== Propagation Delay CDF (ms) ===')
|
||||
|
||||
with open(os.path.join(tracer_output_dir, 'tracestat-summary.txt'), 'w', encoding='utf8') as f:
|
||||
f.write(summary)
|
||||
with open(os.path.join(tracer_output_dir, 'tracestat-cdf.txt'), 'w', encoding='utf8') as f:
|
||||
f.write(cdf)
|
||||
|
||||
print(summary)
|
||||
|
||||
|
||||
def extract_test_outputs(test_output_zip_path, output_dir=None, convert_to_pandas=False, prep_notebook=True):
|
||||
if output_dir is None or output_dir == '':
|
||||
output_dir = os.path.join(os.path.dirname(test_output_zip_path), 'analysis')
|
||||
|
||||
mkdirp(output_dir)
|
||||
aggregate_output(test_output_zip_path, output_dir)
|
||||
run_tracestat(output_dir)
|
||||
|
||||
if convert_to_pandas:
|
||||
import notebook_helper
|
||||
print('converting data to pandas format...')
|
||||
notebook_helper.to_pandas(output_dir, os.path.join(output_dir, 'pandas'))
|
||||
if prep_notebook:
|
||||
prepare_analysis_notebook(analysis_dir=output_dir)
|
||||
return output_dir
|
||||
|
||||
|
||||
def prepare_analysis_notebook(analysis_dir):
|
||||
notebook_out = os.path.join(analysis_dir, 'Analysis.ipynb')
|
||||
shutil.copy(ANALYSIS_NOTEBOOK_TEMPLATE, notebook_out)
|
||||
shutil.copy('./notebook_helper.py', os.path.join(analysis_dir, 'notebook_helper.py'))
|
||||
print('saved analysis notebook to {}'.format(notebook_out))
|
||||
|
||||
|
||||
def run_analysis_notebook(analysis_dir):
|
||||
prepare_analysis_notebook(analysis_dir)
|
||||
notebook_path = os.path.join(analysis_dir, 'Analysis.ipynb')
|
||||
cmd = ['papermill', ANALYSIS_NOTEBOOK_TEMPLATE, notebook_path, '--cwd', analysis_dir]
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
except BaseException as err:
|
||||
print('error executing notebook: {}'.format(err), file=sys.stderr)
|
||||
return
|
||||
|
||||
|
||||
def run_notebooks(test_result_dirs):
|
||||
for d in test_result_dirs:
|
||||
analysis_dir = os.path.join(d, 'analysis')
|
||||
if not os.path.exists(analysis_dir):
|
||||
print('no analysis dir at {}, ignoring'.format(analysis_dir), file=sys.stderr)
|
||||
continue
|
||||
print('running analysis in {}'.format(analysis_dir))
|
||||
run_analysis_notebook(analysis_dir)
|
||||
|
||||
|
||||
def run():
|
||||
args = parse_args()
|
||||
if args.subcommand == 'extract':
|
||||
zip_filename = args.test_output_zip_path[0]
|
||||
extract_test_outputs(zip_filename, args.output_dir)
|
||||
elif args.subcommand == 'run_notebook':
|
||||
run_notebooks(args.test_result_dir)
|
||||
else:
|
||||
print('unknown subcommand', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
|
@ -0,0 +1 @@
|
|||
{"main": {"test_execution": {"output_dir": {"value": "./output/pubsub-test-20200511-162649"}, "failed_dir": {"value": "./output/failed"}}, "testground": {"daemon_endpoint": {"value": "localhost:8080"}, "builder": {"value": "docker:go"}, "runner": {"value": "cluster:k8s"}, "keep_service": {"value": false}, "log_level": {"value": "info"}}, "time": {"setup": {"value": "3m"}, "run": {"value": "3m"}, "warm": {"value": "30s"}, "cool": {"value": "30s"}}, "node_counts": {"total": {"value": 1000}, "publisher": {"value": 100}, "lurker": {"value": 900}, "honest_per_container": {"value": 1}}, "pubsub": {"branch": {"value": "master"}, "use_hardened_api": {"value": true}, "heartbeat": {"value": "1s"}, "hearbeat_delay": {"value": "100ms"}, "validate_queue_size": {"value": 1024}, "outbound_queue_size": {"value": 128}, "score_inspect_period": {"value": "5s"}, "full_traces": {"value": false}, "degree": {"value": 8}, "degree_lo": {"value": 6}, "degree_hi": {"value": 12}, "degree_score": {"value": 6}, "degree_lazy": {"value": 12}, "gossip_factor": {"value": 0.25}, "opportunistic_graft_ticks": {"value": 60}}, "network": {"latency": {"value": "25ms"}, "max_latency": {"value": "50ms"}, "jitter_pct": {"value": 10}, "bandwidth_mb": {"value": 10240}, "degree": {"value": 20}}, "honest_behavior": {"flood_publishing": {"value": true}, "connect_delay": {"value": "0s"}, "connect_jitter_pct": {"value": 5}}, "peer_score": {"gossip_threshold": {"value": -4000.0}, "publish_threshold": {"value": -5000.0}, "graylist_threshold": {"value": -10000.0}, "acceptpx_threshold": {"value": 0.0}, "opportunistic_graft_threshold": {"value": 0.0}, "ip_colocation_weight": {"value": 0.0}, "ip_colocation_threshold": {"value": 1}, "decay_interval": {"value": "1s"}, "decay_to_zero": {"value": 0.01}, "retain_score": {"value": "30s"}}}, "topic": {"topic_weight": {"value": 0.25}, "topic": {"name": {"value": "blocks"}, "message_rate": {"value": "120/s"}, "message_size": {"value": "2 KiB"}}, "score": {"time_in_mesh": {"weight": {"value": 0.0027}, "quantum": {"value": "1s"}, "cap": {"value": 3600.0}}, "first_message_deliveries": {"weight": {"value": 0.664}, "decay": {"value": 0.9916}, "cap": {"value": 1500.0}}, "mesh_message_deliveries": {"weight": {"value": -0.25}, "decay": {"value": 0.997}, "cap": {"value": 400.0}, "threshold": {"value": 10.0}, "activation": {"value": "1m"}, "window": {"value": "5ms"}}, "mesh_failure_penalty": {"weight": {"value": -0.25}, "decay": {"value": 0.997}}, "invalid_message_deliveries": {"weight": {"value": -99.0}, "decay": {"value": 0.9994}}}}}
|
|
@ -0,0 +1 @@
|
|||
{"main": {"test_execution": {"output_dir": {"value": "./output/pubsub-test-20200513-125310"}, "failed_dir": {"value": "./output/failed"}}, "testground": {"daemon_endpoint": {"value": "localhost:8080"}, "builder": {"value": "docker:go"}, "runner": {"value": "local:docker"}, "keep_service": {"value": false}, "log_level": {"value": "info"}}, "time": {"setup": {"value": "3m"}, "run": {"value": "3m"}, "warm": {"value": "30s"}, "cool": {"value": "30s"}}, "node_counts": {"total": {"value": 100}, "total_peers": {"value": 100}, "publisher": {"value": 10}, "lurker": {"value": 90}, "honest_per_container": {"value": 1}}, "pubsub": {"branch": {"value": "master"}, "use_hardened_api": {"value": true}, "heartbeat": {"value": "1s"}, "hearbeat_delay": {"value": "100ms"}, "validate_queue_size": {"value": 1024}, "outbound_queue_size": {"value": 128}, "score_inspect_period": {"value": "5s"}, "full_traces": {"value": false}, "degree": {"value": 8}, "degree_lo": {"value": 6}, "degree_hi": {"value": 12}, "degree_score": {"value": 6}, "degree_lazy": {"value": 12}, "gossip_factor": {"value": 0.25}, "opportunistic_graft_ticks": {"value": 60}}, "network": {"latency": {"value": "25ms"}, "max_latency": {"value": "50ms"}, "jitter_pct": {"value": 10}, "bandwidth_mb": {"value": 10240}, "degree": {"value": 20}}, "honest_behavior": {"flood_publishing": {"value": true}, "connect_delay": {"value": "0s"}, "connect_jitter_pct": {"value": 5}}, "peer_score": {"gossip_threshold": {"value": -4000.0}, "publish_threshold": {"value": -5000.0}, "graylist_threshold": {"value": -10000.0}, "acceptpx_threshold": {"value": 0.0}, "opportunistic_graft_threshold": {"value": 0.0}, "ip_colocation_weight": {"value": 0.0}, "ip_colocation_threshold": {"value": 1}, "decay_interval": {"value": "1s"}, "decay_to_zero": {"value": 0.01}, "retain_score": {"value": "30s"}}}, "topic": {"topic_weight": {"value": 0.25}, "topic": {"name": {"value": "blocks"}, "message_rate": {"value": "120/s"}, "message_size": {"value": "2 KiB"}}, "score": {"time_in_mesh": {"weight": {"value": 0.0027}, "quantum": {"value": "1s"}, "cap": {"value": 3600.0}}, "first_message_deliveries": {"weight": {"value": 0.664}, "decay": {"value": 0.9916}, "cap": {"value": 1500.0}}, "mesh_message_deliveries": {"weight": {"value": -0.25}, "decay": {"value": 0.997}, "cap": {"value": 400.0}, "threshold": {"value": 10.0}, "activation": {"value": "1m"}, "window": {"value": "5ms"}}, "mesh_failure_penalty": {"weight": {"value": -0.25}, "decay": {"value": 0.997}}, "invalid_message_deliveries": {"weight": {"value": -99.0}, "decay": {"value": 0.9994}}}}}
|
|
@ -0,0 +1 @@
|
|||
{"main": {"test_execution": {"output_dir": {"value": "./output/pubsub-test-20200513-125310"}, "failed_dir": {"value": "./output/failed"}}, "testground": {"daemon_endpoint": {"value": "localhost:8080"}, "builder": {"value": "docker:go"}, "runner": {"value": "local:docker"}, "keep_service": {"value": false}, "log_level": {"value": "info"}}, "time": {"setup": {"value": "3m"}, "run": {"value": "3m"}, "warm": {"value": "30s"}, "cool": {"value": "30s"}}, "node_counts": {"total": {"value": 25}, "total_peers": {"value": 25}, "publisher": {"value": 5}, "lurker": {"value": 20}, "honest_per_container": {"value": 1}}, "pubsub": {"branch": {"value": "master"}, "use_hardened_api": {"value": true}, "heartbeat": {"value": "1s"}, "hearbeat_delay": {"value": "100ms"}, "validate_queue_size": {"value": 1024}, "outbound_queue_size": {"value": 128}, "score_inspect_period": {"value": "5s"}, "full_traces": {"value": false}, "degree": {"value": 8}, "degree_lo": {"value": 6}, "degree_hi": {"value": 12}, "degree_score": {"value": 6}, "degree_lazy": {"value": 12}, "gossip_factor": {"value": 0.25}, "opportunistic_graft_ticks": {"value": 60}}, "network": {"latency": {"value": "25ms"}, "max_latency": {"value": "50ms"}, "jitter_pct": {"value": 10}, "bandwidth_mb": {"value": 10240}, "degree": {"value": 20}}, "honest_behavior": {"flood_publishing": {"value": true}, "connect_delay": {"value": "0s"}, "connect_jitter_pct": {"value": 5}}, "peer_score": {"gossip_threshold": {"value": -4000.0}, "publish_threshold": {"value": -5000.0}, "graylist_threshold": {"value": -10000.0}, "acceptpx_threshold": {"value": 0.0}, "opportunistic_graft_threshold": {"value": 0.0}, "ip_colocation_weight": {"value": 0.0}, "ip_colocation_threshold": {"value": 1}, "decay_interval": {"value": "1s"}, "decay_to_zero": {"value": 0.01}, "retain_score": {"value": "30s"}}}, "topic": {"topic_weight": {"value": 0.25}, "topic": {"name": {"value": "blocks"}, "message_rate": {"value": "120/s"}, "message_size": {"value": "2 KiB"}}, "score": {"time_in_mesh": {"weight": {"value": 0.0027}, "quantum": {"value": "1s"}, "cap": {"value": 3600.0}}, "first_message_deliveries": {"weight": {"value": 0.664}, "decay": {"value": 0.9916}, "cap": {"value": 1500.0}}, "mesh_message_deliveries": {"weight": {"value": -0.25}, "decay": {"value": 0.997}, "cap": {"value": 400.0}, "threshold": {"value": 10.0}, "activation": {"value": "1m"}, "window": {"value": "5ms"}}, "mesh_failure_penalty": {"weight": {"value": -0.25}, "decay": {"value": 0.997}}, "invalid_message_deliveries": {"weight": {"value": -99.0}, "decay": {"value": 0.9994}}}}}
|
|
@ -0,0 +1 @@
|
|||
{"main": {"test_execution": {"output_dir": {"value": "./output/pubsub-test-20200513-125310"}, "failed_dir": {"value": "./output/failed"}}, "testground": {"daemon_endpoint": {"value": "localhost:8080"}, "builder": {"value": "docker:go"}, "runner": {"value": "local:docker"}, "keep_service": {"value": false}, "log_level": {"value": "info"}}, "time": {"setup": {"value": "3m"}, "run": {"value": "3m"}, "warm": {"value": "30s"}, "cool": {"value": "30s"}}, "node_counts": {"total": {"value": 50}, "total_peers": {"value": 50}, "publisher": {"value": 5}, "lurker": {"value": 45}, "honest_per_container": {"value": 1}}, "pubsub": {"branch": {"value": "master"}, "use_hardened_api": {"value": true}, "heartbeat": {"value": "1s"}, "hearbeat_delay": {"value": "100ms"}, "validate_queue_size": {"value": 1024}, "outbound_queue_size": {"value": 128}, "score_inspect_period": {"value": "5s"}, "full_traces": {"value": false}, "degree": {"value": 8}, "degree_lo": {"value": 6}, "degree_hi": {"value": 12}, "degree_score": {"value": 6}, "degree_lazy": {"value": 12}, "gossip_factor": {"value": 0.25}, "opportunistic_graft_ticks": {"value": 60}}, "network": {"latency": {"value": "25ms"}, "max_latency": {"value": "50ms"}, "jitter_pct": {"value": 10}, "bandwidth_mb": {"value": 10240}, "degree": {"value": 20}}, "honest_behavior": {"flood_publishing": {"value": true}, "connect_delay": {"value": "0s"}, "connect_jitter_pct": {"value": 5}}, "peer_score": {"gossip_threshold": {"value": -4000.0}, "publish_threshold": {"value": -5000.0}, "graylist_threshold": {"value": -10000.0}, "acceptpx_threshold": {"value": 0.0}, "opportunistic_graft_threshold": {"value": 0.0}, "ip_colocation_weight": {"value": 0.0}, "ip_colocation_threshold": {"value": 1}, "decay_interval": {"value": "1s"}, "decay_to_zero": {"value": 0.01}, "retain_score": {"value": "30s"}}}, "topic": {"topic_weight": {"value": 0.25}, "topic": {"name": {"value": "blocks"}, "message_rate": {"value": "120/s"}, "message_size": {"value": "2 KiB"}}, "score": {"time_in_mesh": {"weight": {"value": 0.0027}, "quantum": {"value": "1s"}, "cap": {"value": 3600.0}}, "first_message_deliveries": {"weight": {"value": 0.664}, "decay": {"value": 0.9916}, "cap": {"value": 1500.0}}, "mesh_message_deliveries": {"weight": {"value": -0.25}, "decay": {"value": 0.997}, "cap": {"value": 400.0}, "threshold": {"value": 10.0}, "activation": {"value": "1m"}, "window": {"value": "5ms"}}, "mesh_failure_penalty": {"weight": {"value": -0.25}, "decay": {"value": 0.997}}, "invalid_message_deliveries": {"weight": {"value": -99.0}, "decay": {"value": 0.9994}}}}}
|
|
@ -0,0 +1 @@
|
|||
{"main": {"test_execution": {"output_dir": {"value": "./output/pubsub-test-20200513-125310"}, "failed_dir": {"value": "./output/failed"}}, "testground": {"daemon_endpoint": {"value": "localhost:8080"}, "builder": {"value": "exec:go"}, "runner": {"value": "local:exec"}, "keep_service": {"value": false}, "log_level": {"value": "info"}}, "time": {"setup": {"value": "3m"}, "run": {"value": "3m"}, "warm": {"value": "30s"}, "cool": {"value": "30s"}}, "node_counts": {"total": {"value": 25}, "total_peers": {"value": 25}, "publisher": {"value": 5}, "lurker": {"value": 20}, "honest_per_container": {"value": 1}}, "pubsub": {"branch": {"value": "master"}, "use_hardened_api": {"value": true}, "heartbeat": {"value": "1s"}, "hearbeat_delay": {"value": "100ms"}, "validate_queue_size": {"value": 1024}, "outbound_queue_size": {"value": 128}, "score_inspect_period": {"value": "5s"}, "full_traces": {"value": false}, "degree": {"value": 8}, "degree_lo": {"value": 6}, "degree_hi": {"value": 12}, "degree_score": {"value": 6}, "degree_lazy": {"value": 12}, "gossip_factor": {"value": 0.25}, "opportunistic_graft_ticks": {"value": 60}}, "network": {"latency": {"value": "25ms"}, "max_latency": {"value": "50ms"}, "jitter_pct": {"value": 10}, "bandwidth_mb": {"value": 10240}, "degree": {"value": 20}}, "honest_behavior": {"flood_publishing": {"value": true}, "connect_delay": {"value": "0s"}, "connect_jitter_pct": {"value": 5}}, "peer_score": {"gossip_threshold": {"value": -4000.0}, "publish_threshold": {"value": -5000.0}, "graylist_threshold": {"value": -10000.0}, "acceptpx_threshold": {"value": 0.0}, "opportunistic_graft_threshold": {"value": 0.0}, "ip_colocation_weight": {"value": 0.0}, "ip_colocation_threshold": {"value": 1}, "decay_interval": {"value": "1s"}, "decay_to_zero": {"value": 0.01}, "retain_score": {"value": "30s"}}}, "topic": {"topic_weight": {"value": 0.25}, "topic": {"name": {"value": "blocks"}, "message_rate": {"value": "120/s"}, "message_size": {"value": "2 KiB"}}, "score": {"time_in_mesh": {"weight": {"value": 0.0027}, "quantum": {"value": "1s"}, "cap": {"value": 3600.0}}, "first_message_deliveries": {"weight": {"value": 0.664}, "decay": {"value": 0.9916}, "cap": {"value": 1500.0}}, "mesh_message_deliveries": {"weight": {"value": -0.25}, "decay": {"value": 0.997}, "cap": {"value": 400.0}, "threshold": {"value": 10.0}, "activation": {"value": "1m"}, "window": {"value": "5ms"}}, "mesh_failure_penalty": {"weight": {"value": -0.25}, "decay": {"value": 0.997}}, "invalid_message_deliveries": {"weight": {"value": -99.0}, "decay": {"value": 0.9994}}}}}
|
|
@ -0,0 +1 @@
|
|||
{"main": {"test_execution": {"output_dir": {"value": "./output/pubsub-test-20200513-125310"}, "failed_dir": {"value": "./output/failed"}}, "testground": {"daemon_endpoint": {"value": "localhost:8080"}, "builder": {"value": "exec:go"}, "runner": {"value": "local:exec"}, "keep_service": {"value": false}, "log_level": {"value": "info"}}, "time": {"setup": {"value": "3m"}, "run": {"value": "3m"}, "warm": {"value": "30s"}, "cool": {"value": "30s"}}, "node_counts": {"total": {"value": 50}, "total_peers": {"value": 50}, "publisher": {"value": 5}, "lurker": {"value": 45}, "honest_per_container": {"value": 1}}, "pubsub": {"branch": {"value": "master"}, "use_hardened_api": {"value": true}, "heartbeat": {"value": "1s"}, "hearbeat_delay": {"value": "100ms"}, "validate_queue_size": {"value": 1024}, "outbound_queue_size": {"value": 128}, "score_inspect_period": {"value": "5s"}, "full_traces": {"value": false}, "degree": {"value": 8}, "degree_lo": {"value": 6}, "degree_hi": {"value": 12}, "degree_score": {"value": 6}, "degree_lazy": {"value": 12}, "gossip_factor": {"value": 0.25}, "opportunistic_graft_ticks": {"value": 60}}, "network": {"latency": {"value": "25ms"}, "max_latency": {"value": "50ms"}, "jitter_pct": {"value": 10}, "bandwidth_mb": {"value": 10240}, "degree": {"value": 20}}, "honest_behavior": {"flood_publishing": {"value": true}, "connect_delay": {"value": "0s"}, "connect_jitter_pct": {"value": 5}}, "peer_score": {"gossip_threshold": {"value": -4000.0}, "publish_threshold": {"value": -5000.0}, "graylist_threshold": {"value": -10000.0}, "acceptpx_threshold": {"value": 0.0}, "opportunistic_graft_threshold": {"value": 0.0}, "ip_colocation_weight": {"value": 0.0}, "ip_colocation_threshold": {"value": 1}, "decay_interval": {"value": "1s"}, "decay_to_zero": {"value": 0.01}, "retain_score": {"value": "30s"}}}, "topic": {"topic_weight": {"value": 0.25}, "topic": {"name": {"value": "blocks"}, "message_rate": {"value": "120/s"}, "message_size": {"value": "2 KiB"}}, "score": {"time_in_mesh": {"weight": {"value": 0.0027}, "quantum": {"value": "1s"}, "cap": {"value": 3600.0}}, "first_message_deliveries": {"weight": {"value": 0.664}, "decay": {"value": 0.9916}, "cap": {"value": 1500.0}}, "mesh_message_deliveries": {"weight": {"value": -0.25}, "decay": {"value": 0.997}, "cap": {"value": 400.0}, "threshold": {"value": 10.0}, "activation": {"value": "1m"}, "window": {"value": "5ms"}}, "mesh_failure_penalty": {"weight": {"value": -0.25}, "decay": {"value": 0.997}}, "invalid_message_deliveries": {"weight": {"value": -99.0}, "decay": {"value": 0.9994}}}}}
|
|
@ -0,0 +1,8 @@
|
|||
// This module only exists so that our script can invoke tracestat using `go run` instead of having to
|
||||
// install the tracestat binary.
|
||||
|
||||
module github.com/libp2p/test-plans/pubsub/scripts
|
||||
|
||||
go 1.14
|
||||
|
||||
require github.com/libp2p/go-libp2p-pubsub-tracer v0.0.0-20200120141315-151ce254cf29 // indirect
|
|
@ -0,0 +1,334 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
|
||||
github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4=
|
||||
github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
|
||||
github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU=
|
||||
github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
|
||||
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.4 h1:UlfXKrZx1DjZoBhQHmNHLC1fK1dUJDN20Y28A7s+gJ8=
|
||||
github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M=
|
||||
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw=
|
||||
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
|
||||
github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8=
|
||||
github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s=
|
||||
github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk=
|
||||
github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc=
|
||||
github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8=
|
||||
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
|
||||
github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc=
|
||||
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
|
||||
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs=
|
||||
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
|
||||
github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=
|
||||
github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ=
|
||||
github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ=
|
||||
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
|
||||
github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc=
|
||||
github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4=
|
||||
github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8=
|
||||
github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs=
|
||||
github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs=
|
||||
github.com/libp2p/go-libp2p v0.5.1/go.mod h1:Os7a5Z3B+ErF4v7zgIJ7nBHNu2LYt8ZMLkTQUB3G/wA=
|
||||
github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE=
|
||||
github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro=
|
||||
github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU=
|
||||
github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU=
|
||||
github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco=
|
||||
github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I=
|
||||
github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI=
|
||||
github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0=
|
||||
github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g=
|
||||
github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA=
|
||||
github.com/libp2p/go-libp2p-core v0.3.0 h1:F7PqduvrztDtFsAa/bcheQ3azmNo+Nq7m8hQY5GiUW8=
|
||||
github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw=
|
||||
github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI=
|
||||
github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg=
|
||||
github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90=
|
||||
github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo=
|
||||
github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE=
|
||||
github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE=
|
||||
github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU=
|
||||
github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs=
|
||||
github.com/libp2p/go-libp2p-pnet v0.1.0/go.mod h1:ZkyZw3d0ZFOex71halXRihWf9WH/j3OevcJdTmD0lyE=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.2.5 h1:tPKbkjAUI0xLGN3KKTKKy9TQEviVfrP++zJgH5Muke4=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.2.5/go.mod h1:9Q2RRq8ofXkoewORcyVlgUFDKLKw7BuYSlJVWRcVk3Y=
|
||||
github.com/libp2p/go-libp2p-pubsub-tracer v0.0.0-20200120141315-151ce254cf29 h1:Fj1cGt6KgExlji/QYQBUi6O7bryGzUATDoPx6qXTJ38=
|
||||
github.com/libp2p/go-libp2p-pubsub-tracer v0.0.0-20200120141315-151ce254cf29/go.mod h1:gSp/Ht64JWnnWb4X10w9IsBbgAlg203PAOyKi1m94AQ=
|
||||
github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8=
|
||||
github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g=
|
||||
github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8=
|
||||
github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0=
|
||||
github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0=
|
||||
github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA=
|
||||
github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8=
|
||||
github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI=
|
||||
github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q=
|
||||
github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M=
|
||||
github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0=
|
||||
github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU=
|
||||
github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
|
||||
github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
|
||||
github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo=
|
||||
github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0=
|
||||
github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc=
|
||||
github.com/libp2p/go-openssl v0.0.4 h1:d27YZvLoTyMhIN4njrkr8zMDOM4lfpHIp6A+TK9fovg=
|
||||
github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc=
|
||||
github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA=
|
||||
github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs=
|
||||
github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14=
|
||||
github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc=
|
||||
github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc=
|
||||
github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY=
|
||||
github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM=
|
||||
github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
|
||||
github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
|
||||
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
|
||||
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
|
||||
github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
|
||||
github.com/multiformats/go-multiaddr v0.2.0 h1:lR52sFwcTCuQb6bTfnXF6zA2XfyYvyd+5a9qECv/J90=
|
||||
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
|
||||
github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
|
||||
github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
|
||||
github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0=
|
||||
github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q=
|
||||
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
|
||||
github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU=
|
||||
github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ=
|
||||
github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ=
|
||||
github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA=
|
||||
github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs=
|
||||
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
|
||||
github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po=
|
||||
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
|
||||
github.com/multiformats/go-multihash v0.0.10 h1:lMoNbh2Ssd9PUF74Nz008KGzGPlfeV6wH3rit5IIGCM=
|
||||
github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
|
||||
github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg=
|
||||
github.com/multiformats/go-varint v0.0.1 h1:TR/0rdQtnNxuN2IhiB639xC3tWM4IUi7DkTBVTdGW/M=
|
||||
github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY=
|
||||
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0=
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=
|
||||
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM=
|
||||
github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE=
|
||||
github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA=
|
||||
github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4=
|
||||
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI=
|
||||
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8=
|
||||
gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
@ -0,0 +1,389 @@
|
|||
import os
|
||||
import json
|
||||
import pathlib
|
||||
import multiprocessing as mp
|
||||
from glob import glob
|
||||
import pandas as pd
|
||||
import toml
|
||||
import ipywidgets as widgets
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.lines as mlines
|
||||
import matplotlib.patches as mpatches
|
||||
import seaborn as sns
|
||||
import numpy as np
|
||||
import zipfile
|
||||
|
||||
|
||||
def mkdirp(dirpath):
|
||||
pathlib.Path(dirpath).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# sugar around recursive glob search
|
||||
def find_files(dirname, filename_glob):
|
||||
path = '{}/**/{}'.format(dirname, filename_glob)
|
||||
return glob(path, recursive=True)
|
||||
|
||||
def empty_scores_dataframe():
|
||||
return pd.DataFrame([], columns=['observer', 'peer', 'timestamp', 'score']).astype(
|
||||
{'score': 'float64', 'observer': 'int64', 'peer': 'int64', 'timestamp': 'datetime64[ns]'})
|
||||
|
||||
|
||||
def aggregate_peer_scores_single(scores_filepath, peers_table):
|
||||
df = empty_scores_dataframe()
|
||||
|
||||
# select the cols from peers table we want to join on
|
||||
p = peers_table[['peer_id', 'seq', 'honest']]
|
||||
|
||||
with open(scores_filepath, 'rt') as f:
|
||||
for line in iter(f.readline, ''):
|
||||
try:
|
||||
data = json.loads(line)
|
||||
except BaseException as err:
|
||||
print('error parsing score json: ', err)
|
||||
continue
|
||||
scores = pd.json_normalize(data['Scores'])
|
||||
scores = scores.T \
|
||||
.rename(columns={0: 'score'}) \
|
||||
.reset_index() \
|
||||
.rename(columns={'index': 'peer_id'})
|
||||
scores['timestamp'] = pd.to_datetime(data['Timestamp'])
|
||||
scores['observer_id'] = data['PeerID']
|
||||
|
||||
# join with peers table to convert peer ids to seq numbers
|
||||
s = scores.merge(p, on='peer_id').drop(columns=['peer_id'])
|
||||
s = s.merge(p.drop(columns=['honest']), left_on='observer_id', right_on='peer_id', suffixes=['_peer', '_observer'])
|
||||
s = s.drop(columns=['peer_id', 'observer_id'])
|
||||
s = s.rename(columns={'seq_peer': 'peer', 'seq_observer': 'observer'})
|
||||
|
||||
df = df.append(s, ignore_index=True)
|
||||
df.set_index('timestamp', inplace=True)
|
||||
return df
|
||||
|
||||
|
||||
def aggregate_peer_scores(score_filepaths, peers_table):
|
||||
if len(score_filepaths) == 0:
|
||||
return empty_scores_dataframe()
|
||||
pool = mp.Pool(mp.cpu_count())
|
||||
args = [(f, peers_table) for f in score_filepaths]
|
||||
results = pool.starmap(aggregate_peer_scores_single, args)
|
||||
# concat all data frames into one
|
||||
return pd.concat(results)
|
||||
|
||||
|
||||
def empty_metrics_dataframe():
|
||||
return pd.DataFrame([], columns=['published', 'rejected', 'delivered', 'duplicates', 'droppedrpc',
|
||||
'peersadded', 'peersremoved', 'topicsjoined', 'topicsleft', 'peer',
|
||||
'sent_rpcs', 'sent_messages', 'sent_grafts', 'sent_prunes',
|
||||
'sent_iwants', 'sent_ihaves', 'recv_rpcs', 'recv_messages',
|
||||
'recv_grafts', 'recv_prunes', 'recv_iwants', 'recv_ihaves'])
|
||||
|
||||
|
||||
def aggregate_metrics_to_pandas_single(metrics_filepath, peers_table):
|
||||
def munge_keys(d, prefix=''):
|
||||
out = dict()
|
||||
for k, v in d.items():
|
||||
outkey = prefix + k.lower()
|
||||
out[outkey] = v
|
||||
return out
|
||||
|
||||
|
||||
rows = list()
|
||||
with open(metrics_filepath, 'rb') as f:
|
||||
try:
|
||||
e = json.load(f)
|
||||
except BaseException as err:
|
||||
print('error loading metrics entry: ', err)
|
||||
else:
|
||||
pid = e['LocalPeer']
|
||||
sent = munge_keys(e['SentRPC'], 'sent_')
|
||||
recv = munge_keys(e['ReceivedRPC'], 'recv_')
|
||||
del(e['LocalPeer'], e['SentRPC'], e['ReceivedRPC'])
|
||||
row = munge_keys(e)
|
||||
row.update(sent)
|
||||
row.update(recv)
|
||||
rows.append(row)
|
||||
row['peer_id'] = pid
|
||||
|
||||
df = pd.DataFrame(rows)
|
||||
p = peers_table[['peer_id', 'seq']]
|
||||
df = df.merge(p, on='peer_id').drop(columns=['peer_id']).rename(columns={'seq': 'peer'})
|
||||
return df.astype('int64')
|
||||
|
||||
|
||||
def aggregate_metrics_to_pandas(metrics_filepaths, peers_table):
|
||||
if len(metrics_filepaths) == 0:
|
||||
return empty_metrics_dataframe()
|
||||
pool = mp.Pool(mp.cpu_count())
|
||||
args = [(f, peers_table) for f in metrics_filepaths]
|
||||
results = pool.starmap(aggregate_metrics_to_pandas_single, args)
|
||||
# concat all data frames into one
|
||||
return pd.concat(results)
|
||||
|
||||
|
||||
def cdf_to_pandas(cdf_filepath):
|
||||
if os.path.exists(cdf_filepath):
|
||||
return pd.read_csv(cdf_filepath, delim_whitespace=True, names=['delay_ms', 'count'], dtype='int64')
|
||||
else:
|
||||
return pd.DataFrame([], columns=['delay_ms', 'count'], dtype='int64')
|
||||
|
||||
|
||||
def peer_info_to_pandas(peer_info_filename):
|
||||
with open(peer_info_filename, 'rt') as f:
|
||||
data = json.load(f)
|
||||
peers = pd.json_normalize(data)
|
||||
peers['honest'] = peers['type'] == 'honest'
|
||||
return peers.astype({'type': 'category',
|
||||
't_warm': 'datetime64[ns]',
|
||||
't_connect': 'datetime64[ns]',
|
||||
't_run': 'datetime64[ns]',
|
||||
't_cool': 'datetime64[ns]',
|
||||
't_complete': 'datetime64[ns]'})
|
||||
|
||||
|
||||
def to_pandas(aggregate_output_dir, pandas_output_dir):
|
||||
mkdirp(pandas_output_dir)
|
||||
|
||||
print('converting peer ids and info to pandas...')
|
||||
peer_info_filename = os.path.join(aggregate_output_dir, 'peer-info.json')
|
||||
peers = peer_info_to_pandas(peer_info_filename)
|
||||
outfile = os.path.join(pandas_output_dir, 'peers.gz')
|
||||
peers.to_pickle(outfile)
|
||||
|
||||
print('converting peer scores to pandas...')
|
||||
scores_files = find_files(aggregate_output_dir, 'peer-scores*')
|
||||
df = aggregate_peer_scores(scores_files, peers)
|
||||
outfile = os.path.join(pandas_output_dir, 'scores.gz')
|
||||
print('writing pandas peer scores to {}'.format(outfile))
|
||||
df.to_pickle(outfile)
|
||||
|
||||
print('converting aggregate metrics to pandas...')
|
||||
outfile = os.path.join(pandas_output_dir, 'metrics.gz')
|
||||
metrics_files = find_files(aggregate_output_dir, '*aggregate.json')
|
||||
df = aggregate_metrics_to_pandas(metrics_files, peers)
|
||||
print('writing aggregate metrics pandas data to {}'.format(outfile))
|
||||
df.to_pickle(outfile)
|
||||
|
||||
print('converting latency cdf to pandas...')
|
||||
outfile = os.path.join(pandas_output_dir, 'cdf.gz')
|
||||
cdf_file = os.path.join(aggregate_output_dir, 'tracestat-cdf.txt')
|
||||
df = cdf_to_pandas(cdf_file)
|
||||
print('writing cdf pandas data to {}'.format(outfile))
|
||||
df.to_pickle(outfile)
|
||||
|
||||
|
||||
def write_pandas(tables, output_dir):
|
||||
pandas_dir = os.path.join(output_dir, 'pandas')
|
||||
mkdirp(pandas_dir)
|
||||
for name, df in tables.items():
|
||||
fname = os.path.join(pandas_dir, '{}.gz'.format(name))
|
||||
df.to_pickle(fname)
|
||||
|
||||
|
||||
def load_pandas(analysis_dir):
|
||||
analysis_dir = os.path.abspath(analysis_dir)
|
||||
pandas_dir = os.path.join(analysis_dir, 'pandas')
|
||||
if not os.path.exists(pandas_dir):
|
||||
print('Cached pandas data not found. Converting analysis data from {} to pandas'.format(analysis_dir))
|
||||
to_pandas(analysis_dir, pandas_dir)
|
||||
|
||||
tables = {}
|
||||
for f in os.listdir(pandas_dir):
|
||||
if not f.endswith('.gz'):
|
||||
continue
|
||||
name = os.path.splitext(f)[0]
|
||||
tables[name] = pd.read_pickle(os.path.join(pandas_dir, f))
|
||||
|
||||
if 'cdf' in tables:
|
||||
tables['pdf'] = cdf_to_pdf(tables['cdf'])
|
||||
|
||||
return tables
|
||||
|
||||
|
||||
def test_params_panel(analysis_dir):
|
||||
param_filename = os.path.join(analysis_dir, '..', 'template-params.toml')
|
||||
with open(param_filename, 'rt') as f:
|
||||
contents = f.read()
|
||||
test_params = toml.loads(contents)
|
||||
|
||||
params_out = widgets.Output()
|
||||
with params_out:
|
||||
print(contents)
|
||||
|
||||
params_panel = widgets.Accordion([params_out])
|
||||
params_panel.set_title(0, 'Test Parameters')
|
||||
params_panel.selected_index = None
|
||||
return (params_panel, test_params)
|
||||
|
||||
|
||||
def save_fig_fn(dest, formats=['png', 'pdf']):
|
||||
mkdirp(dest)
|
||||
|
||||
def save_fig(fig, filename, **kwargs):
|
||||
try:
|
||||
for fmt in formats:
|
||||
base = os.path.splitext(filename)[0]
|
||||
name = os.path.join(dest, '{}.{}'.format(base, fmt))
|
||||
fig.savefig(name, format=fmt, **kwargs)
|
||||
except BaseException as err:
|
||||
print('Error saving figure to {}: {}'.format(filename, err))
|
||||
return save_fig
|
||||
|
||||
|
||||
def zipdir(path, ziph, extensions=['.png', '.pdf', '.eps', '.svg']):
|
||||
# ziph is zipfile handle
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
strs = os.path.splitext(file)
|
||||
if len(strs) < 2:
|
||||
continue
|
||||
ext = strs[1]
|
||||
if ext not in extensions:
|
||||
continue
|
||||
ziph.write(os.path.join(root, file))
|
||||
|
||||
|
||||
def archive_figures(figure_dir, out_filename):
|
||||
zipf = zipfile.ZipFile(out_filename, 'w', zipfile.ZIP_DEFLATED)
|
||||
zipdir(figure_dir, zipf)
|
||||
zipf.close()
|
||||
|
||||
|
||||
def no_scores_message():
|
||||
from IPython.display import display, Markdown
|
||||
display(Markdown("""##### No peer score data, chart omitted"""))
|
||||
|
||||
|
||||
def tracestat_summary(analysis_dir):
|
||||
summary_file = os.path.join(analysis_dir, 'tracestat-summary.txt')
|
||||
if os.path.exists(summary_file):
|
||||
with open(summary_file, 'rt') as f:
|
||||
return f.read()
|
||||
else:
|
||||
return('no tracestat summary file found')
|
||||
|
||||
|
||||
def make_line(label, ax, x, color, alpha=0.5, linestyle='dashed'):
|
||||
ax.axvline(x=x, linestyle=linestyle, color=color, alpha=alpha)
|
||||
return mlines.Line2D([], [], color=color, linestyle=linestyle, label=label, alpha=alpha)
|
||||
|
||||
|
||||
def make_span(label, ax, start, end, color, alpha=0.3):
|
||||
ax.axvspan(start, end, facecolor=color, alpha=alpha)
|
||||
return mpatches.Patch(color=color, alpha=alpha, label=label)
|
||||
|
||||
|
||||
def annotate_times(ax, time_annotations, legend_anchor=None):
|
||||
colors = sns.color_palette('Set2')
|
||||
def next_color():
|
||||
c = colors.pop(0)
|
||||
colors.append(c)
|
||||
return c
|
||||
|
||||
legends = []
|
||||
for a in time_annotations:
|
||||
t1 = a['time']
|
||||
if pd.isnull(t1):
|
||||
continue
|
||||
label = a['label']
|
||||
if 'end_time' in a:
|
||||
# if we have an end_time, draw a span between start and end
|
||||
t2 = a['end_time']
|
||||
if pd.isnull(t2):
|
||||
continue
|
||||
legends.append(make_span(label, ax, t1, t2, next_color()))
|
||||
else:
|
||||
# otherwise, draw a dashed line at t1
|
||||
legends.append(make_line(label, ax, t1, next_color()))
|
||||
|
||||
if len(legends) != 0 and legend_anchor is not None:
|
||||
# add the original legend to the plot
|
||||
ax.add_artist(ax.legend(loc='upper left'))
|
||||
# add second legend for marker lines
|
||||
ax.legend(handles=legends, bbox_to_anchor=legend_anchor, loc='upper left')
|
||||
|
||||
|
||||
def annotate_score_plot(plot, title, legend_anchor=None, time_annotations=[]):
|
||||
plot.set_title(title)
|
||||
plot.set_ylabel('score')
|
||||
plot.set_xlabel('')
|
||||
if len(time_annotations) != 0:
|
||||
annotate_times(plot, time_annotations, legend_anchor=legend_anchor)
|
||||
|
||||
|
||||
def draw_latency_threshold_lines(max_val, eth_threshold=3000, fil_threshold=6000):
|
||||
legends = []
|
||||
if max_val > eth_threshold * 0.75:
|
||||
plt.axvline(eth_threshold, linestyle='--', color='orange')
|
||||
l = mlines.Line2D([], [], color='orange', linestyle='--', label='Eth2 threshold')
|
||||
legends.append(l)
|
||||
|
||||
if max_val > fil_threshold * 0.75:
|
||||
plt.axvline(fil_threshold, linestyle='--', color='blue')
|
||||
l = mlines.Line2D([], [], color='blue', linestyle='--', label='Fil threshold')
|
||||
legends.append(l)
|
||||
|
||||
if len(legends) > 0:
|
||||
plt.legend(handles=legends)
|
||||
|
||||
|
||||
def plot_latency_cdf(cdf):
|
||||
fig = plt.figure(figsize=(11,6))
|
||||
fig.suptitle("Latency CDF")
|
||||
plt.plot('delay_ms', 'count', data=cdf)
|
||||
plt.ylabel('messages')
|
||||
plt.xlabel('ms to fully propagate')
|
||||
draw_latency_threshold_lines(cdf['delay_ms'].max())
|
||||
plt.show()
|
||||
return fig
|
||||
|
||||
|
||||
def plot_latency_pdf(pdf):
|
||||
fig = plt.figure(figsize=(11,6))
|
||||
fig.suptitle('Latency Distribution (PDF)')
|
||||
plt.hist(pdf['delay_ms'], weights=pdf['count'], bins=50)
|
||||
plt.ylabel('messages')
|
||||
plt.xlabel('ms to fully propagate')
|
||||
draw_latency_threshold_lines(pdf['delay_ms'].max())
|
||||
plt.show()
|
||||
return fig
|
||||
|
||||
|
||||
def plot_latency_pdf_above_quantile(pdf, quantile=0.99):
|
||||
delays = pdf.reindex(pdf.index.repeat(pdf['count']))
|
||||
q = delays['delay_ms'].quantile(quantile)
|
||||
|
||||
fig = plt.figure(figsize=(11,6))
|
||||
qname = 'p{}'.format(int(round(quantile, 2) * 100))
|
||||
fig.suptitle('Latency PDF above {} ({:.2f}ms)'.format(qname, round(q, 2)))
|
||||
delays['delay_ms'].where(delays['delay_ms'] > q).dropna().plot.hist(bins=50)
|
||||
plt.ylabel('messages')
|
||||
plt.xlabel('ms to fully propagate')
|
||||
plt.show()
|
||||
return fig
|
||||
|
||||
|
||||
def cdf_to_pdf(cdf):
|
||||
delta = [0] * len(cdf['count'])
|
||||
delta[0] = cdf['count'][0]
|
||||
for x in range(1, len(cdf['count'])):
|
||||
delta[x] = cdf['count'][x] - cdf['count'][x-1]
|
||||
return pd.DataFrame({'delay_ms': cdf['delay_ms'], 'count': delta})
|
||||
|
||||
|
||||
def p25(x):
|
||||
return np.percentile(x, q=25)
|
||||
|
||||
|
||||
def p50(x):
|
||||
return np.percentile(x, q=50)
|
||||
|
||||
|
||||
def p75(x):
|
||||
return np.percentile(x, q=75)
|
||||
|
||||
|
||||
def p95(x):
|
||||
return np.percentile(x, q=95)
|
||||
|
||||
|
||||
def p99(x):
|
||||
return np.percentile(x, q=99)
|
|
@ -0,0 +1,16 @@
|
|||
toml
|
||||
jinja2
|
||||
ndjson
|
||||
pandas
|
||||
numpy
|
||||
matplotlib
|
||||
jupyter
|
||||
ipywidgets
|
||||
bunch
|
||||
stringcase
|
||||
papermill
|
||||
jupyter-ui-poll
|
||||
jupyter_contrib_nbextensions
|
||||
durations
|
||||
seaborn
|
||||
python-rclone
|
|
@ -0,0 +1,256 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import toml
|
||||
import jinja2
|
||||
import json
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import time
|
||||
import analyze
|
||||
import re
|
||||
|
||||
TESTGROUND_BIN = 'testground'
|
||||
|
||||
DEFAULT_GS_VERSION = 'latest'
|
||||
|
||||
# setting this build tag lets us compile test code that targets the new API from the hardening branch
|
||||
HARDENED_API_BUILD_TAG = 'hardened_api'
|
||||
|
||||
# Testground build/run config settings to use when running on kubernetes
|
||||
K8S_BUILD_CONFIG = {'bypass_cache': True, 'push_registry': True, 'registry_type': 'aws'}
|
||||
K8S_RUN_CONFIG = {}
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('param_files', nargs='*',
|
||||
help='name of one or more parameter files to use when generating composition from template. ' +
|
||||
'if a param is defined in multiple files, last one wins.')
|
||||
|
||||
parser.add_argument('--name',
|
||||
help='name of composition. will be used to create output directory.')
|
||||
|
||||
parser.add_argument('--template_dir',
|
||||
default='./templates/baseline',
|
||||
help='path to directory containing composition template and param files')
|
||||
|
||||
parser.add_argument('-o', '--output',
|
||||
help='directory to write composition file and test outputs to',
|
||||
default='./output')
|
||||
|
||||
parser.add_argument('--branch',
|
||||
default='master',
|
||||
help='configures the test to use the API from the given gossipsub branch')
|
||||
|
||||
parser.add_argument('--commit',
|
||||
help='configures the test to use the API from the given gossipsub commit')
|
||||
|
||||
parser.add_argument('--k8s', action='store_true', default=False,
|
||||
help='runs the test on kubernetes')
|
||||
|
||||
parser.add_argument('--dry-run', dest='dry_run', action='store_true', default=False,
|
||||
help='skip running tests, just write composition files and exit')
|
||||
|
||||
parser.add_argument('--instances', type=int,
|
||||
help='override the total number of test instances. equivalent to -D N_NODES=x')
|
||||
|
||||
parser.add_argument('-D', '--define', dest='definitions', action='append',
|
||||
metavar='<key=value>',
|
||||
help='set template variable `key` to `value`, e.g. -D T_RUN=10m')
|
||||
|
||||
parser.add_argument('--skip-analysis', dest='skip_analysis', action='store_true', default=False,
|
||||
help='skip analysis phase after test run (can run manually later with analyze.py)')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_param_filepath(template_dir, param_filename):
|
||||
p = param_filename
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
|
||||
p = os.path.join(template_dir, 'params', p)
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
|
||||
p = param_filename + '.toml'
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
|
||||
p = os.path.join(template_dir, 'params', p)
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
|
||||
raise ValueError("can't find param file " + param_filename)
|
||||
|
||||
|
||||
def load_params(template_dir, param_files):
|
||||
base_params_path = os.path.join(template_dir, 'params', '_base.toml')
|
||||
paths = [get_param_filepath(template_dir, p) for p in param_files]
|
||||
|
||||
params = dict()
|
||||
for path in [base_params_path] + paths:
|
||||
p = toml.load(path)
|
||||
for k, v in p.items():
|
||||
params[k] = v
|
||||
|
||||
return params
|
||||
|
||||
|
||||
|
||||
# The TOPOLOGY parameter can be either
|
||||
# - a JSON representation of a topology
|
||||
# - a path to a file in that format
|
||||
def parse_topology(params):
|
||||
if 'TOPOLOGY' not in params:
|
||||
params['TOPOLOGY'] = None
|
||||
return
|
||||
|
||||
# If it's already JSON, we're all set
|
||||
top = params['TOPOLOGY']
|
||||
if len(top) > 0 and top[0] == '{':
|
||||
return
|
||||
|
||||
# It's not JSON so assume it's a file path
|
||||
if not os.path.exists(top):
|
||||
raise ValueError("can't find topology file " + top)
|
||||
|
||||
# Make sure the file contents is JSON
|
||||
with open(top, 'r') as infile:
|
||||
contents = infile.read()
|
||||
jsonstr = json.loads(contents)
|
||||
params['TOPOLOGY'] = jsonstr
|
||||
|
||||
|
||||
# N_CONTAINER_NODES_TOTAL is the total number of nodes including multiple nodes
|
||||
# per container
|
||||
def parse_n_container_nodes_total(params):
|
||||
n_nodes = params.get('N_NODES', 20)
|
||||
n_nodes_cont_honest = params.get('N_HONEST_PEERS_PER_NODE', 1)
|
||||
|
||||
total = n_nodes * n_nodes_cont_honest
|
||||
params['N_CONTAINER_NODES_TOTAL'] = total
|
||||
|
||||
|
||||
def render_template(template_dir, params):
|
||||
env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(template_dir)
|
||||
)
|
||||
|
||||
template = env.get_template('template.toml.j2')
|
||||
return template.render(**params)
|
||||
|
||||
|
||||
def composition_name():
|
||||
ts = time.strftime("%Y%m%d-%H%M%S")
|
||||
return 'pubsub-test-{}'.format(ts)
|
||||
|
||||
|
||||
def mkdirp(dirpath):
|
||||
pathlib.Path(dirpath).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def run_composition(comp_filepath, output_dir, k8s=False):
|
||||
archive_type = 'tgz' if k8s else 'zip'
|
||||
outfilename = 'test-output.{}'.format(archive_type)
|
||||
outpath = os.path.join(output_dir, outfilename)
|
||||
|
||||
print('running testground composition {}'.format(comp_filepath))
|
||||
print("writing test outputs to {}".format(output_dir))
|
||||
cmd = [TESTGROUND_BIN, 'run', 'composition', '-f', comp_filepath, '--collect', '-o', outpath]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
print('test completed successfully!')
|
||||
return outpath
|
||||
|
||||
|
||||
def pubsub_commit(ref_str):
|
||||
# if the input looks like a git commit already, just return it as-is
|
||||
if re.match(r'\b([a-f0-9]{40})\b', ref_str):
|
||||
return ref_str
|
||||
|
||||
out = subprocess.run(['git', 'ls-remote', 'git://github.com/libp2p/go-libp2p-pubsub'],
|
||||
check=True, capture_output=True, text=True)
|
||||
|
||||
# look for matching branch or tag in output
|
||||
pattern = r'^\b([a-f0-9]{40})\b.*refs/(heads|tags)/' + ref_str + '$'
|
||||
m = re.search(pattern, out.stdout, re.MULTILINE)
|
||||
if not m:
|
||||
raise ValueError('no branch or tag found matching {}'.format(ref_str))
|
||||
return m.group(1)
|
||||
|
||||
|
||||
def run():
|
||||
args = parse_args()
|
||||
template_dir = args.template_dir
|
||||
|
||||
params = load_params(template_dir, args.param_files)
|
||||
|
||||
branch = None
|
||||
if args.branch:
|
||||
branch = args.branch
|
||||
params['GS_VERSION'] = pubsub_commit(args.branch)
|
||||
if args.commit:
|
||||
params['GS_VERSION'] = args.commit
|
||||
if branch is None and args.commit is None:
|
||||
params['GS_VERSION'] = 'latest'
|
||||
|
||||
|
||||
gs_version_msg = 'Using go-libp2p-pubsub commit ' + params['GS_VERSION']
|
||||
if branch:
|
||||
gs_version_msg += ' (' + branch + ')'
|
||||
print(gs_version_msg)
|
||||
|
||||
if args.k8s:
|
||||
params['TEST_RUNNER'] = 'cluster:k8s'
|
||||
params['BUILD_CONFIG'] = toml.dumps(K8S_BUILD_CONFIG)
|
||||
params['RUN_CONFIG'] = toml.dumps(K8S_RUN_CONFIG)
|
||||
|
||||
if args.instances is not None:
|
||||
params['N_NODES'] = args.instances
|
||||
|
||||
if args.definitions is not None:
|
||||
for defstr in args.definitions:
|
||||
[k, v] = defstr.split('=')
|
||||
try:
|
||||
v = float(v)
|
||||
if v.is_integer():
|
||||
v = int(v)
|
||||
params[k] = v
|
||||
except:
|
||||
params[k] = v
|
||||
|
||||
parse_topology(params)
|
||||
parse_n_container_nodes_total(params)
|
||||
|
||||
comp = composition_name()
|
||||
if args.name:
|
||||
comp += '-' + args.name
|
||||
|
||||
workdir = os.path.join(args.output, comp)
|
||||
pathlib.Path(workdir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
comp_filepath = os.path.join(workdir, 'composition.toml')
|
||||
with open(comp_filepath, 'w', encoding='utf8') as f:
|
||||
f.write(render_template(template_dir, params))
|
||||
|
||||
param_filepath = os.path.join(workdir, 'template-params.toml')
|
||||
with open(param_filepath, 'wt') as f:
|
||||
toml.dump(params, f)
|
||||
|
||||
print('wrote composition file to {}'.format(comp_filepath))
|
||||
|
||||
if args.dry_run:
|
||||
print('dry run. skipping test execution')
|
||||
return
|
||||
|
||||
test_output_archive = run_composition(comp_filepath, workdir, args.k8s)
|
||||
if not args.skip_analysis:
|
||||
print('extracting test output data')
|
||||
analyze.extract_test_outputs(test_output_archive)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import rclone
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
|
||||
DEFAULT_S3_BUCKET = 'gossipsub-test-outputs'
|
||||
DEFAULT_REGION = 'eu-central-1'
|
||||
RCLONE_CONFIG_TEMPLATE = """
|
||||
[s3]
|
||||
type = s3
|
||||
provider = AWS
|
||||
env_auth = true
|
||||
region = {region}
|
||||
location_constraint = "{region}"
|
||||
acl = public-read
|
||||
"""
|
||||
|
||||
|
||||
def rclone_config(region):
|
||||
return RCLONE_CONFIG_TEMPLATE.format(region=region)
|
||||
|
||||
|
||||
class OutputSyncer(object):
|
||||
def __init__(self, region=DEFAULT_REGION, bucket=DEFAULT_S3_BUCKET):
|
||||
self.config = rclone_config(region)
|
||||
self.bucket = bucket
|
||||
self._ensure_rclone_exists()
|
||||
|
||||
def _ensure_rclone_exists(self):
|
||||
result = rclone.with_config(self.config).listremotes()
|
||||
if result['code'] == -20:
|
||||
raise EnvironmentError("the 'rclone' command must be present on the $PATH")
|
||||
|
||||
def list_outputs(self):
|
||||
path = 's3:/{}/'.format(self.bucket)
|
||||
result = rclone.with_config(self.config).run_cmd('lsd', [path])
|
||||
if result['code'] != 0:
|
||||
raise ValueError('failed to list output bucket: {}'.format(result))
|
||||
out = result['out'].decode('utf8')
|
||||
dirs = []
|
||||
for line in out.splitlines():
|
||||
name = line.split()[-1]
|
||||
dirs.append(name)
|
||||
return dirs
|
||||
|
||||
def fetch(self, name, dest_dir):
|
||||
src = 's3:/{}/{}'.format(self.bucket, name)
|
||||
dest = os.path.join(dest_dir, name)
|
||||
result = rclone.with_config(self.config).sync(src, dest)
|
||||
if result['code'] != 0:
|
||||
print('error fetching {}: {}'.format(name, result['error']), file=sys.stderr)
|
||||
|
||||
def fetch_all(self, dest_dir):
|
||||
src = 's3:/{}/'.format(self.bucket)
|
||||
result = rclone.with_config(self.config).sync(src, dest_dir)
|
||||
if result['code'] != 0:
|
||||
print('error fetching all test outputs: {}'.format(result['error']), file=sys.stderr)
|
||||
|
||||
def store_single(self, test_run_dir):
|
||||
"""
|
||||
:param test_run_dir: path to local dir containing a single test run output, e.g. ./output/pubsub-test-20200409-152658
|
||||
"""
|
||||
name = os.path.basename(test_run_dir)
|
||||
dest = 's3:/{}/{}'.format(self.bucket, name)
|
||||
result = rclone.with_config(self.config).sync(test_run_dir, dest)
|
||||
if result['code'] != 0:
|
||||
print('error storing {}: {}'.format(name, result['error']), file=sys.stderr)
|
||||
|
||||
def store_all(self, src_dir, ignore=[]):
|
||||
"""
|
||||
:param src_dir: path to local dir containing multiple test run dirs, e.g. ./output
|
||||
:param ignore: list of subdirectories to ignore
|
||||
"""
|
||||
for f in os.listdir(src_dir):
|
||||
if f in ignore:
|
||||
continue
|
||||
src = os.path.join(src_dir, f)
|
||||
dest = 's3:/{}/{}'.format(self.bucket, f)
|
||||
|
||||
print('syncing {} to {}'.format(src, dest))
|
||||
result = rclone.with_config(self.config).sync(src, dest)
|
||||
if result['code'] != 0:
|
||||
print('error storing {}: {}'.format(f, result['error']), file=sys.stderr)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="sync test outputs to/from an s3 bucket")
|
||||
parser.add_argument('--region', default=DEFAULT_REGION, help='AWS region containing test output bucket')
|
||||
parser.add_argument('--bucket', default=DEFAULT_S3_BUCKET, help='name of s3 bucket to store and fetch test outputs')
|
||||
|
||||
commands = parser.add_subparsers()
|
||||
ls_cmd = commands.add_parser('list', aliases=['ls'], help='list test outputs in the s3 bucket')
|
||||
ls_cmd.set_defaults(subcommand='list')
|
||||
|
||||
fetch_cmd = commands.add_parser('fetch', help='fetch one or more named test outputs from the s3 bucket')
|
||||
fetch_cmd.set_defaults(subcommand='fetch')
|
||||
fetch_cmd.add_argument('names', nargs='+', help='name of a test output directory to fetch')
|
||||
fetch_cmd.add_argument('--dest', default='./output', help='directory to store fetched test output')
|
||||
|
||||
fetch_all_cmd = commands.add_parser('fetch-all', help='fetch all test outputs from the s3 bucket to a local dir')
|
||||
fetch_all_cmd.set_defaults(subcommand='fetch-all')
|
||||
fetch_all_cmd.add_argument('dest', help='directory to store fetched test output')
|
||||
|
||||
store_cmd = commands.add_parser('store', help='store one or more test outputs in s3')
|
||||
store_cmd.set_defaults(subcommand='store')
|
||||
store_cmd.add_argument('paths', nargs='+', help='path to a test output directory to store')
|
||||
|
||||
store_all_cmd = commands.add_parser('store-all', help='send all test outputs in a directory to s3')
|
||||
store_all_cmd.set_defaults(subcommand='store-all')
|
||||
store_all_cmd.set_defaults(ignore=['failed'])
|
||||
store_all_cmd.add_argument('dir', help='local dir containing test output directories')
|
||||
store_all_cmd.add_argument('--ignore', help='subdirectory to ignore (e.g. failed outputs)',
|
||||
action='append')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def run():
|
||||
args = parse_args()
|
||||
|
||||
syncer = OutputSyncer(region=args.region, bucket=args.bucket)
|
||||
if args.subcommand == 'list':
|
||||
outputs = syncer.list_outputs()
|
||||
print('\n'.join(outputs))
|
||||
return
|
||||
|
||||
if args.subcommand == 'fetch':
|
||||
dest_dir = args.dest
|
||||
for name in args.names:
|
||||
print('fetching {} from s3://{} to {}'.format(name, args.bucket, dest_dir))
|
||||
syncer.fetch(name, dest_dir)
|
||||
return
|
||||
|
||||
if args.subcommand == 'fetch-all':
|
||||
dest_dir = args.dest
|
||||
print('fetching all test outputs from s3://{}'.format(args.bucket))
|
||||
syncer.fetch_all(dest_dir)
|
||||
return
|
||||
|
||||
if args.subcommand == 'store':
|
||||
for p in args.paths:
|
||||
print('syncing {} to s3://{}'.format(p, args.bucket))
|
||||
syncer.store_single(p)
|
||||
return
|
||||
|
||||
if args.subcommand == 'store-all':
|
||||
print('syncing all subdirs of {} to s3://{} - excluding {}'.format(args.dir, args.bucket, args.ignore))
|
||||
syncer.store_all(args.dir, ignore=args.ignore)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
|
@ -0,0 +1,3 @@
|
|||
COMPOSITION_NAME = "baseline-1k-nodes"
|
||||
N_NODES = 1000
|
||||
N_PUBLISHER = 100
|
|
@ -0,0 +1,90 @@
|
|||
COMPOSITION_NAME = "baseline"
|
||||
|
||||
# the set of go build tags to apply when building.
|
||||
# set this to an empty array if you want to target a commit before gossipsub v1.1 was merged.
|
||||
BUILD_SELECTORS = ['hardened_api']
|
||||
|
||||
# version of go-libp2p-pubsub to use when building the test plan
|
||||
GS_VERSION = "latest"
|
||||
|
||||
# time to run simulation (after warmup)
|
||||
T_RUN = "3m"
|
||||
|
||||
# time to wait after subscribing to topics before publishing
|
||||
T_WARM = "30s"
|
||||
|
||||
# cooldown time after publishing stops
|
||||
T_COOL = "30s"
|
||||
|
||||
# total number of nodes in simulation
|
||||
N_NODES = 20
|
||||
|
||||
# number of nodes that are attackers
|
||||
N_ATTACK_NODES = 0
|
||||
|
||||
# number of honest (non-attack) nodes that are publishers
|
||||
# the remaining honest nodes will lurk in their topics without
|
||||
# publishing
|
||||
N_PUBLISHER = 10
|
||||
|
||||
# how often to dump peer score values
|
||||
T_SCORE_INSPECT_PERIOD = '5s'
|
||||
|
||||
# TOPIC_CONFIG controls which topics will be joined.
|
||||
# The n_messages and message_size params are only relevant to publisher
|
||||
# nodes.
|
||||
#
|
||||
# The message delivery rate is derived from the runtime, with each
|
||||
# publisher attempting to deliver n_messages at a uniform rate througout
|
||||
# t_run.
|
||||
#
|
||||
# Right now we use one TOPIC_CONFIG for all groups in the composition,
|
||||
# but there's no reason you coudn't give each group its own config,
|
||||
# for example to have peers publishing at different rates to the
|
||||
# same topic.
|
||||
TOPIC_CONFIG = [
|
||||
{ id = 'blocks', message_rate = "120/s", message_size = '2 KiB' },
|
||||
]
|
||||
|
||||
[PEER_SCORE_PARAMS]
|
||||
|
||||
IPColocationFactorWeight=0
|
||||
IPColocationFactorThreshold=1
|
||||
DecayInterval="5s"
|
||||
DecayToZero=0.01
|
||||
RetainScore="10s"
|
||||
|
||||
[PEER_SCORE_PARAMS.Thresholds]
|
||||
GossipThreshold = -4000
|
||||
PublishThreshold = -5000
|
||||
GraylistThreshold = -10000
|
||||
AcceptPXThreshold = 0
|
||||
|
||||
[PEER_SCORE_PARAMS.Topics.blocks]
|
||||
TopicWeight = 0.25
|
||||
|
||||
# P1
|
||||
TimeInMeshWeight = 0.0027
|
||||
TimeInMeshQuantum = "1s"
|
||||
TimeInMeshCap = 3600
|
||||
|
||||
# P2
|
||||
FirstMessageDeliveriesWeight = 0.664
|
||||
FirstMessageDeliveriesDecay = 0.9916
|
||||
FirstMessageDeliveriesCap = 1500
|
||||
|
||||
# P3
|
||||
MeshMessageDeliveriesWeight = -0.25
|
||||
MeshMessageDeliveriesDecay = 0.97
|
||||
MeshMessageDeliveriesCap = 400
|
||||
MeshMessageDeliveriesThreshold = 100
|
||||
MeshMessageDeliveriesActivation = '30s'
|
||||
MeshMessageDeliveryWindow = '5ms'
|
||||
|
||||
# P3b
|
||||
MeshFailurePenaltyWeight = -0.25
|
||||
MeshFailurePenaltyDecay = 0.997
|
||||
|
||||
# P4
|
||||
InvalidMessageDeliveriesWeight = -99
|
||||
InvalidMessageDeliveriesDecay = 0.9994
|
|
@ -0,0 +1,116 @@
|
|||
## This composition runs a pubsub simulation with no adversarial nodes, to
|
||||
## establish baseline metrics.
|
||||
##
|
||||
[metadata]
|
||||
name = "pubsub-{{ COMPOSITION_NAME }}"
|
||||
author = "yusefnapora"
|
||||
|
||||
[global]
|
||||
plan = "{{ TEST_PLAN | default('test-plans/pubsub/test') }}"
|
||||
case = "evaluate"
|
||||
builder = "{{ TEST_BUILDER | default('docker:go') }}"
|
||||
runner = "{{ TEST_RUNNER | default('local:docker') }}"
|
||||
total_instances = {{ N_NODES }}
|
||||
|
||||
[global.build_config]
|
||||
{{ BUILD_CONFIG | default('') }}
|
||||
|
||||
[global.run_config]
|
||||
{{ RUN_CONFIG | default('') }}
|
||||
|
||||
[[groups]]
|
||||
id = "publishers"
|
||||
instances = { count = {{ N_PUBLISHER }} }
|
||||
|
||||
[groups.build]
|
||||
selectors = {{ BUILD_SELECTORS }}
|
||||
dependencies = [
|
||||
{ module = "github.com/libp2p/go-libp2p-pubsub", version = "{{ GS_VERSION }}" }
|
||||
]
|
||||
|
||||
[groups.run.test_params]
|
||||
t_heartbeat = "{{ T_HEARTBEAT | default('1s') }}"
|
||||
t_heartbeat_initial_delay = "{{ T_HEARTBEAT_INITIAL_DELAY | default('100ms') }}"
|
||||
t_run = "{{ T_RUN }}"
|
||||
t_warm = "{{ T_WARM }}"
|
||||
t_cool = "{{ T_COOL | default('10s') }}"
|
||||
t_setup = "{{ T_SETUP | default('1m') }}"
|
||||
full_traces = "{{ FULL_TRACES | default('false') }}"
|
||||
|
||||
publisher = "true"
|
||||
flood_publishing = '{{ FLOOD_PUBLISHING }}'
|
||||
topics = '{{ TOPIC_CONFIG | tojson() }}'
|
||||
score_params = '{{ PEER_SCORE_PARAMS | tojson() }}'
|
||||
t_score_inspect_period = '{{ T_SCORE_INSPECT_PERIOD | default('0s') }}'
|
||||
validate_queue_size = '{{ VALIDATE_QUEUE_SIZE | default(0) }}'
|
||||
outbound_queue_size = '{{ OUTBOUND_QUEUE_SIZE | default(0) }}'
|
||||
|
||||
t_latency = '{{ T_LATENCY | default('5ms') }}'
|
||||
t_latency_max = '{{ T_LATENCY_MAX | default('50ms') }}'
|
||||
jitter_pct = '{{ JITTER_PCT | default(10) | int }}'
|
||||
bandwidth_mb = '{{ BANDWIDTH_MB | default(10240) | int }}'
|
||||
|
||||
topology = '{{ TOPOLOGY | tojson() }}'
|
||||
degree = '{{ N_DEGREE | default(20) | int }}'
|
||||
|
||||
overlay_d = '{{ OVERLAY_D | default(-1) | int }}'
|
||||
overlay_dlo = '{{ OVERLAY_DLO | default(-1) | int }}'
|
||||
overlay_dhi = '{{ OVERLAY_DHI | default(-1) | int }}'
|
||||
overlay_dscore = '{{ OVERLAY_DSCORE | default(-1) | int }}'
|
||||
overlay_dlazy = '{{ OVERLAY_DLAZY | default(-1) | int }}'
|
||||
gossip_factor = '{{ GOSSIP_FACTOR | default(0.25) | float }}'
|
||||
opportunistic_graft_ticks = '{{ OPPORTUNISTIC_GRAFT_TICKS | default(60) | int }}'
|
||||
|
||||
n_container_nodes_total = '{{ N_CONTAINER_NODES_TOTAL | default(N_NODES) }}'
|
||||
n_nodes_per_container = '{{ N_HONEST_PEERS_PER_NODE | default(1) | int }}'
|
||||
|
||||
connect_delays = '{{ HONEST_CONNECT_DELAYS | default('') }}'
|
||||
connect_delay_jitter_pct = '{{ HONEST_CONNECT_DELAY_JITTER_PCT | default('5') | int }}'
|
||||
|
||||
[[groups]]
|
||||
id = "lurkers"
|
||||
instances = { count = {{ N_NODES - N_PUBLISHER | int }} }
|
||||
|
||||
[groups.build]
|
||||
selectors = {{ BUILD_SELECTORS }}
|
||||
dependencies = [
|
||||
{ module = "github.com/libp2p/go-libp2p-pubsub", version = "{{ GS_VERSION }}" }
|
||||
]
|
||||
|
||||
[groups.run.test_params]
|
||||
t_heartbeat = "{{ T_HEARTBEAT | default('1s') }}"
|
||||
t_heartbeat_initial_delay = "{{ T_HEARTBEAT_INITIAL_DELAY | default('100ms') }}"
|
||||
t_run = "{{ T_RUN }}"
|
||||
t_warm = "{{ T_WARM }}"
|
||||
t_cool = "{{ T_COOL | default('10s') }}"
|
||||
t_setup = "{{ T_SETUP | default('1m') }}"
|
||||
full_traces = "{{ FULL_TRACES | default('false') }}"
|
||||
|
||||
topics = '{{ TOPIC_CONFIG | tojson() }}'
|
||||
score_params = '{{ PEER_SCORE_PARAMS | tojson() }}'
|
||||
t_score_inspect_period = '{{ T_SCORE_INSPECT_PERIOD | default('0s') }}'
|
||||
validate_queue_size = '{{ VALIDATE_QUEUE_SIZE | default(0) }}'
|
||||
outbound_queue_size = '{{ OUTBOUND_QUEUE_SIZE | default(0) }}'
|
||||
|
||||
t_latency = '{{ T_LATENCY | default('5ms') }}'
|
||||
t_latency_max = '{{ T_LATENCY_MAX | default('50ms') }}'
|
||||
jitter_pct = '{{ JITTER_PCT | default(10) | int }}'
|
||||
bandwidth_mb = '{{ BANDWIDTH_MB | default(10240) | int }}'
|
||||
|
||||
topology = '{{ TOPOLOGY | tojson() }}'
|
||||
degree = '{{ N_DEGREE | default(20) | int }}'
|
||||
|
||||
overlay_d = '{{ OVERLAY_D | default(-1) | int }}'
|
||||
overlay_dlo = '{{ OVERLAY_DLO | default(-1) | int }}'
|
||||
overlay_dhi = '{{ OVERLAY_DHI | default(-1) | int }}'
|
||||
overlay_dscore = '{{ OVERLAY_DSCORE | default(-1) | int }}'
|
||||
overlay_dlazy = '{{ OVERLAY_DLAZY | default(-1) | int }}'
|
||||
gossip_factor = '{{ GOSSIP_FACTOR | default(0.25) | float }}'
|
||||
opportunistic_graft_ticks = '{{ OPPORTUNISTIC_GRAFT_TICKS | default(60) | int }}'
|
||||
|
||||
|
||||
n_container_nodes_total = '{{ N_CONTAINER_NODES_TOTAL }}'
|
||||
n_nodes_per_container = '{{ N_HONEST_PEERS_PER_NODE | default(1) | int }}'
|
||||
|
||||
connect_delays = '{{ HONEST_CONNECT_DELAYS | default('') }}'
|
||||
connect_delay_jitter_pct = '{{ HONEST_CONNECT_DELAY_JITTER_PCT | default('5') | int }}'
|
|
@ -0,0 +1,545 @@
|
|||
import ipywidgets as widgets
|
||||
import run as run_helpers
|
||||
from bunch import Bunch
|
||||
import functools
|
||||
import operator
|
||||
import stringcase
|
||||
import subprocess
|
||||
import os
|
||||
import toml
|
||||
import json
|
||||
import time
|
||||
from jupyter_ui_poll import ui_events
|
||||
from IPython.display import display
|
||||
import analyze
|
||||
import shutil
|
||||
|
||||
# TODO: move constants
|
||||
TEMPLATE = 'templates/baseline'
|
||||
|
||||
# set to path of testground bin if not in PATH
|
||||
TESTGROUND = 'testground'
|
||||
|
||||
|
||||
class RunButton(object):
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.pressed = False
|
||||
self.button = widgets.Button(description='Run Test', button_style='primary')
|
||||
self.button.on_click(self._clicked)
|
||||
|
||||
def _clicked(self, evt):
|
||||
self.pressed = True
|
||||
self.button.description = 'Running'
|
||||
self.button.button_style = 'info'
|
||||
self.button.disabled = True
|
||||
|
||||
def wait(self):
|
||||
display(self.button)
|
||||
with ui_events() as poll:
|
||||
while self.pressed is False:
|
||||
poll(10) # React to UI events (upto 10 at a time)
|
||||
time.sleep(0.1)
|
||||
self._run()
|
||||
|
||||
def _run(self):
|
||||
endpoint = self.config.widgets.testground.daemon_endpoint.value
|
||||
workdir = self.config.widgets.test_execution.output_dir.value
|
||||
failed_dir = self.config.widgets.test_execution.failed_dir.value
|
||||
run_helpers.mkdirp(workdir)
|
||||
run_helpers.mkdirp(failed_dir)
|
||||
|
||||
params = self.config.template_params()
|
||||
comp = self.config.composition()
|
||||
comp_filename = os.path.join(workdir, 'composition.toml')
|
||||
params_filename = os.path.join(workdir, 'template-params.toml')
|
||||
config_snapshot_filename = os.path.join(workdir, 'config-snapshot.json')
|
||||
|
||||
if 'k8s' in params['TEST_RUNNER']:
|
||||
archive_filename = os.path.join(workdir, 'test-output.tgz')
|
||||
else:
|
||||
archive_filename = os.path.join(workdir, 'test-output.zip')
|
||||
|
||||
with open(comp_filename, 'wt') as f:
|
||||
f.write(comp)
|
||||
|
||||
with open(params_filename, 'w') as f:
|
||||
toml.dump(params, f)
|
||||
|
||||
with open(config_snapshot_filename, 'w') as f:
|
||||
json.dump(self.config.snapshot(), f)
|
||||
|
||||
cmd = [TESTGROUND, '--vv',
|
||||
'--endpoint', endpoint,
|
||||
'run', 'composition',
|
||||
'-f', comp_filename,
|
||||
'--collect', '-o', archive_filename]
|
||||
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
|
||||
for line in iter(p.stdout.readline, ''):
|
||||
print(line, end='')
|
||||
if p.poll():
|
||||
break
|
||||
self.button.description = 'Done'
|
||||
self.button.button_style = 'danger'
|
||||
return_code = p.wait()
|
||||
if return_code:
|
||||
try:
|
||||
shutil.move(workdir, failed_dir)
|
||||
except BaseException as err:
|
||||
print('tried to move output from failed test to {}, but failed with error: {}'.format(failed_dir, err))
|
||||
raise ValueError('test execution failed, skipping analysis. moved outputs to {}'.format(failed_dir))
|
||||
|
||||
print('test outputs saved to {}'.format(workdir))
|
||||
print('extracting test data for analysis...')
|
||||
analysis_dir = os.path.join(workdir, 'analysis')
|
||||
analyze.extract_test_outputs(archive_filename, analysis_dir, convert_to_pandas=False, prep_notebook=True)
|
||||
print('saved analysis outputs to {}'.format(analysis_dir))
|
||||
|
||||
|
||||
|
||||
|
||||
# a collapsible panel for a single topic's params
|
||||
class TopicConfigPanel(object):
|
||||
def __init__(self):
|
||||
self.topic_widgets = Bunch(
|
||||
name=widgets.Text(description="Topic Name", value="blocks"),
|
||||
message_rate=widgets.Text(description="Message Rate (msg/sec)", value='120/s'),
|
||||
message_size=widgets.Text(description="Message Size", value="2 KiB"),
|
||||
)
|
||||
|
||||
self.topic_weight = widgets.FloatText(description="Topic Weight", value=0.25)
|
||||
|
||||
# NOTE: don't change the description values! they're used to derive the JSON keys when
|
||||
# collecting the param values later
|
||||
self.score_widgets = Bunch(
|
||||
time_in_mesh=Bunch(
|
||||
weight=widgets.FloatText(description="Time in Mesh Weight", value=0.0027),
|
||||
quantum=widgets.Text(description="Time in Mesh Quantum", value='1s'),
|
||||
cap=widgets.FloatText(description="Time in Mesh Cap", value=3600)
|
||||
),
|
||||
first_message_deliveries=Bunch(
|
||||
weight=widgets.FloatText(description="First Message Deliveries Weight", value=0.664),
|
||||
decay=widgets.FloatText(description="First Message Deliveries Decay", value=0.9916),
|
||||
cap=widgets.FloatText(description="First Message Deliveries Cap", value=1500),
|
||||
),
|
||||
mesh_message_deliveries=Bunch(
|
||||
weight=widgets.FloatText(description="Mesh Message Deliveries Weight", value=-0.25),
|
||||
decay=widgets.FloatText(description="Mesh Message Deliveries Decay", value=0.97),
|
||||
cap=widgets.FloatText(description="Mesh Message Deliveries Cap", value=400),
|
||||
threshold=widgets.FloatText(description="Mesh Message Deliveries Threshold", value=100),
|
||||
activation=widgets.Text(description="Mesh Message Deliveries Activation", value="30s"),
|
||||
window=widgets.Text(description="Mesh Message Delivery Window", value="5ms"),
|
||||
),
|
||||
mesh_failure_penalty=Bunch(
|
||||
weight=widgets.FloatText(description="Mesh Failure Penalty Weight", value=-0.25),
|
||||
decay=widgets.FloatText(description="Mesh Failure Penalty Decay", value=0.997),
|
||||
),
|
||||
invalid_message_deliveries=Bunch(
|
||||
weight=widgets.FloatText(description="Invalid Message Deliveries Weight", value=-99),
|
||||
decay=widgets.FloatText(description="Invalid Message Deliveries Decay", value=0.9994),
|
||||
)
|
||||
)
|
||||
|
||||
topic_panel = widgets.VBox([
|
||||
labeled(self.topic_widgets.name),
|
||||
labeled(self.topic_widgets.message_rate),
|
||||
labeled(self.topic_widgets.message_size),
|
||||
])
|
||||
|
||||
score_panel = widgets.VBox([
|
||||
widgets.HTML('<h3>Peer Score Params</h3>'),
|
||||
labeled(self.topic_weight),
|
||||
to_collapsible_sections(self.score_widgets)],
|
||||
layout={'width': '900px'})
|
||||
|
||||
self.panel = widgets.VBox([topic_panel, score_panel], layout={'width': '900px'})
|
||||
|
||||
def ui(self):
|
||||
return self.panel
|
||||
|
||||
def snapshot(self):
|
||||
return {
|
||||
'topic_weight': {'value': self.topic_weight.value},
|
||||
'topic': widget_snapshot(self.topic_widgets),
|
||||
'score': widget_snapshot(self.score_widgets),
|
||||
}
|
||||
|
||||
def apply_snapshot(self, snapshot):
|
||||
if 'topic_weight' in snapshot and 'value' in snapshot['topic_weight']:
|
||||
self.topic_weight.value = snapshot['topic_weight']['value']
|
||||
if 'topic' in snapshot:
|
||||
apply_snapshot(self.topic_widgets, snapshot['topic'])
|
||||
if 'score' in snapshot:
|
||||
apply_snapshot(self.score_widgets, snapshot['score'])
|
||||
|
||||
def topic_id(self):
|
||||
return self.topic_widgets.name.value
|
||||
|
||||
def topic_params(self):
|
||||
return {
|
||||
'id': self.topic_widgets.name.value,
|
||||
'message_rate': self.topic_widgets.message_rate.value,
|
||||
'message_size': self.topic_widgets.message_size.value,
|
||||
}
|
||||
|
||||
def score_params(self):
|
||||
p = {
|
||||
'TopicWeight': self.topic_weight.value,
|
||||
}
|
||||
for group in self.score_widgets.values():
|
||||
for param in group.values():
|
||||
key = param.description.replace(' ', '')
|
||||
p[key] = param.value
|
||||
return p
|
||||
|
||||
|
||||
# ConfigPanel is a collection of widgets to set the test parameters.
|
||||
class ConfigPanel(object):
|
||||
|
||||
def __init__(self):
|
||||
# all the widgets used to configure the test
|
||||
|
||||
default_out_dir = os.path.join('.', 'output', 'pubsub-test-{}'.format(time.strftime("%Y%m%d-%H%M%S")))
|
||||
default_failed_dir = os.path.join('.', 'output', 'failed')
|
||||
w = Bunch(
|
||||
test_execution=Bunch(
|
||||
output_dir=widgets.Text(description="Local directory to collect test outputs", value=default_out_dir),
|
||||
failed_dir=widgets.Text(description="Local dir to store output from failed runs", value=default_failed_dir)
|
||||
),
|
||||
|
||||
testground=Bunch(
|
||||
daemon_endpoint=widgets.Text(description="Daemon Endpoint", value='localhost:8080'),
|
||||
builder=widgets.Dropdown(description="Builder", options=['docker:go', 'exec:go']),
|
||||
runner=widgets.Dropdown(description="Runner", options=['cluster:k8s', 'local:docker', 'local:exec']),
|
||||
plan_dir=widgets.Text(description="Subdir of $TESTGROUND_HOME/plans containing pubsub plan", value="test-plans/pubsub/test"),
|
||||
keep_service=widgets.Checkbox(description="Keep pods after execution? (k8s only)", value=False),
|
||||
log_level=widgets.Dropdown(description="Log level to set on test instances", options=["info", "debug", "warn", "error"]),
|
||||
),
|
||||
|
||||
time=Bunch(
|
||||
setup=widgets.Text(description="Test Setup time", value='1m'),
|
||||
run=widgets.Text(description="Test Runtime", value='2m'),
|
||||
warm=widgets.Text(description="Warmup time", value='5s'),
|
||||
cool=widgets.Text(description="Cooldown time", value='10s'),
|
||||
),
|
||||
|
||||
node_counts=Bunch(
|
||||
total=widgets.IntText(description="Total number of test instances", disabled=True),
|
||||
total_peers=widgets.IntText(description="Total number of peers in all containers", disabled=True),
|
||||
publisher=widgets.IntText(description="Number of publisher nodes", value=100),
|
||||
lurker=widgets.IntText(description="Number of lurker nodes", value=50),
|
||||
honest_per_container=widgets.IntText(description="# of honest peers per container", value=1),
|
||||
),
|
||||
|
||||
pubsub=Bunch(
|
||||
branch=widgets.Text(description="go-libp2p-pubsub branch/tag/commit to target", value="master"),
|
||||
use_hardened_api=widgets.Checkbox(description="target hardening branch API", value=True),
|
||||
heartbeat=widgets.Text(description='Heartbeat interval', value='1s'),
|
||||
hearbeat_delay=widgets.Text(description='Initial heartbeat delay', value='100ms'),
|
||||
validate_queue_size=widgets.IntText(description='Size of validation queue', value=32),
|
||||
outbound_queue_size=widgets.IntText(description='Size of outbound RPC queue', value=32),
|
||||
score_inspect_period=widgets.Text(description='Interval to dump peer scores', value='5s'),
|
||||
full_traces=widgets.Checkbox(description='Capture full event traces)', value=False),
|
||||
degree=widgets.IntText(description='D: target mesh degree', value=10),
|
||||
degree_lo=widgets.IntText(description='D_lo: mesh degree low bound', value=8),
|
||||
degree_hi=widgets.IntText(description='D_hi: mesh degree upper bound', value=16),
|
||||
degree_score=widgets.IntText(description='D_score: peers to select by score', value=5),
|
||||
degree_lazy=widgets.IntText(description='D_lazy: lazy propagation degree', value=12),
|
||||
gossip_factor=widgets.FloatText(description='Gossip Factor', value=0.25),
|
||||
opportunistic_graft_ticks=widgets.IntText(description='Opportunistic Graft heartbeat ticks', value=60),
|
||||
),
|
||||
|
||||
network=Bunch(
|
||||
latency = widgets.Text(description="Min latency", value='5ms'),
|
||||
max_latency = widgets.Text(description="Max latency. If zero, latency will = min latency.", value='50ms'),
|
||||
jitter_pct = widgets.IntSlider(description="Latency jitter %", value=10, min=1, max=100),
|
||||
bandwidth_mb = widgets.IntText(description="Bandwidth (mb)", value=10240),
|
||||
degree=widgets.IntText(description="Degree (# of initial connections) for honest peers", value=20),
|
||||
|
||||
# TODO: support upload of topology file
|
||||
# topology_file = widgets.FileUpload(description="Upload fixed topology file", accept='.json', multiple=False),
|
||||
),
|
||||
|
||||
honest_behavior=Bunch(
|
||||
flood_publishing=widgets.Checkbox(value=True, description='Flood Publishing', indent=False),
|
||||
connect_delay = widgets.Text(description='Honest peer connection delay. e.g. "30s" or "50@30s,30@1m"', value='0s'),
|
||||
connect_jitter_pct = widgets.BoundedIntText(description='Jitter % for honest connect delay', value=5, min=0, max=100),
|
||||
),
|
||||
|
||||
peer_score=Bunch(
|
||||
gossip_threshold=widgets.FloatText(description='Gossip Threshold', value=-4000),
|
||||
publish_threshold=widgets.FloatText(description='Publish Threshold', value=-5000),
|
||||
graylist_threshold=widgets.FloatText(description='Graylist Threshold', value=-10000),
|
||||
acceptpx_threshold=widgets.FloatText(description='Accept PX Threshold', value=0),
|
||||
opportunistic_graft_threshold=widgets.FloatText(description='Opportunistic Graft Threshold', value=0),
|
||||
ip_colocation_weight=widgets.FloatText(description='IP Colocation Factor Weight', value=0),
|
||||
ip_colocation_threshold=widgets.IntText(description='IP Colocation Factor Threshold', value=1),
|
||||
decay_interval=widgets.Text(description='Score Decay Interval', value='1s'),
|
||||
decay_to_zero=widgets.FloatText(description='Decay Zero Threshold', value=0.01),
|
||||
retain_score=widgets.Text(description="Time to Retain Score", value='30s'),
|
||||
)
|
||||
)
|
||||
|
||||
# wire up node count widgets to calculate and show the total number of containers and peers
|
||||
# and update when the params they're derived from change
|
||||
sum_values(w.node_counts.total, w.node_counts.publisher, w.node_counts.lurker)
|
||||
mul_values(w.node_counts.total_peers, w.node_counts.total, w.node_counts.honest_per_container)
|
||||
|
||||
|
||||
self.topic_config = TopicConfigPanel()
|
||||
|
||||
self.save_widgets = Bunch(
|
||||
save_button = widgets.Button(description='Save Config', button_style='primary'),
|
||||
load_button = widgets.Button(description='Load Saved Config', button_style='warning'),
|
||||
snapshot_filename = widgets.Text(description='Path:', value='configs/snapshot.json')
|
||||
)
|
||||
|
||||
self.save_widgets.save_button.on_click(self.save_clicked)
|
||||
self.save_widgets.load_button.on_click(self.load_clicked)
|
||||
save_panel = widgets.HBox(list(self.save_widgets.values()))
|
||||
|
||||
self.panel = widgets.VBox([
|
||||
to_collapsible_sections(w),
|
||||
collapsible("Topic Config", [self.topic_config.ui()]),
|
||||
save_panel,
|
||||
])
|
||||
|
||||
self.widgets = w
|
||||
|
||||
def ui(self):
|
||||
return self.panel
|
||||
|
||||
def save_clicked(self, evt):
|
||||
filename = self.save_widgets.snapshot_filename.value
|
||||
with open(filename, 'wt') as f:
|
||||
json.dump(self.snapshot(), f)
|
||||
print('saved config snapshot to {}'.format(filename))
|
||||
|
||||
def load_clicked(self, evt):
|
||||
filename = self.save_widgets.snapshot_filename.value
|
||||
with open(filename, 'rt') as f:
|
||||
snap = json.load(f)
|
||||
|
||||
# HACK: ignore the test_execution.output_dir param from the snapshot, to
|
||||
# avoid overwriting the output of a prior run
|
||||
if 'test_execution' in snap.get('main', {}):
|
||||
del(snap['main']['test_execution']['output_dir'])
|
||||
|
||||
self.apply_snapshot(snap)
|
||||
print('loaded config snapshot from {}'.format(filename))
|
||||
|
||||
def snapshot(self):
|
||||
return {
|
||||
'main': widget_snapshot(self.widgets),
|
||||
'topic': self.topic_config.snapshot(),
|
||||
}
|
||||
|
||||
def apply_snapshot(self, snapshot):
|
||||
if 'main' in snapshot:
|
||||
apply_snapshot(self.widgets, snapshot['main'])
|
||||
if 'topic' in snapshot:
|
||||
self.topic_config.apply_snapshot(snapshot['topic'])
|
||||
|
||||
def template_params(self):
|
||||
w = self.widgets
|
||||
|
||||
n_nodes = w.node_counts.total.value
|
||||
n_publisher = w.node_counts.publisher.value
|
||||
n_nodes_cont_honest = w.node_counts.honest_per_container.value
|
||||
n_honest_nodes = n_nodes
|
||||
n_honest_peers_total = n_honest_nodes * n_nodes_cont_honest
|
||||
n_container_nodes_total = n_honest_peers_total
|
||||
|
||||
p = {
|
||||
# testground
|
||||
'TEST_BUILDER': w.testground.builder.value,
|
||||
'TEST_RUNNER': w.testground.runner.value,
|
||||
'TEST_PLAN': w.testground.plan_dir.value,
|
||||
|
||||
# time
|
||||
'T_SETUP': w.time.setup.value,
|
||||
'T_RUN': w.time.run.value,
|
||||
'T_WARM': w.time.warm.value,
|
||||
'T_COOL': w.time.cool.value,
|
||||
|
||||
# node counts
|
||||
'N_NODES': n_nodes,
|
||||
'N_CONTAINER_NODES_TOTAL': n_container_nodes_total,
|
||||
'N_PUBLISHER': n_publisher,
|
||||
'N_HONEST_PEERS_PER_NODE': n_nodes_cont_honest,
|
||||
|
||||
# pubsub
|
||||
'T_HEARTBEAT': w.pubsub.heartbeat.value,
|
||||
'T_HEARTBEAT_INITIAL_DELAY': w.pubsub.hearbeat_delay.value,
|
||||
'T_SCORE_INSPECT_PERIOD': w.pubsub.score_inspect_period.value,
|
||||
'VALIDATE_QUEUE_SIZE': w.pubsub.validate_queue_size.value,
|
||||
'OUTBOUND_QUEUE_SIZE': w.pubsub.outbound_queue_size.value,
|
||||
'FULL_TRACES': w.pubsub.full_traces.value,
|
||||
'OVERLAY_D': w.pubsub.degree.value,
|
||||
'OVERLAY_DLO': w.pubsub.degree_lo.value,
|
||||
'OVERLAY_DHI': w.pubsub.degree_hi.value,
|
||||
'OVERLAY_DSCORE': w.pubsub.degree_score.value,
|
||||
'OVERLAY_DLAZY': w.pubsub.degree_lazy.value,
|
||||
'GOSSIP_FACTOR': w.pubsub.gossip_factor.value,
|
||||
'OPPORTUNISTIC_GRAFT_TICKS': w.pubsub.opportunistic_graft_ticks.value,
|
||||
|
||||
# network
|
||||
'T_LATENCY': w.network.latency.value,
|
||||
'T_LATENCY_MAX': w.network.max_latency.value,
|
||||
'JITTER_PCT': w.network.jitter_pct.value,
|
||||
'BANDWIDTH_MB': w.network.bandwidth_mb.value,
|
||||
'N_DEGREE': w.network.degree.value,
|
||||
# TODO: load topology file
|
||||
'TOPOLOGY': {},
|
||||
|
||||
# honest behavior
|
||||
'FLOOD_PUBLISHING': w.honest_behavior.flood_publishing.value,
|
||||
'HONEST_CONNECT_DELAY_JITTER_PCT': w.honest_behavior.connect_jitter_pct.value,
|
||||
|
||||
# topic & peer score configs
|
||||
'TOPIC_CONFIG': self._topic_config(),
|
||||
'PEER_SCORE_PARAMS': self._peer_score_params(),
|
||||
}
|
||||
|
||||
if w.pubsub.use_hardened_api.value:
|
||||
p['BUILD_SELECTORS'] = [run_helpers.HARDENED_API_BUILD_TAG]
|
||||
else:
|
||||
p['BUILD_SELECTORS'] = []
|
||||
|
||||
p['GS_VERSION'] = run_helpers.pubsub_commit(w.pubsub.branch.value)
|
||||
|
||||
run_config = ['log_level="{}"'.format(w.testground.log_level.value)]
|
||||
|
||||
if w.testground.runner.value == 'cluster:k8s':
|
||||
buildopts = ['push_registry=true', 'registry_type="aws"']
|
||||
p['BUILD_CONFIG'] = '\n'.join(buildopts)
|
||||
if w.testground.keep_service.value:
|
||||
run_config.append('keep_service=true')
|
||||
|
||||
p['RUN_CONFIG'] = '\n'.join(run_config)
|
||||
|
||||
# if the connect_delay param doesn't specify a count,
|
||||
# make it apply to all honest nodes
|
||||
delay = w.honest_behavior.connect_delay.value
|
||||
if '@' not in delay:
|
||||
delay = '{}@{}'.format(n_honest_peers_total, delay)
|
||||
p['HONEST_CONNECT_DELAYS'] = delay
|
||||
return p
|
||||
|
||||
def composition(self):
|
||||
return run_helpers.render_template(TEMPLATE, self.template_params())
|
||||
|
||||
def _topic_config(self):
|
||||
# TODO: support multiple topics
|
||||
topics = [self.topic_config.topic_params()]
|
||||
return topics
|
||||
|
||||
def _peer_score_params(self):
|
||||
p = {
|
||||
'Thresholds': {
|
||||
'GossipThreshold': self.widgets.peer_score.gossip_threshold.value,
|
||||
'PublishThreshold': self.widgets.peer_score.publish_threshold.value,
|
||||
'GraylistThreshold': self.widgets.peer_score.graylist_threshold.value,
|
||||
'AcceptPXThreshold': self.widgets.peer_score.acceptpx_threshold.value,
|
||||
'OpportunisticGraftThreshold': self.widgets.peer_score.opportunistic_graft_threshold.value,
|
||||
},
|
||||
'IPColocationFactorWeight': self.widgets.peer_score.ip_colocation_weight.value,
|
||||
'IPColocationFactorThreshold': self.widgets.peer_score.ip_colocation_threshold.value,
|
||||
'DecayInterval': self.widgets.peer_score.decay_interval.value,
|
||||
'DecayToZero': self.widgets.peer_score.decay_to_zero.value,
|
||||
'RetainScore': self.widgets.peer_score.retain_score.value,
|
||||
|
||||
# TODO: support multiple topics
|
||||
'Topics': {self.topic_config.topic_id(): self.topic_config.score_params()}
|
||||
}
|
||||
return p
|
||||
|
||||
|
||||
#### widget helpers ####
|
||||
|
||||
def labeled(widget):
|
||||
if widget.description is None or widget.description == '':
|
||||
return widget
|
||||
label = widget.description
|
||||
widget.style.description_width = '0'
|
||||
return widgets.VBox([widgets.Label(value=label), widget])
|
||||
|
||||
|
||||
def collapsible(title, params, expanded=False):
|
||||
grid = widgets.Layout(width='900px', grid_template_columns="repeat(2, 400px)")
|
||||
inner = widgets.GridBox(params, layout=grid)
|
||||
a = widgets.Accordion(children=[inner])
|
||||
a.set_title(0, title)
|
||||
a.selected_index = 0 if expanded else None
|
||||
return a
|
||||
|
||||
|
||||
def to_collapsible_sections(w, expanded=False):
|
||||
# build up vbox of collapsible sections
|
||||
sections = []
|
||||
for name, params in w.items():
|
||||
title = stringcase.sentencecase(name)
|
||||
children = []
|
||||
for p in params.values():
|
||||
children.append(labeled(p))
|
||||
|
||||
sections.append(collapsible(title, children, expanded=expanded))
|
||||
return widgets.VBox(sections, layout={'width': '900px'})
|
||||
|
||||
|
||||
|
||||
# sets the value of target widget to the sum of all arg widgets and updates when values change
|
||||
def sum_values(target, *args):
|
||||
def callback(change):
|
||||
if change['name'] != 'value':
|
||||
return
|
||||
target.value = functools.reduce(operator.add, [a.value for a in args])
|
||||
|
||||
for widget in args:
|
||||
widget.observe(callback)
|
||||
|
||||
# trigger callback to set initial value
|
||||
callback({'name': 'value'})
|
||||
|
||||
|
||||
# sets the value of target widget to the product of all arg widgets and updates when values change
|
||||
def mul_values(target, *args):
|
||||
def callback(change):
|
||||
if change['name'] != 'value':
|
||||
return
|
||||
target.value = functools.reduce(operator.mul, [a.value for a in args])
|
||||
|
||||
for widget in args:
|
||||
widget.observe(callback)
|
||||
|
||||
# trigger callback to set initial value
|
||||
callback({'name': 'value'})
|
||||
|
||||
|
||||
# takes a nested dict (or Bunch) whose leaves are widgets,
|
||||
# and returns a dict with the same structure, but with widgets replaced with
|
||||
# a snapshot of their current values
|
||||
def widget_snapshot(widgets):
|
||||
out = dict()
|
||||
for name, val in widgets.items():
|
||||
if isinstance(val, Bunch) or isinstance(val, dict):
|
||||
out[name] = widget_snapshot(val)
|
||||
else:
|
||||
w = {'value': val.value}
|
||||
out[name] = w
|
||||
return out
|
||||
|
||||
|
||||
# takes a nested dict or Bunch of widgets and the output of widget_snapshot,
|
||||
# and sets the current widget values to the values from the snapshot
|
||||
def apply_snapshot(widgets, snapshot):
|
||||
for name, val in widgets.items():
|
||||
if name not in snapshot:
|
||||
continue
|
||||
if isinstance(val, Bunch) or isinstance(val, dict):
|
||||
apply_snapshot(val, snapshot[name])
|
||||
else:
|
||||
s = snapshot[name]
|
||||
if 'value' in s:
|
||||
val.value = s['value']
|
|
@ -0,0 +1,382 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
swarm "github.com/libp2p/go-libp2p-swarm"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/testground/sdk-go/runtime"
|
||||
tgsync "github.com/testground/sdk-go/sync"
|
||||
)
|
||||
|
||||
const (
|
||||
PeerConnectTimeout = time.Second * 10
|
||||
MaxConnectRetries = 5
|
||||
)
|
||||
|
||||
// PeerRegistration contains the addresses, sequence numbers and node type (honest / sybil / etc)
|
||||
// for each peer in the test. It is shared with every other peer using the sync service.
|
||||
type PeerRegistration struct {
|
||||
Info peer.AddrInfo
|
||||
NType NodeType
|
||||
NodeTypeSeq int64
|
||||
NodeIdx int
|
||||
IsPublisher bool
|
||||
}
|
||||
|
||||
// SyncDiscovery uses the testground sync API to share PeerRegistrations for the
|
||||
// local test peers and collect the info from all the other peers. It then allows
|
||||
// you to connect the local peers to a subset of the test peers, using a Topology
|
||||
// to control the peer selection.
|
||||
type SyncDiscovery struct {
|
||||
h host.Host
|
||||
runenv *runtime.RunEnv
|
||||
peerSubscriber *PeerSubscriber
|
||||
topology Topology
|
||||
nodeType NodeType
|
||||
nodeTypeSeq int64
|
||||
nodeIdx int
|
||||
isPublisher bool
|
||||
|
||||
// All peers in the test
|
||||
allPeers []PeerRegistration
|
||||
|
||||
// The peers that this node connects to
|
||||
connectedLk sync.RWMutex
|
||||
connected map[peer.ID]PeerRegistration
|
||||
}
|
||||
|
||||
// A Topology filters the set of all nodes
|
||||
type Topology interface {
|
||||
SelectPeers(local peer.ID, remote []PeerRegistration) []PeerRegistration
|
||||
}
|
||||
|
||||
// RandomTopology selects a subset of the total nodes at random
|
||||
type RandomTopology struct {
|
||||
// Count is the number of total peers to return
|
||||
Count int
|
||||
}
|
||||
|
||||
func (t RandomTopology) SelectPeers(local peer.ID, remote []PeerRegistration) []PeerRegistration {
|
||||
if len(remote) == 0 || t.Count == 0 {
|
||||
return []PeerRegistration{}
|
||||
}
|
||||
|
||||
n := t.Count
|
||||
if n > len(remote) {
|
||||
n = len(remote)
|
||||
}
|
||||
|
||||
indices := rand.Perm(len(remote))
|
||||
out := make([]PeerRegistration, n)
|
||||
for i := 0; i < n; i++ {
|
||||
out[i] = remote[indices[i]]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RandomHonestTopology is a Topology that returns a subset of all non-attack nodes
|
||||
type RandomHonestTopology struct {
|
||||
// Count is the number of total peers to return
|
||||
Count int
|
||||
// PublishersOnly indicates whether to connect to publishers only or to
|
||||
// both publishers and lurkers
|
||||
PublishersOnly bool
|
||||
}
|
||||
|
||||
func (t RandomHonestTopology) SelectPeers(local peer.ID, remote []PeerRegistration) []PeerRegistration {
|
||||
if len(remote) == 0 {
|
||||
return []PeerRegistration{}
|
||||
}
|
||||
|
||||
filtered := make([]PeerRegistration, 0, len(remote))
|
||||
for _, peer := range remote {
|
||||
// Only connect to honest nodes.
|
||||
// If PublishersOnly is true, only connect to Publishers
|
||||
if peer.NType == NodeTypeHonest && (!t.PublishersOnly || peer.IsPublisher) {
|
||||
filtered = append(filtered, peer)
|
||||
}
|
||||
}
|
||||
|
||||
return RandomTopology{t.Count}.SelectPeers(local, filtered)
|
||||
}
|
||||
|
||||
// SinglePublisherTopology is a Topology that returns the first publisher node
|
||||
type SinglePublisherTopology struct {
|
||||
}
|
||||
|
||||
func (t SinglePublisherTopology) SelectPeers(local peer.ID, remote []PeerRegistration) []PeerRegistration {
|
||||
publisher := selectSinglePublisher(remote)
|
||||
if publisher != nil {
|
||||
return []PeerRegistration{*publisher}
|
||||
}
|
||||
return []PeerRegistration{}
|
||||
}
|
||||
|
||||
// Select the publisher with the lowest sequence number and index
|
||||
func selectSinglePublisher(peers []PeerRegistration) *PeerRegistration {
|
||||
lowest := int64(-1)
|
||||
var lowestp PeerRegistration
|
||||
for _, p := range peers {
|
||||
if p.IsPublisher {
|
||||
current := int64(p.NodeTypeSeq*1000000 + int64(p.NodeIdx))
|
||||
if lowest < 0 || current < lowest {
|
||||
lowest = current
|
||||
lowestp = p
|
||||
}
|
||||
}
|
||||
}
|
||||
if lowest == -1 {
|
||||
return nil
|
||||
}
|
||||
return &lowestp
|
||||
}
|
||||
|
||||
// FixedTopology is defined by a topology file
|
||||
type FixedTopology struct {
|
||||
// def contains the definition of the topology
|
||||
def *ConnectionsDef
|
||||
}
|
||||
|
||||
func (t FixedTopology) SelectPeers(local peer.ID, remote []PeerRegistration) []PeerRegistration {
|
||||
if len(remote) == 0 {
|
||||
return []PeerRegistration{}
|
||||
}
|
||||
|
||||
out := make([]PeerRegistration, 0, len(t.def.Connections))
|
||||
for _, conn := range t.def.Connections {
|
||||
parts := strings.Split(conn, "-")
|
||||
if len(parts) != 3 {
|
||||
panic(fmt.Sprintf("Badly formatted topology file"))
|
||||
}
|
||||
nodeType := parts[0]
|
||||
nodeTypeSeq := parts[1]
|
||||
nodeIdx := parts[2]
|
||||
for _, p := range remote {
|
||||
if nodeType == string(p.NType) && nodeTypeSeq == strconv.Itoa(int(p.NodeTypeSeq)) && nodeIdx == strconv.Itoa(int(p.NodeIdx)) {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// PeerSubscriber subscribes to peer information from all nodes in all containers.
|
||||
// There is one PeerSubscriber per container (but there may be several nodes per container)
|
||||
type PeerSubscriber struct {
|
||||
lk sync.Mutex
|
||||
peers []PeerRegistration
|
||||
runenv *runtime.RunEnv
|
||||
client *tgsync.Client
|
||||
containerCount int
|
||||
containerNodesTotal int
|
||||
}
|
||||
|
||||
func NewPeerSubscriber(ctx context.Context, runenv *runtime.RunEnv, client *tgsync.Client, containerCount int, containerNodesTotal int) *PeerSubscriber {
|
||||
return &PeerSubscriber{
|
||||
runenv: runenv,
|
||||
client: client,
|
||||
containerCount: containerCount,
|
||||
containerNodesTotal: containerNodesTotal,
|
||||
}
|
||||
}
|
||||
|
||||
var PeerRegistrationTopic = tgsync.NewTopic("pubsub-test-peers", &PeerRegistration{})
|
||||
|
||||
// Register node information for the local node
|
||||
func (ps *PeerSubscriber) register(ctx context.Context, entry PeerRegistration) error {
|
||||
if _, err := ps.client.Publish(ctx, PeerRegistrationTopic, &entry); err != nil {
|
||||
return fmt.Errorf("failed to write to pubsub subtree in sync service: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for node information from all nodes in all containers
|
||||
func (ps *PeerSubscriber) waitForPeers(ctx context.Context) ([]PeerRegistration, error) {
|
||||
ps.lk.Lock()
|
||||
defer ps.lk.Unlock()
|
||||
|
||||
if ps.peers != nil {
|
||||
return ps.peers, nil
|
||||
}
|
||||
|
||||
// wait for all other peers to send their peer registration
|
||||
peerCh := make(chan *PeerRegistration, 16)
|
||||
ps.peers = make([]PeerRegistration, 0, ps.containerNodesTotal)
|
||||
|
||||
// add a random delay before subscribing, to avoid overloading the subscriber system
|
||||
delay := time.Duration(rand.Intn(ps.containerCount)) * time.Millisecond
|
||||
if delay > time.Second {
|
||||
ps.runenv.RecordMessage("waiting for %s before subscribing", delay)
|
||||
}
|
||||
time.Sleep(delay)
|
||||
|
||||
sctx, cancelSub := context.WithCancel(ctx)
|
||||
if _, err := ps.client.Subscribe(sctx, PeerRegistrationTopic, peerCh); err != nil {
|
||||
cancelSub()
|
||||
return nil, err
|
||||
}
|
||||
defer cancelSub()
|
||||
|
||||
start := time.Now()
|
||||
ps.runenv.RecordMessage("waiting for peer information from %d peers", ps.containerNodesTotal)
|
||||
for i := 0; i < ps.containerNodesTotal; i++ {
|
||||
select {
|
||||
case ai, ok := <-peerCh:
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not enough peer infos. expected %d, got %d", ps.containerNodesTotal, len(ps.peers))
|
||||
}
|
||||
ps.peers = append(ps.peers, *ai)
|
||||
if len(ps.peers)%500 == 0 {
|
||||
ps.runenv.RecordMessage("received peer information from %d of %d peers in %s", len(ps.peers), ps.containerNodesTotal, time.Since(start))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
ps.runenv.RecordMessage("context cancelled before receiving peer information from %d peers: %s", ps.containerNodesTotal, ctx.Err())
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
ps.runenv.RecordMessage("received peer information from %d peers in %s", len(ps.peers), time.Since(start))
|
||||
|
||||
return ps.peers, nil
|
||||
}
|
||||
|
||||
func NewSyncDiscovery(h host.Host, runenv *runtime.RunEnv, peerSubscriber *PeerSubscriber,
|
||||
topology Topology, nodeType NodeType, nodeTypeSeq int64, nodeIdx int, isPublisher bool) (*SyncDiscovery, error) {
|
||||
|
||||
return &SyncDiscovery{
|
||||
h: h,
|
||||
runenv: runenv,
|
||||
peerSubscriber: peerSubscriber,
|
||||
topology: topology,
|
||||
nodeType: nodeType,
|
||||
nodeTypeSeq: nodeTypeSeq,
|
||||
nodeIdx: nodeIdx,
|
||||
isPublisher: isPublisher,
|
||||
connected: make(map[peer.ID]PeerRegistration),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Registers node and waits to collect all other nodes' registrations.
|
||||
func (s *SyncDiscovery) registerAndWait(ctx context.Context) error {
|
||||
// Register this node's information
|
||||
localPeer := *host.InfoFromHost(s.h)
|
||||
entry := PeerRegistration{
|
||||
Info: localPeer,
|
||||
NType: s.nodeType,
|
||||
NodeTypeSeq: s.nodeTypeSeq,
|
||||
NodeIdx: s.nodeIdx,
|
||||
IsPublisher: s.isPublisher,
|
||||
}
|
||||
err := s.peerSubscriber.register(ctx, entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for all peers' node information
|
||||
peers, err := s.peerSubscriber.waitForPeers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Filter out this node's information from all peers
|
||||
s.allPeers = make([]PeerRegistration, 0, len(peers)-1)
|
||||
for _, p := range peers {
|
||||
if p.Info.ID != localPeer.ID {
|
||||
s.allPeers = append(s.allPeers, p)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect to all peers in the topology
|
||||
func (s *SyncDiscovery) ConnectTopology(ctx context.Context, delay time.Duration) error {
|
||||
s.runenv.RecordMessage("delay connect to peers by %s", delay)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(delay):
|
||||
s.runenv.RecordMessage("connecting to peers after %s", delay)
|
||||
}
|
||||
|
||||
selected := s.topology.SelectPeers(s.h.ID(), s.allPeers)
|
||||
if len(selected) == 0 {
|
||||
panic("topology selected zero peers. so lonely!!!")
|
||||
}
|
||||
|
||||
s.connectedLk.Lock()
|
||||
|
||||
errgrp, ctx := errgroup.WithContext(ctx)
|
||||
for _, p := range selected {
|
||||
p := p
|
||||
if _, ok := s.connected[p.Info.ID]; !ok {
|
||||
s.connected[p.Info.ID] = p
|
||||
s.runenv.RecordMessage("%s-%d-%d connecting to %s-%d-%d\n", s.nodeType, s.nodeTypeSeq, s.nodeIdx, p.NType, p.NodeTypeSeq, p.NodeIdx)
|
||||
errgrp.Go(func() error {
|
||||
err := s.connectWithRetry(ctx, p.Info)
|
||||
if err != nil {
|
||||
s.runenv.RecordMessage("error connecting libp2p host: %s", err)
|
||||
}
|
||||
conns := s.h.Network().ConnsToPeer(p.Info.ID)
|
||||
for _, conn := range conns {
|
||||
s.runenv.RecordMessage("%s-%d-%d connected to %s-%d-%d. local addr: %s remote addr: %s\n",
|
||||
s.nodeType, s.nodeTypeSeq, s.nodeIdx, p.NType, p.NodeTypeSeq, p.NodeIdx,
|
||||
conn.LocalMultiaddr(), conn.RemoteMultiaddr())
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
s.connectedLk.Unlock()
|
||||
|
||||
return errgrp.Wait()
|
||||
}
|
||||
|
||||
func (s *SyncDiscovery) connectWithRetry(ctx context.Context, p peer.AddrInfo) error {
|
||||
return retry.Do(
|
||||
func() error {
|
||||
// add a random delay to each connection attempt to spread the network load
|
||||
connectDelay := time.Duration(rand.Intn(1000)) * time.Millisecond
|
||||
<-time.After(connectDelay)
|
||||
|
||||
boundedCtx, cancel := context.WithTimeout(ctx, PeerConnectTimeout)
|
||||
defer cancel()
|
||||
return s.h.Connect(boundedCtx, p)
|
||||
},
|
||||
retry.Attempts(MaxConnectRetries),
|
||||
retry.OnRetry(func(n uint, err error) {
|
||||
s.runenv.RecordMessage("connection attempt #%d to %s failed: %s", n, p.ID.Pretty(), err)
|
||||
|
||||
// clear the libp2p dial backoff for this peer, otherwise the swarm will ignore our
|
||||
// dial attempt and immediately return a "dial backoff" error
|
||||
if sw, ok := s.h.Network().(*swarm.Swarm); ok {
|
||||
s.runenv.RecordMessage("clearing swarm dial backoff for peer %s", p.ID.Pretty())
|
||||
sw.Backoff().Clear(p.ID)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *SyncDiscovery) Connected() []PeerRegistration {
|
||||
s.connectedLk.RLock()
|
||||
defer s.connectedLk.RUnlock()
|
||||
|
||||
d := make([]PeerRegistration, 0, len(s.connected))
|
||||
for _, p := range s.connected {
|
||||
d = append(d, p)
|
||||
}
|
||||
return d
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
module github.com/libp2p/test-plans/pubsub/test
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/avast/retry-go v2.6.0+incompatible
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/libp2p/go-libp2p v0.6.1
|
||||
github.com/libp2p/go-libp2p-core v0.5.0
|
||||
github.com/libp2p/go-libp2p-pubsub v0.2.6
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.2
|
||||
github.com/multiformats/go-multiaddr v0.2.1
|
||||
github.com/multiformats/go-multiaddr-net v0.1.3
|
||||
github.com/testground/sdk-go v0.2.1
|
||||
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
)
|
|
@ -0,0 +1,541 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/avast/retry-go v2.6.0+incompatible h1:FelcMrm7Bxacr1/RM8+/eqkDkmVN7tjlsy51dOzB3LI=
|
||||
github.com/avast/retry-go v2.6.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
|
||||
github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0=
|
||||
github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4=
|
||||
github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
|
||||
github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
|
||||
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU=
|
||||
github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
|
||||
github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
|
||||
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M=
|
||||
github.com/ipfs/go-cid v0.0.5 h1:o0Ix8e/ql7Zb5UVUJEUfjsWCIY8t48++9lR8qi6oiJU=
|
||||
github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog=
|
||||
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA=
|
||||
github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA=
|
||||
github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA=
|
||||
github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
|
||||
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
|
||||
github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8=
|
||||
github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s=
|
||||
github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE=
|
||||
github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc=
|
||||
github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s=
|
||||
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
|
||||
github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50=
|
||||
github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc=
|
||||
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
|
||||
github.com/ipfs/go-log v1.0.2 h1:s19ZwJxH8rPWzypjcDpqPLIyV7BnbLqvpli3iZoqYK0=
|
||||
github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk=
|
||||
github.com/ipfs/go-log/v2 v2.0.2 h1:xguurydRdfKMJjKyxNXNU8lYP0VZH1NUwJRwUorjuEw=
|
||||
github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0=
|
||||
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
|
||||
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/jackpal/go-nat-pmp v1.0.1 h1:i0LektDkO1QlrTm/cSuP+PyBCDnYvjPLGl4LdWEMiaA=
|
||||
github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs=
|
||||
github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc=
|
||||
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
|
||||
github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2 h1:vhC1OXXiT9R2pczegwz6moDvuRpggaroAXhPIseh57A=
|
||||
github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs=
|
||||
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=
|
||||
github.com/jbenet/goprocess v0.1.3 h1:YKyIEECS/XvcfHtBzxtjBBbWK+MbvA6dG8ASiqwvr10=
|
||||
github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ=
|
||||
github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/libp2p/go-addr-util v0.0.1 h1:TpTQm9cXVRVSKsYbgQ7GKc3KbbHVTnbostgGaDEP+88=
|
||||
github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ=
|
||||
github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ=
|
||||
github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
|
||||
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
|
||||
github.com/libp2p/go-conn-security-multistream v0.1.0 h1:aqGmto+ttL/uJgX0JtQI0tD21CIEy5eYd1Hlp0juHY0=
|
||||
github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc=
|
||||
github.com/libp2p/go-eventbus v0.1.0 h1:mlawomSAjjkk97QnYiEmHsLu7E136+2oCWSHRUvMfzQ=
|
||||
github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4=
|
||||
github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8=
|
||||
github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM=
|
||||
github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs=
|
||||
github.com/libp2p/go-libp2p v0.6.1 h1:mxabyJf4l6AmotDOKObwSfBNBWjL5VYXysVFLUMAuB8=
|
||||
github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54=
|
||||
github.com/libp2p/go-libp2p-autonat v0.1.1 h1:WLBZcIRsjZlWdAZj9CiBSvU2wQXoUOiS1Zk1tM7DTJI=
|
||||
github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE=
|
||||
github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro=
|
||||
github.com/libp2p/go-libp2p-blankhost v0.1.4 h1:I96SWjR4rK9irDHcHq3XHN6hawCRTPUADzkJacgZLvk=
|
||||
github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU=
|
||||
github.com/libp2p/go-libp2p-circuit v0.1.4 h1:Phzbmrg3BkVzbqd4ZZ149JxCuUWu2wZcXf/Kr6hZJj8=
|
||||
github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU=
|
||||
github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco=
|
||||
github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I=
|
||||
github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI=
|
||||
github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0=
|
||||
github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g=
|
||||
github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw=
|
||||
github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII=
|
||||
github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0=
|
||||
github.com/libp2p/go-libp2p-core v0.5.0 h1:FBQ1fpq2Fo/ClyjojVJ5AKXlKhvNc/B6U0O+7AN1ffE=
|
||||
github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0=
|
||||
github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI=
|
||||
github.com/libp2p/go-libp2p-discovery v0.2.0 h1:1p3YSOq7VsgaL+xVHPi8XAmtGyas6D2J6rWBEfz/aiY=
|
||||
github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg=
|
||||
github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8=
|
||||
github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90=
|
||||
github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo=
|
||||
github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE=
|
||||
github.com/libp2p/go-libp2p-mplex v0.2.2 h1:+Ld7YDAfVERQ0E+qqjE7o6fHwKuM0SqTzYiwN1lVVSA=
|
||||
github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo=
|
||||
github.com/libp2p/go-libp2p-nat v0.0.5 h1:/mH8pXFVKleflDL1YwqMg27W9GD8kjEx7NY0P6eGc98=
|
||||
github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE=
|
||||
github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ=
|
||||
github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU=
|
||||
github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.0 h1:XcgJhI8WyUOCbHyRLNEX5542YNj8hnLSJ2G1InRjDhk=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ=
|
||||
github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k=
|
||||
github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.2.6 h1:ypZaukCFrtD8cNeeb9nnWG4MD2Y1T0p22aQ+f7FKJig=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.2.6/go.mod h1:5jEp7R3ItQ0pgcEMrPZYE9DQTg/H3CTc7Mu1j2G4Y5o=
|
||||
github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8=
|
||||
github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g=
|
||||
github.com/libp2p/go-libp2p-secio v0.2.1 h1:eNWbJTdyPA7NxhP7J3c5lT97DC5d+u+IldkgCYFTPVA=
|
||||
github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8=
|
||||
github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.2 h1:T4hUpgEs2r371PweU3DuH7EOmBIdTBCwWs+FLcgx3bQ=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0=
|
||||
github.com/libp2p/go-libp2p-testing v0.1.1 h1:U03z3HnGI7Ni8Xx6ONVZvUFOAzWYmolWf5W5jAOPNmU=
|
||||
github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0=
|
||||
github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA=
|
||||
github.com/libp2p/go-libp2p-transport-upgrader v0.2.0 h1:5EhPgQhXZNyfL22ERZTUoVp9UVVbNowWNVtELQaKCHk=
|
||||
github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns=
|
||||
github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8=
|
||||
github.com/libp2p/go-libp2p-yamux v0.2.5 h1:MuyItOqz03oi8npvjgMJxgnhllJLZnO/dKVOpTZ9+XI=
|
||||
github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA=
|
||||
github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q=
|
||||
github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg=
|
||||
github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M=
|
||||
github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0=
|
||||
github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU=
|
||||
github.com/libp2p/go-mplex v0.1.1 h1:huPH/GGRJzmsHR9IZJJsrSwIM5YE2gL4ssgl1YWb/ps=
|
||||
github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk=
|
||||
github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
|
||||
github.com/libp2p/go-msgio v0.0.4 h1:agEFehY3zWJFUHK6SEMR7UYmk2z6kC3oeCM7ybLhguA=
|
||||
github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
|
||||
github.com/libp2p/go-nat v0.0.4 h1:KbizNnq8YIf7+Hn7+VFL/xE0eDrkPru2zIO9NMwL8UQ=
|
||||
github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo=
|
||||
github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0=
|
||||
github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc=
|
||||
github.com/libp2p/go-openssl v0.0.4 h1:d27YZvLoTyMhIN4njrkr8zMDOM4lfpHIp6A+TK9fovg=
|
||||
github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc=
|
||||
github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw=
|
||||
github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA=
|
||||
github.com/libp2p/go-reuseport-transport v0.0.2 h1:WglMwyXyBu61CMkjCCtnmqNqnjib0GIEjMiHTwR/KN4=
|
||||
github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs=
|
||||
github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14=
|
||||
github.com/libp2p/go-stream-muxer-multistream v0.2.0 h1:714bRJ4Zy9mdhyTLJ+ZKiROmAFwUHpeRidG+q7LTQOg=
|
||||
github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc=
|
||||
github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc=
|
||||
github.com/libp2p/go-tcp-transport v0.1.1 h1:yGlqURmqgNA2fvzjSgZNlHcsd/IulAnKM8Ncu+vlqnw=
|
||||
github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY=
|
||||
github.com/libp2p/go-ws-transport v0.2.0 h1:MJCw2OrPA9+76YNRvdo1wMnSOxb9Bivj6sVFY1Xrj6w=
|
||||
github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM=
|
||||
github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
|
||||
github.com/libp2p/go-yamux v1.3.3 h1:mWuzZRCAeTBFdynLlsYgA/EIeMOLr8XY04wa52NRhsE=
|
||||
github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
|
||||
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
|
||||
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
|
||||
github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
|
||||
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
|
||||
github.com/multiformats/go-multiaddr v0.2.1 h1:SgG/cw5vqyB5QQe5FPe2TqggU9WtrA9X4nZw7LlVqOI=
|
||||
github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE=
|
||||
github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
|
||||
github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
|
||||
github.com/multiformats/go-multiaddr-dns v0.2.0 h1:YWJoIDwLePniH7OU5hBnDZV6SWuvJqJ0YtN6pLeH9zA=
|
||||
github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0=
|
||||
github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q=
|
||||
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
|
||||
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
|
||||
github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU=
|
||||
github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ=
|
||||
github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ=
|
||||
github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y=
|
||||
github.com/multiformats/go-multiaddr-net v0.1.3 h1:q/IYAvoPKuRzGeERn3uacWgm0LIWkLZBAvO5DxSzq3g=
|
||||
github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA=
|
||||
github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA=
|
||||
github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs=
|
||||
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
|
||||
github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po=
|
||||
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
|
||||
github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
|
||||
github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc=
|
||||
github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
|
||||
github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg=
|
||||
github.com/multiformats/go-multistream v0.1.1 h1:JlAdpIFhBhGRLxe9W6Om0w++Gd6KMWoFPZL/dEnm9nI=
|
||||
github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38=
|
||||
github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
|
||||
github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
|
||||
github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg=
|
||||
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
||||
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
|
||||
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
|
||||
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY=
|
||||
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0=
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/testground/sdk-go v0.2.1 h1:/YLO5ZY084tMyUckkapOcKVZEdPrbJM2b010ficJq7M=
|
||||
github.com/testground/sdk-go v0.2.1/go.mod h1:3auzMDXaoK7NQ+CLQS3pqp4hmREECWO9V+TJi/IWmms=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=
|
||||
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM=
|
||||
github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE=
|
||||
github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA=
|
||||
github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA=
|
||||
github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4=
|
||||
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds=
|
||||
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI=
|
||||
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee h1:lYbXeSvJi5zk5GLKVuid9TVjS9a0OmLIDKTfoZBL6Ow=
|
||||
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
|
||||
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8=
|
||||
gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
@ -0,0 +1,24 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/testground/sdk-go/runtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.Invoke(run)
|
||||
}
|
||||
|
||||
// Pick a different example function to run
|
||||
// depending on the name of the test case.
|
||||
func run(runenv *runtime.RunEnv) error {
|
||||
switch c := runenv.TestCase; c {
|
||||
case "evaluate":
|
||||
return RunSimulation(runenv)
|
||||
default:
|
||||
msg := fmt.Sprintf("Unknown Testcase %s", c)
|
||||
return errors.New(msg)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
name = "pubsub"
|
||||
|
||||
[defaults]
|
||||
builder = "exec:go"
|
||||
runner = "local:exec"
|
||||
|
||||
[builders."docker:go"]
|
||||
enabled = true
|
||||
go_version = "1.14"
|
||||
module_path = "github.com/libp2p/test-plans/pubsub/test"
|
||||
exec_pkg = "."
|
||||
go_ipfs_version = "0.4.22"
|
||||
|
||||
[builders."exec:go"]
|
||||
enabled = true
|
||||
module_path = "github.com/libp2p/test-plans/pubsub/test"
|
||||
exec_pkg = "."
|
||||
|
||||
[runners."local:docker"]
|
||||
enabled = true
|
||||
|
||||
[runners."local:exec"]
|
||||
enabled = true
|
||||
|
||||
[runners."cluster:k8s"]
|
||||
enabled = true
|
||||
|
||||
[[testcases]]
|
||||
name = "evaluate"
|
||||
instances = { min = 1, max = 2000, default = 10 }
|
||||
[testcases.params]
|
||||
# params with type "duration" must be parseable by time.ParseDuration, e.g. 2m or 30s
|
||||
# params with type "size" must be parseable by https://godoc.org/github.com/dustin/go-humanize#ParseBytes, e.g. "1kb"
|
||||
|
||||
## global params
|
||||
t_heartbeat = { type = "duration", desc = "Interval between emiting maintenance messages", default="1s" }
|
||||
t_heartbeat_initial_delay = { type = "duration", desc = "Delay before starting hearbeat", default="100ms" }
|
||||
t_setup = { type = "duration", desc = "Upper bound on expected time period for waiting for all peers to register etc", default="1m" }
|
||||
t_run = { type = "duration", desc = "Time to run the simulation", default="2m" }
|
||||
t_warm = { type = "duration", desc = "Time to wait for nodes to establish connections before beginning publishing", default="5s" }
|
||||
t_cool = { type = "duration", desc = "Time to wait after test execution for straggling publishers, etc.", default="10s" }
|
||||
topics = { type = "json", desc = "json array of TopicConfig objects." }
|
||||
score_params = { type = "json", desc = "a json ScoreParams object (see params.go). ignored unless hardened_api build flag is set."}
|
||||
full_traces = { type = "bool", desc = "if true, collect full pubsub protobuf trace events, in addition to aggregate metrics", default="false" }
|
||||
validate_queue_size = { type = "int", desc = "Size of pubsub validation queue", default=0 }
|
||||
outbound_queue_size = { type = "int", desc = "Size of pubsub outbound queue", default=0 }
|
||||
t_latency = { type = "duration", desc = "Network latency between nodes", default="5ms" }
|
||||
t_latency_max = { type = "duration", desc = "If supplied, latency is between t_latency and t_latency_max", default="50ms" }
|
||||
jitter_pct = { type = "int", desc = "Jitter in latency", default=10 }
|
||||
bandwidth_mb = { type = "int", desc = "Bandwidth in MiB", default=10240 }
|
||||
topology = { type = "string", desc = "topology in json format" }
|
||||
degree = { type = "int", desc = "the number of nodes to connect to", default=20 }
|
||||
n_container_nodes_total = { type = "int", desc = "the number of total nodes including multiple nodes per container", default=1 }
|
||||
n_nodes_per_container = { type = "int", desc = "the number of nodes to start up in each container", default=1 }
|
||||
|
||||
## pubsub node config
|
||||
publisher = { type = "bool", desc = "if true, this instance should publish to subscribed topics instead of lurking", default=false }
|
||||
flood_publishing = { type = "bool", desc = "if true, nodes will flood when publishing their own messages. only applies to hardening branch", default=false }
|
||||
t_score_inspect_period = { type = "duration", desc = "Interval between printing peer scores", default="0" }
|
||||
overlay_d = { type = "int", desc = "the number of nodes gossipsub tries to stay connected to", default=-1 }
|
||||
overlay_dlo = { type = "int", desc = "the low watermark of overlay_d", default=-1 }
|
||||
overlay_dhi = { type = "int", desc = "the high watermark of overlay_d", default=-1 }
|
||||
overlay_dscore = { type = "int", desc = "the number of peers to keep by score", default=-1 }
|
||||
overlay_dlazy = { type = "int", desc = "degree for gossip nodes", default=-1 }
|
||||
gossip_factor = { type = "float", desc = "gossip factor", default=0.25 }
|
||||
opportunistic_graft_ticks = { type = "int", desc = "Number of heartbeat ticks for attempting opportunistic grafting", default=60 }
|
|
@ -0,0 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/testground/sdk-go/network"
|
||||
"github.com/testground/sdk-go/runtime"
|
||||
)
|
||||
|
||||
// setupNetwork instructs the sidecar (if enabled) to setup the network for this
|
||||
// test case.
|
||||
func setupNetwork(ctx context.Context, runenv *runtime.RunEnv, netParams NetworkParams, netclient *network.Client) error {
|
||||
if !runenv.TestSidecar {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for the network to be initialized.
|
||||
runenv.RecordMessage("Waiting for network initialization")
|
||||
err := netclient.WaitNetworkInitialized(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runenv.RecordMessage("Network init complete")
|
||||
|
||||
latency := netParams.latency
|
||||
if netParams.latencyMax > 0 {
|
||||
// If a maximum latency is supplied, choose a random latency between
|
||||
// latency and max latency
|
||||
latency += time.Duration(rand.Float64() * float64(netParams.latencyMax-latency))
|
||||
}
|
||||
|
||||
config := &network.Config{
|
||||
Network: "default",
|
||||
Enable: true,
|
||||
Default: network.LinkShape{
|
||||
Latency: latency,
|
||||
Bandwidth: uint64(netParams.bandwidthMB) * 1024 * 1024,
|
||||
Jitter: (time.Duration(netParams.jitterPct) * netParams.latency) / 100,
|
||||
},
|
||||
CallbackState: "network-configured",
|
||||
}
|
||||
|
||||
// random delay to avoid overloading weave (we hope)
|
||||
delay := time.Duration(rand.Intn(1000)) * time.Millisecond
|
||||
<-time.After(delay)
|
||||
err = netclient.ConfigureNetwork(ctx, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runenv.RecordMessage("egress: %s latency (%d%% jitter) and %dMB bandwidth", netParams.latency, netParams.jitterPct, netParams.bandwidthMB)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/testground/sdk-go/runtime"
|
||||
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
)
|
||||
|
||||
type PubsubNodeConfig struct {
|
||||
// topics to join when node starts
|
||||
Topics []TopicConfig
|
||||
|
||||
// whether we're a publisher or a lurker
|
||||
Publisher bool
|
||||
|
||||
// pubsub event tracer
|
||||
Tracer pubsub.EventTracer
|
||||
|
||||
// Test instance identifier
|
||||
Seq int64
|
||||
|
||||
// How long to wait after connecting to bootstrap peers before publishing
|
||||
Warmup time.Duration
|
||||
|
||||
// How long to wait for cooldown
|
||||
Cooldown time.Duration
|
||||
|
||||
// Gossipsub heartbeat params
|
||||
Heartbeat HeartbeatParams
|
||||
|
||||
// whether to flood the network when publishing our own messages.
|
||||
// Ignored unless hardening_api build tag is present.
|
||||
FloodPublishing bool
|
||||
|
||||
// Params for peer scoring function. Ignored unless hardening_api build tag is present.
|
||||
PeerScoreParams ScoreParams
|
||||
|
||||
OverlayParams OverlayParams
|
||||
|
||||
// Params for inspecting the scoring values.
|
||||
PeerScoreInspect InspectParams
|
||||
|
||||
// Size of the pubsub validation queue.
|
||||
ValidateQueueSize int
|
||||
|
||||
// Size of the pubsub outbound queue.
|
||||
OutboundQueueSize int
|
||||
|
||||
// Heartbeat tics for opportunistic grafting
|
||||
OpportunisticGraftTicks int
|
||||
}
|
||||
|
||||
type InspectParams struct {
|
||||
// The callback function that is called with the peer scores
|
||||
Inspect func(map[peer.ID]float64)
|
||||
// The interval between calling Inspect (defaults to zero: dont inspect).
|
||||
Period time.Duration
|
||||
}
|
||||
|
||||
type topicState struct {
|
||||
cfg TopicConfig
|
||||
nMessages int64
|
||||
topic *pubsub.Topic
|
||||
sub *pubsub.Subscription
|
||||
pubTicker *time.Ticker
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type PubsubNode struct {
|
||||
cfg PubsubNodeConfig
|
||||
ctx context.Context
|
||||
shutdown func()
|
||||
runenv *runtime.RunEnv
|
||||
h host.Host
|
||||
ps *pubsub.PubSub
|
||||
|
||||
lk sync.RWMutex
|
||||
topics map[string]*topicState
|
||||
|
||||
pubwg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewPubsubNode prepares the given Host to act as an honest pubsub node using the provided PubsubNodeConfig.
|
||||
// The returned PubsubNode will not start immediately; call Run to begin the test behavior.
|
||||
func NewPubsubNode(runenv *runtime.RunEnv, ctx context.Context, h host.Host, cfg PubsubNodeConfig) (*PubsubNode, error) {
|
||||
opts, err := pubsubOptions(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the heartbeat initial delay and interval
|
||||
pubsub.GossipSubHeartbeatInitialDelay = cfg.Heartbeat.InitialDelay
|
||||
pubsub.GossipSubHeartbeatInterval = cfg.Heartbeat.Interval
|
||||
|
||||
ps, err := pubsub.NewGossipSub(ctx, h, opts...)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making new gossipsub: %s", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
p := PubsubNode{
|
||||
cfg: cfg,
|
||||
ctx: ctx,
|
||||
shutdown: cancel,
|
||||
runenv: runenv,
|
||||
h: h,
|
||||
ps: ps,
|
||||
topics: make(map[string]*topicState),
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (p *PubsubNode) log(msg string, args ...interface{}) {
|
||||
id := p.h.ID().Pretty()
|
||||
idSuffix := id[len(id)-8:]
|
||||
prefix := fmt.Sprintf("[honest %d %s] ", p.cfg.Seq, idSuffix)
|
||||
p.runenv.RecordMessage(prefix+msg, args...)
|
||||
}
|
||||
|
||||
func (p *PubsubNode) Run(runtime time.Duration, waitForReadyStateThenConnectAsync func(context.Context) error) error {
|
||||
defer func() {
|
||||
// end subscription goroutines before exit
|
||||
for _, ts := range p.topics {
|
||||
ts.done <- struct{}{}
|
||||
}
|
||||
|
||||
p.shutdown()
|
||||
}()
|
||||
|
||||
// Wait for all nodes to be in the ready state (including attack nodes, if any)
|
||||
// then start connecting (asynchronously)
|
||||
if err := waitForReadyStateThenConnectAsync(p.ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// join initial topics
|
||||
p.runenv.RecordMessage("Joining initial topics")
|
||||
for _, t := range p.cfg.Topics {
|
||||
go p.joinTopic(t, runtime)
|
||||
}
|
||||
|
||||
// wait for warmup time to expire
|
||||
p.runenv.RecordMessage("Wait for %s warmup time", p.cfg.Warmup)
|
||||
select {
|
||||
case <-time.After(p.cfg.Warmup):
|
||||
case <-p.ctx.Done():
|
||||
return p.ctx.Err()
|
||||
}
|
||||
|
||||
// ensure we have at least enough peers to fill a mesh after warmup period
|
||||
npeers := len(p.h.Network().Peers())
|
||||
if npeers < pubsub.GossipSubD {
|
||||
panic(fmt.Errorf("not enough peers after warmup period. Need at least D=%d, have %d", pubsub.GossipSubD, npeers))
|
||||
}
|
||||
|
||||
// block until complete
|
||||
p.runenv.RecordMessage("Wait for %s run time", runtime)
|
||||
select {
|
||||
case <-time.After(runtime):
|
||||
case <-p.ctx.Done():
|
||||
return p.ctx.Err()
|
||||
}
|
||||
|
||||
// if we're publishing, wait until we've sent all our messages or the context expires
|
||||
if p.cfg.Publisher {
|
||||
donech := make(chan struct{}, 1)
|
||||
go func() {
|
||||
p.pubwg.Wait()
|
||||
donech <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-donech:
|
||||
case <-p.ctx.Done():
|
||||
return p.ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
p.runenv.RecordMessage("Run time complete, cooling down for %s", p.cfg.Cooldown)
|
||||
select {
|
||||
case <-time.After(p.cfg.Cooldown):
|
||||
case <-p.ctx.Done():
|
||||
return p.ctx.Err()
|
||||
}
|
||||
|
||||
p.runenv.RecordMessage("Cool down complete")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PubsubNode) joinTopic(t TopicConfig, runtime time.Duration) {
|
||||
p.lk.Lock()
|
||||
defer p.lk.Unlock()
|
||||
|
||||
publishInterval := time.Duration(float64(t.MessageRate.Interval) / t.MessageRate.Quantity)
|
||||
totalMessages := int64(runtime / publishInterval)
|
||||
|
||||
if p.cfg.Publisher {
|
||||
p.log("publishing to topic %s. message_rate: %.2f/%ds, publishInterval %dms, msg size %d bytes. total expected messages: %d",
|
||||
t.Id, t.MessageRate.Quantity, t.MessageRate.Interval/time.Second, publishInterval/time.Millisecond, t.MessageSize, totalMessages)
|
||||
} else {
|
||||
p.log("joining topic %s as a lurker", t.Id)
|
||||
}
|
||||
|
||||
if _, ok := p.topics[t.Id]; ok {
|
||||
// already joined, ignore
|
||||
return
|
||||
}
|
||||
topic, err := p.ps.Join(t.Id)
|
||||
if err != nil {
|
||||
p.log("error joining topic %s: %s", t.Id, err)
|
||||
return
|
||||
}
|
||||
sub, err := topic.Subscribe()
|
||||
if err != nil {
|
||||
p.log("error subscribing to topic %s: %s", t.Id, err)
|
||||
return
|
||||
}
|
||||
|
||||
ts := topicState{
|
||||
cfg: t,
|
||||
topic: topic,
|
||||
sub: sub,
|
||||
nMessages: totalMessages,
|
||||
done: make(chan struct{}, 1),
|
||||
}
|
||||
p.topics[t.Id] = &ts
|
||||
go p.consumeTopic(&ts)
|
||||
|
||||
if !p.cfg.Publisher {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
p.runenv.RecordMessage("Wait for %s warmup time before starting publisher", p.cfg.Warmup)
|
||||
select {
|
||||
case <-time.After(p.cfg.Warmup):
|
||||
case <-p.ctx.Done():
|
||||
p.runenv.RecordMessage("Context done before warm up time in publisher: %s", p.ctx.Err())
|
||||
return
|
||||
}
|
||||
|
||||
p.runenv.RecordMessage("Starting publisher with %s publish interval", publishInterval)
|
||||
ts.pubTicker = time.NewTicker(publishInterval)
|
||||
p.publishLoop(&ts)
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *PubsubNode) makeMessage(seq int64, size uint64) ([]byte, error) {
|
||||
type msg struct {
|
||||
sender string
|
||||
seq int64
|
||||
data []byte
|
||||
}
|
||||
data := make([]byte, size)
|
||||
rand.Read(data)
|
||||
m := msg{sender: p.h.ID().Pretty(), seq: seq, data: data}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func (p *PubsubNode) sendMsg(seq int64, ts *topicState) {
|
||||
msg, err := p.makeMessage(seq, uint64(ts.cfg.MessageSize))
|
||||
if err != nil {
|
||||
p.log("error making message for topic %s: %s", ts.cfg.Id, err)
|
||||
return
|
||||
}
|
||||
err = ts.topic.Publish(p.ctx, msg)
|
||||
if err != nil && err != context.Canceled {
|
||||
p.log("error publishing to %s: %s", ts.cfg.Id, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PubsubNode) publishLoop(ts *topicState) {
|
||||
var counter int64
|
||||
p.pubwg.Add(1)
|
||||
defer p.pubwg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ts.done:
|
||||
return
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
case <-ts.pubTicker.C:
|
||||
go p.sendMsg(counter, ts)
|
||||
counter++
|
||||
if counter > ts.nMessages {
|
||||
ts.pubTicker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PubsubNode) consumeTopic(ts *topicState) {
|
||||
for {
|
||||
_, err := ts.sub.Next(p.ctx)
|
||||
if err != nil && err != context.Canceled {
|
||||
p.log("error reading from %s: %s", ts.cfg.Id, err)
|
||||
return
|
||||
}
|
||||
//p.log("got message on topic %s from %s\n", ts.cfg.Id, msg.ReceivedFrom.Pretty())
|
||||
|
||||
select {
|
||||
case <-ts.done:
|
||||
return
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// +build !hardened_api
|
||||
|
||||
// This file is used when the hardened_api build tag is not present.
|
||||
// It targets the go-libp2p-pubsub API before GossipSub v1.1.0 was introduced.
|
||||
// When using this file, peer scores will not be present in the test output.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
)
|
||||
|
||||
func pubsubOptions(cfg PubsubNodeConfig) ([]pubsub.Option, error) {
|
||||
opts := []pubsub.Option{
|
||||
pubsub.WithEventTracer(cfg.Tracer),
|
||||
}
|
||||
|
||||
if cfg.ValidateQueueSize > 0 {
|
||||
opts = append(opts, pubsub.WithValidateQueueSize(cfg.ValidateQueueSize))
|
||||
}
|
||||
|
||||
if cfg.OutboundQueueSize > 0 {
|
||||
opts = append(opts, pubsub.WithPeerOutboundQueueSize(cfg.OutboundQueueSize))
|
||||
}
|
||||
|
||||
// Set the overlay parameters
|
||||
if cfg.OverlayParams.d >= 0 {
|
||||
pubsub.GossipSubD = cfg.OverlayParams.d
|
||||
}
|
||||
if cfg.OverlayParams.dlo >= 0 {
|
||||
pubsub.GossipSubDlo = cfg.OverlayParams.dlo
|
||||
}
|
||||
if cfg.OverlayParams.dhi >= 0 {
|
||||
pubsub.GossipSubDhi = cfg.OverlayParams.dhi
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// +build hardened_api
|
||||
|
||||
// The hardened_api build tag should be used when targeting a version of go-libp2p-pubsub after
|
||||
// GossipSub v1.1.0 was introduced.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
)
|
||||
|
||||
func pubsubOptions(cfg PubsubNodeConfig) ([]pubsub.Option, error) {
|
||||
opts := []pubsub.Option{
|
||||
pubsub.WithEventTracer(cfg.Tracer),
|
||||
pubsub.WithFloodPublish(cfg.FloodPublishing),
|
||||
scoreParamsOption(cfg.PeerScoreParams),
|
||||
}
|
||||
|
||||
if cfg.PeerScoreInspect.Inspect != nil && cfg.PeerScoreInspect.Period != 0 {
|
||||
opts = append(opts, pubsub.WithPeerScoreInspect(cfg.PeerScoreInspect.Inspect, cfg.PeerScoreInspect.Period))
|
||||
}
|
||||
|
||||
if cfg.ValidateQueueSize > 0 {
|
||||
opts = append(opts, pubsub.WithValidateQueueSize(cfg.ValidateQueueSize))
|
||||
}
|
||||
|
||||
if cfg.OutboundQueueSize > 0 {
|
||||
opts = append(opts, pubsub.WithPeerOutboundQueueSize(cfg.OutboundQueueSize))
|
||||
}
|
||||
|
||||
// Set the overlay parameters
|
||||
if cfg.OverlayParams.d >= 0 {
|
||||
pubsub.GossipSubD = cfg.OverlayParams.d
|
||||
}
|
||||
if cfg.OverlayParams.dlo >= 0 {
|
||||
pubsub.GossipSubDlo = cfg.OverlayParams.dlo
|
||||
}
|
||||
if cfg.OverlayParams.dhi >= 0 {
|
||||
pubsub.GossipSubDhi = cfg.OverlayParams.dhi
|
||||
}
|
||||
if cfg.OverlayParams.dscore >= 0 {
|
||||
pubsub.GossipSubDscore = cfg.OverlayParams.dscore
|
||||
}
|
||||
if cfg.OverlayParams.dlazy >= 0 {
|
||||
pubsub.GossipSubDlazy = cfg.OverlayParams.dlazy
|
||||
}
|
||||
if cfg.OverlayParams.gossipFactor > 0 {
|
||||
pubsub.GossipSubGossipFactor = cfg.OverlayParams.gossipFactor
|
||||
}
|
||||
|
||||
// set opportunistic graft params
|
||||
if cfg.OpportunisticGraftTicks > 0 {
|
||||
pubsub.GossipSubOpportunisticGraftTicks = uint64(cfg.OpportunisticGraftTicks)
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// TODO: implement app-specific scoring
|
||||
func applicationScore(id peer.ID) float64 {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
func scoreParamsOption(params ScoreParams) pubsub.Option {
|
||||
topicParams := make(map[string]*pubsub.TopicScoreParams, len(params.Topics))
|
||||
for name, t := range params.Topics {
|
||||
topicParams[name] = convertTopicParams(t)
|
||||
}
|
||||
psp := pubsub.PeerScoreParams{
|
||||
Topics: topicParams,
|
||||
|
||||
AppSpecificScore: applicationScore,
|
||||
AppSpecificWeight: 0,
|
||||
|
||||
IPColocationFactorWeight: params.IPColocationFactorWeight,
|
||||
IPColocationFactorThreshold: params.IPColocationFactorThreshold,
|
||||
DecayInterval: params.DecayInterval.Duration,
|
||||
DecayToZero: params.DecayToZero,
|
||||
RetainScore: params.RetainScore.Duration,
|
||||
}
|
||||
pst := pubsub.PeerScoreThresholds{
|
||||
GossipThreshold: params.Thresholds.GossipThreshold,
|
||||
PublishThreshold: params.Thresholds.PublishThreshold,
|
||||
GraylistThreshold: params.Thresholds.GraylistThreshold,
|
||||
AcceptPXThreshold: params.Thresholds.AcceptPXThreshold,
|
||||
OpportunisticGraftThreshold: params.Thresholds.OpportunisticGraftThreshold,
|
||||
}
|
||||
|
||||
fmt.Printf("peer score params: %v\nthresholds: %v\n", psp, pst)
|
||||
return pubsub.WithPeerScore(&psp, &pst)
|
||||
}
|
||||
|
||||
func convertTopicParams(p *TopicScoreParams) *pubsub.TopicScoreParams {
|
||||
return &pubsub.TopicScoreParams{
|
||||
TopicWeight: p.TopicWeight,
|
||||
TimeInMeshWeight: p.TimeInMeshWeight,
|
||||
TimeInMeshQuantum: p.TimeInMeshQuantum.Duration,
|
||||
TimeInMeshCap: p.TimeInMeshCap,
|
||||
FirstMessageDeliveriesWeight: p.FirstMessageDeliveriesWeight,
|
||||
FirstMessageDeliveriesDecay: p.FirstMessageDeliveriesDecay,
|
||||
FirstMessageDeliveriesCap: p.FirstMessageDeliveriesCap,
|
||||
MeshMessageDeliveriesWeight: p.MeshMessageDeliveriesWeight,
|
||||
MeshMessageDeliveriesDecay: p.MeshMessageDeliveriesDecay,
|
||||
MeshMessageDeliveriesCap: p.MeshMessageDeliveriesCap,
|
||||
MeshMessageDeliveriesThreshold: p.MeshMessageDeliveriesThreshold,
|
||||
MeshMessageDeliveriesWindow: p.MeshMessageDeliveriesWindow.Duration,
|
||||
MeshMessageDeliveriesActivation: p.MeshMessageDeliveriesActivation.Duration,
|
||||
MeshFailurePenaltyWeight: p.MeshFailurePenaltyWeight,
|
||||
MeshFailurePenaltyDecay: p.MeshFailurePenaltyDecay,
|
||||
InvalidMessageDeliveriesWeight: p.InvalidMessageDeliveriesWeight,
|
||||
InvalidMessageDeliveriesDecay: p.InvalidMessageDeliveriesDecay,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/testground/sdk-go/ptypes"
|
||||
"github.com/testground/sdk-go/runtime"
|
||||
)
|
||||
|
||||
type NodeType string
|
||||
|
||||
const (
|
||||
NodeTypeHonest NodeType = "honest"
|
||||
)
|
||||
|
||||
type TopicConfig struct {
|
||||
Id string `json:"id"`
|
||||
MessageRate ptypes.Rate `json:"message_rate"`
|
||||
MessageSize ptypes.Size `json:"message_size"`
|
||||
}
|
||||
|
||||
type HeartbeatParams struct {
|
||||
InitialDelay time.Duration
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
type NetworkParams struct {
|
||||
latency time.Duration
|
||||
latencyMax time.Duration
|
||||
jitterPct int
|
||||
bandwidthMB int
|
||||
}
|
||||
|
||||
// ScoreParams is mapped to pubsub.PeerScoreParams when targeting the hardened_api pubsub branch
|
||||
type ScoreParams struct {
|
||||
Topics map[string]*TopicScoreParams
|
||||
Thresholds PeerScoreThresholds
|
||||
|
||||
// TODO: figure out how to parameterize the app score function
|
||||
|
||||
IPColocationFactorWeight float64
|
||||
IPColocationFactorThreshold int
|
||||
|
||||
DecayInterval ptypes.Duration
|
||||
DecayToZero float64
|
||||
RetainScore ptypes.Duration
|
||||
}
|
||||
|
||||
type OverlayParams struct {
|
||||
d int
|
||||
dlo int
|
||||
dhi int
|
||||
dscore int
|
||||
dlazy int
|
||||
gossipFactor float64
|
||||
}
|
||||
|
||||
type PeerScoreThresholds struct {
|
||||
GossipThreshold float64
|
||||
PublishThreshold float64
|
||||
GraylistThreshold float64
|
||||
AcceptPXThreshold float64
|
||||
OpportunisticGraftThreshold float64
|
||||
}
|
||||
|
||||
// TopicScoreParams is mapped to pubsub.TopicScoreParams when targeting the hardened_api pubsub branch
|
||||
type TopicScoreParams struct {
|
||||
TopicWeight float64
|
||||
|
||||
TimeInMeshWeight float64
|
||||
TimeInMeshQuantum ptypes.Duration
|
||||
TimeInMeshCap float64
|
||||
|
||||
FirstMessageDeliveriesWeight float64
|
||||
FirstMessageDeliveriesDecay float64
|
||||
FirstMessageDeliveriesCap float64
|
||||
|
||||
MeshMessageDeliveriesWeight, MeshMessageDeliveriesDecay float64
|
||||
MeshMessageDeliveriesCap, MeshMessageDeliveriesThreshold float64
|
||||
MeshMessageDeliveriesWindow, MeshMessageDeliveriesActivation ptypes.Duration
|
||||
|
||||
MeshFailurePenaltyWeight, MeshFailurePenaltyDecay float64
|
||||
|
||||
InvalidMessageDeliveriesWeight, InvalidMessageDeliveriesDecay float64
|
||||
}
|
||||
|
||||
|
||||
type testParams struct {
|
||||
heartbeat HeartbeatParams
|
||||
setup time.Duration
|
||||
warmup time.Duration
|
||||
runtime time.Duration
|
||||
cooldown time.Duration
|
||||
|
||||
nodeType NodeType
|
||||
publisher bool
|
||||
floodPublishing bool
|
||||
fullTraces bool
|
||||
topics []TopicConfig
|
||||
degree int
|
||||
|
||||
containerNodesTotal int
|
||||
nodesPerContainer int
|
||||
|
||||
connectDelays []time.Duration
|
||||
connectDelayJitterPct int
|
||||
connsDef map[string]*ConnectionsDef
|
||||
connectToPublishersOnly bool
|
||||
|
||||
netParams NetworkParams
|
||||
overlayParams OverlayParams
|
||||
scoreParams ScoreParams
|
||||
scoreInspectPeriod time.Duration
|
||||
validateQueueSize int
|
||||
outboundQueueSize int
|
||||
|
||||
opportunisticGraftTicks int
|
||||
}
|
||||
|
||||
func durationParam(runenv *runtime.RunEnv, name string) time.Duration {
|
||||
if !runenv.IsParamSet(name) {
|
||||
runenv.RecordMessage("duration param %s not set, defaulting to zero", name)
|
||||
return 0
|
||||
}
|
||||
return parseDuration(runenv.StringParam(name))
|
||||
}
|
||||
|
||||
func parseDuration(val string) time.Duration {
|
||||
d, err := time.ParseDuration(val)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("param %s is not a valid duration: %s", val, err))
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func parseParams(runenv *runtime.RunEnv) testParams {
|
||||
np := NetworkParams{
|
||||
latency: durationParam(runenv, "t_latency"),
|
||||
latencyMax: durationParam(runenv, "t_latency_max"),
|
||||
jitterPct: runenv.IntParam("jitter_pct"),
|
||||
bandwidthMB: runenv.IntParam("bandwidth_mb"),
|
||||
}
|
||||
|
||||
op := OverlayParams{
|
||||
d: runenv.IntParam("overlay_d"),
|
||||
dlo: runenv.IntParam("overlay_dlo"),
|
||||
dhi: runenv.IntParam("overlay_dhi"),
|
||||
dscore: runenv.IntParam("overlay_dscore"),
|
||||
dlazy: runenv.IntParam("overlay_dlazy"),
|
||||
gossipFactor: runenv.FloatParam("gossip_factor"),
|
||||
}
|
||||
|
||||
p := testParams{
|
||||
heartbeat: HeartbeatParams{
|
||||
InitialDelay: durationParam(runenv, "t_heartbeat_initial_delay"),
|
||||
Interval: durationParam(runenv, "t_heartbeat"),
|
||||
},
|
||||
setup: durationParam(runenv, "t_setup"),
|
||||
warmup: durationParam(runenv, "t_warm"),
|
||||
runtime: durationParam(runenv, "t_run"),
|
||||
cooldown: durationParam(runenv, "t_cool"),
|
||||
publisher: runenv.BooleanParam("publisher"),
|
||||
floodPublishing: runenv.BooleanParam("flood_publishing"),
|
||||
fullTraces: runenv.BooleanParam("full_traces"),
|
||||
nodeType: NodeTypeHonest,
|
||||
connectToPublishersOnly: runenv.BooleanParam("connect_to_publishers_only"),
|
||||
degree: runenv.IntParam("degree"),
|
||||
containerNodesTotal: runenv.IntParam("n_container_nodes_total"),
|
||||
nodesPerContainer: runenv.IntParam("n_nodes_per_container"),
|
||||
scoreInspectPeriod: durationParam(runenv, "t_score_inspect_period"),
|
||||
netParams: np,
|
||||
overlayParams: op,
|
||||
validateQueueSize: runenv.IntParam("validate_queue_size"),
|
||||
outboundQueueSize: runenv.IntParam("outbound_queue_size"),
|
||||
opportunisticGraftTicks: runenv.IntParam("opportunistic_graft_ticks"),
|
||||
}
|
||||
|
||||
if runenv.IsParamSet("topics") {
|
||||
jsonstr := runenv.StringParam("topics")
|
||||
err := json.Unmarshal([]byte(jsonstr), &p.topics)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
runenv.RecordMessage("topics: %v", p.topics)
|
||||
}
|
||||
|
||||
if runenv.IsParamSet("score_params") {
|
||||
jsonstr := runenv.StringParam("score_params")
|
||||
err := json.Unmarshal([]byte(jsonstr), &p.scoreParams)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// add warmup time to the mesh delivery activation window for each topic
|
||||
for _, topic := range p.scoreParams.Topics {
|
||||
topic.MeshMessageDeliveriesActivation.Duration += p.warmup
|
||||
}
|
||||
}
|
||||
|
||||
if runenv.IsParamSet("topology") {
|
||||
jsonstr := runenv.StringParam("topology")
|
||||
err := json.Unmarshal([]byte(jsonstr), &p.connsDef)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if runenv.IsParamSet("connect_delays") {
|
||||
// eg: "5@10s,15@1m,5@2m"
|
||||
connDelays := runenv.StringParam("connect_delays")
|
||||
if connDelays != "" && connDelays != "\"\"" {
|
||||
cds := strings.Split(connDelays, ",")
|
||||
for _, cd := range cds {
|
||||
parts := strings.Split(cd, "@")
|
||||
if len(parts) != 2 {
|
||||
panic(fmt.Sprintf("Badly formatted connect_delays param %s", connDelays))
|
||||
}
|
||||
count, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Badly formatted connect_delays param %s", connDelays))
|
||||
}
|
||||
|
||||
dur := parseDuration(parts[1])
|
||||
for i := 0; i < count; i++ {
|
||||
p.connectDelays = append(p.connectDelays, dur)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.connectDelayJitterPct = 5
|
||||
if runenv.IsParamSet("connect_delay_jitter_pct") {
|
||||
p.connectDelayJitterPct = runenv.IntParam("connect_delay_jitter_pct")
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func parseNodeType(nt string) NodeType {
|
||||
switch nt {
|
||||
// currently only honest nodes are supported
|
||||
default:
|
||||
return NodeTypeHonest
|
||||
}
|
||||
}
|
|
@ -0,0 +1,446 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
rt "runtime"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p"
|
||||
"github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr-net"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/testground/sdk-go/network"
|
||||
"github.com/testground/sdk-go/runtime"
|
||||
"github.com/testground/sdk-go/sync"
|
||||
)
|
||||
|
||||
// Listen on the address in the testground data network
|
||||
func listenAddrs(netclient *network.Client) []multiaddr.Multiaddr {
|
||||
ip, err := netclient.GetDataNetworkIP()
|
||||
if err == network.ErrNoTrafficShaping {
|
||||
ip = net.ParseIP("0.0.0.0")
|
||||
} else if err != nil {
|
||||
panic(fmt.Errorf("error getting data network addr: %s", err))
|
||||
}
|
||||
|
||||
dataAddr, err := manet.FromIP(ip)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not convert IP to multiaddr; ip=%s, err=%s", ip, err))
|
||||
}
|
||||
|
||||
// add /tcp/0 to auto select TCP listen port
|
||||
listenAddr := dataAddr.Encapsulate(multiaddr.StringCast("/tcp/0"))
|
||||
return []multiaddr.Multiaddr{listenAddr}
|
||||
}
|
||||
|
||||
type testInstance struct {
|
||||
*runtime.RunEnv
|
||||
params testParams
|
||||
|
||||
h host.Host
|
||||
seq int64
|
||||
nodeTypeSeq int64
|
||||
nodeIdx int
|
||||
latency time.Duration
|
||||
connsDef *ConnectionsDef
|
||||
client *sync.Client
|
||||
discovery *SyncDiscovery
|
||||
peerSubscriber *PeerSubscriber
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Name string
|
||||
Body string
|
||||
Time int64
|
||||
}
|
||||
|
||||
// Create a new libp2p host
|
||||
func createHost(ctx context.Context) (host.Host, error) {
|
||||
priv, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 256)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Don't listen yet, we need to set up networking first
|
||||
return libp2p.New(ctx, libp2p.Identity(priv), libp2p.NoListenAddrs)
|
||||
}
|
||||
|
||||
func RunSimulation(runenv *runtime.RunEnv) error {
|
||||
params := parseParams(runenv)
|
||||
|
||||
totalTime := params.setup + params.runtime + params.warmup + params.cooldown
|
||||
ctx, cancel := context.WithTimeout(context.Background(), totalTime)
|
||||
defer cancel()
|
||||
client := sync.MustBoundClient(ctx, runenv)
|
||||
defer client.Close()
|
||||
|
||||
// Create the hosts, but don't listen yet (we need to set up the data
|
||||
// network before listening)
|
||||
hosts := make([]host.Host, params.nodesPerContainer)
|
||||
for i := 0; i < params.nodesPerContainer; i++ {
|
||||
h, err := createHost(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[i] = h
|
||||
}
|
||||
|
||||
// Get sequence number within a node type (eg honest-1, honest-2, etc)
|
||||
nodeTypeSeq, err := getNodeTypeSeqNum(ctx, client, hosts[0], params.nodeType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get node type sequence number: %w", err)
|
||||
}
|
||||
|
||||
// Make sure each container has a distinct random seed
|
||||
rand.Seed(nodeTypeSeq * time.Now().UnixNano())
|
||||
|
||||
runenv.RecordMessage("%s container %d num cpus: %d", params.nodeType, nodeTypeSeq, rt.NumCPU())
|
||||
|
||||
// Get the sequence number of each node in the container
|
||||
peers := sync.NewTopic("nodes", &peer.AddrInfo{})
|
||||
seqs := make([]int64, params.nodesPerContainer)
|
||||
for nodeIdx := 0; nodeIdx < params.nodesPerContainer; nodeIdx++ {
|
||||
seq, err := client.Publish(ctx, peers, host.InfoFromHost(hosts[nodeIdx]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write peer subtree in sync service: %w", err)
|
||||
}
|
||||
seqs[nodeIdx] = seq
|
||||
}
|
||||
|
||||
// If a topology definition was provided, read the latency from it
|
||||
if len(params.connsDef) > 0 {
|
||||
// Note: The latency is the same for all nodes in the same container
|
||||
nodeIdx := 0
|
||||
connsDef, err := loadConnections(params.connsDef, params.nodeType, nodeTypeSeq, nodeIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params.netParams.latency = connsDef.Latency
|
||||
params.netParams.latencyMax = time.Duration(0)
|
||||
}
|
||||
|
||||
netclient := network.NewClient(client, runenv)
|
||||
|
||||
// Set up traffic shaping. Note: this is the same for all nodes in the same container.
|
||||
if err := setupNetwork(ctx, runenv, params.netParams, netclient); err != nil {
|
||||
return fmt.Errorf("Failed to set up network: %w", err)
|
||||
}
|
||||
|
||||
// Set up a subscription for node information from all peers in all containers.
|
||||
// Note that there is only on PeerSubscriber per container (but there may be
|
||||
// several nodes per container).
|
||||
peerSubscriber := NewPeerSubscriber(ctx, runenv, client, runenv.TestInstanceCount, params.containerNodesTotal)
|
||||
|
||||
// Create each node in the container
|
||||
errgrp, ctx := errgroup.WithContext(ctx)
|
||||
for nodeIdx := 0; nodeIdx < params.nodesPerContainer; nodeIdx++ {
|
||||
nodeIdx := nodeIdx
|
||||
|
||||
errgrp.Go(func() (err error) {
|
||||
t := testInstance{
|
||||
RunEnv: runenv,
|
||||
h: hosts[nodeIdx],
|
||||
seq: seqs[nodeIdx],
|
||||
nodeTypeSeq: nodeTypeSeq,
|
||||
nodeIdx: nodeIdx,
|
||||
params: params,
|
||||
client: client,
|
||||
peerSubscriber: peerSubscriber,
|
||||
}
|
||||
|
||||
// Load the connection definition for the node
|
||||
var connsDef *ConnectionsDef
|
||||
if len(params.connsDef) > 0 {
|
||||
connsDef, err = loadConnections(params.connsDef, params.nodeType, nodeTypeSeq, nodeIdx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.connsDef = connsDef
|
||||
}
|
||||
|
||||
// Listen for incoming connections
|
||||
laddr := listenAddrs(netclient)
|
||||
runenv.RecordMessage("listening on %s", laddr)
|
||||
if err = t.h.Network().Listen(laddr...); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
id := host.InfoFromHost(t.h).ID.Pretty()
|
||||
runenv.RecordMessage("Host peer ID: %s, seq %d, node type: %s, node type seq: %d, node index: %d / %d, addrs: %v",
|
||||
id, t.seq, params.nodeType, nodeTypeSeq, nodeIdx, params.nodesPerContainer, t.h.Addrs())
|
||||
|
||||
switch params.nodeType {
|
||||
case NodeTypeHonest:
|
||||
err = t.startPubsubNode(ctx)
|
||||
default:
|
||||
runenv.RecordMessage("unsupported node type %d", params.nodeType)
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
return errgrp.Wait()
|
||||
}
|
||||
|
||||
func getNodeTypeSeqNum(ctx context.Context, client *sync.Client, h host.Host, nodeType NodeType) (int64, error) {
|
||||
topic := sync.NewTopic("node-type-"+string(nodeType), &peer.AddrInfo{})
|
||||
return client.Publish(ctx, topic, host.InfoFromHost(h))
|
||||
}
|
||||
|
||||
func (t *testInstance) startPubsubNode(ctx context.Context) error {
|
||||
tracerOut := fmt.Sprintf("%s%ctracer-output-honest-%d", t.TestOutputsPath, os.PathSeparator, t.seq)
|
||||
t.RecordMessage("writing honest node tracer output to %s", tracerOut)
|
||||
|
||||
// if we're a publisher, our message publish rate should be a fraction of
|
||||
// the total message rate for each topic. For now, we distribute the
|
||||
// publish rates uniformly across the number of instances in our
|
||||
// testground composition
|
||||
topics := make([]TopicConfig, len(t.params.topics))
|
||||
if t.params.publisher {
|
||||
// FIXME: this assumes all publishers are in the same group, might not always hold up.
|
||||
nPublishers := t.TestGroupInstanceCount
|
||||
for i, topic := range t.params.topics {
|
||||
topics[i] = topic
|
||||
topics[i].MessageRate.Quantity /= float64(nPublishers)
|
||||
}
|
||||
} else {
|
||||
topics = t.params.topics
|
||||
}
|
||||
|
||||
tracer, err := NewTestTracer(tracerOut, t.h.ID(), t.params.fullTraces)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making test tracer: %s", err)
|
||||
}
|
||||
|
||||
scoreInspectParams := InspectParams{}
|
||||
if t.params.scoreInspectPeriod != 0 {
|
||||
scoreInspectParams.Period = t.params.scoreInspectPeriod
|
||||
|
||||
outpath := fmt.Sprintf("%s%cpeer-scores-honest-%d.json", t.TestOutputsPath, os.PathSeparator, t.seq)
|
||||
file, err := os.OpenFile(outpath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening peer score output file at %s: %s", outpath, err)
|
||||
}
|
||||
defer file.Close()
|
||||
t.RecordMessage("recording peer scores to %s", outpath)
|
||||
enc := json.NewEncoder(file)
|
||||
type entry struct {
|
||||
Timestamp int64
|
||||
PeerID string
|
||||
Scores map[string]float64
|
||||
}
|
||||
scoreInspectParams.Inspect = func(scores map[peer.ID]float64) {
|
||||
ts := time.Now().UnixNano()
|
||||
pretty := make(map[string]float64, len(scores))
|
||||
for p, s := range scores {
|
||||
pretty[p.Pretty()] = s
|
||||
}
|
||||
e := entry{
|
||||
Timestamp: ts,
|
||||
PeerID: t.h.ID().Pretty(),
|
||||
Scores: pretty,
|
||||
}
|
||||
err := enc.Encode(e)
|
||||
if err != nil {
|
||||
t.RecordMessage("error encoding peer scores: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg := PubsubNodeConfig{
|
||||
Publisher: t.params.publisher,
|
||||
FloodPublishing: t.params.floodPublishing,
|
||||
PeerScoreParams: t.params.scoreParams,
|
||||
OverlayParams: t.params.overlayParams,
|
||||
PeerScoreInspect: scoreInspectParams,
|
||||
Topics: topics,
|
||||
Tracer: tracer,
|
||||
Seq: t.seq,
|
||||
Warmup: t.params.warmup,
|
||||
Cooldown: t.params.cooldown,
|
||||
Heartbeat: t.params.heartbeat,
|
||||
ValidateQueueSize: t.params.validateQueueSize,
|
||||
OutboundQueueSize: t.params.outboundQueueSize,
|
||||
OpportunisticGraftTicks: t.params.opportunisticGraftTicks,
|
||||
}
|
||||
|
||||
n, err := NewPubsubNode(t.RunEnv, ctx, t.h, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
discovery, err := t.setupDiscovery(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = n.Run(t.params.runtime, func(ctx context.Context) error {
|
||||
// wait for all other nodes to be ready
|
||||
if err := t.waitForReadyState(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// connect topology async
|
||||
go t.connectTopology(ctx)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err2 := tracer.Stop(); err2 != nil {
|
||||
t.RecordMessage("error stopping test tracer: %s", err2)
|
||||
}
|
||||
|
||||
return t.outputConns(discovery)
|
||||
}
|
||||
|
||||
func (t *testInstance) setupDiscovery(ctx context.Context) (*SyncDiscovery, error) {
|
||||
t.RecordMessage("Setup discovery")
|
||||
|
||||
// By default connect to a randomly-chosen subset of all honest nodes
|
||||
var topology Topology
|
||||
topology = RandomHonestTopology{
|
||||
Count: t.params.degree,
|
||||
PublishersOnly: t.params.connectToPublishersOnly,
|
||||
}
|
||||
|
||||
// If a topology file was supplied, use the topology defined there
|
||||
if t.connsDef != nil {
|
||||
topology = FixedTopology{t.connsDef}
|
||||
}
|
||||
|
||||
// Register this node and get node information for all peers
|
||||
discovery, err := NewSyncDiscovery(t.h, t.RunEnv, t.peerSubscriber, topology,
|
||||
t.params.nodeType, t.nodeTypeSeq, t.nodeIdx, t.params.publisher)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating discovery service: %s", err)
|
||||
}
|
||||
t.discovery = discovery
|
||||
|
||||
err = discovery.registerAndWait(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error waiting for discovery service: %s", err)
|
||||
}
|
||||
|
||||
return discovery, nil
|
||||
}
|
||||
|
||||
// Called when nodes are ready to start the run, and are waiting for all other nodes to be ready
|
||||
func (t *testInstance) waitForReadyStateThenConnect(ctx context.Context) error {
|
||||
// wait for all other nodes to be ready
|
||||
if err := t.waitForReadyState(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// connect topology
|
||||
return t.connectTopology(ctx)
|
||||
}
|
||||
|
||||
// Called when nodes are ready to start the run, and are waiting for all other nodes to be ready
|
||||
func (t *testInstance) waitForReadyState(ctx context.Context) error {
|
||||
// Set a state barrier.
|
||||
state := sync.State("ready")
|
||||
doneCh := t.client.MustBarrier(ctx, state, t.params.containerNodesTotal).C
|
||||
|
||||
// Signal we've entered the state.
|
||||
t.RecordMessage("Signalling ready state")
|
||||
_, err := t.client.SignalEntry(ctx, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait until all others have signalled.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-doneCh:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.RecordMessage("All instances in ready state, continuing")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testInstance) connectTopology(ctx context.Context) error {
|
||||
// Default to a connect delay in the range of 0s - 1s
|
||||
delay := time.Duration(float64(time.Second) * rand.Float64())
|
||||
|
||||
// If an explicit delay was specified, calculate the delay
|
||||
nodeTypeIdx := int(t.nodeTypeSeq - 1)
|
||||
if nodeTypeIdx < len(t.params.connectDelays) {
|
||||
expdelay := t.params.connectDelays[nodeTypeIdx]
|
||||
|
||||
// Add +/- jitter percent
|
||||
delay = expdelay + time.Duration(t.params.connectDelayJitterPct)*expdelay/100
|
||||
delay += time.Duration(rand.Float64() * float64(time.Duration(t.params.connectDelayJitterPct*2)*expdelay/100))
|
||||
}
|
||||
|
||||
// Connect to other peers in the topology
|
||||
err := t.discovery.ConnectTopology(ctx, delay)
|
||||
if err != nil {
|
||||
t.RecordMessage("Error connecting to topology peer: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for all nodes to signal that they have completed the run
|
||||
// (or there's a timeout)
|
||||
func (t *testInstance) waitForCompleteState(ctx context.Context) error {
|
||||
// Set a state barrier.
|
||||
state := sync.State("complete")
|
||||
|
||||
// Signal we've entered the state, and wait until all others have signalled.
|
||||
t.RecordMessage("Signalling complete state")
|
||||
_, err := t.client.SignalAndWait(ctx, state, t.params.containerNodesTotal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.RecordMessage("All instances in complete state, done")
|
||||
return nil
|
||||
}
|
||||
|
||||
type ConnectionsDef struct {
|
||||
Latency time.Duration
|
||||
Connections []string
|
||||
}
|
||||
|
||||
func loadConnections(connsDef map[string]*ConnectionsDef, nodeType NodeType, nodeTypeSeq int64, nodeIdx int) (*ConnectionsDef, error) {
|
||||
nodeKey := fmt.Sprintf("%s-%d-%d", nodeType, nodeTypeSeq, nodeIdx)
|
||||
def, ok := connsDef[nodeKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Topology file '%s' has no entry for '%s'")
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func (t *testInstance) outputConns(discovery *SyncDiscovery) error {
|
||||
connsOut := fmt.Sprintf("%s%cconnections-%s-%d-%d.json", t.TestOutputsPath, os.PathSeparator, t.params.nodeType, t.nodeTypeSeq, t.nodeIdx)
|
||||
|
||||
var conns []string
|
||||
for _, p := range discovery.Connected() {
|
||||
conns = append(conns, fmt.Sprintf("%s-%d-%d", p.NType, p.NodeTypeSeq, p.NodeIdx))
|
||||
}
|
||||
|
||||
jsonstr, err := json.MarshalIndent(ConnectionsDef{
|
||||
Latency: t.params.netParams.latency,
|
||||
Connections: conns,
|
||||
}, "", " ")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(connsOut, jsonstr, os.ModePerm)
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
pb "github.com/libp2p/go-libp2p-pubsub/pb"
|
||||
)
|
||||
|
||||
type RPCMetrics struct {
|
||||
RPCs uint64
|
||||
Messages uint64
|
||||
Grafts uint64
|
||||
Prunes uint64
|
||||
IWants uint64
|
||||
IHaves uint64
|
||||
}
|
||||
|
||||
type TestMetrics struct {
|
||||
LocalPeer string
|
||||
Published uint64
|
||||
Rejected uint64
|
||||
Delivered uint64
|
||||
Duplicates uint64
|
||||
DroppedRPC uint64
|
||||
PeersAdded uint64
|
||||
PeersRemoved uint64
|
||||
TopicsJoined uint64
|
||||
TopicsLeft uint64
|
||||
|
||||
SentRPC RPCMetrics
|
||||
ReceivedRPC RPCMetrics
|
||||
}
|
||||
|
||||
type TestTracer struct {
|
||||
full pubsub.EventTracer
|
||||
filtered pubsub.EventTracer
|
||||
aggregateOutputPath string
|
||||
|
||||
eventCh chan *pb.TraceEvent
|
||||
doneCh chan struct{}
|
||||
|
||||
metrics TestMetrics
|
||||
}
|
||||
|
||||
func NewTestTracer(outputPathPrefix string, localPeerID peer.ID, full bool) (*TestTracer, error) {
|
||||
var fullTracer pubsub.EventTracer
|
||||
var err error
|
||||
if full {
|
||||
fullTracer, err = pubsub.NewPBTracer(outputPathPrefix + "-full.bin")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making protobuf event tracer: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
filteredTracer, err := newFilteringTracer(outputPathPrefix+"-filtered.bin",
|
||||
pb.TraceEvent_PUBLISH_MESSAGE, pb.TraceEvent_DELIVER_MESSAGE,
|
||||
pb.TraceEvent_GRAFT, pb.TraceEvent_PRUNE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making filtered event tracer: %s", err)
|
||||
}
|
||||
|
||||
t := &TestTracer{
|
||||
full: fullTracer,
|
||||
filtered: filteredTracer,
|
||||
aggregateOutputPath: outputPathPrefix + "-aggregate.json",
|
||||
eventCh: make(chan *pb.TraceEvent, 1024),
|
||||
doneCh: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
t.metrics.LocalPeer = localPeerID.Pretty()
|
||||
|
||||
go t.eventLoop()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *TestTracer) Stop() error {
|
||||
t.doneCh <- struct{}{}
|
||||
|
||||
jsonstr, err := json.MarshalIndent(t.metrics, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(t.aggregateOutputPath, jsonstr, os.ModePerm)
|
||||
}
|
||||
|
||||
func (t *TestTracer) eventLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-t.doneCh:
|
||||
return
|
||||
case evt := <-t.eventCh:
|
||||
switch evt.GetType() {
|
||||
case pb.TraceEvent_PUBLISH_MESSAGE:
|
||||
t.publishMessage(evt)
|
||||
case pb.TraceEvent_REJECT_MESSAGE:
|
||||
t.rejectMessage(evt)
|
||||
case pb.TraceEvent_DUPLICATE_MESSAGE:
|
||||
t.duplicateMessage(evt)
|
||||
case pb.TraceEvent_DELIVER_MESSAGE:
|
||||
t.deliverMessage(evt)
|
||||
case pb.TraceEvent_ADD_PEER:
|
||||
t.addPeer(evt)
|
||||
case pb.TraceEvent_REMOVE_PEER:
|
||||
t.removePeer(evt)
|
||||
case pb.TraceEvent_RECV_RPC:
|
||||
t.recvRPC(evt)
|
||||
case pb.TraceEvent_SEND_RPC:
|
||||
t.sendRPC(evt)
|
||||
case pb.TraceEvent_DROP_RPC:
|
||||
t.dropRPC(evt)
|
||||
case pb.TraceEvent_JOIN:
|
||||
t.join(evt)
|
||||
case pb.TraceEvent_LEAVE:
|
||||
t.leave(evt)
|
||||
case pb.TraceEvent_GRAFT:
|
||||
t.graft(evt)
|
||||
case pb.TraceEvent_PRUNE:
|
||||
t.prune(evt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TestTracer) Trace(evt *pb.TraceEvent) {
|
||||
t.filtered.Trace(evt)
|
||||
if t.full != nil {
|
||||
t.full.Trace(evt)
|
||||
}
|
||||
t.eventCh <- evt
|
||||
}
|
||||
|
||||
func (t *TestTracer) publishMessage(evt *pb.TraceEvent) {
|
||||
t.metrics.Published++
|
||||
}
|
||||
|
||||
func (t *TestTracer) rejectMessage(evt *pb.TraceEvent) {
|
||||
t.metrics.Rejected++
|
||||
}
|
||||
|
||||
func (t *TestTracer) deliverMessage(evt *pb.TraceEvent) {
|
||||
t.metrics.Delivered++
|
||||
}
|
||||
|
||||
func (t *TestTracer) duplicateMessage(evt *pb.TraceEvent) {
|
||||
t.metrics.Duplicates++
|
||||
}
|
||||
|
||||
func (t *TestTracer) sendRPC(evt *pb.TraceEvent) {
|
||||
meta := evt.GetSendRPC().GetMeta()
|
||||
updateRPCStats(&t.metrics.SentRPC, meta)
|
||||
}
|
||||
|
||||
func (t *TestTracer) recvRPC(evt *pb.TraceEvent) {
|
||||
meta := evt.GetRecvRPC().GetMeta()
|
||||
updateRPCStats(&t.metrics.ReceivedRPC, meta)
|
||||
}
|
||||
|
||||
func updateRPCStats(stats *RPCMetrics, meta *pb.TraceEvent_RPCMeta) {
|
||||
ctrl := meta.GetControl()
|
||||
stats.RPCs += 1
|
||||
stats.Messages += uint64(len(meta.GetMessages()))
|
||||
stats.IHaves += uint64(len(ctrl.GetIhave()))
|
||||
stats.IWants += uint64(len(ctrl.GetIwant()))
|
||||
stats.Grafts += uint64(len(ctrl.GetGraft()))
|
||||
stats.Prunes += uint64(len(ctrl.GetPrune()))
|
||||
}
|
||||
|
||||
func (t *TestTracer) dropRPC(evt *pb.TraceEvent) {
|
||||
t.metrics.DroppedRPC++
|
||||
}
|
||||
|
||||
func (t *TestTracer) addPeer(evt *pb.TraceEvent) {
|
||||
t.metrics.PeersAdded++
|
||||
}
|
||||
|
||||
func (t *TestTracer) removePeer(evt *pb.TraceEvent) {
|
||||
t.metrics.PeersRemoved++
|
||||
}
|
||||
|
||||
func (t *TestTracer) join(evt *pb.TraceEvent) {
|
||||
t.metrics.TopicsJoined++
|
||||
}
|
||||
|
||||
func (t *TestTracer) leave(evt *pb.TraceEvent) {
|
||||
t.metrics.TopicsLeft++
|
||||
}
|
||||
|
||||
func (t *TestTracer) graft(evt *pb.TraceEvent) {
|
||||
// already accounted for in sendRPC
|
||||
}
|
||||
|
||||
func (t *TestTracer) prune(evt *pb.TraceEvent) {
|
||||
// already accounted for in sendRPC
|
||||
}
|
||||
|
||||
var _ pubsub.EventTracer = (*TestTracer)(nil)
|
||||
|
||||
type filteringTracer struct {
|
||||
pubsub.EventTracer
|
||||
whitelist []pb.TraceEvent_Type
|
||||
}
|
||||
|
||||
func newFilteringTracer(outputPath string, typeWhitelist ...pb.TraceEvent_Type) (*filteringTracer, error) {
|
||||
tracer, err := pubsub.NewPBTracer(outputPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making protobuf event tracer: %s", err)
|
||||
}
|
||||
return &filteringTracer{EventTracer: tracer, whitelist: typeWhitelist}, nil
|
||||
}
|
||||
|
||||
func (t *filteringTracer) Trace(evt *pb.TraceEvent) {
|
||||
for _, typ := range t.whitelist {
|
||||
if evt.GetType() == typ {
|
||||
t.EventTracer.Trace(evt)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue