Merge pull request #168 from greatest-ape/udp-bench-fixes

udp and bencher tweaks
This commit is contained in:
Joakim Frostegård 2024-01-07 11:28:06 +01:00 committed by GitHub
commit 1ee08bf323
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 687 additions and 514 deletions

View file

@ -18,6 +18,9 @@
#### Changed #### Changed
* Index peers by packet source IP and provided port, instead of by peer_id.
This prevents users from impersonating others and is likely also slightly
faster for IPv4 peers.
* Remove support for unbounded worker channels * Remove support for unbounded worker channels
* Add backpressure in socket workers. They will postpone reading from the * Add backpressure in socket workers. They will postpone reading from the
socket if sending a request to a swarm worker failed socket if sending a request to a swarm worker failed

View file

@ -2,8 +2,15 @@
## High priority ## High priority
* aquatic_bench
* Opentracker "slow to get up to speed", is it due to getting faster once
inserts are rarely needed since most ip-port combinations have been sent?
In that case, a shorter duration (e.g., 30 seconds) would be a good idea.
* Maybe investigate aquatic memory use.
* Would it use significantly less memory to store peers in an ArrayVec if
there are only, say, 2 of them?
* CI transfer test * CI transfer test
* add udp with io_uring
* add HTTP without TLS * add HTTP without TLS
* http * http

View file

@ -2,6 +2,23 @@ use std::{fmt::Display, ops::Range, thread::available_parallelism};
use itertools::Itertools; use itertools::Itertools;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
pub enum Priority {
Low,
Medium,
High,
}
impl Display for Priority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Low => f.write_str("low"),
Self::Medium => f.write_str("medium"),
Self::High => f.write_str("high"),
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TaskSetCpuList(pub Vec<TaskSetCpuIndicator>); pub struct TaskSetCpuList(pub Vec<TaskSetCpuIndicator>);
@ -141,13 +158,17 @@ pub enum CpuDirection {
Desc, Desc,
} }
pub fn simple_load_test_runs(cpu_mode: CpuMode, workers: &[usize]) -> Vec<(usize, TaskSetCpuList)> { pub fn simple_load_test_runs(
cpu_mode: CpuMode,
workers: &[(usize, Priority)],
) -> Vec<(usize, Priority, TaskSetCpuList)> {
workers workers
.into_iter() .into_iter()
.copied() .copied()
.map(|workers| { .map(|(workers, priority)| {
( (
workers, workers,
priority,
TaskSetCpuList::new(cpu_mode, CpuDirection::Desc, workers).unwrap(), TaskSetCpuList::new(cpu_mode, CpuDirection::Desc, workers).unwrap(),
) )
}) })

View file

@ -5,7 +5,8 @@ pub mod run;
pub mod set; pub mod set;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use common::CpuMode; use common::{CpuMode, Priority};
use set::run_sets;
#[derive(Parser)] #[derive(Parser)]
#[command(author, version, about)] #[command(author, version, about)]
@ -20,6 +21,20 @@ struct Args {
/// Maximum number of tracker cpu cores to run benchmarks for /// Maximum number of tracker cpu cores to run benchmarks for
#[arg(long)] #[arg(long)]
max_cores: Option<usize>, max_cores: Option<usize>,
/// Minimum benchmark priority
#[arg(long, default_value_t = Priority::Medium)]
min_priority: Priority,
/// How long to run each load test for
#[arg(long, default_value_t = 90)]
duration: usize,
/// Only include data for last N seconds of load test runs.
///
/// Useful if the tracker/load tester combination is slow at reaching
/// maximum throughput
///
/// 0 = use data for whole run
#[arg(long, default_value_t = 30)]
summarize_last: usize,
#[command(subcommand)] #[command(subcommand)]
command: Command, command: Command,
} }
@ -36,8 +51,21 @@ fn main() {
match args.command { match args.command {
#[cfg(feature = "udp")] #[cfg(feature = "udp")]
Command::Udp(command) => command Command::Udp(command) => {
.run(args.cpu_mode, args.min_cores, args.max_cores) let sets = command.sets(args.cpu_mode);
.unwrap(), let load_test_gen = protocols::udp::UdpCommand::load_test_gen;
run_sets(
&command,
args.cpu_mode,
args.min_cores,
args.max_cores,
args.min_priority,
args.duration,
args.summarize_last,
sets,
load_test_gen,
);
}
} }
} }

View file

