-
Notifications
You must be signed in to change notification settings - Fork 957
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(perf): implement libp2p perf protocol #3508
Changes from 28 commits
43a4f3e
d843caa
261922d
26e26d3
a31e69a
04858c6
e1a0ced
a41f41b
a0a55ae
1c3bfe9
b95f8a6
9bbd99a
761df78
3b68b37
ffdf089
39694af
4cab07d
b9ef80f
489fbfe
96e7997
27586dc
3fbd70e
8b52690
d9ee033
8c43498
ded45c5
d91a4f8
e023e89
5cd617d
2efd0a9
fbca42e
ce57655
85bb764
bb1b553
ade98eb
4e35494
96327ba
d7c9328
74dd50e
a05272e
3182903
d0e96d2
cd8baf6
8ef5de8
82eb107
b14f6b8
5f3e382
ba96227
64b6498
9519562
3b3b08f
ea30c1b
a98ca38
cd846b1
2651d72
75065bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +0,0 @@ | ||
target | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
[package] | ||
name = "libp2p-perf" | ||
edition = "2021" | ||
rust-version = "1.64.0" | ||
description = "libp2p perf protocol implementation" | ||
version = "0.1.0" | ||
authors = ["Max Inden <mail@max-inden.de>"] | ||
license = "MIT" | ||
repository = "https://github.com/libp2p/rust-libp2p" | ||
keywords = ["peer-to-peer", "libp2p", "networking"] | ||
categories = ["network-programming", "asynchronous"] | ||
|
||
[dependencies] | ||
anyhow = "1" | ||
clap = { version = "4.1.6", features = ["derive"] } | ||
env_logger = "0.10.0" | ||
futures = "0.3.26" | ||
libp2p-core = { version = "0.39.0", path = "../../core" } | ||
libp2p-dns = { version = "0.39.0", path = "../../transports/dns", features = ["async-std"] } | ||
libp2p-noise = { version = "0.42.0", path = "../../transports/noise" } | ||
libp2p-quic = { version = "0.7.0-alpha.2", path = "../../transports/quic", features = ["async-std"] } | ||
libp2p-swarm = { version = "0.42.0", path = "../../swarm", features = ["macros", "async-std"] } | ||
libp2p-tcp = { version = "0.39.0", path = "../../transports/tcp", features = ["async-io"] } | ||
libp2p-yamux = { version = "0.43.0", path = "../../muxers/yamux" } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only need most these for the binaries right? How about we introduce a separate crate that implements the server and client binaries, similarly to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which folder should these binaries go into?
I don't think we should introduce any complexity for the sake of supporting WASM at this point. It is not a critical protocol. I don't see it used in WASM any time soon.
Consistency is a good argument in my eyes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You'd have to make another crate, like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could put them into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @thomaseizinger do you feel strongly about this? Thinking about it some more, I don't think the split into two folders is worth the effort at this point. I expect this crate to change quite a bit after this pull request merges. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. |
||
log = "0.4" | ||
thiserror = "1.0" | ||
void = "1" | ||
|
||
[dev-dependencies] | ||
rand = "0.8" | ||
libp2p-plaintext = { path = "../../transports/plaintext" } | ||
|
||
# Passing arguments to the docsrs builder in order to properly document cfg's. | ||
# More information: https://docs.rs/about/builds#cross-compiling | ||
[package.metadata.docs.rs] | ||
all-features = true | ||
rustdoc-args = ["--cfg", "docsrs"] | ||
rustc-args = ["--cfg", "docsrs"] | ||
thomaseizinger marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
FROM ubuntu:kinetic | ||
ADD ./target/release/perf-client /usr/local/bin/perf-client | ||
ADD ./target/release/perf-server /usr/local/bin/perf-server | ||
ENTRYPOINT [ "perf-server"] | ||
thomaseizinger marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// Copyright 2023 Protocol Labs. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a | ||
// copy of this software and associated documentation files (the "Software"), | ||
// to deal in the Software without restriction, including without limitation | ||
// the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
// and/or sell copies of the Software, and to permit persons to whom the | ||
// Software is furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | ||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
// DEALINGS IN THE SOFTWARE. | ||
|
||
use anyhow::{bail, Result}; | ||
use clap::Parser; | ||
use futures::{executor::block_on, future::Either, StreamExt}; | ||
use libp2p_core::{ | ||
identity, muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multiaddr, PeerId, Transport, | ||
}; | ||
use libp2p_dns::DnsConfig; | ||
use libp2p_perf::client::RunParams; | ||
use libp2p_swarm::{SwarmBuilder, SwarmEvent}; | ||
use log::info; | ||
|
||
#[derive(Debug, Parser)] | ||
#[clap(name = "libp2p perf client")] | ||
struct Opts { | ||
#[arg(long)] | ||
server_address: Multiaddr, | ||
} | ||
|
||
fn main() -> Result<()> { | ||
env_logger::init(); | ||
|
||
let opts = Opts::parse(); | ||
|
||
// Create a random PeerId | ||
let local_key = identity::Keypair::generate_ed25519(); | ||
let local_peer_id = PeerId::from(local_key.public()); | ||
|
||
let transport = { | ||
let tcp = | ||
libp2p_tcp::async_io::Transport::new(libp2p_tcp::Config::default().port_reuse(true)) | ||
.upgrade(upgrade::Version::V1Lazy) | ||
.authenticate( | ||
libp2p_noise::NoiseAuthenticated::xx(&local_key) | ||
.expect("Signing libp2p-noise static DH keypair failed."), | ||
) | ||
.multiplex(libp2p_yamux::YamuxConfig::default()); | ||
|
||
let quic = { | ||
let mut config = libp2p_quic::Config::new(&local_key); | ||
config.support_draft_29 = true; | ||
libp2p_quic::async_std::Transport::new(config) | ||
}; | ||
|
||
let dns = block_on(DnsConfig::system(OrTransport::new(quic, tcp))).unwrap(); | ||
|
||
dns.map(|either_output, _| match either_output { | ||
Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), | ||
Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), | ||
}) | ||
.boxed() | ||
}; | ||
|
||
let mut swarm = SwarmBuilder::with_async_std_executor( | ||
transport, | ||
libp2p_perf::client::behaviour::Behaviour::default(), | ||
local_peer_id, | ||
) | ||
.substream_upgrade_protocol_override(upgrade::Version::V1Lazy) | ||
thomaseizinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.build(); | ||
|
||
swarm.dial(opts.server_address).unwrap(); | ||
let server_peer_id = block_on(async { | ||
loop { | ||
match swarm.next().await.unwrap() { | ||
SwarmEvent::ConnectionEstablished { peer_id, .. } => return Ok(peer_id), | ||
SwarmEvent::OutgoingConnectionError { peer_id, error } => { | ||
bail!("Outgoing connection error to {:?}: {:?}", peer_id, error); | ||
} | ||
e => panic!("{e:?}"), | ||
} | ||
} | ||
})?; | ||
|
||
swarm.behaviour_mut().perf( | ||
server_peer_id, | ||
RunParams { | ||
to_send: 10 * 1024 * 1024, | ||
to_receive: 10 * 1024 * 1024, | ||
}, | ||
)?; | ||
|
||
let result = block_on(async { | ||
thomaseizinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
loop { | ||
match swarm.next().await.unwrap() { | ||
SwarmEvent::ConnectionEstablished { | ||
peer_id, endpoint, .. | ||
} => { | ||
info!("Established connection to {:?} via {:?}", peer_id, endpoint); | ||
} | ||
SwarmEvent::OutgoingConnectionError { peer_id, error } => { | ||
info!("Outgoing connection error to {:?}: {:?}", peer_id, error); | ||
} | ||
SwarmEvent::Behaviour(libp2p_perf::client::behaviour::Event { id: _, result }) => { | ||
break result | ||
} | ||
e => panic!("{e:?}"), | ||
} | ||
} | ||
}); | ||
|
||
let stats = result?; | ||
|
||
let sent_mebibytes = stats.params.to_send as f64 / 1024.0 / 1024.0; | ||
let sent_time = (stats.timers.write_done - stats.timers.write_start).as_secs_f64(); | ||
let sent_bandwidth_mebibit_second = (sent_mebibytes * 8.0) / sent_time; | ||
|
||
let received_mebibytes = stats.params.to_receive as f64 / 1024.0 / 1024.0; | ||
let receive_time = (stats.timers.read_done - stats.timers.write_done).as_secs_f64(); | ||
let receive_bandwidth_mebibit_second = (received_mebibytes * 8.0) / receive_time; | ||
|
||
println!( | ||
"Finished run: Sent {sent_mebibytes} MiB in {sent_time} s with \ | ||
{sent_bandwidth_mebibit_second} MiBit/s and received \ | ||
{received_mebibytes} MiB in {receive_time} s with \ | ||
{receive_bandwidth_mebibit_second} MiBit/s", | ||
); | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// Copyright 2023 Protocol Labs. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a | ||
// copy of this software and associated documentation files (the "Software"), | ||
// to deal in the Software without restriction, including without limitation | ||
// the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
// and/or sell copies of the Software, and to permit persons to whom the | ||
// Software is furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | ||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
// DEALINGS IN THE SOFTWARE. | ||
|
||
use clap::Parser; | ||
use futures::{executor::block_on, future::Either, StreamExt}; | ||
use libp2p_core::{ | ||
identity, muxing::StreamMuxerBox, transport::OrTransport, upgrade, PeerId, Transport, | ||
}; | ||
use libp2p_dns::DnsConfig; | ||
use libp2p_swarm::{SwarmBuilder, SwarmEvent}; | ||
use log::{error, info}; | ||
|
||
#[derive(Debug, Parser)] | ||
#[clap(name = "libp2p perf server")] | ||
struct Opts {} | ||
|
||
fn main() { | ||
env_logger::init(); | ||
|
||
let _opts = Opts::parse(); | ||
|
||
// Create a random PeerId | ||
let local_key = identity::Keypair::generate_ed25519(); | ||
let local_peer_id = PeerId::from(local_key.public()); | ||
println!("Local peer id: {local_peer_id:?}"); | ||
thomaseizinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let transport = { | ||
let tcp = | ||
libp2p_tcp::async_io::Transport::new(libp2p_tcp::Config::default().port_reuse(true)) | ||
.upgrade(upgrade::Version::V1Lazy) | ||
.authenticate( | ||
libp2p_noise::NoiseAuthenticated::xx(&local_key) | ||
.expect("Signing libp2p-noise static DH keypair failed."), | ||
) | ||
.multiplex(libp2p_yamux::YamuxConfig::default()); | ||
|
||
let quic = { | ||
let mut config = libp2p_quic::Config::new(&local_key); | ||
config.support_draft_29 = true; | ||
libp2p_quic::async_std::Transport::new(config) | ||
}; | ||
|
||
let dns = block_on(DnsConfig::system(OrTransport::new(quic, tcp))).unwrap(); | ||
|
||
dns.map(|either_output, _| match either_output { | ||
Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), | ||
Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), | ||
}) | ||
.boxed() | ||
}; | ||
|
||
let mut swarm = SwarmBuilder::with_async_std_executor( | ||
transport, | ||
libp2p_perf::server::behaviour::Behaviour::default(), | ||
local_peer_id, | ||
) | ||
.substream_upgrade_protocol_override(upgrade::Version::V1Lazy) | ||
.build(); | ||
|
||
swarm | ||
.listen_on("/ip4/0.0.0.0/tcp/4001".parse().unwrap()) | ||
.unwrap(); | ||
|
||
swarm | ||
.listen_on("/ip4/0.0.0.0/udp/4001/quic-v1".parse().unwrap()) | ||
.unwrap(); | ||
|
||
block_on(async { | ||
loop { | ||
match swarm.next().await.unwrap() { | ||
SwarmEvent::NewListenAddr { address, .. } => { | ||
info!("Listening on {:?}", address); | ||
thomaseizinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
SwarmEvent::IncomingConnection { .. } => {} | ||
e @ SwarmEvent::IncomingConnectionError { .. } => { | ||
error!("{e:?}"); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of printing the enum variant, could only print the error, then you wouldn't need to bind the pattern. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But then I wouldn't know who (i.e. which dialer) caused the incoming connection error. Am I missing something? |
||
SwarmEvent::ConnectionEstablished { | ||
peer_id, endpoint, .. | ||
} => { | ||
info!("Established connection to {:?} via {:?}", peer_id, endpoint); | ||
} | ||
SwarmEvent::ConnectionClosed { .. } => {} | ||
SwarmEvent::Behaviour(libp2p_perf::server::behaviour::Event::Finished { | ||
remote_peer_id, | ||
stats, | ||
}) => { | ||
let received_mebibytes = stats.params.received as f64 / 1024.0 / 1024.0; | ||
let receive_time = | ||
(stats.timers.read_done - stats.timers.read_start).as_secs_f64(); | ||
let receive_bandwidth_mebibit_second = | ||
(received_mebibytes * 8.0) / receive_time; | ||
|
||
let sent_mebibytes = stats.params.sent as f64 / 1024.0 / 1024.0; | ||
let sent_time = | ||
(stats.timers.write_done - stats.timers.read_done).as_secs_f64(); | ||
let sent_bandwidth_mebibit_second = (sent_mebibytes * 8.0) / sent_time; | ||
|
||
info!( | ||
"Finished run with {}: Received {} MiB in {} s with {} MiBit/s and sent {} MiB in {} s with {} MiBit/s", | ||
remote_peer_id, | ||
received_mebibytes, | ||
receive_time, | ||
receive_bandwidth_mebibit_second, | ||
sent_mebibytes, | ||
sent_time, | ||
sent_bandwidth_mebibit_second, | ||
) | ||
} | ||
e => panic!("{e:?}"), | ||
} | ||
} | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing this will send the entire target directory to docker which can be dozens of GB if you don't run cargo clean regularly.
We should use a dedicated run cache instead to speed up the build of docker files. See "Use a dedicated RUN cache": https://docs.docker.com/build/cache/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this was meant for debugging only. Thanks for highlighting it.
5cd617d reverts the change and uses the dedicated run caches. Mind taking another look @thomaseizinger?