From e4afb8e08faa464bd3e6da5f9462394047ef9543 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 20 Mar 2023 10:32:23 +0100 Subject: [PATCH 01/37] Have at most one stream in-flight - Enforces backpressure on stream creation on QUIC - Note that this does not sequentialize stream negotiation as we are using V1Lazy As an aside, with and without `V1Lazy`: ``` [2023-03-20T09:31:21Z INFO perf_client] Starting: single connection requests per second benchmark. [2023-03-20T09:31:28Z INFO perf_client] Finsihed: sent 10000 1 bytes requests with 1 bytes response each within 6.28 s thus 1591.963270172488 req/s. ``` ``` [2023-03-20T09:34:03Z INFO perf_client] Starting: single connection requests per second benchmark. [2023-03-20T09:37:07Z INFO perf_client] Finsihed: sent 10000 1 bytes requests with 1 bytes response each within 183.90 s thus 54.37835611842429 req/s. ``` --- protocols/perf/src/client/handler.rs | 53 +++++++++++++++++----------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/protocols/perf/src/client/handler.rs b/protocols/perf/src/client/handler.rs index f75a43f0a4e..6d78b677054 100644 --- a/protocols/perf/src/client/handler.rs +++ b/protocols/perf/src/client/handler.rs @@ -61,6 +61,10 @@ pub struct Handler { >, >, + new_commands: VecDeque, + + pending_command: Option, + outbound: FuturesUnordered>>, keep_alive: KeepAlive, @@ -70,6 +74,8 @@ impl Handler { pub fn new() -> Self { Self { queued_events: Default::default(), + new_commands: Default::default(), + pending_command: Default::default(), outbound: Default::default(), keep_alive: KeepAlive::Yes, } @@ -87,8 +93,8 @@ impl ConnectionHandler for Handler { type OutEvent = Event; type Error = Void; type InboundProtocol = DeniedUpgrade; - type OutboundProtocol = ReadyUpgrade<&'static [u8]>; - type OutboundOpenInfo = Command; + type OutboundProtocol = ReadyUpgrade<&'static [u8; 11]>; + type OutboundOpenInfo = (); type InboundOpenInfo = (); fn listen_protocol(&self) -> SubstreamProtocol { @@ -96,10 +102,7 @@ impl ConnectionHandler for Handler { } fn on_behaviour_event(&mut self, command: Self::InEvent) { - self.queued_events - .push_back(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new(ReadyUpgrade::new(crate::PROTOCOL_NAME), command), - }) + self.new_commands.push_back(command); } fn on_connection_event( @@ -117,24 +120,24 @@ impl ConnectionHandler for Handler { }) => void::unreachable(protocol), ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { protocol, - info: Command { params, id }, - }) => self.outbound.push( - crate::protocol::send_receive(params, protocol) - .map_ok(move |timers| Event { - id, - result: Ok(RunStats { params, timers }), - }) - .boxed(), - ), + info: (), + }) => { + let Command { params, id } = self.pending_command.take().unwrap(); + self.outbound.push( + crate::protocol::send_receive(params, protocol) + .map_ok(move |timers| Event { + id, + result: Ok(RunStats { params, timers }), + }) + .boxed(), + ) + } ConnectionEvent::AddressChange(_) => {} - ConnectionEvent::DialUpgradeError(DialUpgradeError { - info: Command { id, .. }, - error, - }) => self + ConnectionEvent::DialUpgradeError(DialUpgradeError { info: (), error }) => self .queued_events .push_back(ConnectionHandlerEvent::Custom(Event { - id, + id: self.pending_command.take().unwrap().id, result: Err(error), })), ConnectionEvent::ListenUpgradeError(ListenUpgradeError { info: (), error }) => { @@ -179,6 +182,16 @@ impl ConnectionHandler for Handler { } } + if self.pending_command.is_none() { + if let Some(command) = self.new_commands.pop_front() { + self.pending_command = Some(command); + return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(ReadyUpgrade::new(crate::PROTOCOL_NAME), ()) + .with_timeout(Duration::from_secs(60)), + }); + } + } + if self.outbound.is_empty() { match self.keep_alive { KeepAlive::Yes => { From 1c5384d3703415c1d40cf0d1f35623ba185ac148 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 20 Mar 2023 10:38:58 +0100 Subject: [PATCH 02/37] Implement single connection requests per second benchmark --- protocols/perf/src/bin/perf-client.rs | 62 ++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index c12ab5cbe74..826612e10d0 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -21,6 +21,7 @@ use anyhow::{bail, Result}; use clap::Parser; use futures::{future::Either, StreamExt}; +use instant::Instant; use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multiaddr, Transport}; use libp2p_dns::DnsConfig; use libp2p_identity::PeerId; @@ -93,10 +94,9 @@ async fn main() -> Result<()> { } }; - info!( - "Connection to {} established. Launching benchmarks.", - opts.server_address - ); + info!("Connection to {} established.\n", opts.server_address); + + info!("Starting: single connection single channel throughput benchmark."); swarm.behaviour_mut().perf( server_peer_id, @@ -130,10 +130,60 @@ async fn main() -> Result<()> { let receive_bandwidth_mebibit_second = (received_mebibytes * 8.0) / receive_time; info!( - "Finished run: Sent {sent_mebibytes:.2} MiB in {sent_time:.2} s with \ + "Finished: sent {sent_mebibytes:.2} MiB in {sent_time:.2} s with \ {sent_bandwidth_mebibit_second:.2} MiBit/s and received \ {received_mebibytes:.2} MiB in {receive_time:.2} s with \ - {receive_bandwidth_mebibit_second:.2} MiBit/s", + {receive_bandwidth_mebibit_second:.2} MiBit/s\n", + ); + + info!("Starting: single connection requests per second benchmark."); + + let num = 10_000; + let to_send = 1; + let to_receive = 1; + + for _ in 0..num { + swarm.behaviour_mut().perf( + server_peer_id, + RunParams { + to_send, + to_receive, + }, + )?; + } + + let mut finished = 0; + let start = Instant::now(); + + 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::Event { + id: _, + result: Ok(_), + }) => { + finished += 1; + + if finished == num { + break; + } + } + e => panic!("{e:?}"), + } + } + + let duration = start.elapsed().as_secs_f64(); + let requests_per_second = num as f64 / duration; + + info!( + "Finsihed: sent {num} {to_send} bytes requests with {to_receive} bytes response each within {duration:.2} s thus {requests_per_second} req/s.\n", ); Ok(()) From 2bc92fe741c39573ff2a9c3da2316af211f10625 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 20 Mar 2023 11:05:39 +0100 Subject: [PATCH 03/37] Use `colored` crate for log output ``` [2023-03-20T10:09:29Z INFO perf_client] Initiating performance tests with /dns4/libp2p-perf.max-inden.de/udp/4001/quic-v1 [2023-03-20T10:09:29Z INFO perf_client] Connection to /dns4/libp2p-perf.max-inden.de/udp/4001/quic-v1 established. [2023-03-20T10:09:29Z INFO perf_client] Start benchmark: single connection single channel throughput [2023-03-20T10:09:41Z INFO perf_client] Finished: sent 10.00 MiB in 7.60 s with 10.53 MiBit/s and received 10.00 MiB in 3.92 s with 20.41 MiBit/s [2023-03-20T10:09:41Z INFO perf_client] Start benchmark: single connection requests per second [2023-03-20T10:09:48Z INFO perf_client] Finished: sent 10000 1 bytes requests with 1 bytes response each within 6.93 s thus 1442.81 req/s ``` --- Cargo.lock | 12 ++++++++++ protocols/perf/Cargo.toml | 1 + protocols/perf/src/bin/perf-client.rs | 34 ++++++++++++++++++++------- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c63611c013..51600b021ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -758,6 +758,17 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "combine" version = "4.6.6" @@ -2559,6 +2570,7 @@ dependencies = [ "anyhow", "async-std", "clap 4.1.8", + "colored", "env_logger 0.10.0", "futures", "instant", diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index e56338354e9..f2fba6601ef 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -14,6 +14,7 @@ categories = ["network-programming", "asynchronous"] anyhow = "1" async-std = { version = "1.9.0", features = ["attributes"] } clap = { version = "4.1.6", features = ["derive"] } +colored = "2" env_logger = "0.10.0" futures = "0.3.26" instant = "0.1.11" diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index 826612e10d0..73ac2bd56e5 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -20,6 +20,7 @@ use anyhow::{bail, Result}; use clap::Parser; +use colored::*; use futures::{future::Either, StreamExt}; use instant::Instant; use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multiaddr, Transport}; @@ -42,7 +43,14 @@ async fn main() -> Result<()> { let opts = Opts::parse(); - info!("Initiating performance tests with {}", opts.server_address); + info!( + "{}", + format!( + "Initiating performance tests with {}\n", + opts.server_address + ) + .bold() + ); // Create a random PeerId let local_key = libp2p_identity::Keypair::generate_ed25519(); @@ -96,7 +104,10 @@ async fn main() -> Result<()> { info!("Connection to {} established.\n", opts.server_address); - info!("Starting: single connection single channel throughput benchmark."); + info!( + "{}", + "Start benchmark: single connection single channel throughput".underline(), + ); swarm.behaviour_mut().perf( server_peer_id, @@ -123,20 +134,25 @@ async fn main() -> 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 sent_bandwidth_mebibit_second = + format!("{:.2} MiBit/s", (sent_mebibytes * 8.0) / sent_time).bold(); 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; + let receive_bandwidth_mebibit_second = + format!("{:.2} MiBit/s", (received_mebibytes * 8.0) / receive_time).bold(); info!( "Finished: sent {sent_mebibytes:.2} MiB in {sent_time:.2} s with \ - {sent_bandwidth_mebibit_second:.2} MiBit/s and received \ + {sent_bandwidth_mebibit_second} and received \ {received_mebibytes:.2} MiB in {receive_time:.2} s with \ - {receive_bandwidth_mebibit_second:.2} MiBit/s\n", + {receive_bandwidth_mebibit_second}\n", ); - info!("Starting: single connection requests per second benchmark."); + info!( + "{}", + "Start benchmark: single connection requests per second".underline(), + ); let num = 10_000; let to_send = 1; @@ -180,10 +196,10 @@ async fn main() -> Result<()> { } let duration = start.elapsed().as_secs_f64(); - let requests_per_second = num as f64 / duration; + let requests_per_second = format!("{:.2} req/s", num as f64 / duration).bold(); info!( - "Finsihed: sent {num} {to_send} bytes requests with {to_receive} bytes response each within {duration:.2} s thus {requests_per_second} req/s.\n", + "Finished: sent {num} {to_send} bytes requests with {to_receive} bytes response each within {duration:.2} s thus {requests_per_second}\n", ); Ok(()) From 9dffaf7afeb42c7ffdaefb7bf8c3bfac12867db2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 20 Mar 2023 12:18:58 +0100 Subject: [PATCH 04/37] Implement connections per second benchmark --- Cargo.lock | 1 + protocols/perf/Cargo.toml | 1 + protocols/perf/src/bin/perf-client.rs | 333 +++++++++++++++++--------- 3 files changed, 223 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51600b021ce..21eeff9a26a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2569,6 +2569,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-std", + "async-trait", "clap 4.1.8", "colored", "env_logger 0.10.0", diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index f2fba6601ef..028476a5d8a 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -13,6 +13,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] anyhow = "1" async-std = { version = "1.9.0", features = ["attributes"] } +async-trait = "0.1" clap = { version = "4.1.6", features = ["derive"] } colored = "2" env_logger = "0.10.0" diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index 73ac2bd56e5..8fba571f8bd 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -19,6 +19,7 @@ // DEALINGS IN THE SOFTWARE. use anyhow::{bail, Result}; +use async_trait::async_trait; use clap::Parser; use colored::*; use futures::{future::Either, StreamExt}; @@ -27,7 +28,7 @@ use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multi use libp2p_dns::DnsConfig; use libp2p_identity::PeerId; use libp2p_perf::client::RunParams; -use libp2p_swarm::{SwarmBuilder, SwarmEvent}; +use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::info; #[derive(Debug, Parser)] @@ -43,15 +44,217 @@ async fn main() -> Result<()> { let opts = Opts::parse(); - info!( - "{}", - format!( - "Initiating performance tests with {}\n", - opts.server_address - ) - .bold() + let benchmarks: [Box; 3] = [ + Box::new(Throughput {}), + Box::new(RequestsPerSecond {}), + Box::new(ConnectionsPerSecond {}), + ]; + + for benchmark in benchmarks { + info!( + "{}", + format!("Start benchmark: {}", benchmark.name()).underline(), + ); + + benchmark.run(opts.server_address.clone()).await?; + } + + Ok(()) +} + +#[async_trait] +trait Benchmark { + fn name(&self) -> &'static str; + + async fn run(&self, server_address: Multiaddr) -> Result<()>; +} + +struct Throughput {} + +#[async_trait] +impl Benchmark for Throughput { + fn name(&self) -> &'static str { + "single connection single channel throughput" + } + + async fn run(&self, server_address: Multiaddr) -> Result<()> { + let mut swarm = swarm().await; + + let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + + swarm.behaviour_mut().perf( + server_peer_id, + RunParams { + to_send: 10 * 1024 * 1024, + to_receive: 10 * 1024 * 1024, + }, + )?; + + let stats = 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::Event { id: _, result }) => { + break result? + } + e => panic!("{e:?}"), + } + }; + + 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 = + format!("{:.2} MiBit/s", (sent_mebibytes * 8.0) / sent_time).bold(); + + 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 = + format!("{:.2} MiBit/s", (received_mebibytes * 8.0) / receive_time).bold(); + + info!( + "Finished: sent {sent_mebibytes:.2} MiB in {sent_time:.2} s with \ + {sent_bandwidth_mebibit_second} and received \ + {received_mebibytes:.2} MiB in {receive_time:.2} s with \ + {receive_bandwidth_mebibit_second}\n", + ); + Ok(()) + } +} + +struct RequestsPerSecond {} + +#[async_trait] +impl Benchmark for RequestsPerSecond { + fn name(&self) -> &'static str { + "single connection parallel requests per second" + } + + async fn run(&self, server_address: Multiaddr) -> Result<()> { + let mut swarm = swarm().await; + + let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + + let num = 10_000; + let to_send = 1; + let to_receive = 1; + + for _ in 0..num { + swarm.behaviour_mut().perf( + server_peer_id, + RunParams { + to_send, + to_receive, + }, + )?; + } + + let mut finished = 0; + let start = Instant::now(); + + 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::Event { + id: _, + result: Ok(_), + }) => { + finished += 1; + + if finished == num { + break; + } + } + e => panic!("{e:?}"), + } + } + + let duration = start.elapsed().as_secs_f64(); + let requests_per_second = format!("{:.2} req/s", num as f64 / duration).bold(); + + info!( + "Finished: sent {num} {to_send} bytes requests with {to_receive} bytes response each within {duration:.2} s thus {requests_per_second}\n", ); + Ok(()) + } +} + +struct ConnectionsPerSecond {} + +#[async_trait] +impl Benchmark for ConnectionsPerSecond { + fn name(&self) -> &'static str { + "sequential connections with single request per second" + } + + async fn run(&self, server_address: Multiaddr) -> Result<()> { + let num = 100; + let to_send = 1; + let to_receive = 1; + let start = Instant::now(); + let mut finished = 0; + + for _ in 0..num { + let mut swarm = swarm().await; + + let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + + swarm.behaviour_mut().perf( + server_peer_id, + RunParams { + to_send, + to_receive, + }, + )?; + + 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::Event { + id: _, + result: Ok(_), + }) => { + finished += 1; + + if finished == num { + break; + } + } + e => panic!("{e:?}"), + }; + } + + let duration = start.elapsed().as_secs_f64(); + let connections_per_second = format!("{:.2} conns/s", num as f64 / duration).bold(); + + info!( + "Finished: established {num} connections with one {to_send} bytes request with {to_receive} bytes response each within {duration:.2} s thus {connections_per_second}\n", + ); + + Ok(()) + } +} + +async fn swarm() -> Swarm { // Create a random PeerId let local_key = libp2p_identity::Keypair::generate_ed25519(); let local_peer_id = PeerId::from(local_key.public()); @@ -83,15 +286,21 @@ async fn main() -> Result<()> { .boxed() }; - let mut swarm = SwarmBuilder::with_async_std_executor( + SwarmBuilder::with_async_std_executor( transport, libp2p_perf::client::Behaviour::default(), local_peer_id, ) .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) - .build(); + .build() +} + +async fn connect( + swarm: &mut Swarm, + server_address: Multiaddr, +) -> Result { + swarm.dial(server_address.clone()).unwrap(); - swarm.dial(opts.server_address.clone()).unwrap(); let server_peer_id = loop { match swarm.next().await.unwrap() { SwarmEvent::ConnectionEstablished { peer_id, .. } => break peer_id, @@ -102,105 +311,5 @@ async fn main() -> Result<()> { } }; - info!("Connection to {} established.\n", opts.server_address); - - info!( - "{}", - "Start benchmark: single connection single channel throughput".underline(), - ); - - swarm.behaviour_mut().perf( - server_peer_id, - RunParams { - to_send: 10 * 1024 * 1024, - to_receive: 10 * 1024 * 1024, - }, - )?; - - let stats = 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::Event { id: _, result }) => break result?, - e => panic!("{e:?}"), - } - }; - - 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 = - format!("{:.2} MiBit/s", (sent_mebibytes * 8.0) / sent_time).bold(); - - 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 = - format!("{:.2} MiBit/s", (received_mebibytes * 8.0) / receive_time).bold(); - - info!( - "Finished: sent {sent_mebibytes:.2} MiB in {sent_time:.2} s with \ - {sent_bandwidth_mebibit_second} and received \ - {received_mebibytes:.2} MiB in {receive_time:.2} s with \ - {receive_bandwidth_mebibit_second}\n", - ); - - info!( - "{}", - "Start benchmark: single connection requests per second".underline(), - ); - - let num = 10_000; - let to_send = 1; - let to_receive = 1; - - for _ in 0..num { - swarm.behaviour_mut().perf( - server_peer_id, - RunParams { - to_send, - to_receive, - }, - )?; - } - - let mut finished = 0; - let start = Instant::now(); - - 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::Event { - id: _, - result: Ok(_), - }) => { - finished += 1; - - if finished == num { - break; - } - } - e => panic!("{e:?}"), - } - } - - let duration = start.elapsed().as_secs_f64(); - let requests_per_second = format!("{:.2} req/s", num as f64 / duration).bold(); - - info!( - "Finished: sent {num} {to_send} bytes requests with {to_receive} bytes response each within {duration:.2} s thus {requests_per_second}\n", - ); - - Ok(()) + Ok(server_peer_id) } From bff4279a87cb46f8dc6c8b04e2ddcba43230839d Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 20 Mar 2023 14:43:51 +0100 Subject: [PATCH 05/37] Add ping latency benchmark --- protocols/perf/src/bin/perf-client.rs | 137 ++++++++++++++++++++------ 1 file changed, 105 insertions(+), 32 deletions(-) diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index 8fba571f8bd..61651e3d3d7 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -27,7 +27,7 @@ use instant::Instant; use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multiaddr, Transport}; use libp2p_dns::DnsConfig; use libp2p_identity::PeerId; -use libp2p_perf::client::RunParams; +use libp2p_perf::client::{RunParams, RunStats, RunTimers}; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::info; @@ -44,7 +44,8 @@ async fn main() -> Result<()> { let opts = Opts::parse(); - let benchmarks: [Box; 3] = [ + let benchmarks: [Box; 4] = [ + Box::new(Latency {}), Box::new(Throughput {}), Box::new(RequestsPerSecond {}), Box::new(ConnectionsPerSecond {}), @@ -69,6 +70,74 @@ trait Benchmark { async fn run(&self, server_address: Multiaddr) -> Result<()>; } +struct Latency {} + +#[async_trait] +impl Benchmark for Latency { + fn name(&self) -> &'static str { + "round-trip-time latency" + } + + async fn run(&self, server_address: Multiaddr) -> Result<()> { + let mut swarm = swarm().await; + + let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + + let num = 1_000; + let mut latencies = Vec::new(); + + for _ in 0..num { + swarm.behaviour_mut().perf( + server_peer_id, + RunParams { + to_send: 1, + to_receive: 1, + }, + )?; + + let latency = 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::Event { + id: _, + result: + Ok(RunStats { + timers: + RunTimers { + write_start, + read_done, + .. + }, + .. + }), + }) => break read_done.duration_since(write_start).as_secs_f64(), + e => panic!("{e:?}"), + }; + }; + latencies.push(latency); + } + + latencies.sort_by(|a, b| a.partial_cmp(b).unwrap()); + + info!("Finished: {num} pings",); + info!("- {:.4} s median", percentile(&latencies, 0.50),); + info!("- {:.4} s 95th percentile\n", percentile(&latencies, 0.95),); + Ok(()) + } +} + +fn percentile(values: &[V], percentile: f64) -> V { + let n: usize = (values.len() as f64 * percentile).ceil() as usize - 1; + values[n] +} + struct Throughput {} #[async_trait] @@ -109,20 +178,19 @@ impl Benchmark for Throughput { 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 = - format!("{:.2} MiBit/s", (sent_mebibytes * 8.0) / sent_time).bold(); + 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 = - format!("{:.2} MiBit/s", (received_mebibytes * 8.0) / receive_time).bold(); + let receive_bandwidth_mebibit_second = (received_mebibytes * 8.0) / receive_time; info!( - "Finished: sent {sent_mebibytes:.2} MiB in {sent_time:.2} s with \ - {sent_bandwidth_mebibit_second} and received \ - {received_mebibytes:.2} MiB in {receive_time:.2} s with \ - {receive_bandwidth_mebibit_second}\n", + "Finished: sent {sent_mebibytes:.2} MiB in {sent_time:.2} s \ + and received {received_mebibytes:.2} MiB in {receive_time:.2} s", ); + info!("- {sent_bandwidth_mebibit_second:.2}"); + info!("- {receive_bandwidth_mebibit_second:.2}\n"); + Ok(()) } } @@ -182,11 +250,12 @@ impl Benchmark for RequestsPerSecond { } let duration = start.elapsed().as_secs_f64(); - let requests_per_second = format!("{:.2} req/s", num as f64 / duration).bold(); + let requests_per_second = num as f64 / duration; info!( - "Finished: sent {num} {to_send} bytes requests with {to_receive} bytes response each within {duration:.2} s thus {requests_per_second}\n", - ); + "Finished: sent {num} {to_send} bytes requests with {to_receive} bytes response each within {duration:.2} s", + ); + info!("- {requests_per_second:.2} req/s\n"); Ok(()) } @@ -205,13 +274,19 @@ impl Benchmark for ConnectionsPerSecond { let to_send = 1; let to_receive = 1; let start = Instant::now(); - let mut finished = 0; + + let mut latency_connection_establishment = Vec::new(); + let mut latency_connection_establishment_plus_request = Vec::new(); for _ in 0..num { let mut swarm = swarm().await; + let start = Instant::now(); + let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + latency_connection_establishment.push(start.elapsed().as_secs_f64()); + swarm.behaviour_mut().perf( server_peer_id, RunParams { @@ -221,34 +296,32 @@ impl Benchmark for ConnectionsPerSecond { )?; 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::Event { id: _, result: Ok(_), - }) => { - finished += 1; - - if finished == num { - break; - } - } + }) => {} e => panic!("{e:?}"), }; + + latency_connection_establishment_plus_request.push(start.elapsed().as_secs_f64()) } let duration = start.elapsed().as_secs_f64(); - let connections_per_second = format!("{:.2} conns/s", num as f64 / duration).bold(); + let connections_per_second = num as f64 / duration; + + latency_connection_establishment.sort_by(|a, b| a.partial_cmp(b).unwrap()); + latency_connection_establishment_plus_request.sort_by(|a, b| a.partial_cmp(b).unwrap()); + + let connection_establishment_95th = percentile(&latency_connection_establishment, 0.95); + let connection_establishment_plus_request_95th = + percentile(&latency_connection_establishment_plus_request, 0.95); info!( - "Finished: established {num} connections with one {to_send} bytes request with {to_receive} bytes response each within {duration:.2} s thus {connections_per_second}\n", - ); + "Finished: established {num} connections with one {to_send} bytes request and one {to_receive} bytes response within {duration:.2} s", + ); + info!("- {connections_per_second:.2} conns/s"); + info!("- {connection_establishment_95th:.4} s 95th percentile connection establishment"); + info!("- {connection_establishment_plus_request_95th:.4} s 95th percentile connection establishment + one request"); Ok(()) } From dfa1e57065be66a9a29a05e0b808648306f0f501 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 20 Mar 2023 14:49:29 +0100 Subject: [PATCH 06/37] Fix units --- protocols/perf/src/bin/perf-client.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index 61651e3d3d7..c23982a0918 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -188,8 +188,8 @@ impl Benchmark for Throughput { "Finished: sent {sent_mebibytes:.2} MiB in {sent_time:.2} s \ and received {received_mebibytes:.2} MiB in {receive_time:.2} s", ); - info!("- {sent_bandwidth_mebibit_second:.2}"); - info!("- {receive_bandwidth_mebibit_second:.2}\n"); + info!("- {sent_bandwidth_mebibit_second:.2} MiBit/s up"); + info!("- {receive_bandwidth_mebibit_second:.2} MiBit/s down\n"); Ok(()) } @@ -307,7 +307,6 @@ impl Benchmark for ConnectionsPerSecond { } let duration = start.elapsed().as_secs_f64(); - let connections_per_second = num as f64 / duration; latency_connection_establishment.sort_by(|a, b| a.partial_cmp(b).unwrap()); latency_connection_establishment_plus_request.sort_by(|a, b| a.partial_cmp(b).unwrap()); @@ -319,7 +318,6 @@ impl Benchmark for ConnectionsPerSecond { info!( "Finished: established {num} connections with one {to_send} bytes request and one {to_receive} bytes response within {duration:.2} s", ); - info!("- {connections_per_second:.2} conns/s"); info!("- {connection_establishment_95th:.4} s 95th percentile connection establishment"); info!("- {connection_establishment_plus_request_95th:.4} s 95th percentile connection establishment + one request"); From ed6dd3fae8768aed22dbdf04805c40796bd079f9 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 24 Mar 2023 13:58:08 +0100 Subject: [PATCH 07/37] Print benchmark output according to json schema See https://github.com/libp2p/test-plans/pull/159 for schema discussion. --- Cargo.lock | 146 ++++++++++++++---- protocols/perf/Cargo.toml | 3 + protocols/perf/src/benchmark-data-schema.json | 45 ++++++ protocols/perf/src/bin/perf-client.rs | 63 ++++++-- 4 files changed, 218 insertions(+), 39 deletions(-) create mode 100644 protocols/perf/src/benchmark-data-schema.json diff --git a/Cargo.lock b/Cargo.lock index 21eeff9a26a..8f36ecfc54c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "adler" version = "1.0.2" @@ -194,7 +204,7 @@ checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -206,7 +216,7 @@ checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -218,7 +228,7 @@ checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -234,7 +244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -406,7 +416,7 @@ checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -737,7 +747,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1006,7 +1016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1086,7 +1096,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] @@ -1097,7 +1107,7 @@ checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1123,7 +1133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" dependencies = [ "data-encoding", - "syn", + "syn 1.0.109", ] [[package]] @@ -1195,7 +1205,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1205,7 +1215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" dependencies = [ "derive_builder_core", - "syn", + "syn 1.0.109", ] [[package]] @@ -1236,7 +1246,7 @@ checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1341,7 +1351,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1531,7 +1541,7 @@ checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2587,6 +2597,9 @@ dependencies = [ "libp2p-yamux", "log", "rand 0.8.5", + "schemafy", + "serde", + "serde_json", "thiserror", "void", ] @@ -2793,7 +2806,7 @@ version = "0.32.0" dependencies = [ "heck", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3187,7 +3200,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -3541,7 +3554,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3705,7 +3718,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -3749,7 +3762,7 @@ checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3772,7 +3785,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4239,6 +4252,49 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemafy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9725c16a64e85972fcb3630677be83fef699a1cd8e4bfbdcf3b3c6675f838a19" +dependencies = [ + "Inflector", + "schemafy_core", + "schemafy_lib", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "syn 1.0.109", +] + +[[package]] +name = "schemafy_core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bec29dddcfe60f92f3c0d422707b8b56473983ef0481df8d5236ed3ab8fdf24" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "schemafy_lib" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3d87f1df246a9b7e2bfd1f4ee5f88e48b11ef9cfc62e63f0dead255b1a6f5f" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "schemafy_core", + "serde", + "serde_derive", + "serde_json", + "syn 1.0.109", + "uriparse", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -4329,7 +4385,7 @@ checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4343,6 +4399,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.8", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -4577,6 +4644,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -4585,7 +4663,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -4655,7 +4733,7 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4738,7 +4816,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4802,7 +4880,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4967,6 +5045,16 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + [[package]] name = "url" version = "2.3.1" @@ -5084,7 +5172,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -5118,7 +5206,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5675,6 +5763,6 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index 028476a5d8a..63fd97ba023 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -29,6 +29,9 @@ libp2p-swarm-test = { path = "../../swarm-test"} libp2p-tcp = { version = "0.39.0", path = "../../transports/tcp", features = ["async-io"] } libp2p-yamux = { version = "0.43.0", path = "../../muxers/yamux" } log = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +schemafy = "0.6.0" thiserror = "1.0" void = "1" diff --git a/protocols/perf/src/benchmark-data-schema.json b/protocols/perf/src/benchmark-data-schema.json new file mode 100644 index 00000000000..ed06bbbf23e --- /dev/null +++ b/protocols/perf/src/benchmark-data-schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://libp2p.io/benchmark.schema.json", + "title": "Benchmark Results", + "description": "Results from a benchmark run", + "type": "object", + "properties": { + "benchmarks": { + "description": "A list of benchmark results", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "The name of this benchmark", + "type": "string" + }, + "unit": { + "description": "The unit for the result", + "enum": [ + "bits/s", + "s" + ] + }, + "result": { + "description": "String encoded result. Parse as float64", + "type": "string" + } + }, + "required": [ + "name", + "unit", + "result", + "implementation", + "stack", + "version" + ] + } + } + }, + "required": [ + "benchmarks", + "productName" + ] +} diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index c23982a0918..74edd140eb3 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -30,6 +30,12 @@ use libp2p_identity::PeerId; use libp2p_perf::client::{RunParams, RunStats, RunTimers}; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::info; +use serde::{Deserialize, Serialize}; + +schemafy::schemafy!( + root: BenchmarkResults + "src/benchmark-data-schema.json" +); #[derive(Debug, Parser)] #[clap(name = "libp2p perf client")] @@ -51,15 +57,28 @@ async fn main() -> Result<()> { Box::new(ConnectionsPerSecond {}), ]; + let mut results = vec![]; + for benchmark in benchmarks { info!( "{}", format!("Start benchmark: {}", benchmark.name()).underline(), ); - benchmark.run(opts.server_address.clone()).await?; + let result = benchmark.run(opts.server_address.clone()).await?; + if let Some(result) = result { + results.push(result); + } } + println!( + "{}", + serde_json::to_string(&BenchmarkResults { + benchmarks: results, + }) + .unwrap() + ); + Ok(()) } @@ -67,7 +86,10 @@ async fn main() -> Result<()> { trait Benchmark { fn name(&self) -> &'static str; - async fn run(&self, server_address: Multiaddr) -> Result<()>; + async fn run( + &self, + server_address: Multiaddr, + ) -> Result>; } struct Latency {} @@ -78,7 +100,10 @@ impl Benchmark for Latency { "round-trip-time latency" } - async fn run(&self, server_address: Multiaddr) -> Result<()> { + async fn run( + &self, + server_address: Multiaddr, + ) -> Result> { let mut swarm = swarm().await; let server_peer_id = connect(&mut swarm, server_address.clone()).await?; @@ -129,7 +154,7 @@ impl Benchmark for Latency { info!("Finished: {num} pings",); info!("- {:.4} s median", percentile(&latencies, 0.50),); info!("- {:.4} s 95th percentile\n", percentile(&latencies, 0.95),); - Ok(()) + Ok(None) } } @@ -146,7 +171,10 @@ impl Benchmark for Throughput { "single connection single channel throughput" } - async fn run(&self, server_address: Multiaddr) -> Result<()> { + async fn run( + &self, + server_address: Multiaddr, + ) -> Result> { let mut swarm = swarm().await; let server_peer_id = connect(&mut swarm, server_address.clone()).await?; @@ -191,7 +219,11 @@ impl Benchmark for Throughput { info!("- {sent_bandwidth_mebibit_second:.2} MiBit/s up"); info!("- {receive_bandwidth_mebibit_second:.2} MiBit/s down\n"); - Ok(()) + Ok(Some(BenchmarkResultsItemBenchmarks { + name: "Single Connection throughput – Upload".to_string(), + unit: serde_json::Value::String("bits/s".to_string()), + result: (stats.params.to_send as f64 / sent_time).to_string(), + })) } } @@ -203,7 +235,10 @@ impl Benchmark for RequestsPerSecond { "single connection parallel requests per second" } - async fn run(&self, server_address: Multiaddr) -> Result<()> { + async fn run( + &self, + server_address: Multiaddr, + ) -> Result> { let mut swarm = swarm().await; let server_peer_id = connect(&mut swarm, server_address.clone()).await?; @@ -257,7 +292,7 @@ impl Benchmark for RequestsPerSecond { ); info!("- {requests_per_second:.2} req/s\n"); - Ok(()) + Ok(None) } } @@ -269,7 +304,10 @@ impl Benchmark for ConnectionsPerSecond { "sequential connections with single request per second" } - async fn run(&self, server_address: Multiaddr) -> Result<()> { + async fn run( + &self, + server_address: Multiaddr, + ) -> Result> { let num = 100; let to_send = 1; let to_receive = 1; @@ -321,7 +359,12 @@ impl Benchmark for ConnectionsPerSecond { info!("- {connection_establishment_95th:.4} s 95th percentile connection establishment"); info!("- {connection_establishment_plus_request_95th:.4} s 95th percentile connection establishment + one request"); - Ok(()) + Ok(Some(BenchmarkResultsItemBenchmarks { + name: "Single Connection 1 byte round trip latency 100 runs 95th percentile" + .to_string(), + unit: serde_json::Value::String("s".to_string()), + result: connection_establishment_plus_request_95th.to_string(), + })) } } From bebb7e77108b54d6179d91a6fdfc1804a1053d57 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 8 Apr 2023 10:26:20 +0200 Subject: [PATCH 08/37] Update data schema --- protocols/perf/src/benchmark-data-schema.json | 133 ++++++++++++------ protocols/perf/src/bin/perf-client.rs | 99 +++++++------ 2 files changed, 149 insertions(+), 83 deletions(-) diff --git a/protocols/perf/src/benchmark-data-schema.json b/protocols/perf/src/benchmark-data-schema.json index ed06bbbf23e..73143f4ec21 100644 --- a/protocols/perf/src/benchmark-data-schema.json +++ b/protocols/perf/src/benchmark-data-schema.json @@ -1,45 +1,98 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://libp2p.io/benchmark.schema.json", - "title": "Benchmark Results", - "description": "Results from a benchmark run", - "type": "object", - "properties": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/Benchmarks", + "definitions": { + "Benchmarks": { + "type": "object", + "properties": { "benchmarks": { - "description": "A list of benchmark results", - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "description": "The name of this benchmark", - "type": "string" - }, - "unit": { - "description": "The unit for the result", - "enum": [ - "bits/s", - "s" - ] - }, - "result": { - "description": "String encoded result. Parse as float64", - "type": "string" - } - }, - "required": [ - "name", - "unit", - "result", - "implementation", - "stack", - "version" - ] - } + "type": "array", + "items": { + "$ref": "#/definitions/Benchmark" + } + }, + "$schema": { + "type": "string" } + }, + "required": [ + "benchmarks" + ], + "additionalProperties": false }, - "required": [ - "benchmarks", - "productName" - ] + "Benchmark": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "unit": { + "type": "string", + "enum": [ + "bits/s", + "s" + ] + }, + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/Result" + } + }, + "comparisons": { + "type": "array", + "items": { + "$ref": "#/definitions/Comparison" + } + } + }, + "required": [ + "name", + "unit", + "results", + "comparisons" + ], + "additionalProperties": false + }, + "Result": { + "type": "object", + "properties": { + "result": { + "type": "number" + }, + "implementation": { + "type": "string" + }, + "transportStack": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "result", + "implementation", + "transportStack", + "version" + ], + "additionalProperties": false + }, + "Comparison": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "result": { + "type": "number" + } + }, + "required": [ + "name", + "result" + ], + "additionalProperties": false + } + } } diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index 74edd140eb3..c20d2e38077 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -23,19 +23,21 @@ use async_trait::async_trait; use clap::Parser; use colored::*; use futures::{future::Either, StreamExt}; -use instant::Instant; +use instant::{Duration, Instant}; use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multiaddr, Transport}; use libp2p_dns::DnsConfig; use libp2p_identity::PeerId; use libp2p_perf::client::{RunParams, RunStats, RunTimers}; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::info; -use serde::{Deserialize, Serialize}; -schemafy::schemafy!( - root: BenchmarkResults - "src/benchmark-data-schema.json" -); +mod schema { + use serde::{Deserialize, Serialize}; + schemafy::schemafy!( + root: BenchmarkResults + "src/benchmark-data-schema.json" + ); +} #[derive(Debug, Parser)] #[clap(name = "libp2p perf client")] @@ -73,8 +75,9 @@ async fn main() -> Result<()> { println!( "{}", - serde_json::to_string(&BenchmarkResults { + serde_json::to_string(&schema::Benchmarks { benchmarks: results, + schema: None, }) .unwrap() ); @@ -86,10 +89,7 @@ async fn main() -> Result<()> { trait Benchmark { fn name(&self) -> &'static str; - async fn run( - &self, - server_address: Multiaddr, - ) -> Result>; + async fn run(&self, server_address: Multiaddr) -> Result>; } struct Latency {} @@ -100,18 +100,20 @@ impl Benchmark for Latency { "round-trip-time latency" } - async fn run( - &self, - server_address: Multiaddr, - ) -> Result> { + async fn run(&self, server_address: Multiaddr) -> Result> { let mut swarm = swarm().await; let server_peer_id = connect(&mut swarm, server_address.clone()).await?; - let num = 1_000; + let mut rounds = 0; + let start = Instant::now(); let mut latencies = Vec::new(); - for _ in 0..num { + loop { + if start.elapsed() > Duration::from_secs(30) { + break; + } + swarm.behaviour_mut().perf( server_peer_id, RunParams { @@ -147,11 +149,15 @@ impl Benchmark for Latency { }; }; latencies.push(latency); + rounds += 1; } latencies.sort_by(|a, b| a.partial_cmp(b).unwrap()); - info!("Finished: {num} pings",); + info!( + "Finished: {rounds} pings in {:.4}s", + start.elapsed().as_secs_f64() + ); info!("- {:.4} s median", percentile(&latencies, 0.50),); info!("- {:.4} s 95th percentile\n", percentile(&latencies, 0.95),); Ok(None) @@ -171,10 +177,7 @@ impl Benchmark for Throughput { "single connection single channel throughput" } - async fn run( - &self, - server_address: Multiaddr, - ) -> Result> { + async fn run(&self, server_address: Multiaddr) -> Result> { let mut swarm = swarm().await; let server_peer_id = connect(&mut swarm, server_address.clone()).await?; @@ -219,10 +222,16 @@ impl Benchmark for Throughput { info!("- {sent_bandwidth_mebibit_second:.2} MiBit/s up"); info!("- {receive_bandwidth_mebibit_second:.2} MiBit/s down\n"); - Ok(Some(BenchmarkResultsItemBenchmarks { + Ok(Some(schema::Benchmark { name: "Single Connection throughput – Upload".to_string(), - unit: serde_json::Value::String("bits/s".to_string()), - result: (stats.params.to_send as f64 / sent_time).to_string(), + unit: "bits/s".to_string(), + comparisons: vec![], + results: vec![schema::Result { + implementation: "rust-libp2p".to_string(), + transport_stack: "TODO".to_string(), + version: "TODO".to_string(), + result: stats.params.to_send as f64 / sent_time, + }], })) } } @@ -235,15 +244,12 @@ impl Benchmark for RequestsPerSecond { "single connection parallel requests per second" } - async fn run( - &self, - server_address: Multiaddr, - ) -> Result> { + async fn run(&self, server_address: Multiaddr) -> Result> { let mut swarm = swarm().await; let server_peer_id = connect(&mut swarm, server_address.clone()).await?; - let num = 10_000; + let num = 1_000; let to_send = 1; let to_receive = 1; @@ -304,11 +310,8 @@ impl Benchmark for ConnectionsPerSecond { "sequential connections with single request per second" } - async fn run( - &self, - server_address: Multiaddr, - ) -> Result> { - let num = 100; + async fn run(&self, server_address: Multiaddr) -> Result> { + let mut rounds = 0; let to_send = 1; let to_receive = 1; let start = Instant::now(); @@ -316,7 +319,11 @@ impl Benchmark for ConnectionsPerSecond { let mut latency_connection_establishment = Vec::new(); let mut latency_connection_establishment_plus_request = Vec::new(); - for _ in 0..num { + loop { + if start.elapsed() > Duration::from_secs(30) { + break; + } + let mut swarm = swarm().await; let start = Instant::now(); @@ -341,7 +348,8 @@ impl Benchmark for ConnectionsPerSecond { e => panic!("{e:?}"), }; - latency_connection_establishment_plus_request.push(start.elapsed().as_secs_f64()) + latency_connection_establishment_plus_request.push(start.elapsed().as_secs_f64()); + rounds += 1; } let duration = start.elapsed().as_secs_f64(); @@ -354,16 +362,21 @@ impl Benchmark for ConnectionsPerSecond { percentile(&latency_connection_establishment_plus_request, 0.95); info!( - "Finished: established {num} connections with one {to_send} bytes request and one {to_receive} bytes response within {duration:.2} s", + "Finished: established {rounds} connections with one {to_send} bytes request and one {to_receive} bytes response within {duration:.2} s", ); info!("- {connection_establishment_95th:.4} s 95th percentile connection establishment"); info!("- {connection_establishment_plus_request_95th:.4} s 95th percentile connection establishment + one request"); - Ok(Some(BenchmarkResultsItemBenchmarks { - name: "Single Connection 1 byte round trip latency 100 runs 95th percentile" - .to_string(), - unit: serde_json::Value::String("s".to_string()), - result: connection_establishment_plus_request_95th.to_string(), + Ok(Some(schema::Benchmark { + name: "Single Connection 1 byte round trip latency 95th percentile".to_string(), + unit: "s".to_string(), + comparisons: vec![], + results: vec![schema::Result { + implementation: "rust-libp2p".to_string(), + transport_stack: "TODO".to_string(), + version: "TODO".to_string(), + result: connection_establishment_plus_request_95th, + }], })) } } From a01ff2c5f25a15ca52fe8cedce07c71aeef71308 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 11 Apr 2023 11:54:27 +0200 Subject: [PATCH 09/37] Introduce custom benchmark --- protocols/perf/src/bin/perf-client.rs | 121 +++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 14 deletions(-) diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index c20d2e38077..f8baca07bba 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -30,6 +30,7 @@ use libp2p_identity::PeerId; use libp2p_perf::client::{RunParams, RunStats, RunTimers}; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::info; +use serde::{Deserialize, Serialize}; mod schema { use serde::{Deserialize, Serialize}; @@ -44,6 +45,12 @@ mod schema { struct Opts { #[arg(long)] server_address: Multiaddr, + #[arg(long)] + upload_bytes: Option, + #[arg(long)] + download_bytes: Option, + #[arg(long)] + n_times: Option, } #[async_std::main] @@ -52,12 +59,20 @@ async fn main() -> Result<()> { let opts = Opts::parse(); - let benchmarks: [Box; 4] = [ - Box::new(Latency {}), - Box::new(Throughput {}), - Box::new(RequestsPerSecond {}), - Box::new(ConnectionsPerSecond {}), - ]; + let benchmarks: Vec> = if opts.n_times.is_some() { + vec![Box::new(Custom { + upload_bytes: opts.upload_bytes.unwrap(), + download_bytes: opts.download_bytes.unwrap(), + n_times: opts.n_times.unwrap(), + })] + } else { + vec![ + Box::new(Latency {}), + Box::new(Throughput {}), + Box::new(RequestsPerSecond {}), + Box::new(ConnectionsPerSecond {}), + ] + }; let mut results = vec![]; @@ -73,14 +88,16 @@ async fn main() -> Result<()> { } } - println!( - "{}", - serde_json::to_string(&schema::Benchmarks { - benchmarks: results, - schema: None, - }) - .unwrap() - ); + if !results.is_empty() { + println!( + "{}", + serde_json::to_string(&schema::Benchmarks { + benchmarks: results, + schema: None, + }) + .unwrap() + ); + } Ok(()) } @@ -92,6 +109,82 @@ trait Benchmark { async fn run(&self, server_address: Multiaddr) -> Result>; } +struct Custom { + upload_bytes: usize, + download_bytes: usize, + n_times: usize, +} + +#[async_trait] +impl Benchmark for Custom { + fn name(&self) -> &'static str { + "custom" + } + + async fn run(&self, server_address: Multiaddr) -> Result> { + let mut latencies = Vec::new(); + + for _ in 0..self.n_times { + let mut swarm = swarm().await; + + let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + + swarm.behaviour_mut().perf( + server_peer_id, + RunParams { + to_send: 1, + to_receive: 1, + }, + )?; + + let latency = 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::Event { + id: _, + result: + Ok(RunStats { + timers: + RunTimers { + write_start, + read_done, + .. + }, + .. + }), + }) => break read_done.duration_since(write_start).as_secs_f64(), + e => panic!("{e:?}"), + }; + }; + latencies.push(latency); + } + + info!( + "Finished: Established {} connections uploading {} and download {} bytes each", + self.n_times, self.upload_bytes, self.download_bytes + ); + + println!( + "{}", + serde_json::to_string(&CustomResult { latencies }).unwrap() + ); + + Ok(None) + } +} + +#[derive(Serialize, Deserialize)] +struct CustomResult { + latencies: Vec, +} + struct Latency {} #[async_trait] From 8cef2d6a80302d77e94f7c0ec4e566ed7c5896ba Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 11 Apr 2023 13:57:22 +0200 Subject: [PATCH 10/37] Fix forwarding upload and download bytes --- protocols/perf/src/bin/perf-client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index f8baca07bba..a6716216142 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -132,8 +132,8 @@ impl Benchmark for Custom { swarm.behaviour_mut().perf( server_peer_id, RunParams { - to_send: 1, - to_receive: 1, + to_send: self.upload_bytes, + to_receive: self.download_bytes, }, )?; From 095ceb6ca08305d24f0865f6c43cf8ca01cff521 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 11 Apr 2023 13:57:49 +0200 Subject: [PATCH 11/37] Wait for remote to close read side --- protocols/perf/src/protocol.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/protocols/perf/src/protocol.rs b/protocols/perf/src/protocol.rs index 808ea45752b..c7be76cb11c 100644 --- a/protocols/perf/src/protocol.rs +++ b/protocols/perf/src/protocol.rs @@ -54,9 +54,16 @@ pub async fn send_receive( let write_done = Instant::now(); let mut received = 0; - while received < to_receive { - received += stream.read(&mut receive_buf).await?; + loop { + let n = stream.read(&mut receive_buf).await?; + received += n; + // Make sure to wait for the remote to close the stream. Otherwise with `to_receive` of `0` + // one does not measure the full round-trip of the previous write. + if n == 0 { + break; + } } + assert_eq!(received, to_receive); let read_done = Instant::now(); From 001936695b806d3b81a72d6aabd62328e551e19f Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 12 Apr 2023 19:23:31 +0200 Subject: [PATCH 12/37] Provide option to pass secret key seed --- protocols/perf/src/bin/perf-server.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/protocols/perf/src/bin/perf-server.rs b/protocols/perf/src/bin/perf-server.rs index b12972e3c38..35dba3720a9 100644 --- a/protocols/perf/src/bin/perf-server.rs +++ b/protocols/perf/src/bin/perf-server.rs @@ -28,16 +28,20 @@ use log::{error, info}; #[derive(Debug, Parser)] #[clap(name = "libp2p perf server")] -struct Opts {} +struct Opts { + /// Fixed value to generate deterministic peer id. + #[clap(long)] + secret_key_seed: u8, +} #[async_std::main] async fn main() { env_logger::init(); - let _opts = Opts::parse(); + let opts = Opts::parse(); // Create a random PeerId - let local_key = libp2p_identity::Keypair::generate_ed25519(); + let local_key = generate_ed25519(opts.secret_key_seed); let local_peer_id = PeerId::from(local_key.public()); println!("Local peer id: {local_peer_id}"); @@ -126,3 +130,10 @@ async fn main() { } } } + +fn generate_ed25519(secret_key_seed: u8) -> libp2p_identity::Keypair { + let mut bytes = [0u8; 32]; + bytes[0] = secret_key_seed; + + libp2p_identity::Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length") +} From 2a686302e0825bcaa1b96465ee62fa544eabd38e Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 12 Apr 2023 19:23:56 +0200 Subject: [PATCH 13/37] Follow go implementation entry point convention --- protocols/perf/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocols/perf/Dockerfile b/protocols/perf/Dockerfile index aef8eed1cad..131d2325177 100644 --- a/protocols/perf/Dockerfile +++ b/protocols/perf/Dockerfile @@ -16,7 +16,7 @@ RUN --mount=type=cache,target=./target \ FROM debian:bullseye-slim -COPY --from=builder /usr/local/bin/perf-server /usr/local/bin/perf-server -COPY --from=builder /usr/local/bin/perf-client /usr/local/bin/perf-client +COPY --from=builder /usr/local/bin/perf-server /app/server +COPY --from=builder /usr/local/bin/perf-client /app/perf -ENTRYPOINT [ "perf-server"] +ENTRYPOINT [ "/app/perf" ] From d3c3ad4d9d3de2aa6b40354c6998d99b2fcf0952 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 13 Apr 2023 18:37:16 +0200 Subject: [PATCH 14/37] Change to tokio runtime --- Cargo.lock | 17 +++--- protocols/perf/Cargo.toml | 10 ++-- protocols/perf/src/bin/perf-client.rs | 33 ++++++----- protocols/perf/src/bin/perf-server.rs | 83 ++++++++++++++------------- protocols/perf/tests/lib.rs | 4 +- 5 files changed, 76 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce16e2984a9..e3b658a4c52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2697,7 +2697,6 @@ name = "libp2p-perf" version = "0.1.0" dependencies = [ "anyhow", - "async-std", "async-trait", "clap 4.2.1", "colored", @@ -2719,6 +2718,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "tokio", "void", ] @@ -4704,9 +4704,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -4947,14 +4947,13 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.26.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot 0.12.1", @@ -4967,13 +4966,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.11", ] [[package]] diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index 311b83fe488..18a36e9daf0 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -11,8 +11,8 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] +tokio = { version = "1.27.0", features = ["full"] } anyhow = "1" -async-std = { version = "1.9.0", features = ["attributes"] } async-trait = "0.1" clap = { version = "4.2.1", features = ["derive"] } colored = "2" @@ -20,12 +20,12 @@ env_logger = "0.10.0" futures = "0.3.28" instant = "0.1.11" libp2p-core = { version = "0.39.0", path = "../../core" } -libp2p-dns = { version = "0.39.0", path = "../../transports/dns", features = ["async-std"] } +libp2p-dns = { version = "0.39.0", path = "../../transports/dns", features = ["tokio"] } libp2p-identity = { version = "0.1.0", path = "../../identity" } 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-quic = { version = "0.7.0-alpha.2", path = "../../transports/quic", features = ["tokio"] } +libp2p-swarm = { version = "0.42.0", path = "../../swarm", features = ["macros", "tokio"] } +libp2p-tcp = { version = "0.39.0", path = "../../transports/tcp", features = ["tokio"] } libp2p-yamux = { version = "0.43.0", path = "../../muxers/yamux" } log = "0.4" serde = { version = "1.0", features = ["derive"] } diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index a6716216142..97cfaae1a07 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -53,7 +53,7 @@ struct Opts { n_times: Option, } -#[async_std::main] +#[tokio::main] async fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); @@ -74,19 +74,24 @@ async fn main() -> Result<()> { ] }; - let mut results = vec![]; + let results = tokio::spawn(async move { + let mut results = vec![]; - for benchmark in benchmarks { - info!( - "{}", - format!("Start benchmark: {}", benchmark.name()).underline(), - ); + for benchmark in benchmarks { + info!( + "{}", + format!("Start benchmark: {}", benchmark.name()).underline(), + ); - let result = benchmark.run(opts.server_address.clone()).await?; - if let Some(result) = result { - results.push(result); + let result = benchmark.run(opts.server_address.clone()).await?; + if let Some(result) = result { + results.push(result); + } } - } + + anyhow::Ok(results) + }) + .await??; if !results.is_empty() { println!( @@ -103,7 +108,7 @@ async fn main() -> Result<()> { } #[async_trait] -trait Benchmark { +trait Benchmark: Send + Sync + 'static { fn name(&self) -> &'static str; async fn run(&self, server_address: Multiaddr) -> Result>; @@ -492,7 +497,7 @@ async fn swarm() -> Swarm { let quic = { let mut config = libp2p_quic::Config::new(&local_key); config.support_draft_29 = true; - libp2p_quic::async_std::Transport::new(config) + libp2p_quic::tokio::Transport::new(config) }; let dns = DnsConfig::system(OrTransport::new(quic, tcp)) @@ -506,7 +511,7 @@ async fn swarm() -> Swarm { .boxed() }; - SwarmBuilder::with_async_std_executor( + SwarmBuilder::with_tokio_executor( transport, libp2p_perf::client::Behaviour::default(), local_peer_id, diff --git a/protocols/perf/src/bin/perf-server.rs b/protocols/perf/src/bin/perf-server.rs index 35dba3720a9..66a424e3bd1 100644 --- a/protocols/perf/src/bin/perf-server.rs +++ b/protocols/perf/src/bin/perf-server.rs @@ -34,7 +34,7 @@ struct Opts { secret_key_seed: u8, } -#[async_std::main] +#[tokio::main] async fn main() { env_logger::init(); @@ -46,19 +46,18 @@ async fn main() { println!("Local peer id: {local_peer_id}"); 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 tcp = libp2p_tcp::tokio::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) + libp2p_quic::tokio::Transport::new(config) }; let dns = DnsConfig::system(OrTransport::new(quic, tcp)) @@ -72,7 +71,7 @@ async fn main() { .boxed() }; - let mut swarm = SwarmBuilder::with_async_std_executor( + let mut swarm = SwarmBuilder::with_tokio_executor( transport, libp2p_perf::server::Behaviour::default(), local_peer_id, @@ -88,34 +87,35 @@ async fn main() { .listen_on("/ip4/0.0.0.0/udp/4001/quic-v1".parse().unwrap()) .unwrap(); - loop { - match swarm.next().await.unwrap() { - SwarmEvent::NewListenAddr { address, .. } => { - info!("Listening on {address}"); - } - SwarmEvent::IncomingConnection { .. } => {} - e @ SwarmEvent::IncomingConnectionError { .. } => { - error!("{e:?}"); - } - SwarmEvent::ConnectionEstablished { - peer_id, endpoint, .. - } => { - info!("Established connection to {:?} via {:?}", peer_id, endpoint); - } - SwarmEvent::ConnectionClosed { .. } => {} - SwarmEvent::Behaviour(libp2p_perf::server::Event { - 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!( + tokio::spawn(async move { + loop { + match swarm.next().await.unwrap() { + SwarmEvent::NewListenAddr { address, .. } => { + info!("Listening on {address}"); + } + SwarmEvent::IncomingConnection { .. } => {} + e @ SwarmEvent::IncomingConnectionError { .. } => { + error!("{e:?}"); + } + SwarmEvent::ConnectionEstablished { + peer_id, endpoint, .. + } => { + info!("Established connection to {:?} via {:?}", peer_id, endpoint); + } + SwarmEvent::ConnectionClosed { .. } => {} + SwarmEvent::Behaviour(libp2p_perf::server::Event { + 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 {:.2} MiB in {:.2} s with {:.2} MiBit/s and sent {:.2} MiB in {:.2} s with {:.2} MiBit/s", remote_peer_id, received_mebibytes, @@ -125,10 +125,11 @@ async fn main() { sent_time, sent_bandwidth_mebibit_second, ) + } + e => panic!("{e:?}"), } - e => panic!("{e:?}"), } - } + }).await.unwrap(); } fn generate_ed25519(secret_key_seed: u8) -> libp2p_identity::Keypair { diff --git a/protocols/perf/tests/lib.rs b/protocols/perf/tests/lib.rs index 1d93ce3ee8d..e448fd01c43 100644 --- a/protocols/perf/tests/lib.rs +++ b/protocols/perf/tests/lib.rs @@ -25,7 +25,7 @@ use libp2p_perf::{ use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; -#[async_std::test] +#[tokio::test] async fn perf() { let _ = env_logger::try_init(); @@ -36,7 +36,7 @@ async fn perf() { server.listen().await; client.connect(&mut server).await; - async_std::task::spawn(server.loop_on_next()); + tokio::spawn(server.loop_on_next()); client .behaviour_mut() From 285a17576e8497380e81df4f2692c69fe351b3c2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 13 Apr 2023 19:44:54 +0200 Subject: [PATCH 15/37] Fix DNS imports --- protocols/perf/src/bin/perf-client.rs | 21 +++++++++------------ protocols/perf/src/bin/perf-server.rs | 6 ++---- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index 97cfaae1a07..1f0095af08f 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -25,7 +25,7 @@ use colored::*; use futures::{future::Either, StreamExt}; use instant::{Duration, Instant}; use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multiaddr, Transport}; -use libp2p_dns::DnsConfig; +use libp2p_dns::TokioDnsConfig; use libp2p_identity::PeerId; use libp2p_perf::client::{RunParams, RunStats, RunTimers}; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; @@ -485,14 +485,13 @@ async fn swarm() -> Swarm { 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 tcp = libp2p_tcp::tokio::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); @@ -500,9 +499,7 @@ async fn swarm() -> Swarm { libp2p_quic::tokio::Transport::new(config) }; - let dns = DnsConfig::system(OrTransport::new(quic, tcp)) - .await - .unwrap(); + let dns = TokioDnsConfig::system(OrTransport::new(quic, tcp)).unwrap(); dns.map(|either_output, _| match either_output { Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), diff --git a/protocols/perf/src/bin/perf-server.rs b/protocols/perf/src/bin/perf-server.rs index 66a424e3bd1..9758ad20a66 100644 --- a/protocols/perf/src/bin/perf-server.rs +++ b/protocols/perf/src/bin/perf-server.rs @@ -21,7 +21,7 @@ use clap::Parser; use futures::{future::Either, StreamExt}; use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Transport}; -use libp2p_dns::DnsConfig; +use libp2p_dns::TokioDnsConfig; use libp2p_identity::PeerId; use libp2p_swarm::{SwarmBuilder, SwarmEvent}; use log::{error, info}; @@ -60,9 +60,7 @@ async fn main() { libp2p_quic::tokio::Transport::new(config) }; - let dns = DnsConfig::system(OrTransport::new(quic, tcp)) - .await - .unwrap(); + let dns = TokioDnsConfig::system(OrTransport::new(quic, tcp)).unwrap(); dns.map(|either_output, _| match either_output { Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), From cc064f5b998e1e2c8e36b1f6865e90fb95fad36a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 14 Apr 2023 14:39:57 +0200 Subject: [PATCH 16/37] Use nodelay don't reuse port --- protocols/perf/src/bin/perf-client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf-client.rs index 1f0095af08f..082bc5c8253 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf-client.rs @@ -485,7 +485,7 @@ async fn swarm() -> Swarm { let local_peer_id = PeerId::from(local_key.public()); let transport = { - let tcp = libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default().port_reuse(true)) + let tcp = libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default().nodelay(true)) .upgrade(upgrade::Version::V1Lazy) .authenticate( libp2p_noise::NoiseAuthenticated::xx(&local_key) From 4cde5b5da1c022172cb4234db2b15ef5adbffcd3 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 14 Apr 2023 14:43:52 +0200 Subject: [PATCH 17/37] Increase read buffer --- protocols/perf/src/protocol.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/protocols/perf/src/protocol.rs b/protocols/perf/src/protocol.rs index c7be76cb11c..cf89ea644b6 100644 --- a/protocols/perf/src/protocol.rs +++ b/protocols/perf/src/protocol.rs @@ -24,7 +24,7 @@ use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use crate::{client, server}; -const BUF: [u8; 1024] = [0; 1024]; +const BUF: [u8; 65536] = [0; 64 << 10]; pub async fn send_receive( params: client::RunParams, @@ -35,7 +35,7 @@ pub async fn send_receive( to_receive, } = params; - let mut receive_buf = vec![0; 1024]; + let mut receive_buf = vec![0; 64 << 10]; stream.write_all(&(to_receive as u64).to_be_bytes()).await?; @@ -77,6 +77,8 @@ pub async fn send_receive( pub async fn receive_send( mut stream: S, ) -> Result { + let mut receive_buf = vec![0; 64 << 10]; + let to_send = { let mut buf = [0; 8]; stream.read_exact(&mut buf).await?; @@ -86,7 +88,6 @@ pub async fn receive_send( let read_start = Instant::now(); - let mut receive_buf = vec![0; 1024]; let mut received = 0; loop { let n = stream.read(&mut receive_buf).await?; From 3aec5e540e22c7be5eb30267bc4d2a197ad9cda3 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 14 Apr 2023 17:16:36 +0200 Subject: [PATCH 18/37] Set TCP nodelay on server --- protocols/perf/src/bin/perf-server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/perf/src/bin/perf-server.rs b/protocols/perf/src/bin/perf-server.rs index 9758ad20a66..1e042d185a1 100644 --- a/protocols/perf/src/bin/perf-server.rs +++ b/protocols/perf/src/bin/perf-server.rs @@ -46,7 +46,7 @@ async fn main() { println!("Local peer id: {local_peer_id}"); let transport = { - let tcp = libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default().port_reuse(true)) + let tcp = libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default().nodelay(true)) .upgrade(upgrade::Version::V1Lazy) .authenticate( libp2p_noise::NoiseAuthenticated::xx(&local_key) From cef0f2355aafa41911fbdbcba17ca8a4cb7871d6 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 24 Apr 2023 08:44:29 +0200 Subject: [PATCH 19/37] Merge server and client into single entry point --- protocols/perf/Dockerfile | 8 +- protocols/perf/src/bin/perf-server.rs | 138 ---------------- .../perf/src/bin/{perf-client.rs => perf.rs} | 151 +++++++++++++++++- 3 files changed, 146 insertions(+), 151 deletions(-) delete mode 100644 protocols/perf/src/bin/perf-server.rs rename protocols/perf/src/bin/{perf-client.rs => perf.rs} (76%) diff --git a/protocols/perf/Dockerfile b/protocols/perf/Dockerfile index 131d2325177..6523e3bede1 100644 --- a/protocols/perf/Dockerfile +++ b/protocols/perf/Dockerfile @@ -9,14 +9,10 @@ RUN --mount=type=cache,target=./target \ cargo build --release --package libp2p-perf RUN --mount=type=cache,target=./target \ - mv ./target/release/perf-server /usr/local/bin/perf-server - -RUN --mount=type=cache,target=./target \ - mv ./target/release/perf-client /usr/local/bin/perf-client + mv ./target/release/perf /usr/local/bin/perf FROM debian:bullseye-slim -COPY --from=builder /usr/local/bin/perf-server /app/server -COPY --from=builder /usr/local/bin/perf-client /app/perf +COPY --from=builder /usr/local/bin/perf /app/perf ENTRYPOINT [ "/app/perf" ] diff --git a/protocols/perf/src/bin/perf-server.rs b/protocols/perf/src/bin/perf-server.rs deleted file mode 100644 index 1e042d185a1..00000000000 --- a/protocols/perf/src/bin/perf-server.rs +++ /dev/null @@ -1,138 +0,0 @@ -// 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::{future::Either, StreamExt}; -use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Transport}; -use libp2p_dns::TokioDnsConfig; -use libp2p_identity::PeerId; -use libp2p_swarm::{SwarmBuilder, SwarmEvent}; -use log::{error, info}; - -#[derive(Debug, Parser)] -#[clap(name = "libp2p perf server")] -struct Opts { - /// Fixed value to generate deterministic peer id. - #[clap(long)] - secret_key_seed: u8, -} - -#[tokio::main] -async fn main() { - env_logger::init(); - - let opts = Opts::parse(); - - // Create a random PeerId - let local_key = generate_ed25519(opts.secret_key_seed); - let local_peer_id = PeerId::from(local_key.public()); - println!("Local peer id: {local_peer_id}"); - - let transport = { - let tcp = libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default().nodelay(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::tokio::Transport::new(config) - }; - - let dns = TokioDnsConfig::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_tokio_executor( - transport, - libp2p_perf::server::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(); - - tokio::spawn(async move { - loop { - match swarm.next().await.unwrap() { - SwarmEvent::NewListenAddr { address, .. } => { - info!("Listening on {address}"); - } - SwarmEvent::IncomingConnection { .. } => {} - e @ SwarmEvent::IncomingConnectionError { .. } => { - error!("{e:?}"); - } - SwarmEvent::ConnectionEstablished { - peer_id, endpoint, .. - } => { - info!("Established connection to {:?} via {:?}", peer_id, endpoint); - } - SwarmEvent::ConnectionClosed { .. } => {} - SwarmEvent::Behaviour(libp2p_perf::server::Event { - 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 {:.2} MiB in {:.2} s with {:.2} MiBit/s and sent {:.2} MiB in {:.2} s with {:.2} MiBit/s", - remote_peer_id, - received_mebibytes, - receive_time, - receive_bandwidth_mebibit_second, - sent_mebibytes, - sent_time, - sent_bandwidth_mebibit_second, - ) - } - e => panic!("{e:?}"), - } - } - }).await.unwrap(); -} - -fn generate_ed25519(secret_key_seed: u8) -> libp2p_identity::Keypair { - let mut bytes = [0u8; 32]; - bytes[0] = secret_key_seed; - - libp2p_identity::Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length") -} diff --git a/protocols/perf/src/bin/perf-client.rs b/protocols/perf/src/bin/perf.rs similarity index 76% rename from protocols/perf/src/bin/perf-client.rs rename to protocols/perf/src/bin/perf.rs index 082bc5c8253..7e90846264d 100644 --- a/protocols/perf/src/bin/perf-client.rs +++ b/protocols/perf/src/bin/perf.rs @@ -29,7 +29,7 @@ use libp2p_dns::TokioDnsConfig; use libp2p_identity::PeerId; use libp2p_perf::client::{RunParams, RunStats, RunTimers}; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; -use log::info; +use log::{error, info}; use serde::{Deserialize, Serialize}; mod schema { @@ -44,13 +44,20 @@ mod schema { #[clap(name = "libp2p perf client")] struct Opts { #[arg(long)] - server_address: Multiaddr, + server_address: Option, #[arg(long)] upload_bytes: Option, #[arg(long)] download_bytes: Option, #[arg(long)] n_times: Option, + + /// Run in server mode. + #[clap(long)] + run_server: bool, + /// Fixed value to generate deterministic peer id. + #[clap(long)] + secret_key_seed: Option, } #[tokio::main] @@ -58,12 +65,135 @@ async fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let opts = Opts::parse(); + match opts { + Opts { + server_address: None, + upload_bytes: None, + download_bytes: None, + n_times: None, + run_server: true, + secret_key_seed: Some(secret_key_seed), + } => server(secret_key_seed).await?, + Opts { + server_address: Some(server_address), + upload_bytes, + download_bytes, + n_times, + run_server: false, + secret_key_seed: None, + } => { + client(server_address, upload_bytes, download_bytes, n_times).await?; + } + _ => panic!("invalid command line arguments: {opts:?}"), + }; - let benchmarks: Vec> = if opts.n_times.is_some() { + Ok(()) +} + +async fn server(secret_key_seed: u8) -> Result<()> { + // Create a random PeerId + let local_key = generate_ed25519(secret_key_seed); + let local_peer_id = PeerId::from(local_key.public()); + println!("Local peer id: {local_peer_id}"); + + let transport = { + let tcp = libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default().nodelay(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::tokio::Transport::new(config) + }; + + let dns = TokioDnsConfig::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_tokio_executor( + transport, + libp2p_perf::server::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(); + + tokio::spawn(async move { + loop { + match swarm.next().await.unwrap() { + SwarmEvent::NewListenAddr { address, .. } => { + info!("Listening on {address}"); + } + SwarmEvent::IncomingConnection { .. } => {} + e @ SwarmEvent::IncomingConnectionError { .. } => { + error!("{e:?}"); + } + SwarmEvent::ConnectionEstablished { + peer_id, endpoint, .. + } => { + info!("Established connection to {:?} via {:?}", peer_id, endpoint); + } + SwarmEvent::ConnectionClosed { .. } => {} + SwarmEvent::Behaviour(libp2p_perf::server::Event { + 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 {:.2} MiB in {:.2} s with {:.2} MiBit/s and sent {:.2} MiB in {:.2} s with {:.2} MiBit/s", + remote_peer_id, + received_mebibytes, + receive_time, + receive_bandwidth_mebibit_second, + sent_mebibytes, + sent_time, + sent_bandwidth_mebibit_second, + ) + } + e => panic!("{e:?}"), + } + } + }).await.unwrap(); + + Ok(()) +} + +async fn client( + server_address: Multiaddr, + upload_bytes: Option, + download_bytes: Option, + n_times: Option, +) -> Result<()> { + let benchmarks: Vec> = if n_times.is_some() { vec![Box::new(Custom { - upload_bytes: opts.upload_bytes.unwrap(), - download_bytes: opts.download_bytes.unwrap(), - n_times: opts.n_times.unwrap(), + upload_bytes: upload_bytes.unwrap(), + download_bytes: download_bytes.unwrap(), + n_times: n_times.unwrap(), })] } else { vec![ @@ -83,7 +213,7 @@ async fn main() -> Result<()> { format!("Start benchmark: {}", benchmark.name()).underline(), ); - let result = benchmark.run(opts.server_address.clone()).await?; + let result = benchmark.run(server_address.clone()).await?; if let Some(result) = result { results.push(result); } @@ -535,3 +665,10 @@ async fn connect( Ok(server_peer_id) } + +fn generate_ed25519(secret_key_seed: u8) -> libp2p_identity::Keypair { + let mut bytes = [0u8; 32]; + bytes[0] = secret_key_seed; + + libp2p_identity::Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length") +} From 66a28b37d8deb4d0e38e61f5d3ca0d34368f5770 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 24 Apr 2023 10:14:33 +0200 Subject: [PATCH 20/37] Run n times on same connection --- protocols/perf/src/bin/perf.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 7e90846264d..71f6b324121 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -259,11 +259,11 @@ impl Benchmark for Custom { async fn run(&self, server_address: Multiaddr) -> Result> { let mut latencies = Vec::new(); - for _ in 0..self.n_times { - let mut swarm = swarm().await; + let mut swarm = swarm().await; - let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + for _ in 0..self.n_times { swarm.behaviour_mut().perf( server_peer_id, RunParams { From da5cc28cacdc3aaba8d947345c6e650c1f12b29a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 24 Apr 2023 11:23:20 +0200 Subject: [PATCH 21/37] Fix info log --- protocols/perf/src/bin/perf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 71f6b324121..91b1148884c 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -302,7 +302,7 @@ impl Benchmark for Custom { } info!( - "Finished: Established {} connections uploading {} and download {} bytes each", + "Finished: Sent {} perf requests on single connection uploading {} and download {} bytes each", self.n_times, self.upload_bytes, self.download_bytes ); From 90964ffeace98cb7a8bd7ee17170091d1ce04950 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 1 May 2023 11:36:38 +0900 Subject: [PATCH 22/37] Use request-response and adjust to new flags --- Cargo.lock | 1 + protocols/perf/Cargo.toml | 5 +- protocols/perf/src/bin/perf.rs | 273 +++++++++++++----------- protocols/perf/src/client.rs | 238 +++++++++++++++++++-- protocols/perf/src/client/behaviour.rs | 157 -------------- protocols/perf/src/client/handler.rs | 209 ------------------ protocols/perf/src/lib.rs | 10 + protocols/perf/src/protocol.rs | 282 ++++++++++--------------- protocols/perf/src/server.rs | 153 ++++++++++++-- protocols/perf/src/server/behaviour.rs | 120 ----------- protocols/perf/src/server/handler.rs | 159 -------------- protocols/perf/tests/lib.rs | 5 +- 12 files changed, 619 insertions(+), 993 deletions(-) delete mode 100644 protocols/perf/src/client/behaviour.rs delete mode 100644 protocols/perf/src/client/handler.rs delete mode 100644 protocols/perf/src/server/behaviour.rs delete mode 100644 protocols/perf/src/server/handler.rs diff --git a/Cargo.lock b/Cargo.lock index e3b658a4c52..d9d7f69524a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2708,6 +2708,7 @@ dependencies = [ "libp2p-identity", "libp2p-noise", "libp2p-quic", + "libp2p-request-response", "libp2p-swarm", "libp2p-swarm-test", "libp2p-tcp", diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index 18a36e9daf0..03f648ab241 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -11,7 +11,6 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -tokio = { version = "1.27.0", features = ["full"] } anyhow = "1" async-trait = "0.1" clap = { version = "4.2.1", features = ["derive"] } @@ -24,14 +23,16 @@ libp2p-dns = { version = "0.39.0", path = "../../transports/dns", features = ["t libp2p-identity = { version = "0.1.0", path = "../../identity" } libp2p-noise = { version = "0.42.0", path = "../../transports/noise" } libp2p-quic = { version = "0.7.0-alpha.2", path = "../../transports/quic", features = ["tokio"] } +libp2p-request-response = { version = "0.24.0", path = "../request-response" } libp2p-swarm = { version = "0.42.0", path = "../../swarm", features = ["macros", "tokio"] } libp2p-tcp = { version = "0.39.0", path = "../../transports/tcp", features = ["tokio"] } libp2p-yamux = { version = "0.43.0", path = "../../muxers/yamux" } log = "0.4" +schemafy = "0.6.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -schemafy = "0.6.0" thiserror = "1.0" +tokio = { version = "1.27.0", features = ["full"] } void = "1" [dev-dependencies] diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 91b1148884c..3c9abbb8d17 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -18,16 +18,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{net::Ipv4Addr, str::FromStr}; + use anyhow::{bail, Result}; use async_trait::async_trait; use clap::Parser; use colored::*; use futures::{future::Either, StreamExt}; use instant::{Duration, Instant}; -use libp2p_core::{muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multiaddr, Transport}; +use libp2p_core::{ + multiaddr::Protocol, muxing::StreamMuxerBox, transport::OrTransport, upgrade, Multiaddr, + Transport as _, +}; use libp2p_dns::TokioDnsConfig; use libp2p_identity::PeerId; -use libp2p_perf::client::{RunParams, RunStats, RunTimers}; +use libp2p_perf::RunParams; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::{error, info}; use serde::{Deserialize, Serialize}; @@ -44,7 +49,9 @@ mod schema { #[clap(name = "libp2p perf client")] struct Opts { #[arg(long)] - server_address: Option, + server_ip_address: Option, + #[arg(long)] + transport: Option, #[arg(long)] upload_bytes: Option, #[arg(long)] @@ -60,6 +67,25 @@ struct Opts { secret_key_seed: Option, } +/// Supported transports by rust-libp2p. +#[derive(Clone, Debug)] +pub enum Transport { + Tcp, + QuicV1, +} + +impl FromStr for Transport { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "tcp" => Self::Tcp, + "quic-v1" => Self::QuicV1, + other => bail!("unknown transport {other}"), + }) + } +} + #[tokio::main] async fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); @@ -67,7 +93,8 @@ async fn main() -> Result<()> { let opts = Opts::parse(); match opts { Opts { - server_address: None, + server_ip_address: None, + transport: None, upload_bytes: None, download_bytes: None, n_times: None, @@ -75,14 +102,22 @@ async fn main() -> Result<()> { secret_key_seed: Some(secret_key_seed), } => server(secret_key_seed).await?, Opts { - server_address: Some(server_address), + server_ip_address: Some(server_ip_address), + transport: Some(transport), upload_bytes, download_bytes, n_times, run_server: false, secret_key_seed: None, } => { - client(server_address, upload_bytes, download_bytes, n_times).await?; + client( + server_ip_address, + transport, + upload_bytes, + download_bytes, + n_times, + ) + .await?; } _ => panic!("invalid command line arguments: {opts:?}"), }; @@ -152,44 +187,27 @@ async fn server(secret_key_seed: u8) -> Result<()> { info!("Established connection to {:?} via {:?}", peer_id, endpoint); } SwarmEvent::ConnectionClosed { .. } => {} - SwarmEvent::Behaviour(libp2p_perf::server::Event { - 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 {:.2} MiB in {:.2} s with {:.2} MiBit/s and sent {:.2} MiB in {:.2} s with {:.2} MiBit/s", - remote_peer_id, - received_mebibytes, - receive_time, - receive_bandwidth_mebibit_second, - sent_mebibytes, - sent_time, - sent_bandwidth_mebibit_second, - ) + SwarmEvent::Behaviour(()) => { + info!("Finished run",) } e => panic!("{e:?}"), } } - }).await.unwrap(); + }) + .await + .unwrap(); Ok(()) } async fn client( - server_address: Multiaddr, + server_ip_address: Ipv4Addr, + transport: Transport, upload_bytes: Option, download_bytes: Option, n_times: Option, ) -> Result<()> { - let benchmarks: Vec> = if n_times.is_some() { + let benchmarks: Vec> = if upload_bytes.is_some() { vec![Box::new(Custom { upload_bytes: upload_bytes.unwrap(), download_bytes: download_bytes.unwrap(), @@ -198,12 +216,22 @@ async fn client( } else { vec![ Box::new(Latency {}), - Box::new(Throughput {}), + // Box::new(Throughput {}), Box::new(RequestsPerSecond {}), Box::new(ConnectionsPerSecond {}), ] }; + let address = match transport { + Transport::Tcp => Multiaddr::empty() + .with(Protocol::Ip4(server_ip_address)) + .with(Protocol::Tcp(4001)), + Transport::QuicV1 => Multiaddr::empty() + .with(Protocol::Ip4(server_ip_address)) + .with(Protocol::Udp(4001)) + .with(Protocol::QuicV1), + }; + let results = tokio::spawn(async move { let mut results = vec![]; @@ -213,7 +241,7 @@ async fn client( format!("Start benchmark: {}", benchmark.name()).underline(), ); - let result = benchmark.run(server_address.clone()).await?; + let result = benchmark.run(address.clone()).await?; if let Some(result) = result { results.push(result); } @@ -264,6 +292,8 @@ impl Benchmark for Custom { let server_peer_id = connect(&mut swarm, server_address.clone()).await?; for _ in 0..self.n_times { + let start = Instant::now(); + swarm.behaviour_mut().perf( server_peer_id, RunParams { @@ -272,7 +302,7 @@ impl Benchmark for Custom { }, )?; - let latency = loop { + loop { match swarm.next().await.unwrap() { SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. @@ -284,21 +314,13 @@ impl Benchmark for Custom { } SwarmEvent::Behaviour(libp2p_perf::client::Event { id: _, - result: - Ok(RunStats { - timers: - RunTimers { - write_start, - read_done, - .. - }, - .. - }), - }) => break read_done.duration_since(write_start).as_secs_f64(), + result: Ok(()), + }) => break, e => panic!("{e:?}"), }; - }; - latencies.push(latency); + } + + latencies.push(start.elapsed().as_secs_f64()); } info!( @@ -342,6 +364,8 @@ impl Benchmark for Latency { break; } + let start = Instant::now(); + swarm.behaviour_mut().perf( server_peer_id, RunParams { @@ -350,7 +374,7 @@ impl Benchmark for Latency { }, )?; - let latency = loop { + loop { match swarm.next().await.unwrap() { SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. @@ -362,21 +386,12 @@ impl Benchmark for Latency { } SwarmEvent::Behaviour(libp2p_perf::client::Event { id: _, - result: - Ok(RunStats { - timers: - RunTimers { - write_start, - read_done, - .. - }, - .. - }), - }) => break read_done.duration_since(write_start).as_secs_f64(), + result: Ok(()), + }) => break, e => panic!("{e:?}"), }; - }; - latencies.push(latency); + } + latencies.push(start.elapsed().as_secs_f64()); rounds += 1; } @@ -397,72 +412,76 @@ fn percentile(values: &[V], percentile: f64) -> V { values[n] } -struct Throughput {} - -#[async_trait] -impl Benchmark for Throughput { - fn name(&self) -> &'static str { - "single connection single channel throughput" - } - - async fn run(&self, server_address: Multiaddr) -> Result> { - let mut swarm = swarm().await; - - let server_peer_id = connect(&mut swarm, server_address.clone()).await?; - - swarm.behaviour_mut().perf( - server_peer_id, - RunParams { - to_send: 10 * 1024 * 1024, - to_receive: 10 * 1024 * 1024, - }, - )?; - - let stats = 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::Event { id: _, result }) => { - break result? - } - e => panic!("{e:?}"), - } - }; - - 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; - - info!( - "Finished: sent {sent_mebibytes:.2} MiB in {sent_time:.2} s \ - and received {received_mebibytes:.2} MiB in {receive_time:.2} s", - ); - info!("- {sent_bandwidth_mebibit_second:.2} MiBit/s up"); - info!("- {receive_bandwidth_mebibit_second:.2} MiBit/s down\n"); - - Ok(Some(schema::Benchmark { - name: "Single Connection throughput – Upload".to_string(), - unit: "bits/s".to_string(), - comparisons: vec![], - results: vec![schema::Result { - implementation: "rust-libp2p".to_string(), - transport_stack: "TODO".to_string(), - version: "TODO".to_string(), - result: stats.params.to_send as f64 / sent_time, - }], - })) - } -} +// // TODO: Bring back? +// struct Throughput {} +// +// #[async_trait] +// impl Benchmark for Throughput { +// fn name(&self) -> &'static str { +// "single connection single channel throughput" +// } +// +// async fn run(&self, server_address: Multiaddr) -> Result> { +// let mut swarm = swarm().await; +// +// let server_peer_id = connect(&mut swarm, server_address.clone()).await?; +// +// let params = +// RunParams { +// to_send: 10 * 1024 * 1024, +// to_receive: 10 * 1024 * 1024, +// }; +// +// swarm.behaviour_mut().perf( +// server_peer_id, +// params, +// )?; +// +// let stats = 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::Event { id: _, result }) => { +// break result? +// } +// e => panic!("{e:?}"), +// } +// }; +// +// let sent_mebibytes = 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 = 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; +// +// info!( +// "Finished: sent {sent_mebibytes:.2} MiB \ +// and received {received_mebibytes:.2} MiB in {time:.2} s", +// ); +// info!("- {sent_bandwidth_mebibit_second:.2} MiBit/s up"); +// info!("- {receive_bandwidth_mebibit_second:.2} MiBit/s down\n"); +// +// Ok(Some(schema::Benchmark { +// name: "Single Connection throughput – Upload".to_string(), +// unit: "bits/s".to_string(), +// comparisons: vec![], +// results: vec![schema::Result { +// implementation: "rust-libp2p".to_string(), +// transport_stack: "TODO".to_string(), +// version: "TODO".to_string(), +// result: stats.params.to_send as f64 / sent_time, +// }], +// })) +// } +// } struct RequestsPerSecond {} diff --git a/protocols/perf/src/client.rs b/protocols/perf/src/client.rs index c320b18ea32..e8bc6024644 100644 --- a/protocols/perf/src/client.rs +++ b/protocols/perf/src/client.rs @@ -18,20 +18,22 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -mod behaviour; -mod handler; - use instant::Instant; -use std::sync::atomic::{AtomicUsize, Ordering}; -pub use behaviour::{Behaviour, Event}; +use std::{ + collections::{HashMap, HashSet}, + task::{Context, Poll}, +}; -/// Parameters for a single run, i.e. one stream, sending and receiving data. -#[derive(Debug, Clone, Copy)] -pub struct RunParams { - pub to_send: usize, - pub to_receive: usize, -} +use libp2p_core::Multiaddr; +use libp2p_identity::PeerId; +use libp2p_request_response as request_response; +use libp2p_swarm::{ + derive_prelude::ConnectionEstablished, ConnectionClosed, ConnectionId, FromSwarm, + NetworkBehaviour, PollParameters, THandlerInEvent, THandlerOutEvent, ToSwarm, +}; + +use crate::RunParams; /// Timers for a single run, i.e. one stream, sending and receiving data. #[derive(Debug, Clone, Copy)] @@ -41,22 +43,212 @@ pub struct RunTimers { pub read_done: Instant, } -/// Statistics for a single run, i.e. one stream, sending and receiving data. +/// Connection identifier. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct RunId(request_response::RequestId); + +impl From for RunId { + fn from(value: request_response::RequestId) -> Self { + Self(value) + } +} + #[derive(Debug)] -pub struct RunStats { - pub params: RunParams, - pub timers: RunTimers, +pub struct Event { + pub id: RunId, + pub result: Result<(), request_response::OutboundFailure>, } -static NEXT_RUN_ID: AtomicUsize = AtomicUsize::new(1); +pub struct Behaviour { + connected: HashSet, + in_progress_runs: HashMap, + request_response: request_response::Behaviour, +} -/// Connection identifier. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct RunId(usize); +impl Default for Behaviour { + fn default() -> Self { + Self { + connected: Default::default(), + in_progress_runs: Default::default(), + request_response: request_response::Behaviour::new( + crate::protocol::Codec {}, + std::iter::once(( + crate::PROTOCOL_NAME, + request_response::ProtocolSupport::Outbound, + )), + Default::default(), + ), + } + } +} + +impl Behaviour { + pub fn new() -> Self { + Self::default() + } + + pub fn perf(&mut self, server: PeerId, params: RunParams) -> Result { + if !self.connected.contains(&server) { + return Err(PerfError::NotConnected); + } + + let id = self.request_response.send_request(&server, params).into(); + + self.in_progress_runs.insert(id, params); + + Ok(id) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum PerfError { + #[error("Not connected to peer")] + NotConnected, +} + +impl NetworkBehaviour for Behaviour { + type ConnectionHandler = + as NetworkBehaviour>::ConnectionHandler; + type OutEvent = Event; + + fn handle_pending_outbound_connection( + &mut self, + connection_id: ConnectionId, + maybe_peer: Option, + addresses: &[Multiaddr], + effective_role: libp2p_core::Endpoint, + ) -> Result, libp2p_swarm::ConnectionDenied> { + self.request_response.handle_pending_outbound_connection( + connection_id, + maybe_peer, + addresses, + effective_role, + ) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: libp2p_core::Endpoint, + ) -> Result, libp2p_swarm::ConnectionDenied> { + self.request_response + .handle_established_outbound_connection(connection_id, peer, addr, role_override) + } + + fn handle_pending_inbound_connection( + &mut self, + connection_id: ConnectionId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<(), libp2p_swarm::ConnectionDenied> { + self.request_response.handle_pending_inbound_connection( + connection_id, + local_addr, + remote_addr, + ) + } + + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, libp2p_swarm::ConnectionDenied> { + self.request_response.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id, .. }) => { + self.connected.insert(peer_id); + } + FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id: _, + endpoint: _, + handler: _, + remaining_established, + }) => { + if remaining_established == 0 { + assert!(self.connected.remove(&peer_id)); + } + } + FromSwarm::AddressChange(_) + | FromSwarm::DialFailure(_) + | FromSwarm::ListenFailure(_) + | FromSwarm::NewListener(_) + | FromSwarm::NewListenAddr(_) + | FromSwarm::ExpiredListenAddr(_) + | FromSwarm::ListenerError(_) + | FromSwarm::ListenerClosed(_) + | FromSwarm::NewExternalAddr(_) + | FromSwarm::ExpiredExternalAddr(_) => {} + }; + + self.request_response.on_swarm_event(event); + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + connection_id: ConnectionId, + event: THandlerOutEvent, + ) { + self.request_response + .on_connection_handler_event(peer_id, connection_id, event); + } -impl RunId { - /// Returns the next available [`RunId`]. - pub(crate) fn next() -> Self { - Self(NEXT_RUN_ID.fetch_add(1, Ordering::SeqCst)) + fn poll( + &mut self, + cx: &mut Context<'_>, + params: &mut impl PollParameters, + ) -> Poll>> { + self.request_response.poll(cx, params).map(|to_swarm| { + to_swarm.map_out(|m| match m { + request_response::Event::Message { + peer: _, + message: + request_response::Message::Response { + request_id, + response, + }, + } => { + let params = self.in_progress_runs.remove(&request_id.into()).unwrap(); + assert_eq!(params.to_receive, response); + Event { + id: request_id.into(), + result: Ok(()), + } + } + request_response::Event::Message { + peer: _, + message: request_response::Message::Request { .. }, + } => { + unreachable!() + } + request_response::Event::OutboundFailure { + peer: _, + request_id, + error, + } => Event { + id: request_id.into(), + result: Err(error), + }, + request_response::Event::InboundFailure { + peer: _, + request_id: _, + error: _, + } => unreachable!(), + request_response::Event::ResponseSent { .. } => unreachable!(), + }) + }) } } diff --git a/protocols/perf/src/client/behaviour.rs b/protocols/perf/src/client/behaviour.rs deleted file mode 100644 index dade91c5a7f..00000000000 --- a/protocols/perf/src/client/behaviour.rs +++ /dev/null @@ -1,157 +0,0 @@ -// 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. - -//! [`NetworkBehaviour`] of the libp2p perf client protocol. - -use std::{ - collections::{HashSet, VecDeque}, - task::{Context, Poll}, -}; - -use libp2p_core::Multiaddr; -use libp2p_identity::PeerId; -use libp2p_swarm::{ - derive_prelude::ConnectionEstablished, ConnectionClosed, ConnectionHandlerUpgrErr, - ConnectionId, FromSwarm, NetworkBehaviour, NotifyHandler, PollParameters, THandlerInEvent, - THandlerOutEvent, ToSwarm, -}; -use void::Void; - -use crate::client::handler::Handler; - -use super::{RunId, RunParams, RunStats}; - -#[derive(Debug)] -pub struct Event { - pub id: RunId, - pub result: Result>, -} - -#[derive(Default)] -pub struct Behaviour { - /// Queue of actions to return when polled. - queued_events: VecDeque>>, - /// Set of connected peers. - connected: HashSet, -} - -impl Behaviour { - pub fn new() -> Self { - Self::default() - } - - pub fn perf(&mut self, server: PeerId, params: RunParams) -> Result { - if !self.connected.contains(&server) { - return Err(PerfError::NotConnected); - } - - let id = RunId::next(); - - self.queued_events.push_back(ToSwarm::NotifyHandler { - peer_id: server, - handler: NotifyHandler::Any, - event: crate::client::handler::Command { id, params }, - }); - - Ok(id) - } -} - -#[derive(thiserror::Error, Debug)] -pub enum PerfError { - #[error("Not connected to peer")] - NotConnected, -} - -impl NetworkBehaviour for Behaviour { - type ConnectionHandler = Handler; - type OutEvent = Event; - - fn handle_established_outbound_connection( - &mut self, - _connection_id: ConnectionId, - _peer: PeerId, - _addr: &Multiaddr, - _role_override: libp2p_core::Endpoint, - ) -> Result, libp2p_swarm::ConnectionDenied> { - Ok(Handler::default()) - } - - fn handle_established_inbound_connection( - &mut self, - _connection_id: ConnectionId, - _peer: PeerId, - _local_addr: &Multiaddr, - _remote_addr: &Multiaddr, - ) -> Result, libp2p_swarm::ConnectionDenied> { - Ok(Handler::default()) - } - - fn on_swarm_event(&mut self, event: FromSwarm) { - match event { - FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id, .. }) => { - self.connected.insert(peer_id); - } - FromSwarm::ConnectionClosed(ConnectionClosed { - peer_id, - connection_id: _, - endpoint: _, - handler: _, - remaining_established, - }) => { - if remaining_established == 0 { - assert!(self.connected.remove(&peer_id)); - } - } - FromSwarm::AddressChange(_) - | FromSwarm::DialFailure(_) - | FromSwarm::ListenFailure(_) - | FromSwarm::NewListener(_) - | FromSwarm::NewListenAddr(_) - | FromSwarm::ExpiredListenAddr(_) - | FromSwarm::ListenerError(_) - | FromSwarm::ListenerClosed(_) - | FromSwarm::NewExternalAddr(_) - | FromSwarm::ExpiredExternalAddr(_) => {} - } - } - - fn on_connection_handler_event( - &mut self, - _event_source: PeerId, - _connection_id: ConnectionId, - super::handler::Event { id, result }: THandlerOutEvent, - ) { - self.queued_events - .push_back(ToSwarm::GenerateEvent(Event { id, result })); - } - - fn poll( - &mut self, - _cx: &mut Context<'_>, - _: &mut impl PollParameters, - ) -> Poll>> { - if let Some(event) = self.queued_events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} diff --git a/protocols/perf/src/client/handler.rs b/protocols/perf/src/client/handler.rs deleted file mode 100644 index 6d78b677054..00000000000 --- a/protocols/perf/src/client/handler.rs +++ /dev/null @@ -1,209 +0,0 @@ -// 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 std::{ - collections::VecDeque, - task::{Context, Poll}, - time::{Duration, Instant}, -}; - -use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt, TryFutureExt}; -use libp2p_core::upgrade::{DeniedUpgrade, ReadyUpgrade}; -use libp2p_swarm::{ - handler::{ - ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, - ListenUpgradeError, - }, - ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, - SubstreamProtocol, -}; -use void::Void; - -use super::{RunId, RunParams, RunStats}; - -#[derive(Debug)] -pub struct Command { - pub(crate) id: RunId, - pub(crate) params: RunParams, -} - -#[derive(Debug)] -pub struct Event { - pub(crate) id: RunId, - pub(crate) result: Result>, -} - -pub struct Handler { - /// Queue of events to return when polled. - queued_events: VecDeque< - ConnectionHandlerEvent< - ::OutboundProtocol, - ::OutboundOpenInfo, - ::OutEvent, - ::Error, - >, - >, - - new_commands: VecDeque, - - pending_command: Option, - - outbound: FuturesUnordered>>, - - keep_alive: KeepAlive, -} - -impl Handler { - pub fn new() -> Self { - Self { - queued_events: Default::default(), - new_commands: Default::default(), - pending_command: Default::default(), - outbound: Default::default(), - keep_alive: KeepAlive::Yes, - } - } -} - -impl Default for Handler { - fn default() -> Self { - Self::new() - } -} - -impl ConnectionHandler for Handler { - type InEvent = Command; - type OutEvent = Event; - type Error = Void; - type InboundProtocol = DeniedUpgrade; - type OutboundProtocol = ReadyUpgrade<&'static [u8; 11]>; - type OutboundOpenInfo = (); - type InboundOpenInfo = (); - - fn listen_protocol(&self) -> SubstreamProtocol { - SubstreamProtocol::new(DeniedUpgrade, ()) - } - - fn on_behaviour_event(&mut self, command: Self::InEvent) { - self.new_commands.push_back(command); - } - - fn on_connection_event( - &mut self, - event: ConnectionEvent< - Self::InboundProtocol, - Self::OutboundProtocol, - Self::InboundOpenInfo, - Self::OutboundOpenInfo, - >, - ) { - match event { - ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { - protocol, .. - }) => void::unreachable(protocol), - ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { - protocol, - info: (), - }) => { - let Command { params, id } = self.pending_command.take().unwrap(); - self.outbound.push( - crate::protocol::send_receive(params, protocol) - .map_ok(move |timers| Event { - id, - result: Ok(RunStats { params, timers }), - }) - .boxed(), - ) - } - - ConnectionEvent::AddressChange(_) => {} - ConnectionEvent::DialUpgradeError(DialUpgradeError { info: (), error }) => self - .queued_events - .push_back(ConnectionHandlerEvent::Custom(Event { - id: self.pending_command.take().unwrap().id, - result: Err(error), - })), - ConnectionEvent::ListenUpgradeError(ListenUpgradeError { info: (), error }) => { - match error { - ConnectionHandlerUpgrErr::Timeout => {} - ConnectionHandlerUpgrErr::Timer => {} - ConnectionHandlerUpgrErr::Upgrade(error) => match error { - libp2p_core::UpgradeError::Select(_) => {} - libp2p_core::UpgradeError::Apply(v) => void::unreachable(v), - }, - } - } - } - } - - fn connection_keep_alive(&self) -> KeepAlive { - self.keep_alive - } - - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll< - ConnectionHandlerEvent< - Self::OutboundProtocol, - Self::OutboundOpenInfo, - Self::OutEvent, - Self::Error, - >, - > { - // Return queued events. - if let Some(event) = self.queued_events.pop_front() { - return Poll::Ready(event); - } - - while let Poll::Ready(Some(result)) = self.outbound.poll_next_unpin(cx) { - match result { - Ok(event) => return Poll::Ready(ConnectionHandlerEvent::Custom(event)), - Err(e) => { - panic!("{e:?}") - } - } - } - - if self.pending_command.is_none() { - if let Some(command) = self.new_commands.pop_front() { - self.pending_command = Some(command); - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new(ReadyUpgrade::new(crate::PROTOCOL_NAME), ()) - .with_timeout(Duration::from_secs(60)), - }); - } - } - - if self.outbound.is_empty() { - match self.keep_alive { - KeepAlive::Yes => { - self.keep_alive = KeepAlive::Until(Instant::now() + Duration::from_secs(10)); - } - KeepAlive::Until(_) => {} - KeepAlive::No => panic!("Handler never sets KeepAlive::No."), - } - } else { - self.keep_alive = KeepAlive::Yes - } - - Poll::Pending - } -} diff --git a/protocols/perf/src/lib.rs b/protocols/perf/src/lib.rs index 19bb956a1d2..a6a77a3381c 100644 --- a/protocols/perf/src/lib.rs +++ b/protocols/perf/src/lib.rs @@ -29,3 +29,13 @@ mod protocol; pub mod server; pub const PROTOCOL_NAME: &[u8; 11] = b"/perf/1.0.0"; + +/// Parameters for a single run, i.e. one stream, sending and receiving data. +/// +/// Property names are from the perspective of the actor. E.g. `to_send` is the amount of data to +/// send, both as the client and the server. +#[derive(Debug, Clone, Copy)] +pub struct RunParams { + pub to_send: usize, + pub to_receive: usize, +} diff --git a/protocols/perf/src/protocol.rs b/protocols/perf/src/protocol.rs index cf89ea644b6..02ed5922571 100644 --- a/protocols/perf/src/protocol.rs +++ b/protocols/perf/src/protocol.rs @@ -18,197 +18,135 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use instant::Instant; - +use async_trait::async_trait; use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use libp2p_request_response as request_response; +use std::io; -use crate::{client, server}; +use crate::RunParams; const BUF: [u8; 65536] = [0; 64 << 10]; -pub async fn send_receive( - params: client::RunParams, - mut stream: S, -) -> Result { - let client::RunParams { - to_send, - to_receive, - } = params; - - let mut receive_buf = vec![0; 64 << 10]; - - stream.write_all(&(to_receive as u64).to_be_bytes()).await?; - - let write_start = Instant::now(); - - let mut sent = 0; - while sent < to_send { - let n = std::cmp::min(to_send - sent, BUF.len()); - let buf = &BUF[..n]; - - sent += stream.write(buf).await?; - } - - stream.close().await?; - - let write_done = Instant::now(); - - let mut received = 0; - loop { - let n = stream.read(&mut receive_buf).await?; - received += n; - // Make sure to wait for the remote to close the stream. Otherwise with `to_receive` of `0` - // one does not measure the full round-trip of the previous write. - if n == 0 { - break; - } - } - assert_eq!(received, to_receive); - - let read_done = Instant::now(); - - Ok(client::RunTimers { - write_start, - write_done, - read_done, - }) -} - -pub async fn receive_send( - mut stream: S, -) -> Result { - let mut receive_buf = vec![0; 64 << 10]; - - let to_send = { - let mut buf = [0; 8]; - stream.read_exact(&mut buf).await?; - - u64::from_be_bytes(buf) as usize - }; - - let read_start = Instant::now(); - - let mut received = 0; - loop { - let n = stream.read(&mut receive_buf).await?; - received += n; - if n == 0 { - break; +#[derive(Clone)] +pub struct Codec {} + +#[async_trait] +impl request_response::Codec for Codec { + /// The type of protocol(s) or protocol versions being negotiated. + type Protocol = &'static [u8; 11]; + /// The type of inbound and outbound requests. + type Request = RunParams; + /// The type of inbound and outbound responses. + type Response = usize; + + /// Reads a request from the given I/O stream according to the + /// negotiated protocol. + async fn read_request(&mut self, _: &Self::Protocol, io: &mut T) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + let mut receive_buf = vec![0; 64 << 10]; + + let to_send = { + let mut buf = [0; 8]; + io.read_exact(&mut buf).await?; + + u64::from_be_bytes(buf) as usize + }; + + let mut received = 0; + loop { + let n = io.read(&mut receive_buf).await?; + received += n; + if n == 0 { + break; + } } - } - - let read_done = Instant::now(); - - let mut sent = 0; - while sent < to_send { - let n = std::cmp::min(to_send - sent, BUF.len()); - let buf = &BUF[..n]; - sent += stream.write(buf).await?; + Ok(RunParams { + to_receive: received, + to_send, + }) } - stream.close().await?; - let write_done = Instant::now(); - - Ok(server::RunStats { - params: server::RunParams { sent, received }, - timers: server::RunTimers { - read_start, - read_done, - write_done, - }, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::{executor::block_on, AsyncRead, AsyncWrite}; - use std::{ - pin::Pin, - sync::{Arc, Mutex}, - task::Poll, - }; - - #[derive(Clone)] - struct DummyStream { - inner: Arc>, - } - - struct DummyStreamInner { - read: Vec, - write: Vec, - } - - impl DummyStream { - fn new(read: Vec) -> Self { - Self { - inner: Arc::new(Mutex::new(DummyStreamInner { - read, - write: Vec::new(), - })), + /// Reads a response from the given I/O stream according to the + /// negotiated protocol. + async fn read_response( + &mut self, + _: &Self::Protocol, + io: &mut T, + ) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + let mut receive_buf = vec![0; 64 << 10]; + + let mut received = 0; + loop { + let n = io.read(&mut receive_buf).await?; + received += n; + // Make sure to wait for the remote to close the stream. Otherwise with `to_receive` of `0` + // one does not measure the full round-trip of the previous write. + if n == 0 { + break; } } - } - impl Unpin for DummyStream {} + Ok(received) + } - impl AsyncWrite for DummyStream { - fn poll_write( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> std::task::Poll> { - Pin::new(&mut self.inner.lock().unwrap().write).poll_write(cx, buf) + /// Writes a request to the given I/O stream according to the + /// negotiated protocol. + async fn write_request( + &mut self, + _: &Self::Protocol, + io: &mut T, + req: Self::Request, + ) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + let RunParams { + to_send, + to_receive, + } = req; + + io.write_all(&(to_receive as u64).to_be_bytes()).await?; + + let mut sent = 0; + while sent < to_send { + let n = std::cmp::min(to_send - sent, BUF.len()); + let buf = &BUF[..n]; + + sent += io.write(buf).await?; } - fn poll_flush( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - Pin::new(&mut self.inner.lock().unwrap().write).poll_flush(cx) - } + io.close().await?; - fn poll_close( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - Pin::new(&mut self.inner.lock().unwrap().write).poll_close(cx) - } + Ok(()) } - impl AsyncRead for DummyStream { - fn poll_read( - self: Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - buf: &mut [u8], - ) -> std::task::Poll> { - let amt = std::cmp::min(buf.len(), self.inner.lock().unwrap().read.len()); - let new = self.inner.lock().unwrap().read.split_off(amt); - - buf[..amt].copy_from_slice(self.inner.lock().unwrap().read.as_slice()); - - self.inner.lock().unwrap().read = new; - Poll::Ready(Ok(amt)) + /// Writes a response to the given I/O stream according to the + /// negotiated protocol. + async fn write_response( + &mut self, + _: &Self::Protocol, + io: &mut T, + to_send: Self::Response, + ) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + let mut sent = 0; + while sent < to_send { + let n = std::cmp::min(to_send - sent, BUF.len()); + let buf = &BUF[..n]; + + sent += io.write(buf).await?; } - } - #[test] - fn test_client() { - let stream = DummyStream::new(vec![0]); - - block_on(send_receive( - client::RunParams { - to_send: 0, - to_receive: 0, - }, - stream.clone(), - )) - .unwrap(); - - assert_eq!( - stream.inner.lock().unwrap().write, - 0u64.to_be_bytes().to_vec() - ); + io.close().await?; + + Ok(()) } } diff --git a/protocols/perf/src/server.rs b/protocols/perf/src/server.rs index fd0643a0079..546ca5dca3c 100644 --- a/protocols/perf/src/server.rs +++ b/protocols/perf/src/server.rs @@ -18,31 +18,144 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -mod behaviour; -mod handler; +use std::task::{Context, Poll}; -use instant::Instant; +use libp2p_core::Multiaddr; +use libp2p_identity::PeerId; +use libp2p_request_response as request_response; +use libp2p_swarm::{ + ConnectionId, FromSwarm, NetworkBehaviour, PollParameters, THandlerInEvent, THandlerOutEvent, + ToSwarm, +}; -pub use behaviour::{Behaviour, Event}; +pub struct Behaviour { + request_response: request_response::Behaviour, +} -/// Parameters for a single run, i.e. one stream, sending and receiving data. -#[derive(Debug, Clone, Copy)] -pub struct RunParams { - pub sent: usize, - pub received: usize, +impl Default for Behaviour { + fn default() -> Self { + Self { + request_response: request_response::Behaviour::new( + crate::protocol::Codec {}, + std::iter::once(( + crate::PROTOCOL_NAME, + request_response::ProtocolSupport::Inbound, + )), + Default::default(), + ), + } + } } -/// Timers for a single run, i.e. one stream, sending and receiving data. -#[derive(Debug, Clone, Copy)] -pub struct RunTimers { - pub read_start: Instant, - pub read_done: Instant, - pub write_done: Instant, +impl Behaviour { + pub fn new() -> Self { + Self::default() + } } -/// Statistics for a single run, i.e. one stream, sending and receiving data. -#[derive(Debug)] -pub struct RunStats { - pub params: RunParams, - pub timers: RunTimers, +impl NetworkBehaviour for Behaviour { + type ConnectionHandler = + as NetworkBehaviour>::ConnectionHandler; + type OutEvent = (); + + fn handle_pending_outbound_connection( + &mut self, + connection_id: ConnectionId, + maybe_peer: Option, + addresses: &[Multiaddr], + effective_role: libp2p_core::Endpoint, + ) -> Result, libp2p_swarm::ConnectionDenied> { + self.request_response.handle_pending_outbound_connection( + connection_id, + maybe_peer, + addresses, + effective_role, + ) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: libp2p_core::Endpoint, + ) -> Result, libp2p_swarm::ConnectionDenied> { + self.request_response + .handle_established_outbound_connection(connection_id, peer, addr, role_override) + } + + fn handle_pending_inbound_connection( + &mut self, + connection_id: ConnectionId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<(), libp2p_swarm::ConnectionDenied> { + self.request_response.handle_pending_inbound_connection( + connection_id, + local_addr, + remote_addr, + ) + } + + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, libp2p_swarm::ConnectionDenied> { + self.request_response.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + self.request_response.on_swarm_event(event); + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + connection_id: ConnectionId, + event: THandlerOutEvent, + ) { + self.request_response + .on_connection_handler_event(peer_id, connection_id, event); + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + params: &mut impl PollParameters, + ) -> Poll>> { + self.request_response.poll(cx, params).map(|to_swarm| { + to_swarm.map_out(|m| match m { + request_response::Event::Message { + peer: _, + message: request_response::Message::Response { .. }, + } => { + unreachable!() + } + request_response::Event::Message { + peer: _, + message: + request_response::Message::Request { + request_id: _, + request, + channel, + }, + } => { + let _ = self + .request_response + .send_response(channel, request.to_send); + } + request_response::Event::OutboundFailure { .. } => unreachable!(), + request_response::Event::InboundFailure { .. } => {} + request_response::Event::ResponseSent { .. } => {} + }) + }) + } } diff --git a/protocols/perf/src/server/behaviour.rs b/protocols/perf/src/server/behaviour.rs deleted file mode 100644 index 5d63475c999..00000000000 --- a/protocols/perf/src/server/behaviour.rs +++ /dev/null @@ -1,120 +0,0 @@ -// 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. - -//! [`NetworkBehaviour`] of the libp2p perf server protocol. - -use std::{ - collections::VecDeque, - task::{Context, Poll}, -}; - -use libp2p_identity::PeerId; -use libp2p_swarm::{ - ConnectionId, FromSwarm, NetworkBehaviour, PollParameters, THandlerInEvent, THandlerOutEvent, - ToSwarm, -}; - -use crate::server::handler::Handler; - -use super::RunStats; - -#[derive(Debug)] -pub struct Event { - pub remote_peer_id: PeerId, - pub stats: RunStats, -} - -#[derive(Default)] -pub struct Behaviour { - /// Queue of actions to return when polled. - queued_events: VecDeque>>, -} - -impl Behaviour { - pub fn new() -> Self { - Self::default() - } -} - -impl NetworkBehaviour for Behaviour { - type ConnectionHandler = Handler; - type OutEvent = Event; - - fn handle_established_inbound_connection( - &mut self, - _connection_id: ConnectionId, - _peer: PeerId, - _local_addr: &libp2p_core::Multiaddr, - _remote_addr: &libp2p_core::Multiaddr, - ) -> Result, libp2p_swarm::ConnectionDenied> { - Ok(Handler::default()) - } - - fn handle_established_outbound_connection( - &mut self, - _connection_id: ConnectionId, - _peer: PeerId, - _addr: &libp2p_core::Multiaddr, - _role_override: libp2p_core::Endpoint, - ) -> Result, libp2p_swarm::ConnectionDenied> { - Ok(Handler::default()) - } - - fn on_swarm_event(&mut self, event: FromSwarm) { - match event { - FromSwarm::ConnectionEstablished(_) => {} - FromSwarm::ConnectionClosed(_) => {} - FromSwarm::AddressChange(_) => {} - FromSwarm::DialFailure(_) => {} - FromSwarm::ListenFailure(_) => {} - FromSwarm::NewListener(_) => {} - FromSwarm::NewListenAddr(_) => {} - FromSwarm::ExpiredListenAddr(_) => {} - FromSwarm::ListenerError(_) => {} - FromSwarm::ListenerClosed(_) => {} - FromSwarm::NewExternalAddr(_) => {} - FromSwarm::ExpiredExternalAddr(_) => {} - } - } - - fn on_connection_handler_event( - &mut self, - event_source: PeerId, - _connection_id: ConnectionId, - super::handler::Event { stats }: THandlerOutEvent, - ) { - self.queued_events.push_back(ToSwarm::GenerateEvent(Event { - remote_peer_id: event_source, - stats, - })) - } - - fn poll( - &mut self, - _cx: &mut Context<'_>, - _: &mut impl PollParameters, - ) -> Poll>> { - if let Some(event) = self.queued_events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} diff --git a/protocols/perf/src/server/handler.rs b/protocols/perf/src/server/handler.rs deleted file mode 100644 index 2946b6d4a4c..00000000000 --- a/protocols/perf/src/server/handler.rs +++ /dev/null @@ -1,159 +0,0 @@ -// 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 std::{ - task::{Context, Poll}, - time::{Duration, Instant}, -}; - -use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt}; -use libp2p_core::upgrade::{DeniedUpgrade, ReadyUpgrade}; -use libp2p_swarm::{ - handler::{ - ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, - ListenUpgradeError, - }, - ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, - SubstreamProtocol, -}; -use log::error; -use void::Void; - -use super::RunStats; - -#[derive(Debug)] -pub struct Event { - pub stats: RunStats, -} - -pub struct Handler { - inbound: FuturesUnordered>>, - keep_alive: KeepAlive, -} - -impl Handler { - pub fn new() -> Self { - Self { - inbound: Default::default(), - keep_alive: KeepAlive::Yes, - } - } -} - -impl Default for Handler { - fn default() -> Self { - Self::new() - } -} - -impl ConnectionHandler for Handler { - type InEvent = Void; - type OutEvent = Event; - type Error = Void; - type InboundProtocol = ReadyUpgrade<&'static [u8]>; - type OutboundProtocol = DeniedUpgrade; - type OutboundOpenInfo = Void; - type InboundOpenInfo = (); - - fn listen_protocol(&self) -> SubstreamProtocol { - SubstreamProtocol::new(ReadyUpgrade::new(crate::PROTOCOL_NAME), ()) - } - - fn on_behaviour_event(&mut self, v: Self::InEvent) { - void::unreachable(v) - } - - fn on_connection_event( - &mut self, - event: ConnectionEvent< - Self::InboundProtocol, - Self::OutboundProtocol, - Self::InboundOpenInfo, - Self::OutboundOpenInfo, - >, - ) { - match event { - ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { - protocol, - info: _, - }) => { - self.inbound - .push(crate::protocol::receive_send(protocol).boxed()); - } - ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { info, .. }) => { - void::unreachable(info) - } - - ConnectionEvent::DialUpgradeError(DialUpgradeError { info, .. }) => { - void::unreachable(info) - } - ConnectionEvent::AddressChange(_) => {} - ConnectionEvent::ListenUpgradeError(ListenUpgradeError { info: (), error }) => { - match error { - ConnectionHandlerUpgrErr::Timeout => {} - ConnectionHandlerUpgrErr::Timer => {} - ConnectionHandlerUpgrErr::Upgrade(error) => match error { - libp2p_core::UpgradeError::Select(_) => {} - libp2p_core::UpgradeError::Apply(v) => void::unreachable(v), - }, - } - } - } - } - - fn connection_keep_alive(&self) -> KeepAlive { - self.keep_alive - } - - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll< - ConnectionHandlerEvent< - Self::OutboundProtocol, - Self::OutboundOpenInfo, - Self::OutEvent, - Self::Error, - >, - > { - while let Poll::Ready(Some(result)) = self.inbound.poll_next_unpin(cx) { - match result { - Ok(stats) => return Poll::Ready(ConnectionHandlerEvent::Custom(Event { stats })), - Err(e) => { - error!("{e:?}") - } - } - } - - if self.inbound.is_empty() { - match self.keep_alive { - KeepAlive::Yes => { - self.keep_alive = KeepAlive::Until(Instant::now() + Duration::from_secs(10)); - } - KeepAlive::Until(_) => {} - KeepAlive::No => panic!("Handler never sets KeepAlive::No."), - } - } else { - self.keep_alive = KeepAlive::Yes - } - - Poll::Pending - } -} diff --git a/protocols/perf/tests/lib.rs b/protocols/perf/tests/lib.rs index e448fd01c43..604f08e49ef 100644 --- a/protocols/perf/tests/lib.rs +++ b/protocols/perf/tests/lib.rs @@ -18,10 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_perf::{ - client::{self, RunParams}, - server, -}; +use libp2p_perf::{client, server, RunParams}; use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; From e984af639d17e19ec76d8aad6d77a9d8819c278c Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 1 May 2023 18:58:13 +0900 Subject: [PATCH 23/37] Debug log level --- protocols/perf/src/bin/perf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 3c9abbb8d17..e7ec7d4b8d5 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -88,7 +88,7 @@ impl FromStr for Transport { #[tokio::main] async fn main() -> Result<()> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init(); let opts = Opts::parse(); match opts { From 59dd7848f6328ac23dfe61bdd3b02ef6dec1dfe0 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 1 May 2023 19:08:12 +0900 Subject: [PATCH 24/37] request-response closes for us --- protocols/perf/src/protocol.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/protocols/perf/src/protocol.rs b/protocols/perf/src/protocol.rs index 02ed5922571..b762113a7c9 100644 --- a/protocols/perf/src/protocol.rs +++ b/protocols/perf/src/protocol.rs @@ -121,8 +121,6 @@ impl request_response::Codec for Codec { sent += io.write(buf).await?; } - io.close().await?; - Ok(()) } @@ -145,8 +143,6 @@ impl request_response::Codec for Codec { sent += io.write(buf).await?; } - io.close().await?; - Ok(()) } } From 9d49f263f4a8dbeaec69536bf39e2b841dbd73ed Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 1 May 2023 19:15:29 +0900 Subject: [PATCH 25/37] Bump timeouts --- protocols/perf/src/client.rs | 5 ++++- protocols/perf/src/server.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/protocols/perf/src/client.rs b/protocols/perf/src/client.rs index e8bc6024644..84aaf7bd47a 100644 --- a/protocols/perf/src/client.rs +++ b/protocols/perf/src/client.rs @@ -76,7 +76,10 @@ impl Default for Behaviour { crate::PROTOCOL_NAME, request_response::ProtocolSupport::Outbound, )), - Default::default(), + request_response::Config { + request_timeout: Duration::from_secs(60 * 5), + connection_keep_alive: Duration::from_secs(60 * 5), + }, ), } } diff --git a/protocols/perf/src/server.rs b/protocols/perf/src/server.rs index 546ca5dca3c..ee9567532c1 100644 --- a/protocols/perf/src/server.rs +++ b/protocols/perf/src/server.rs @@ -41,7 +41,10 @@ impl Default for Behaviour { crate::PROTOCOL_NAME, request_response::ProtocolSupport::Inbound, )), - Default::default(), + request_response::Config { + request_timeout: Duration::from_secs(60 * 5), + connection_keep_alive: Duration::from_secs(60 * 5), + }, ), } } From 613e6847f23638628da18caa454abf560d524363 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 1 May 2023 19:15:50 +0900 Subject: [PATCH 26/37] Log on info --- protocols/perf/src/bin/perf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index e7ec7d4b8d5..3c9abbb8d17 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -88,7 +88,7 @@ impl FromStr for Transport { #[tokio::main] async fn main() -> Result<()> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let opts = Opts::parse(); match opts { From f515473f7f94597d55056df88cc9f784c35d202f Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 1 May 2023 19:20:07 +0900 Subject: [PATCH 27/37] Fix compilation --- protocols/perf/src/client.rs | 10 +++++----- protocols/perf/src/server.rs | 10 ++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/protocols/perf/src/client.rs b/protocols/perf/src/client.rs index 84aaf7bd47a..febb7b3d78f 100644 --- a/protocols/perf/src/client.rs +++ b/protocols/perf/src/client.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use instant::Instant; +use instant::{Duration, Instant}; use std::{ collections::{HashMap, HashSet}, @@ -67,6 +67,9 @@ pub struct Behaviour { impl Default for Behaviour { fn default() -> Self { + let mut req_resp_config = request_response::Config::default(); + req_resp_config.set_connection_keep_alive(Duration::from_secs(60 * 5)); + req_resp_config.set_request_timeout(Duration::from_secs(60 * 5)); Self { connected: Default::default(), in_progress_runs: Default::default(), @@ -76,10 +79,7 @@ impl Default for Behaviour { crate::PROTOCOL_NAME, request_response::ProtocolSupport::Outbound, )), - request_response::Config { - request_timeout: Duration::from_secs(60 * 5), - connection_keep_alive: Duration::from_secs(60 * 5), - }, + req_resp_config, ), } } diff --git a/protocols/perf/src/server.rs b/protocols/perf/src/server.rs index ee9567532c1..61ceec2d709 100644 --- a/protocols/perf/src/server.rs +++ b/protocols/perf/src/server.rs @@ -20,6 +20,7 @@ use std::task::{Context, Poll}; +use instant::Duration; use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_request_response as request_response; @@ -34,6 +35,10 @@ pub struct Behaviour { impl Default for Behaviour { fn default() -> Self { + let mut req_resp_config = request_response::Config::default(); + req_resp_config.set_connection_keep_alive(Duration::from_secs(60 * 5)); + req_resp_config.set_request_timeout(Duration::from_secs(60 * 5)); + Self { request_response: request_response::Behaviour::new( crate::protocol::Codec {}, @@ -41,10 +46,7 @@ impl Default for Behaviour { crate::PROTOCOL_NAME, request_response::ProtocolSupport::Inbound, )), - request_response::Config { - request_timeout: Duration::from_secs(60 * 5), - connection_keep_alive: Duration::from_secs(60 * 5), - }, + req_resp_config, ), } } From 59d8f3f02ce0b72320202bf43c84a8060f3db545 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 11 May 2023 16:01:09 +0900 Subject: [PATCH 28/37] Remove n-times flag --- protocols/perf/src/bin/perf.rs | 74 +++++++++++++--------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 3c9abbb8d17..27411f891d0 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -56,8 +56,6 @@ struct Opts { upload_bytes: Option, #[arg(long)] download_bytes: Option, - #[arg(long)] - n_times: Option, /// Run in server mode. #[clap(long)] @@ -97,7 +95,6 @@ async fn main() -> Result<()> { transport: None, upload_bytes: None, download_bytes: None, - n_times: None, run_server: true, secret_key_seed: Some(secret_key_seed), } => server(secret_key_seed).await?, @@ -106,18 +103,10 @@ async fn main() -> Result<()> { transport: Some(transport), upload_bytes, download_bytes, - n_times, run_server: false, secret_key_seed: None, } => { - client( - server_ip_address, - transport, - upload_bytes, - download_bytes, - n_times, - ) - .await?; + client(server_ip_address, transport, upload_bytes, download_bytes).await?; } _ => panic!("invalid command line arguments: {opts:?}"), }; @@ -205,13 +194,11 @@ async fn client( transport: Transport, upload_bytes: Option, download_bytes: Option, - n_times: Option, ) -> Result<()> { let benchmarks: Vec> = if upload_bytes.is_some() { vec![Box::new(Custom { upload_bytes: upload_bytes.unwrap(), download_bytes: download_bytes.unwrap(), - n_times: n_times.unwrap(), })] } else { vec![ @@ -275,7 +262,6 @@ trait Benchmark: Send + Sync + 'static { struct Custom { upload_bytes: usize, download_bytes: usize, - n_times: usize, } #[async_trait] @@ -291,41 +277,39 @@ impl Benchmark for Custom { let server_peer_id = connect(&mut swarm, server_address.clone()).await?; - for _ in 0..self.n_times { - let start = Instant::now(); + let start = Instant::now(); - swarm.behaviour_mut().perf( - server_peer_id, - RunParams { - to_send: self.upload_bytes, - to_receive: self.download_bytes, - }, - )?; + swarm.behaviour_mut().perf( + server_peer_id, + RunParams { + to_send: self.upload_bytes, + to_receive: self.download_bytes, + }, + )?; - 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::Event { - id: _, - result: Ok(()), - }) => break, - e => panic!("{e:?}"), - }; - } - - latencies.push(start.elapsed().as_secs_f64()); + 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::Event { + id: _, + result: Ok(()), + }) => break, + e => panic!("{e:?}"), + }; } + latencies.push(start.elapsed().as_secs_f64()); + info!( - "Finished: Sent {} perf requests on single connection uploading {} and download {} bytes each", - self.n_times, self.upload_bytes, self.download_bytes + "Finished: Sent perf request on single connection uploading {} and download {} bytes each", + self.upload_bytes, self.download_bytes ); println!( From 08e4276e048f4d90c064960e689f41e4f30c80b9 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 12 May 2023 19:45:48 +0900 Subject: [PATCH 29/37] Provide ip:port --- protocols/perf/src/bin/perf.rs | 37 +++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 8a7a42dec9e..666c09c22dc 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::{net::Ipv4Addr, str::FromStr}; +use std::{net::SocketAddr, str::FromStr}; use anyhow::{bail, Result}; use async_trait::async_trait; @@ -48,7 +48,7 @@ mod schema { #[clap(name = "libp2p perf client")] struct Opts { #[arg(long)] - server_ip_address: Option, + server_address: Option, #[arg(long)] transport: Option, #[arg(long)] @@ -90,22 +90,22 @@ async fn main() -> Result<()> { let opts = Opts::parse(); match opts { Opts { - server_ip_address: None, + server_address: Some(server_address), transport: None, upload_bytes: None, download_bytes: None, run_server: true, secret_key_seed: Some(secret_key_seed), - } => server(secret_key_seed).await?, + } => server(server_address, secret_key_seed).await?, Opts { - server_ip_address: Some(server_ip_address), + server_address: Some(server_address), transport: Some(transport), upload_bytes, download_bytes, run_server: false, secret_key_seed: None, } => { - client(server_ip_address, transport, upload_bytes, download_bytes).await?; + client(server_address, transport, upload_bytes, download_bytes).await?; } _ => panic!("invalid command line arguments: {opts:?}"), }; @@ -113,7 +113,7 @@ async fn main() -> Result<()> { Ok(()) } -async fn server(secret_key_seed: u8) -> Result<()> { +async fn server(server_address: SocketAddr, secret_key_seed: u8) -> Result<()> { // Create a random PeerId let local_key = generate_ed25519(secret_key_seed); let local_peer_id = PeerId::from(local_key.public()); @@ -152,11 +152,20 @@ async fn server(secret_key_seed: u8) -> Result<()> { .build(); swarm - .listen_on("/ip4/0.0.0.0/tcp/4001".parse().unwrap()) + .listen_on( + Multiaddr::empty() + .with(server_address.ip().into()) + .with(Protocol::Tcp(server_address.port())), + ) .unwrap(); swarm - .listen_on("/ip4/0.0.0.0/udp/4001/quic-v1".parse().unwrap()) + .listen_on( + Multiaddr::empty() + .with(server_address.ip().into()) + .with(Protocol::Udp(server_address.port())) + .with(Protocol::QuicV1), + ) .unwrap(); tokio::spawn(async move { @@ -189,7 +198,7 @@ async fn server(secret_key_seed: u8) -> Result<()> { } async fn client( - server_ip_address: Ipv4Addr, + server_address: SocketAddr, transport: Transport, upload_bytes: Option, download_bytes: Option, @@ -210,11 +219,11 @@ async fn client( let address = match transport { Transport::Tcp => Multiaddr::empty() - .with(Protocol::Ip4(server_ip_address)) - .with(Protocol::Tcp(4001)), + .with(server_address.ip().into()) + .with(Protocol::Tcp(server_address.port())), Transport::QuicV1 => Multiaddr::empty() - .with(Protocol::Ip4(server_ip_address)) - .with(Protocol::Udp(4001)) + .with(server_address.ip().into()) + .with(Protocol::Udp(server_address.port())) .with(Protocol::QuicV1), }; From 647d76cbd957a9665064847b832e7766a91e5193 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 19 May 2023 12:29:43 +0900 Subject: [PATCH 30/37] Remove schema No longer needed. Output of perf binaries is much smaller now. --- Cargo.lock | 75 -------------- protocols/perf/Cargo.toml | 1 - protocols/perf/src/benchmark-data-schema.json | 98 ------------------- protocols/perf/src/bin/perf.rs | 58 +++-------- 4 files changed, 12 insertions(+), 220 deletions(-) delete mode 100644 protocols/perf/src/benchmark-data-schema.json diff --git a/Cargo.lock b/Cargo.lock index 5df4a1fdf00..786604ad561 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,16 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "adler" version = "1.0.2" @@ -2822,7 +2812,6 @@ dependencies = [ "libp2p-yamux", "log", "rand 0.8.5", - "schemafy", "serde", "serde_json", "thiserror", @@ -4548,49 +4537,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schemafy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9725c16a64e85972fcb3630677be83fef699a1cd8e4bfbdcf3b3c6675f838a19" -dependencies = [ - "Inflector", - "schemafy_core", - "schemafy_lib", - "serde", - "serde_derive", - "serde_json", - "serde_repr", - "syn 1.0.109", -] - -[[package]] -name = "schemafy_core" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bec29dddcfe60f92f3c0d422707b8b56473983ef0481df8d5236ed3ab8fdf24" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "schemafy_lib" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3d87f1df246a9b7e2bfd1f4ee5f88e48b11ef9cfc62e63f0dead255b1a6f5f" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "schemafy_core", - "serde", - "serde_derive", - "serde_json", - "syn 1.0.109", - "uriparse", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -4709,17 +4655,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_repr" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - [[package]] name = "sha-1" version = "0.9.8" @@ -5380,16 +5315,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "uriparse" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" -dependencies = [ - "fnv", - "lazy_static", -] - [[package]] name = "url" version = "2.3.1" diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index 09a212fefa3..d162ffb713e 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -28,7 +28,6 @@ libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } libp2p-tcp = { workspace = true, features = ["tokio"] } libp2p-yamux = { workspace = true } log = "0.4" -schemafy = "0.6.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" diff --git a/protocols/perf/src/benchmark-data-schema.json b/protocols/perf/src/benchmark-data-schema.json deleted file mode 100644 index 73143f4ec21..00000000000 --- a/protocols/perf/src/benchmark-data-schema.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/Benchmarks", - "definitions": { - "Benchmarks": { - "type": "object", - "properties": { - "benchmarks": { - "type": "array", - "items": { - "$ref": "#/definitions/Benchmark" - } - }, - "$schema": { - "type": "string" - } - }, - "required": [ - "benchmarks" - ], - "additionalProperties": false - }, - "Benchmark": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "unit": { - "type": "string", - "enum": [ - "bits/s", - "s" - ] - }, - "results": { - "type": "array", - "items": { - "$ref": "#/definitions/Result" - } - }, - "comparisons": { - "type": "array", - "items": { - "$ref": "#/definitions/Comparison" - } - } - }, - "required": [ - "name", - "unit", - "results", - "comparisons" - ], - "additionalProperties": false - }, - "Result": { - "type": "object", - "properties": { - "result": { - "type": "number" - }, - "implementation": { - "type": "string" - }, - "transportStack": { - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "result", - "implementation", - "transportStack", - "version" - ], - "additionalProperties": false - }, - "Comparison": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "result": { - "type": "number" - } - }, - "required": [ - "name", - "result" - ], - "additionalProperties": false - } - } -} diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 666c09c22dc..e99817edcc6 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -36,14 +36,6 @@ use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::{error, info}; use serde::{Deserialize, Serialize}; -mod schema { - use serde::{Deserialize, Serialize}; - schemafy::schemafy!( - root: BenchmarkResults - "src/benchmark-data-schema.json" - ); -} - #[derive(Debug, Parser)] #[clap(name = "libp2p perf client")] struct Opts { @@ -227,36 +219,20 @@ async fn client( .with(Protocol::QuicV1), }; - let results = tokio::spawn(async move { - let mut results = vec![]; - + tokio::spawn(async move { for benchmark in benchmarks { info!( "{}", format!("Start benchmark: {}", benchmark.name()).underline(), ); - let result = benchmark.run(address.clone()).await?; - if let Some(result) = result { - results.push(result); - } + benchmark.run(address.clone()).await?; } - anyhow::Ok(results) + anyhow::Ok(()) }) .await??; - if !results.is_empty() { - println!( - "{}", - serde_json::to_string(&schema::Benchmarks { - benchmarks: results, - schema: None, - }) - .unwrap() - ); - } - Ok(()) } @@ -264,7 +240,7 @@ async fn client( trait Benchmark: Send + Sync + 'static { fn name(&self) -> &'static str; - async fn run(&self, server_address: Multiaddr) -> Result>; + async fn run(&self, server_address: Multiaddr) -> Result<()>; } struct Custom { @@ -278,7 +254,7 @@ impl Benchmark for Custom { "custom" } - async fn run(&self, server_address: Multiaddr) -> Result> { + async fn run(&self, server_address: Multiaddr) -> Result<()> { let mut latencies = Vec::new(); let mut swarm = swarm().await; @@ -325,7 +301,7 @@ impl Benchmark for Custom { serde_json::to_string(&CustomResult { latencies }).unwrap() ); - Ok(None) + Ok(()) } } @@ -342,7 +318,7 @@ impl Benchmark for Latency { "round-trip-time latency" } - async fn run(&self, server_address: Multiaddr) -> Result> { + async fn run(&self, server_address: Multiaddr) -> Result<()> { let mut swarm = swarm().await; let server_peer_id = connect(&mut swarm, server_address.clone()).await?; @@ -395,7 +371,7 @@ impl Benchmark for Latency { ); info!("- {:.4} s median", percentile(&latencies, 0.50),); info!("- {:.4} s 95th percentile\n", percentile(&latencies, 0.95),); - Ok(None) + Ok(()) } } @@ -483,7 +459,7 @@ impl Benchmark for RequestsPerSecond { "single connection parallel requests per second" } - async fn run(&self, server_address: Multiaddr) -> Result> { + async fn run(&self, server_address: Multiaddr) -> Result<()> { let mut swarm = swarm().await; let server_peer_id = connect(&mut swarm, server_address.clone()).await?; @@ -537,7 +513,7 @@ impl Benchmark for RequestsPerSecond { ); info!("- {requests_per_second:.2} req/s\n"); - Ok(None) + Ok(()) } } @@ -549,7 +525,7 @@ impl Benchmark for ConnectionsPerSecond { "sequential connections with single request per second" } - async fn run(&self, server_address: Multiaddr) -> Result> { + async fn run(&self, server_address: Multiaddr) -> Result<()> { let mut rounds = 0; let to_send = 1; let to_receive = 1; @@ -606,17 +582,7 @@ impl Benchmark for ConnectionsPerSecond { info!("- {connection_establishment_95th:.4} s 95th percentile connection establishment"); info!("- {connection_establishment_plus_request_95th:.4} s 95th percentile connection establishment + one request"); - Ok(Some(schema::Benchmark { - name: "Single Connection 1 byte round trip latency 95th percentile".to_string(), - unit: "s".to_string(), - comparisons: vec![], - results: vec![schema::Result { - implementation: "rust-libp2p".to_string(), - transport_stack: "TODO".to_string(), - version: "TODO".to_string(), - result: connection_establishment_plus_request_95th, - }], - })) + Ok(()) } } From c4c84e7be3f2442a6b92087cb405485a82c3bd98 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 19 May 2023 14:11:12 +0900 Subject: [PATCH 31/37] Differentiate by up- and download time --- protocols/perf/src/bin/perf.rs | 201 ++++++++++++--------------------- protocols/perf/src/client.rs | 44 +++----- protocols/perf/src/lib.rs | 42 +++++++ protocols/perf/src/protocol.rs | 64 ++++++++++- protocols/perf/src/server.rs | 6 +- 5 files changed, 194 insertions(+), 163 deletions(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index e99817edcc6..d699374371f 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -31,7 +31,7 @@ use libp2p_core::{ Transport as _, }; use libp2p_identity::PeerId; -use libp2p_perf::RunParams; +use libp2p_perf::{RunDuration, RunParams}; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::{error, info}; use serde::{Deserialize, Serialize}; @@ -255,50 +255,29 @@ impl Benchmark for Custom { } async fn run(&self, server_address: Multiaddr) -> Result<()> { - let mut latencies = Vec::new(); - let mut swarm = swarm().await; - let server_peer_id = connect(&mut swarm, server_address.clone()).await?; - - let start = Instant::now(); + let (server_peer_id, connection_established) = + connect(&mut swarm, server_address.clone()).await?; - swarm.behaviour_mut().perf( + let RunDuration { upload, download } = perf( + &mut swarm, server_peer_id, RunParams { to_send: self.upload_bytes, to_receive: self.download_bytes, }, - )?; - - 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::Event { - id: _, - result: Ok(()), - }) => break, - e => panic!("{e:?}"), - }; - } - - latencies.push(start.elapsed().as_secs_f64()); - - info!( - "Finished: Sent perf request on single connection uploading {} and download {} bytes each", - self.upload_bytes, self.download_bytes - ); + ) + .await?; println!( "{}", - serde_json::to_string(&CustomResult { latencies }).unwrap() + serde_json::to_string(&CustomResult { + connection_established_seconds: connection_established.as_secs_f64(), + upload_seconds: upload.as_secs_f64(), + download_seconds: download.as_secs_f64(), + }) + .unwrap() ); Ok(()) @@ -307,7 +286,9 @@ impl Benchmark for Custom { #[derive(Serialize, Deserialize)] struct CustomResult { - latencies: Vec, + connection_established_seconds: f64, + upload_seconds: f64, + download_seconds: f64, } struct Latency {} @@ -321,7 +302,7 @@ impl Benchmark for Latency { async fn run(&self, server_address: Multiaddr) -> Result<()> { let mut swarm = swarm().await; - let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; let mut rounds = 0; let start = Instant::now(); @@ -334,31 +315,16 @@ impl Benchmark for Latency { let start = Instant::now(); - swarm.behaviour_mut().perf( + perf( + &mut swarm, server_peer_id, RunParams { to_send: 1, to_receive: 1, }, - )?; + ) + .await?; - 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::Event { - id: _, - result: Ok(()), - }) => break, - e => panic!("{e:?}"), - }; - } latencies.push(start.elapsed().as_secs_f64()); rounds += 1; } @@ -380,76 +346,29 @@ fn percentile(values: &[V], percentile: f64) -> V { values[n] } -// // TODO: Bring back? -// struct Throughput {} -// -// #[async_trait] -// impl Benchmark for Throughput { -// fn name(&self) -> &'static str { -// "single connection single channel throughput" -// } -// -// async fn run(&self, server_address: Multiaddr) -> Result> { -// let mut swarm = swarm().await; -// -// let server_peer_id = connect(&mut swarm, server_address.clone()).await?; -// -// let params = -// RunParams { -// to_send: 10 * 1024 * 1024, -// to_receive: 10 * 1024 * 1024, -// }; -// -// swarm.behaviour_mut().perf( -// server_peer_id, -// params, -// )?; -// -// let stats = 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::Event { id: _, result }) => { -// break result? -// } -// e => panic!("{e:?}"), -// } -// }; -// -// let sent_mebibytes = 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 = 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; -// -// info!( -// "Finished: sent {sent_mebibytes:.2} MiB \ -// and received {received_mebibytes:.2} MiB in {time:.2} s", -// ); -// info!("- {sent_bandwidth_mebibit_second:.2} MiBit/s up"); -// info!("- {receive_bandwidth_mebibit_second:.2} MiBit/s down\n"); -// -// Ok(Some(schema::Benchmark { -// name: "Single Connection throughput – Upload".to_string(), -// unit: "bits/s".to_string(), -// comparisons: vec![], -// results: vec![schema::Result { -// implementation: "rust-libp2p".to_string(), -// transport_stack: "TODO".to_string(), -// version: "TODO".to_string(), -// result: stats.params.to_send as f64 / sent_time, -// }], -// })) -// } -// } +struct Throughput {} + +#[async_trait] +impl Benchmark for Throughput { + fn name(&self) -> &'static str { + "single connection single channel throughput" + } + + async fn run(&self, server_address: Multiaddr) -> Result<()> { + let mut swarm = swarm().await; + + let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; + + let params = RunParams { + to_send: 10 * 1024 * 1024, + to_receive: 10 * 1024 * 1024, + }; + + perf(&mut swarm, server_peer_id, params).await?; + + Ok(()) + } +} struct RequestsPerSecond {} @@ -462,7 +381,7 @@ impl Benchmark for RequestsPerSecond { async fn run(&self, server_address: Multiaddr) -> Result<()> { let mut swarm = swarm().await; - let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; let num = 1_000; let to_send = 1; @@ -543,7 +462,7 @@ impl Benchmark for ConnectionsPerSecond { let start = Instant::now(); - let server_peer_id = connect(&mut swarm, server_address.clone()).await?; + let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; latency_connection_establishment.push(start.elapsed().as_secs_f64()); @@ -627,7 +546,8 @@ async fn swarm() -> Swarm { async fn connect( swarm: &mut Swarm, server_address: Multiaddr, -) -> Result { +) -> Result<(PeerId, Duration)> { + let start = Instant::now(); swarm.dial(server_address.clone()).unwrap(); let server_peer_id = loop { @@ -640,7 +560,30 @@ async fn connect( } }; - Ok(server_peer_id) + let duration = start.elapsed(); + let duration_seconds = duration.as_secs_f64(); + + info!("established connection in {duration_seconds:4} s"); + + Ok((server_peer_id, duration)) +} + +async fn perf( + swarm: &mut Swarm, + server_peer_id: PeerId, + params: RunParams, +) -> Result { + swarm.behaviour_mut().perf(server_peer_id, params)?; + + let duration = match swarm.next().await.unwrap() { + SwarmEvent::Behaviour(libp2p_perf::client::Event { + id: _, + result: Ok(duration), + }) => duration, + e => panic!("{e:?}"), + }; + + return Ok(duration); } fn generate_ed25519(secret_key_seed: u8) -> libp2p_identity::Keypair { diff --git a/protocols/perf/src/client.rs b/protocols/perf/src/client.rs index febb7b3d78f..927e1be281b 100644 --- a/protocols/perf/src/client.rs +++ b/protocols/perf/src/client.rs @@ -18,10 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use instant::{Duration, Instant}; +use instant::Duration; use std::{ - collections::{HashMap, HashSet}, + collections::HashSet, task::{Context, Poll}, }; @@ -33,15 +33,7 @@ use libp2p_swarm::{ NetworkBehaviour, PollParameters, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use crate::RunParams; - -/// Timers for a single run, i.e. one stream, sending and receiving data. -#[derive(Debug, Clone, Copy)] -pub struct RunTimers { - pub write_start: Instant, - pub write_done: Instant, - pub read_done: Instant, -} +use crate::{protocol::Response, RunDuration, RunParams}; /// Connection identifier. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] @@ -56,12 +48,11 @@ impl From for RunId { #[derive(Debug)] pub struct Event { pub id: RunId, - pub result: Result<(), request_response::OutboundFailure>, + pub result: Result, } pub struct Behaviour { connected: HashSet, - in_progress_runs: HashMap, request_response: request_response::Behaviour, } @@ -72,9 +63,8 @@ impl Default for Behaviour { req_resp_config.set_request_timeout(Duration::from_secs(60 * 5)); Self { connected: Default::default(), - in_progress_runs: Default::default(), request_response: request_response::Behaviour::new( - crate::protocol::Codec {}, + crate::protocol::Codec::default(), std::iter::once(( crate::PROTOCOL_NAME, request_response::ProtocolSupport::Outbound, @@ -97,8 +87,6 @@ impl Behaviour { let id = self.request_response.send_request(&server, params).into(); - self.in_progress_runs.insert(id, params); - Ok(id) } } @@ -221,16 +209,20 @@ impl NetworkBehaviour for Behaviour { message: request_response::Message::Response { request_id, - response, + response: Response::Receiver(run_duration), }, - } => { - let params = self.in_progress_runs.remove(&request_id.into()).unwrap(); - assert_eq!(params.to_receive, response); - Event { - id: request_id.into(), - result: Ok(()), - } - } + } => Event { + id: request_id.into(), + result: Ok(run_duration), + }, + request_response::Event::Message { + peer: _, + message: + request_response::Message::Response { + response: Response::Sender(_), + .. + }, + } => unreachable!(), request_response::Event::Message { peer: _, message: request_response::Message::Request { .. }, diff --git a/protocols/perf/src/lib.rs b/protocols/perf/src/lib.rs index a6562ddf0af..2b357b1ff0b 100644 --- a/protocols/perf/src/lib.rs +++ b/protocols/perf/src/lib.rs @@ -24,6 +24,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use std::fmt::Display; + +use instant::Duration; use libp2p_swarm::StreamProtocol; pub mod client; @@ -41,3 +44,42 @@ pub struct RunParams { pub to_send: usize, pub to_receive: usize, } + +/// Duration for a single run, i.e. one stream, sending and receiving data. +#[derive(Debug, Clone, Copy)] +pub struct RunDuration { + pub upload: Duration, + pub download: Duration, +} + +struct Run { + params: RunParams, + duration: RunDuration, +} + +impl Display for Run { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Run { + params: RunParams { + to_send, + to_receive, + }, + duration: RunDuration { upload, download }, + } = self; + let upload_seconds = upload.as_secs_f64(); + let download_seconds = download.as_secs_f64(); + + let sent_mebibytes = *to_send as f64 / 1024.0 / 1024.0; + let sent_bandwidth_mebibit_second = (sent_mebibytes * 8.0) / upload_seconds; + + let received_mebibytes = *to_receive as f64 / 1024.0 / 1024.0; + let receive_bandwidth_mebibit_second = (received_mebibytes * 8.0) / download_seconds; + + write!( + f, + "uploaded {to_send} in {upload_seconds:.4} ({sent_bandwidth_mebibit_second} MiBit/s), downloaded {to_receive} in {download_seconds} ({receive_bandwidth_mebibit_second} MiBit/s)", + )?; + + Ok(()) + } +} diff --git a/protocols/perf/src/protocol.rs b/protocols/perf/src/protocol.rs index fb31ff21d05..4b454fb88b7 100644 --- a/protocols/perf/src/protocol.rs +++ b/protocols/perf/src/protocol.rs @@ -20,16 +20,35 @@ use async_trait::async_trait; use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use instant::Instant; use libp2p_request_response as request_response; use libp2p_swarm::StreamProtocol; use std::io; -use crate::RunParams; +use crate::{RunDuration, RunParams}; const BUF: [u8; 65536] = [0; 64 << 10]; -#[derive(Clone)] -pub struct Codec {} +#[derive(Debug)] +pub enum Response { + Sender(usize), + Receiver(RunDuration), +} + +#[derive(Default)] +pub struct Codec { + to_receive: Option, + + write_start: Option, + read_start: Option, + read_done: Option, +} + +impl Clone for Codec { + fn clone(&self) -> Self { + Default::default() + } +} #[async_trait] impl request_response::Codec for Codec { @@ -38,7 +57,7 @@ impl request_response::Codec for Codec { /// The type of inbound and outbound requests. type Request = RunParams; /// The type of inbound and outbound responses. - type Response = usize; + type Response = Response; /// Reads a request from the given I/O stream according to the /// negotiated protocol. @@ -80,6 +99,12 @@ impl request_response::Codec for Codec { where T: AsyncRead + Unpin + Send, { + assert!(self.write_start.is_some()); + assert_eq!(self.read_start, None); + assert_eq!(self.read_done, None); + + self.read_start = Some(Instant::now()); + let mut receive_buf = vec![0; 64 << 10]; let mut received = 0; @@ -93,7 +118,20 @@ impl request_response::Codec for Codec { } } - Ok(received) + self.read_done = Some(Instant::now()); + + assert_eq!(received, self.to_receive.unwrap()); + + Ok(Response::Receiver(RunDuration { + upload: self + .read_start + .unwrap() + .duration_since(self.write_start.unwrap()), + download: self + .read_done + .unwrap() + .duration_since(self.read_start.unwrap()), + })) } /// Writes a request to the given I/O stream according to the @@ -107,11 +145,20 @@ impl request_response::Codec for Codec { where T: AsyncWrite + Unpin + Send, { + assert_eq!(self.to_receive, None); + assert_eq!(self.write_start, None); + assert_eq!(self.read_start, None); + assert_eq!(self.read_done, None); + + self.write_start = Some(Instant::now()); + let RunParams { to_send, to_receive, } = req; + self.to_receive = Some(to_receive); + io.write_all(&(to_receive as u64).to_be_bytes()).await?; let mut sent = 0; @@ -131,11 +178,16 @@ impl request_response::Codec for Codec { &mut self, _: &Self::Protocol, io: &mut T, - to_send: Self::Response, + response: Self::Response, ) -> io::Result<()> where T: AsyncWrite + Unpin + Send, { + let to_send = match response { + Response::Sender(to_send) => to_send, + Response::Receiver(_) => unreachable!(), + }; + let mut sent = 0; while sent < to_send { let n = std::cmp::min(to_send - sent, BUF.len()); diff --git a/protocols/perf/src/server.rs b/protocols/perf/src/server.rs index 61ceec2d709..e143da27912 100644 --- a/protocols/perf/src/server.rs +++ b/protocols/perf/src/server.rs @@ -29,6 +29,8 @@ use libp2p_swarm::{ ToSwarm, }; +use crate::protocol::{Codec, Response}; + pub struct Behaviour { request_response: request_response::Behaviour, } @@ -41,7 +43,7 @@ impl Default for Behaviour { Self { request_response: request_response::Behaviour::new( - crate::protocol::Codec {}, + Codec::default(), std::iter::once(( crate::PROTOCOL_NAME, request_response::ProtocolSupport::Inbound, @@ -155,7 +157,7 @@ impl NetworkBehaviour for Behaviour { } => { let _ = self .request_response - .send_response(channel, request.to_send); + .send_response(channel, Response::Sender(request.to_send)); } request_response::Event::OutboundFailure { .. } => unreachable!(), request_response::Event::InboundFailure { .. } => {} From fa6886cd73d09b9b0ff0b7ce6334a42d760ddf88 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 19 May 2023 15:14:15 +0900 Subject: [PATCH 32/37] Remove Benchmark trait --- Cargo.lock | 12 - protocols/perf/Cargo.toml | 1 - protocols/perf/src/bin/perf.rs | 392 ++++++++++++++------------------- protocols/perf/src/lib.rs | 57 ++++- 4 files changed, 205 insertions(+), 257 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 786604ad561..94c2dbedeb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,17 +842,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - [[package]] name = "combine" version = "4.6.6" @@ -2796,7 +2785,6 @@ dependencies = [ "anyhow", "async-trait", "clap 4.2.7", - "colored", "env_logger 0.10.0", "futures", "instant", diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index d162ffb713e..e9e2724583f 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -14,7 +14,6 @@ categories = ["network-programming", "asynchronous"] anyhow = "1" async-trait = "0.1" clap = { version = "4.2.7", features = ["derive"] } -colored = "2" env_logger = "0.10.0" futures = "0.3.28" instant = "0.1.11" diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index d699374371f..d17f27a2007 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -21,9 +21,8 @@ use std::{net::SocketAddr, str::FromStr}; use anyhow::{bail, Result}; -use async_trait::async_trait; use clap::Parser; -use colored::*; +use futures::FutureExt; use futures::{future::Either, StreamExt}; use instant::{Duration, Instant}; use libp2p_core::{ @@ -31,7 +30,7 @@ use libp2p_core::{ Transport as _, }; use libp2p_identity::PeerId; -use libp2p_perf::{RunDuration, RunParams}; +use libp2p_perf::{Run, RunDuration, RunParams}; use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; use log::{error, info}; use serde::{Deserialize, Serialize}; @@ -195,21 +194,7 @@ async fn client( upload_bytes: Option, download_bytes: Option, ) -> Result<()> { - let benchmarks: Vec> = if upload_bytes.is_some() { - vec![Box::new(Custom { - upload_bytes: upload_bytes.unwrap(), - download_bytes: download_bytes.unwrap(), - })] - } else { - vec![ - Box::new(Latency {}), - // Box::new(Throughput {}), - Box::new(RequestsPerSecond {}), - Box::new(ConnectionsPerSecond {}), - ] - }; - - let address = match transport { + let server_address = match transport { Transport::Tcp => Multiaddr::empty() .with(server_address.ip().into()) .with(Protocol::Tcp(server_address.port())), @@ -219,14 +204,27 @@ async fn client( .with(Protocol::QuicV1), }; + let benchmarks = if upload_bytes.is_some() { + vec![custom( + server_address, + RunParams { + to_send: upload_bytes.unwrap(), + to_receive: download_bytes.unwrap(), + }, + ) + .boxed()] + } else { + vec![ + latency(server_address.clone()).boxed(), + throughput(server_address.clone()).boxed(), + requests_per_second(server_address.clone()).boxed(), + sequential_connections_per_second(server_address.clone()).boxed(), + ] + }; + tokio::spawn(async move { for benchmark in benchmarks { - info!( - "{}", - format!("Start benchmark: {}", benchmark.name()).underline(), - ); - - benchmark.run(address.clone()).await?; + benchmark.await?; } anyhow::Ok(()) @@ -236,109 +234,75 @@ async fn client( Ok(()) } -#[async_trait] -trait Benchmark: Send + Sync + 'static { - fn name(&self) -> &'static str; +async fn custom(server_address: Multiaddr, params: RunParams) -> Result<()> { + info!("start benchmark: custom"); + let mut swarm = swarm().await; - async fn run(&self, server_address: Multiaddr) -> Result<()>; -} + let (server_peer_id, connection_established) = + connect(&mut swarm, server_address.clone()).await?; -struct Custom { - upload_bytes: usize, - download_bytes: usize, -} + let RunDuration { upload, download } = perf(&mut swarm, server_peer_id, params).await?; -#[async_trait] -impl Benchmark for Custom { - fn name(&self) -> &'static str { - "custom" + #[derive(Serialize, Deserialize)] + struct CustomResult { + connection_established_seconds: f64, + upload_seconds: f64, + download_seconds: f64, } - async fn run(&self, server_address: Multiaddr) -> Result<()> { - let mut swarm = swarm().await; - - let (server_peer_id, connection_established) = - connect(&mut swarm, server_address.clone()).await?; - - let RunDuration { upload, download } = perf( - &mut swarm, - server_peer_id, - RunParams { - to_send: self.upload_bytes, - to_receive: self.download_bytes, - }, - ) - .await?; - - println!( - "{}", - serde_json::to_string(&CustomResult { - connection_established_seconds: connection_established.as_secs_f64(), - upload_seconds: upload.as_secs_f64(), - download_seconds: download.as_secs_f64(), - }) - .unwrap() - ); - - Ok(()) - } -} + println!( + "{}", + serde_json::to_string(&CustomResult { + connection_established_seconds: connection_established.as_secs_f64(), + upload_seconds: upload.as_secs_f64(), + download_seconds: download.as_secs_f64(), + }) + .unwrap() + ); -#[derive(Serialize, Deserialize)] -struct CustomResult { - connection_established_seconds: f64, - upload_seconds: f64, - download_seconds: f64, + Ok(()) } -struct Latency {} +async fn latency(server_address: Multiaddr) -> Result<()> { + info!("start benchmark: round-trip-time latency"); + let mut swarm = swarm().await; -#[async_trait] -impl Benchmark for Latency { - fn name(&self) -> &'static str { - "round-trip-time latency" - } + let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; - async fn run(&self, server_address: Multiaddr) -> Result<()> { - let mut swarm = swarm().await; + let mut rounds = 0; + let start = Instant::now(); + let mut latencies = Vec::new(); - let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; + loop { + if start.elapsed() > Duration::from_secs(30) { + break; + } - let mut rounds = 0; let start = Instant::now(); - let mut latencies = Vec::new(); - loop { - if start.elapsed() > Duration::from_secs(30) { - break; - } - - let start = Instant::now(); - - perf( - &mut swarm, - server_peer_id, - RunParams { - to_send: 1, - to_receive: 1, - }, - ) - .await?; + perf( + &mut swarm, + server_peer_id, + RunParams { + to_send: 1, + to_receive: 1, + }, + ) + .await?; - latencies.push(start.elapsed().as_secs_f64()); - rounds += 1; - } + latencies.push(start.elapsed().as_secs_f64()); + rounds += 1; + } - latencies.sort_by(|a, b| a.partial_cmp(b).unwrap()); + latencies.sort_by(|a, b| a.partial_cmp(b).unwrap()); - info!( - "Finished: {rounds} pings in {:.4}s", - start.elapsed().as_secs_f64() - ); - info!("- {:.4} s median", percentile(&latencies, 0.50),); - info!("- {:.4} s 95th percentile\n", percentile(&latencies, 0.95),); - Ok(()) - } + info!( + "Finished: {rounds} pings in {:.4}s", + start.elapsed().as_secs_f64() + ); + info!("- {:.4} s median", percentile(&latencies, 0.50),); + info!("- {:.4} s 95th percentile\n", percentile(&latencies, 0.95),); + Ok(()) } fn percentile(values: &[V], percentile: f64) -> V { @@ -346,163 +310,125 @@ fn percentile(values: &[V], percentile: f64) -> V { values[n] } -struct Throughput {} - -#[async_trait] -impl Benchmark for Throughput { - fn name(&self) -> &'static str { - "single connection single channel throughput" - } - - async fn run(&self, server_address: Multiaddr) -> Result<()> { - let mut swarm = swarm().await; +async fn throughput(server_address: Multiaddr) -> Result<()> { + info!("start benchmark: single connection single channel throughput"); + let mut swarm = swarm().await; - let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; + let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; - let params = RunParams { - to_send: 10 * 1024 * 1024, - to_receive: 10 * 1024 * 1024, - }; + let params = RunParams { + to_send: 10 * 1024 * 1024, + to_receive: 10 * 1024 * 1024, + }; - perf(&mut swarm, server_peer_id, params).await?; + perf(&mut swarm, server_peer_id, params).await?; - Ok(()) - } + Ok(()) } -struct RequestsPerSecond {} - -#[async_trait] -impl Benchmark for RequestsPerSecond { - fn name(&self) -> &'static str { - "single connection parallel requests per second" - } +async fn requests_per_second(server_address: Multiaddr) -> Result<()> { + info!("start benchmark: single connection parallel requests per second"); + let mut swarm = swarm().await; - async fn run(&self, server_address: Multiaddr) -> Result<()> { - let mut swarm = swarm().await; + let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; - let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; + let num = 1_000; + let to_send = 1; + let to_receive = 1; - let num = 1_000; - let to_send = 1; - let to_receive = 1; - - for _ in 0..num { - swarm.behaviour_mut().perf( - server_peer_id, - RunParams { - to_send, - to_receive, - }, - )?; - } + for _ in 0..num { + swarm.behaviour_mut().perf( + server_peer_id, + RunParams { + to_send, + to_receive, + }, + )?; + } - let mut finished = 0; - let start = Instant::now(); + let mut finished = 0; + let start = Instant::now(); - 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::Event { - id: _, - result: Ok(_), - }) => { - finished += 1; - - if finished == num { - break; - } + loop { + match swarm.next().await.unwrap() { + SwarmEvent::Behaviour(libp2p_perf::client::Event { + id: _, + result: Ok(_), + }) => { + finished += 1; + + if finished == num { + break; } - e => panic!("{e:?}"), } + e => panic!("{e:?}"), } + } - let duration = start.elapsed().as_secs_f64(); - let requests_per_second = num as f64 / duration; + let duration = start.elapsed().as_secs_f64(); + let requests_per_second = num as f64 / duration; - info!( + info!( "Finished: sent {num} {to_send} bytes requests with {to_receive} bytes response each within {duration:.2} s", ); - info!("- {requests_per_second:.2} req/s\n"); + info!("- {requests_per_second:.2} req/s\n"); - Ok(()) - } + Ok(()) } -struct ConnectionsPerSecond {} - -#[async_trait] -impl Benchmark for ConnectionsPerSecond { - fn name(&self) -> &'static str { - "sequential connections with single request per second" - } - - async fn run(&self, server_address: Multiaddr) -> Result<()> { - let mut rounds = 0; - let to_send = 1; - let to_receive = 1; - let start = Instant::now(); - - let mut latency_connection_establishment = Vec::new(); - let mut latency_connection_establishment_plus_request = Vec::new(); +async fn sequential_connections_per_second(server_address: Multiaddr) -> Result<()> { + info!("start benchmark: sequential connections with single request per second"); + let mut rounds = 0; + let to_send = 1; + let to_receive = 1; + let start = Instant::now(); - loop { - if start.elapsed() > Duration::from_secs(30) { - break; - } + let mut latency_connection_establishment = Vec::new(); + let mut latency_connection_establishment_plus_request = Vec::new(); - let mut swarm = swarm().await; + loop { + if start.elapsed() > Duration::from_secs(30) { + break; + } - let start = Instant::now(); + let mut swarm = swarm().await; - let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; + let start = Instant::now(); - latency_connection_establishment.push(start.elapsed().as_secs_f64()); + let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; - swarm.behaviour_mut().perf( - server_peer_id, - RunParams { - to_send, - to_receive, - }, - )?; + latency_connection_establishment.push(start.elapsed().as_secs_f64()); - match swarm.next().await.unwrap() { - SwarmEvent::Behaviour(libp2p_perf::client::Event { - id: _, - result: Ok(_), - }) => {} - e => panic!("{e:?}"), - }; + perf( + &mut swarm, + server_peer_id, + RunParams { + to_send, + to_receive, + }, + ) + .await?; - latency_connection_establishment_plus_request.push(start.elapsed().as_secs_f64()); - rounds += 1; - } + latency_connection_establishment_plus_request.push(start.elapsed().as_secs_f64()); + rounds += 1; + } - let duration = start.elapsed().as_secs_f64(); + let duration = start.elapsed().as_secs_f64(); - latency_connection_establishment.sort_by(|a, b| a.partial_cmp(b).unwrap()); - latency_connection_establishment_plus_request.sort_by(|a, b| a.partial_cmp(b).unwrap()); + latency_connection_establishment.sort_by(|a, b| a.partial_cmp(b).unwrap()); + latency_connection_establishment_plus_request.sort_by(|a, b| a.partial_cmp(b).unwrap()); - let connection_establishment_95th = percentile(&latency_connection_establishment, 0.95); - let connection_establishment_plus_request_95th = - percentile(&latency_connection_establishment_plus_request, 0.95); + let connection_establishment_95th = percentile(&latency_connection_establishment, 0.95); + let connection_establishment_plus_request_95th = + percentile(&latency_connection_establishment_plus_request, 0.95); - info!( + info!( "Finished: established {rounds} connections with one {to_send} bytes request and one {to_receive} bytes response within {duration:.2} s", ); - info!("- {connection_establishment_95th:.4} s 95th percentile connection establishment"); - info!("- {connection_establishment_plus_request_95th:.4} s 95th percentile connection establishment + one request"); + info!("- {connection_establishment_95th:.4} s 95th percentile connection establishment"); + info!("- {connection_establishment_plus_request_95th:.4} s 95th percentile connection establishment + one request"); - Ok(()) - } + Ok(()) } async fn swarm() -> Swarm { @@ -563,7 +489,7 @@ async fn connect( let duration = start.elapsed(); let duration_seconds = duration.as_secs_f64(); - info!("established connection in {duration_seconds:4} s"); + info!("established connection in {duration_seconds:.4} s"); Ok((server_peer_id, duration)) } @@ -583,6 +509,8 @@ async fn perf( e => panic!("{e:?}"), }; + info!("{}", Run { params, duration }); + return Ok(duration); } diff --git a/protocols/perf/src/lib.rs b/protocols/perf/src/lib.rs index 2b357b1ff0b..b2b12244341 100644 --- a/protocols/perf/src/lib.rs +++ b/protocols/perf/src/lib.rs @@ -52,13 +52,48 @@ pub struct RunDuration { pub download: Duration, } -struct Run { - params: RunParams, - duration: RunDuration, +pub struct Run { + pub params: RunParams, + pub duration: RunDuration, } impl Display for Run { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + const KILO: f64 = 1024.0; + const MEGA: f64 = KILO * 1024.0; + const GIGA: f64 = MEGA * 1024.0; + + fn format_bytes(bytes: usize) -> String { + let bytes = bytes as f64; + if bytes >= GIGA { + format!("{:.2} GiB", bytes / GIGA) + } else if bytes >= MEGA { + format!("{:.2} MiB", bytes / MEGA) + } else if bytes >= KILO { + format!("{:.2} KiB", bytes / KILO) + } else { + format!("{} B", bytes) + } + } + + fn format_bandwidth(duration: Duration, bytes: usize) -> String { + const KILO: f64 = 1024.0; + const MEGA: f64 = KILO * 1024.0; + const GIGA: f64 = MEGA * 1024.0; + + let bandwidth = (bytes as f64 * 8.0) / duration.as_secs_f64(); + + if bandwidth >= GIGA { + format!("{:.2} Gbit/s", bandwidth / GIGA) + } else if bandwidth >= MEGA { + format!("{:.2} Mbit/s", bandwidth / MEGA) + } else if bandwidth >= KILO { + format!("{:.2} Kbit/s", bandwidth / KILO) + } else { + format!("{:.2} bit/s", bandwidth) + } + } + let Run { params: RunParams { to_send, @@ -66,18 +101,16 @@ impl Display for Run { }, duration: RunDuration { upload, download }, } = self; - let upload_seconds = upload.as_secs_f64(); - let download_seconds = download.as_secs_f64(); - - let sent_mebibytes = *to_send as f64 / 1024.0 / 1024.0; - let sent_bandwidth_mebibit_second = (sent_mebibytes * 8.0) / upload_seconds; - - let received_mebibytes = *to_receive as f64 / 1024.0 / 1024.0; - let receive_bandwidth_mebibit_second = (received_mebibytes * 8.0) / download_seconds; write!( f, - "uploaded {to_send} in {upload_seconds:.4} ({sent_bandwidth_mebibit_second} MiBit/s), downloaded {to_receive} in {download_seconds} ({receive_bandwidth_mebibit_second} MiBit/s)", + "uploaded {} in {:.4} s ({}), downloaded {} in {:.4} s ({})", + format_bytes(*to_send), + upload.as_secs_f64(), + format_bandwidth(*upload, *to_send), + format_bytes(*to_receive), + download.as_secs_f64(), + format_bandwidth(*download, *to_receive), )?; Ok(()) From 488f0072cb937394d6c5c4678a5504b63403316e Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 19 May 2023 18:46:08 +0900 Subject: [PATCH 33/37] Use serde camelCase --- protocols/perf/src/bin/perf.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index d17f27a2007..c24dd848489 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -244,6 +244,7 @@ async fn custom(server_address: Multiaddr, params: RunParams) -> Result<()> { let RunDuration { upload, download } = perf(&mut swarm, server_peer_id, params).await?; #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] struct CustomResult { connection_established_seconds: f64, upload_seconds: f64, From ec52392539dc6cbf12f2c77e1c8ed29fac0473a1 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 26 May 2023 09:49:53 +0900 Subject: [PATCH 34/37] Log with millis to debug RTT --- protocols/perf/src/bin/perf.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index c24dd848489..94f22469655 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -76,7 +76,9 @@ impl FromStr for Transport { #[tokio::main] async fn main() -> Result<()> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_timestamp_millis() + .init(); let opts = Opts::parse(); match opts { From e343e5a359e200f22e03eb1da65ecf3b60fde7da Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 28 May 2023 14:22:33 +0900 Subject: [PATCH 35/37] Use `swarm` to contruct server --- protocols/perf/src/bin/perf.rs | 80 +++++++++------------------------- 1 file changed, 21 insertions(+), 59 deletions(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 24f2cc5493e..de7013d4144 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -31,7 +31,7 @@ use libp2p_core::{ }; use libp2p_identity::PeerId; use libp2p_perf::{Run, RunDuration, RunParams}; -use libp2p_swarm::{Swarm, SwarmBuilder, SwarmEvent}; +use libp2p_swarm::{NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent}; use log::{error, info}; use serde::{Deserialize, Serialize}; @@ -107,50 +107,13 @@ async fn main() -> Result<()> { } async fn server(server_address: SocketAddr, secret_key_seed: u8) -> Result<()> { - // Create a random PeerId - let local_key = generate_ed25519(secret_key_seed); - let local_peer_id = PeerId::from(local_key.public()); - println!("Local peer id: {local_peer_id}"); - - let transport = { - let tcp = libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default().nodelay(true)) - .upgrade(upgrade::Version::V1Lazy) - .authenticate( - libp2p_noise::Config::new(&local_key) - .expect("Signing libp2p-noise static DH keypair failed."), - ) - .multiplex(libp2p_yamux::Config::default()); - - let quic = { - let mut config = libp2p_quic::Config::new(&local_key); - config.support_draft_29 = true; - libp2p_quic::tokio::Transport::new(config) - }; - - let dns = libp2p_dns::TokioDnsConfig::system(OrTransport::new(quic, tcp)).unwrap(); + let mut swarm = swarm::(Some(secret_key_seed)).await; - 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_tokio_executor( - transport, - libp2p_perf::server::Behaviour::default(), - local_peer_id, - ) - .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) - .build(); - - swarm - .listen_on( - Multiaddr::empty() - .with(server_address.ip().into()) - .with(Protocol::Tcp(server_address.port())), - ) - .unwrap(); + swarm.listen_on( + Multiaddr::empty() + .with(server_address.ip().into()) + .with(Protocol::Tcp(server_address.port())), + )?; swarm .listen_on( @@ -238,7 +201,7 @@ async fn client( async fn custom(server_address: Multiaddr, params: RunParams) -> Result<()> { info!("start benchmark: custom"); - let mut swarm = swarm().await; + let mut swarm = swarm(None).await; let (server_peer_id, connection_established) = connect(&mut swarm, server_address.clone()).await?; @@ -268,7 +231,7 @@ async fn custom(server_address: Multiaddr, params: RunParams) -> Result<()> { async fn latency(server_address: Multiaddr) -> Result<()> { info!("start benchmark: round-trip-time latency"); - let mut swarm = swarm().await; + let mut swarm = swarm(None).await; let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; @@ -315,7 +278,7 @@ fn percentile(values: &[V], percentile: f64) -> V { async fn throughput(server_address: Multiaddr) -> Result<()> { info!("start benchmark: single connection single channel throughput"); - let mut swarm = swarm().await; + let mut swarm = swarm(None).await; let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; @@ -331,7 +294,7 @@ async fn throughput(server_address: Multiaddr) -> Result<()> { async fn requests_per_second(server_address: Multiaddr) -> Result<()> { info!("start benchmark: single connection parallel requests per second"); - let mut swarm = swarm().await; + let mut swarm = swarm(None).await; let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; @@ -394,7 +357,7 @@ async fn sequential_connections_per_second(server_address: Multiaddr) -> Result< break; } - let mut swarm = swarm().await; + let mut swarm = swarm(None).await; let start = Instant::now(); @@ -434,9 +397,12 @@ async fn sequential_connections_per_second(server_address: Multiaddr) -> Result< Ok(()) } -async fn swarm() -> Swarm { - // Create a random PeerId - let local_key = libp2p_identity::Keypair::generate_ed25519(); +async fn swarm(secret_key_seed: Option) -> Swarm { + let local_key = if let Some(seed) = secret_key_seed { + generate_ed25519(seed) + } else { + libp2p_identity::Keypair::generate_ed25519() + }; let local_peer_id = PeerId::from(local_key.public()); let transport = { @@ -463,13 +429,9 @@ async fn swarm() -> Swarm { .boxed() }; - SwarmBuilder::with_tokio_executor( - transport, - libp2p_perf::client::Behaviour::default(), - local_peer_id, - ) - .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) - .build() + SwarmBuilder::with_tokio_executor(transport, Default::default(), local_peer_id) + .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) + .build() } async fn connect( From 539eb3deec9afcee26eecfb9c273467d7ed9cce7 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 28 May 2023 14:27:05 +0900 Subject: [PATCH 36/37] Fix clippy --- protocols/perf/src/bin/perf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index de7013d4144..33fc6d06392 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -476,7 +476,7 @@ async fn perf( info!("{}", Run { params, duration }); - return Ok(duration); + Ok(duration) } fn generate_ed25519(secret_key_seed: u8) -> libp2p_identity::Keypair { From 6bb18a5de22b2f08b998be5d45c49c3e28d479a0 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 28 May 2023 14:32:02 +0900 Subject: [PATCH 37/37] Use tls instead of noise Makes it easier to compare with http performance --- Cargo.lock | 2 +- protocols/perf/Cargo.toml | 2 +- protocols/perf/src/bin/perf.rs | 29 ++++++++++++++--------------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf397f6f53c..19d7b99da42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2828,12 +2828,12 @@ dependencies = [ "libp2p-core", "libp2p-dns", "libp2p-identity", - "libp2p-noise", "libp2p-quic", "libp2p-request-response", "libp2p-swarm", "libp2p-swarm-test", "libp2p-tcp", + "libp2p-tls", "libp2p-yamux", "log", "rand 0.8.5", diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index 4afc215b878..5dd8979c71f 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -20,7 +20,7 @@ instant = "0.1.12" libp2p-core = { workspace = true } libp2p-dns = { workspace = true, features = ["tokio"] } libp2p-identity = { workspace = true } -libp2p-noise = { workspace = true } +libp2p-tls = { workspace = true } libp2p-quic = { workspace = true, features = ["tokio"] } libp2p-request-response = { workspace = true } libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 33fc6d06392..5404f405bfa 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -107,7 +107,7 @@ async fn main() -> Result<()> { } async fn server(server_address: SocketAddr, secret_key_seed: u8) -> Result<()> { - let mut swarm = swarm::(Some(secret_key_seed)).await; + let mut swarm = swarm::(Some(secret_key_seed)).await?; swarm.listen_on( Multiaddr::empty() @@ -201,7 +201,7 @@ async fn client( async fn custom(server_address: Multiaddr, params: RunParams) -> Result<()> { info!("start benchmark: custom"); - let mut swarm = swarm(None).await; + let mut swarm = swarm(None).await?; let (server_peer_id, connection_established) = connect(&mut swarm, server_address.clone()).await?; @@ -231,7 +231,7 @@ async fn custom(server_address: Multiaddr, params: RunParams) -> Result<()> { async fn latency(server_address: Multiaddr) -> Result<()> { info!("start benchmark: round-trip-time latency"); - let mut swarm = swarm(None).await; + let mut swarm = swarm(None).await?; let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; @@ -278,7 +278,7 @@ fn percentile(values: &[V], percentile: f64) -> V { async fn throughput(server_address: Multiaddr) -> Result<()> { info!("start benchmark: single connection single channel throughput"); - let mut swarm = swarm(None).await; + let mut swarm = swarm(None).await?; let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; @@ -294,7 +294,7 @@ async fn throughput(server_address: Multiaddr) -> Result<()> { async fn requests_per_second(server_address: Multiaddr) -> Result<()> { info!("start benchmark: single connection parallel requests per second"); - let mut swarm = swarm(None).await; + let mut swarm = swarm(None).await?; let (server_peer_id, _) = connect(&mut swarm, server_address.clone()).await?; @@ -357,7 +357,7 @@ async fn sequential_connections_per_second(server_address: Multiaddr) -> Result< break; } - let mut swarm = swarm(None).await; + let mut swarm = swarm(None).await?; let start = Instant::now(); @@ -397,7 +397,7 @@ async fn sequential_connections_per_second(server_address: Multiaddr) -> Result< Ok(()) } -async fn swarm(secret_key_seed: Option) -> Swarm { +async fn swarm(secret_key_seed: Option) -> Result> { let local_key = if let Some(seed) = secret_key_seed { generate_ed25519(seed) } else { @@ -408,10 +408,7 @@ async fn swarm(secret_key_seed: Option) -> Sw let transport = { let tcp = libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default().nodelay(true)) .upgrade(upgrade::Version::V1Lazy) - .authenticate( - libp2p_noise::Config::new(&local_key) - .expect("Signing libp2p-noise static DH keypair failed."), - ) + .authenticate(libp2p_tls::Config::new(&local_key)?) .multiplex(libp2p_yamux::Config::default()); let quic = { @@ -420,7 +417,7 @@ async fn swarm(secret_key_seed: Option) -> Sw libp2p_quic::tokio::Transport::new(config) }; - let dns = libp2p_dns::TokioDnsConfig::system(OrTransport::new(quic, tcp)).unwrap(); + let dns = libp2p_dns::TokioDnsConfig::system(OrTransport::new(quic, tcp))?; dns.map(|either_output, _| match either_output { Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), @@ -429,9 +426,11 @@ async fn swarm(secret_key_seed: Option) -> Sw .boxed() }; - SwarmBuilder::with_tokio_executor(transport, Default::default(), local_peer_id) - .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) - .build() + Ok( + SwarmBuilder::with_tokio_executor(transport, Default::default(), local_peer_id) + .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) + .build(), + ) } async fn connect(