@ -1,5 +1,6 @@
use std::{ use std::{
io::Write, io::Write,
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
path::PathBuf, path::PathBuf,
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
rc::Rc, rc::Rc,
@ -11,9 +12,9 @@ use indoc::writedoc;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use crate::{ use crate::{
common::{simple_load_test_runs, CpuMode, TaskSetCpuList}, common::{simple_load_test_runs, CpuMode, Priority, TaskSetCpuList},
run::ProcessRunner, run::ProcessRunner,
set::{run_sets, SetConfig, Tracker}, set::{LoadTestRunnerParameters, SetConfig, Tracker},
}; };
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -50,179 +51,223 @@ pub struct UdpCommand {
} }
impl UdpCommand { impl UdpCommand {
pub fn run( pub fn sets(&self, cpu_mode: CpuMode) -> IndexMap<usize, SetConfig<UdpCommand, UdpTracker>> {
&self, // Priorities are based on what has previously produced the best results
cpu_mode: CpuMode,
min_cores: Option<usize>,
max_cores: Option<usize>,
) -> anyhow::Result<()> {
let mut sets = self.sets(cpu_mode);
if let Some(min_cores) = min_cores {
sets = sets.into_iter().filter(|(k, _)| *k >= min_cores).collect();
}
if let Some(max_cores) = max_cores {
sets = sets.into_iter().filter(|(k, _)| *k <= max_cores).collect();
}
run_sets(self, cpu_mode, sets, |workers| {
Box::new(AquaticUdpLoadTestRunner { workers })
});
Ok(())
}
fn sets(&self, cpu_mode: CpuMode) -> IndexMap<usize, SetConfig<UdpCommand, UdpTracker>> {
indexmap::indexmap! { indexmap::indexmap! {
1 => SetConfig { 1 => SetConfig {
implementations: indexmap! { implementations: indexmap! {
UdpTracker::Aquatic => vec![ UdpTracker::Aquatic => vec![
AquaticUdpRunner::new(1, 1), AquaticUdpRunner::new(1, 1, Priority::High),
AquaticUdpRunner::new(2, 1), AquaticUdpRunner::new(2, 1, Priority::High),
], ],
UdpTracker::OpenTracker => vec![ UdpTracker::OpenTracker => vec![
OpenTrackerUdpRunner::new(0), // Handle requests within event loop OpenTrackerUdpRunner::new(0, Priority::Low), // Handle requests within event loop
OpenTrackerUdpRunner::new(1), OpenTrackerUdpRunner::new(1, Priority::Medium),
OpenTrackerUdpRunner::new(2), OpenTrackerUdpRunner::new(2, Priority::High),
], ],
UdpTracker::Chihaya => vec![ UdpTracker::Chihaya => vec![
ChihayaUdpRunner::new(), ChihayaUdpRunner::new(),
], ],
}, },
load_test_runs: simple_load_test_runs(cpu_mode, &[1, 2, 4, 6, 8]), load_test_runs: simple_load_test_runs(cpu_mode, &[
(1, Priority::High),
(2, Priority::Medium),
(4, Priority::Medium),
(6, Priority::Medium),
(8, Priority::High)
]),
}, },
2 => SetConfig { 2 => SetConfig {
implementations: indexmap! { implementations: indexmap! {
UdpTracker::Aquatic => vec![ UdpTracker::Aquatic => vec![
AquaticUdpRunner::new(1, 1), AquaticUdpRunner::new(1, 1, Priority::Low),
AquaticUdpRunner::new(2, 1), AquaticUdpRunner::new(2, 1, Priority::Medium),
AquaticUdpRunner::new(3, 1), AquaticUdpRunner::new(3, 1, Priority::High),
], ],
UdpTracker::OpenTracker => vec![ UdpTracker::OpenTracker => vec![
OpenTrackerUdpRunner::new(2), OpenTrackerUdpRunner::new(2, Priority::Medium),
OpenTrackerUdpRunner::new(4), OpenTrackerUdpRunner::new(4, Priority::High),
], ],
UdpTracker::Chihaya => vec![ UdpTracker::Chihaya => vec![
ChihayaUdpRunner::new(), ChihayaUdpRunner::new(),
], ],
}, },
load_test_runs: simple_load_test_runs(cpu_mode, &[1, 2, 4, 6, 8]), load_test_runs: simple_load_test_runs(cpu_mode, &[
}, (1, Priority::Medium),
3 => SetConfig { (2, Priority::Medium),
implementations: indexmap! { (4, Priority::Medium),
UdpTracker::Aquatic => vec![ (6, Priority::Medium),
AquaticUdpRunner::new(2, 1), (8, Priority::High)
AquaticUdpRunner::new(3, 1), ]),
AquaticUdpRunner::new(5, 1),
],
UdpTracker::OpenTracker => vec![
OpenTrackerUdpRunner::new(3),
OpenTrackerUdpRunner::new(6),
],
UdpTracker::Chihaya => vec![
ChihayaUdpRunner::new(),
],
},
load_test_runs: simple_load_test_runs(cpu_mode, &[4, 6, 8]),
}, },
4 => SetConfig { 4 => SetConfig {
implementations: indexmap! { implementations: indexmap! {
UdpTracker::Aquatic => vec![ UdpTracker::Aquatic => vec![
AquaticUdpRunner::new(3, 1), AquaticUdpRunner::new(3, 1, Priority::Low),
AquaticUdpRunner::new(4, 1), AquaticUdpRunner::new(4, 1, Priority::Low),
AquaticUdpRunner::new(7, 1), AquaticUdpRunner::new(5, 1, Priority::Medium),
AquaticUdpRunner::new(6, 1, Priority::Medium),
AquaticUdpRunner::new(7, 1, Priority::High),
], ],
UdpTracker::OpenTracker => vec![ UdpTracker::OpenTracker => vec![
OpenTrackerUdpRunner::new(4), OpenTrackerUdpRunner::new(4, Priority::High),
OpenTrackerUdpRunner::new(8), OpenTrackerUdpRunner::new(8, Priority::Medium),
], ],
UdpTracker::Chihaya => vec![ UdpTracker::Chihaya => vec![
ChihayaUdpRunner::new(), ChihayaUdpRunner::new(),
], ],
}, },
load_test_runs: simple_load_test_runs(cpu_mode, &[4, 6, 8]), load_test_runs: simple_load_test_runs(cpu_mode, &[
(6, Priority::High),
(8, Priority::Medium),
(12, Priority::High),
(16, Priority::Medium)
]),
}, },
6 => SetConfig { 6 => SetConfig {
implementations: indexmap! { implementations: indexmap! {
UdpTracker::Aquatic => vec![ UdpTracker::Aquatic => vec![
AquaticUdpRunner::new(5, 1), AquaticUdpRunner::new(5, 1, Priority::Medium),
AquaticUdpRunner::new(10, 1), AquaticUdpRunner::new(6, 1, Priority::Medium),
AquaticUdpRunner::new(4, 2), AquaticUdpRunner::new(10, 1, Priority::Low),
AquaticUdpRunner::new(8, 2),
AquaticUdpRunner::new(4, 2, Priority::Low),
AquaticUdpRunner::new(6, 2, Priority::Medium),
AquaticUdpRunner::new(8, 2, Priority::High),
], ],
UdpTracker::OpenTracker => vec![ UdpTracker::OpenTracker => vec![
OpenTrackerUdpRunner::new(6), OpenTrackerUdpRunner::new(6, Priority::High),
OpenTrackerUdpRunner::new(12), OpenTrackerUdpRunner::new(12, Priority::Medium),
], ],
}, },
load_test_runs: simple_load_test_runs(cpu_mode, &[4, 6, 8, 12]), load_test_runs: simple_load_test_runs(cpu_mode, &[
(6, Priority::Medium),
(8, Priority::Medium),
(12, Priority::High),
(16, Priority::High),
(24, Priority::Medium),
]),
}, },
8 => SetConfig { 8 => SetConfig {
implementations: indexmap! { implementations: indexmap! {
UdpTracker::Aquatic => vec![ UdpTracker::Aquatic => vec![
AquaticUdpRunner::new(7, 1), AquaticUdpRunner::new(7, 1, Priority::Medium),
AquaticUdpRunner::new(6, 2), AquaticUdpRunner::new(8, 1, Priority::Medium),
AquaticUdpRunner::new(12, 2), AquaticUdpRunner::new(14, 1, Priority::Low),
AquaticUdpRunner::new(5, 3), AquaticUdpRunner::new(6, 2, Priority::Low),
AquaticUdpRunner::new(12, 2, Priority::High),
AquaticUdpRunner::new(5, 3, Priority::Low),
AquaticUdpRunner::new(10, 3, Priority::Medium),
], ],
UdpTracker::OpenTracker => vec![ UdpTracker::OpenTracker => vec![
OpenTrackerUdpRunner::new(8), OpenTrackerUdpRunner::new(8, Priority::High),
OpenTrackerUdpRunner::new(16), OpenTrackerUdpRunner::new(16, Priority::Medium),
], ],
}, },
load_test_runs: simple_load_test_runs(cpu_mode, &[4, 8, 12, 16]), load_test_runs: simple_load_test_runs(cpu_mode, &[
(8, Priority::High),
(12, Priority::Medium),
(16, Priority::High),
(24, Priority::Medium)
]),
}, },
12 => SetConfig { 12 => SetConfig {
implementations: indexmap! { implementations: indexmap! {
UdpTracker::Aquatic => vec![ UdpTracker::Aquatic => vec![
AquaticUdpRunner::new(10, 2), AquaticUdpRunner::new(10, 2, Priority::Medium),
AquaticUdpRunner::new(9, 3), AquaticUdpRunner::new(12, 2, Priority::Medium),
AquaticUdpRunner::new(8, 4), AquaticUdpRunner::new(20, 2, Priority::Low),
AquaticUdpRunner::new(16, 4),
AquaticUdpRunner::new(9, 5), AquaticUdpRunner::new(9, 3, Priority::Low),
AquaticUdpRunner::new(12, 3, Priority::Medium),
AquaticUdpRunner::new(18, 3, Priority::Low),
AquaticUdpRunner::new(8, 4, Priority::Low),
AquaticUdpRunner::new(12, 4, Priority::Medium),
AquaticUdpRunner::new(16, 4, Priority::High),
AquaticUdpRunner::new(7, 5, Priority::Low),
AquaticUdpRunner::new(12, 5, Priority::Medium),
AquaticUdpRunner::new(14, 5, Priority::Medium),
], ],
UdpTracker::OpenTracker => vec![ UdpTracker::OpenTracker => vec![
OpenTrackerUdpRunner::new(12), OpenTrackerUdpRunner::new(12, Priority::High),
OpenTrackerUdpRunner::new(24), OpenTrackerUdpRunner::new(24, Priority::Medium),
], ],
}, },
load_test_runs: simple_load_test_runs(cpu_mode, &[8, 12, 16, 24]), load_test_runs: simple_load_test_runs(cpu_mode, &[
(8, Priority::Medium),
(12, Priority::Medium),
(16, Priority::High),
(24, Priority::High),
]),
}, },
16 => SetConfig { 16 => SetConfig {
implementations: indexmap! { implementations: indexmap! {
UdpTracker::Aquatic => vec![ UdpTracker::Aquatic => vec![
AquaticUdpRunner::new(13, 3), AquaticUdpRunner::new(14, 2, Priority::Low),
AquaticUdpRunner::new(12, 4), AquaticUdpRunner::new(16, 2, Priority::Low),
AquaticUdpRunner::new(11, 5), AquaticUdpRunner::new(28, 2, Priority::Low),
AquaticUdpRunner::new(10, 6),
AquaticUdpRunner::new(20, 6), AquaticUdpRunner::new(13, 3, Priority::Low),
AquaticUdpRunner::new(9, 7), AquaticUdpRunner::new(16, 3, Priority::Low),
AquaticUdpRunner::new(26, 3, Priority::Low),
AquaticUdpRunner::new(12, 4, Priority::Medium),
AquaticUdpRunner::new(16, 4, Priority::Medium),
AquaticUdpRunner::new(24, 4, Priority::Low),
AquaticUdpRunner::new(11, 5, Priority::Low),
AquaticUdpRunner::new(16, 5, Priority::Medium),
AquaticUdpRunner::new(22, 5, Priority::Low),
AquaticUdpRunner::new(10, 6, Priority::Low),
AquaticUdpRunner::new(16, 6, Priority::High),
AquaticUdpRunner::new(20, 6, Priority::Medium),
AquaticUdpRunner::new(9, 7, Priority::Low),
AquaticUdpRunner::new(16, 7, Priority::Medium),
AquaticUdpRunner::new(18, 7, Priority::Low),
], ],
UdpTracker::OpenTracker => vec![ UdpTracker::OpenTracker => vec![
OpenTrackerUdpRunner::new(16), OpenTrackerUdpRunner::new(16, Priority::High),
OpenTrackerUdpRunner::new(32), OpenTrackerUdpRunner::new(32, Priority::Medium),
], ],
}, },
load_test_runs: simple_load_test_runs(cpu_mode, &[8, 12, 16, 24]), load_test_runs: simple_load_test_runs(cpu_mode, &[
(8, Priority::High),
(12, Priority::High),
(16, Priority::High),
(24, Priority::High),
]),
}, },
} }
} }
pub fn load_test_gen(
parameters: LoadTestRunnerParameters,
) -> Box<dyn ProcessRunner<Command = UdpCommand>> {
Box::new(AquaticUdpLoadTestRunner { parameters })
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct AquaticUdpRunner { struct AquaticUdpRunner {
socket_workers: usize, socket_workers: usize,
swarm_workers: usize, swarm_workers: usize,
priority: Priority,
} }
impl AquaticUdpRunner { impl AquaticUdpRunner {
fn new( fn new(
socket_workers: usize, socket_workers: usize,
swarm_workers: usize, swarm_workers: usize,
priority: Priority,
) -> Rc<dyn ProcessRunner<Command = UdpCommand>> { ) -> Rc<dyn ProcessRunner<Command = UdpCommand>> {
Rc::new(Self { Rc::new(Self {
socket_workers, socket_workers,
swarm_workers, swarm_workers,
priority,
}) })
} }
} }
@ -240,7 +285,8 @@ impl ProcessRunner for AquaticUdpRunner {
c.socket_workers = self.socket_workers; c.socket_workers = self.socket_workers;
c.swarm_workers = self.swarm_workers; c.swarm_workers = self.swarm_workers;
c.protocol.max_response_peers = 50; c.network.address = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 3000));
c.protocol.max_response_peers = 30;
let c = toml::to_string_pretty(&c)?; let c = toml::to_string_pretty(&c)?;
@ -257,6 +303,10 @@ impl ProcessRunner for AquaticUdpRunner {
.spawn()?) .spawn()?)
} }
fn priority(&self) -> crate::common::Priority {
self.priority
}
fn keys(&self) -> IndexMap<String, String> { fn keys(&self) -> IndexMap<String, String> {
indexmap! { indexmap! {
"socket workers".to_string() => self.socket_workers.to_string(), "socket workers".to_string() => self.socket_workers.to_string(),
@ -268,11 +318,12 @@ impl ProcessRunner for AquaticUdpRunner {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct OpenTrackerUdpRunner { struct OpenTrackerUdpRunner {
workers: usize, workers: usize,
priority: Priority,
} }
impl OpenTrackerUdpRunner { impl OpenTrackerUdpRunner {
fn new(workers: usize) -> Rc<dyn ProcessRunner<Command = UdpCommand>> { fn new(workers: usize, priority: Priority) -> Rc<dyn ProcessRunner<Command = UdpCommand>> {
Rc::new(Self { workers }) Rc::new(Self { workers, priority })
} }
} }
@ -287,7 +338,7 @@ impl ProcessRunner for OpenTrackerUdpRunner {
) -> anyhow::Result<Child> { ) -> anyhow::Result<Child> {
writeln!( writeln!(
tmp_file, tmp_file,
"listen.udp.workers {}\nlisten.udp 0.0.0.0:3000", "listen.udp.workers {}\nlisten.udp 127.0.0.1:3000",
self.workers self.workers
)?; )?;
@ -302,6 +353,10 @@ impl ProcessRunner for OpenTrackerUdpRunner {
.spawn()?) .spawn()?)
} }
fn priority(&self) -> crate::common::Priority {
self.priority
}
fn keys(&self) -> IndexMap<String, String> { fn keys(&self) -> IndexMap<String, String> {
indexmap! { indexmap! {
"workers".to_string() => self.workers.to_string(), "workers".to_string() => self.workers.to_string(),
@ -314,7 +369,7 @@ struct ChihayaUdpRunner;
impl ChihayaUdpRunner { impl ChihayaUdpRunner {
fn new() -> Rc<dyn ProcessRunner<Command = UdpCommand>> { fn new() -> Rc<dyn ProcessRunner<Command = UdpCommand>> {
Rc::new(Self) Rc::new(Self {})
} }
} }
@ -336,8 +391,8 @@ impl ProcessRunner for ChihayaUdpRunner {
udp: udp:
addr: "127.0.0.1:3000" addr: "127.0.0.1:3000"
private_key: "abcdefghijklmnopqrst" private_key: "abcdefghijklmnopqrst"
max_numwant: 50 max_numwant: 30
default_numwant: 50 default_numwant: 30
storage: storage:
name: "memory" name: "memory"
"#, "#,
@ -354,6 +409,10 @@ impl ProcessRunner for ChihayaUdpRunner {
.spawn()?) .spawn()?)
} }
fn priority(&self) -> crate::common::Priority {
Priority::High
}
fn keys(&self) -> IndexMap<String, String> { fn keys(&self) -> IndexMap<String, String> {
Default::default() Default::default()
} }
@ -361,7 +420,7 @@ impl ProcessRunner for ChihayaUdpRunner {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct AquaticUdpLoadTestRunner { struct AquaticUdpLoadTestRunner {
workers: usize, parameters: LoadTestRunnerParameters,
} }
impl ProcessRunner for AquaticUdpLoadTestRunner { impl ProcessRunner for AquaticUdpLoadTestRunner {
@ -375,9 +434,11 @@ impl ProcessRunner for AquaticUdpLoadTestRunner {
) -> anyhow::Result<Child> { ) -> anyhow::Result<Child> {
let mut c = aquatic_udp_load_test::config::Config::default(); let mut c = aquatic_udp_load_test::config::Config::default();
c.workers = self.workers as u8; c.workers = self.parameters.workers as u8;
c.duration = 60; c.duration = self.parameters.duration;
c.summarize_last = self.parameters.summarize_last;
c.requests.announce_peers_wanted = 30;
c.requests.weight_connect = 0; c.requests.weight_connect = 0;
c.requests.weight_announce = 100; c.requests.weight_announce = 100;
c.requests.weight_scrape = 1; c.requests.weight_scrape = 1;
@ -397,9 +458,15 @@ impl ProcessRunner for AquaticUdpLoadTestRunner {
.spawn()?) .spawn()?)
} }
fn priority(&self) -> crate::common::Priority {
eprintln!("load test runner priority method called");
Priority::High
}
fn keys(&self) -> IndexMap<String, String> { fn keys(&self) -> IndexMap<String, String> {
indexmap! { indexmap! {
"workers".to_string() => self.workers.to_string(), "workers".to_string() => self.parameters.workers.to_string(),
} }
} }
} }

View file

@ -12,7 +12,7 @@ use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use crate::common::TaskSetCpuList; use crate::common::{Priority, TaskSetCpuList};
pub trait ProcessRunner: ::std::fmt::Debug { pub trait ProcessRunner: ::std::fmt::Debug {
type Command; type Command;
@ -26,6 +26,8 @@ pub trait ProcessRunner: ::std::fmt::Debug {
fn keys(&self) -> IndexMap<String, String>; fn keys(&self) -> IndexMap<String, String>;
fn priority(&self) -> Priority;
fn info(&self) -> String { fn info(&self) -> String {
self.keys() self.keys()
.into_iter() .into_iter()
@ -43,7 +45,11 @@ pub struct RunConfig<C> {
} }
impl<C> RunConfig<C> { impl<C> RunConfig<C> {
pub fn run(self, command: &C) -> Result<RunSuccessResults, RunErrorResults<C>> { pub fn run(
self,
command: &C,
duration: usize,
) -> Result<RunSuccessResults, RunErrorResults<C>> {
let mut tracker_config_file = NamedTempFile::new().unwrap(); let mut tracker_config_file = NamedTempFile::new().unwrap();
let mut load_test_config_file = NamedTempFile::new().unwrap(); let mut load_test_config_file = NamedTempFile::new().unwrap();
@ -73,7 +79,7 @@ impl<C> RunConfig<C> {
} }
}; };
for _ in 0..59 { for _ in 0..(duration - 1) {
if let Ok(Some(status)) = tracker.0.try_wait() { if let Ok(Some(status)) = tracker.0.try_wait() {
return Err(RunErrorResults::new(self) return Err(RunErrorResults::new(self)
.set_tracker_outputs(tracker) .set_tracker_outputs(tracker)

View file

@ -5,30 +5,59 @@ use indexmap::IndexMap;
use num_format::{Locale, ToFormattedString}; use num_format::{Locale, ToFormattedString};
use crate::{ use crate::{
common::{CpuDirection, CpuMode, TaskSetCpuList}, common::{CpuDirection, CpuMode, Priority, TaskSetCpuList},
html::{html_all_runs, html_best_results}, html::{html_all_runs, html_best_results},
run::{ProcessRunner, ProcessStats, RunConfig}, run::{ProcessRunner, ProcessStats, RunConfig},
}; };
#[derive(Debug, Clone, Copy)]
pub struct LoadTestRunnerParameters {
pub workers: usize,
pub duration: usize,
pub summarize_last: usize,
}
pub trait Tracker: ::std::fmt::Debug + Copy + Clone + ::std::hash::Hash { pub trait Tracker: ::std::fmt::Debug + Copy + Clone + ::std::hash::Hash {
fn name(&self) -> String; fn name(&self) -> String;
} }
pub struct SetConfig<C, I> { pub struct SetConfig<C, I> {
pub implementations: IndexMap<I, Vec<Rc<dyn ProcessRunner<Command = C>>>>, pub implementations: IndexMap<I, Vec<Rc<dyn ProcessRunner<Command = C>>>>,
pub load_test_runs: Vec<(usize, TaskSetCpuList)>, pub load_test_runs: Vec<(usize, Priority, TaskSetCpuList)>,
} }
pub fn run_sets<C, F, I>( pub fn run_sets<C, F, I>(
command: &C, command: &C,
cpu_mode: CpuMode, cpu_mode: CpuMode,
set_configs: IndexMap<usize, SetConfig<C, I>>, min_cores: Option<usize>,
max_cores: Option<usize>,
min_priority: Priority,
duration: usize,
summarize_last: usize,
mut set_configs: IndexMap<usize, SetConfig<C, I>>,
load_test_gen: F, load_test_gen: F,
) where ) where
C: ::std::fmt::Debug, C: ::std::fmt::Debug,
I: Tracker, I: Tracker,
F: Fn(usize) -> Box<dyn ProcessRunner<Command = C>>, F: Fn(LoadTestRunnerParameters) -> Box<dyn ProcessRunner<Command = C>>,
{ {
if let Some(min_cores) = min_cores {
set_configs.retain(|cores, _| *cores >= min_cores);
}
if let Some(max_cores) = max_cores {
set_configs.retain(|cores, _| *cores <= max_cores);
}
for set_config in set_configs.values_mut() {
for runners in set_config.implementations.values_mut() {
runners.retain(|r| r.priority() >= min_priority);
}
set_config
.load_test_runs
.retain(|(_, priority, _)| *priority >= min_priority);
}
println!("# Benchmark report"); println!("# Benchmark report");
let total_num_runs = set_configs let total_num_runs = set_configs
@ -39,7 +68,7 @@ pub fn run_sets<C, F, I>(
.sum::<usize>(); .sum::<usize>();
let (estimated_hours, estimated_minutes) = { let (estimated_hours, estimated_minutes) = {
let minutes = (total_num_runs * 67) / 60; let minutes = (total_num_runs * (duration + 7)) / 60;
(minutes / 60, minutes % 60) (minutes / 60, minutes % 60)
}; };
@ -75,14 +104,19 @@ pub fn run_sets<C, F, I>(
.load_test_runs .load_test_runs
.clone() .clone()
.into_iter() .into_iter()
.map(|(workers, load_test_vcpus)| { .map(|(workers, _, load_test_vcpus)| {
let load_test_parameters = LoadTestRunnerParameters {
workers,
duration,
summarize_last,
};
LoadTestRunResults::produce( LoadTestRunResults::produce(
command, command,
&load_test_gen, &load_test_gen,
load_test_parameters,
implementation, implementation,
&tracker_run, &tracker_run,
tracker_vcpus.clone(), tracker_vcpus.clone(),
workers,
load_test_vcpus, load_test_vcpus,
) )
}) })
@ -168,26 +202,26 @@ impl LoadTestRunResults {
pub fn produce<C, F, I>( pub fn produce<C, F, I>(
command: &C, command: &C,
load_test_gen: &F, load_test_gen: &F,
load_test_parameters: LoadTestRunnerParameters,
implementation: I, implementation: I,
tracker_process: &Rc<dyn ProcessRunner<Command = C>>, tracker_process: &Rc<dyn ProcessRunner<Command = C>>,
tracker_vcpus: TaskSetCpuList, tracker_vcpus: TaskSetCpuList,
workers: usize,
load_test_vcpus: TaskSetCpuList, load_test_vcpus: TaskSetCpuList,
) -> Self ) -> Self
where where
C: ::std::fmt::Debug, C: ::std::fmt::Debug,
I: Tracker, I: Tracker,
F: Fn(usize) -> Box<dyn ProcessRunner<Command = C>>, F: Fn(LoadTestRunnerParameters) -> Box<dyn ProcessRunner<Command = C>>,
{ {
println!( println!(
"### {} run ({}) (load test workers: {}, cpus: {})", "### {} run ({}) (load test workers: {}, cpus: {})",
implementation.name(), implementation.name(),
tracker_process.info(), tracker_process.info(),
workers, load_test_parameters.workers,
load_test_vcpus.as_cpu_list() load_test_vcpus.as_cpu_list()
); );
let load_test_runner = load_test_gen(workers); let load_test_runner = load_test_gen(load_test_parameters);
let load_test_keys = load_test_runner.keys(); let load_test_keys = load_test_runner.keys();
let run_config = RunConfig { let run_config = RunConfig {
@ -197,7 +231,7 @@ impl LoadTestRunResults {
load_test_vcpus: load_test_vcpus.clone(), load_test_vcpus: load_test_vcpus.clone(),
}; };
match run_config.run(command) { match run_config.run(command, load_test_parameters.duration) {
Ok(r) => { Ok(r) => {
println!( println!(
"- Average responses per second: {}", "- Average responses per second: {}",

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::hash::Hash; use std::hash::Hash;
use std::io::Write; use std::io::Write;
use std::mem::size_of;
use std::net::{SocketAddr, SocketAddrV4}; use std::net::{SocketAddr, SocketAddrV4};
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::Arc; use std::sync::Arc;
@ -101,6 +102,15 @@ pub struct ConnectedResponseWithAddr {
pub addr: CanonicalSocketAddr, pub addr: CanonicalSocketAddr,
} }
impl ConnectedResponseWithAddr {
pub fn estimated_max_size(config: &Config) -> usize {
size_of::<Self>()
+ config.protocol.max_response_peers
* (size_of::<ResponsePeer<Ipv4AddrBytes>>()
+ size_of::<ResponsePeer<Ipv6AddrBytes>>())
}
}
pub struct Recycler; pub struct Recycler;
impl thingbuf::Recycle<ConnectedResponseWithAddr> for Recycler { impl thingbuf::Recycle<ConnectedResponseWithAddr> for Recycler {

View file

@ -58,7 +58,7 @@ impl Default for Config {
socket_workers: 1, socket_workers: 1,
swarm_workers: 1, swarm_workers: 1,
log_level: LogLevel::Error, log_level: LogLevel::Error,
worker_channel_size: 1024 * 128, worker_channel_size: 1_024,
request_channel_recv_timeout_ms: 100, request_channel_recv_timeout_ms: 100,
network: NetworkConfig::default(), network: NetworkConfig::default(),
protocol: ProtocolConfig::default(), protocol: ProtocolConfig::default(),
@ -92,11 +92,11 @@ pub struct NetworkConfig {
/// values for different operating systems: /// values for different operating systems:
/// ///
/// macOS: /// macOS:
/// $ sudo sysctl net.inet.udp.recvspace=6000000 /// $ sudo sysctl net.inet.udp.recvspace=8000000
/// ///
/// Linux: /// Linux:
/// $ sudo sysctl -w net.core.rmem_max=104857600 /// $ sudo sysctl -w net.core.rmem_max=8000000
/// $ sudo sysctl -w net.core.rmem_default=104857600 /// $ sudo sysctl -w net.core.rmem_default=8000000
pub socket_recv_buffer_size: usize, pub socket_recv_buffer_size: usize,
/// Poll timeout in milliseconds (mio backend only) /// Poll timeout in milliseconds (mio backend only)
pub poll_timeout_ms: u64, pub poll_timeout_ms: u64,
@ -129,7 +129,7 @@ impl Default for NetworkConfig {
Self { Self {
address: SocketAddr::from(([0, 0, 0, 0], 3000)), address: SocketAddr::from(([0, 0, 0, 0], 3000)),
only_ipv6: false, only_ipv6: false,
socket_recv_buffer_size: 4096 * 128, socket_recv_buffer_size: 8_000_000,
poll_timeout_ms: 50, poll_timeout_ms: 50,
#[cfg(feature = "io-uring")] #[cfg(feature = "io-uring")]
ring_size: 1024, ring_size: 1024,

View file

@ -3,6 +3,7 @@ pub mod config;
pub mod workers; pub mod workers;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::mem::size_of;
use std::thread::Builder; use std::thread::Builder;
use std::time::Duration; use std::time::Duration;
@ -15,7 +16,7 @@ use aquatic_common::access_list::update_access_list;
#[cfg(feature = "cpu-pinning")] #[cfg(feature = "cpu-pinning")]
use aquatic_common::cpu_pinning::{pin_current_if_configured_to, WorkerIndex}; use aquatic_common::cpu_pinning::{pin_current_if_configured_to, WorkerIndex};
use aquatic_common::privileges::PrivilegeDropper; use aquatic_common::privileges::PrivilegeDropper;
use aquatic_common::{PanicSentinelWatcher, ServerStartInstant}; use aquatic_common::{CanonicalSocketAddr, PanicSentinelWatcher, ServerStartInstant};
use common::{ use common::{
ConnectedRequestSender, ConnectedResponseSender, Recycler, SocketWorkerIndex, State, ConnectedRequestSender, ConnectedResponseSender, Recycler, SocketWorkerIndex, State,
@ -24,12 +25,19 @@ use common::{
use config::Config; use config::Config;
use workers::socket::ConnectionValidator; use workers::socket::ConnectionValidator;
use crate::common::{ConnectedRequest, ConnectedResponseWithAddr};
pub const APP_NAME: &str = "aquatic_udp: UDP BitTorrent tracker"; pub const APP_NAME: &str = "aquatic_udp: UDP BitTorrent tracker";
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn run(config: Config) -> ::anyhow::Result<()> { pub fn run(config: Config) -> ::anyhow::Result<()> {
let mut signals = Signals::new([SIGUSR1, SIGTERM])?; let mut signals = Signals::new([SIGUSR1, SIGTERM])?;
::log::info!(
"Estimated max channel memory use: {:.02} MB",
est_max_total_channel_memory(&config)
);
let state = State::new(config.swarm_workers); let state = State::new(config.swarm_workers);
let connection_validator = ConnectionValidator::new(&config)?; let connection_validator = ConnectionValidator::new(&config)?;
let (sentinel_watcher, sentinel) = PanicSentinelWatcher::create_with_sentinel(); let (sentinel_watcher, sentinel) = PanicSentinelWatcher::create_with_sentinel();
@ -206,3 +214,16 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
Ok(()) Ok(())
} }
fn est_max_total_channel_memory(config: &Config) -> f64 {
let request_channel_max_size = config.swarm_workers
* config.worker_channel_size
* (size_of::<SocketWorkerIndex>()
+ size_of::<ConnectedRequest>()
+ size_of::<CanonicalSocketAddr>());
let response_channel_max_size = config.socket_workers
* config.worker_channel_size
* ConnectedResponseWithAddr::estimated_max_size(&config);
(request_channel_max_size as u64 + response_channel_max_size as u64) as f64 / (1024.0 * 1024.0)
}

View file

@ -125,7 +125,7 @@ impl SocketWorker {
self.read_and_handle_requests(pending_scrape_valid_until); self.read_and_handle_requests(pending_scrape_valid_until);
} }
PollMode::SkipReceiving => { PollMode::SkipReceiving => {
::log::info!("Postponing receiving requests because swarm worker channel is full. This means that the OS will be relied on to buffer incoming packets. To prevent this, raise config.worker_channel_size."); ::log::debug!("Postponing receiving requests because swarm worker channel is full. This means that the OS will be relied on to buffer incoming packets. To prevent this, raise config.worker_channel_size.");
self.polling_mode = PollMode::SkipPolling; self.polling_mode = PollMode::SkipPolling;
} }
@ -192,7 +192,7 @@ impl SocketWorker {
match self.socket.recv_from(&mut self.buffer[..]) { match self.socket.recv_from(&mut self.buffer[..]) {
Ok((bytes_read, src)) => { Ok((bytes_read, src)) => {
if src.port() == 0 { if src.port() == 0 {
::log::info!("Ignored request from {} because source port is zero", src); ::log::debug!("Ignored request from {} because source port is zero", src);
continue; continue;
} }
@ -501,7 +501,7 @@ impl SocketWorker {
|| (err.kind() == ErrorKind::WouldBlock) => || (err.kind() == ErrorKind::WouldBlock) =>
{ {
if resend_buffer.len() < config.network.resend_buffer_max_len { if resend_buffer.len() < config.network.resend_buffer_max_len {
::log::info!("Adding response to resend queue, since sending it to {} failed with: {:#}", addr, err); ::log::debug!("Adding response to resend queue, since sending it to {} failed with: {:#}", addr, err);
resend_buffer.push((response.into_owned(), canonical_addr)); resend_buffer.push((response.into_owned(), canonical_addr));
} else { } else {

View file

@ -157,7 +157,7 @@ impl<I: Ip> TorrentMap<I> {
} }
pub struct TorrentData<I: Ip> { pub struct TorrentData<I: Ip> {
peers: IndexMap<PeerId, Peer<I>>, peers: IndexMap<ResponsePeer<I>, Peer>,
num_seeders: usize, num_seeders: usize,
} }
@ -184,7 +184,12 @@ impl<I: Ip> TorrentData<I> {
let status = let status =
PeerStatus::from_event_and_bytes_left(request.event.into(), request.bytes_left); PeerStatus::from_event_and_bytes_left(request.event.into(), request.bytes_left);
let opt_removed_peer = self.peers.remove(&request.peer_id); let peer_map_key = ResponsePeer {
ip_address,
port: request.port,
};
let opt_removed_peer = self.peers.remove(&peer_map_key);
if let Some(Peer { if let Some(Peer {
is_seeder: true, .. is_seeder: true, ..
@ -208,20 +213,19 @@ impl<I: Ip> TorrentData<I> {
rng, rng,
&self.peers, &self.peers,
max_num_peers_to_take, max_num_peers_to_take,
Peer::to_response_peer, |k, _| *k,
&mut response.peers, &mut response.peers,
); );
match status { match status {
PeerStatus::Leeching => { PeerStatus::Leeching => {
let peer = Peer { let peer = Peer {
ip_address, peer_id: request.peer_id,
port: request.port,
is_seeder: false, is_seeder: false,
valid_until, valid_until,
}; };
self.peers.insert(request.peer_id, peer); self.peers.insert(peer_map_key, peer);
if config.statistics.peer_clients && opt_removed_peer.is_none() { if config.statistics.peer_clients && opt_removed_peer.is_none() {
statistics_sender statistics_sender
@ -231,13 +235,12 @@ impl<I: Ip> TorrentData<I> {
} }
PeerStatus::Seeding => { PeerStatus::Seeding => {
let peer = Peer { let peer = Peer {
ip_address, peer_id: request.peer_id,
port: request.port,
is_seeder: true, is_seeder: true,
valid_until, valid_until,
}; };
self.peers.insert(request.peer_id, peer); self.peers.insert(peer_map_key, peer);
self.num_seeders += 1; self.num_seeders += 1;
@ -279,7 +282,7 @@ impl<I: Ip> TorrentData<I> {
statistics_sender: &Sender<StatisticsMessage>, statistics_sender: &Sender<StatisticsMessage>,
now: SecondsSinceServerStart, now: SecondsSinceServerStart,
) { ) {
self.peers.retain(|peer_id, peer| { self.peers.retain(|_, peer| {
let keep = peer.valid_until.valid(now); let keep = peer.valid_until.valid(now);
if !keep { if !keep {
@ -288,7 +291,7 @@ impl<I: Ip> TorrentData<I> {
} }
if config.statistics.peer_clients { if config.statistics.peer_clients {
if let Err(_) = if let Err(_) =
statistics_sender.try_send(StatisticsMessage::PeerRemoved(*peer_id)) statistics_sender.try_send(StatisticsMessage::PeerRemoved(peer.peer_id))
{ {
// Should never happen in practice // Should never happen in practice
::log::error!("Couldn't send StatisticsMessage::PeerRemoved"); ::log::error!("Couldn't send StatisticsMessage::PeerRemoved");
@ -315,22 +318,12 @@ impl<I: Ip> Default for TorrentData<I> {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Peer<I: Ip> { struct Peer {
ip_address: I, peer_id: PeerId,
port: Port,
is_seeder: bool, is_seeder: bool,
valid_until: ValidUntil, valid_until: ValidUntil,
} }
impl<I: Ip> Peer<I> {
fn to_response_peer(_: &PeerId, peer: &Self) -> ResponsePeer<I> {
ResponsePeer {
ip_address: peer.ip_address,
port: peer.port,
}
}
}
/// Extract response peers /// Extract response peers
/// ///
/// If there are more peers in map than `max_num_peers_to_take`, do a random /// If there are more peers in map than `max_num_peers_to_take`, do a random

View file

@ -7,7 +7,7 @@ use aquatic_udp_protocol::*;
#[derive(PartialEq, Eq, Clone)] #[derive(PartialEq, Eq, Clone)]
pub struct TorrentPeer { pub struct TorrentPeer {
pub info_hash: InfoHash, pub info_hash: InfoHash,
pub scrape_hash_indeces: Vec<usize>, pub scrape_hash_indices: Box<[usize]>,
pub connection_id: ConnectionId, pub connection_id: ConnectionId,
pub peer_id: PeerId, pub peer_id: PeerId,
pub port: Port, pub port: Port,
@ -27,7 +27,7 @@ pub struct Statistics {
#[derive(Clone)] #[derive(Clone)]
pub struct LoadTestState { pub struct LoadTestState {
pub info_hashes: Arc<Vec<InfoHash>>, pub info_hashes: Arc<[InfoHash]>,
pub statistics: Arc<Statistics>, pub statistics: Arc<Statistics>,
} }

View file

@ -21,6 +21,10 @@ pub struct Config {
pub workers: u8, pub workers: u8,
/// Run duration (quit and generate report after this many seconds) /// Run duration (quit and generate report after this many seconds)
pub duration: usize, pub duration: usize,
/// Only report summary for the last N seconds of run
///
/// 0 = include whole run
pub summarize_last: usize,
pub network: NetworkConfig, pub network: NetworkConfig,
pub requests: RequestConfig, pub requests: RequestConfig,
#[cfg(feature = "cpu-pinning")] #[cfg(feature = "cpu-pinning")]
@ -34,6 +38,7 @@ impl Default for Config {
log_level: LogLevel::Error, log_level: LogLevel::Error,
workers: 1, workers: 1,
duration: 0, duration: 0,
summarize_last: 0,
network: NetworkConfig::default(), network: NetworkConfig::default(),
requests: RequestConfig::default(), requests: RequestConfig::default(),
#[cfg(feature = "cpu-pinning")] #[cfg(feature = "cpu-pinning")]
@ -63,11 +68,11 @@ pub struct NetworkConfig {
/// values for different operating systems: /// values for different operating systems:
/// ///
/// macOS: /// macOS:
/// $ sudo sysctl net.inet.udp.recvspace=6000000 /// $ sudo sysctl net.inet.udp.recvspace=8000000
/// ///
/// Linux: /// Linux:
/// $ sudo sysctl -w net.core.rmem_max=104857600 /// $ sudo sysctl -w net.core.rmem_max=8000000
/// $ sudo sysctl -w net.core.rmem_default=104857600 /// $ sudo sysctl -w net.core.rmem_default=8000000
pub recv_buffer: usize, pub recv_buffer: usize,
} }
@ -76,8 +81,8 @@ impl Default for NetworkConfig {
Self { Self {
multiple_client_ipv4s: true, multiple_client_ipv4s: true,
first_port: 45_000, first_port: 45_000,
poll_timeout: 276, poll_timeout: 1,
recv_buffer: 6_000_000, recv_buffer: 8_000_000,
} }
} }
} }
@ -89,6 +94,8 @@ pub struct RequestConfig {
pub number_of_torrents: usize, pub number_of_torrents: usize,
/// Maximum number of torrents to ask about in scrape requests /// Maximum number of torrents to ask about in scrape requests
pub scrape_max_torrents: usize, pub scrape_max_torrents: usize,
/// Ask for this number of peers in announce requests
pub announce_peers_wanted: i32,
/// Probability that a generated request is a connect request as part /// Probability that a generated request is a connect request as part
/// of sum of the various weight arguments. /// of sum of the various weight arguments.
pub weight_connect: usize, pub weight_connect: usize,
@ -113,13 +120,14 @@ impl Default for RequestConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
number_of_torrents: 10_000, number_of_torrents: 10_000,
scrape_max_torrents: 50, scrape_max_torrents: 10,
announce_peers_wanted: 30,
weight_connect: 0, weight_connect: 0,
weight_announce: 100, weight_announce: 100,
weight_scrape: 1, weight_scrape: 1,
torrent_gamma_shape: 0.2, torrent_gamma_shape: 0.2,
torrent_gamma_scale: 100.0, torrent_gamma_scale: 100.0,
peer_seeder_probability: 0.25, peer_seeder_probability: 0.75,
additional_request_probability: 0.5, additional_request_probability: 0.5,
} }
} }

View file

@ -33,6 +33,10 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
panic!("Error: at least one weight must be larger than zero."); panic!("Error: at least one weight must be larger than zero.");
} }
if config.summarize_last > config.duration {
panic!("Error: report_last_seconds can't be larger than duration");
}
println!("Starting client with config: {:#?}", config); println!("Starting client with config: {:#?}", config);
let mut info_hashes = Vec::with_capacity(config.requests.number_of_torrents); let mut info_hashes = Vec::with_capacity(config.requests.number_of_torrents);
@ -42,7 +46,7 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
} }
let state = LoadTestState { let state = LoadTestState {
info_hashes: Arc::new(info_hashes), info_hashes: Arc::from(info_hashes.into_boxed_slice()),
statistics: Arc::new(Statistics::default()), statistics: Arc::new(Statistics::default()),
}; };
@ -80,7 +84,7 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
WorkerIndex::SocketWorker(i as usize), WorkerIndex::SocketWorker(i as usize),
); );
run_worker_thread(state, gamma, &config, addr) Worker::run(state, gamma, config, addr)
})?; })?;
} }
@ -103,7 +107,7 @@ fn monitor_statistics(state: LoadTestState, config: &Config) {
let mut report_avg_scrape: Vec<f64> = Vec::new(); let mut report_avg_scrape: Vec<f64> = Vec::new();
let mut report_avg_error: Vec<f64> = Vec::new(); let mut report_avg_error: Vec<f64> = Vec::new();
let interval = 5; const INTERVAL: u64 = 5;
let start_time = Instant::now(); let start_time = Instant::now();
let duration = Duration::from_secs(config.duration as u64); let duration = Duration::from_secs(config.duration as u64);
@ -111,7 +115,7 @@ fn monitor_statistics(state: LoadTestState, config: &Config) {
let mut last = start_time; let mut last = start_time;
let time_elapsed = loop { let time_elapsed = loop {
thread::sleep(Duration::from_secs(interval)); thread::sleep(Duration::from_secs(INTERVAL));
let requests = fetch_and_reset(&state.statistics.requests); let requests = fetch_and_reset(&state.statistics.requests);
let response_peers = fetch_and_reset(&state.statistics.response_peers); let response_peers = fetch_and_reset(&state.statistics.response_peers);
@ -163,6 +167,15 @@ fn monitor_statistics(state: LoadTestState, config: &Config) {
} }
}; };
if config.summarize_last != 0 {
let split_at = (config.duration - config.summarize_last) / INTERVAL as usize;
report_avg_connect = report_avg_connect.split_off(split_at);
report_avg_announce = report_avg_announce.split_off(split_at);
report_avg_scrape = report_avg_scrape.split_off(split_at);
report_avg_error = report_avg_error.split_off(split_at);
}
let len = report_avg_connect.len() as f64; let len = report_avg_connect.len() as f64;
let avg_connect: f64 = report_avg_connect.into_iter().sum::<f64>() / len; let avg_connect: f64 = report_avg_connect.into_iter().sum::<f64>() / len;
@ -175,7 +188,15 @@ fn monitor_statistics(state: LoadTestState, config: &Config) {
println!(); println!();
println!("# aquatic load test report"); println!("# aquatic load test report");
println!(); println!();
println!("Test ran for {} seconds", time_elapsed.as_secs()); println!(
"Test ran for {} seconds {}",
time_elapsed.as_secs(),
if config.summarize_last != 0 {
format!("(only last {} included in summary)", config.summarize_last)
} else {
"".to_string()
}
);
println!("Average responses per second: {:.2}", avg_total); println!("Average responses per second: {:.2}", avg_total);
println!(" - Connect responses: {:.2}", avg_connect); println!(" - Connect responses: {:.2}", avg_connect);
println!(" - Announce responses: {:.2}", avg_announce); println!(" - Announce responses: {:.2}", avg_announce);

View file

@ -1,5 +1,3 @@
mod request_gen;
use std::io::Cursor; use std::io::Cursor;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -7,88 +5,76 @@ use std::time::Duration;
use mio::{net::UdpSocket, Events, Interest, Poll, Token}; use mio::{net::UdpSocket, Events, Interest, Poll, Token};
use rand::Rng; use rand::Rng;
use rand::{prelude::SmallRng, thread_rng, SeedableRng}; use rand::{prelude::SmallRng, SeedableRng};
use rand_distr::Gamma; use rand_distr::{Distribution, Gamma, WeightedIndex};
use socket2::{Domain, Protocol, Socket, Type}; use socket2::{Domain, Protocol, Socket, Type};
use aquatic_udp_protocol::*; use aquatic_udp_protocol::*;
use crate::config::Config; use crate::config::Config;
use crate::{common::*, utils::*}; use crate::{common::*, utils::*};
use request_gen::process_response;
const MAX_PACKET_SIZE: usize = 8192; const MAX_PACKET_SIZE: usize = 8192;
pub fn run_worker_thread( pub struct Worker {
state: LoadTestState, config: Config,
shared_state: LoadTestState,
gamma: Gamma<f64>, gamma: Gamma<f64>,
config: &Config,
addr: SocketAddr, addr: SocketAddr,
) { socket: UdpSocket,
let mut socket = UdpSocket::from_std(create_socket(config, addr)); buffer: [u8; MAX_PACKET_SIZE],
let mut buffer = [0u8; MAX_PACKET_SIZE]; rng: SmallRng,
torrent_peers: TorrentPeerMap,
statistics: SocketWorkerLocalStatistics,
}
let mut rng = SmallRng::from_rng(thread_rng()).expect("create SmallRng from thread_rng()"); impl Worker {
let mut torrent_peers = TorrentPeerMap::default(); pub fn run(shared_state: LoadTestState, gamma: Gamma<f64>, config: Config, addr: SocketAddr) {
let socket = UdpSocket::from_std(create_socket(&config, addr));
let buffer = [0u8; MAX_PACKET_SIZE];
let rng = SmallRng::seed_from_u64(0xc3aa8be617b3acce);
let torrent_peers = TorrentPeerMap::default();
let statistics = SocketWorkerLocalStatistics::default();
let token = Token(0); let mut instance = Self {
let interests = Interest::READABLE; config,
let timeout = Duration::from_micros(config.network.poll_timeout); shared_state,
gamma,
addr,
socket,
buffer,
rng,
torrent_peers,
statistics,
};
let mut poll = Poll::new().expect("create poll"); instance.run_inner();
}
poll.registry() fn run_inner(&mut self) {
.register(&mut socket, token, interests) let mut poll = Poll::new().expect("create poll");
.unwrap(); let mut events = Events::with_capacity(1);
let mut events = Events::with_capacity(1); poll.registry()
.register(&mut self.socket, Token(0), Interest::READABLE)
.unwrap();
let mut statistics = SocketWorkerLocalStatistics::default(); // Bootstrap request cycle
let initial_request = create_connect_request(generate_transaction_id(&mut self.rng));
self.send_request(initial_request);
// Bootstrap request cycle let timeout = Duration::from_micros(self.config.network.poll_timeout);
let initial_request = create_connect_request(generate_transaction_id(&mut thread_rng()));
send_request(&mut socket, &mut buffer, &mut statistics, initial_request);
loop { loop {
poll.poll(&mut events, Some(timeout)) poll.poll(&mut events, Some(timeout))
.expect("failed polling"); .expect("failed polling");
for event in events.iter() { for _ in events.iter() {
if (event.token() == token) & event.is_readable() { while let Ok(amt) = self.socket.recv(&mut self.buffer) {
while let Ok(amt) = socket.recv(&mut buffer) { match Response::from_bytes(&self.buffer[0..amt], self.addr.is_ipv4()) {
match Response::from_bytes(&buffer[0..amt], addr.is_ipv4()) {
Ok(response) => { Ok(response) => {
match response { if let Some(request) = self.process_response(response) {
Response::AnnounceIpv4(ref r) => { self.send_request(request);
statistics.responses_announce += 1;
statistics.response_peers += r.peers.len();
}
Response::AnnounceIpv6(ref r) => {
statistics.responses_announce += 1;
statistics.response_peers += r.peers.len();
}
Response::Scrape(_) => {
statistics.responses_scrape += 1;
}
Response::Connect(_) => {
statistics.responses_connect += 1;
}
Response::Error(_) => {
statistics.responses_error += 1;
}
}
let opt_request = process_response(
&mut rng,
gamma,
&state.info_hashes,
&config,
&mut torrent_peers,
response,
);
if let Some(request) = opt_request {
send_request(&mut socket, &mut buffer, &mut statistics, request);
} }
} }
Err(err) => { Err(err) => {
@ -97,79 +83,259 @@ pub fn run_worker_thread(
} }
} }
if rng.gen::<f32>() <= config.requests.additional_request_probability { if self.rng.gen::<f32>() <= self.config.requests.additional_request_probability {
let additional_request = let additional_request =
create_connect_request(generate_transaction_id(&mut rng)); create_connect_request(generate_transaction_id(&mut self.rng));
send_request( self.send_request(additional_request);
&mut socket, }
&mut buffer,
&mut statistics, self.update_shared_statistics();
additional_request, }
}
}
fn process_response(&mut self, response: Response) -> Option<Request> {
match response {
Response::Connect(r) => {
self.statistics.responses_connect += 1;
// Fetch the torrent peer or create it if is doesn't exists. Update
// the connection id if fetched. Create a request and move the
// torrent peer appropriately.
let mut torrent_peer = self
.torrent_peers
.remove(&r.transaction_id)
.unwrap_or_else(|| self.create_torrent_peer(r.connection_id));
torrent_peer.connection_id = r.connection_id;
let new_transaction_id = generate_transaction_id(&mut self.rng);
let request = self.create_random_request(new_transaction_id, &torrent_peer);
self.torrent_peers.insert(new_transaction_id, torrent_peer);
Some(request)
}
Response::AnnounceIpv4(r) => {
self.statistics.responses_announce += 1;
self.statistics.response_peers += r.peers.len();
self.if_torrent_peer_move_and_create_random_request(r.fixed.transaction_id)
}
Response::AnnounceIpv6(r) => {
self.statistics.responses_announce += 1;
self.statistics.response_peers += r.peers.len();
self.if_torrent_peer_move_and_create_random_request(r.fixed.transaction_id)
}
Response::Scrape(r) => {
self.statistics.responses_scrape += 1;
self.if_torrent_peer_move_and_create_random_request(r.transaction_id)
}
Response::Error(r) => {
self.statistics.responses_error += 1;
if !r.message.to_lowercase().contains("connection") {
eprintln!(
"Received error response which didn't contain the word 'connection': {}",
r.message
); );
} }
update_shared_statistics(&state, &mut statistics); if let Some(torrent_peer) = self.torrent_peers.remove(&r.transaction_id) {
} let new_transaction_id = generate_transaction_id(&mut self.rng);
}
}
}
fn send_request( self.torrent_peers.insert(new_transaction_id, torrent_peer);
socket: &mut UdpSocket,
buffer: &mut [u8],
statistics: &mut SocketWorkerLocalStatistics,
request: Request,
) {
let mut cursor = Cursor::new(buffer);
match request.write(&mut cursor) { Some(create_connect_request(new_transaction_id))
Ok(()) => { } else {
let position = cursor.position() as usize; Some(create_connect_request(generate_transaction_id(
let inner = cursor.get_ref(); &mut self.rng,
)))
match socket.send(&inner[..position]) {
Ok(_) => {
statistics.requests += 1;
}
Err(err) => {
eprintln!("Couldn't send packet: {:?}", err);
} }
} }
} }
Err(err) => { }
eprintln!("request_to_bytes err: {}", err);
fn if_torrent_peer_move_and_create_random_request(
&mut self,
transaction_id: TransactionId,
) -> Option<Request> {
let torrent_peer = self.torrent_peers.remove(&transaction_id)?;
let new_transaction_id = generate_transaction_id(&mut self.rng);
let request = self.create_random_request(new_transaction_id, &torrent_peer);
self.torrent_peers.insert(new_transaction_id, torrent_peer);
Some(request)
}
fn create_torrent_peer(&mut self, connection_id: ConnectionId) -> TorrentPeer {
let num_scrape_hashes = self
.rng
.gen_range(1..self.config.requests.scrape_max_torrents);
let scrape_hash_indices = (0..num_scrape_hashes)
.map(|_| self.random_info_hash_index())
.collect::<Vec<_>>()
.into_boxed_slice();
let info_hash_index = self.random_info_hash_index();
TorrentPeer {
info_hash: self.shared_state.info_hashes[info_hash_index],
scrape_hash_indices,
connection_id,
peer_id: generate_peer_id(),
port: Port::new(self.rng.gen()),
} }
} }
}
fn update_shared_statistics(state: &LoadTestState, statistics: &mut SocketWorkerLocalStatistics) { fn create_random_request(
state &mut self,
.statistics transaction_id: TransactionId,
.requests torrent_peer: &TorrentPeer,
.fetch_add(statistics.requests, Ordering::Relaxed); ) -> Request {
state const ITEMS: [RequestType; 3] = [
.statistics RequestType::Announce,
.responses_connect RequestType::Connect,
.fetch_add(statistics.responses_connect, Ordering::Relaxed); RequestType::Scrape,
state ];
.statistics
.responses_announce
.fetch_add(statistics.responses_announce, Ordering::Relaxed);
state
.statistics
.responses_scrape
.fetch_add(statistics.responses_scrape, Ordering::Relaxed);
state
.statistics
.responses_error
.fetch_add(statistics.responses_error, Ordering::Relaxed);
state
.statistics
.response_peers
.fetch_add(statistics.response_peers, Ordering::Relaxed);
*statistics = SocketWorkerLocalStatistics::default(); let weights = [
self.config.requests.weight_announce as u32,
self.config.requests.weight_connect as u32,
self.config.requests.weight_scrape as u32,
];
let dist = WeightedIndex::new(weights).expect("random request weighted index");
match ITEMS[dist.sample(&mut self.rng)] {
RequestType::Announce => self.create_announce_request(torrent_peer, transaction_id),
RequestType::Connect => (ConnectRequest { transaction_id }).into(),
RequestType::Scrape => self.create_scrape_request(torrent_peer, transaction_id),
}
}
fn create_announce_request(
&mut self,
torrent_peer: &TorrentPeer,
transaction_id: TransactionId,
) -> Request {
let (event, bytes_left) = {
if self
.rng
.gen_bool(self.config.requests.peer_seeder_probability)
{
(AnnounceEvent::Completed, NumberOfBytes::new(0))
} else {
(AnnounceEvent::Started, NumberOfBytes::new(50))
}
};
(AnnounceRequest {
connection_id: torrent_peer.connection_id,
action_placeholder: Default::default(),
transaction_id,
info_hash: torrent_peer.info_hash,
peer_id: torrent_peer.peer_id,
bytes_downloaded: NumberOfBytes::new(50),
bytes_uploaded: NumberOfBytes::new(50),
bytes_left,
event: event.into(),
ip_address: Ipv4AddrBytes([0; 4]),
key: PeerKey::new(0),
peers_wanted: NumberOfPeers::new(self.config.requests.announce_peers_wanted),
port: torrent_peer.port,
})
.into()
}
fn create_scrape_request(
&self,
torrent_peer: &TorrentPeer,
transaction_id: TransactionId,
) -> Request {
let indeces = &torrent_peer.scrape_hash_indices;
let mut scape_hashes = Vec::with_capacity(indeces.len());
for i in indeces.iter() {
scape_hashes.push(self.shared_state.info_hashes[*i].to_owned())
}
(ScrapeRequest {
connection_id: torrent_peer.connection_id,
transaction_id,
info_hashes: scape_hashes,
})
.into()
}
fn random_info_hash_index(&mut self) -> usize {
gamma_usize(
&mut self.rng,
self.gamma,
&self.config.requests.number_of_torrents - 1,
)
}
fn send_request(&mut self, request: Request) {
let mut cursor = Cursor::new(self.buffer);
match request.write(&mut cursor) {
Ok(()) => {
let position = cursor.position() as usize;
let inner = cursor.get_ref();
match self.socket.send(&inner[..position]) {
Ok(_) => {
self.statistics.requests += 1;
}
Err(err) => {
eprintln!("Couldn't send packet: {:?}", err);
}
}
}
Err(err) => {
eprintln!("request_to_bytes err: {}", err);
}
}
}
fn update_shared_statistics(&mut self) {
self.shared_state
.statistics
.requests
.fetch_add(self.statistics.requests, Ordering::Relaxed);
self.shared_state
.statistics
.responses_connect
.fetch_add(self.statistics.responses_connect, Ordering::Relaxed);
self.shared_state
.statistics
.responses_announce
.fetch_add(self.statistics.responses_announce, Ordering::Relaxed);
self.shared_state
.statistics
.responses_scrape
.fetch_add(self.statistics.responses_scrape, Ordering::Relaxed);
self.shared_state
.statistics
.responses_error
.fetch_add(self.statistics.responses_error, Ordering::Relaxed);
self.shared_state
.statistics
.response_peers
.fetch_add(self.statistics.response_peers, Ordering::Relaxed);
self.statistics = SocketWorkerLocalStatistics::default();
}
} }
fn create_socket(config: &Config, addr: SocketAddr) -> ::std::net::UdpSocket { fn create_socket(config: &Config, addr: SocketAddr) -> ::std::net::UdpSocket {

View file

@ -1,219 +0,0 @@
use std::sync::Arc;
use rand::distributions::WeightedIndex;
use rand::prelude::*;
use rand_distr::Gamma;
use aquatic_udp_protocol::*;
use crate::common::*;
use crate::config::Config;
use crate::utils::*;
pub fn process_response(
rng: &mut impl Rng,
gamma: Gamma<f64>,
info_hashes: &Arc<Vec<InfoHash>>,
config: &Config,
torrent_peers: &mut TorrentPeerMap,
response: Response,
) -> Option<Request> {
match response {
Response::Connect(r) => {
// Fetch the torrent peer or create it if is doesn't exists. Update
// the connection id if fetched. Create a request and move the
// torrent peer appropriately.
let torrent_peer = torrent_peers
.remove(&r.transaction_id)
.map(|mut torrent_peer| {
torrent_peer.connection_id = r.connection_id;
torrent_peer
})
.unwrap_or_else(|| {
create_torrent_peer(config, rng, gamma, info_hashes, r.connection_id)
});
let new_transaction_id = generate_transaction_id(rng);
let request =
create_random_request(config, rng, info_hashes, new_transaction_id, &torrent_peer);
torrent_peers.insert(new_transaction_id, torrent_peer);
Some(request)
}
Response::AnnounceIpv4(r) => if_torrent_peer_move_and_create_random_request(
config,
rng,
info_hashes,
torrent_peers,
r.fixed.transaction_id,
),
Response::AnnounceIpv6(r) => if_torrent_peer_move_and_create_random_request(
config,
rng,
info_hashes,
torrent_peers,
r.fixed.transaction_id,
),
Response::Scrape(r) => if_torrent_peer_move_and_create_random_request(
config,
rng,
info_hashes,
torrent_peers,
r.transaction_id,
),
Response::Error(r) => {
if !r.message.to_lowercase().contains("connection") {
eprintln!(
"Received error response which didn't contain the word 'connection': {}",
r.message
);
}
if let Some(torrent_peer) = torrent_peers.remove(&r.transaction_id) {
let new_transaction_id = generate_transaction_id(rng);
torrent_peers.insert(new_transaction_id, torrent_peer);
Some(create_connect_request(new_transaction_id))
} else {
Some(create_connect_request(generate_transaction_id(rng)))
}
}
}
}
fn if_torrent_peer_move_and_create_random_request(
config: &Config,
rng: &mut impl Rng,
info_hashes: &Arc<Vec<InfoHash>>,
torrent_peers: &mut TorrentPeerMap,
transaction_id: TransactionId,
) -> Option<Request> {
if let Some(torrent_peer) = torrent_peers.remove(&transaction_id) {
let new_transaction_id = generate_transaction_id(rng);
let request =
create_random_request(config, rng, info_hashes, new_transaction_id, &torrent_peer);
torrent_peers.insert(new_transaction_id, torrent_peer);
Some(request)
} else {
None
}
}
fn create_random_request(
config: &Config,
rng: &mut impl Rng,
info_hashes: &Arc<Vec<InfoHash>>,
transaction_id: TransactionId,
torrent_peer: &TorrentPeer,
) -> Request {
const ITEMS: [RequestType; 3] = [
RequestType::Announce,
RequestType::Connect,
RequestType::Scrape,
];
let weights = [
config.requests.weight_announce as u32,
config.requests.weight_connect as u32,
config.requests.weight_scrape as u32,
];
let dist = WeightedIndex::new(weights).expect("random request weighted index");
match ITEMS[dist.sample(rng)] {
RequestType::Announce => create_announce_request(config, rng, torrent_peer, transaction_id),
RequestType::Connect => create_connect_request(transaction_id),
RequestType::Scrape => create_scrape_request(&info_hashes, torrent_peer, transaction_id),
}
}
fn create_announce_request(
config: &Config,
rng: &mut impl Rng,
torrent_peer: &TorrentPeer,
transaction_id: TransactionId,
) -> Request {
let (event, bytes_left) = {
if rng.gen_bool(config.requests.peer_seeder_probability) {
(AnnounceEvent::Completed, NumberOfBytes::new(0))
} else {
(AnnounceEvent::Started, NumberOfBytes::new(50))
}
};
(AnnounceRequest {
connection_id: torrent_peer.connection_id,
action_placeholder: Default::default(),
transaction_id,
info_hash: torrent_peer.info_hash,
peer_id: torrent_peer.peer_id,
bytes_downloaded: NumberOfBytes::new(50),
bytes_uploaded: NumberOfBytes::new(50),
bytes_left,
event: event.into(),
ip_address: Ipv4AddrBytes([0; 4]),
key: PeerKey::new(12345),
peers_wanted: NumberOfPeers::new(100),
port: torrent_peer.port,
})
.into()
}
fn create_scrape_request(
info_hashes: &Arc<Vec<InfoHash>>,
torrent_peer: &TorrentPeer,
transaction_id: TransactionId,
) -> Request {
let indeces = &torrent_peer.scrape_hash_indeces;
let mut scape_hashes = Vec::with_capacity(indeces.len());
for i in indeces {
scape_hashes.push(info_hashes[*i].to_owned())
}
(ScrapeRequest {
connection_id: torrent_peer.connection_id,
transaction_id,
info_hashes: scape_hashes,
})
.into()
}
fn create_torrent_peer(
config: &Config,
rng: &mut impl Rng,
gamma: Gamma<f64>,
info_hashes: &Arc<Vec<InfoHash>>,
connection_id: ConnectionId,
) -> TorrentPeer {
let num_scape_hashes = rng.gen_range(1..config.requests.scrape_max_torrents);
let mut scrape_hash_indeces = Vec::new();
for _ in 0..num_scape_hashes {
scrape_hash_indeces.push(select_info_hash_index(config, rng, gamma))
}
let info_hash_index = select_info_hash_index(config, rng, gamma);
TorrentPeer {
info_hash: info_hashes[info_hash_index],
scrape_hash_indeces,
connection_id,
peer_id: generate_peer_id(),
port: Port::new(rng.gen()),
}
}
fn select_info_hash_index(config: &Config, rng: &mut impl Rng, gamma: Gamma<f64>) -> usize {
gamma_usize(rng, gamma, config.requests.number_of_torrents - 1)
}

View file

@ -5,7 +5,7 @@ pub use aquatic_peer_id::{PeerClient, PeerId};
use zerocopy::network_endian::{I32, I64, U16, U32}; use zerocopy::network_endian::{I32, I64, U16, U32};
use zerocopy::{AsBytes, FromBytes, FromZeroes}; use zerocopy::{AsBytes, FromBytes, FromZeroes};
pub trait Ip: Clone + Copy + Debug + PartialEq + Eq + AsBytes {} pub trait Ip: Clone + Copy + Debug + PartialEq + Eq + std::hash::Hash + AsBytes {}
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, AsBytes, FromBytes, FromZeroes)] #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, AsBytes, FromBytes, FromZeroes)]
#[repr(transparent)] #[repr(transparent)]
@ -91,7 +91,7 @@ impl PeerKey {
} }
} }
#[derive(PartialEq, Eq, Clone, Copy, Debug, AsBytes, FromBytes, FromZeroes)] #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, AsBytes, FromBytes, FromZeroes)]
#[repr(C, packed)] #[repr(C, packed)]
pub struct ResponsePeer<I: Ip> { pub struct ResponsePeer<I: Ip> {
pub ip_address: I, pub ip_address: I,

View file

@ -14,6 +14,7 @@ source "$HOME/.cargo/env"
cargo build --profile "release-debug" -p aquatic_udp cargo build --profile "release-debug" -p aquatic_udp
cargo build --profile "release-debug" -p aquatic_udp_load_test cargo build --profile "release-debug" -p aquatic_udp_load_test
cargo build --profile "release-debug" -p aquatic_bencher --features udp cargo build --profile "release-debug" -p aquatic_bencher --features udp
git log --oneline | head -n 1
cd $HOME cd $HOME
mkdir -p projects mkdir -p projects
@ -27,9 +28,9 @@ cd ..
git clone git://erdgeist.org/opentracker git clone git://erdgeist.org/opentracker
cd opentracker cd opentracker
sed -i "s/^OPTS_production=-O3/OPTS_production=-O3 -march=native -mtune=native/g" Makefile sed -i "s/^OPTS_production=-O3/OPTS_production=-O3 -march=native -mtune=native/g" Makefile
sed -i "s/if \(numwant > 200\) numwant = 200/if (numwant > 50) numwant = 50/g" ot_udp.c
make make
sudo cp ./opentracker /usr/local/bin/ sudo cp ./opentracker /usr/local/bin/
git log --oneline | head -n 1
cd .. cd ..
# Install chihaya # Install chihaya
@ -37,6 +38,12 @@ git clone https://github.com/chihaya/chihaya.git
cd chihaya cd chihaya
go build ./cmd/chihaya go build ./cmd/chihaya
sudo cp ./chihaya /usr/local/bin/ sudo cp ./chihaya /usr/local/bin/
git log --oneline | head -n 1
cd .. cd ..
rustc --version
gcc --version
go version
lscpu
echo "Finished. Reboot before running aquatic_bencher" echo "Finished. Reboot before running aquatic_bencher"