use futures::{sink::SinkExt, stream::StreamExt};
use ghost_actor::dependencies::tracing;
use kitsune_p2p_proxy::*;
use kitsune_p2p_transport_quic::*;
use kitsune_p2p_types::{
config::KitsuneP2pTuningParams,
dependencies::{ghost_actor, url2},
metrics::metric_task,
transport::*,
transport_mem::*,
};
use structopt::StructOpt;
#[derive(structopt::StructOpt, Debug, Clone, Copy)]
pub enum ProxyTransport {
Mem,
Quic,
}
const E: &str = "please specify 'mem'/'m' or 'quic'/'q'";
impl std::str::FromStr for ProxyTransport {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.chars().next() {
Some('M') | Some('m') => Ok(Self::Mem),
Some('Q') | Some('q') => Ok(Self::Quic),
_ => Err(E),
}
}
}
#[derive(structopt::StructOpt, Debug, Clone)]
#[structopt(name = "proxy-stress")]
pub struct Opt {
#[structopt(short = "t", long, default_value = "Mem")]
transport: ProxyTransport,
#[structopt(short = "n", long, default_value = "128")]
node_count: u32,
#[structopt(short = "i", long, default_value = "200")]
request_interval_ms: u32,
#[structopt(short = "d", long, default_value = "200")]
process_delay_ms: u32,
}
#[tokio::main]
async fn main() {
observability::init_fmt(observability::Output::Compact)
.expect("Failed to start contextual logging");
if let Err(e) = inner().await {
eprintln!("{:?}", e);
}
}
async fn gen_base_con(
t: &ProxyTransport,
) -> TransportResult<(
ghost_actor::GhostSender<TransportListener>,
futures::channel::mpsc::Receiver<TransportEvent>,
)> {
match t {
ProxyTransport::Mem => spawn_bind_transport_mem().await,
ProxyTransport::Quic => spawn_transport_listener_quic(Default::default()).await,
}
}
async fn gen_proxy_con(
t: &ProxyTransport,
) -> TransportResult<(
ghost_actor::GhostSender<TransportListener>,
futures::channel::mpsc::Receiver<TransportEvent>,
)> {
let (listener, events) = gen_base_con(t).await?;
let proxy_config = ProxyConfig::local_proxy_server(
TlsConfig::new_ephemeral().await?,
AcceptProxyCallback::accept_all(),
);
spawn_kitsune_proxy_listener(
proxy_config,
KitsuneP2pTuningParams::default(),
listener,
events,
)
.await
}
async fn gen_cli_con(
t: &ProxyTransport,
proxy_url: url2::Url2,
) -> TransportResult<(
ghost_actor::GhostSender<TransportListener>,
futures::channel::mpsc::Receiver<TransportEvent>,
)> {
let (listener, events) = gen_base_con(t).await?;
let proxy_config =
ProxyConfig::remote_proxy_client(TlsConfig::new_ephemeral().await?, proxy_url.into());
spawn_kitsune_proxy_listener(
proxy_config,
KitsuneP2pTuningParams::default(),
listener,
events,
)
.await
}
#[derive(Debug)]
enum Metric {
Tick,
RequestOverhead(u64),
}
#[allow(unreachable_code)]
#[allow(clippy::single_match)]
async fn inner() -> TransportResult<()> {
let opt = Opt::from_args();
println!("{:#?}", opt);
let (listener, mut events) = gen_proxy_con(&opt.transport).await?;
let proxy_url = listener.bound_url().await?;
println!("Proxy Url: {}", proxy_url);
metric_task(async move {
while let Some(evt) = events.next().await {
match evt {
TransportEvent::IncomingChannel(url, mut write, _read) => {
tracing::debug!("{} is trying to talk directly to us", url);
let _ = write.write_and_close(b"".to_vec()).await;
}
}
}
<Result<(), ()>>::Ok(())
});
let (metric_send, mut metric_recv) =
futures::channel::mpsc::channel((opt.node_count + 10) as usize);
metric_task({
let mut metric_send = metric_send.clone();
async move {
loop {
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
metric_send
.send(Metric::Tick)
.await
.map_err(TransportError::other)?;
}
TransportResult::Ok(())
}
});
metric_task(async move {
let mut last_disp = std::time::Instant::now();
let mut rtime = Vec::new();
while let Some(metric) = metric_recv.next().await {
match metric {
Metric::RequestOverhead(time) => rtime.push(time),
_ => (),
}
if last_disp.elapsed().as_millis() > 5000 {
last_disp = std::time::Instant::now();
let cnt = rtime.len() as f64;
let mut avg = rtime.drain(..).fold(0.0_f64, |acc, x| acc + (x as f64));
avg /= cnt;
println!("Avg Request Overhead ({} requests): {} ms", cnt, avg);
}
}
<Result<(), ()>>::Ok(())
});
let (_con, con_url) = gen_client(opt.clone(), proxy_url.clone()).await?;
println!("Responder Url: {}", con_url);
for _ in 0..opt.node_count {
metric_task(client_loop(
opt.clone(),
proxy_url.clone(),
con_url.clone(),
metric_send.clone(),
));
}
futures::future::pending().await
}
async fn gen_client(
opt: Opt,
proxy_url: url2::Url2,
) -> TransportResult<(ghost_actor::GhostSender<TransportListener>, url2::Url2)> {
let (con, events) = gen_cli_con(&opt.transport, proxy_url).await?;
let con_url = con.bound_url().await?;
metric_task(async move {
let process_delay_ms = opt.process_delay_ms as u64;
events
.for_each_concurrent(
(opt.node_count + 10) as usize,
move |evt| async move {
match evt {
TransportEvent::IncomingChannel(_url, mut write, _read) => {
tokio::time::sleep(std::time::Duration::from_millis(process_delay_ms))
.await;
let _ = write.write_and_close(b"".to_vec()).await;
}
}
},
)
.await;
<Result<(), ()>>::Ok(())
});
Ok((con, con_url))
}
async fn client_loop(
opt: Opt,
proxy_url: url2::Url2,
con_url: url2::Url2,
mut metric_send: futures::channel::mpsc::Sender<Metric>,
) -> TransportResult<()> {
let (con, _my_url) = gen_client(opt.clone(), proxy_url).await?;
loop {
tokio::time::sleep(std::time::Duration::from_millis(
opt.request_interval_ms as u64,
))
.await;
let start = std::time::Instant::now();
let (_, mut write, read) = con.create_channel(con_url.clone()).await?;
write.write_and_close(b"".to_vec()).await?;
read.read_to_end().await;
metric_send
.send(Metric::RequestOverhead(
start.elapsed().as_millis() as u64 - opt.process_delay_ms as u64,
))
.await
.map_err(TransportError::other)?;
}
}