Skip to content

Commit

Permalink
bench-cli: Add a bench CLI tool for kademlia
Browse files Browse the repository at this point in the history
Signed-off-by: Alexandru Vasile <[email protected]>
  • Loading branch information
lexnv committed Aug 20, 2024
1 parent a9fec84 commit c7a66cc
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 1 deletion.
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[workspace]
members = [
"cli",
"bench-cli",

"subp2p-explorer",
"subp2p-explorer-core",
]
Expand Down Expand Up @@ -59,6 +61,7 @@ litep2p = "0.6.2"
multiaddr = { version = "0.18.1" }
multihash = { version = "0.19.1", default-features = false }
libp2p-identity = { version = "0.2.9" }
serde = "1.0"

#workspace crates:
subp2p-explorer = { version = "0.1.0", path = "subp2p-explorer", default-features = false }
Expand Down
46 changes: 46 additions & 0 deletions bench-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[package]
name = "subp2p-explorer-bench-cli"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
repository.workspace = true
homepage.workspace = true
license.workspace = true
description = "Command line utilities for working with subp2p-explorer"

[[bin]]
name = "bench-cli"
path = "src/main.rs"

[dependencies]
subp2p-explorer-core = { workspace = true }
tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread"] }
async-trait = { workspace = true }
env_logger = { workspace = true }
tracing-subscriber = { workspace = true }
tracing = { workspace = true }
futures = { workspace = true }
libp2p = { workspace = true, features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "tcp", "tokio", "yamux", "websocket", "request-response"] }
rand = { workspace = true }
fnv = { workspace = true }
log = { workspace = true }
either = { workspace = true }
void = { workspace = true }
pin-project = { workspace = true }
asynchronous-codec = { workspace = true }
unsigned-varint = { workspace = true, features = ["futures", "asynchronous_codec"] }
thiserror = { workspace = true }
bytes = { workspace = true }
codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] }
primitive-types = { workspace = true, default-features = false, features = ["codec", "scale-info", "serde"] }
serde_json = { workspace = true }
hex = { workspace = true }
clap = { workspace = true }
ip_network = { workspace = true }
maxminddb = { workspace = true }
trust-dns-resolver = { workspace = true }
multihash-codetable = { workspace = true, features = ["digest", "serde", "sha2"] }
jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport-native-tls"] }
ss58-registry = { version = "1.34.0", default-features = false }
serde = { workspace = true, features = ["derive"] }
31 changes: 31 additions & 0 deletions bench-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

# Benchmarking CLI

The benchmarking CLI is designed to benchmark the performance of different network backends.

This data helps us drive improvements in various network components, such as the discovery process.

At the moment the CLI supports the following backends:

- [x] litep2p
- [x] libp2p

## Usage

### Discovery Kademlia Benchmarking (live chains)

Although the tool is targeting a live chain, it can also be used to benchmark the discovery process on a local chain by providing the genesis block hash and at least one boot node.

Local chains can be started with other tools, like zombie-net-cli, and is out of the scope of this tool.

- Benchmark the discovery process on kusama using the litep2p backend with 100 peers:

```bash
cargo run -- discovery --genesis b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe --bootnodes /dns/kusama-bootnode-0.polkadot.io/tcp/30333/p2p/12D3KooWSueCPH3puP2PcvqPJdNaDNF3jMZjtJtDiSy35pWrbt5h --backend-type litep2p --num-peers 100
```

- Benchmark the discovery process on kusama using the libp2p backend with 100 peers:

```bash
cargo run -- discovery --genesis b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe --bootnodes /dns/kusama-bootnode-0.polkadot.io/tcp/30333/p2p/12D3KooWSueCPH3puP2PcvqPJdNaDNF3jMZjtJtDiSy35pWrbt5h --backend-type litep2p --num-peers 100
```
151 changes: 151 additions & 0 deletions bench-cli/src/discovery_backends.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use std::collections::{HashMap, HashSet};
use std::error::Error;

use futures::StreamExt;
use subp2p_explorer_core::{
types::{multiaddr::Multiaddr, peer_id::PeerId},
NetworkEvent,
};

use crate::DiscoverBackendsNetworkOpts;

