add aquatic crate with master executable, refactor cli_helpers

This commit is contained in:
Joakim Frostegård 2020-08-13 00:13:01 +02:00
parent c69b09ab9c
commit 9efc1fc66a
17 changed files with 243 additions and 62 deletions

32
Cargo.lock generated
View file

@ -36,12 +36,22 @@ version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
[[package]]
name = "aquatic"
version = "0.1.0"
dependencies = [
"aquatic_cli_helpers",
"aquatic_http",
"aquatic_udp",
"aquatic_ws",
"mimalloc",
]
[[package]] [[package]]
name = "aquatic_cli_helpers" name = "aquatic_cli_helpers"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"gumdrop",
"serde", "serde",
"simplelog", "simplelog",
"toml", "toml",
@ -739,26 +749,6 @@ version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
[[package]]
name = "gumdrop"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b"
dependencies = [
"gumdrop_derive",
]
[[package]]
name = "gumdrop_derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "half" name = "half"
version = "1.6.0" version = "1.6.0"

View file

@ -1,6 +1,7 @@
[workspace] [workspace]
members = [ members = [
"aquatic",
"aquatic_cli_helpers", "aquatic_cli_helpers",
"aquatic_common", "aquatic_common",
"aquatic_http", "aquatic_http",

16
aquatic/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "aquatic"
version = "0.1.0"
authors = ["Joakim Frostegård <joakim.frostegard@gmail.com>"]
edition = "2018"
license = "Apache-2.0"
[[bin]]
name = "aquatic"
[dependencies]
aquatic_cli_helpers = { path = "../aquatic_cli_helpers" }
aquatic_http = { path = "../aquatic_http" }
aquatic_udp = { path = "../aquatic_udp" }
aquatic_ws = { path = "../aquatic_ws" }
mimalloc = { version = "0.1", default-features = false }

99
aquatic/src/main.rs Normal file
View file

@ -0,0 +1,99 @@
use aquatic_cli_helpers::{Options, run_app_with_cli_and_config, print_help};
use aquatic_http::config::Config as HttpConfig;
use aquatic_udp::config::Config as UdpConfig;
use aquatic_ws::config::Config as WsConfig;
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
const APP_NAME: &str = "aquatic: BitTorrent tracker";
fn main(){
::std::process::exit(match run(){
Ok(()) => 0,
Err(None) => {
print_help(|| gen_info(), None);
0
},
Err(opt_err@Some(_)) => {
print_help(|| gen_info(), opt_err);
1
},
})
}
fn run() -> Result<(), Option<String>>{
let mut arg_iter = ::std::env::args().skip(1);
let protocol = if let Some(protocol) = arg_iter.next(){
protocol
} else {
return Err(None);
};
let options = match Options::parse_args(arg_iter){
Ok(options) => options,
Err(opt_err) => {
return Err(opt_err);
}
};
match protocol.as_str() {
"udp" => {
run_app_with_cli_and_config::<UdpConfig>(
aquatic_udp::APP_NAME,
aquatic_udp::run,
Some(options),
)
},
"http" => {
run_app_with_cli_and_config::<HttpConfig>(
aquatic_http::APP_NAME,
aquatic_http::run,
Some(options),
)
},
"ws" => {
run_app_with_cli_and_config::<WsConfig>(
aquatic_ws::APP_NAME,
aquatic_ws::run,
Some(options),
)
},
arg => {
let opt_err = if arg == "-h" || arg == "--help" {
None
} else if arg.chars().next() == Some('-'){
Some("First argument must be protocol".to_string())
} else {
Some("Invalid protocol".to_string())
};
return Err(opt_err)
},
}
Ok(())
}
fn gen_info() -> String {
let mut info = String::new();
info.push_str(APP_NAME);
let app_path = ::std::env::args().next().unwrap();
info.push_str(&format!("\n\nUsage: {} PROTOCOL [OPTIONS]", app_path));
info.push_str("\n\nAvailable protocols:");
info.push_str("\n udp");
info.push_str("\n http");
info.push_str("\n ws");
info
}

View file

@ -7,7 +7,6 @@ license = "Apache-2.0"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
gumdrop = "0.8"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
simplelog = "0.8" simplelog = "0.8"
toml = "0.5" toml = "0.5"

View file

@ -2,7 +2,6 @@ use std::fs::File;
use std::io::Read; use std::io::Read;
use anyhow::Context; use anyhow::Context;
use gumdrop::Options;
use serde::{Serialize, Deserialize, de::DeserializeOwned}; use serde::{Serialize, Deserialize, de::DeserializeOwned};
use simplelog::{ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; use simplelog::{ConfigBuilder, LevelFilter, TermLogger, TerminalMode};
@ -33,23 +32,60 @@ pub trait Config: Default + Serialize + DeserializeOwned {
} }
#[derive(Debug, Options)] #[derive(Debug, Default)]
struct AppOptions { pub struct Options {
#[options(help = "run with config file", short = "c", meta = "PATH")]
config_file: Option<String>, config_file: Option<String>,
#[options(help = "print default config file", short = "p")]
print_config: bool, print_config: bool,
#[options(help = "print help message")] }
help: bool,
impl Options {
pub fn parse_args<I>(
mut arg_iter: I
) -> Result<Options, Option<String>>
where I: Iterator<Item = String>
{
let mut options = Options::default();
loop {
if let Some(arg) = arg_iter.next(){
match arg.as_str(){
"-c" | "--config-file" => {
if let Some(path) = arg_iter.next(){
options.config_file = Some(path);
} else {
return Err(
Some("No config file path given".to_string())
);
}
},
"-p" | "--print-config" => {
options.print_config = true;
},
"-h" | "--help" => {
return Err(None);
},
_ => {
return Err(Some("Unrecognized argument".to_string()));
}
}
} else {
break;
}
}
Ok(options)
}
} }
pub fn run_app_with_cli_and_config<T>( pub fn run_app_with_cli_and_config<T>(
title: &str, app_title: &str,
// Function that takes config file and runs application // Function that takes config file and runs application
app_fn: fn(T) -> anyhow::Result<()>, app_fn: fn(T) -> anyhow::Result<()>,
opts: Option<Options>,
) where T: Config { ) where T: Config {
::std::process::exit(match run_inner(title, app_fn) { ::std::process::exit(match run_inner(app_title, app_fn, opts) {
Ok(()) => 0, Ok(()) => 0,
Err(err) => { Err(err) => {
eprintln!("Error: {:#}", err); eprintln!("Error: {:#}", err);
@ -61,32 +97,45 @@ pub fn run_app_with_cli_and_config<T>(
fn run_inner<T>( fn run_inner<T>(
title: &str, app_title: &str,
// Function that takes config file and runs application // Function that takes config file and runs application
app_fn: fn(T) -> anyhow::Result<()>, app_fn: fn(T) -> anyhow::Result<()>,
// Possibly preparsed options
options: Option<Options>,
) -> anyhow::Result<()> where T: Config { ) -> anyhow::Result<()> where T: Config {
let args: Vec<String> = ::std::env::args().collect(); let options = if let Some(options) = options {
options
} else {
let mut arg_iter = ::std::env::args();
let opts = AppOptions::parse_args_default(&args[1..])?; let app_path = arg_iter.next().unwrap();
if opts.help_requested(){ match Options::parse_args(arg_iter){
print_help(title, None); Ok(options) => options,
Err(opt_err) => {
let gen_info = || format!(
"{}\n\nUsage: {} [OPTIONS]",
app_title,
app_path
);
Ok(()) print_help(gen_info, opt_err);
} else if opts.print_config {
return Ok(())
}
}
};
if options.print_config {
print!("{}", default_config_as_toml::<T>()); print!("{}", default_config_as_toml::<T>());
Ok(()) Ok(())
} else if let Some(config_file) = opts.config_file {
let config: T = config_from_toml_file(config_file)?;
if let Some(log_level) = config.get_log_level(){
start_logger(log_level)?;
}
app_fn(config)
} else { } else {
let config = T::default(); let config = if let Some(path) = options.config_file {
config_from_toml_file(path)?
} else {
T::default()
};
if let Some(log_level) = config.get_log_level(){ if let Some(log_level) = config.get_log_level(){
start_logger(log_level)?; start_logger(log_level)?;
@ -97,14 +146,20 @@ fn run_inner<T>(
} }
fn print_help(title: &str, opt_error: Option<anyhow::Error>){ pub fn print_help<F>(
println!("{}", title); info_generator: F,
opt_error: Option<String>
) where F: FnOnce() -> String {
println!("{}", info_generator());
println!("\nOptions:");
println!(" -c, --config-file Load config from this path");
println!(" -p, --print-config Print default config");
println!(" -h, --help Print this help message");
if let Some(error) = opt_error { if let Some(error) = opt_error {
println!("\nError: {:#}.", error); println!("\nError: {}.", error);
} }
println!("\n{}", AppOptions::usage());
} }

View file

@ -8,7 +8,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main(){ fn main(){
run_app_with_cli_and_config::<Config>( run_app_with_cli_and_config::<Config>(
"aquatic: BitTorrent (HTTP/TLS) tracker", aquatic_http::APP_NAME,
aquatic_http::run aquatic_http::run,
None
) )
} }

View file

@ -18,6 +18,9 @@ use config::Config;
use network::utils::create_tls_acceptor; use network::utils::create_tls_acceptor;
pub const APP_NAME: &str = "aquatic_http: HTTP/TLS BitTorrent tracker";
pub fn run(config: Config) -> anyhow::Result<()> { pub fn run(config: Config) -> anyhow::Result<()> {
let opt_tls_acceptor = create_tls_acceptor(&config.network.tls)?; let opt_tls_acceptor = create_tls_acceptor(&config.network.tls)?;

View file

@ -25,8 +25,9 @@ const MBITS_FACTOR: f64 = 1.0 / ((1024.0 * 1024.0) / 8.0);
pub fn main(){ pub fn main(){
aquatic_cli_helpers::run_app_with_cli_and_config::<Config>( aquatic_cli_helpers::run_app_with_cli_and_config::<Config>(
"aquatic http bittorrent tracker: load tester", "aquatic_http_load_test: BitTorrent load tester",
run, run,
None
) )
} }

View file

@ -4,7 +4,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main(){ fn main(){
aquatic_cli_helpers::run_app_with_cli_and_config::<aquatic_udp::config::Config>( aquatic_cli_helpers::run_app_with_cli_and_config::<aquatic_udp::config::Config>(
"aquatic: udp bittorrent tracker", aquatic_udp::APP_NAME,
aquatic_udp::run, aquatic_udp::run,
None
) )
} }

View file

@ -15,6 +15,9 @@ use config::Config;
use common::State; use common::State;
pub const APP_NAME: &str = "aquatic_udp: UDP BitTorrent tracker";
pub fn run(config: Config) -> ::anyhow::Result<()> { pub fn run(config: Config) -> ::anyhow::Result<()> {
let state = State::default(); let state = State::default();

View file

@ -39,8 +39,9 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main(){ fn main(){
run_app_with_cli_and_config::<BenchConfig>( run_app_with_cli_and_config::<BenchConfig>(
"aquatic udp bittorrent tracker: benchmarker", "aquatic_udp_bench: Run aquatic_udp benchmarks",
run run,
None
) )
} }

View file

@ -26,8 +26,9 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
pub fn main(){ pub fn main(){
aquatic_cli_helpers::run_app_with_cli_and_config::<Config>( aquatic_cli_helpers::run_app_with_cli_and_config::<Config>(
"aquatic udp bittorrent tracker: load tester", "aquatic_udp_load_test: BitTorrent load tester",
run, run,
None,
) )
} }

View file

@ -8,7 +8,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main(){ fn main(){
run_app_with_cli_and_config::<Config>( run_app_with_cli_and_config::<Config>(
"aquatic: webtorrent tracker", aquatic_ws::APP_NAME,
aquatic_ws::run aquatic_ws::run,
None
) )
} }

View file

@ -20,6 +20,9 @@ use common::*;
use config::Config; use config::Config;
pub const APP_NAME: &str = "aquatic_ws: WebTorrent tracker";
pub fn run(config: Config) -> anyhow::Result<()> { pub fn run(config: Config) -> anyhow::Result<()> {
let opt_tls_acceptor = create_tls_acceptor(&config)?; let opt_tls_acceptor = create_tls_acceptor(&config)?;

View file

@ -21,8 +21,9 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
pub fn main(){ pub fn main(){
aquatic_cli_helpers::run_app_with_cli_and_config::<Config>( aquatic_cli_helpers::run_app_with_cli_and_config::<Config>(
"aquatic: webtorrent tracker load tester", "aquatic_ws_load_test: WebTorrent load tester",
run, run,
None
) )
} }

5
scripts/run-aquatic.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
. ./scripts/env-native-cpu-without-avx-512
cargo run --release --bin aquatic -- $@