pub async fn discovery_backends(opts: DiscoverBackendsNetworkOpts) -> Result<(), Box<dyn Error>> {
let mut peers_data = HashMap::new();
let mut queries = HashSet::new();

// Parse the provided bootnodes as `PeerId` and `MultiAddress`.
let bootnodes: Vec<_> = opts
.bootnodes
.iter()
.map(|bootnode| {
let parts: Vec<_> = bootnode.split('/').collect();
let peer = parts.last().expect("Valid bootnode has peer; qed");
let multiaddress: Multiaddr = bootnode.parse().expect("Valid multiaddress; qed");
let peer_id: PeerId = peer.parse().expect("Valid peer ID; qed");

log::info!("Bootnode peer={:?}", peer_id);
(peer_id, multiaddress)
})
.collect();

let now = std::time::Instant::now();

match opts.backend_type {
crate::BackendType::Litep2p => {
let mut backend = subp2p_explorer_core::litep2p::Litep2pBackend::new(opts.genesis);

for (peer_id, address) in bootnodes {
backend
.add_known_peer(peer_id, std::iter::once(address))
.await;
}

let num_peers = opts.num_peers;

log::info!("Discovering peers...");
for _ in 0..50 {
let query_id = backend.find_node(PeerId::random()).await;
queries.insert(query_id);
}

while let Some(event) = backend.next().await {
match event {
NetworkEvent::PeerIdentified {
peer,
listen_addresses,
..
} => {
let entry = peers_data.entry(peer).or_insert_with(|| HashSet::new());
listen_addresses.into_iter().for_each(|address| {
entry.insert(address);
});
}
NetworkEvent::FindNode {
peers, query_id, ..
} => {
log::info!(" Kademlia query finished {:?}", query_id);
queries.remove(&query_id);

peers.into_iter().for_each(|(peer, addresses)| {
let entry = peers_data.entry(peer).or_insert_with(|| HashSet::new());
addresses.into_iter().for_each(|address| {
entry.insert(address);
});
});
}
}

log::info!("Discovered {}/{} peers", peers_data.len(), num_peers);

if peers_data.len() >= num_peers {
break;
}

while queries.len() < 50 {
let query_id = backend.find_node(PeerId::random()).await;
queries.insert(query_id);
}
}
}
crate::BackendType::Libp2p => {
let mut backend = subp2p_explorer_core::libp2p::Libp2pBackend::new(opts.genesis);

for (peer_id, address) in bootnodes {
backend
.add_known_peer(peer_id, std::iter::once(address))
.await;
}

let num_peers = opts.num_peers;

log::info!("Discovering peers...");
for _ in 0..50 {
let query_id = backend.find_node(PeerId::random()).await;
queries.insert(query_id);
}

while let Some(event) = backend.next().await {
match event {
NetworkEvent::PeerIdentified {
peer,
listen_addresses,
..
} => {
let entry = peers_data.entry(peer).or_insert_with(|| HashSet::new());
listen_addresses.into_iter().for_each(|address| {
entry.insert(address);
});
}
NetworkEvent::FindNode {
peers, query_id, ..
} => {
log::info!(" Kademlia query finished {:?}", query_id);
queries.remove(&query_id);

peers.into_iter().for_each(|(peer, addresses)| {
let entry = peers_data.entry(peer).or_insert_with(|| HashSet::new());
addresses.into_iter().for_each(|address| {
entry.insert(address);
});
});
}
}

log::info!("Discovered {}/{} peers", peers_data.len(), num_peers);

if peers_data.len() >= num_peers {
break;
}

while queries.len() < 50 {
let query_id = backend.find_node(PeerId::random()).await;
queries.insert(query_id);
}
}
}
};

log::info!("Discovery took {:?}", now.elapsed());

Ok(())
}
61 changes: 61 additions & 0 deletions bench-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2023 Alexandru Vasile
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use clap::Parser as ClapParser;
use serde::Serialize;
use std::error::Error;

mod discovery_backends;

/// Command for interacting with the CLI.
#[derive(Debug, ClapParser)]
enum Command {
Discovery(DiscoverBackendsNetworkOpts),
}

#[derive(Debug, Default, clap::ValueEnum, Clone, Copy, Serialize)]
#[serde(rename_all = "lowercase")]
enum BackendType {
/// Use the `litep2p` backend.
#[default]
Litep2p,

/// Use the `libp2p` backend.
Libp2p,
}

/// Discover the p2p network.
#[derive(Debug, ClapParser)]
pub struct DiscoverBackendsNetworkOpts {
/// Hex-encoded genesis hash of the chain.
///
/// For example, "781e4046b4e8b5e83d33dde04b32e7cb5d43344b1f19b574f6d31cbbd99fe738"
#[clap(long, short)]
genesis: String,

/// Bootnodes of the chain, must contain a multiaddress together with the peer ID.
/// For example, "/ip4/127.0.0.1/tcp/30333/ws/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp".
#[clap(long, use_value_delimiter = true, value_parser)]
bootnodes: Vec<String>,

/// The number of peers discovered after which the discovery process should stop.
#[clap(long, short)]
num_peers: usize,

/// The backend type to use for the discovery process.
#[clap(long, short)]
backend_type: BackendType,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();

let args = Command::parse();
match args {
Command::Discovery(opts) => discovery_backends::discovery_backends(opts).await,
}
}
1 change: 0 additions & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,3 @@ trust-dns-resolver = { workspace = true }
multihash-codetable = { workspace = true, features = ["digest", "serde", "sha2"] }
jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport-native-tls"] }
ss58-registry = { version = "1.34.0", default-features = false }

0 comments on commit c7a66cc

Please sign in to comment.