From bfbbd1d7b0ab6423e115ff8567d1d84b8923368e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 17 Dec 2023 18:35:57 +0800 Subject: [PATCH 01/18] cargo fmt && cargo clippy --- .github/workflows/rust.yml | 33 + .gitignore | 6 + src/client.rs | 438 +++++++++----- src/main.rs | 45 +- src/protocol/communication.rs | 209 ++++--- src/protocol/messaging.rs | 249 +++++--- src/protocol/mod.rs | 8 +- src/protocol/results.rs | 1064 ++++++++++++++++++++------------- src/server.rs | 432 ++++++++----- src/stream/mod.rs | 26 +- src/stream/tcp.rs | 704 +++++++++++++--------- src/stream/udp.rs | 645 ++++++++++++-------- src/utils/cpu_affinity.rs | 37 +- src/utils/mod.rs | 10 +- 14 files changed, 2409 insertions(+), 1497 deletions(-) create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..d6447b1 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,33 @@ +name: Rust + +on: + [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + matrix: + host_os: + - ubuntu-latest + - macos-latest + # - windows-latest + + runs-on: ${{ matrix.host_os }} + + steps: + - uses: actions/checkout@v3 + - name: check + run: cargo check + - name: rustfmt + run: | + rustc --version + cargo fmt --all -- --check + - name: clippy + run: cargo clippy -- -D warnings + - name: Build + run: cargo build --verbose --all-features --tests --examples + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..269985d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.vscode/ +.VSCodeCounter/ +build/ +tmp/ +Cargo.lock +target/ diff --git a/src/client.rs b/src/client.rs index 797c540..6afee76 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,91 +1,112 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ use std::net::{IpAddr, Shutdown, ToSocketAddrs}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; use std::sync::mpsc::channel; +use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use clap::ArgMatches; -use mio::net::{TcpStream}; +use mio::net::TcpStream; use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; use crate::protocol::messaging::{ - prepare_begin, prepare_end, - prepare_upload_configuration, prepare_download_configuration, + prepare_begin, prepare_download_configuration, prepare_end, prepare_upload_configuration, }; -use crate::protocol::results::{IntervalResult, IntervalResultKind, TestResults, TcpTestResults, UdpTestResults}; +use crate::protocol::results::{ + IntervalResultBox, IntervalResultKind, TcpTestResults, TestResults, UdpTestResults, +}; -use crate::stream::TestStream; use crate::stream::tcp; use crate::stream::udp; +use crate::stream::TestStream; use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; /// when false, the system is shutting down -static ALIVE:AtomicBool = AtomicBool::new(true); +static ALIVE: AtomicBool = AtomicBool::new(true); /// a deferred kill-switch to handle shutdowns a bit more gracefully in the event of a probable disconnect -static mut KILL_TIMER_RELATIVE_START_TIME:f64 = 0.0; //the time at which the kill-timer was started -const KILL_TIMEOUT:f64 = 5.0; //once testing finishes, allow a few seconds for the server to respond +static mut KILL_TIMER_RELATIVE_START_TIME: f64 = 0.0; //the time at which the kill-timer was started +const KILL_TIMEOUT: f64 = 5.0; //once testing finishes, allow a few seconds for the server to respond -const CONNECT_TIMEOUT:Duration = Duration::from_secs(2); +const CONNECT_TIMEOUT: Duration = Duration::from_secs(2); -fn connect_to_server(address:&str, port:&u16) -> BoxResult { +fn connect_to_server(address: &str, port: &u16) -> BoxResult { let destination = format!("{}:{}", address, port); log::info!("connecting to server at {}...", destination); - + let server_addr = destination.to_socket_addrs()?.next(); if server_addr.is_none() { - return Err(Box::new(simple_error::simple_error!("unable to resolve {}", address))); + return Err(Box::new(simple_error::simple_error!( + "unable to resolve {}", + address + ))); } - let raw_stream = match std::net::TcpStream::connect_timeout(&server_addr.unwrap(), CONNECT_TIMEOUT) { - Ok(s) => s, - Err(e) => return Err(Box::new(simple_error::simple_error!("unable to connect: {}", e))), - }; + let raw_stream = + match std::net::TcpStream::connect_timeout(&server_addr.unwrap(), CONNECT_TIMEOUT) { + Ok(s) => s, + Err(e) => { + return Err(Box::new(simple_error::simple_error!( + "unable to connect: {}", + e + ))) + } + }; let stream = match TcpStream::from_stream(raw_stream) { Ok(s) => s, - Err(e) => return Err(Box::new(simple_error::simple_error!("unable to prepare TCP control-channel: {}", e))), + Err(e) => { + return Err(Box::new(simple_error::simple_error!( + "unable to prepare TCP control-channel: {}", + e + ))) + } }; log::info!("connected to server"); - - stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); - stream.set_keepalive(Some(KEEPALIVE_DURATION)).expect("unable to set TCP keepalive"); - + + stream + .set_nodelay(true) + .expect("cannot disable Nagle's algorithm"); + stream + .set_keepalive(Some(KEEPALIVE_DURATION)) + .expect("unable to set TCP keepalive"); + Ok(stream) } -fn prepare_test_results(is_udp:bool, stream_count:u8) -> Mutex> { - if is_udp { //UDP +fn prepare_test_results(is_udp: bool, stream_count: u8) -> Mutex> { + if is_udp { + //UDP let mut udp_test_results = UdpTestResults::new(); for i in 0..stream_count { udp_test_results.prepare_index(&i); } Mutex::new(Box::new(udp_test_results)) - } else { //TCP + } else { + //TCP let mut tcp_test_results = TcpTestResults::new(); for i in 0..stream_count { tcp_test_results.prepare_index(&i); @@ -94,10 +115,9 @@ fn prepare_test_results(is_udp:bool, stream_count:u8) -> Mutex BoxResult<()> { +pub fn execute(args: ArgMatches) -> BoxResult<()> { let mut complete = false; - + //config-parsing and pre-connection setup let mut tcp_port_pool = tcp::receiver::TcpPortPool::new( args.value_of("tcp_port_pool").unwrap().to_string(), @@ -107,107 +127,122 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { args.value_of("udp_port_pool").unwrap().to_string(), args.value_of("udp6_port_pool").unwrap().to_string(), ); - - let cpu_affinity_manager = Arc::new(Mutex::new(crate::utils::cpu_affinity::CpuAffinityManager::new(args.value_of("affinity").unwrap())?)); - - let display_json:bool; - let display_bit:bool; + + let cpu_affinity_manager = Arc::new(Mutex::new( + crate::utils::cpu_affinity::CpuAffinityManager::new(args.value_of("affinity").unwrap())?, + )); + + let display_json: bool; + let display_bit: bool; match args.value_of("format").unwrap() { "json" => { display_json = true; display_bit = false; - }, + } "megabit" => { display_json = false; display_bit = true; - }, + } "megabyte" => { display_json = false; display_bit = false; - }, + } _ => { log::error!("unsupported display-mode; defaulting to JSON"); display_json = true; display_bit = false; - }, + } } - + let is_udp = args.is_present("udp"); - + let test_id = uuid::Uuid::new_v4(); let mut upload_config = prepare_upload_configuration(&args, test_id.as_bytes())?; let mut download_config = prepare_download_configuration(&args, test_id.as_bytes())?; - - + //connect to the server - let mut stream = connect_to_server(&args.value_of("client").unwrap(), &(args.value_of("port").unwrap().parse()?))?; + let mut stream = connect_to_server( + args.value_of("client").unwrap(), + &(args.value_of("port").unwrap().parse()?), + )?; let server_addr = stream.peer_addr()?; - - + //scaffolding to track and relay the streams and stream-results associated with this test let stream_count = download_config.get("streams").unwrap().as_i64().unwrap() as usize; - let mut parallel_streams:Vec>> = Vec::with_capacity(stream_count); + let mut parallel_streams: Vec>> = + Vec::with_capacity(stream_count); let mut parallel_streams_joinhandles = Vec::with_capacity(stream_count); - let (results_tx, results_rx):(std::sync::mpsc::Sender>, std::sync::mpsc::Receiver>) = channel(); - - let test_results:Mutex> = prepare_test_results(is_udp, stream_count as u8); - + let (results_tx, results_rx): ( + std::sync::mpsc::Sender, + std::sync::mpsc::Receiver, + ) = channel(); + + let test_results: Mutex> = + prepare_test_results(is_udp, stream_count as u8); + //a closure used to pass results from stream-handlers to the test-result structure let mut results_handler = || -> BoxResult<()> { - loop { //drain all results every time this closer is invoked - match results_rx.try_recv() { //see if there's a result to pass on - Ok(result) => { - if !display_json { //since this runs in the main thread, which isn't involved in any testing, render things immediately - println!("{}", result.to_string(display_bit)); + //drain all results every time this closer is invoked + while let Ok(result) = results_rx.try_recv() { + //see if there's a result to pass on + if !display_json { + //since this runs in the main thread, which isn't involved in any testing, render things immediately + println!("{}", result.to_string(display_bit)); + } + + //update the test-results accordingly + let mut tr = test_results.lock().unwrap(); + match result.kind() { + IntervalResultKind::ClientDone | IntervalResultKind::ClientFailed => { + if result.kind() == IntervalResultKind::ClientDone { + log::info!("stream {} is done", result.get_stream_idx()); + } else { + log::warn!("stream {} failed", result.get_stream_idx()); } - - //update the test-results accordingly - let mut tr = test_results.lock().unwrap(); - match result.kind() { - IntervalResultKind::ClientDone | IntervalResultKind::ClientFailed => { - if result.kind() == IntervalResultKind::ClientDone { - log::info!("stream {} is done", result.get_stream_idx()); - } else { - log::warn!("stream {} failed", result.get_stream_idx()); - } - tr.mark_stream_done(&result.get_stream_idx(), result.kind() == IntervalResultKind::ClientDone); - if tr.count_in_progress_streams() == 0 { - complete = true; - - if tr.count_in_progress_streams_server() > 0 { - log::info!("giving the server a few seconds to report results..."); - start_kill_timer(); - } else { //all data gathered from both sides - kill(); - } - } - }, - _ => { - tr.update_from_json(result.to_json())?; + tr.mark_stream_done( + &result.get_stream_idx(), + result.kind() == IntervalResultKind::ClientDone, + ); + if tr.count_in_progress_streams() == 0 { + complete = true; + + if tr.count_in_progress_streams_server() > 0 { + log::info!("giving the server a few seconds to report results..."); + start_kill_timer(); + } else { + //all data gathered from both sides + kill(); } } - }, - Err(_) => break, //whether it's empty or disconnected, there's nothing to do + } + _ => { + tr.update_from_json(result.to_json())?; + } } } Ok(()) }; - + //depending on whether this is a forward- or reverse-test, the order of configuring test-streams will differ if args.is_present("reverse") { log::debug!("running in reverse-mode: server will be uploading data"); - + //when we're receiving data, we're also responsible for letting the server know where to send it let mut stream_ports = Vec::with_capacity(stream_count); - - if is_udp { //UDP - log::info!("preparing for reverse-UDP test with {} streams...", stream_count); - + + if is_udp { + //UDP + log::info!( + "preparing for reverse-UDP test with {} streams...", + stream_count + ); + let test_definition = udp::UdpTestDefinition::new(&download_config)?; for stream_idx in 0..stream_count { log::debug!("preparing UDP-receiver for stream {}...", stream_idx); let test = udp::receiver::UdpReceiver::new( - test_definition.clone(), &(stream_idx as u8), + test_definition.clone(), + &(stream_idx as u8), &mut udp_port_pool, &server_addr.ip(), &(download_config["receive_buffer"].as_i64().unwrap() as usize), @@ -215,67 +250,93 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { stream_ports.push(test.get_port()?); parallel_streams.push(Arc::new(Mutex::new(test))); } - } else { //TCP - log::info!("preparing for reverse-TCP test with {} streams...", stream_count); - + } else { + //TCP + log::info!( + "preparing for reverse-TCP test with {} streams...", + stream_count + ); + let test_definition = tcp::TcpTestDefinition::new(&download_config)?; for stream_idx in 0..stream_count { log::debug!("preparing TCP-receiver for stream {}...", stream_idx); let test = tcp::receiver::TcpReceiver::new( - test_definition.clone(), &(stream_idx as u8), + test_definition.clone(), + &(stream_idx as u8), &mut tcp_port_pool, &server_addr.ip(), &(download_config["receive_buffer"].as_i64().unwrap() as usize), )?; stream_ports.push(test.get_port()?); parallel_streams.push(Arc::new(Mutex::new(test))); - } + } } - + //add the port-list to the upload-config that the server will receive; this is in stream-index order upload_config["stream_ports"] = serde_json::json!(stream_ports); - + //let the server know what we're expecting send(&mut stream, &upload_config)?; } else { log::debug!("running in forward-mode: server will be receiving data"); - + //let the server know to prepare for us to connect send(&mut stream, &download_config)?; //NOTE: we don't prepare to send data at this point; that happens in the loop below, after the server signals that it's ready } - - + //now that the server knows what we need to do, we have to wait for its response let connection_payload = receive(&mut stream, is_alive, &mut results_handler)?; match connection_payload.get("kind") { Some(kind) => { match kind.as_str().unwrap_or_default() { - "connect" => { //we need to connect to the server - if is_udp { //UDP + "connect" => { + //we need to connect to the server + if is_udp { + //UDP log::info!("preparing for UDP test with {} streams...", stream_count); - + let test_definition = udp::UdpTestDefinition::new(&upload_config)?; - for (stream_idx, port) in connection_payload.get("stream_ports").unwrap().as_array().unwrap().iter().enumerate() { + for (stream_idx, port) in connection_payload + .get("stream_ports") + .unwrap() + .as_array() + .unwrap() + .iter() + .enumerate() + { log::debug!("preparing UDP-sender for stream {}...", stream_idx); let test = udp::sender::UdpSender::new( - test_definition.clone(), &(stream_idx as u8), - &0, &server_addr.ip(), &(port.as_i64().unwrap() as u16), + test_definition.clone(), + &(stream_idx as u8), + &0, + &server_addr.ip(), + &(port.as_i64().unwrap() as u16), &(upload_config["duration"].as_f64().unwrap() as f32), &(upload_config["send_interval"].as_f64().unwrap() as f32), &(upload_config["send_buffer"].as_i64().unwrap() as usize), )?; parallel_streams.push(Arc::new(Mutex::new(test))); } - } else { //TCP + } else { + //TCP log::info!("preparing for TCP test with {} streams...", stream_count); - + let test_definition = tcp::TcpTestDefinition::new(&upload_config)?; - for (stream_idx, port) in connection_payload.get("stream_ports").unwrap().as_array().unwrap().iter().enumerate() { + for (stream_idx, port) in connection_payload + .get("stream_ports") + .unwrap() + .as_array() + .unwrap() + .iter() + .enumerate() + { log::debug!("preparing TCP-sender for stream {}...", stream_idx); let test = tcp::sender::TcpSender::new( - test_definition.clone(), &(stream_idx as u8), - &server_addr.ip(), &(port.as_i64().unwrap() as u16), + test_definition.clone(), + &(stream_idx as u8), + &server_addr.ip(), + &(port.as_i64().unwrap() as u16), &(upload_config["duration"].as_f64().unwrap() as f32), &(upload_config["send_interval"].as_f64().unwrap() as f32), &(upload_config["send_buffer"].as_i64().unwrap() as usize), @@ -284,37 +345,46 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { parallel_streams.push(Arc::new(Mutex::new(test))); } } - }, + } "connect-ready" => { //server is ready to connect to us - //nothing more to do in this flow - }, + //nothing more to do in this flow + } _ => { - log::error!("invalid data from {}: {}", stream.peer_addr()?, serde_json::to_string(&connection_payload)?); + log::error!( + "invalid data from {}: {}", + stream.peer_addr()?, + serde_json::to_string(&connection_payload)? + ); kill(); - }, + } } - }, + } None => { - log::error!("invalid data from {}: {}", stream.peer_addr()?, serde_json::to_string(&connection_payload)?); + log::error!( + "invalid data from {}: {}", + stream.peer_addr()?, + serde_json::to_string(&connection_payload)? + ); kill(); - }, + } } - - - if is_alive() { //if interrupted while waiting for the server to respond, there's no reason to continue + + if is_alive() { + //if interrupted while waiting for the server to respond, there's no reason to continue log::info!("informing server that testing can begin..."); //tell the server to start send(&mut stream, &prepare_begin())?; - + log::debug!("spawning stream-threads"); //begin the test-streams for (stream_idx, parallel_stream) in parallel_streams.iter_mut().enumerate() { log::info!("beginning execution of stream {}...", stream_idx); - let c_ps = Arc::clone(¶llel_stream); + let c_ps = Arc::clone(parallel_stream); let c_results_tx = results_tx.clone(); let c_cam = cpu_affinity_manager.clone(); let handle = thread::spawn(move || { - { //set CPU affinity, if enabled + { + //set CPU affinity, if enabled c_cam.lock().unwrap().set_affinity(); } loop { @@ -326,31 +396,44 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { Ok(_) => (), Err(e) => { log::error!("unable to report interval-result: {}", e); - break - }, + break; + } }, Err(e) => { log::error!("unable to process stream: {}", e); - match c_results_tx.send(Box::new(crate::protocol::results::ClientFailedResult{stream_idx: test.get_idx()})) { + match c_results_tx.send(Box::new( + crate::protocol::results::ClientFailedResult { + stream_idx: test.get_idx(), + }, + )) { Ok(_) => (), - Err(e) => log::error!("unable to report interval-failed-result: {}", e), + Err(e) => log::error!( + "unable to report interval-failed-result: {}", + e + ), } break; - }, + } }, None => { - match c_results_tx.send(Box::new(crate::protocol::results::ClientDoneResult{stream_idx: test.get_idx()})) { + match c_results_tx.send(Box::new( + crate::protocol::results::ClientDoneResult { + stream_idx: test.get_idx(), + }, + )) { Ok(_) => (), - Err(e) => log::error!("unable to report interval-done-result: {}", e), + Err(e) => { + log::error!("unable to report interval-done-result: {}", e) + } } break; - }, + } } } }); parallel_streams_joinhandles.push(handle); } - + //watch for events from the server while is_alive() { match receive(&mut stream, is_alive, &mut results_handler) { @@ -381,7 +464,6 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { _ => (), //not possible } tr.mark_stream_done_server(&(idx64 as u8)); - if tr.count_in_progress_streams() == 0 && tr.count_in_progress_streams_server() == 0 { //all data gathered from both sides kill(); } @@ -395,36 +477,43 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { break; }, } - }, + } None => { - log::error!("invalid data from {}: {}", stream.peer_addr()?, serde_json::to_string(&connection_payload)?); + log::error!( + "invalid data from {}: {}", + stream.peer_addr()?, + serde_json::to_string(&connection_payload)? + ); break; - }, + } } - }, + } Err(e) => { - if !complete { //when complete, this also occurs + if !complete { + //when complete, this also occurs return Err(e); } break; - }, + } } } } - + //assume this is a controlled shutdown; if it isn't, this is just a very slight waste of time send(&mut stream, &prepare_end()).unwrap_or_default(); thread::sleep(Duration::from_millis(250)); //wait a moment for the "end" message to be queued for delivery to the server stream.shutdown(Shutdown::Both).unwrap_or_default(); - + log::debug!("stopping any still-in-progress streams"); for ps in parallel_streams.iter_mut() { let mut stream = match (*ps).lock() { Ok(guard) => guard, Err(poisoned) => { - log::error!("a stream-handler was poisoned; this indicates some sort of logic error"); + log::error!( + "a stream-handler was poisoned; this indicates some sort of logic error" + ); poisoned.into_inner() - }, + } }; stream.stop(); } @@ -435,8 +524,8 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { Err(e) => log::error!("error in parallel stream: {:?}", e), } } - - let common_config:serde_json::Value; + + let common_config: serde_json::Value; //sanitise the config structures for export { let upload_config_map = upload_config.as_object_mut().unwrap(); @@ -450,7 +539,7 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { if upload_config_map["send_buffer"].as_i64().unwrap() == 0 { upload_config_map.remove("send_buffer"); } - + let download_config_map = download_config.as_object_mut().unwrap(); download_config_map.remove("family"); download_config_map.remove("kind"); @@ -461,32 +550,41 @@ pub fn execute(args:ArgMatches) -> BoxResult<()> { if download_config_map["receive_buffer"].as_i64().unwrap() == 0 { download_config_map.remove("receive_buffer"); } - + common_config = serde_json::json!({ "family": cc_family, "length": cc_length, "streams": cc_streams, }); } - + log::debug!("displaying test results"); - let omit_seconds:usize = args.value_of("omit").unwrap().parse()?; + let omit_seconds: usize = args.value_of("omit").unwrap().parse()?; { let tr = test_results.lock().unwrap(); if display_json { - println!("{}", tr.to_json_string(omit_seconds, upload_config, download_config, common_config, serde_json::json!({ - "omit_seconds": omit_seconds, - "ip_version": match server_addr.ip() { - IpAddr::V4(_) => 4, - IpAddr::V6(_) => 6, - }, - "reverse": args.is_present("reverse"), - }))); + println!( + "{}", + tr.to_json_string( + omit_seconds, + upload_config, + download_config, + common_config, + serde_json::json!({ + "omit_seconds": omit_seconds, + "ip_version": match server_addr.ip() { + IpAddr::V4(_) => 4, + IpAddr::V6(_) => 6, + }, + "reverse": args.is_present("reverse"), + }) + ) + ); } else { println!("{}", tr.to_string(display_bit, omit_seconds)); } } - + Ok(()) } @@ -495,13 +593,23 @@ pub fn kill() -> bool { } fn start_kill_timer() { unsafe { - KILL_TIMER_RELATIVE_START_TIME = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64(); + KILL_TIMER_RELATIVE_START_TIME = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs_f64(); } } fn is_alive() -> bool { unsafe { - if KILL_TIMER_RELATIVE_START_TIME != 0.0 { //initialised - if SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64() - KILL_TIMER_RELATIVE_START_TIME >= KILL_TIMEOUT { + if KILL_TIMER_RELATIVE_START_TIME != 0.0 { + //initialised + if SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs_f64() + - KILL_TIMER_RELATIVE_START_TIME + >= KILL_TIMEOUT + { return false; } } diff --git a/src/main.rs b/src/main.rs index 6ec2614..d8c1122 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,33 +1,33 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ - -extern crate log; + extern crate env_logger; +extern crate log; use clap::{App, Arg}; +mod client; mod protocol; +mod server; mod stream; mod utils; -mod client; -mod server; fn main() { let args = App::new("rperf") @@ -44,7 +44,6 @@ fn main() { .required(false) .default_value("5199") ) - .arg( Arg::with_name("affinity") .help("specify logical CPUs, delimited by commas, across which to round-robin affinity; not supported on all systems") @@ -63,8 +62,6 @@ fn main() { .short("d") .required(false) ) - - .arg( Arg::with_name("server") .help("run in server mode") @@ -89,7 +86,6 @@ fn main() { .required(false) .default_value("0") ) - .arg( Arg::with_name("client") .help("run in client mode; value is the server's address") @@ -234,25 +230,27 @@ fn main() { .default_value("") ) .get_matches(); - - let mut env = env_logger::Env::default() - .filter_or("RUST_LOG", "info"); + + let mut env = env_logger::Env::default().filter_or("RUST_LOG", "info"); if args.is_present("debug") { env = env.filter_or("RUST_LOG", "debug"); } env_logger::init_from_env(env); - + if args.is_present("server") { log::debug!("registering SIGINT handler..."); ctrlc::set_handler(move || { if server::kill() { - log::warn!("shutdown requested; please allow a moment for any in-progress tests to stop"); + log::warn!( + "shutdown requested; please allow a moment for any in-progress tests to stop" + ); } else { log::warn!("forcing shutdown immediately"); std::process::exit(3); } - }).expect("unable to set SIGINT handler"); - + }) + .expect("unable to set SIGINT handler"); + log::debug!("beginning normal operation..."); let service = server::serve(args); if service.is_err() { @@ -263,13 +261,16 @@ fn main() { log::debug!("registering SIGINT handler..."); ctrlc::set_handler(move || { if client::kill() { - log::warn!("shutdown requested; please allow a moment for any in-progress tests to stop"); + log::warn!( + "shutdown requested; please allow a moment for any in-progress tests to stop" + ); } else { log::warn!("forcing shutdown immediately"); std::process::exit(3); } - }).expect("unable to set SIGINT handler"); - + }) + .expect("unable to set SIGINT handler"); + log::debug!("connecting to server..."); let execution = client::execute(args); if execution.is_err() { diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index 5eaae27..b398417 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -1,19 +1,19 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ @@ -21,34 +21,43 @@ use std::io::{self, Read, Write}; use std::time::Duration; -use mio::{Events, Ready, Poll, PollOpt, Token}; -use mio::net::{TcpStream}; +use mio::net::TcpStream; +use mio::{Events, Poll, PollOpt, Ready, Token}; use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; /// how long to wait for keepalive events // the communications channels typically exchange data every second, so 2s is reasonable to avoid excess noise -pub const KEEPALIVE_DURATION:Duration = Duration::from_secs(2); +pub const KEEPALIVE_DURATION: Duration = Duration::from_secs(2); /// how long to block on polling operations -const POLL_TIMEOUT:Duration = Duration::from_millis(50); +const POLL_TIMEOUT: Duration = Duration::from_millis(50); /// sends JSON data over a client-server communications stream -pub fn send(stream:&mut TcpStream, message:&serde_json::Value) -> BoxResult<()> { +pub fn send(stream: &mut TcpStream, message: &serde_json::Value) -> BoxResult<()> { let serialised_message = serde_json::to_vec(message)?; - - log::debug!("sending message of length {}, {:?}, to {}...", serialised_message.len(), message, stream.peer_addr()?); - let mut output_buffer = vec![0_u8; (serialised_message.len() + 2).into()]; + + log::debug!( + "sending message of length {}, {:?}, to {}...", + serialised_message.len(), + message, + stream.peer_addr()? + ); + let mut output_buffer = vec![0_u8; serialised_message.len() + 2]; output_buffer[..2].copy_from_slice(&(serialised_message.len() as u16).to_be_bytes()); output_buffer[2..].copy_from_slice(serialised_message.as_slice()); Ok(stream.write_all(&output_buffer)?) } /// receives the length-count of a pending message over a client-server communications stream -fn receive_length(stream:&mut TcpStream, alive_check:fn() -> bool, results_handler:&mut dyn FnMut() -> BoxResult<()>) -> BoxResult { +fn receive_length( + stream: &mut TcpStream, + alive_check: fn() -> bool, + results_handler: &mut dyn FnMut() -> BoxResult<()>, +) -> BoxResult { let mut cloned_stream = stream.try_clone()?; - + let mio_token = Token(0); let poll = Poll::new()?; poll.register( @@ -58,51 +67,71 @@ fn receive_length(stream:&mut TcpStream, alive_check:fn() -> bool, results_handl PollOpt::edge(), )?; let mut events = Events::with_capacity(1); //only interacting with one stream - + let mut length_bytes_read = 0; - let mut length_spec:[u8; 2] = [0; 2]; - while alive_check() { //waiting to find out how long the next message is + let mut length_spec: [u8; 2] = [0; 2]; + while alive_check() { + //waiting to find out how long the next message is results_handler()?; //send any outstanding results between cycles poll.poll(&mut events, Some(POLL_TIMEOUT))?; for event in events.iter() { - match event.token() { - _ => loop { - match cloned_stream.read(&mut length_spec[length_bytes_read..]) { - Ok(size) => { - if size == 0 { - if alive_check() { - return Err(Box::new(simple_error::simple_error!("connection lost"))); - } else { //shutting down; a disconnect is expected - return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); - } - } - - length_bytes_read += size; - if length_bytes_read == 2 { - let length = u16::from_be_bytes(length_spec); - log::debug!("received length-spec of {} from {}", length, stream.peer_addr()?); - return Ok(length); + event.token(); + loop { + match cloned_stream.read(&mut length_spec[length_bytes_read..]) { + Ok(size) => { + if size == 0 { + if alive_check() { + return Err(Box::new(simple_error::simple_error!( + "connection lost" + ))); } else { - log::debug!("received partial length-spec from {}", stream.peer_addr()?); + //shutting down; a disconnect is expected + return Err(Box::new(simple_error::simple_error!( + "local shutdown requested" + ))); } - }, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { //nothing left to process - break; - }, - Err(e) => { - return Err(Box::new(e)); - }, + } + + length_bytes_read += size; + if length_bytes_read == 2 { + let length = u16::from_be_bytes(length_spec); + log::debug!( + "received length-spec of {} from {}", + length, + stream.peer_addr()? + ); + return Ok(length); + } else { + log::debug!( + "received partial length-spec from {}", + stream.peer_addr()? + ); + } + } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + //nothing left to process + break; } - }, + Err(e) => { + return Err(Box::new(e)); + } + } } } } - Err(Box::new(simple_error::simple_error!("system shutting down"))) + Err(Box::new(simple_error::simple_error!( + "system shutting down" + ))) } /// receives the data-value of a pending message over a client-server communications stream -fn receive_payload(stream:&mut TcpStream, alive_check:fn() -> bool, results_handler:&mut dyn FnMut() -> BoxResult<()>, length:u16) -> BoxResult { +fn receive_payload( + stream: &mut TcpStream, + alive_check: fn() -> bool, + results_handler: &mut dyn FnMut() -> BoxResult<()>, + length: u16, +) -> BoxResult { let mut cloned_stream = stream.try_clone()?; - + let mio_token = Token(0); let poll = Poll::new()?; poll.register( @@ -112,57 +141,69 @@ fn receive_payload(stream:&mut TcpStream, alive_check:fn() -> bool, results_hand PollOpt::edge(), )?; let mut events = Events::with_capacity(1); //only interacting with one stream - + let mut bytes_read = 0; let mut buffer = vec![0_u8; length.into()]; - while alive_check() { //waiting to receive the payload + while alive_check() { + //waiting to receive the payload results_handler()?; //send any outstanding results between cycles poll.poll(&mut events, Some(POLL_TIMEOUT))?; for event in events.iter() { - match event.token() { - _ => loop { - match cloned_stream.read(&mut buffer[bytes_read..]) { - Ok(size) => { - if size == 0 { - if alive_check() { - return Err(Box::new(simple_error::simple_error!("connection lost"))); - } else { //shutting down; a disconnect is expected - return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); - } + event.token(); + loop { + match cloned_stream.read(&mut buffer[bytes_read..]) { + Ok(size) => { + if size == 0 { + if alive_check() { + return Err(Box::new(simple_error::simple_error!( + "connection lost" + ))); + } else { + //shutting down; a disconnect is expected + return Err(Box::new(simple_error::simple_error!( + "local shutdown requested" + ))); } - - bytes_read += size; - if bytes_read == length as usize { - match serde_json::from_slice(&buffer) { - Ok(v) => { - log::debug!("received {:?} from {}", v, stream.peer_addr()?); - return Ok(v); - }, - Err(e) => { - return Err(Box::new(e)); - }, + } + + bytes_read += size; + if bytes_read == length as usize { + match serde_json::from_slice(&buffer) { + Ok(v) => { + log::debug!("received {:?} from {}", v, stream.peer_addr()?); + return Ok(v); + } + Err(e) => { + return Err(Box::new(e)); } - } else { - log::debug!("received partial payload from {}", stream.peer_addr()?); } - }, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { //nothing left to process - break; - }, - Err(e) => { - return Err(Box::new(e)); - }, + } else { + log::debug!("received partial payload from {}", stream.peer_addr()?); + } + } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + //nothing left to process + break; + } + Err(e) => { + return Err(Box::new(e)); } - }, + } } } } - Err(Box::new(simple_error::simple_error!("system shutting down"))) + Err(Box::new(simple_error::simple_error!( + "system shutting down" + ))) } /// handles the full process of retrieving a message from a client-server communications stream -pub fn receive(mut stream:&mut TcpStream, alive_check:fn() -> bool, results_handler:&mut dyn FnMut() -> BoxResult<()>) -> BoxResult { +pub fn receive( + stream: &mut TcpStream, + alive_check: fn() -> bool, + results_handler: &mut dyn FnMut() -> BoxResult<()>, +) -> BoxResult { log::debug!("awaiting length-value from {}...", stream.peer_addr()?); - let length = receive_length(&mut stream, alive_check, results_handler)?; + let length = receive_length(stream, alive_check, results_handler)?; log::debug!("awaiting payload from {}...", stream.peer_addr()?); - receive_payload(&mut stream, alive_check, results_handler, length) + receive_payload(stream, alive_check, results_handler, length) } diff --git a/src/protocol/messaging.rs b/src/protocol/messaging.rs index a4ec012..880af8b 100644 --- a/src/protocol/messaging.rs +++ b/src/protocol/messaging.rs @@ -1,25 +1,25 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; /// prepares a message used to tell the server to begin operations pub fn prepare_begin() -> serde_json::Value { @@ -29,10 +29,10 @@ pub fn prepare_begin() -> serde_json::Value { } /// prepares a message used to tell the client to connect its test-streams -pub fn prepare_connect(stream_ports:&[u16]) -> serde_json::Value { +pub fn prepare_connect(stream_ports: &[u16]) -> serde_json::Value { serde_json::json!({ "kind": "connect", - + "stream_ports": stream_ports, }) } @@ -51,86 +51,108 @@ pub fn prepare_end() -> serde_json::Value { }) } - - - /// prepares a message used to describe the upload role of a TCP test -fn prepare_configuration_tcp_upload(test_id:&[u8; 16], streams:u8, bandwidth:u64, seconds:f32, length:usize, send_interval:f32, send_buffer:u32, no_delay:bool) -> serde_json::Value { +#[allow(clippy::too_many_arguments)] +fn prepare_configuration_tcp_upload( + test_id: &[u8; 16], + streams: u8, + bandwidth: u64, + seconds: f32, + length: usize, + send_interval: f32, + send_buffer: u32, + no_delay: bool, +) -> serde_json::Value { serde_json::json!({ "kind": "configuration", - + "family": "tcp", "role": "upload", - + "test_id": test_id, "streams": validate_streams(streams), - + "bandwidth": validate_bandwidth(bandwidth), "duration": seconds, "length": calculate_length_tcp(length), "send_interval": validate_send_interval(send_interval), - + "send_buffer": send_buffer, "no_delay": no_delay, }) } /// prepares a message used to describe the download role of a TCP test -fn prepare_configuration_tcp_download(test_id:&[u8; 16], streams:u8, length:usize, receive_buffer:u32) -> serde_json::Value { +fn prepare_configuration_tcp_download( + test_id: &[u8; 16], + streams: u8, + length: usize, + receive_buffer: u32, +) -> serde_json::Value { serde_json::json!({ "kind": "configuration", - + "family": "tcp", "role": "download", - + "test_id": test_id, "streams": validate_streams(streams), - + "length": calculate_length_tcp(length), "receive_buffer": receive_buffer, }) } /// prepares a message used to describe the upload role of a UDP test -fn prepare_configuration_udp_upload(test_id:&[u8; 16], streams:u8, bandwidth:u64, seconds:f32, length:u16, send_interval:f32, send_buffer:u32) -> serde_json::Value { +fn prepare_configuration_udp_upload( + test_id: &[u8; 16], + streams: u8, + bandwidth: u64, + seconds: f32, + length: u16, + send_interval: f32, + send_buffer: u32, +) -> serde_json::Value { serde_json::json!({ "kind": "configuration", - + "family": "udp", "role": "upload", - + "test_id": test_id, "streams": validate_streams(streams), - + "bandwidth": validate_bandwidth(bandwidth), "duration": seconds, "length": calculate_length_udp(length), "send_interval": validate_send_interval(send_interval), - + "send_buffer": send_buffer, }) } /// prepares a message used to describe the download role of a UDP test -fn prepare_configuration_udp_download(test_id:&[u8; 16], streams:u8, length:u16, receive_buffer:u32) -> serde_json::Value { +fn prepare_configuration_udp_download( + test_id: &[u8; 16], + streams: u8, + length: u16, + receive_buffer: u32, +) -> serde_json::Value { serde_json::json!({ "kind": "configuration", - + "family": "udp", "role": "download", - + "test_id": test_id, "streams": validate_streams(streams), - + "length": calculate_length_udp(length), "receive_buffer": receive_buffer, }) } - - - -fn validate_streams(streams:u8) -> u8 { +fn validate_streams(streams: u8) -> u8 { if streams > 0 { streams } else { @@ -139,7 +161,7 @@ fn validate_streams(streams:u8) -> u8 { } } -fn validate_bandwidth(bandwidth:u64) -> u64 { +fn validate_bandwidth(bandwidth: u64) -> u64 { if bandwidth > 0 { bandwidth } else { @@ -148,7 +170,7 @@ fn validate_bandwidth(bandwidth:u64) -> u64 { } } -fn validate_send_interval(send_interval:f32) -> f32 { +fn validate_send_interval(send_interval: f32) -> f32 { if send_interval > 0.0 && send_interval <= 1.0 { send_interval } else { @@ -157,90 +179,109 @@ fn validate_send_interval(send_interval:f32) -> f32 { } } -fn calculate_length_tcp(length:usize) -> usize { - if length < crate::stream::tcp::TEST_HEADER_SIZE { //length must be at least enough to hold the test data +fn calculate_length_tcp(length: usize) -> usize { + if length < crate::stream::tcp::TEST_HEADER_SIZE { + //length must be at least enough to hold the test data crate::stream::tcp::TEST_HEADER_SIZE } else { length } } -fn calculate_length_udp(length:u16) -> u16 { - if length < crate::stream::udp::TEST_HEADER_SIZE { //length must be at least enough to hold the test data +fn calculate_length_udp(length: u16) -> u16 { + if length < crate::stream::udp::TEST_HEADER_SIZE { + //length must be at least enough to hold the test data crate::stream::udp::TEST_HEADER_SIZE } else { length } } - /// prepares a message used to describe the upload role in a test -pub fn prepare_upload_configuration(args:&clap::ArgMatches, test_id:&[u8; 16]) -> BoxResult { - let parallel_streams:u8 = args.value_of("parallel").unwrap().parse()?; - let mut seconds:f32 = args.value_of("time").unwrap().parse()?; - let mut send_interval:f32 = args.value_of("send_interval").unwrap().parse()?; - let mut length:u32 = args.value_of("length").unwrap().parse()?; - - let mut send_buffer:u32 = args.value_of("send_buffer").unwrap().parse()?; - +pub fn prepare_upload_configuration( + args: &clap::ArgMatches, + test_id: &[u8; 16], +) -> BoxResult { + let parallel_streams: u8 = args.value_of("parallel").unwrap().parse()?; + let mut seconds: f32 = args.value_of("time").unwrap().parse()?; + let mut send_interval: f32 = args.value_of("send_interval").unwrap().parse()?; + let mut length: u32 = args.value_of("length").unwrap().parse()?; + + let mut send_buffer: u32 = args.value_of("send_buffer").unwrap().parse()?; + let mut bandwidth_string = args.value_of("bandwidth").unwrap(); - let bandwidth:u64; - let bandwidth_multiplier:f64; + let bandwidth: u64; + let bandwidth_multiplier: f64; match bandwidth_string.chars().last() { Some(v) => { match v { - 'k' => { //kilobits + 'k' => { + //kilobits bandwidth_multiplier = 1000.0 / 8.0; - }, - 'K' => { //kilobytes + } + 'K' => { + //kilobytes bandwidth_multiplier = 1000.0; - }, - 'm' => { //megabits + } + 'm' => { + //megabits bandwidth_multiplier = 1000.0 * 1000.0 / 8.0; - }, - 'M' => { //megabytes + } + 'M' => { + //megabytes bandwidth_multiplier = 1000.0 * 1000.0; - }, - 'g' => { //gigabits + } + 'g' => { + //gigabits bandwidth_multiplier = 1000.0 * 1000.0 * 1000.0 / 8.0; - }, - 'G' => { //gigabytes + } + 'G' => { + //gigabytes bandwidth_multiplier = 1000.0 * 1000.0 * 1000.0; - }, + } _ => { bandwidth_multiplier = 1.0; - }, + } } - - if bandwidth_multiplier != 1.0 { //the value uses a suffix - bandwidth_string = &bandwidth_string[0..(bandwidth_string.len() - 1)]; + + if bandwidth_multiplier != 1.0 { + //the value uses a suffix + bandwidth_string = &bandwidth_string[0..(bandwidth_string.len() - 1)]; } - + match bandwidth_string.parse::() { Ok(v2) => { bandwidth = (v2 * bandwidth_multiplier) as u64; - }, - Err(_) => { //invalid input; fall back to 1mbps - log::warn!("invalid bandwidth: {}; setting value to 1mbps", args.value_of("bandwidth").unwrap()); + } + Err(_) => { + //invalid input; fall back to 1mbps + log::warn!( + "invalid bandwidth: {}; setting value to 1mbps", + args.value_of("bandwidth").unwrap() + ); bandwidth = 125000; - }, + } } - }, - None => { //invalid input; fall back to 1mbps - log::warn!("invalid bandwidth: {}; setting value to 1mbps", args.value_of("bandwidth").unwrap()); + } + None => { + //invalid input; fall back to 1mbps + log::warn!( + "invalid bandwidth: {}; setting value to 1mbps", + args.value_of("bandwidth").unwrap() + ); bandwidth = 125000; - }, + } } - + if seconds <= 0.0 { log::warn!("time was not in an acceptable range and has been set to 0.0"); seconds = 0.0 } - + if send_interval > 1.0 || send_interval <= 0.0 { log::warn!("send-interval was not in an acceptable range and has been set to 0.05"); send_interval = 0.05 } - + if args.is_present("udp") { log::debug!("preparing UDP upload config..."); if length == 0 { @@ -250,7 +291,15 @@ pub fn prepare_upload_configuration(args:&clap::ArgMatches, test_id:&[u8; 16]) - log::warn!("requested send-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", send_buffer, length * 2); send_buffer = length * 2; } - Ok(prepare_configuration_udp_upload(test_id, parallel_streams, bandwidth, seconds, length as u16, send_interval, send_buffer)) + Ok(prepare_configuration_udp_upload( + test_id, + parallel_streams, + bandwidth, + seconds, + length as u16, + send_interval, + send_buffer, + )) } else { log::debug!("preparing TCP upload config..."); if length == 0 { @@ -260,18 +309,30 @@ pub fn prepare_upload_configuration(args:&clap::ArgMatches, test_id:&[u8; 16]) - log::warn!("requested send-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", send_buffer, length * 2); send_buffer = length * 2; } - - let no_delay:bool = args.is_present("no_delay"); - - Ok(prepare_configuration_tcp_upload(test_id, parallel_streams, bandwidth, seconds, length as usize, send_interval, send_buffer, no_delay)) + + let no_delay: bool = args.is_present("no_delay"); + + Ok(prepare_configuration_tcp_upload( + test_id, + parallel_streams, + bandwidth, + seconds, + length as usize, + send_interval, + send_buffer, + no_delay, + )) } } /// prepares a message used to describe the download role in a test -pub fn prepare_download_configuration(args:&clap::ArgMatches, test_id:&[u8; 16]) -> BoxResult { - let parallel_streams:u8 = args.value_of("parallel").unwrap().parse()?; - let mut length:u32 = args.value_of("length").unwrap().parse()?; - let mut receive_buffer:u32 = args.value_of("receive_buffer").unwrap().parse()?; - +pub fn prepare_download_configuration( + args: &clap::ArgMatches, + test_id: &[u8; 16], +) -> BoxResult { + let parallel_streams: u8 = args.value_of("parallel").unwrap().parse()?; + let mut length: u32 = args.value_of("length").unwrap().parse()?; + let mut receive_buffer: u32 = args.value_of("receive_buffer").unwrap().parse()?; + if args.is_present("udp") { log::debug!("preparing UDP download config..."); if length == 0 { @@ -281,7 +342,12 @@ pub fn prepare_download_configuration(args:&clap::ArgMatches, test_id:&[u8; 16]) log::warn!("requested receive-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", receive_buffer, length * 2); receive_buffer = length * 2; } - Ok(prepare_configuration_udp_download(test_id, parallel_streams, length as u16, receive_buffer)) + Ok(prepare_configuration_udp_download( + test_id, + parallel_streams, + length as u16, + receive_buffer, + )) } else { log::debug!("preparing TCP download config..."); if length == 0 { @@ -291,6 +357,11 @@ pub fn prepare_download_configuration(args:&clap::ArgMatches, test_id:&[u8; 16]) log::warn!("requested receive-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", receive_buffer, length * 2); receive_buffer = length * 2; } - Ok(prepare_configuration_tcp_download(test_id, parallel_streams, length as usize, receive_buffer)) + Ok(prepare_configuration_tcp_download( + test_id, + parallel_streams, + length as usize, + receive_buffer, + )) } } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 8e4a093..afedd6d 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,19 +1,19 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ diff --git a/src/protocol/results.rs b/src/protocol/results.rs index 852361f..d59f833 100644 --- a/src/protocol/results.rs +++ b/src/protocol/results.rs @@ -1,19 +1,19 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ @@ -21,17 +21,16 @@ use std::collections::{HashMap, HashSet}; use std::time::{SystemTime, UNIX_EPOCH}; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; /* This module contains structures used to represent and collect the results of tests. * Since everything is basically just a data-container with representation methods, * it isn't extensively documented. */ - #[derive(PartialEq)] pub enum IntervalResultKind { ClientDone, @@ -53,27 +52,29 @@ pub fn get_unix_timestamp() -> f64 { pub trait IntervalResult { fn kind(&self) -> IntervalResultKind; - + fn get_stream_idx(&self) -> u8; - + fn to_json(&self) -> serde_json::Value; - + //produces test-results in tabular form - fn to_string(&self, bit:bool) -> String; + fn to_string(&self, bit: bool) -> String; } +pub type IntervalResultBox = Box; + pub struct ClientDoneResult { pub stream_idx: u8, } impl IntervalResult for ClientDoneResult { - fn kind(&self) -> IntervalResultKind{ + fn kind(&self) -> IntervalResultKind { IntervalResultKind::ClientDone } - + fn get_stream_idx(&self) -> u8 { self.stream_idx } - + fn to_json(&self) -> serde_json::Value { serde_json::json!({ "kind": "done", @@ -81,11 +82,12 @@ impl IntervalResult for ClientDoneResult { "stream_idx": self.stream_idx, }) } - - fn to_string(&self, _bit:bool) -> String { - format!("----------\n\ + + fn to_string(&self, _bit: bool) -> String { + format!( + "----------\n\ End of stream from client | stream: {}", - self.stream_idx, + self.stream_idx, ) } } @@ -93,14 +95,14 @@ pub struct ServerDoneResult { pub stream_idx: u8, } impl IntervalResult for ServerDoneResult { - fn kind(&self) -> IntervalResultKind{ + fn kind(&self) -> IntervalResultKind { IntervalResultKind::ServerDone } - + fn get_stream_idx(&self) -> u8 { self.stream_idx } - + fn to_json(&self) -> serde_json::Value { serde_json::json!({ "kind": "done", @@ -108,11 +110,12 @@ impl IntervalResult for ServerDoneResult { "stream_idx": self.stream_idx, }) } - - fn to_string(&self, _bit:bool) -> String { - format!("----------\n\ + + fn to_string(&self, _bit: bool) -> String { + format!( + "----------\n\ End of stream from server | stream: {}", - self.stream_idx, + self.stream_idx, ) } } @@ -121,14 +124,14 @@ pub struct ClientFailedResult { pub stream_idx: u8, } impl IntervalResult for ClientFailedResult { - fn kind(&self) -> IntervalResultKind{ + fn kind(&self) -> IntervalResultKind { IntervalResultKind::ClientFailed } - + fn get_stream_idx(&self) -> u8 { self.stream_idx } - + fn to_json(&self) -> serde_json::Value { serde_json::json!({ "kind": "failed", @@ -136,11 +139,12 @@ impl IntervalResult for ClientFailedResult { "stream_idx": self.stream_idx, }) } - - fn to_string(&self, _bit:bool) -> String { - format!("----------\n\ + + fn to_string(&self, _bit: bool) -> String { + format!( + "----------\n\ Failure in client stream | stream: {}", - self.stream_idx, + self.stream_idx, ) } } @@ -148,14 +152,14 @@ pub struct ServerFailedResult { pub stream_idx: u8, } impl IntervalResult for ServerFailedResult { - fn kind(&self) -> IntervalResultKind{ + fn kind(&self) -> IntervalResultKind { IntervalResultKind::ServerFailed } - + fn get_stream_idx(&self) -> u8 { self.stream_idx } - + fn to_json(&self) -> serde_json::Value { serde_json::json!({ "kind": "failed", @@ -163,32 +167,32 @@ impl IntervalResult for ServerFailedResult { "stream_idx": self.stream_idx, }) } - - fn to_string(&self, _bit:bool) -> String { - format!("----------\n\ + + fn to_string(&self, _bit: bool) -> String { + format!( + "----------\n\ Failure in server stream | stream: {}", - self.stream_idx, + self.stream_idx, ) } } - #[derive(Serialize, Deserialize)] pub struct TcpReceiveResult { pub timestamp: f64, - + pub stream_idx: u8, - + pub duration: f32, - + pub bytes_received: u64, } impl TcpReceiveResult { - fn from_json(value:serde_json::Value) -> BoxResult { - let receive_result:TcpReceiveResult = serde_json::from_value(value)?; + fn from_json(value: serde_json::Value) -> BoxResult { + let receive_result: TcpReceiveResult = serde_json::from_value(value)?; Ok(receive_result) } - + fn to_result_json(&self) -> serde_json::Value { let mut serialised = serde_json::to_value(self).unwrap(); serialised.as_object_mut().unwrap().remove("stream_idx"); @@ -196,41 +200,44 @@ impl TcpReceiveResult { } } impl IntervalResult for TcpReceiveResult { - fn kind(&self) -> IntervalResultKind{ + fn kind(&self) -> IntervalResultKind { IntervalResultKind::TcpReceive } - + fn get_stream_idx(&self) -> u8 { self.stream_idx } - + fn to_json(&self) -> serde_json::Value { let mut serialised = serde_json::to_value(self).unwrap(); serialised["family"] = serde_json::json!("tcp"); serialised["kind"] = serde_json::json!("receive"); serialised } - - fn to_string(&self, bit:bool) -> String { - let duration_divisor; - if self.duration == 0.0 { //avoid zerodiv, which can happen if the stream fails - duration_divisor = 1.0; + + fn to_string(&self, bit: bool) -> String { + let duration_divisor = if self.duration == 0.0 { + //avoid zerodiv, which can happen if the stream fails + 1.0 } else { - duration_divisor = self.duration; - } - + self.duration + }; + let bytes_per_second = self.bytes_received as f32 / duration_divisor; - + let throughput = match bit { - true => format!("megabits/second: {:.3}", bytes_per_second / (1_000_000.00 / 8.0)), + true => format!( + "megabits/second: {:.3}", + bytes_per_second / (1_000_000.00 / 8.0) + ), false => format!("megabytes/second: {:.3}", bytes_per_second / 1_000_000.00), }; - - format!("----------\n\ + + format!( + "----------\n\ TCP receive result over {:.2}s | stream: {}\n\ bytes: {} | per second: {:.3} | {}", - self.duration, self.stream_idx, - self.bytes_received, bytes_per_second, throughput, + self.duration, self.stream_idx, self.bytes_received, bytes_per_second, throughput, ) } } @@ -238,25 +245,26 @@ impl IntervalResult for TcpReceiveResult { #[derive(Serialize, Deserialize)] pub struct TcpSendResult { pub timestamp: f64, - + pub stream_idx: u8, - + pub duration: f32, - + pub bytes_sent: u64, pub sends_blocked: u64, } impl TcpSendResult { - fn from_json(value:serde_json::Value) -> BoxResult { + fn from_json(value: serde_json::Value) -> BoxResult { let mut local_value = value.clone(); - if local_value.get("sends_blocked").is_none() { //pre-0.1.8 peer - local_value["sends_blocked"] = serde_json::json!(0 as u64); //report pre-0.1.8 status + if local_value.get("sends_blocked").is_none() { + //pre-0.1.8 peer + local_value["sends_blocked"] = serde_json::json!(0_u64); //report pre-0.1.8 status } - - let send_result:TcpSendResult = serde_json::from_value(local_value)?; + + let send_result: TcpSendResult = serde_json::from_value(local_value)?; Ok(send_result) } - + fn to_result_json(&self) -> serde_json::Value { let mut serialised = serde_json::to_value(self).unwrap(); serialised.as_object_mut().unwrap().remove("stream_idx"); @@ -264,73 +272,78 @@ impl TcpSendResult { } } impl IntervalResult for TcpSendResult { - fn kind(&self) -> IntervalResultKind{ + fn kind(&self) -> IntervalResultKind { IntervalResultKind::TcpSend } - + fn get_stream_idx(&self) -> u8 { self.stream_idx } - + fn to_json(&self) -> serde_json::Value { let mut serialised = serde_json::to_value(self).unwrap(); serialised["family"] = serde_json::json!("tcp"); serialised["kind"] = serde_json::json!("send"); serialised } - - fn to_string(&self, bit:bool) -> String { - let duration_divisor; - if self.duration == 0.0 { //avoid zerodiv, which can happen if the stream fails - duration_divisor = 1.0; + + fn to_string(&self, bit: bool) -> String { + let duration_divisor = if self.duration == 0.0 { + //avoid zerodiv, which can happen if the stream fails + 1.0 } else { - duration_divisor = self.duration; - } - + self.duration + }; + let bytes_per_second = self.bytes_sent as f32 / duration_divisor; - + let throughput = match bit { - true => format!("megabits/second: {:.3}", bytes_per_second / (1_000_000.00 / 8.0)), + true => format!( + "megabits/second: {:.3}", + bytes_per_second / (1_000_000.00 / 8.0) + ), false => format!("megabytes/second: {:.3}", bytes_per_second / 1_000_000.00), }; - - let mut output = format!("----------\n\ + + let mut output = format!( + "----------\n\ TCP send result over {:.2}s | stream: {}\n\ bytes: {} | per second: {:.3} | {}", - self.duration, self.stream_idx, - self.bytes_sent, bytes_per_second, throughput, + self.duration, self.stream_idx, self.bytes_sent, bytes_per_second, throughput, ); if self.sends_blocked > 0 { - output.push_str(&format!("\nstalls due to full send-buffer: {}", self.sends_blocked)); + output.push_str(&format!( + "\nstalls due to full send-buffer: {}", + self.sends_blocked + )); } output } } - #[derive(Serialize, Deserialize)] pub struct UdpReceiveResult { pub timestamp: f64, - + pub stream_idx: u8, - + pub duration: f32, - + pub bytes_received: u64, pub packets_received: u64, pub packets_lost: i64, pub packets_out_of_order: u64, pub packets_duplicated: u64, - + pub unbroken_sequence: u64, pub jitter_seconds: Option, } impl UdpReceiveResult { - fn from_json(value:serde_json::Value) -> BoxResult { - let receive_result:UdpReceiveResult = serde_json::from_value(value)?; + fn from_json(value: serde_json::Value) -> BoxResult { + let receive_result: UdpReceiveResult = serde_json::from_value(value)?; Ok(receive_result) } - + fn to_result_json(&self) -> serde_json::Value { let mut serialised = serde_json::to_value(self).unwrap(); serialised.as_object_mut().unwrap().remove("stream_idx"); @@ -338,36 +351,39 @@ impl UdpReceiveResult { } } impl IntervalResult for UdpReceiveResult { - fn kind(&self) -> IntervalResultKind{ + fn kind(&self) -> IntervalResultKind { IntervalResultKind::UdpReceive } - + fn get_stream_idx(&self) -> u8 { self.stream_idx } - + fn to_json(&self) -> serde_json::Value { let mut serialised = serde_json::to_value(self).unwrap(); serialised["family"] = serde_json::json!("udp"); serialised["kind"] = serde_json::json!("receive"); serialised } - - fn to_string(&self, bit:bool) -> String { - let duration_divisor; - if self.duration == 0.0 { //avoid zerodiv, which can happen if the stream fails - duration_divisor = 1.0; + + fn to_string(&self, bit: bool) -> String { + let duration_divisor = if self.duration == 0.0 { + //avoid zerodiv, which can happen if the stream fails + 1.0 } else { - duration_divisor = self.duration; - } - + self.duration + }; + let bytes_per_second = self.bytes_received as f32 / duration_divisor; - + let throughput = match bit { - true => format!("megabits/second: {:.3}", bytes_per_second / (1_000_000.00 / 8.0)), + true => format!( + "megabits/second: {:.3}", + bytes_per_second / (1_000_000.00 / 8.0) + ), false => format!("megabytes/second: {:.3}", bytes_per_second / 1_000_000.00), }; - + let mut output = format!("----------\n\ UDP receive result over {:.2}s | stream: {}\n\ bytes: {} | per second: {:.3} | {}\n\ @@ -377,7 +393,11 @@ impl IntervalResult for UdpReceiveResult { self.packets_received, self.packets_lost, self.packets_out_of_order, self.packets_duplicated, self.packets_received as f32 / duration_divisor, ); if self.jitter_seconds.is_some() { - output.push_str(&format!("\njitter: {:.6}s over {} consecutive packets", self.jitter_seconds.unwrap(), self.unbroken_sequence)); + output.push_str(&format!( + "\njitter: {:.6}s over {} consecutive packets", + self.jitter_seconds.unwrap(), + self.unbroken_sequence + )); } output } @@ -386,26 +406,27 @@ impl IntervalResult for UdpReceiveResult { #[derive(Serialize, Deserialize)] pub struct UdpSendResult { pub timestamp: f64, - + pub stream_idx: u8, - + pub duration: f32, - + pub bytes_sent: u64, pub packets_sent: u64, pub sends_blocked: u64, } impl UdpSendResult { - fn from_json(value:serde_json::Value) -> BoxResult { + fn from_json(value: serde_json::Value) -> BoxResult { let mut local_value = value.clone(); - if local_value.get("sends_blocked").is_none() { //pre-0.1.8 peer - local_value["sends_blocked"] = serde_json::json!(0 as u64); //report pre-0.1.8 status + if local_value.get("sends_blocked").is_none() { + //pre-0.1.8 peer + local_value["sends_blocked"] = serde_json::json!(0_u64); //report pre-0.1.8 status } - - let send_result:UdpSendResult = serde_json::from_value(local_value)?; + + let send_result: UdpSendResult = serde_json::from_value(local_value)?; Ok(send_result) } - + fn to_result_json(&self) -> serde_json::Value { let mut serialised = serde_json::to_value(self).unwrap(); serialised.as_object_mut().unwrap().remove("stream_idx"); @@ -413,53 +434,63 @@ impl UdpSendResult { } } impl IntervalResult for UdpSendResult { - fn kind(&self) -> IntervalResultKind{ + fn kind(&self) -> IntervalResultKind { IntervalResultKind::UdpSend } - + fn get_stream_idx(&self) -> u8 { self.stream_idx } - + fn to_json(&self) -> serde_json::Value { let mut serialised = serde_json::to_value(self).unwrap(); serialised["family"] = serde_json::json!("udp"); serialised["kind"] = serde_json::json!("send"); serialised } - - fn to_string(&self, bit:bool) -> String { - let duration_divisor; - if self.duration == 0.0 { //avoid zerodiv, which can happen if the stream fails - duration_divisor = 1.0; + + fn to_string(&self, bit: bool) -> String { + let duration_divisor = if self.duration == 0.0 { + //avoid zerodiv, which can happen if the stream fails + 1.0 } else { - duration_divisor = self.duration; - } - + self.duration + }; + let bytes_per_second = self.bytes_sent as f32 / duration_divisor; - + let throughput = match bit { - true => format!("megabits/second: {:.3}", bytes_per_second / (1_000_000.00 / 8.0)), + true => format!( + "megabits/second: {:.3}", + bytes_per_second / (1_000_000.00 / 8.0) + ), false => format!("megabytes/second: {:.3}", bytes_per_second / 1_000_000.00), }; - - let mut output = format!("----------\n\ + + let mut output = format!( + "----------\n\ UDP send result over {:.2}s | stream: {}\n\ bytes: {} | per second: {:.3} | {}\n\ packets: {} per second: {:.3}", - self.duration, self.stream_idx, - self.bytes_sent, bytes_per_second, throughput, - self.packets_sent, self.packets_sent as f32 / duration_divisor, + self.duration, + self.stream_idx, + self.bytes_sent, + bytes_per_second, + throughput, + self.packets_sent, + self.packets_sent as f32 / duration_divisor, ); if self.sends_blocked > 0 { - output.push_str(&format!("\nstalls due to full send-buffer: {}", self.sends_blocked)); + output.push_str(&format!( + "\nstalls due to full send-buffer: {}", + self.sends_blocked + )); } output } } - -pub fn interval_result_from_json(value:serde_json::Value) -> BoxResult> { +pub fn interval_result_from_json(value: serde_json::Value) -> BoxResult> { match value.get("family") { Some(f) => match f.as_str() { Some(family) => match family { @@ -468,38 +499,56 @@ pub fn interval_result_from_json(value:serde_json::Value) -> BoxResult match kind { "receive" => Ok(Box::new(TcpReceiveResult::from_json(value)?)), "send" => Ok(Box::new(TcpSendResult::from_json(value)?)), - _ => Err(Box::new(simple_error::simple_error!("unsupported interval-result kind: {}", kind))), + _ => Err(Box::new(simple_error::simple_error!( + "unsupported interval-result kind: {}", + kind + ))), }, - None => Err(Box::new(simple_error::simple_error!("interval-result's kind is not a string"))), + None => Err(Box::new(simple_error::simple_error!( + "interval-result's kind is not a string" + ))), }, - None => Err(Box::new(simple_error::simple_error!("interval-result has no kind"))), + None => Err(Box::new(simple_error::simple_error!( + "interval-result has no kind" + ))), }, "udp" => match value.get("kind") { Some(k) => match k.as_str() { Some(kind) => match kind { "receive" => Ok(Box::new(UdpReceiveResult::from_json(value)?)), "send" => Ok(Box::new(UdpSendResult::from_json(value)?)), - _ => Err(Box::new(simple_error::simple_error!("unsupported interval-result kind: {}", kind))), + _ => Err(Box::new(simple_error::simple_error!( + "unsupported interval-result kind: {}", + kind + ))), }, - None => Err(Box::new(simple_error::simple_error!("interval-result's kind is not a string"))), + None => Err(Box::new(simple_error::simple_error!( + "interval-result's kind is not a string" + ))), }, - None => Err(Box::new(simple_error::simple_error!("interval-result has no kind"))), + None => Err(Box::new(simple_error::simple_error!( + "interval-result has no kind" + ))), }, - _ => Err(Box::new(simple_error::simple_error!("unsupported interval-result family: {}", family))), + _ => Err(Box::new(simple_error::simple_error!( + "unsupported interval-result family: {}", + family + ))), }, - None => Err(Box::new(simple_error::simple_error!("interval-result's family is not a string"))), + None => Err(Box::new(simple_error::simple_error!( + "interval-result's family is not a string" + ))), }, - None => Err(Box::new(simple_error::simple_error!("interval-result has no family"))), + None => Err(Box::new(simple_error::simple_error!( + "interval-result has no family" + ))), } } - - - pub trait StreamResults { - fn update_from_json(&mut self, value:serde_json::Value) -> BoxResult<()>; - - fn to_json(&self, omit_seconds:usize) -> serde_json::Value; + fn update_from_json(&mut self, value: serde_json::Value) -> BoxResult<()>; + + fn to_json(&self, omit_seconds: usize) -> serde_json::Value; } struct TcpStreamResults { @@ -507,53 +556,67 @@ struct TcpStreamResults { send_results: Vec, } impl StreamResults for TcpStreamResults { - fn update_from_json(&mut self, value:serde_json::Value) -> BoxResult<()> { + fn update_from_json(&mut self, value: serde_json::Value) -> BoxResult<()> { match value.get("kind") { Some(k) => match k.as_str() { Some(kind) => match kind { - "send" => Ok(self.send_results.push(TcpSendResult::from_json(value)?)), - "receive" => Ok(self.receive_results.push(TcpReceiveResult::from_json(value)?)), - _ => Err(Box::new(simple_error::simple_error!("unsupported kind for TCP stream-result: {}", kind))), + "send" => { + self.send_results.push(TcpSendResult::from_json(value)?); + Ok(()) + } + "receive" => { + self.receive_results + .push(TcpReceiveResult::from_json(value)?); + Ok(()) + } + _ => Err(Box::new(simple_error::simple_error!( + "unsupported kind for TCP stream-result: {}", + kind + ))), }, - None => Err(Box::new(simple_error::simple_error!("kind must be a string for TCP stream-result"))), + None => Err(Box::new(simple_error::simple_error!( + "kind must be a string for TCP stream-result" + ))), }, - None => Err(Box::new(simple_error::simple_error!("no kind specified for TCP stream-result"))), + None => Err(Box::new(simple_error::simple_error!( + "no kind specified for TCP stream-result" + ))), } } - - fn to_json(&self, omit_seconds:usize) -> serde_json::Value { - let mut duration_send:f64 = 0.0; - let mut bytes_sent:u64 = 0; - - let mut duration_receive:f64 = 0.0; - let mut bytes_received:u64 = 0; - + + fn to_json(&self, omit_seconds: usize) -> serde_json::Value { + let mut duration_send: f64 = 0.0; + let mut bytes_sent: u64 = 0; + + let mut duration_receive: f64 = 0.0; + let mut bytes_received: u64 = 0; + for (i, sr) in self.send_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_send += sr.duration as f64; bytes_sent += sr.bytes_sent; } - + for (i, rr) in self.receive_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_receive += rr.duration as f64; bytes_received += rr.bytes_received; } - + let summary = serde_json::json!({ "duration_send": duration_send, "bytes_sent": bytes_sent, - + "duration_receive": duration_receive, "bytes_received": bytes_received, }); - + serde_json::json!({ "receive": self.receive_results.iter().map(|rr| rr.to_result_json()).collect::>(), "send": self.send_results.iter().map(|sr| sr.to_result_json()).collect::>(), @@ -567,78 +630,92 @@ struct UdpStreamResults { send_results: Vec, } impl StreamResults for UdpStreamResults { - fn update_from_json(&mut self, value:serde_json::Value) -> BoxResult<()> { + fn update_from_json(&mut self, value: serde_json::Value) -> BoxResult<()> { match value.get("kind") { Some(k) => match k.as_str() { Some(kind) => match kind { - "send" => Ok(self.send_results.push(UdpSendResult::from_json(value)?)), - "receive" => Ok(self.receive_results.push(UdpReceiveResult::from_json(value)?)), - _ => Err(Box::new(simple_error::simple_error!("unsupported kind for UDP stream-result: {}", kind))), + "send" => { + self.send_results.push(UdpSendResult::from_json(value)?); + Ok(()) + } + "receive" => { + self.receive_results + .push(UdpReceiveResult::from_json(value)?); + Ok(()) + } + _ => Err(Box::new(simple_error::simple_error!( + "unsupported kind for UDP stream-result: {}", + kind + ))), }, - None => Err(Box::new(simple_error::simple_error!("kind must be a string for UDP stream-result"))), + None => Err(Box::new(simple_error::simple_error!( + "kind must be a string for UDP stream-result" + ))), }, - None => Err(Box::new(simple_error::simple_error!("no kind specified for UDP stream-result"))), + None => Err(Box::new(simple_error::simple_error!( + "no kind specified for UDP stream-result" + ))), } } - - fn to_json(&self, omit_seconds:usize) -> serde_json::Value { - let mut duration_send:f64 = 0.0; - - let mut bytes_sent:u64 = 0; - let mut packets_sent:u64 = 0; - - - let mut duration_receive:f64 = 0.0; - - let mut bytes_received:u64 = 0; - let mut packets_received:u64 = 0; - let mut packets_out_of_order:u64 = 0; - let mut packets_duplicated:u64 = 0; - + + fn to_json(&self, omit_seconds: usize) -> serde_json::Value { + let mut duration_send: f64 = 0.0; + + let mut bytes_sent: u64 = 0; + let mut packets_sent: u64 = 0; + + let mut duration_receive: f64 = 0.0; + + let mut bytes_received: u64 = 0; + let mut packets_received: u64 = 0; + let mut packets_out_of_order: u64 = 0; + let mut packets_duplicated: u64 = 0; + let mut jitter_calculated = false; - let mut unbroken_sequence_count:u64 = 0; - let mut jitter_weight:f64 = 0.0; - + let mut unbroken_sequence_count: u64 = 0; + let mut jitter_weight: f64 = 0.0; + for (i, sr) in self.send_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_send += sr.duration as f64; - + bytes_sent += sr.bytes_sent; packets_sent += sr.packets_sent; } - + for (i, rr) in self.receive_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_receive += rr.duration as f64; - + bytes_received += rr.bytes_received; packets_received += rr.packets_received; packets_out_of_order += rr.packets_out_of_order; packets_duplicated += rr.packets_duplicated; - + if rr.jitter_seconds.is_some() { - jitter_weight += (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); + jitter_weight += + (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); unbroken_sequence_count += rr.unbroken_sequence; - + jitter_calculated = true; } } - + let mut summary = serde_json::json!({ "duration_send": duration_send, - + "bytes_sent": bytes_sent, "packets_sent": packets_sent, - - + + "duration_receive": duration_receive, - + "bytes_received": bytes_received, "packets_received": packets_received, "packets_lost": packets_sent - packets_received, @@ -649,10 +726,11 @@ impl StreamResults for UdpStreamResults { summary["framed_packet_size"] = serde_json::json!(bytes_sent / packets_sent); } if jitter_calculated { - summary["jitter_average"] = serde_json::json!(jitter_weight / (unbroken_sequence_count as f64)); + summary["jitter_average"] = + serde_json::json!(jitter_weight / (unbroken_sequence_count as f64)); summary["jitter_packets_consecutive"] = serde_json::json!(unbroken_sequence_count); } - + serde_json::json!({ "receive": self.receive_results.iter().map(|rr| rr.to_result_json()).collect::>(), "send": self.send_results.iter().map(|sr| sr.to_result_json()).collect::>(), @@ -661,27 +739,47 @@ impl StreamResults for UdpStreamResults { } } - pub trait TestResults { fn count_in_progress_streams(&self) -> u8; - fn mark_stream_done(&mut self, idx:&u8, success:bool); - + fn mark_stream_done(&mut self, idx: &u8, success: bool); + fn count_in_progress_streams_server(&self) -> u8; - fn mark_stream_done_server(&mut self, idx:&u8); - + fn mark_stream_done_server(&mut self, idx: &u8); + fn is_success(&self) -> bool; - - fn update_from_json(&mut self, value:serde_json::Value) -> BoxResult<()>; - - fn to_json(&self, omit_seconds:usize, upload_config:serde_json::Value, download_config:serde_json::Value, common_config:serde_json::Value, additional_config:serde_json::Value) -> serde_json::Value; - + + fn update_from_json(&mut self, value: serde_json::Value) -> BoxResult<()>; + + fn to_json( + &self, + omit_seconds: usize, + upload_config: serde_json::Value, + download_config: serde_json::Value, + common_config: serde_json::Value, + additional_config: serde_json::Value, + ) -> serde_json::Value; + //produces a pretty-printed JSON string with the test results - fn to_json_string(&self, omit_seconds:usize, upload_config:serde_json::Value, download_config:serde_json::Value, common_config:serde_json::Value, additional_config:serde_json::Value) -> String { - serde_json::to_string_pretty(&self.to_json(omit_seconds, upload_config, download_config, common_config, additional_config)).unwrap() + fn to_json_string( + &self, + omit_seconds: usize, + upload_config: serde_json::Value, + download_config: serde_json::Value, + common_config: serde_json::Value, + additional_config: serde_json::Value, + ) -> String { + serde_json::to_string_pretty(&self.to_json( + omit_seconds, + upload_config, + download_config, + common_config, + additional_config, + )) + .unwrap() } - + //produces test-results in tabular form - fn to_string(&self, bit:bool, omit_seconds:usize) -> String; + fn to_string(&self, bit: bool, omit_seconds: usize) -> String; } pub struct TcpTestResults { @@ -692,18 +790,21 @@ pub struct TcpTestResults { } impl TcpTestResults { pub fn new() -> TcpTestResults { - TcpTestResults{ + TcpTestResults { stream_results: HashMap::new(), pending_tests: HashSet::new(), failed_tests: HashSet::new(), server_tests_finished: HashSet::new(), } } - pub fn prepare_index(&mut self, idx:&u8) { - self.stream_results.insert(*idx, TcpStreamResults{ - receive_results: Vec::new(), - send_results: Vec::new(), - }); + pub fn prepare_index(&mut self, idx: &u8) { + self.stream_results.insert( + *idx, + TcpStreamResults { + receive_results: Vec::new(), + send_results: Vec::new(), + }, + ); self.pending_tests.insert(*idx); } } @@ -711,15 +812,15 @@ impl TestResults for TcpTestResults { fn count_in_progress_streams(&self) -> u8 { self.pending_tests.len() as u8 } - fn mark_stream_done(&mut self, idx:&u8, success:bool) { + fn mark_stream_done(&mut self, idx: &u8, success: bool) { self.pending_tests.remove(idx); if !success { self.failed_tests.insert(*idx); } } - + fn count_in_progress_streams_server(&self) -> u8 { - let mut count:u8 = 0; + let mut count: u8 = 0; for idx in self.stream_results.keys() { if !self.server_tests_finished.contains(idx) { count += 1; @@ -727,15 +828,15 @@ impl TestResults for TcpTestResults { } count } - fn mark_stream_done_server(&mut self, idx:&u8) { + fn mark_stream_done_server(&mut self, idx: &u8) { self.server_tests_finished.insert(*idx); } - + fn is_success(&self) -> bool { - self.pending_tests.len() == 0 && self.failed_tests.len() == 0 + self.pending_tests.is_empty() && self.failed_tests.is_empty() } - - fn update_from_json(&mut self, value:serde_json::Value) -> BoxResult<()> { + + fn update_from_json(&mut self, value: serde_json::Value) -> BoxResult<()> { match value.get("family") { Some(f) => match f.as_str() { Some(family) => match family { @@ -743,28 +844,48 @@ impl TestResults for TcpTestResults { Some(idx) => match idx.as_i64() { Some(idx64) => match self.stream_results.get_mut(&(idx64 as u8)) { Some(stream_results) => stream_results.update_from_json(value), - None => Err(Box::new(simple_error::simple_error!("stream-index {} is not a valid identifier", idx64))), + None => Err(Box::new(simple_error::simple_error!( + "stream-index {} is not a valid identifier", + idx64 + ))), }, - None => Err(Box::new(simple_error::simple_error!("stream-index is not an integer"))), + None => Err(Box::new(simple_error::simple_error!( + "stream-index is not an integer" + ))), }, - None => Err(Box::new(simple_error::simple_error!("no stream-index specified"))), + None => Err(Box::new(simple_error::simple_error!( + "no stream-index specified" + ))), }, - _ => Err(Box::new(simple_error::simple_error!("unsupported family for TCP stream-result: {}", family))), + _ => Err(Box::new(simple_error::simple_error!( + "unsupported family for TCP stream-result: {}", + family + ))), }, - None => Err(Box::new(simple_error::simple_error!("kind must be a string for TCP stream-result"))), + None => Err(Box::new(simple_error::simple_error!( + "kind must be a string for TCP stream-result" + ))), }, - None => Err(Box::new(simple_error::simple_error!("no kind specified for TCP stream-result"))), + None => Err(Box::new(simple_error::simple_error!( + "no kind specified for TCP stream-result" + ))), } } - - fn to_json(&self, omit_seconds:usize, upload_config:serde_json::Value, download_config:serde_json::Value, common_config:serde_json::Value, additional_config:serde_json::Value) -> serde_json::Value { - let mut duration_send:f64 = 0.0; - let mut bytes_sent:u64 = 0; - - let mut duration_receive:f64 = 0.0; - let mut bytes_received:u64 = 0; - - + + fn to_json( + &self, + omit_seconds: usize, + upload_config: serde_json::Value, + download_config: serde_json::Value, + common_config: serde_json::Value, + additional_config: serde_json::Value, + ) -> serde_json::Value { + let mut duration_send: f64 = 0.0; + let mut bytes_sent: u64 = 0; + + let mut duration_receive: f64 = 0.0; + let mut bytes_received: u64 = 0; + let mut streams = Vec::with_capacity(self.stream_results.len()); for (idx, stream) in self.stream_results.iter() { streams.push(serde_json::json!({ @@ -772,34 +893,34 @@ impl TestResults for TcpTestResults { "abandoned": self.pending_tests.contains(idx), "failed": self.failed_tests.contains(idx), })); - + for (i, sr) in stream.send_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_send += sr.duration as f64; bytes_sent += sr.bytes_sent; } - + for (i, rr) in stream.receive_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_receive += rr.duration as f64; bytes_received += rr.bytes_received; } } - + let summary = serde_json::json!({ "duration_send": duration_send, "bytes_sent": bytes_sent, - + "duration_receive": duration_receive, "bytes_received": bytes_received, }); - + serde_json::json!({ "config": { "upload": upload_config, @@ -812,81 +933,106 @@ impl TestResults for TcpTestResults { "success": self.is_success(), }) } - - fn to_string(&self, bit:bool, omit_seconds:usize) -> String { + + fn to_string(&self, bit: bool, omit_seconds: usize) -> String { let stream_count = self.stream_results.len(); let mut stream_send_durations = vec![0.0; stream_count]; let mut stream_receive_durations = vec![0.0; stream_count]; - - let mut duration_send:f64 = 0.0; - let mut bytes_sent:u64 = 0; - - let mut duration_receive:f64 = 0.0; - let mut bytes_received:u64 = 0; - + + let mut duration_send: f64 = 0.0; + let mut bytes_sent: u64 = 0; + + let mut duration_receive: f64 = 0.0; + let mut bytes_received: u64 = 0; + let mut sends_blocked = false; - + for (stream_idx, stream) in self.stream_results.values().enumerate() { for (i, sr) in stream.send_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_send += sr.duration as f64; stream_send_durations[stream_idx] += sr.duration as f64; - + bytes_sent += sr.bytes_sent; - + sends_blocked |= sr.sends_blocked > 0; } - + for (i, rr) in stream.receive_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_receive += rr.duration as f64; stream_receive_durations[stream_idx] += rr.duration as f64; - + bytes_received += rr.bytes_received; } } stream_send_durations.sort_by(|a, b| a.partial_cmp(b).unwrap()); stream_receive_durations.sort_by(|a, b| a.partial_cmp(b).unwrap()); - - let send_duration_divisor; - if duration_send == 0.0 { //avoid zerodiv, which can happen if all streams fail - send_duration_divisor = 1.0; + + let send_duration_divisor = if duration_send == 0.0 { + //avoid zerodiv, which can happen if all streams fail + 1.0 } else { - send_duration_divisor = duration_send; - } + duration_send + }; let send_bytes_per_second = bytes_sent as f64 / send_duration_divisor; let send_throughput = match bit { - true => format!("megabits/second: {:.3}", send_bytes_per_second / (1_000_000.00 / 8.0)), - false => format!("megabytes/second: {:.3}", send_bytes_per_second / 1_000_000.00), + true => format!( + "megabits/second: {:.3}", + send_bytes_per_second / (1_000_000.00 / 8.0) + ), + false => format!( + "megabytes/second: {:.3}", + send_bytes_per_second / 1_000_000.00 + ), }; let total_send_throughput = match bit { - true => format!("megabits/second: {:.3}", (send_bytes_per_second / (1_000_000.00 / 8.0)) * stream_count as f64), - false => format!("megabytes/second: {:.3}", (send_bytes_per_second / 1_000_000.00) * stream_count as f64), + true => format!( + "megabits/second: {:.3}", + (send_bytes_per_second / (1_000_000.00 / 8.0)) * stream_count as f64 + ), + false => format!( + "megabytes/second: {:.3}", + (send_bytes_per_second / 1_000_000.00) * stream_count as f64 + ), }; - - let receive_duration_divisor; - if duration_receive == 0.0 { //avoid zerodiv, which can happen if all streams fail - receive_duration_divisor = 1.0; + + let receive_duration_divisor = if duration_receive == 0.0 { + //avoid zerodiv, which can happen if all streams fail + 1.0 } else { - receive_duration_divisor = duration_receive; - } + duration_receive + }; let receive_bytes_per_second = bytes_received as f64 / receive_duration_divisor; let receive_throughput = match bit { - true => format!("megabits/second: {:.3}", receive_bytes_per_second / (1_000_000.00 / 8.0)), - false => format!("megabytes/second: {:.3}", receive_bytes_per_second / 1_000_000.00), + true => format!( + "megabits/second: {:.3}", + receive_bytes_per_second / (1_000_000.00 / 8.0) + ), + false => format!( + "megabytes/second: {:.3}", + receive_bytes_per_second / 1_000_000.00 + ), }; let total_receive_throughput = match bit { - true => format!("megabits/second: {:.3}", (receive_bytes_per_second / (1_000_000.00 / 8.0)) * stream_count as f64), - false => format!("megabytes/second: {:.3}", (receive_bytes_per_second / 1_000_000.00) * stream_count as f64), + true => format!( + "megabits/second: {:.3}", + (receive_bytes_per_second / (1_000_000.00 / 8.0)) * stream_count as f64 + ), + false => format!( + "megabytes/second: {:.3}", + (receive_bytes_per_second / 1_000_000.00) * stream_count as f64 + ), }; - - let mut output = format!("==========\n\ + + let mut output = format!( + "==========\n\ TCP send result over {:.2}s | streams: {}\n\ stream-average bytes per second: {:.3} | {}\n\ total bytes: {} | per second: {:.3} | {}\n\ @@ -894,21 +1040,28 @@ impl TestResults for TcpTestResults { TCP receive result over {:.2}s | streams: {}\n\ stream-average bytes per second: {:.3} | {}\n\ total bytes: {} | per second: {:.3} | {}", - stream_send_durations[stream_send_durations.len() - 1], stream_count, - send_bytes_per_second, send_throughput, - bytes_sent, send_bytes_per_second * stream_count as f64, total_send_throughput, - - stream_receive_durations[stream_receive_durations.len() - 1], stream_count, - receive_bytes_per_second, receive_throughput, - bytes_received, receive_bytes_per_second * stream_count as f64, total_receive_throughput, + stream_send_durations[stream_send_durations.len() - 1], + stream_count, + send_bytes_per_second, + send_throughput, + bytes_sent, + send_bytes_per_second * stream_count as f64, + total_send_throughput, + stream_receive_durations[stream_receive_durations.len() - 1], + stream_count, + receive_bytes_per_second, + receive_throughput, + bytes_received, + receive_bytes_per_second * stream_count as f64, + total_receive_throughput, ); if sends_blocked { - output.push_str(&format!("\nthroughput throttled by buffer limitations")); + output.push_str("\nthroughput throttled by buffer limitations"); } if !self.is_success() { - output.push_str(&format!("\nTESTING DID NOT COMPLETE SUCCESSFULLY")); + output.push_str("\nTESTING DID NOT COMPLETE SUCCESSFULLY"); } - + output } } @@ -921,18 +1074,21 @@ pub struct UdpTestResults { } impl UdpTestResults { pub fn new() -> UdpTestResults { - UdpTestResults{ + UdpTestResults { stream_results: HashMap::new(), pending_tests: HashSet::new(), failed_tests: HashSet::new(), server_tests_finished: HashSet::new(), } } - pub fn prepare_index(&mut self, idx:&u8) { - self.stream_results.insert(*idx, UdpStreamResults{ - receive_results: Vec::new(), - send_results: Vec::new(), - }); + pub fn prepare_index(&mut self, idx: &u8) { + self.stream_results.insert( + *idx, + UdpStreamResults { + receive_results: Vec::new(), + send_results: Vec::new(), + }, + ); self.pending_tests.insert(*idx); } } @@ -940,15 +1096,15 @@ impl TestResults for UdpTestResults { fn count_in_progress_streams(&self) -> u8 { self.pending_tests.len() as u8 } - fn mark_stream_done(&mut self, idx:&u8, success:bool) { + fn mark_stream_done(&mut self, idx: &u8, success: bool) { self.pending_tests.remove(idx); if !success { self.failed_tests.insert(*idx); } } - + fn count_in_progress_streams_server(&self) -> u8 { - let mut count:u8 = 0; + let mut count: u8 = 0; for idx in self.stream_results.keys() { if !self.server_tests_finished.contains(idx) { count += 1; @@ -956,15 +1112,15 @@ impl TestResults for UdpTestResults { } count } - fn mark_stream_done_server(&mut self, idx:&u8) { + fn mark_stream_done_server(&mut self, idx: &u8) { self.server_tests_finished.insert(*idx); } - + fn is_success(&self) -> bool { - self.pending_tests.len() == 0 && self.failed_tests.len() == 0 + self.pending_tests.is_empty() && self.failed_tests.is_empty() } - - fn update_from_json(&mut self, value:serde_json::Value) -> BoxResult<()> { + + fn update_from_json(&mut self, value: serde_json::Value) -> BoxResult<()> { match value.get("family") { Some(f) => match f.as_str() { Some(family) => match family { @@ -972,39 +1128,58 @@ impl TestResults for UdpTestResults { Some(idx) => match idx.as_i64() { Some(idx64) => match self.stream_results.get_mut(&(idx64 as u8)) { Some(stream_results) => stream_results.update_from_json(value), - None => Err(Box::new(simple_error::simple_error!("stream-index {} is not a valid identifier", idx64))), + None => Err(Box::new(simple_error::simple_error!( + "stream-index {} is not a valid identifier", + idx64 + ))), }, - None => Err(Box::new(simple_error::simple_error!("stream-index is not an integer"))), + None => Err(Box::new(simple_error::simple_error!( + "stream-index is not an integer" + ))), }, - None => Err(Box::new(simple_error::simple_error!("no stream-index specified"))), + None => Err(Box::new(simple_error::simple_error!( + "no stream-index specified" + ))), }, - _ => Err(Box::new(simple_error::simple_error!("unsupported family for UDP stream-result: {}", family))), + _ => Err(Box::new(simple_error::simple_error!( + "unsupported family for UDP stream-result: {}", + family + ))), }, - None => Err(Box::new(simple_error::simple_error!("kind must be a string for UDP stream-result"))), + None => Err(Box::new(simple_error::simple_error!( + "kind must be a string for UDP stream-result" + ))), }, - None => Err(Box::new(simple_error::simple_error!("no kind specified for UDP stream-result"))), + None => Err(Box::new(simple_error::simple_error!( + "no kind specified for UDP stream-result" + ))), } } - - fn to_json(&self, omit_seconds:usize, upload_config:serde_json::Value, download_config:serde_json::Value, common_config:serde_json::Value, additional_config:serde_json::Value) -> serde_json::Value { - let mut duration_send:f64 = 0.0; - - let mut bytes_sent:u64 = 0; - let mut packets_sent:u64 = 0; - - - let mut duration_receive:f64 = 0.0; - - let mut bytes_received:u64 = 0; - let mut packets_received:u64 = 0; - let mut packets_out_of_order:u64 = 0; - let mut packets_duplicated:u64 = 0; - + + fn to_json( + &self, + omit_seconds: usize, + upload_config: serde_json::Value, + download_config: serde_json::Value, + common_config: serde_json::Value, + additional_config: serde_json::Value, + ) -> serde_json::Value { + let mut duration_send: f64 = 0.0; + + let mut bytes_sent: u64 = 0; + let mut packets_sent: u64 = 0; + + let mut duration_receive: f64 = 0.0; + + let mut bytes_received: u64 = 0; + let mut packets_received: u64 = 0; + let mut packets_out_of_order: u64 = 0; + let mut packets_duplicated: u64 = 0; + let mut jitter_calculated = false; - let mut unbroken_sequence_count:u64 = 0; - let mut jitter_weight:f64 = 0.0; - - + let mut unbroken_sequence_count: u64 = 0; + let mut jitter_weight: f64 = 0.0; + let mut streams = Vec::with_capacity(self.stream_results.len()); for (idx, stream) in self.stream_results.iter() { streams.push(serde_json::json!({ @@ -1012,48 +1187,49 @@ impl TestResults for UdpTestResults { "abandoned": self.pending_tests.contains(idx), "failed": self.failed_tests.contains(idx), })); - + for (i, sr) in stream.send_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_send += sr.duration as f64; - + bytes_sent += sr.bytes_sent; packets_sent += sr.packets_sent; } - + for (i, rr) in stream.receive_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_receive += rr.duration as f64; - + bytes_received += rr.bytes_received; packets_received += rr.packets_received; packets_out_of_order += rr.packets_out_of_order; packets_duplicated += rr.packets_duplicated; - + if rr.jitter_seconds.is_some() { - jitter_weight += (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); + jitter_weight += + (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); unbroken_sequence_count += rr.unbroken_sequence; - + jitter_calculated = true; } } } - + let mut summary = serde_json::json!({ "duration_send": duration_send, - + "bytes_sent": bytes_sent, "packets_sent": packets_sent, - - + + "duration_receive": duration_receive, - + "bytes_received": bytes_received, "packets_received": packets_received, "packets_lost": packets_sent - packets_received, @@ -1064,10 +1240,11 @@ impl TestResults for UdpTestResults { summary["framed_packet_size"] = serde_json::json!(bytes_sent / packets_sent); } if jitter_calculated { - summary["jitter_average"] = serde_json::json!(jitter_weight / (unbroken_sequence_count as f64)); + summary["jitter_average"] = + serde_json::json!(jitter_weight / (unbroken_sequence_count as f64)); summary["jitter_packets_consecutive"] = serde_json::json!(unbroken_sequence_count); } - + serde_json::json!({ "config": { "upload": upload_config, @@ -1080,109 +1257,133 @@ impl TestResults for UdpTestResults { "success": self.is_success(), }) } - - fn to_string(&self, bit:bool, omit_seconds:usize) -> String { + + fn to_string(&self, bit: bool, omit_seconds: usize) -> String { let stream_count = self.stream_results.len(); let mut stream_send_durations = vec![0.0; stream_count]; let mut stream_receive_durations = vec![0.0; stream_count]; - - let mut duration_send:f64 = 0.0; - - let mut bytes_sent:u64 = 0; - let mut packets_sent:u64 = 0; - - - let mut duration_receive:f64 = 0.0; - - let mut bytes_received:u64 = 0; - let mut packets_received:u64 = 0; - let mut packets_out_of_order:u64 = 0; - let mut packets_duplicated:u64 = 0; - + + let mut duration_send: f64 = 0.0; + + let mut bytes_sent: u64 = 0; + let mut packets_sent: u64 = 0; + + let mut duration_receive: f64 = 0.0; + + let mut bytes_received: u64 = 0; + let mut packets_received: u64 = 0; + let mut packets_out_of_order: u64 = 0; + let mut packets_duplicated: u64 = 0; + let mut jitter_calculated = false; - let mut unbroken_sequence_count:u64 = 0; - let mut jitter_weight:f64 = 0.0; - + let mut unbroken_sequence_count: u64 = 0; + let mut jitter_weight: f64 = 0.0; + let mut sends_blocked = false; - + for (stream_idx, stream) in self.stream_results.values().enumerate() { for (i, sr) in stream.send_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_send += sr.duration as f64; stream_send_durations[stream_idx] += sr.duration as f64; - + bytes_sent += sr.bytes_sent; packets_sent += sr.packets_sent; - + sends_blocked |= sr.sends_blocked > 0; } - + for (i, rr) in stream.receive_results.iter().enumerate() { if i < omit_seconds { continue; } - + duration_receive += rr.duration as f64; stream_receive_durations[stream_idx] += rr.duration as f64; - + bytes_received += rr.bytes_received; packets_received += rr.packets_received; packets_out_of_order += rr.packets_out_of_order; packets_duplicated += rr.packets_duplicated; - + if rr.jitter_seconds.is_some() { - jitter_weight += (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); + jitter_weight += + (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); unbroken_sequence_count += rr.unbroken_sequence; - + jitter_calculated = true; } } } stream_send_durations.sort_by(|a, b| a.partial_cmp(b).unwrap()); stream_receive_durations.sort_by(|a, b| a.partial_cmp(b).unwrap()); - - let send_duration_divisor; - if duration_send == 0.0 { //avoid zerodiv, which can happen if all streams fail - send_duration_divisor = 1.0; + + let send_duration_divisor = if duration_send == 0.0 { + //avoid zerodiv, which can happen if all streams fail + 1.0 } else { - send_duration_divisor = duration_send; - } + duration_send + }; let send_bytes_per_second = bytes_sent as f64 / send_duration_divisor; let send_throughput = match bit { - true => format!("megabits/second: {:.3}", send_bytes_per_second / (1_000_000.00 / 8.0)), - false => format!("megabytes/second: {:.3}", send_bytes_per_second / 1_000_000.00), + true => format!( + "megabits/second: {:.3}", + send_bytes_per_second / (1_000_000.00 / 8.0) + ), + false => format!( + "megabytes/second: {:.3}", + send_bytes_per_second / 1_000_000.00 + ), }; let total_send_throughput = match bit { - true => format!("megabits/second: {:.3}", (send_bytes_per_second / (1_000_000.00 / 8.0)) * stream_count as f64), - false => format!("megabytes/second: {:.3}", (send_bytes_per_second / 1_000_000.00) * stream_count as f64), + true => format!( + "megabits/second: {:.3}", + (send_bytes_per_second / (1_000_000.00 / 8.0)) * stream_count as f64 + ), + false => format!( + "megabytes/second: {:.3}", + (send_bytes_per_second / 1_000_000.00) * stream_count as f64 + ), }; - - let receive_duration_divisor; - if duration_receive == 0.0 { //avoid zerodiv, which can happen if all streams fail - receive_duration_divisor = 1.0; + + let receive_duration_divisor = if duration_receive == 0.0 { + //avoid zerodiv, which can happen if all streams fail + 1.0 } else { - receive_duration_divisor = duration_receive; - } + duration_receive + }; let receive_bytes_per_second = bytes_received as f64 / receive_duration_divisor; let receive_throughput = match bit { - true => format!("megabits/second: {:.3}", receive_bytes_per_second / (1_000_000.00 / 8.0)), - false => format!("megabytes/second: {:.3}", receive_bytes_per_second / 1_000_000.00), + true => format!( + "megabits/second: {:.3}", + receive_bytes_per_second / (1_000_000.00 / 8.0) + ), + false => format!( + "megabytes/second: {:.3}", + receive_bytes_per_second / 1_000_000.00 + ), }; let total_receive_throughput = match bit { - true => format!("megabits/second: {:.3}", (receive_bytes_per_second / (1_000_000.00 / 8.0)) * stream_count as f64), - false => format!("megabytes/second: {:.3}", (receive_bytes_per_second / 1_000_000.00) * stream_count as f64), + true => format!( + "megabits/second: {:.3}", + (receive_bytes_per_second / (1_000_000.00 / 8.0)) * stream_count as f64 + ), + false => format!( + "megabytes/second: {:.3}", + (receive_bytes_per_second / 1_000_000.00) * stream_count as f64 + ), }; - + let packets_lost = packets_sent - (packets_received - packets_duplicated); - let packets_sent_divisor; - if packets_sent == 0 { //avoid zerodiv, which can happen if all streams fail - packets_sent_divisor = 1.0 + let packets_sent_divisor = if packets_sent == 0 { + //avoid zerodiv, which can happen if all streams fail + 1.0 } else { - packets_sent_divisor = packets_sent as f64; - } + packets_sent as f64 + }; let mut output = format!("==========\n\ UDP send result over {:.2}s | streams: {}\n\ stream-average bytes per second: {:.3} | {}\n\ @@ -1197,22 +1398,25 @@ impl TestResults for UdpTestResults { send_bytes_per_second, send_throughput, bytes_sent, send_bytes_per_second * stream_count as f64, total_send_throughput, packets_sent, (packets_sent as f64 / send_duration_divisor) * stream_count as f64, - stream_receive_durations[stream_receive_durations.len() - 1], stream_count, receive_bytes_per_second, receive_throughput, bytes_received, receive_bytes_per_second * stream_count as f64, total_receive_throughput, packets_received, packets_lost, (packets_lost as f64 / packets_sent_divisor) * 100.0, packets_out_of_order, packets_duplicated, (packets_received as f64 / receive_duration_divisor) * stream_count as f64, ); if jitter_calculated { - output.push_str(&format!("\njitter: {:.6}s over {} consecutive packets", jitter_weight / (unbroken_sequence_count as f64), unbroken_sequence_count)); + output.push_str(&format!( + "\njitter: {:.6}s over {} consecutive packets", + jitter_weight / (unbroken_sequence_count as f64), + unbroken_sequence_count + )); } if sends_blocked { - output.push_str(&format!("\nthroughput throttled by buffer limitations")); + output.push_str("\nthroughput throttled by buffer limitations"); } if !self.is_success() { - output.push_str(&format!("\nTESTING DID NOT COMPLETE SUCCESSFULLY")); + output.push_str("\nTESTING DID NOT COMPLETE SUCCESSFULLY"); } - + output } } diff --git a/src/server.rs b/src/server.rs index 7256226..dd2ae56 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,19 +1,19 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ @@ -22,91 +22,116 @@ use std::error::Error; use std::io; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; use std::sync::atomic::{AtomicBool, AtomicU16, Ordering}; -use std::sync::{Arc, Mutex}; use std::sync::mpsc::channel; +use std::sync::{Arc, Mutex}; use std::thread; -use std::time::{Duration}; +use std::time::Duration; use clap::ArgMatches; use mio::net::{TcpListener, TcpStream}; -use mio::{Events, Ready, Poll, PollOpt, Token}; +use mio::{Events, Poll, PollOpt, Ready, Token}; use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; -use crate::protocol::messaging::{ - prepare_connect, prepare_connect_ready, -}; +use crate::protocol::messaging::{prepare_connect, prepare_connect_ready}; -use crate::stream::TestStream; use crate::stream::tcp; use crate::stream::udp; +use crate::stream::TestStream; -type BoxResult = Result>; +type BoxResult = Result>; -const POLL_TIMEOUT:Duration = Duration::from_millis(500); +const POLL_TIMEOUT: Duration = Duration::from_millis(500); /// when false, the system is shutting down -static ALIVE:AtomicBool = AtomicBool::new(true); +static ALIVE: AtomicBool = AtomicBool::new(true); /// a count of connected clients -static CLIENTS:AtomicU16 = AtomicU16::new(0); - +static CLIENTS: AtomicU16 = AtomicU16::new(0); fn handle_client( - stream:&mut TcpStream, - cpu_affinity_manager:Arc>, - tcp_port_pool:Arc>, - udp_port_pool:Arc>, + stream: &mut TcpStream, + cpu_affinity_manager: Arc>, + tcp_port_pool: Arc>, + udp_port_pool: Arc>, ) -> BoxResult<()> { let mut started = false; let peer_addr = stream.peer_addr()?; - - + //scaffolding to track and relay the streams and stream-results associated with this client - let mut parallel_streams:Vec>> = Vec::new(); + let mut parallel_streams: Vec>> = Vec::new(); let mut parallel_streams_joinhandles = Vec::new(); - let (results_tx, results_rx):(std::sync::mpsc::Sender>, std::sync::mpsc::Receiver>) = channel(); - + + let (results_tx, results_rx): ( + std::sync::mpsc::Sender, + std::sync::mpsc::Receiver, + ) = channel(); + //a closure used to pass results from stream-handlers to the client-communication stream let mut forwarding_send_stream = stream.try_clone()?; let mut results_handler = || -> BoxResult<()> { - loop { //drain all results every time this closer is invoked - match results_rx.try_recv() { //if there's something to forward, write it to the client - Ok(result) => { - send(&mut forwarding_send_stream, &result.to_json())?; - }, - Err(_) => break, //whether it's empty or disconnected, there's nothing to do - } + // drain all results every time this closer is invoked + while let Ok(result) = results_rx.try_recv() { + // if there's something to forward, write it to the client + send(&mut forwarding_send_stream, &result.to_json())?; } Ok(()) }; - - + //server operations are entirely driven by client-signalling, making this a (simple) state-machine while is_alive() { let payload = receive(stream, is_alive, &mut results_handler)?; match payload.get("kind") { Some(kind) => { match kind.as_str().unwrap() { - "configuration" => { //we either need to connect streams to the client or prepare to receive connections - if payload.get("role").unwrap_or(&serde_json::json!("download")).as_str().unwrap() == "download" { - log::info!("[{}] running in forward-mode: server will be receiving data", &peer_addr); - - let stream_count = payload.get("streams").unwrap_or(&serde_json::json!(1)).as_i64().unwrap(); + "configuration" => { + //we either need to connect streams to the client or prepare to receive connections + if payload + .get("role") + .unwrap_or(&serde_json::json!("download")) + .as_str() + .unwrap() + == "download" + { + log::info!( + "[{}] running in forward-mode: server will be receiving data", + &peer_addr + ); + + let stream_count = payload + .get("streams") + .unwrap_or(&serde_json::json!(1)) + .as_i64() + .unwrap(); //since we're receiving data, we're also responsible for letting the client know where to send it let mut stream_ports = Vec::with_capacity(stream_count as usize); - - if payload.get("family").unwrap_or(&serde_json::json!("tcp")).as_str().unwrap() == "udp" { - log::info!("[{}] preparing for UDP test with {} streams...", &peer_addr, stream_count); - + + if payload + .get("family") + .unwrap_or(&serde_json::json!("tcp")) + .as_str() + .unwrap() + == "udp" + { + log::info!( + "[{}] preparing for UDP test with {} streams...", + &peer_addr, + stream_count + ); + let mut c_udp_port_pool = udp_port_pool.lock().unwrap(); - + let test_definition = udp::UdpTestDefinition::new(&payload)?; for stream_idx in 0..stream_count { - log::debug!("[{}] preparing UDP-receiver for stream {}...", &peer_addr, stream_idx); + log::debug!( + "[{}] preparing UDP-receiver for stream {}...", + &peer_addr, + stream_idx + ); let test = udp::receiver::UdpReceiver::new( - test_definition.clone(), &(stream_idx as u8), + test_definition.clone(), + &(stream_idx as u8), &mut c_udp_port_pool, &peer_addr.ip(), &(payload["receive_buffer"].as_i64().unwrap() as usize), @@ -114,16 +139,26 @@ fn handle_client( stream_ports.push(test.get_port()?); parallel_streams.push(Arc::new(Mutex::new(test))); } - } else { //TCP - log::info!("[{}] preparing for TCP test with {} streams...", &peer_addr, stream_count); - + } else { + //TCP + log::info!( + "[{}] preparing for TCP test with {} streams...", + &peer_addr, + stream_count + ); + let mut c_tcp_port_pool = tcp_port_pool.lock().unwrap(); - + let test_definition = tcp::TcpTestDefinition::new(&payload)?; for stream_idx in 0..stream_count { - log::debug!("[{}] preparing TCP-receiver for stream {}...", &peer_addr, stream_idx); + log::debug!( + "[{}] preparing TCP-receiver for stream {}...", + &peer_addr, + stream_idx + ); let test = tcp::receiver::TcpReceiver::new( - test_definition.clone(), &(stream_idx as u8), + test_definition.clone(), + &(stream_idx as u8), &mut c_tcp_port_pool, &peer_addr.ip(), &(payload["receive_buffer"].as_i64().unwrap() as usize), @@ -132,38 +167,81 @@ fn handle_client( parallel_streams.push(Arc::new(Mutex::new(test))); } } - + //let the client know we're ready to receive the connection; stream-ports are in stream-index order send(stream, &prepare_connect(&stream_ports))?; - } else { //upload - log::info!("[{}] running in reverse-mode: server will be uploading data", &peer_addr); - - let stream_ports = payload.get("stream_ports").unwrap().as_array().unwrap(); - - if payload.get("family").unwrap_or(&serde_json::json!("tcp")).as_str().unwrap() == "udp" { - log::info!("[{}] preparing for UDP test with {} streams...", &peer_addr, stream_ports.len()); - + } else { + //upload + log::info!( + "[{}] running in reverse-mode: server will be uploading data", + &peer_addr + ); + + let stream_ports = + payload.get("stream_ports").unwrap().as_array().unwrap(); + + if payload + .get("family") + .unwrap_or(&serde_json::json!("tcp")) + .as_str() + .unwrap() + == "udp" + { + log::info!( + "[{}] preparing for UDP test with {} streams...", + &peer_addr, + stream_ports.len() + ); + let test_definition = udp::UdpTestDefinition::new(&payload)?; for (stream_idx, port) in stream_ports.iter().enumerate() { - log::debug!("[{}] preparing UDP-sender for stream {}...", &peer_addr, stream_idx); + log::debug!( + "[{}] preparing UDP-sender for stream {}...", + &peer_addr, + stream_idx + ); let test = udp::sender::UdpSender::new( - test_definition.clone(), &(stream_idx as u8), - &0, &peer_addr.ip(), &(port.as_i64().unwrap_or(0) as u16), - &(payload.get("duration").unwrap_or(&serde_json::json!(0.0)).as_f64().unwrap() as f32), - &(payload.get("send_interval").unwrap_or(&serde_json::json!(1.0)).as_f64().unwrap() as f32), + test_definition.clone(), + &(stream_idx as u8), + &0, + &peer_addr.ip(), + &(port.as_i64().unwrap_or(0) as u16), + &(payload + .get("duration") + .unwrap_or(&serde_json::json!(0.0)) + .as_f64() + .unwrap() + as f32), + &(payload + .get("send_interval") + .unwrap_or(&serde_json::json!(1.0)) + .as_f64() + .unwrap() + as f32), &(payload["send_buffer"].as_i64().unwrap() as usize), )?; parallel_streams.push(Arc::new(Mutex::new(test))); } - } else { //TCP - log::info!("[{}] preparing for TCP test with {} streams...", &peer_addr, stream_ports.len()); - + } else { + //TCP + log::info!( + "[{}] preparing for TCP test with {} streams...", + &peer_addr, + stream_ports.len() + ); + let test_definition = tcp::TcpTestDefinition::new(&payload)?; for (stream_idx, port) in stream_ports.iter().enumerate() { - log::debug!("[{}] preparing TCP-sender for stream {}...", &peer_addr, stream_idx); + log::debug!( + "[{}] preparing TCP-sender for stream {}...", + &peer_addr, + stream_idx + ); let test = tcp::sender::TcpSender::new( - test_definition.clone(), &(stream_idx as u8), - &peer_addr.ip(), &(port.as_i64().unwrap() as u16), + test_definition.clone(), + &(stream_idx as u8), + &peer_addr.ip(), + &(port.as_i64().unwrap() as u16), &(payload["duration"].as_f64().unwrap() as f32), &(payload["send_interval"].as_f64().unwrap() as f32), &(payload["send_buffer"].as_i64().unwrap() as usize), @@ -172,42 +250,59 @@ fn handle_client( parallel_streams.push(Arc::new(Mutex::new(test))); } } - + //let the client know we're ready to begin send(stream, &prepare_connect_ready())?; } - }, - "begin" => { //the client has indicated that testing can begin - if !started { //a simple guard to protect against reinitialisaion - for (stream_idx, parallel_stream) in parallel_streams.iter_mut().enumerate() { - log::info!("[{}] beginning execution of stream {}...", &peer_addr, stream_idx); - let c_ps = Arc::clone(¶llel_stream); + } + "begin" => { + //the client has indicated that testing can begin + if !started { + //a simple guard to protect against reinitialisaion + for (stream_idx, parallel_stream) in + parallel_streams.iter_mut().enumerate() + { + log::info!( + "[{}] beginning execution of stream {}...", + &peer_addr, + stream_idx + ); + let c_ps = Arc::clone(parallel_stream); let c_results_tx = results_tx.clone(); let c_cam = cpu_affinity_manager.clone(); let handle = thread::spawn(move || { - { //set CPU affinity, if enabled + { + //set CPU affinity, if enabled c_cam.lock().unwrap().set_affinity(); } loop { let mut test = c_ps.lock().unwrap(); - log::debug!("[{}] beginning test-interval for stream {}", &peer_addr, test.get_idx()); + log::debug!( + "[{}] beginning test-interval for stream {}", + &peer_addr, + test.get_idx() + ); match test.run_interval() { Some(interval_result) => match interval_result { Ok(ir) => match c_results_tx.send(ir) { Ok(_) => (), Err(e) => { log::error!("[{}] unable to process interval-result: {}", &peer_addr, e); - break - }, + break; + } }, Err(e) => { - log::error!("[{}] unable to process stream: {}", peer_addr, e); + log::error!( + "[{}] unable to process stream: {}", + peer_addr, + e + ); match c_results_tx.send(Box::new(crate::protocol::results::ServerFailedResult{stream_idx: test.get_idx()})) { Ok(_) => (), Err(e) => log::error!("[{}] unable to report interval-failed-result: {}", &peer_addr, e), } break; - }, + } }, None => { match c_results_tx.send(Box::new(crate::protocol::results::ServerDoneResult{stream_idx: test.get_idx()})) { @@ -215,43 +310,48 @@ fn handle_client( Err(e) => log::error!("[{}] unable to report interval-done-result: {}", &peer_addr, e), } break; - }, + } } } }); parallel_streams_joinhandles.push(handle); } started = true; - } else { //this can only happen in case of malicious action + } else { + //this can only happen in case of malicious action log::error!("[{}] duplicate begin-signal", &peer_addr); break; } - }, - "end" => { //the client has indicated that testing is done; stop cleanly + } + "end" => { + //the client has indicated that testing is done; stop cleanly log::info!("[{}] end of testing signaled", &peer_addr); break; - }, + } _ => { log::error!("[{}] invalid data", &peer_addr); break; - }, + } } - }, + } None => { log::error!("[{}] invalid data", &peer_addr); break; - }, + } } } - + log::debug!("[{}] stopping any still-in-progress streams", &peer_addr); for ps in parallel_streams.iter_mut() { let mut stream = match (*ps).lock() { Ok(guard) => guard, Err(poisoned) => { - log::error!("[{}] a stream-handler was poisoned; this indicates some sort of logic error", &peer_addr); + log::error!( + "[{}] a stream-handler was poisoned; this indicates some sort of logic error", + &peer_addr + ); poisoned.into_inner() - }, + } }; stream.stop(); } @@ -262,7 +362,7 @@ fn handle_client( Err(e) => log::error!("[{}] error in parallel stream: {:?}", &peer_addr, e), } } - + Ok(()) } @@ -273,7 +373,7 @@ struct ClientThreadMonitor { impl Drop for ClientThreadMonitor { fn drop(&mut self) { CLIENTS.fetch_sub(1, Ordering::Relaxed); - if thread::panicking(){ + if thread::panicking() { log::warn!("{} disconnecting due to panic", self.client_address); } else { log::info!("{} disconnected", self.client_address); @@ -281,7 +381,7 @@ impl Drop for ClientThreadMonitor { } } -pub fn serve(args:ArgMatches) -> BoxResult<()> { +pub fn serve(args: ArgMatches) -> BoxResult<()> { //config-parsing and pre-connection setup let tcp_port_pool = Arc::new(Mutex::new(tcp::receiver::TcpPortPool::new( args.value_of("tcp_port_pool").unwrap().to_string(), @@ -291,85 +391,95 @@ pub fn serve(args:ArgMatches) -> BoxResult<()> { args.value_of("udp_port_pool").unwrap().to_string(), args.value_of("udp6_port_pool").unwrap().to_string(), ))); - - let cpu_affinity_manager = Arc::new(Mutex::new(crate::utils::cpu_affinity::CpuAffinityManager::new(args.value_of("affinity").unwrap())?)); - - let client_limit:u16 = args.value_of("client_limit").unwrap().parse()?; + + let cpu_affinity_manager = Arc::new(Mutex::new( + crate::utils::cpu_affinity::CpuAffinityManager::new(args.value_of("affinity").unwrap())?, + )); + + let client_limit: u16 = args.value_of("client_limit").unwrap().parse()?; if client_limit > 0 { log::debug!("limiting service to {} concurrent clients", client_limit); } - + //start listening for connections - let port:u16 = args.value_of("port").unwrap().parse()?; - let mut listener:TcpListener; - if args.is_present("version6") { - listener = TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)).expect(format!("failed to bind TCP socket, port {}", port).as_str()); + let port: u16 = args.value_of("port").unwrap().parse()?; + let listener: TcpListener = if args.is_present("version6") { + TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)) + .unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)) } else { - listener = TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port)).expect(format!("failed to bind TCP socket, port {}", port).as_str()); - } + TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port)) + .unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)) + }; log::info!("server listening on {}", listener.local_addr()?); - + let mio_token = Token(0); let poll = Poll::new()?; - poll.register( - &mut listener, - mio_token, - Ready::readable(), - PollOpt::edge(), - )?; + poll.register(&listener, mio_token, Ready::readable(), PollOpt::edge())?; let mut events = Events::with_capacity(32); - + while is_alive() { poll.poll(&mut events, Some(POLL_TIMEOUT))?; for event in events.iter() { - match event.token() { - _ => loop { - match listener.accept() { - Ok((mut stream, address)) => { - log::info!("connection from {}", address); - - stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); - stream.set_keepalive(Some(KEEPALIVE_DURATION)).expect("unable to set TCP keepalive"); - - let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; - if client_limit > 0 && client_count > client_limit { - log::warn!("client-limit ({}) reached; disconnecting {}...", client_limit, address.to_string()); + event.token(); + loop { + match listener.accept() { + Ok((mut stream, address)) => { + log::info!("connection from {}", address); + + stream + .set_nodelay(true) + .expect("cannot disable Nagle's algorithm"); + stream + .set_keepalive(Some(KEEPALIVE_DURATION)) + .expect("unable to set TCP keepalive"); + + let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; + if client_limit > 0 && client_count > client_limit { + log::warn!( + "client-limit ({}) reached; disconnecting {}...", + client_limit, + address.to_string() + ); + stream.shutdown(Shutdown::Both).unwrap_or_default(); + CLIENTS.fetch_sub(1, Ordering::Relaxed); + } else { + let c_cam = cpu_affinity_manager.clone(); + let c_tcp_port_pool = tcp_port_pool.clone(); + let c_udp_port_pool = udp_port_pool.clone(); + let thread_builder = thread::Builder::new().name(address.to_string()); + thread_builder.spawn(move || { + //ensure the client is accounted-for even if the handler panics + let _client_thread_monitor = ClientThreadMonitor { + client_address: address.to_string(), + }; + + match handle_client( + &mut stream, + c_cam, + c_tcp_port_pool, + c_udp_port_pool, + ) { + Ok(_) => (), + Err(e) => log::error!("error in client-handler: {}", e), + } + + //in the event of panic, this will happen when the stream is dropped stream.shutdown(Shutdown::Both).unwrap_or_default(); - CLIENTS.fetch_sub(1, Ordering::Relaxed); - } else { - let c_cam = cpu_affinity_manager.clone(); - let c_tcp_port_pool = tcp_port_pool.clone(); - let c_udp_port_pool = udp_port_pool.clone(); - let thread_builder = thread::Builder::new() - .name(address.to_string().into()); - thread_builder.spawn(move || { - //ensure the client is accounted-for even if the handler panics - let _client_thread_monitor = ClientThreadMonitor{ - client_address: address.to_string(), - }; - - match handle_client(&mut stream, c_cam, c_tcp_port_pool, c_udp_port_pool) { - Ok(_) => (), - Err(e) => log::error!("error in client-handler: {}", e), - } - - //in the event of panic, this will happen when the stream is dropped - stream.shutdown(Shutdown::Both).unwrap_or_default(); - })?; - } - }, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { //nothing to do - break; - }, - Err(e) => { - return Err(Box::new(e)); - }, + })?; + } + } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + //nothing to do + break; } - }, + Err(e) => { + return Err(Box::new(e)); + } + } } } } - + //wait until all clients have been disconnected loop { let clients_count = CLIENTS.load(Ordering::Relaxed); diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 62e768a..30eab74 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -1,37 +1,37 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ - + pub mod tcp; pub mod udp; use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; -pub const INTERVAL:std::time::Duration = std::time::Duration::from_secs(1); +pub const INTERVAL: std::time::Duration = std::time::Duration::from_secs(1); /// tests in this system are self-contained iterator-likes, where run_interval() may be invoked multiple times /// until it returns None, indicating that the test has run its course; each invocation blocks for up to approximately /// INTERVAL while gathering data. pub trait TestStream { /// gather data; returns None when the test is over - fn run_interval(&mut self) -> Option>>; + fn run_interval(&mut self) -> Option>; /// return the port associated with the test-stream; this may vary over the test's lifetime fn get_port(&self) -> BoxResult; /// returns the index of the test, used to match client and server data @@ -40,7 +40,7 @@ pub trait TestStream { fn stop(&mut self); } -fn parse_port_spec(port_spec:String) -> Vec { +fn parse_port_spec(port_spec: String) -> Vec { let mut ports = Vec::::new(); if !port_spec.is_empty() { for range in port_spec.split(',') { @@ -48,7 +48,7 @@ fn parse_port_spec(port_spec:String) -> Vec { let mut range_spec = range.split('-'); let range_first = range_spec.next().unwrap().parse::().unwrap(); let range_last = range_spec.last().unwrap().parse::().unwrap(); - + for port in range_first..=range_last { ports.push(port); } @@ -56,9 +56,9 @@ fn parse_port_spec(port_spec:String) -> Vec { ports.push(range.parse::().unwrap()); } } - + ports.sort(); } - - return ports; + + ports } diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 721ed4a..308e427 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -1,19 +1,19 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ @@ -22,14 +22,16 @@ extern crate nix; use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; -use crate::protocol::results::{IntervalResult, TcpReceiveResult, TcpSendResult, get_unix_timestamp}; +use crate::protocol::results::{ + get_unix_timestamp, IntervalResult, TcpReceiveResult, TcpSendResult, +}; -use super::{INTERVAL, TestStream, parse_port_spec}; +use super::{parse_port_spec, TestStream, INTERVAL}; use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; -pub const TEST_HEADER_SIZE:usize = 16; +pub const TEST_HEADER_SIZE: usize = 16; #[derive(Clone)] pub struct TcpTestDefinition { @@ -41,42 +43,61 @@ pub struct TcpTestDefinition { pub length: usize, } impl TcpTestDefinition { - pub fn new(details:&serde_json::Value) -> super::BoxResult { + pub fn new(details: &serde_json::Value) -> super::BoxResult { let mut test_id_bytes = [0_u8; 16]; - for (i, v) in details.get("test_id").unwrap_or(&serde_json::json!([])).as_array().unwrap().iter().enumerate() { - if i >= 16 { //avoid out-of-bounds if given malicious data + for (i, v) in details + .get("test_id") + .unwrap_or(&serde_json::json!([])) + .as_array() + .unwrap() + .iter() + .enumerate() + { + if i >= 16 { + //avoid out-of-bounds if given malicious data break; } test_id_bytes[i] = v.as_i64().unwrap_or(0) as u8; } - - let length = details.get("length").unwrap_or(&serde_json::json!(TEST_HEADER_SIZE)).as_i64().unwrap() as usize; + + let length = details + .get("length") + .unwrap_or(&serde_json::json!(TEST_HEADER_SIZE)) + .as_i64() + .unwrap() as usize; if length < TEST_HEADER_SIZE { - return Err(Box::new(simple_error::simple_error!(std::format!("{} is too short of a length to satisfy testing requirements", length)))); + return Err(Box::new(simple_error::simple_error!(std::format!( + "{} is too short of a length to satisfy testing requirements", + length + )))); } - - Ok(TcpTestDefinition{ + + Ok(TcpTestDefinition { test_id: test_id_bytes, - bandwidth: details.get("bandwidth").unwrap_or(&serde_json::json!(0.0)).as_f64().unwrap() as u64, - length: length, + bandwidth: details + .get("bandwidth") + .unwrap_or(&serde_json::json!(0.0)) + .as_f64() + .unwrap() as u64, + length, }) } } - pub mod receiver { use std::io::Read; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::os::unix::io::AsRawFd; - use std::sync::{Mutex}; + use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; + use std::sync::Mutex; use std::time::{Duration, Instant}; - + use mio::net::{TcpListener, TcpStream}; - use mio::{Events, Ready, Poll, PollOpt, Token}; - - const POLL_TIMEOUT:Duration = Duration::from_millis(250); - const RECEIVE_TIMEOUT:Duration = Duration::from_secs(3); - + use mio::{Events, Poll, PollOpt, Ready, Token}; + + const POLL_TIMEOUT: Duration = Duration::from_millis(250); + const RECEIVE_TIMEOUT: Duration = Duration::from_secs(3); + pub struct TcpPortPool { pub ports_ip4: Vec, pub ports_ip6: Vec, @@ -86,286 +107,380 @@ pub mod receiver { lock_ip6: Mutex, } impl TcpPortPool { - pub fn new(port_spec:String, port_spec6:String) -> TcpPortPool { + pub fn new(port_spec: String, port_spec6: String) -> TcpPortPool { let ports = super::parse_port_spec(port_spec); if !ports.is_empty() { log::debug!("configured IPv4 TCP port pool: {:?}", ports); } else { log::debug!("using OS assignment for IPv4 TCP ports"); } - + let ports6 = super::parse_port_spec(port_spec6); if !ports.is_empty() { log::debug!("configured IPv6 TCP port pool: {:?}", ports6); } else { log::debug!("using OS assignment for IPv6 TCP ports"); } - + TcpPortPool { ports_ip4: ports, pos_ip4: 0, lock_ip4: Mutex::new(0), - + ports_ip6: ports6, pos_ip6: 0, lock_ip6: Mutex::new(0), } } - - pub fn bind(&mut self, peer_ip:&IpAddr) -> super::BoxResult { + + pub fn bind(&mut self, peer_ip: &IpAddr) -> super::BoxResult { match peer_ip { IpAddr::V6(_) => { if self.ports_ip6.is_empty() { - return Ok(TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)).expect(format!("failed to bind OS-assigned IPv6 TCP socket").as_str())); + return Ok(TcpListener::bind(&SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + 0, + )) + .expect("failed to bind OS-assigned IPv6 TCP socket")); } else { let _guard = self.lock_ip6.lock().unwrap(); - - for port_idx in (self.pos_ip6 + 1)..self.ports_ip6.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine - let listener_result = TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); - if listener_result.is_ok() { + + for port_idx in (self.pos_ip6 + 1)..self.ports_ip6.len() { + //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine + let listener_result = TcpListener::bind(&SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + self.ports_ip6[port_idx], + )); + if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; - return Ok(listener_result.unwrap()); + return Ok(listener_result); } else { - log::warn!("unable to bind IPv6 TCP port {}", self.ports_ip6[port_idx]); + log::warn!( + "unable to bind IPv6 TCP port {}", + self.ports_ip6[port_idx] + ); } } - for port_idx in 0..=self.pos_ip6 { //circle back to where the search started - let listener_result = TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); - if listener_result.is_ok() { + for port_idx in 0..=self.pos_ip6 { + //circle back to where the search started + let listener_result = TcpListener::bind(&SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + self.ports_ip6[port_idx], + )); + if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; - return Ok(listener_result.unwrap()); + return Ok(listener_result); } else { - log::warn!("unable to bind IPv6 TCP port {}", self.ports_ip6[port_idx]); + log::warn!( + "unable to bind IPv6 TCP port {}", + self.ports_ip6[port_idx] + ); } } } - return Err(Box::new(simple_error::simple_error!("unable to allocate IPv6 TCP port"))); - }, + Err(Box::new(simple_error::simple_error!( + "unable to allocate IPv6 TCP port" + ))) + } IpAddr::V4(_) => { if self.ports_ip4.is_empty() { - return Ok(TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)).expect(format!("failed to bind OS-assigned IPv4 TCP socket").as_str())); + return Ok(TcpListener::bind(&SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + 0, + )) + .expect("failed to bind OS-assigned IPv4 TCP socket")); } else { let _guard = self.lock_ip4.lock().unwrap(); - - for port_idx in (self.pos_ip4 + 1)..self.ports_ip4.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine - let listener_result = TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); - if listener_result.is_ok() { + + for port_idx in (self.pos_ip4 + 1)..self.ports_ip4.len() { + //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine + let listener_result = TcpListener::bind(&SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + self.ports_ip4[port_idx], + )); + if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; - return Ok(listener_result.unwrap()); + return Ok(listener_result); } else { - log::warn!("unable to bind IPv4 TCP port {}", self.ports_ip4[port_idx]); + log::warn!( + "unable to bind IPv4 TCP port {}", + self.ports_ip4[port_idx] + ); } } - for port_idx in 0..=self.pos_ip4 { //circle back to where the search started - let listener_result = TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); - if listener_result.is_ok() { + for port_idx in 0..=self.pos_ip4 { + //circle back to where the search started + let listener_result = TcpListener::bind(&SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + self.ports_ip4[port_idx], + )); + if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; - return Ok(listener_result.unwrap()); + return Ok(listener_result); } else { - log::warn!("unable to bind IPv4 TCP port {}", self.ports_ip4[port_idx]); + log::warn!( + "unable to bind IPv4 TCP port {}", + self.ports_ip4[port_idx] + ); } } } - return Err(Box::new(simple_error::simple_error!("unable to allocate IPv4 TCP port"))); - }, - }; + Err(Box::new(simple_error::simple_error!( + "unable to allocate IPv4 TCP port" + ))) + } + } } } - + pub struct TcpReceiver { - active: bool, + active: AtomicBool, test_definition: super::TcpTestDefinition, stream_idx: u8, - + listener: Option, stream: Option, mio_poll_token: Token, mio_poll: Poll, - + receive_buffer: usize, } impl TcpReceiver { - pub fn new(test_definition:super::TcpTestDefinition, stream_idx:&u8, port_pool:&mut TcpPortPool, peer_ip:&IpAddr, receive_buffer:&usize) -> super::BoxResult { + pub fn new( + test_definition: super::TcpTestDefinition, + stream_idx: &u8, + port_pool: &mut TcpPortPool, + peer_ip: &IpAddr, + receive_buffer: &usize, + ) -> super::BoxResult { log::debug!("binding TCP listener for stream {}...", stream_idx); - let listener:TcpListener = port_pool.bind(peer_ip).expect(format!("failed to bind TCP socket").as_str()); - log::debug!("bound TCP listener for stream {}: {}", stream_idx, listener.local_addr()?); - + let listener: TcpListener = port_pool.bind(peer_ip).expect("failed to bind TCP socket"); + log::debug!( + "bound TCP listener for stream {}: {}", + stream_idx, + listener.local_addr()? + ); + let mio_poll_token = Token(0); let mio_poll = Poll::new()?; - - Ok(TcpReceiver{ - active: true, - test_definition: test_definition, + + Ok(TcpReceiver { + active: AtomicBool::new(true), + test_definition, stream_idx: stream_idx.to_owned(), - + listener: Some(listener), stream: None, - mio_poll_token: mio_poll_token, - mio_poll: mio_poll, - + mio_poll_token, + mio_poll, + receive_buffer: receive_buffer.to_owned(), }) } - + fn process_connection(&mut self) -> super::BoxResult { - log::debug!("preparing to receive TCP stream {} connection...", self.stream_idx); - + log::debug!( + "preparing to receive TCP stream {} connection...", + self.stream_idx + ); + let listener = self.listener.as_mut().unwrap(); let mio_token = Token(0); let poll = Poll::new()?; - poll.register( - listener, - mio_token, - Ready::readable(), - PollOpt::edge(), - )?; + poll.register(listener, mio_token, Ready::readable(), PollOpt::edge())?; let mut events = Events::with_capacity(1); - + let start = Instant::now(); - - while self.active { + + while self.active.load(Relaxed) { if start.elapsed() >= RECEIVE_TIMEOUT { - return Err(Box::new(simple_error::simple_error!("TCP listening for stream {} timed out", self.stream_idx))); + return Err(Box::new(simple_error::simple_error!( + "TCP listening for stream {} timed out", + self.stream_idx + ))); } - + poll.poll(&mut events, Some(POLL_TIMEOUT))?; for event in events.iter() { - match event.token() { - _ => loop { - match listener.accept() { - Ok((stream, address)) => { - log::debug!("received TCP stream {} connection from {}", self.stream_idx, address); - - let mut verification_stream = stream.try_clone()?; - let mio_token2 = Token(0); - let poll2 = Poll::new()?; - poll2.register( - &verification_stream, - mio_token2, - Ready::readable(), - PollOpt::edge(), - )?; - - let mut buffer = [0_u8; 16]; - let mut events2 = Events::with_capacity(1); - poll2.poll(&mut events2, Some(RECEIVE_TIMEOUT))?; - for event2 in events2.iter() { - match event2.token() { - _ => match verification_stream.read(&mut buffer) { - Ok(_) => { - if buffer == self.test_definition.test_id { - log::debug!("validated TCP stream {} connection from {}", self.stream_idx, address); - if !cfg!(windows) { //NOTE: features unsupported on Windows - if self.receive_buffer != 0 { - log::debug!("setting receive-buffer to {}...", self.receive_buffer); - super::setsockopt(stream.as_raw_fd(), super::RcvBuf, &self.receive_buffer)?; - } - } - - self.mio_poll.register( - &stream, - self.mio_poll_token, - Ready::readable(), - PollOpt::edge(), + event.token(); + loop { + match listener.accept() { + Ok((stream, address)) => { + log::debug!( + "received TCP stream {} connection from {}", + self.stream_idx, + address + ); + + let mut verification_stream = stream.try_clone()?; + let mio_token2 = Token(0); + let poll2 = Poll::new()?; + poll2.register( + &verification_stream, + mio_token2, + Ready::readable(), + PollOpt::edge(), + )?; + + let mut buffer = [0_u8; 16]; + let mut events2 = Events::with_capacity(1); + poll2.poll(&mut events2, Some(RECEIVE_TIMEOUT))?; + for event2 in events2.iter() { + event2.token(); + match verification_stream.read(&mut buffer) { + Ok(_) => { + if buffer == self.test_definition.test_id { + log::debug!( + "validated TCP stream {} connection from {}", + self.stream_idx, + address + ); + if !cfg!(windows) { + //NOTE: features unsupported on Windows + if self.receive_buffer != 0 { + log::debug!( + "setting receive-buffer to {}...", + self.receive_buffer + ); + super::setsockopt( + stream.as_raw_fd(), + super::RcvBuf, + &self.receive_buffer, )?; - return Ok(stream); } - }, - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { //client didn't provide anything - break; - }, - Err(e) => { - return Err(Box::new(e)); - }, + } + + self.mio_poll.register( + &stream, + self.mio_poll_token, + Ready::readable(), + PollOpt::edge(), + )?; + return Ok(stream); } } + Err(ref e) + if e.kind() == std::io::ErrorKind::WouldBlock => + { + //client didn't provide anything + break; + } + Err(e) => { + return Err(Box::new(e)); + } } - log::warn!("could not validate TCP stream {} connection from {}", self.stream_idx, address); - }, - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { //nothing to do - break; - }, - Err(e) => { - return Err(Box::new(e)); - }, + } + log::warn!( + "could not validate TCP stream {} connection from {}", + self.stream_idx, + address + ); } - }, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + //nothing to do + break; + } + Err(e) => { + return Err(Box::new(e)); + } + } } } } - Err(Box::new(simple_error::simple_error!("did not receive a connection"))) + Err(Box::new(simple_error::simple_error!( + "did not receive a connection" + ))) } } impl super::TestStream for TcpReceiver { - fn run_interval(&mut self) -> Option>> { - let mut bytes_received:u64 = 0; - - if self.stream.is_none() { //if still in the setup phase, receive the sender + fn run_interval( + &mut self, + ) -> Option>> { + let mut bytes_received: u64 = 0; + + if self.stream.is_none() { + //if still in the setup phase, receive the sender match self.process_connection() { Ok(stream) => { self.stream = Some(stream); //NOTE: the connection process consumes the test-header; account for those bytes bytes_received += super::TEST_HEADER_SIZE as u64; - }, + } Err(e) => { return Some(Err(e)); - }, + } } self.listener = None; //drop it, closing the socket } let stream = self.stream.as_mut().unwrap(); - + let mut events = Events::with_capacity(1); //only watching one socket let mut buf = vec![0_u8; self.test_definition.length]; - + let peer_addr = match stream.peer_addr() { Ok(pa) => pa, Err(e) => return Some(Err(Box::new(e))), }; let start = Instant::now(); - - while self.active { + + while self.active.load(Relaxed) { if start.elapsed() >= RECEIVE_TIMEOUT { - return Some(Err(Box::new(simple_error::simple_error!("TCP reception for stream {} from {} timed out", self.stream_idx, peer_addr)))); + return Some(Err(Box::new(simple_error::simple_error!( + "TCP reception for stream {} from {} timed out", + self.stream_idx, + peer_addr + )))); } - - log::trace!("awaiting TCP stream {} from {}...", self.stream_idx, peer_addr); + + log::trace!( + "awaiting TCP stream {} from {}...", + self.stream_idx, + peer_addr + ); let poll_result = self.mio_poll.poll(&mut events, Some(POLL_TIMEOUT)); - if poll_result.is_err() { - return Some(Err(Box::new(poll_result.unwrap_err()))); + if let Err(err) = poll_result { + return Some(Err(Box::new(err))); } for event in events.iter() { if event.token() == self.mio_poll_token { loop { match stream.read(&mut buf) { Ok(packet_size) => { - log::trace!("received {} bytes in TCP stream {} from {}", packet_size, self.stream_idx, peer_addr); - if packet_size == 0 { //test's over - self.active = false; //HACK: can't call self.stop() because it's a double-borrow due to the unwrapped stream + log::trace!( + "received {} bytes in TCP stream {} from {}", + packet_size, + self.stream_idx, + peer_addr + ); + if packet_size == 0 { + //test's over + //HACK: can't call self.stop() because it's a double-borrow due to the unwrapped stream + self.active.store(false, Relaxed); break; } - + bytes_received += packet_size as u64; - + let elapsed_time = start.elapsed(); if elapsed_time >= super::INTERVAL { - return Some(Ok(Box::new(super::TcpReceiveResult{ + return Some(Ok(Box::new(super::TcpReceiveResult { timestamp: super::get_unix_timestamp(), - + stream_idx: self.stream_idx, - + duration: elapsed_time.as_secs_f32(), - - bytes_received: bytes_received, - }))) + + bytes_received, + }))); } - }, - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { //receive timeout + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + //receive timeout break; - }, + } Err(e) => { return Some(Err(Box::new(e))); - }, + } } } } else { @@ -374,119 +489,152 @@ pub mod receiver { } } if bytes_received > 0 { - Some(Ok(Box::new(super::TcpReceiveResult{ + Some(Ok(Box::new(super::TcpReceiveResult { timestamp: super::get_unix_timestamp(), - + stream_idx: self.stream_idx, - + duration: start.elapsed().as_secs_f32(), - - bytes_received: bytes_received, + + bytes_received, }))) } else { None } } - + fn get_port(&self) -> super::BoxResult { match &self.listener { Some(listener) => Ok(listener.local_addr()?.port()), None => match &self.stream { Some(stream) => Ok(stream.local_addr()?.port()), - None => Err(Box::new(simple_error::simple_error!("no port currently bound"))), - } + None => Err(Box::new(simple_error::simple_error!( + "no port currently bound" + ))), + }, } } - + fn get_idx(&self) -> u8 { self.stream_idx.to_owned() } - + fn stop(&mut self) { - self.active = false; + self.active.store(false, Relaxed); } } } - - pub mod sender { use std::io::Write; use std::net::{IpAddr, SocketAddr}; use std::os::unix::io::AsRawFd; use std::time::{Duration, Instant}; - + use mio::net::TcpStream; - - use std::thread::{sleep}; - - const CONNECT_TIMEOUT:Duration = Duration::from_secs(2); - const WRITE_TIMEOUT:Duration = Duration::from_millis(50); - const BUFFER_FULL_TIMEOUT:Duration = Duration::from_millis(1); - + + use std::thread::sleep; + + const CONNECT_TIMEOUT: Duration = Duration::from_secs(2); + const WRITE_TIMEOUT: Duration = Duration::from_millis(50); + const BUFFER_FULL_TIMEOUT: Duration = Duration::from_millis(1); + pub struct TcpSender { active: bool, test_definition: super::TcpTestDefinition, stream_idx: u8, - + socket_addr: SocketAddr, stream: Option, - + //the interval, in seconds, at which to send data send_interval: f32, - + remaining_duration: f32, staged_buffer: Vec, - - send_buffer:usize, - no_delay:bool, + + send_buffer: usize, + no_delay: bool, } impl TcpSender { - pub fn new(test_definition:super::TcpTestDefinition, stream_idx:&u8, receiver_ip:&IpAddr, receiver_port:&u16, send_duration:&f32, send_interval:&f32, send_buffer:&usize, no_delay:&bool) -> super::BoxResult { + #[allow(clippy::too_many_arguments)] + pub fn new( + test_definition: super::TcpTestDefinition, + stream_idx: &u8, + receiver_ip: &IpAddr, + receiver_port: &u16, + send_duration: &f32, + send_interval: &f32, + send_buffer: &usize, + no_delay: &bool, + ) -> super::BoxResult { let mut staged_buffer = vec![0_u8; test_definition.length]; - for i in super::TEST_HEADER_SIZE..(staged_buffer.len()) { //fill the packet with a fixed sequence - staged_buffer[i] = (i % 256) as u8; + for (i, staged_buffer_i) in staged_buffer + .iter_mut() + .enumerate() + .skip(super::TEST_HEADER_SIZE) + { + //fill the packet with a fixed sequence + *staged_buffer_i = (i % 256) as u8; } //embed the test ID staged_buffer[0..16].copy_from_slice(&test_definition.test_id); - - Ok(TcpSender{ + + Ok(TcpSender { active: true, - test_definition: test_definition, + test_definition, stream_idx: stream_idx.to_owned(), - + socket_addr: SocketAddr::new(*receiver_ip, *receiver_port), stream: None, - + send_interval: send_interval.to_owned(), - + remaining_duration: send_duration.to_owned(), - staged_buffer: staged_buffer, - + staged_buffer, + send_buffer: send_buffer.to_owned(), no_delay: no_delay.to_owned(), }) } - + fn process_connection(&mut self) -> super::BoxResult { log::debug!("preparing to connect TCP stream {}...", self.stream_idx); - - let raw_stream = match std::net::TcpStream::connect_timeout(&self.socket_addr, CONNECT_TIMEOUT) { - Ok(s) => s, - Err(e) => return Err(Box::new(simple_error::simple_error!("unable to connect stream {}: {}", self.stream_idx, e))), - }; + + let raw_stream = + match std::net::TcpStream::connect_timeout(&self.socket_addr, CONNECT_TIMEOUT) { + Ok(s) => s, + Err(e) => { + return Err(Box::new(simple_error::simple_error!( + "unable to connect stream {}: {}", + self.stream_idx, + e + ))) + } + }; raw_stream.set_write_timeout(Some(WRITE_TIMEOUT))?; let stream = match TcpStream::from_stream(raw_stream) { Ok(s) => s, - Err(e) => return Err(Box::new(simple_error::simple_error!("unable to prepare TCP stream {}: {}", self.stream_idx, e))), + Err(e) => { + return Err(Box::new(simple_error::simple_error!( + "unable to prepare TCP stream {}: {}", + self.stream_idx, + e + ))) + } }; - log::debug!("connected TCP stream {} to {}", self.stream_idx, stream.peer_addr()?); - + log::debug!( + "connected TCP stream {} to {}", + self.stream_idx, + stream.peer_addr()? + ); + if self.no_delay { log::debug!("setting no-delay..."); stream.set_nodelay(true)?; } - if !cfg!(windows) { //NOTE: features unsupported on Windows + if !cfg!(windows) { + //NOTE: features unsupported on Windows if self.send_buffer != 0 { log::debug!("setting send-buffer to {}...", self.send_buffer); super::setsockopt(stream.as_raw_fd(), super::SndBuf, &self.send_buffer)?; @@ -496,81 +644,99 @@ pub mod sender { } } impl super::TestStream for TcpSender { - fn run_interval(&mut self) -> Option>> { - if self.stream.is_none() { //if still in the setup phase, connect to the receiver + fn run_interval( + &mut self, + ) -> Option>> { + if self.stream.is_none() { + //if still in the setup phase, connect to the receiver match self.process_connection() { Ok(stream) => { self.stream = Some(stream); - }, + } Err(e) => { return Some(Err(e)); - }, + } } } let stream = self.stream.as_mut().unwrap(); - - + let interval_duration = Duration::from_secs_f32(self.send_interval); let mut interval_iteration = 0; - let bytes_to_send = ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; + let bytes_to_send = + ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; let mut bytes_to_send_remaining = bytes_to_send; - let bytes_to_send_per_interval_slice = ((bytes_to_send as f32) * self.send_interval) as i64; + let bytes_to_send_per_interval_slice = + ((bytes_to_send as f32) * self.send_interval) as i64; let mut bytes_to_send_per_interval_slice_remaining = bytes_to_send_per_interval_slice; - - let mut sends_blocked:u64 = 0; - let mut bytes_sent:u64 = 0; - + + let mut sends_blocked: u64 = 0; + let mut bytes_sent: u64 = 0; + let peer_addr = match stream.peer_addr() { Ok(pa) => pa, Err(e) => return Some(Err(Box::new(e))), }; let cycle_start = Instant::now(); - + while self.active && self.remaining_duration > 0.0 && bytes_to_send_remaining > 0 { - log::trace!("writing {} bytes in TCP stream {} to {}...", self.staged_buffer.len(), self.stream_idx, peer_addr); + log::trace!( + "writing {} bytes in TCP stream {} to {}...", + self.staged_buffer.len(), + self.stream_idx, + peer_addr + ); let packet_start = Instant::now(); - - match stream.write(&self.staged_buffer) { //it doesn't matter if the whole thing couldn't be written, since it's just garbage data + + match stream.write(&self.staged_buffer) { + //it doesn't matter if the whole thing couldn't be written, since it's just garbage data Ok(packet_size) => { - log::trace!("wrote {} bytes in TCP stream {} to {}", packet_size, self.stream_idx, peer_addr); - + log::trace!( + "wrote {} bytes in TCP stream {} to {}", + packet_size, + self.stream_idx, + peer_addr + ); + let bytes_written = packet_size as i64; bytes_sent += bytes_written as u64; bytes_to_send_remaining -= bytes_written; bytes_to_send_per_interval_slice_remaining -= bytes_written; - + let elapsed_time = cycle_start.elapsed(); if elapsed_time >= super::INTERVAL { self.remaining_duration -= packet_start.elapsed().as_secs_f32(); - - return Some(Ok(Box::new(super::TcpSendResult{ + + return Some(Ok(Box::new(super::TcpSendResult { timestamp: super::get_unix_timestamp(), - + stream_idx: self.stream_idx, - + duration: elapsed_time.as_secs_f32(), - - bytes_sent: bytes_sent, - sends_blocked: sends_blocked, - }))) + + bytes_sent, + sends_blocked, + }))); } - }, - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { //send-buffer is full + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + //send-buffer is full //nothing to do, but avoid burning CPU cycles sleep(BUFFER_FULL_TIMEOUT); sends_blocked += 1; - }, + } Err(e) => { return Some(Err(Box::new(e))); - }, + } } - - if bytes_to_send_remaining <= 0 { //interval's target is exhausted, so sleep until the end + + if bytes_to_send_remaining <= 0 { + //interval's target is exhausted, so sleep until the end let elapsed_time = cycle_start.elapsed(); if super::INTERVAL > elapsed_time { sleep(super::INTERVAL - elapsed_time); } - } else if bytes_to_send_per_interval_slice_remaining <= 0 { // interval subsection exhausted + } else if bytes_to_send_per_interval_slice_remaining <= 0 { + // interval subsection exhausted interval_iteration += 1; bytes_to_send_per_interval_slice_remaining = bytes_to_send_per_interval_slice; let elapsed_time = cycle_start.elapsed(); @@ -582,15 +748,15 @@ pub mod sender { self.remaining_duration -= packet_start.elapsed().as_secs_f32(); } if bytes_sent > 0 { - Some(Ok(Box::new(super::TcpSendResult{ + Some(Ok(Box::new(super::TcpSendResult { timestamp: super::get_unix_timestamp(), - + stream_idx: self.stream_idx, - + duration: cycle_start.elapsed().as_secs_f32(), - - bytes_sent: bytes_sent, - sends_blocked: sends_blocked, + + bytes_sent, + sends_blocked, }))) } else { //indicate that the test is over by dropping the stream @@ -598,18 +764,20 @@ pub mod sender { None } } - + fn get_port(&self) -> super::BoxResult { match &self.stream { Some(stream) => Ok(stream.local_addr()?.port()), - None => Err(Box::new(simple_error::simple_error!("no stream currently exists"))), + None => Err(Box::new(simple_error::simple_error!( + "no stream currently exists" + ))), } } - + fn get_idx(&self) -> u8 { self.stream_idx.to_owned() } - + fn stop(&mut self) { self.active = false; } diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 80404bd..63ebc0d 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -1,23 +1,23 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ - + extern crate log; extern crate nix; @@ -25,14 +25,16 @@ use std::error::Error; use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; -use crate::protocol::results::{IntervalResult, UdpReceiveResult, UdpSendResult, get_unix_timestamp}; +use crate::protocol::results::{ + get_unix_timestamp, IntervalResult, UdpReceiveResult, UdpSendResult, +}; -use super::{INTERVAL, TestStream, parse_port_spec}; +use super::{parse_port_spec, TestStream, INTERVAL}; -type BoxResult = Result>; +type BoxResult = Result>; -pub const TEST_HEADER_SIZE:u16 = 36; -const UDP_HEADER_SIZE:u16 = 8; +pub const TEST_HEADER_SIZE: u16 = 36; +const UDP_HEADER_SIZE: u16 = 8; #[derive(Clone)] pub struct UdpTestDefinition { @@ -44,24 +46,43 @@ pub struct UdpTestDefinition { pub length: u16, } impl UdpTestDefinition { - pub fn new(details:&serde_json::Value) -> super::BoxResult { + pub fn new(details: &serde_json::Value) -> super::BoxResult { let mut test_id_bytes = [0_u8; 16]; - for (i, v) in details.get("test_id").unwrap_or(&serde_json::json!([])).as_array().unwrap().iter().enumerate() { - if i >= 16 { //avoid out-of-bounds if given malicious data + for (i, v) in details + .get("test_id") + .unwrap_or(&serde_json::json!([])) + .as_array() + .unwrap() + .iter() + .enumerate() + { + if i >= 16 { + //avoid out-of-bounds if given malicious data break; } test_id_bytes[i] = v.as_i64().unwrap_or(0) as u8; } - - let length = details.get("length").unwrap_or(&serde_json::json!(TEST_HEADER_SIZE)).as_i64().unwrap() as u16; + + let length = details + .get("length") + .unwrap_or(&serde_json::json!(TEST_HEADER_SIZE)) + .as_i64() + .unwrap() as u16; if length < TEST_HEADER_SIZE { - return Err(Box::new(simple_error::simple_error!(std::format!("{} is too short of a length to satisfy testing requirements", length)))); + return Err(Box::new(simple_error::simple_error!(std::format!( + "{} is too short of a length to satisfy testing requirements", + length + )))); } - - Ok(UdpTestDefinition{ + + Ok(UdpTestDefinition { test_id: test_id_bytes, - bandwidth: details.get("bandwidth").unwrap_or(&serde_json::json!(0.0)).as_f64().unwrap() as u64, - length: length, + bandwidth: details + .get("bandwidth") + .unwrap_or(&serde_json::json!(0.0)) + .as_f64() + .unwrap() as u64, + length, }) } } @@ -70,240 +91,328 @@ pub mod receiver { use std::convert::TryInto; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::os::unix::io::AsRawFd; - use std::sync::{Mutex}; + use std::sync::Mutex; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; - - use chrono::{NaiveDateTime}; - + + use chrono::NaiveDateTime; + use std::net::UdpSocket; - - const READ_TIMEOUT:Duration = Duration::from_millis(50); - const RECEIVE_TIMEOUT:Duration = Duration::from_secs(3); - + + const READ_TIMEOUT: Duration = Duration::from_millis(50); + const RECEIVE_TIMEOUT: Duration = Duration::from_secs(3); + pub struct UdpPortPool { pub ports_ip4: Vec, pos_ip4: usize, lock_ip4: Mutex, - + pub ports_ip6: Vec, pos_ip6: usize, lock_ip6: Mutex, } impl UdpPortPool { - pub fn new(port_spec:String, port_spec6:String) -> UdpPortPool { + pub fn new(port_spec: String, port_spec6: String) -> UdpPortPool { let ports = super::parse_port_spec(port_spec); if !ports.is_empty() { log::debug!("configured IPv4 UDP port pool: {:?}", ports); } else { log::debug!("using OS assignment for IPv4 UDP ports"); } - + let ports6 = super::parse_port_spec(port_spec6); if !ports.is_empty() { log::debug!("configured IPv6 UDP port pool: {:?}", ports6); } else { log::debug!("using OS assignment for IPv6 UDP ports"); } - + UdpPortPool { ports_ip4: ports, pos_ip4: 0, lock_ip4: Mutex::new(0), - + ports_ip6: ports6, pos_ip6: 0, lock_ip6: Mutex::new(0), } } - - pub fn bind(&mut self, peer_ip:&IpAddr) -> super::BoxResult { + + pub fn bind(&mut self, peer_ip: &IpAddr) -> super::BoxResult { match peer_ip { IpAddr::V6(_) => { if self.ports_ip6.is_empty() { - return Ok(UdpSocket::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)).expect(format!("failed to bind OS-assigned IPv6 UDP socket").as_str())); + return Ok(UdpSocket::bind(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + 0, + )) + .expect("failed to bind OS-assigned IPv6 UDP socket")); } else { let _guard = self.lock_ip6.lock().unwrap(); - - for port_idx in (self.pos_ip6 + 1)..self.ports_ip6.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine - let listener_result = UdpSocket::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); - if listener_result.is_ok() { + + for port_idx in (self.pos_ip6 + 1)..self.ports_ip6.len() { + //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine + let listener_result = UdpSocket::bind(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + self.ports_ip6[port_idx], + )); + if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; - return Ok(listener_result.unwrap()); + return Ok(listener_result); } else { - log::warn!("unable to bind IPv6 UDP port {}", self.ports_ip6[port_idx]); + log::warn!( + "unable to bind IPv6 UDP port {}", + self.ports_ip6[port_idx] + ); } } - for port_idx in 0..=self.pos_ip6 { //circle back to where the search started - let listener_result = UdpSocket::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); - if listener_result.is_ok() { + for port_idx in 0..=self.pos_ip6 { + //circle back to where the search started + let listener_result = UdpSocket::bind(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + self.ports_ip6[port_idx], + )); + if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; - return Ok(listener_result.unwrap()); + return Ok(listener_result); } else { - log::warn!("unable to bind IPv6 UDP port {}", self.ports_ip6[port_idx]); + log::warn!( + "unable to bind IPv6 UDP port {}", + self.ports_ip6[port_idx] + ); } } } - return Err(Box::new(simple_error::simple_error!("unable to allocate IPv6 UDP port"))); - }, + Err(Box::new(simple_error::simple_error!( + "unable to allocate IPv6 UDP port" + ))) + } IpAddr::V4(_) => { if self.ports_ip4.is_empty() { - return Ok(UdpSocket::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)).expect(format!("failed to bind OS-assigned IPv4 UDP socket").as_str())); + return Ok(UdpSocket::bind(SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + 0, + )) + .expect("failed to bind OS-assigned IPv4 UDP socket")); } else { let _guard = self.lock_ip4.lock().unwrap(); - - for port_idx in (self.pos_ip4 + 1)..self.ports_ip4.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine - let listener_result = UdpSocket::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); - if listener_result.is_ok() { + + for port_idx in (self.pos_ip4 + 1)..self.ports_ip4.len() { + //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine + let listener_result = UdpSocket::bind(SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + self.ports_ip4[port_idx], + )); + if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; - return Ok(listener_result.unwrap()); + return Ok(listener_result); } else { - log::warn!("unable to bind IPv4 UDP port {}", self.ports_ip4[port_idx]); + log::warn!( + "unable to bind IPv4 UDP port {}", + self.ports_ip4[port_idx] + ); } } - for port_idx in 0..=self.pos_ip4 { //circle back to where the search started - let listener_result = UdpSocket::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); - if listener_result.is_ok() { + for port_idx in 0..=self.pos_ip4 { + //circle back to where the search started + let listener_result = UdpSocket::bind(SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + self.ports_ip4[port_idx], + )); + if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; - return Ok(listener_result.unwrap()); + return Ok(listener_result); } else { - log::warn!("unable to bind IPv4 UDP port {}", self.ports_ip4[port_idx]); + log::warn!( + "unable to bind IPv4 UDP port {}", + self.ports_ip4[port_idx] + ); } } } - return Err(Box::new(simple_error::simple_error!("unable to allocate IPv4 UDP port"))); - }, - }; + Err(Box::new(simple_error::simple_error!( + "unable to allocate IPv4 UDP port" + ))) + } + } } } - + struct UdpReceiverIntervalHistory { packets_received: u64, - + packets_lost: i64, packets_out_of_order: u64, packets_duplicated: u64, - + unbroken_sequence: u64, jitter_seconds: Option, longest_unbroken_sequence: u64, longest_jitter_seconds: Option, previous_time_delta_nanoseconds: i64, } - + pub struct UdpReceiver { active: bool, test_definition: super::UdpTestDefinition, stream_idx: u8, next_packet_id: u64, - + socket: UdpSocket, } impl UdpReceiver { - pub fn new(test_definition:super::UdpTestDefinition, stream_idx:&u8, port_pool:&mut UdpPortPool, peer_ip:&IpAddr, receive_buffer:&usize) -> super::BoxResult { + pub fn new( + test_definition: super::UdpTestDefinition, + stream_idx: &u8, + port_pool: &mut UdpPortPool, + peer_ip: &IpAddr, + receive_buffer: &usize, + ) -> super::BoxResult { log::debug!("binding UDP receive socket for stream {}...", stream_idx); - let socket:UdpSocket = port_pool.bind(peer_ip).expect(format!("failed to bind UDP socket").as_str()); + let socket: UdpSocket = port_pool.bind(peer_ip).expect("failed to bind UDP socket"); socket.set_read_timeout(Some(READ_TIMEOUT))?; - if !cfg!(windows) { //NOTE: features unsupported on Windows + if !cfg!(windows) { + //NOTE: features unsupported on Windows if *receive_buffer != 0 { log::debug!("setting receive-buffer to {}...", receive_buffer); super::setsockopt(socket.as_raw_fd(), super::RcvBuf, receive_buffer)?; } } - log::debug!("bound UDP receive socket for stream {}: {}", stream_idx, socket.local_addr()?); - - Ok(UdpReceiver{ + log::debug!( + "bound UDP receive socket for stream {}: {}", + stream_idx, + socket.local_addr()? + ); + + Ok(UdpReceiver { active: true, - test_definition: test_definition, + test_definition, stream_idx: stream_idx.to_owned(), - + next_packet_id: 0, - - socket: socket, + + socket, }) } - - fn process_packets_ordering(&mut self, packet_id:u64, mut history:&mut UdpReceiverIntervalHistory) -> bool { + + fn process_packets_ordering( + &mut self, + packet_id: u64, + history: &mut UdpReceiverIntervalHistory, + ) -> bool { /* the algorithm from iperf3 provides a pretty decent approximation * for tracking lost and out-of-order packets efficiently, so it's * been minimally reimplemented here, with corrections. - * + * * returns true if packet-ordering is as-expected */ - if packet_id == self.next_packet_id { //expected sequential-ordering case - self.next_packet_id += 1; - return true; - } else if packet_id > self.next_packet_id { //something was either lost or there's an ordering problem - let lost_packet_count = (packet_id - self.next_packet_id) as i64; - log::debug!("UDP reception for stream {} observed a gap of {} packets", self.stream_idx, lost_packet_count); - history.packets_lost += lost_packet_count; //assume everything in-between has been lost - self.next_packet_id = packet_id + 1; //anticipate that ordered receipt will resume - } else { //a packet with a previous ID was received; this is either a duplicate or an ordering issue - //CAUTION: this is where the approximation part of the algorithm comes into play - if history.packets_lost > 0 { //assume it's an ordering issue in the common case - history.packets_lost -= 1; - history.packets_out_of_order += 1; - } else { //the only other thing it could be is a duplicate; in practice, duplicates don't tend to show up alongside losses; non-zero is always bad, though - history.packets_duplicated += 1; + match packet_id.cmp(&self.next_packet_id) { + std::cmp::Ordering::Equal => { + //expected sequential-ordering case + self.next_packet_id += 1; + return true; + } + std::cmp::Ordering::Greater => { + //something was either lost or there's an ordering problem + let lost_packet_count = (packet_id - self.next_packet_id) as i64; + log::debug!( + "UDP reception for stream {} observed a gap of {} packets", + self.stream_idx, + lost_packet_count + ); + history.packets_lost += lost_packet_count; //assume everything in-between has been lost + self.next_packet_id = packet_id + 1; //anticipate that ordered receipt will resume + } + std::cmp::Ordering::Less => { + //a packet with a previous ID was received; this is either a duplicate or an ordering issue + //CAUTION: this is where the approximation part of the algorithm comes into play + if history.packets_lost > 0 { + //assume it's an ordering issue in the common case + history.packets_lost -= 1; + history.packets_out_of_order += 1; + } else { + //the only other thing it could be is a duplicate; in practice, duplicates don't tend to show up alongside losses; non-zero is always bad, though + history.packets_duplicated += 1; + } } } - return false; + false } - - fn process_jitter(&mut self, timestamp:&NaiveDateTime, history:&mut UdpReceiverIntervalHistory) { + + fn process_jitter( + &mut self, + timestamp: &NaiveDateTime, + history: &mut UdpReceiverIntervalHistory, + ) { /* this is a pretty straightforward implementation of RFC 1889, Appendix 8 * it works on an assumption that the timestamp delta between sender and receiver * will remain effectively constant during the testing window */ - let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before UNIX epoch"); - let current_timestamp = NaiveDateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos()); - + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time before UNIX epoch"); + let current_timestamp = + NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) + .unwrap(); + let time_delta = current_timestamp - *timestamp; - + let time_delta_nanoseconds = match time_delta.num_nanoseconds() { Some(ns) => ns, None => { - log::warn!("sender and receiver clocks are too out-of-sync to calculate jitter"); + log::warn!( + "sender and receiver clocks are too out-of-sync to calculate jitter" + ); return; - }, + } }; - - if history.unbroken_sequence > 1 { //do jitter calculation - let delta_seconds = (time_delta_nanoseconds - history.previous_time_delta_nanoseconds).abs() as f32 / 1_000_000_000.00; - - if history.unbroken_sequence > 2 { //normal jitter-calculation, per the RFC + + if history.unbroken_sequence > 1 { + //do jitter calculation + let delta_seconds = + (time_delta_nanoseconds - history.previous_time_delta_nanoseconds).abs() as f32 + / 1_000_000_000.00; + + if history.unbroken_sequence > 2 { + //normal jitter-calculation, per the RFC let mut jitter_seconds = history.jitter_seconds.unwrap(); //since we have a chain, this won't be None jitter_seconds += (delta_seconds - jitter_seconds) / 16.0; history.jitter_seconds = Some(jitter_seconds); - } else { //first observed transition; use this as the calibration baseline + } else { + //first observed transition; use this as the calibration baseline history.jitter_seconds = Some(delta_seconds); } } //update time-delta history.previous_time_delta_nanoseconds = time_delta_nanoseconds; } - - fn process_packet(&mut self, packet:&[u8], mut history:&mut UdpReceiverIntervalHistory) -> bool { + + fn process_packet( + &mut self, + packet: &[u8], + history: &mut UdpReceiverIntervalHistory, + ) -> bool { //the first sixteen bytes are the test's ID if packet[0..16] != self.test_definition.test_id { - return false + return false; } - + //the next eight bytes are the packet's ID, in big-endian order let packet_id = u64::from_be_bytes(packet[16..24].try_into().unwrap()); - + //except for the timestamp, nothing else in the packet actually matters - + history.packets_received += 1; - if self.process_packets_ordering(packet_id, &mut history) { + if self.process_packets_ordering(packet_id, history) { //the second eight are the number of seconds since the UNIX epoch, in big-endian order let origin_seconds = i64::from_be_bytes(packet[24..32].try_into().unwrap()); //and the following four are the number of nanoseconds since the UNIX epoch let origin_nanoseconds = u32::from_be_bytes(packet[32..36].try_into().unwrap()); - let source_timestamp = NaiveDateTime::from_timestamp(origin_seconds, origin_nanoseconds); - + let source_timestamp = + NaiveDateTime::from_timestamp_opt(origin_seconds, origin_nanoseconds).unwrap(); + history.unbroken_sequence += 1; - self.process_jitter(&source_timestamp, &mut history); - + self.process_jitter(&source_timestamp, history); + if history.unbroken_sequence > history.longest_unbroken_sequence { history.longest_unbroken_sequence = history.unbroken_sequence; history.longest_jitter_seconds = history.jitter_seconds; @@ -312,43 +421,52 @@ pub mod receiver { history.unbroken_sequence = 0; history.jitter_seconds = None; } - return true; + true } } impl super::TestStream for UdpReceiver { - fn run_interval(&mut self) -> Option>> { + fn run_interval( + &mut self, + ) -> Option>> { let mut buf = vec![0_u8; self.test_definition.length.into()]; - - let mut bytes_received:u64 = 0; - - let mut history = UdpReceiverIntervalHistory{ + + let mut bytes_received: u64 = 0; + + let mut history = UdpReceiverIntervalHistory { packets_received: 0, - + packets_lost: 0, packets_out_of_order: 0, packets_duplicated: 0, - + unbroken_sequence: 0, jitter_seconds: None, longest_unbroken_sequence: 0, longest_jitter_seconds: None, previous_time_delta_nanoseconds: 0, }; - + let start = Instant::now(); - + while self.active { if start.elapsed() >= RECEIVE_TIMEOUT { return Some(Err(Box::new(simple_error::simple_error!("UDP reception for stream {} timed out, likely because the end-signal was lost", self.stream_idx)))); } - + log::trace!("awaiting UDP packets on stream {}...", self.stream_idx); loop { match self.socket.recv_from(&mut buf) { Ok((packet_size, peer_addr)) => { - log::trace!("received {} bytes in UDP packet {} from {}", packet_size, self.stream_idx, peer_addr); - if packet_size == 16 { //possible end-of-test message - if &buf[0..16] == self.test_definition.test_id { //test's over + log::trace!( + "received {} bytes in UDP packet {} from {}", + packet_size, + self.stream_idx, + peer_addr + ); + if packet_size == 16 { + //possible end-of-test message + if buf[0..16] == self.test_definition.test_id { + //test's over self.stop(); break; } @@ -357,58 +475,64 @@ pub mod receiver { log::warn!("received malformed packet with size {} for UDP stream {} from {}", packet_size, self.stream_idx, peer_addr); continue; } - + if self.process_packet(&buf, &mut history) { //NOTE: duplicate packets increase this count; this is intentional because the stack still processed data - bytes_received += packet_size as u64 + super::UDP_HEADER_SIZE as u64; - + bytes_received += + packet_size as u64 + super::UDP_HEADER_SIZE as u64; + let elapsed_time = start.elapsed(); if elapsed_time >= super::INTERVAL { - return Some(Ok(Box::new(super::UdpReceiveResult{ + return Some(Ok(Box::new(super::UdpReceiveResult { timestamp: super::get_unix_timestamp(), - + stream_idx: self.stream_idx, - + duration: elapsed_time.as_secs_f32(), - - bytes_received: bytes_received, + + bytes_received, packets_received: history.packets_received, packets_lost: history.packets_lost, packets_out_of_order: history.packets_out_of_order, packets_duplicated: history.packets_duplicated, - + unbroken_sequence: history.longest_unbroken_sequence, jitter_seconds: history.longest_jitter_seconds, - }))) + }))); } } else { - log::warn!("received packet unrelated to UDP stream {} from {}", self.stream_idx, peer_addr); + log::warn!( + "received packet unrelated to UDP stream {} from {}", + self.stream_idx, + peer_addr + ); continue; } - }, - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { //receive timeout + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + //receive timeout break; - }, + } Err(e) => { return Some(Err(Box::new(e))); - }, + } } } } if bytes_received > 0 { - Some(Ok(Box::new(super::UdpReceiveResult{ + Some(Ok(Box::new(super::UdpReceiveResult { timestamp: super::get_unix_timestamp(), - + stream_idx: self.stream_idx, - + duration: start.elapsed().as_secs_f32(), - - bytes_received: bytes_received, + + bytes_received, packets_received: history.packets_received, packets_lost: history.packets_lost, packets_out_of_order: history.packets_out_of_order, packets_duplicated: history.packets_duplicated, - + unbroken_sequence: history.longest_unbroken_sequence, jitter_seconds: history.longest_jitter_seconds, }))) @@ -416,167 +540,204 @@ pub mod receiver { None } } - + fn get_port(&self) -> super::BoxResult { let socket_addr = self.socket.local_addr()?; Ok(socket_addr.port()) } - + fn get_idx(&self) -> u8 { self.stream_idx.to_owned() } - + fn stop(&mut self) { self.active = false; } } } - - pub mod sender { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::os::unix::io::AsRawFd; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; - + use std::net::UdpSocket; - - use std::thread::{sleep}; - - const WRITE_TIMEOUT:Duration = Duration::from_millis(50); - const BUFFER_FULL_TIMEOUT:Duration = Duration::from_millis(1); - + + use std::thread::sleep; + + const WRITE_TIMEOUT: Duration = Duration::from_millis(50); + const BUFFER_FULL_TIMEOUT: Duration = Duration::from_millis(1); + pub struct UdpSender { active: bool, test_definition: super::UdpTestDefinition, stream_idx: u8, - + socket: UdpSocket, - + //the interval, in seconds, at which to send data send_interval: f32, - + remaining_duration: f32, next_packet_id: u64, staged_packet: Vec, } impl UdpSender { - pub fn new(test_definition:super::UdpTestDefinition, stream_idx:&u8, port:&u16, receiver_ip:&IpAddr, receiver_port:&u16, send_duration:&f32, send_interval:&f32, send_buffer:&usize) -> super::BoxResult { + #[allow(clippy::too_many_arguments)] + pub fn new( + test_definition: super::UdpTestDefinition, + stream_idx: &u8, + port: &u16, + receiver_ip: &IpAddr, + receiver_port: &u16, + send_duration: &f32, + send_interval: &f32, + send_buffer: &usize, + ) -> super::BoxResult { log::debug!("preparing to connect UDP stream {}...", stream_idx); let socket_addr_receiver = SocketAddr::new(*receiver_ip, *receiver_port); let socket = match receiver_ip { - IpAddr::V6(_) => UdpSocket::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), *port)).expect(format!("failed to bind UDP socket, port {}", port).as_str()), - IpAddr::V4(_) => UdpSocket::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), *port)).expect(format!("failed to bind UDP socket, port {}", port).as_str()), + IpAddr::V6(_) => { + UdpSocket::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), *port)) + .unwrap_or_else(|_| panic!("failed to bind UDP socket, port {}", port)) + } + IpAddr::V4(_) => { + UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), *port)) + .unwrap_or_else(|_| panic!("failed to bind UDP socket, port {}", port)) + } }; socket.set_write_timeout(Some(WRITE_TIMEOUT))?; - if !cfg!(windows) { //NOTE: features unsupported on Windows + if !cfg!(windows) { + //NOTE: features unsupported on Windows if *send_buffer != 0 { log::debug!("setting send-buffer to {}...", send_buffer); super::setsockopt(socket.as_raw_fd(), super::SndBuf, send_buffer)?; } } socket.connect(socket_addr_receiver)?; - log::debug!("connected UDP stream {} to {}", stream_idx, socket_addr_receiver); - + log::debug!( + "connected UDP stream {} to {}", + stream_idx, + socket_addr_receiver + ); + let mut staged_packet = vec![0_u8; test_definition.length.into()]; - for i in super::TEST_HEADER_SIZE..(staged_packet.len() as u16) { //fill the packet with a fixed sequence + for i in super::TEST_HEADER_SIZE..(staged_packet.len() as u16) { + //fill the packet with a fixed sequence staged_packet[i as usize] = (i % 256) as u8; } //embed the test ID staged_packet[0..16].copy_from_slice(&test_definition.test_id); - - Ok(UdpSender{ + + Ok(UdpSender { active: true, - test_definition: test_definition, + test_definition, stream_idx: stream_idx.to_owned(), - - socket: socket, - + + socket, + send_interval: send_interval.to_owned(), - + remaining_duration: send_duration.to_owned(), next_packet_id: 0, - staged_packet: staged_packet, + staged_packet, }) } - + fn prepare_packet(&mut self) { - let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before UNIX epoch"); - + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time before UNIX epoch"); + //eight bytes after the test ID are the packet's ID, in big-endian order self.staged_packet[16..24].copy_from_slice(&self.next_packet_id.to_be_bytes()); - + //the next eight are the seconds part of the UNIX timestamp and the following four are the nanoseconds self.staged_packet[24..32].copy_from_slice(&now.as_secs().to_be_bytes()); self.staged_packet[32..36].copy_from_slice(&now.subsec_nanos().to_be_bytes()); } } impl super::TestStream for UdpSender { - fn run_interval(&mut self) -> Option>> { + fn run_interval( + &mut self, + ) -> Option>> { let interval_duration = Duration::from_secs_f32(self.send_interval); let mut interval_iteration = 0; - let bytes_to_send = ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; + let bytes_to_send = + ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; let mut bytes_to_send_remaining = bytes_to_send; - let bytes_to_send_per_interval_slice = ((bytes_to_send as f32) * self.send_interval) as i64; + let bytes_to_send_per_interval_slice = + ((bytes_to_send as f32) * self.send_interval) as i64; let mut bytes_to_send_per_interval_slice_remaining = bytes_to_send_per_interval_slice; - - let mut packets_sent:u64 = 0; - let mut sends_blocked:u64 = 0; - let mut bytes_sent:u64 = 0; - + + let mut packets_sent: u64 = 0; + let mut sends_blocked: u64 = 0; + let mut bytes_sent: u64 = 0; + let cycle_start = Instant::now(); - + while self.active && self.remaining_duration > 0.0 && bytes_to_send_remaining > 0 { - log::trace!("writing {} bytes in UDP stream {}...", self.staged_packet.len(), self.stream_idx); + log::trace!( + "writing {} bytes in UDP stream {}...", + self.staged_packet.len(), + self.stream_idx + ); let packet_start = Instant::now(); - + self.prepare_packet(); match self.socket.send(&self.staged_packet) { Ok(packet_size) => { - log::trace!("wrote {} bytes in UDP stream {}", packet_size, self.stream_idx); - + log::trace!( + "wrote {} bytes in UDP stream {}", + packet_size, + self.stream_idx + ); + packets_sent += 1; //reflect that a packet is in-flight self.next_packet_id += 1; - + let bytes_written = packet_size as i64 + super::UDP_HEADER_SIZE as i64; bytes_sent += bytes_written as u64; bytes_to_send_remaining -= bytes_written; bytes_to_send_per_interval_slice_remaining -= bytes_written; - + let elapsed_time = cycle_start.elapsed(); if elapsed_time >= super::INTERVAL { self.remaining_duration -= packet_start.elapsed().as_secs_f32(); - - return Some(Ok(Box::new(super::UdpSendResult{ + + return Some(Ok(Box::new(super::UdpSendResult { timestamp: super::get_unix_timestamp(), - + stream_idx: self.stream_idx, - + duration: elapsed_time.as_secs_f32(), - - bytes_sent: bytes_sent, - packets_sent: packets_sent, - sends_blocked: sends_blocked, - }))) + + bytes_sent, + packets_sent, + sends_blocked, + }))); } - }, - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { //send-buffer is full + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + //send-buffer is full //nothing to do, but avoid burning CPU cycles sleep(BUFFER_FULL_TIMEOUT); sends_blocked += 1; - }, + } Err(e) => { return Some(Err(Box::new(e))); - }, + } } - - if bytes_to_send_remaining <= 0 { //interval's target is exhausted, so sleep until the end + + if bytes_to_send_remaining <= 0 { + //interval's target is exhausted, so sleep until the end let elapsed_time = cycle_start.elapsed(); if super::INTERVAL > elapsed_time { sleep(super::INTERVAL - elapsed_time); } - } else if bytes_to_send_per_interval_slice_remaining <= 0 { // interval subsection exhausted + } else if bytes_to_send_per_interval_slice_remaining <= 0 { + // interval subsection exhausted interval_iteration += 1; bytes_to_send_per_interval_slice_remaining = bytes_to_send_per_interval_slice; let elapsed_time = cycle_start.elapsed(); @@ -588,48 +749,54 @@ pub mod sender { self.remaining_duration -= packet_start.elapsed().as_secs_f32(); } if bytes_sent > 0 { - Some(Ok(Box::new(super::UdpSendResult{ + Some(Ok(Box::new(super::UdpSendResult { timestamp: super::get_unix_timestamp(), - + stream_idx: self.stream_idx, - + duration: cycle_start.elapsed().as_secs_f32(), - - bytes_sent: bytes_sent, - packets_sent: packets_sent, - sends_blocked: sends_blocked, + + bytes_sent, + packets_sent, + sends_blocked, }))) } else { //indicate that the test is over by sending the test ID by itself let mut remaining_announcements = 5; - while remaining_announcements > 0 { //do it a few times in case of loss + while remaining_announcements > 0 { + //do it a few times in case of loss match self.socket.send(&self.staged_packet[0..16]) { Ok(packet_size) => { - log::trace!("wrote {} bytes of test-end signal in UDP stream {}", packet_size, self.stream_idx); + log::trace!( + "wrote {} bytes of test-end signal in UDP stream {}", + packet_size, + self.stream_idx + ); remaining_announcements -= 1; - }, - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { //send-buffer is full + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + //send-buffer is full //wait to try again and avoid burning CPU cycles sleep(BUFFER_FULL_TIMEOUT); - }, + } Err(e) => { return Some(Err(Box::new(e))); - }, + } } } None } } - + fn get_port(&self) -> super::BoxResult { let socket_addr = self.socket.local_addr()?; Ok(socket_addr.port()) } - + fn get_idx(&self) -> u8 { self.stream_idx.to_owned() } - + fn stop(&mut self) { self.active = false; } diff --git a/src/utils/cpu_affinity.rs b/src/utils/cpu_affinity.rs index 063d488..4f29935 100644 --- a/src/utils/cpu_affinity.rs +++ b/src/utils/cpu_affinity.rs @@ -1,47 +1,50 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ - + extern crate core_affinity; use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; pub struct CpuAffinityManager { enabled_cores: Vec, last_core_pointer: usize, } impl CpuAffinityManager { - pub fn new(cores:&str) -> BoxResult { + pub fn new(cores: &str) -> BoxResult { let core_ids = core_affinity::get_core_ids().unwrap_or_default(); - log::debug!("enumerated CPU cores: {:?}", core_ids.iter().map(|c| c.id).collect::>()); - + log::debug!( + "enumerated CPU cores: {:?}", + core_ids.iter().map(|c| c.id).collect::>() + ); + let mut enabled_cores = Vec::new(); for cid in cores.split(',') { if cid.is_empty() { continue; } - let cid_usize:usize = cid.parse()?; + let cid_usize: usize = cid.parse()?; let mut cid_valid = false; for core_id in &core_ids { if core_id.id == cid_usize { - enabled_cores.push(core_id.clone()); + enabled_cores.push(*core_id); cid_valid = true; } } @@ -49,20 +52,20 @@ impl CpuAffinityManager { log::warn!("unrecognised CPU core: {}", cid_usize); } } - if enabled_cores.len() > 0 { + if !enabled_cores.is_empty() { log::debug!("selecting from CPU cores {:?}", enabled_cores); } else { log::debug!("not applying CPU core affinity"); } - - Ok(CpuAffinityManager{ - enabled_cores: enabled_cores, + + Ok(CpuAffinityManager { + enabled_cores, last_core_pointer: 0, }) } - + pub fn set_affinity(&mut self) { - if self.enabled_cores.len() > 0 { + if !self.enabled_cores.is_empty() { let core_id = self.enabled_cores[self.last_core_pointer]; log::debug!("setting CPU affinity to {}", core_id.id); core_affinity::set_for_current(core_id); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c81d644..d4505fd 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,21 +1,21 @@ /* * Copyright (C) 2021 Evtech Solutions, Ltd., dba 3D-P * Copyright (C) 2021 Neil Tallim - * + * * This file is part of rperf. - * + * * rperf is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * rperf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with rperf. If not, see . */ - + pub mod cpu_affinity; From 6fbd73689650c532c9aa545a33f5c3ea05018325 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 17 Dec 2023 19:26:58 +0800 Subject: [PATCH 02/18] bind argument --- src/main.rs | 11 +++++++++++ src/server.rs | 21 ++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index d8c1122..9c95041 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,17 @@ fn main() { .multiple(true) .default_value("") ) + .arg( + Arg::with_name("bind") + .help("bind to the interface associated with the address ") + .takes_value(true) + .long("bind") + .short("B") + .value_name("host") + .required(false) + .multiple(true) + .default_value("0.0.0.0") + ) .arg( Arg::with_name("debug") .help("emit debug-level logging on stderr; default is info and above") diff --git a/src/server.rs b/src/server.rs index dd2ae56..b77549e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -401,15 +401,22 @@ pub fn serve(args: ArgMatches) -> BoxResult<()> { log::debug!("limiting service to {} concurrent clients", client_limit); } - //start listening for connections - let port: u16 = args.value_of("port").unwrap().parse()?; - let listener: TcpListener = if args.is_present("version6") { - TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)) - .unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)) + let (unspec_str, unspec) = if args.is_present("version6") { + ("::", IpAddr::V6(Ipv6Addr::UNSPECIFIED)) } else { - TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port)) - .unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)) + ("0.0.0.0", IpAddr::V4(Ipv4Addr::UNSPECIFIED)) }; + + let addr = args + .value_of("bind") + .unwrap_or(unspec_str) + .parse::() + .unwrap_or(unspec); + + //start listening for connections + let port: u16 = args.value_of("port").unwrap().parse()?; + let listener: TcpListener = TcpListener::bind(&SocketAddr::new(addr, port)) + .unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)); log::info!("server listening on {}", listener.local_addr()?); let mio_token = Token(0); From 7721e0f3e4cd286d14127a7265bdb53d8d8eb499 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 18 Dec 2023 01:16:05 +0800 Subject: [PATCH 03/18] upgrade to clap 4.4.x --- Cargo.toml | 16 +-- src/args.rs | 154 ++++++++++++++++++++++++ src/client.rs | 45 +++---- src/main.rs | 239 +++----------------------------------- src/protocol/messaging.rs | 32 ++--- src/server.rs | 35 ++---- 6 files changed, 222 insertions(+), 299 deletions(-) create mode 100644 src/args.rs diff --git a/Cargo.toml b/Cargo.toml index 9a096fb..3506e50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,17 +12,17 @@ readme = "README.md" [dependencies] chrono = "0.4" -clap = "~2.33.3" -core_affinity = "0.5" -ctrlc = "3.1" -env_logger = "0.8" -log = {version = "0.4", features = ["std"]} +clap = { version = "4.4", features = ["derive", "wrap_help"] } +core_affinity = "0.8" +ctrlc2 = "3.5" +env_logger = "0.10" +log = { version = "0.4", features = ["std"] } mio = "0.6" nix = "0.20" -serde = {version = "1.0", features = ["derive"]} +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -simple-error = "0.2" -uuid = {version = "0.8", features = ["v4"]} +simple-error = "0.3" +uuid = { version = "1.6", features = ["v4"] } #configuration for cargo-deb #install with "cargo install cargo-deb" diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..e8812df --- /dev/null +++ b/src/args.rs @@ -0,0 +1,154 @@ +/// rperf, validates network throughput capacity and reliability, +/// https://github.com/opensource-3d-p/rperf +#[derive(clap::Parser, Debug, Clone)] +#[command(author, version, about, long_about = None)] +pub struct Args { + /// the port used for client-server interactions + #[arg(short, long, value_name = "number", default_value_t = 5199)] + pub port: u16, + + /// specify logical CPUs, delimited by commas, across which to round-robin affinity; + /// not supported on all systems + #[arg(short = 'A', long, value_name = "numbers", default_value = "")] + pub affinity: String, + + /// bind to the interface associated with the address + #[arg( + short = 'B', + long, + conflicts_with = "client", + default_value_if("version6", "true", Some("::")), + default_value = "0.0.0.0", + value_name = "host" + )] + pub bind: std::net::IpAddr, + + /// emit debug-level logging on stderr; default is info and above + #[arg(short, long)] + pub debug: bool, + + /// run in server mode + #[arg(short, long, conflicts_with = "client")] + pub server: bool, + + /// enable IPv6 on the server (on most hosts, this will allow both IPv4 and IPv6, + /// but it might limit to just IPv6 on some) + #[arg(short = '6', long)] + pub version6: bool, + + /// limit the number of concurrent clients that can be processed by a server; + /// any over this count will be immediately disconnected + #[arg(long, value_name = "number", default_value = "0")] + pub client_limit: usize, + + /// run in client mode; value is the server's address + #[arg(short, long, value_name = "host", conflicts_with = "server")] + pub client: Option, + + /// run in reverse-mode (server sends, client receives) + #[arg(short = 'R', long)] + pub reverse: bool, + + /// the format in which to deplay information (json, megabit/sec, megabyte/sec) + #[arg( + short, + long, + value_enum, + value_name = "format", + default_value = "megabit" + )] + pub format: Format, + + /// use UDP rather than TCP + #[arg(short, long)] + pub udp: bool, + + /// target bandwidth in bytes/sec; this value is applied to each stream, + /// with a default target of 1 megabit/second for all protocols (note: megabit, not mebibit); + /// the suffixes kKmMgG can also be used for xbit and xbyte, respectively + #[arg(short, long, default_value = "125000", value_name = "bytes/sec")] + pub bandwidth: String, + + /// the time in seconds for which to transmit + #[arg(short, long, default_value = "10.0", value_name = "seconds")] + pub time: f64, + + /// the interval at which to send batches of data, in seconds, between [0.0 and 1.0); + /// this is used to evenly spread packets out over time + #[arg(long, default_value = "0.05", value_name = "seconds")] + pub send_interval: f64, + + /// length of the buffer to exchange; for TCP, this defaults to 32 kibibytes; for UDP, it's 1024 bytes + #[arg( + short, + long, + default_value = "32768", + default_value_if("udp", "true", Some("1024")), + value_name = "bytes" + )] + pub length: usize, + + /// send buffer, in bytes (only supported on some platforms; + /// if set too small, a 'resource unavailable' error may occur; + /// affects TCP window-size) + #[arg(long, default_value = "0", value_name = "bytes")] + pub send_buffer: usize, + + /// receive buffer, in bytes (only supported on some platforms; + /// if set too small, a 'resource unavailable' error may occur; affects TCP window-size) + #[arg(long, default_value = "0", value_name = "bytes")] + pub receive_buffer: usize, + + /// the number of parallel data-streams to use + #[arg(short = 'P', long, value_name = "number", default_value = "1")] + pub parallel: usize, + + /// omit a number of seconds from the start of calculations, + /// primarily to avoid including TCP ramp-up in averages; + /// using this option may result in disagreement between bytes sent and received, + /// since data can be in-flight across time-boundaries + #[arg(short, long, default_value = "0", value_name = "seconds")] + pub omit: usize, + + /// use no-delay mode for TCP tests, disabling Nagle's Algorithm + #[arg(short = 'N', long)] + pub no_delay: bool, + + /// an optional pool of IPv4 TCP ports over which data will be accepted; + /// if omitted, any OS-assignable port is used; format: 1-10,19,21 + #[arg(long, value_name = "ports", default_value = "")] + pub tcp_port_pool: String, + + /// an optional pool of IPv6 TCP ports over which data will be accepted; + /// if omitted, any OS-assignable port is used; format: 1-10,19,21 + #[arg(long, value_name = "ports", default_value = "")] + pub tcp6_port_pool: String, + + /// an optional pool of IPv4 UDP ports over which data will be accepted; + /// if omitted, any OS-assignable port is used; format: 1-10,19,21 + #[arg(long, value_name = "ports", default_value = "")] + pub udp_port_pool: String, + + /// an optional pool of IPv6 UDP ports over which data will be accepted; + /// if omitted, any OS-assignable port is used; format: 1-10,19,21 + #[arg(long, value_name = "ports", default_value = "")] + pub udp6_port_pool: String, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, Default)] +pub enum Format { + #[default] + Json, + Megabit, + Megabyte, +} + +impl std::fmt::Display for Format { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Format::Json => write!(f, "json"), + Format::Megabit => write!(f, "megabit/sec"), + Format::Megabyte => write!(f, "megabyte/sec"), + } + } +} diff --git a/src/client.rs b/src/client.rs index 6afee76..ef3f536 100644 --- a/src/client.rs +++ b/src/client.rs @@ -25,10 +25,9 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use clap::ArgMatches; - use mio::net::TcpStream; +use crate::args; use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; use crate::protocol::messaging::{ @@ -115,56 +114,48 @@ fn prepare_test_results(is_udp: bool, stream_count: u8) -> Mutex BoxResult<()> { +pub fn execute(args: &args::Args) -> BoxResult<()> { let mut complete = false; //config-parsing and pre-connection setup let mut tcp_port_pool = tcp::receiver::TcpPortPool::new( - args.value_of("tcp_port_pool").unwrap().to_string(), - args.value_of("tcp6_port_pool").unwrap().to_string(), + args.tcp_port_pool.to_string(), + args.tcp6_port_pool.to_string(), ); let mut udp_port_pool = udp::receiver::UdpPortPool::new( - args.value_of("udp_port_pool").unwrap().to_string(), - args.value_of("udp6_port_pool").unwrap().to_string(), + args.udp_port_pool.to_string(), + args.udp6_port_pool.to_string(), ); let cpu_affinity_manager = Arc::new(Mutex::new( - crate::utils::cpu_affinity::CpuAffinityManager::new(args.value_of("affinity").unwrap())?, + crate::utils::cpu_affinity::CpuAffinityManager::new(&args.affinity)?, )); let display_json: bool; let display_bit: bool; - match args.value_of("format").unwrap() { - "json" => { + match args.format { + args::Format::Json => { display_json = true; display_bit = false; } - "megabit" => { + args::Format::Megabit => { display_json = false; display_bit = true; } - "megabyte" => { + args::Format::Megabyte => { display_json = false; display_bit = false; } - _ => { - log::error!("unsupported display-mode; defaulting to JSON"); - display_json = true; - display_bit = false; - } } - let is_udp = args.is_present("udp"); + let is_udp = args.udp; let test_id = uuid::Uuid::new_v4(); - let mut upload_config = prepare_upload_configuration(&args, test_id.as_bytes())?; - let mut download_config = prepare_download_configuration(&args, test_id.as_bytes())?; + let mut upload_config = prepare_upload_configuration(args, test_id.as_bytes())?; + let mut download_config = prepare_download_configuration(args, test_id.as_bytes())?; //connect to the server - let mut stream = connect_to_server( - args.value_of("client").unwrap(), - &(args.value_of("port").unwrap().parse()?), - )?; + let mut stream = connect_to_server(&args.client.unwrap().to_string(), &args.port)?; let server_addr = stream.peer_addr()?; //scaffolding to track and relay the streams and stream-results associated with this test @@ -224,7 +215,7 @@ pub fn execute(args: ArgMatches) -> BoxResult<()> { }; //depending on whether this is a forward- or reverse-test, the order of configuring test-streams will differ - if args.is_present("reverse") { + if args.reverse { log::debug!("running in reverse-mode: server will be uploading data"); //when we're receiving data, we're also responsible for letting the server know where to send it @@ -559,7 +550,7 @@ pub fn execute(args: ArgMatches) -> BoxResult<()> { } log::debug!("displaying test results"); - let omit_seconds: usize = args.value_of("omit").unwrap().parse()?; + let omit_seconds: usize = args.omit; { let tr = test_results.lock().unwrap(); if display_json { @@ -576,7 +567,7 @@ pub fn execute(args: ArgMatches) -> BoxResult<()> { IpAddr::V4(_) => 4, IpAddr::V6(_) => 6, }, - "reverse": args.is_present("reverse"), + "reverse": args.reverse, }) ) ); diff --git a/src/main.rs b/src/main.rs index 9c95041..e3fdb22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,11 +18,7 @@ * along with rperf. If not, see . */ -extern crate env_logger; -extern crate log; - -use clap::{App, Arg}; - +mod args; mod client; mod protocol; mod server; @@ -30,227 +26,18 @@ mod stream; mod utils; fn main() { - let args = App::new("rperf") - .about(clap::crate_description!()) - .author("https://github.com/opensource-3d-p/rperf") - .name(clap::crate_name!()) - .version(clap::crate_version!()) - .arg( - Arg::with_name("port") - .help("the port used for client-server interactions") - .takes_value(true) - .long("port") - .short("p") - .required(false) - .default_value("5199") - ) - .arg( - Arg::with_name("affinity") - .help("specify logical CPUs, delimited by commas, across which to round-robin affinity; not supported on all systems") - .takes_value(true) - .long("affinity") - .short("A") - .required(false) - .multiple(true) - .default_value("") - ) - .arg( - Arg::with_name("bind") - .help("bind to the interface associated with the address ") - .takes_value(true) - .long("bind") - .short("B") - .value_name("host") - .required(false) - .multiple(true) - .default_value("0.0.0.0") - ) - .arg( - Arg::with_name("debug") - .help("emit debug-level logging on stderr; default is info and above") - .takes_value(false) - .long("debug") - .short("d") - .required(false) - ) - .arg( - Arg::with_name("server") - .help("run in server mode") - .takes_value(false) - .long("server") - .short("s") - .required(false) - ) - .arg( - Arg::with_name("version6") - .help("enable IPv6 on the server (on most hosts, this will allow both IPv4 and IPv6, but it might limit to just IPv6 on some)") - .takes_value(false) - .long("version6") - .short("6") - .required(false) - ) - .arg( - Arg::with_name("client_limit") - .help("limit the number of concurrent clients that can be processed by a server; any over this count will be immediately disconnected") - .takes_value(true) - .long("client-limit") - .required(false) - .default_value("0") - ) - .arg( - Arg::with_name("client") - .help("run in client mode; value is the server's address") - .takes_value(true) - .long("client") - .short("c") - .required(false) - ) - .arg( - Arg::with_name("reverse") - .help("run in reverse-mode (server sends, client receives)") - .takes_value(false) - .long("reverse") - .short("R") - .required(false) - ) - .arg( - Arg::with_name("format") - .help("the format in which to deplay information (json, megabit/sec, megabyte/sec)") - .takes_value(true) - .long("format") - .short("f") - .required(false) - .default_value("megabit") - .possible_values(&["json", "megabit", "megabyte"]) - ) - .arg( - Arg::with_name("udp") - .help("use UDP rather than TCP") - .takes_value(false) - .long("udp") - .short("u") - .required(false) - ) - .arg( - Arg::with_name("bandwidth") - .help("target bandwidth in bytes/sec; this value is applied to each stream, with a default target of 1 megabit/second for all protocols (note: megabit, not mebibit); the suffixes kKmMgG can also be used for xbit and xbyte, respectively") - .takes_value(true) - .long("bandwidth") - .short("b") - .required(false) - .default_value("125000") - ) - .arg( - Arg::with_name("time") - .help("the time in seconds for which to transmit") - .takes_value(true) - .long("time") - .short("t") - .required(false) - .default_value("10.0") - ) - .arg( - Arg::with_name("send_interval") - .help("the interval at which to send batches of data, in seconds, between [0.0 and 1.0); this is used to evenly spread packets out over time") - .takes_value(true) - .long("send-interval") - .required(false) - .default_value("0.05") - ) - .arg( - Arg::with_name("length") - .help("length of the buffer to exchange; for TCP, this defaults to 32 kibibytes; for UDP, it's 1024 bytes") - .takes_value(true) - .long("length") - .short("l") - .required(false) - .default_value("0") - ) - .arg( - Arg::with_name("send_buffer") - .help("send_buffer, in bytes (only supported on some platforms; if set too small, a 'resource unavailable' error may occur; affects TCP window-size)") - .takes_value(true) - .long("send-buffer") - .required(false) - .default_value("0") - ) - .arg( - Arg::with_name("receive_buffer") - .help("receive_buffer, in bytes (only supported on some platforms; if set too small, a 'resource unavailable' error may occur; affects TCP window-size)") - .takes_value(true) - .long("receive-buffer") - .required(false) - .default_value("0") - ) - .arg( - Arg::with_name("parallel") - .help("the number of parallel data-streams to use") - .takes_value(true) - .long("parallel") - .short("P") - .required(false) - .default_value("1") - ) - .arg( - Arg::with_name("omit") - .help("omit a number of seconds from the start of calculations, primarily to avoid including TCP ramp-up in averages; using this option may result in disagreement between bytes sent and received, since data can be in-flight across time-boundaries") - .takes_value(true) - .long("omit") - .short("O") - .default_value("0") - .required(false) - ) - .arg( - Arg::with_name("no_delay") - .help("use no-delay mode for TCP tests, disabling Nagle's Algorithm") - .takes_value(false) - .long("no-delay") - .short("N") - .required(false) - ) - .arg( - Arg::with_name("tcp_port_pool") - .help("an optional pool of IPv4 TCP ports over which data will be accepted; if omitted, any OS-assignable port is used; format: 1-10,19,21") - .takes_value(true) - .long("tcp-port-pool") - .required(false) - .default_value("") - ) - .arg( - Arg::with_name("tcp6_port_pool") - .help("an optional pool of IPv6 TCP ports over which data will be accepted; if omitted, any OS-assignable port is used; format: 1-10,19,21") - .takes_value(true) - .long("tcp6-port-pool") - .required(false) - .default_value("") - ) - .arg( - Arg::with_name("udp_port_pool") - .help("an optional pool of IPv4 UDP ports over which data will be accepted; if omitted, any OS-assignable port is used; format: 1-10,19,21") - .takes_value(true) - .long("udp-port-pool") - .required(false) - .default_value("") - ) - .arg( - Arg::with_name("udp6_port_pool") - .help("an optional pool of IPv6 UDP ports over which data will be accepted; if omitted, any OS-assignable port is used; format: 1-10,19,21") - .takes_value(true) - .long("udp6-port-pool") - .required(false) - .default_value("") - ) - .get_matches(); + use clap::Parser; + let args = args::Args::parse(); let mut env = env_logger::Env::default().filter_or("RUST_LOG", "info"); - if args.is_present("debug") { + if args.debug { env = env.filter_or("RUST_LOG", "debug"); } env_logger::init_from_env(env); - if args.is_present("server") { + if args.server { log::debug!("registering SIGINT handler..."); - ctrlc::set_handler(move || { + ctrlc2::set_handler(move || { if server::kill() { log::warn!( "shutdown requested; please allow a moment for any in-progress tests to stop" @@ -259,18 +46,19 @@ fn main() { log::warn!("forcing shutdown immediately"); std::process::exit(3); } + true }) .expect("unable to set SIGINT handler"); log::debug!("beginning normal operation..."); - let service = server::serve(args); + let service = server::serve(&args); if service.is_err() { log::error!("unable to run server: {}", service.unwrap_err()); std::process::exit(4); } - } else if args.is_present("client") { + } else if args.client.is_some() { log::debug!("registering SIGINT handler..."); - ctrlc::set_handler(move || { + ctrlc2::set_handler(move || { if client::kill() { log::warn!( "shutdown requested; please allow a moment for any in-progress tests to stop" @@ -279,17 +67,20 @@ fn main() { log::warn!("forcing shutdown immediately"); std::process::exit(3); } + true }) .expect("unable to set SIGINT handler"); log::debug!("connecting to server..."); - let execution = client::execute(args); + let execution = client::execute(&args); if execution.is_err() { log::error!("unable to run client: {}", execution.unwrap_err()); std::process::exit(4); } } else { - std::println!("{}", args.usage()); + use clap::CommandFactory; + let mut cmd = args::Args::command(); + cmd.print_help().unwrap(); std::process::exit(2); } } diff --git a/src/protocol/messaging.rs b/src/protocol/messaging.rs index 880af8b..d75cbce 100644 --- a/src/protocol/messaging.rs +++ b/src/protocol/messaging.rs @@ -198,17 +198,17 @@ fn calculate_length_udp(length: u16) -> u16 { /// prepares a message used to describe the upload role in a test pub fn prepare_upload_configuration( - args: &clap::ArgMatches, + args: &crate::args::Args, test_id: &[u8; 16], ) -> BoxResult { - let parallel_streams: u8 = args.value_of("parallel").unwrap().parse()?; - let mut seconds: f32 = args.value_of("time").unwrap().parse()?; - let mut send_interval: f32 = args.value_of("send_interval").unwrap().parse()?; - let mut length: u32 = args.value_of("length").unwrap().parse()?; + let parallel_streams: u8 = args.parallel as u8; + let mut seconds: f32 = args.time as f32; + let mut send_interval: f32 = args.send_interval as f32; + let mut length: u32 = args.length as u32; - let mut send_buffer: u32 = args.value_of("send_buffer").unwrap().parse()?; + let mut send_buffer: u32 = args.send_buffer as u32; - let mut bandwidth_string = args.value_of("bandwidth").unwrap(); + let mut bandwidth_string = args.bandwidth.as_str(); let bandwidth: u64; let bandwidth_multiplier: f64; match bandwidth_string.chars().last() { @@ -256,7 +256,7 @@ pub fn prepare_upload_configuration( //invalid input; fall back to 1mbps log::warn!( "invalid bandwidth: {}; setting value to 1mbps", - args.value_of("bandwidth").unwrap() + args.bandwidth ); bandwidth = 125000; } @@ -266,7 +266,7 @@ pub fn prepare_upload_configuration( //invalid input; fall back to 1mbps log::warn!( "invalid bandwidth: {}; setting value to 1mbps", - args.value_of("bandwidth").unwrap() + args.bandwidth ); bandwidth = 125000; } @@ -282,7 +282,7 @@ pub fn prepare_upload_configuration( send_interval = 0.05 } - if args.is_present("udp") { + if args.udp { log::debug!("preparing UDP upload config..."); if length == 0 { length = 1024; @@ -310,7 +310,7 @@ pub fn prepare_upload_configuration( send_buffer = length * 2; } - let no_delay: bool = args.is_present("no_delay"); + let no_delay: bool = args.no_delay; Ok(prepare_configuration_tcp_upload( test_id, @@ -326,14 +326,14 @@ pub fn prepare_upload_configuration( } /// prepares a message used to describe the download role in a test pub fn prepare_download_configuration( - args: &clap::ArgMatches, + args: &crate::args::Args, test_id: &[u8; 16], ) -> BoxResult { - let parallel_streams: u8 = args.value_of("parallel").unwrap().parse()?; - let mut length: u32 = args.value_of("length").unwrap().parse()?; - let mut receive_buffer: u32 = args.value_of("receive_buffer").unwrap().parse()?; + let parallel_streams: u8 = args.parallel as u8; + let mut length: u32 = args.length as u32; + let mut receive_buffer: u32 = args.receive_buffer as u32; - if args.is_present("udp") { + if args.udp { log::debug!("preparing UDP download config..."); if length == 0 { length = 1024; diff --git a/src/server.rs b/src/server.rs index b77549e..16e3ca1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -20,18 +20,17 @@ use std::error::Error; use std::io; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use std::net::{Shutdown, SocketAddr}; use std::sync::atomic::{AtomicBool, AtomicU16, Ordering}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use clap::ArgMatches; - use mio::net::{TcpListener, TcpStream}; use mio::{Events, Poll, PollOpt, Ready, Token}; +use crate::args::Args; use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; use crate::protocol::messaging::{prepare_connect, prepare_connect_ready}; @@ -381,41 +380,29 @@ impl Drop for ClientThreadMonitor { } } -pub fn serve(args: ArgMatches) -> BoxResult<()> { +pub fn serve(args: &Args) -> BoxResult<()> { //config-parsing and pre-connection setup let tcp_port_pool = Arc::new(Mutex::new(tcp::receiver::TcpPortPool::new( - args.value_of("tcp_port_pool").unwrap().to_string(), - args.value_of("tcp6_port_pool").unwrap().to_string(), + args.tcp_port_pool.to_string(), + args.tcp6_port_pool.to_string(), ))); let udp_port_pool = Arc::new(Mutex::new(udp::receiver::UdpPortPool::new( - args.value_of("udp_port_pool").unwrap().to_string(), - args.value_of("udp6_port_pool").unwrap().to_string(), + args.udp_port_pool.to_string(), + args.udp6_port_pool.to_string(), ))); let cpu_affinity_manager = Arc::new(Mutex::new( - crate::utils::cpu_affinity::CpuAffinityManager::new(args.value_of("affinity").unwrap())?, + crate::utils::cpu_affinity::CpuAffinityManager::new(&args.affinity)?, )); - let client_limit: u16 = args.value_of("client_limit").unwrap().parse()?; + let client_limit: u16 = args.client_limit as u16; if client_limit > 0 { log::debug!("limiting service to {} concurrent clients", client_limit); } - let (unspec_str, unspec) = if args.is_present("version6") { - ("::", IpAddr::V6(Ipv6Addr::UNSPECIFIED)) - } else { - ("0.0.0.0", IpAddr::V4(Ipv4Addr::UNSPECIFIED)) - }; - - let addr = args - .value_of("bind") - .unwrap_or(unspec_str) - .parse::() - .unwrap_or(unspec); - //start listening for connections - let port: u16 = args.value_of("port").unwrap().parse()?; - let listener: TcpListener = TcpListener::bind(&SocketAddr::new(addr, port)) + let port: u16 = args.port; + let listener: TcpListener = TcpListener::bind(&SocketAddr::new(args.bind, port)) .unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)); log::info!("server listening on {}", listener.local_addr()?); From a80178bc5da4bb042e600a3bd157364d03721eea Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:09:01 +0800 Subject: [PATCH 04/18] refine code --- src/client.rs | 207 ++++++------ src/protocol/communication.rs | 117 ++++--- src/server.rs | 581 +++++++++++++++++----------------- src/stream/tcp.rs | 264 ++++++++------- src/stream/udp.rs | 112 +++---- 5 files changed, 648 insertions(+), 633 deletions(-) diff --git a/src/client.rs b/src/client.rs index ef3f536..8818403 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,31 +18,34 @@ * along with rperf. If not, see . */ -use std::net::{IpAddr, Shutdown, ToSocketAddrs}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::channel; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use mio::net::TcpStream; - -use crate::args; -use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; - -use crate::protocol::messaging::{ - prepare_begin, prepare_download_configuration, prepare_end, prepare_upload_configuration, +use crate::{ + args, + protocol::{ + communication::{receive, send, KEEPALIVE_DURATION}, + messaging::{ + prepare_begin, prepare_download_configuration, prepare_end, + prepare_upload_configuration, + }, + results::ClientDoneResult, + results::{ + IntervalResultBox, IntervalResultKind, TcpTestResults, TestResults, UdpTestResults, + }, + }, + stream::{tcp, udp, TestStream}, }; - -use crate::protocol::results::{ - IntervalResultBox, IntervalResultKind, TcpTestResults, TestResults, UdpTestResults, +use mio::net::TcpStream; +use std::{ + error::Error, + net::{IpAddr, Shutdown, ToSocketAddrs}, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::channel, + Arc, Mutex, + }, + thread, + time::{Duration, SystemTime, UNIX_EPOCH}, }; -use crate::stream::tcp; -use crate::stream::udp; -use crate::stream::TestStream; - -use std::error::Error; type BoxResult = Result>; /// when false, the system is shutting down @@ -381,40 +384,40 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { loop { let mut test = c_ps.lock().unwrap(); log::debug!("beginning test-interval for stream {}", test.get_idx()); - match test.run_interval() { - Some(interval_result) => match interval_result { - Ok(ir) => match c_results_tx.send(ir) { + + let interval_result = match test.run_interval() { + Some(interval_result) => interval_result, + None => { + match c_results_tx.send(Box::new(ClientDoneResult { + stream_idx: test.get_idx(), + })) { Ok(_) => (), Err(e) => { - log::error!("unable to report interval-result: {}", e); - break; + log::error!("unable to report interval-done-result: {}", e) } - }, + } + break; + } + }; + + match interval_result { + Ok(ir) => match c_results_tx.send(ir) { + Ok(_) => (), Err(e) => { - log::error!("unable to process stream: {}", e); - match c_results_tx.send(Box::new( - crate::protocol::results::ClientFailedResult { - stream_idx: test.get_idx(), - }, - )) { - Ok(_) => (), - Err(e) => log::error!( - "unable to report interval-failed-result: {}", - e - ), - } + log::error!("unable to report interval-result: {}", e); break; } }, - None => { + Err(e) => { + log::error!("unable to process stream: {}", e); match c_results_tx.send(Box::new( - crate::protocol::results::ClientDoneResult { + crate::protocol::results::ClientFailedResult { stream_idx: test.get_idx(), }, )) { Ok(_) => (), Err(e) => { - log::error!("unable to report interval-done-result: {}", e) + log::error!("unable to report interval-failed-result: {}", e) } } break; @@ -427,63 +430,77 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { //watch for events from the server while is_alive() { - match receive(&mut stream, is_alive, &mut results_handler) { - Ok(payload) => { - match payload.get("kind") { - Some(kind) => { - match kind.as_str().unwrap_or_default() { - "receive" | "send" => { //receive/send-results from the server - if !display_json { - let result = crate::protocol::results::interval_result_from_json(payload.clone())?; - println!("{}", result.to_string(display_bit)); - } - let mut tr = test_results.lock().unwrap(); - tr.update_from_json(payload)?; - }, - "done" | "failed" => match payload.get("stream_idx") { //completion-result from the server - Some(stream_idx) => match stream_idx.as_i64() { - Some(idx64) => { - let mut tr = test_results.lock().unwrap(); - match kind.as_str().unwrap() { - "done" => { - log::info!("server reported completion of stream {}", idx64); - }, - "failed" => { - log::warn!("server reported failure with stream {}", idx64); - tr.mark_stream_done(&(idx64 as u8), false); - }, - _ => (), //not possible - } - tr.mark_stream_done_server(&(idx64 as u8)); - if tr.count_in_progress_streams() == 0 && tr.count_in_progress_streams_server() == 0 { //all data gathered from both sides - kill(); - } - }, - None => log::error!("completion from server did not include a valid stream_idx"), - }, - None => log::error!("completion from server did not include stream_idx"), - }, - _ => { - log::error!("invalid data from {}: {}", stream.peer_addr()?, serde_json::to_string(&connection_payload)?); - break; - }, + let payload = match receive(&mut stream, is_alive, &mut results_handler) { + Ok(payload) => payload, + Err(e) => { + if !complete { + // when complete, this also occurs + return Err(e); + } + break; + } + }; + + let kind = match payload.get("kind") { + Some(kind) => kind, + None => { + log::error!( + "invalid data from {}: {}", + stream.peer_addr()?, + serde_json::to_string(&connection_payload)? + ); + break; + } + }; + + match kind.as_str().unwrap_or_default() { + "receive" | "send" => { + //receive/send-results from the server + if !display_json { + let result = + crate::protocol::results::interval_result_from_json(payload.clone())?; + println!("{}", result.to_string(display_bit)); + } + let mut tr = test_results.lock().unwrap(); + tr.update_from_json(payload)?; + } + "done" | "failed" => match payload.get("stream_idx") { + //completion-result from the server + Some(stream_idx) => match stream_idx.as_i64() { + Some(idx64) => { + let mut tr = test_results.lock().unwrap(); + match kind.as_str().unwrap() { + "done" => { + log::info!("server reported completion of stream {}", idx64); + } + "failed" => { + log::warn!("server reported failure with stream {}", idx64); + tr.mark_stream_done(&(idx64 as u8), false); + } + _ => (), //not possible + } + tr.mark_stream_done_server(&(idx64 as u8)); + if tr.count_in_progress_streams() == 0 + && tr.count_in_progress_streams_server() == 0 + { + //all data gathered from both sides + kill(); } } None => { - log::error!( - "invalid data from {}: {}", - stream.peer_addr()?, - serde_json::to_string(&connection_payload)? - ); - break; + log::error!("completion from server did not include a valid stream_idx") } + }, + None => { + log::error!("completion from server did not include stream_idx") } - } - Err(e) => { - if !complete { - //when complete, this also occurs - return Err(e); - } + }, + _ => { + log::error!( + "invalid data from {}: {}", + stream.peer_addr()?, + serde_json::to_string(&connection_payload)? + ); break; } } diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index b398417..7fdcc80 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -77,37 +77,8 @@ fn receive_length( for event in events.iter() { event.token(); loop { - match cloned_stream.read(&mut length_spec[length_bytes_read..]) { - Ok(size) => { - if size == 0 { - if alive_check() { - return Err(Box::new(simple_error::simple_error!( - "connection lost" - ))); - } else { - //shutting down; a disconnect is expected - return Err(Box::new(simple_error::simple_error!( - "local shutdown requested" - ))); - } - } - - length_bytes_read += size; - if length_bytes_read == 2 { - let length = u16::from_be_bytes(length_spec); - log::debug!( - "received length-spec of {} from {}", - length, - stream.peer_addr()? - ); - return Ok(length); - } else { - log::debug!( - "received partial length-spec from {}", - stream.peer_addr()? - ); - } - } + let size = match cloned_stream.read(&mut length_spec[length_bytes_read..]) { + Ok(size) => size, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { //nothing left to process break; @@ -115,6 +86,30 @@ fn receive_length( Err(e) => { return Err(Box::new(e)); } + }; + + if size == 0 { + if alive_check() { + return Err(Box::new(simple_error::simple_error!("connection lost"))); + } else { + //shutting down; a disconnect is expected + return Err(Box::new(simple_error::simple_error!( + "local shutdown requested" + ))); + } + } + + length_bytes_read += size; + if length_bytes_read == 2 { + let length = u16::from_be_bytes(length_spec); + log::debug!( + "received length-spec of {} from {}", + length, + stream.peer_addr()? + ); + return Ok(length); + } else { + log::debug!("received partial length-spec from {}", stream.peer_addr()?); } } } @@ -151,43 +146,41 @@ fn receive_payload( for event in events.iter() { event.token(); loop { - match cloned_stream.read(&mut buffer[bytes_read..]) { - Ok(size) => { - if size == 0 { - if alive_check() { - return Err(Box::new(simple_error::simple_error!( - "connection lost" - ))); - } else { - //shutting down; a disconnect is expected - return Err(Box::new(simple_error::simple_error!( - "local shutdown requested" - ))); - } - } - - bytes_read += size; - if bytes_read == length as usize { - match serde_json::from_slice(&buffer) { - Ok(v) => { - log::debug!("received {:?} from {}", v, stream.peer_addr()?); - return Ok(v); - } - Err(e) => { - return Err(Box::new(e)); - } - } - } else { - log::debug!("received partial payload from {}", stream.peer_addr()?); - } - } + let size = match cloned_stream.read(&mut buffer[bytes_read..]) { + Ok(size) => size, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - //nothing left to process + // nothing left to process break; } Err(e) => { return Err(Box::new(e)); } + }; + + if size == 0 { + if alive_check() { + return Err(Box::new(simple_error::simple_error!("connection lost"))); + } else { + // shutting down; a disconnect is expected + return Err(Box::new(simple_error::simple_error!( + "local shutdown requested" + ))); + } + } + + bytes_read += size; + if bytes_read == length as usize { + match serde_json::from_slice(&buffer) { + Ok(v) => { + log::debug!("received {:?} from {}", v, stream.peer_addr()?); + return Ok(v); + } + Err(e) => { + return Err(Box::new(e)); + } + } + } else { + log::debug!("received partial payload from {}", stream.peer_addr()?); } } } diff --git a/src/server.rs b/src/server.rs index 16e3ca1..35e02ff 100644 --- a/src/server.rs +++ b/src/server.rs @@ -32,12 +32,9 @@ use mio::{Events, Poll, PollOpt, Ready, Token}; use crate::args::Args; use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; - use crate::protocol::messaging::{prepare_connect, prepare_connect_ready}; - -use crate::stream::tcp; -use crate::stream::udp; -use crate::stream::TestStream; +use crate::protocol::results::ServerDoneResult; +use crate::stream::{tcp, udp, TestStream}; type BoxResult = Result>; @@ -81,259 +78,272 @@ fn handle_client( //server operations are entirely driven by client-signalling, making this a (simple) state-machine while is_alive() { let payload = receive(stream, is_alive, &mut results_handler)?; - match payload.get("kind") { - Some(kind) => { - match kind.as_str().unwrap() { - "configuration" => { - //we either need to connect streams to the client or prepare to receive connections - if payload - .get("role") - .unwrap_or(&serde_json::json!("download")) - .as_str() - .unwrap() - == "download" - { - log::info!( - "[{}] running in forward-mode: server will be receiving data", - &peer_addr + let kind = if let Some(kind) = payload.get("kind") { + kind + } else { + log::error!("[{}] invalid data", &peer_addr); + break; + }; + match kind.as_str().unwrap() { + "configuration" => { + //we either need to connect streams to the client or prepare to receive connections + if payload + .get("role") + .unwrap_or(&serde_json::json!("download")) + .as_str() + .unwrap() + == "download" + { + log::info!( + "[{}] running in forward-mode: server will be receiving data", + &peer_addr + ); + + let stream_count = payload + .get("streams") + .unwrap_or(&serde_json::json!(1)) + .as_i64() + .unwrap(); + //since we're receiving data, we're also responsible for letting the client know where to send it + let mut stream_ports = Vec::with_capacity(stream_count as usize); + + if payload + .get("family") + .unwrap_or(&serde_json::json!("tcp")) + .as_str() + .unwrap() + == "udp" + { + log::info!( + "[{}] preparing for UDP test with {} streams...", + &peer_addr, + stream_count + ); + + let mut c_udp_port_pool = udp_port_pool.lock().unwrap(); + + let test_definition = udp::UdpTestDefinition::new(&payload)?; + for stream_idx in 0..stream_count { + log::debug!( + "[{}] preparing UDP-receiver for stream {}...", + &peer_addr, + stream_idx ); - - let stream_count = payload - .get("streams") - .unwrap_or(&serde_json::json!(1)) - .as_i64() - .unwrap(); - //since we're receiving data, we're also responsible for letting the client know where to send it - let mut stream_ports = Vec::with_capacity(stream_count as usize); - - if payload - .get("family") - .unwrap_or(&serde_json::json!("tcp")) - .as_str() - .unwrap() - == "udp" - { - log::info!( - "[{}] preparing for UDP test with {} streams...", - &peer_addr, - stream_count - ); - - let mut c_udp_port_pool = udp_port_pool.lock().unwrap(); - - let test_definition = udp::UdpTestDefinition::new(&payload)?; - for stream_idx in 0..stream_count { - log::debug!( - "[{}] preparing UDP-receiver for stream {}...", - &peer_addr, - stream_idx - ); - let test = udp::receiver::UdpReceiver::new( - test_definition.clone(), - &(stream_idx as u8), - &mut c_udp_port_pool, - &peer_addr.ip(), - &(payload["receive_buffer"].as_i64().unwrap() as usize), - )?; - stream_ports.push(test.get_port()?); - parallel_streams.push(Arc::new(Mutex::new(test))); - } - } else { - //TCP - log::info!( - "[{}] preparing for TCP test with {} streams...", - &peer_addr, - stream_count - ); - - let mut c_tcp_port_pool = tcp_port_pool.lock().unwrap(); - - let test_definition = tcp::TcpTestDefinition::new(&payload)?; - for stream_idx in 0..stream_count { - log::debug!( - "[{}] preparing TCP-receiver for stream {}...", - &peer_addr, - stream_idx - ); - let test = tcp::receiver::TcpReceiver::new( - test_definition.clone(), - &(stream_idx as u8), - &mut c_tcp_port_pool, - &peer_addr.ip(), - &(payload["receive_buffer"].as_i64().unwrap() as usize), - )?; - stream_ports.push(test.get_port()?); - parallel_streams.push(Arc::new(Mutex::new(test))); - } - } - - //let the client know we're ready to receive the connection; stream-ports are in stream-index order - send(stream, &prepare_connect(&stream_ports))?; - } else { - //upload - log::info!( - "[{}] running in reverse-mode: server will be uploading data", - &peer_addr + let test = udp::receiver::UdpReceiver::new( + test_definition.clone(), + &(stream_idx as u8), + &mut c_udp_port_pool, + &peer_addr.ip(), + &(payload["receive_buffer"].as_i64().unwrap() as usize), + )?; + stream_ports.push(test.get_port()?); + parallel_streams.push(Arc::new(Mutex::new(test))); + } + } else { + //TCP + log::info!( + "[{}] preparing for TCP test with {} streams...", + &peer_addr, + stream_count + ); + + let mut c_tcp_port_pool = tcp_port_pool.lock().unwrap(); + + let test_definition = tcp::TcpTestDefinition::new(&payload)?; + for stream_idx in 0..stream_count { + log::debug!( + "[{}] preparing TCP-receiver for stream {}...", + &peer_addr, + stream_idx ); + let test = tcp::receiver::TcpReceiver::new( + test_definition.clone(), + &(stream_idx as u8), + &mut c_tcp_port_pool, + &peer_addr.ip(), + &(payload["receive_buffer"].as_i64().unwrap() as usize), + )?; + stream_ports.push(test.get_port()?); + parallel_streams.push(Arc::new(Mutex::new(test))); + } + } - let stream_ports = - payload.get("stream_ports").unwrap().as_array().unwrap(); - - if payload - .get("family") - .unwrap_or(&serde_json::json!("tcp")) - .as_str() - .unwrap() - == "udp" - { - log::info!( - "[{}] preparing for UDP test with {} streams...", - &peer_addr, - stream_ports.len() - ); - - let test_definition = udp::UdpTestDefinition::new(&payload)?; - for (stream_idx, port) in stream_ports.iter().enumerate() { - log::debug!( - "[{}] preparing UDP-sender for stream {}...", - &peer_addr, - stream_idx - ); - let test = udp::sender::UdpSender::new( - test_definition.clone(), - &(stream_idx as u8), - &0, - &peer_addr.ip(), - &(port.as_i64().unwrap_or(0) as u16), - &(payload - .get("duration") - .unwrap_or(&serde_json::json!(0.0)) - .as_f64() - .unwrap() - as f32), - &(payload - .get("send_interval") - .unwrap_or(&serde_json::json!(1.0)) - .as_f64() - .unwrap() - as f32), - &(payload["send_buffer"].as_i64().unwrap() as usize), - )?; - parallel_streams.push(Arc::new(Mutex::new(test))); - } - } else { - //TCP - log::info!( - "[{}] preparing for TCP test with {} streams...", - &peer_addr, - stream_ports.len() - ); - - let test_definition = tcp::TcpTestDefinition::new(&payload)?; - for (stream_idx, port) in stream_ports.iter().enumerate() { - log::debug!( - "[{}] preparing TCP-sender for stream {}...", - &peer_addr, - stream_idx - ); - let test = tcp::sender::TcpSender::new( - test_definition.clone(), - &(stream_idx as u8), - &peer_addr.ip(), - &(port.as_i64().unwrap() as u16), - &(payload["duration"].as_f64().unwrap() as f32), - &(payload["send_interval"].as_f64().unwrap() as f32), - &(payload["send_buffer"].as_i64().unwrap() as usize), - &(payload["no_delay"].as_bool().unwrap()), - )?; - parallel_streams.push(Arc::new(Mutex::new(test))); - } - } - - //let the client know we're ready to begin - send(stream, &prepare_connect_ready())?; + //let the client know we're ready to receive the connection; stream-ports are in stream-index order + send(stream, &prepare_connect(&stream_ports))?; + } else { + //upload + log::info!( + "[{}] running in reverse-mode: server will be uploading data", + &peer_addr + ); + + let stream_ports = payload.get("stream_ports").unwrap().as_array().unwrap(); + + if payload + .get("family") + .unwrap_or(&serde_json::json!("tcp")) + .as_str() + .unwrap() + == "udp" + { + log::info!( + "[{}] preparing for UDP test with {} streams...", + &peer_addr, + stream_ports.len() + ); + + let test_definition = udp::UdpTestDefinition::new(&payload)?; + for (stream_idx, port) in stream_ports.iter().enumerate() { + log::debug!( + "[{}] preparing UDP-sender for stream {}...", + &peer_addr, + stream_idx + ); + let test = udp::sender::UdpSender::new( + test_definition.clone(), + &(stream_idx as u8), + &0, + &peer_addr.ip(), + &(port.as_i64().unwrap_or(0) as u16), + &(payload + .get("duration") + .unwrap_or(&serde_json::json!(0.0)) + .as_f64() + .unwrap() as f32), + &(payload + .get("send_interval") + .unwrap_or(&serde_json::json!(1.0)) + .as_f64() + .unwrap() as f32), + &(payload["send_buffer"].as_i64().unwrap() as usize), + )?; + parallel_streams.push(Arc::new(Mutex::new(test))); + } + } else { + //TCP + log::info!( + "[{}] preparing for TCP test with {} streams...", + &peer_addr, + stream_ports.len() + ); + + let test_definition = tcp::TcpTestDefinition::new(&payload)?; + for (stream_idx, port) in stream_ports.iter().enumerate() { + log::debug!( + "[{}] preparing TCP-sender for stream {}...", + &peer_addr, + stream_idx + ); + let test = tcp::sender::TcpSender::new( + test_definition.clone(), + &(stream_idx as u8), + &peer_addr.ip(), + &(port.as_i64().unwrap() as u16), + &(payload["duration"].as_f64().unwrap() as f32), + &(payload["send_interval"].as_f64().unwrap() as f32), + &(payload["send_buffer"].as_i64().unwrap() as usize), + &(payload["no_delay"].as_bool().unwrap()), + )?; + parallel_streams.push(Arc::new(Mutex::new(test))); } } - "begin" => { - //the client has indicated that testing can begin - if !started { - //a simple guard to protect against reinitialisaion - for (stream_idx, parallel_stream) in - parallel_streams.iter_mut().enumerate() + + //let the client know we're ready to begin + send(stream, &prepare_connect_ready())?; + } + } + "begin" => { + //the client has indicated that testing can begin + if !started { + //a simple guard to protect against reinitialisaion + for (stream_idx, parallel_stream) in parallel_streams.iter_mut().enumerate() { + log::info!( + "[{}] beginning execution of stream {}...", + &peer_addr, + stream_idx + ); + let c_ps = Arc::clone(parallel_stream); + let c_results_tx = results_tx.clone(); + let c_cam = cpu_affinity_manager.clone(); + let handle = thread::spawn(move || { { - log::info!( - "[{}] beginning execution of stream {}...", + //set CPU affinity, if enabled + c_cam.lock().unwrap().set_affinity(); + } + loop { + let mut test = c_ps.lock().unwrap(); + log::debug!( + "[{}] beginning test-interval for stream {}", &peer_addr, - stream_idx + test.get_idx() ); - let c_ps = Arc::clone(parallel_stream); - let c_results_tx = results_tx.clone(); - let c_cam = cpu_affinity_manager.clone(); - let handle = thread::spawn(move || { - { - //set CPU affinity, if enabled - c_cam.lock().unwrap().set_affinity(); + let interval_result = match test.run_interval() { + Some(interval_result) => interval_result, + None => { + match c_results_tx.send(Box::new(ServerDoneResult { + stream_idx: test.get_idx(), + })) { + Ok(_) => (), + Err(e) => log::error!( + "[{}] unable to report interval-done-result: {}", + &peer_addr, + e + ), + } + break; } - loop { - let mut test = c_ps.lock().unwrap(); - log::debug!( - "[{}] beginning test-interval for stream {}", - &peer_addr, - test.get_idx() + }; + + match interval_result { + Ok(ir) => match c_results_tx.send(ir) { + Ok(_) => (), + Err(e) => { + log::error!( + "[{}] unable to process interval-result: {}", + &peer_addr, + e + ); + break; + } + }, + Err(e) => { + log::error!( + "[{}] unable to process stream: {}", + peer_addr, + e ); - match test.run_interval() { - Some(interval_result) => match interval_result { - Ok(ir) => match c_results_tx.send(ir) { - Ok(_) => (), - Err(e) => { - log::error!("[{}] unable to process interval-result: {}", &peer_addr, e); - break; - } - }, - Err(e) => { - log::error!( - "[{}] unable to process stream: {}", - peer_addr, - e - ); - match c_results_tx.send(Box::new(crate::protocol::results::ServerFailedResult{stream_idx: test.get_idx()})) { - Ok(_) => (), - Err(e) => log::error!("[{}] unable to report interval-failed-result: {}", &peer_addr, e), - } - break; - } + match c_results_tx.send(Box::new( + crate::protocol::results::ServerFailedResult { + stream_idx: test.get_idx(), }, - None => { - match c_results_tx.send(Box::new(crate::protocol::results::ServerDoneResult{stream_idx: test.get_idx()})) { - Ok(_) => (), - Err(e) => log::error!("[{}] unable to report interval-done-result: {}", &peer_addr, e), - } - break; - } + )) { + Ok(_) => (), + Err(e) => log::error!( + "[{}] unable to report interval-failed-result: {}", + &peer_addr, + e + ), } + break; } - }); - parallel_streams_joinhandles.push(handle); + } } - started = true; - } else { - //this can only happen in case of malicious action - log::error!("[{}] duplicate begin-signal", &peer_addr); - break; - } - } - "end" => { - //the client has indicated that testing is done; stop cleanly - log::info!("[{}] end of testing signaled", &peer_addr); - break; - } - _ => { - log::error!("[{}] invalid data", &peer_addr); - break; + }); + parallel_streams_joinhandles.push(handle); } + started = true; + } else { + //this can only happen in case of malicious action + log::error!("[{}] duplicate begin-signal", &peer_addr); + break; } } - None => { + "end" => { + //the client has indicated that testing is done; stop cleanly + log::info!("[{}] end of testing signaled", &peer_addr); + break; + } + _ => { log::error!("[{}] invalid data", &peer_addr); break; } @@ -416,59 +426,54 @@ pub fn serve(args: &Args) -> BoxResult<()> { for event in events.iter() { event.token(); loop { - match listener.accept() { - Ok((mut stream, address)) => { - log::info!("connection from {}", address); - - stream - .set_nodelay(true) - .expect("cannot disable Nagle's algorithm"); - stream - .set_keepalive(Some(KEEPALIVE_DURATION)) - .expect("unable to set TCP keepalive"); - - let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; - if client_limit > 0 && client_count > client_limit { - log::warn!( - "client-limit ({}) reached; disconnecting {}...", - client_limit, - address.to_string() - ); - stream.shutdown(Shutdown::Both).unwrap_or_default(); - CLIENTS.fetch_sub(1, Ordering::Relaxed); - } else { - let c_cam = cpu_affinity_manager.clone(); - let c_tcp_port_pool = tcp_port_pool.clone(); - let c_udp_port_pool = udp_port_pool.clone(); - let thread_builder = thread::Builder::new().name(address.to_string()); - thread_builder.spawn(move || { - //ensure the client is accounted-for even if the handler panics - let _client_thread_monitor = ClientThreadMonitor { - client_address: address.to_string(), - }; - - match handle_client( - &mut stream, - c_cam, - c_tcp_port_pool, - c_udp_port_pool, - ) { - Ok(_) => (), - Err(e) => log::error!("error in client-handler: {}", e), - } - - //in the event of panic, this will happen when the stream is dropped - stream.shutdown(Shutdown::Both).unwrap_or_default(); - })?; - } - } + let (mut stream, address) = match listener.accept() { + Ok((stream, address)) => (stream, address), Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - //nothing to do + // nothing to do break; } Err(e) => { return Err(Box::new(e)); } + }; + + log::info!("connection from {}", address); + + stream + .set_nodelay(true) + .expect("cannot disable Nagle's algorithm"); + stream + .set_keepalive(Some(KEEPALIVE_DURATION)) + .expect("unable to set TCP keepalive"); + + let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; + if client_limit > 0 && client_count > client_limit { + log::warn!( + "client-limit ({}) reached; disconnecting {}...", + client_limit, + address.to_string() + ); + stream.shutdown(Shutdown::Both).unwrap_or_default(); + CLIENTS.fetch_sub(1, Ordering::Relaxed); + } else { + let c_cam = cpu_affinity_manager.clone(); + let c_tcp_port_pool = tcp_port_pool.clone(); + let c_udp_port_pool = udp_port_pool.clone(); + let thread_builder = thread::Builder::new().name(address.to_string()); + thread_builder.spawn(move || { + //ensure the client is accounted-for even if the handler panics + let _client_thread_monitor = ClientThreadMonitor { + client_address: address.to_string(), + }; + + match handle_client(&mut stream, c_cam, c_tcp_port_pool, c_udp_port_pool) { + Ok(_) => (), + Err(e) => log::error!("error in client-handler: {}", e), + } + + //in the event of panic, this will happen when the stream is dropped + stream.shutdown(Shutdown::Both).unwrap_or_default(); + })?; } } } diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 308e427..7856779 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -304,86 +304,82 @@ pub mod receiver { for event in events.iter() { event.token(); loop { - match listener.accept() { - Ok((stream, address)) => { + let (stream, address) = match listener.accept() { + Ok((stream, address)) => (stream, address), + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // nothing to do + break; + } + Err(e) => { + return Err(Box::new(e)); + } + }; + + log::debug!( + "received TCP stream {} connection from {}", + self.stream_idx, + address + ); + + let mut verification_stream = stream.try_clone()?; + let mio_token2 = Token(0); + let poll2 = Poll::new()?; + poll2.register( + &verification_stream, + mio_token2, + Ready::readable(), + PollOpt::edge(), + )?; + + let mut buffer = [0_u8; 16]; + let mut events2 = Events::with_capacity(1); + poll2.poll(&mut events2, Some(RECEIVE_TIMEOUT))?; + for event2 in events2.iter() { + event2.token(); + + if let Err(e) = verification_stream.read(&mut buffer) { + if e.kind() == std::io::ErrorKind::WouldBlock { + // client didn't provide anything + break; + } + return Err(Box::new(e)); + } + + if buffer == self.test_definition.test_id { log::debug!( - "received TCP stream {} connection from {}", + "validated TCP stream {} connection from {}", self.stream_idx, address ); + if !cfg!(windows) { + // NOTE: features unsupported on Windows + if self.receive_buffer != 0 { + log::debug!( + "setting receive-buffer to {}...", + self.receive_buffer + ); + super::setsockopt( + stream.as_raw_fd(), + super::RcvBuf, + &self.receive_buffer, + )?; + } + } - let mut verification_stream = stream.try_clone()?; - let mio_token2 = Token(0); - let poll2 = Poll::new()?; - poll2.register( - &verification_stream, - mio_token2, + self.mio_poll.register( + &stream, + self.mio_poll_token, Ready::readable(), PollOpt::edge(), )?; - - let mut buffer = [0_u8; 16]; - let mut events2 = Events::with_capacity(1); - poll2.poll(&mut events2, Some(RECEIVE_TIMEOUT))?; - for event2 in events2.iter() { - event2.token(); - match verification_stream.read(&mut buffer) { - Ok(_) => { - if buffer == self.test_definition.test_id { - log::debug!( - "validated TCP stream {} connection from {}", - self.stream_idx, - address - ); - if !cfg!(windows) { - //NOTE: features unsupported on Windows - if self.receive_buffer != 0 { - log::debug!( - "setting receive-buffer to {}...", - self.receive_buffer - ); - super::setsockopt( - stream.as_raw_fd(), - super::RcvBuf, - &self.receive_buffer, - )?; - } - } - - self.mio_poll.register( - &stream, - self.mio_poll_token, - Ready::readable(), - PollOpt::edge(), - )?; - return Ok(stream); - } - } - Err(ref e) - if e.kind() == std::io::ErrorKind::WouldBlock => - { - //client didn't provide anything - break; - } - Err(e) => { - return Err(Box::new(e)); - } - } - } - log::warn!( - "could not validate TCP stream {} connection from {}", - self.stream_idx, - address - ); - } - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - //nothing to do - break; - } - Err(e) => { - return Err(Box::new(e)); + return Ok(stream); } } + log::warn!( + "could not validate TCP stream {} connection from {}", + self.stream_idx, + address + ); } } } @@ -444,43 +440,43 @@ pub mod receiver { for event in events.iter() { if event.token() == self.mio_poll_token { loop { - match stream.read(&mut buf) { - Ok(packet_size) => { - log::trace!( - "received {} bytes in TCP stream {} from {}", - packet_size, - self.stream_idx, - peer_addr - ); - if packet_size == 0 { - //test's over - //HACK: can't call self.stop() because it's a double-borrow due to the unwrapped stream - self.active.store(false, Relaxed); - break; - } - - bytes_received += packet_size as u64; - - let elapsed_time = start.elapsed(); - if elapsed_time >= super::INTERVAL { - return Some(Ok(Box::new(super::TcpReceiveResult { - timestamp: super::get_unix_timestamp(), - - stream_idx: self.stream_idx, - - duration: elapsed_time.as_secs_f32(), - - bytes_received, - }))); - } - } + let packet_size = match stream.read(&mut buf) { + Ok(packet_size) => packet_size, Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { - //receive timeout + // receive timeout break; } Err(e) => { return Some(Err(Box::new(e))); } + }; + + log::trace!( + "received {} bytes in TCP stream {} from {}", + packet_size, + self.stream_idx, + peer_addr + ); + if packet_size == 0 { + // test's over + // HACK: can't call self.stop() because it's a double-borrow due to the unwrapped stream + self.active.store(false, Relaxed); + break; + } + + bytes_received += packet_size as u64; + + let elapsed_time = start.elapsed(); + if elapsed_time >= super::INTERVAL { + return Some(Ok(Box::new(super::TcpReceiveResult { + timestamp: super::get_unix_timestamp(), + + stream_idx: self.stream_idx, + + duration: elapsed_time.as_secs_f32(), + + bytes_received, + }))); } } } else { @@ -687,46 +683,46 @@ pub mod sender { ); let packet_start = Instant::now(); - match stream.write(&self.staged_buffer) { - //it doesn't matter if the whole thing couldn't be written, since it's just garbage data - Ok(packet_size) => { - log::trace!( - "wrote {} bytes in TCP stream {} to {}", - packet_size, - self.stream_idx, - peer_addr - ); - - let bytes_written = packet_size as i64; - bytes_sent += bytes_written as u64; - bytes_to_send_remaining -= bytes_written; - bytes_to_send_per_interval_slice_remaining -= bytes_written; - - let elapsed_time = cycle_start.elapsed(); - if elapsed_time >= super::INTERVAL { - self.remaining_duration -= packet_start.elapsed().as_secs_f32(); - - return Some(Ok(Box::new(super::TcpSendResult { - timestamp: super::get_unix_timestamp(), - - stream_idx: self.stream_idx, - - duration: elapsed_time.as_secs_f32(), - - bytes_sent, - sends_blocked, - }))); - } - } + let packet_size = match stream.write(&self.staged_buffer) { + // it doesn't matter if the whole thing couldn't be written, since it's just garbage data + Ok(packet_size) => packet_size, Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { - //send-buffer is full - //nothing to do, but avoid burning CPU cycles + // send-buffer is full + // nothing to do, but avoid burning CPU cycles sleep(BUFFER_FULL_TIMEOUT); sends_blocked += 1; + continue; } Err(e) => { return Some(Err(Box::new(e))); } + }; + log::trace!( + "wrote {} bytes in TCP stream {} to {}", + packet_size, + self.stream_idx, + peer_addr + ); + + let bytes_written = packet_size as i64; + bytes_sent += bytes_written as u64; + bytes_to_send_remaining -= bytes_written; + bytes_to_send_per_interval_slice_remaining -= bytes_written; + + let elapsed_time = cycle_start.elapsed(); + if elapsed_time >= super::INTERVAL { + self.remaining_duration -= packet_start.elapsed().as_secs_f32(); + + return Some(Ok(Box::new(super::TcpSendResult { + timestamp: super::get_unix_timestamp(), + + stream_idx: self.stream_idx, + + duration: elapsed_time.as_secs_f32(), + + bytes_sent, + sends_blocked, + }))); } if bytes_to_send_remaining <= 0 { diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 63ebc0d..1ac63fe 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -455,67 +455,71 @@ pub mod receiver { log::trace!("awaiting UDP packets on stream {}...", self.stream_idx); loop { - match self.socket.recv_from(&mut buf) { - Ok((packet_size, peer_addr)) => { - log::trace!( - "received {} bytes in UDP packet {} from {}", - packet_size, - self.stream_idx, - peer_addr - ); - if packet_size == 16 { - //possible end-of-test message - if buf[0..16] == self.test_definition.test_id { - //test's over - self.stop(); - break; - } - } - if packet_size < super::TEST_HEADER_SIZE as usize { - log::warn!("received malformed packet with size {} for UDP stream {} from {}", packet_size, self.stream_idx, peer_addr); - continue; - } + let (packet_size, peer_addr) = match self.socket.recv_from(&mut buf) { + Ok((packet_size, peer_addr)) => (packet_size, peer_addr), + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // receive timeout + break; + } + Err(e) => { + return Some(Err(Box::new(e))); + } + }; - if self.process_packet(&buf, &mut history) { - //NOTE: duplicate packets increase this count; this is intentional because the stack still processed data - bytes_received += - packet_size as u64 + super::UDP_HEADER_SIZE as u64; + log::trace!( + "received {} bytes in UDP packet {} from {}", + packet_size, + self.stream_idx, + peer_addr + ); + if packet_size == 16 { + // possible end-of-test message + if buf[0..16] == self.test_definition.test_id { + // test's over + self.stop(); + break; + } + } + if packet_size < super::TEST_HEADER_SIZE as usize { + log::warn!( + "received malformed packet with size {} for UDP stream {} from {}", + packet_size, + self.stream_idx, + peer_addr + ); + continue; + } - let elapsed_time = start.elapsed(); - if elapsed_time >= super::INTERVAL { - return Some(Ok(Box::new(super::UdpReceiveResult { - timestamp: super::get_unix_timestamp(), + if self.process_packet(&buf, &mut history) { + // NOTE: duplicate packets increase this count; this is intentional because the stack still processed data + bytes_received += packet_size as u64 + super::UDP_HEADER_SIZE as u64; - stream_idx: self.stream_idx, + let elapsed_time = start.elapsed(); + if elapsed_time >= super::INTERVAL { + return Some(Ok(Box::new(super::UdpReceiveResult { + timestamp: super::get_unix_timestamp(), - duration: elapsed_time.as_secs_f32(), + stream_idx: self.stream_idx, - bytes_received, - packets_received: history.packets_received, - packets_lost: history.packets_lost, - packets_out_of_order: history.packets_out_of_order, - packets_duplicated: history.packets_duplicated, + duration: elapsed_time.as_secs_f32(), - unbroken_sequence: history.longest_unbroken_sequence, - jitter_seconds: history.longest_jitter_seconds, - }))); - } - } else { - log::warn!( - "received packet unrelated to UDP stream {} from {}", - self.stream_idx, - peer_addr - ); - continue; - } - } - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { - //receive timeout - break; - } - Err(e) => { - return Some(Err(Box::new(e))); + bytes_received, + packets_received: history.packets_received, + packets_lost: history.packets_lost, + packets_out_of_order: history.packets_out_of_order, + packets_duplicated: history.packets_duplicated, + + unbroken_sequence: history.longest_unbroken_sequence, + jitter_seconds: history.longest_jitter_seconds, + }))); } + } else { + log::warn!( + "received packet unrelated to UDP stream {} from {}", + self.stream_idx, + peer_addr + ); + continue; } } } From 508d4a74da4af27c6b7a045820d899739ff8901e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:36:24 +0800 Subject: [PATCH 05/18] rustfmt max_width = 140 --- rustfmt.toml | 1 + src/args.rs | 8 +- src/client.rs | 107 ++++--------- src/main.rs | 8 +- src/protocol/communication.rs | 42 +---- src/protocol/messaging.rs | 58 +++---- src/protocol/results.rs | 293 +++++++++++++--------------------- src/server.rs | 168 ++++--------------- src/stream/tcp.rs | 205 ++++++------------------ src/stream/udp.rs | 184 ++++++--------------- src/utils/cpu_affinity.rs | 5 +- 11 files changed, 304 insertions(+), 775 deletions(-) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..8449be0 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 140 diff --git a/src/args.rs b/src/args.rs index e8812df..356f43c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -50,13 +50,7 @@ pub struct Args { pub reverse: bool, /// the format in which to deplay information (json, megabit/sec, megabyte/sec) - #[arg( - short, - long, - value_enum, - value_name = "format", - default_value = "megabit" - )] + #[arg(short, long, value_enum, value_name = "format", default_value = "megabit")] pub format: Format, /// use UDP rather than TCP diff --git a/src/client.rs b/src/client.rs index 8818403..b90c633 100644 --- a/src/client.rs +++ b/src/client.rs @@ -22,14 +22,9 @@ use crate::{ args, protocol::{ communication::{receive, send, KEEPALIVE_DURATION}, - messaging::{ - prepare_begin, prepare_download_configuration, prepare_end, - prepare_upload_configuration, - }, - results::ClientDoneResult, - results::{ - IntervalResultBox, IntervalResultKind, TcpTestResults, TestResults, UdpTestResults, - }, + messaging::{prepare_begin, prepare_download_configuration, prepare_end, prepare_upload_configuration}, + results::{ClientDoneResult, ClientFailedResult}, + results::{IntervalResultBox, IntervalResultKind, TcpTestResults, TestResults, UdpTestResults}, }, stream::{tcp, udp, TestStream}, }; @@ -63,21 +58,12 @@ fn connect_to_server(address: &str, port: &u16) -> BoxResult { let server_addr = destination.to_socket_addrs()?.next(); if server_addr.is_none() { - return Err(Box::new(simple_error::simple_error!( - "unable to resolve {}", - address - ))); + return Err(Box::new(simple_error::simple_error!("unable to resolve {}", address))); } - let raw_stream = - match std::net::TcpStream::connect_timeout(&server_addr.unwrap(), CONNECT_TIMEOUT) { - Ok(s) => s, - Err(e) => { - return Err(Box::new(simple_error::simple_error!( - "unable to connect: {}", - e - ))) - } - }; + let raw_stream = match std::net::TcpStream::connect_timeout(&server_addr.unwrap(), CONNECT_TIMEOUT) { + Ok(s) => s, + Err(e) => return Err(Box::new(simple_error::simple_error!("unable to connect: {}", e))), + }; let stream = match TcpStream::from_stream(raw_stream) { Ok(s) => s, Err(e) => { @@ -89,12 +75,8 @@ fn connect_to_server(address: &str, port: &u16) -> BoxResult { }; log::info!("connected to server"); - stream - .set_nodelay(true) - .expect("cannot disable Nagle's algorithm"); - stream - .set_keepalive(Some(KEEPALIVE_DURATION)) - .expect("unable to set TCP keepalive"); + stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); + stream.set_keepalive(Some(KEEPALIVE_DURATION)).expect("unable to set TCP keepalive"); Ok(stream) } @@ -121,18 +103,10 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { let mut complete = false; //config-parsing and pre-connection setup - let mut tcp_port_pool = tcp::receiver::TcpPortPool::new( - args.tcp_port_pool.to_string(), - args.tcp6_port_pool.to_string(), - ); - let mut udp_port_pool = udp::receiver::UdpPortPool::new( - args.udp_port_pool.to_string(), - args.udp6_port_pool.to_string(), - ); - - let cpu_affinity_manager = Arc::new(Mutex::new( - crate::utils::cpu_affinity::CpuAffinityManager::new(&args.affinity)?, - )); + let mut tcp_port_pool = tcp::receiver::TcpPortPool::new(args.tcp_port_pool.to_string(), args.tcp6_port_pool.to_string()); + let mut udp_port_pool = udp::receiver::UdpPortPool::new(args.udp_port_pool.to_string(), args.udp6_port_pool.to_string()); + + let cpu_affinity_manager = Arc::new(Mutex::new(crate::utils::cpu_affinity::CpuAffinityManager::new(&args.affinity)?)); let display_json: bool; let display_bit: bool; @@ -163,16 +137,14 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { //scaffolding to track and relay the streams and stream-results associated with this test let stream_count = download_config.get("streams").unwrap().as_i64().unwrap() as usize; - let mut parallel_streams: Vec>> = - Vec::with_capacity(stream_count); + let mut parallel_streams: Vec>> = Vec::with_capacity(stream_count); let mut parallel_streams_joinhandles = Vec::with_capacity(stream_count); let (results_tx, results_rx): ( std::sync::mpsc::Sender, std::sync::mpsc::Receiver, ) = channel(); - let test_results: Mutex> = - prepare_test_results(is_udp, stream_count as u8); + let test_results: Mutex> = prepare_test_results(is_udp, stream_count as u8); //a closure used to pass results from stream-handlers to the test-result structure let mut results_handler = || -> BoxResult<()> { @@ -193,10 +165,7 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { } else { log::warn!("stream {} failed", result.get_stream_idx()); } - tr.mark_stream_done( - &result.get_stream_idx(), - result.kind() == IntervalResultKind::ClientDone, - ); + tr.mark_stream_done(&result.get_stream_idx(), result.kind() == IntervalResultKind::ClientDone); if tr.count_in_progress_streams() == 0 { complete = true; @@ -226,10 +195,7 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { if is_udp { //UDP - log::info!( - "preparing for reverse-UDP test with {} streams...", - stream_count - ); + log::info!("preparing for reverse-UDP test with {} streams...", stream_count); let test_definition = udp::UdpTestDefinition::new(&download_config)?; for stream_idx in 0..stream_count { @@ -246,10 +212,7 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { } } else { //TCP - log::info!( - "preparing for reverse-TCP test with {} streams...", - stream_count - ); + log::info!("preparing for reverse-TCP test with {} streams...", stream_count); let test_definition = tcp::TcpTestDefinition::new(&download_config)?; for stream_idx in 0..stream_count { @@ -410,11 +373,9 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { }, Err(e) => { log::error!("unable to process stream: {}", e); - match c_results_tx.send(Box::new( - crate::protocol::results::ClientFailedResult { - stream_idx: test.get_idx(), - }, - )) { + match c_results_tx.send(Box::new(ClientFailedResult { + stream_idx: test.get_idx(), + })) { Ok(_) => (), Err(e) => { log::error!("unable to report interval-failed-result: {}", e) @@ -457,8 +418,7 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { "receive" | "send" => { //receive/send-results from the server if !display_json { - let result = - crate::protocol::results::interval_result_from_json(payload.clone())?; + let result = crate::protocol::results::interval_result_from_json(payload.clone())?; println!("{}", result.to_string(display_bit)); } let mut tr = test_results.lock().unwrap(); @@ -480,9 +440,7 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { _ => (), //not possible } tr.mark_stream_done_server(&(idx64 as u8)); - if tr.count_in_progress_streams() == 0 - && tr.count_in_progress_streams_server() == 0 - { + if tr.count_in_progress_streams() == 0 && tr.count_in_progress_streams_server() == 0 { //all data gathered from both sides kill(); } @@ -517,9 +475,7 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { let mut stream = match (*ps).lock() { Ok(guard) => guard, Err(poisoned) => { - log::error!( - "a stream-handler was poisoned; this indicates some sort of logic error" - ); + log::error!("a stream-handler was poisoned; this indicates some sort of logic error"); poisoned.into_inner() } }; @@ -601,23 +557,14 @@ pub fn kill() -> bool { } fn start_kill_timer() { unsafe { - KILL_TIMER_RELATIVE_START_TIME = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs_f64(); + KILL_TIMER_RELATIVE_START_TIME = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64(); } } fn is_alive() -> bool { unsafe { if KILL_TIMER_RELATIVE_START_TIME != 0.0 { //initialised - if SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs_f64() - - KILL_TIMER_RELATIVE_START_TIME - >= KILL_TIMEOUT - { + if SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64() - KILL_TIMER_RELATIVE_START_TIME >= KILL_TIMEOUT { return false; } } diff --git a/src/main.rs b/src/main.rs index e3fdb22..12cdf80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,9 +39,7 @@ fn main() { log::debug!("registering SIGINT handler..."); ctrlc2::set_handler(move || { if server::kill() { - log::warn!( - "shutdown requested; please allow a moment for any in-progress tests to stop" - ); + log::warn!("shutdown requested; please allow a moment for any in-progress tests to stop"); } else { log::warn!("forcing shutdown immediately"); std::process::exit(3); @@ -60,9 +58,7 @@ fn main() { log::debug!("registering SIGINT handler..."); ctrlc2::set_handler(move || { if client::kill() { - log::warn!( - "shutdown requested; please allow a moment for any in-progress tests to stop" - ); + log::warn!("shutdown requested; please allow a moment for any in-progress tests to stop"); } else { log::warn!("forcing shutdown immediately"); std::process::exit(3); diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index 7fdcc80..1aec987 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -51,21 +51,12 @@ pub fn send(stream: &mut TcpStream, message: &serde_json::Value) -> BoxResult<() } /// receives the length-count of a pending message over a client-server communications stream -fn receive_length( - stream: &mut TcpStream, - alive_check: fn() -> bool, - results_handler: &mut dyn FnMut() -> BoxResult<()>, -) -> BoxResult { +fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_handler: &mut dyn FnMut() -> BoxResult<()>) -> BoxResult { let mut cloned_stream = stream.try_clone()?; let mio_token = Token(0); let poll = Poll::new()?; - poll.register( - &cloned_stream, - mio_token, - Ready::readable(), - PollOpt::edge(), - )?; + poll.register(&cloned_stream, mio_token, Ready::readable(), PollOpt::edge())?; let mut events = Events::with_capacity(1); //only interacting with one stream let mut length_bytes_read = 0; @@ -93,20 +84,14 @@ fn receive_length( return Err(Box::new(simple_error::simple_error!("connection lost"))); } else { //shutting down; a disconnect is expected - return Err(Box::new(simple_error::simple_error!( - "local shutdown requested" - ))); + return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); } } length_bytes_read += size; if length_bytes_read == 2 { let length = u16::from_be_bytes(length_spec); - log::debug!( - "received length-spec of {} from {}", - length, - stream.peer_addr()? - ); + log::debug!("received length-spec of {} from {}", length, stream.peer_addr()?); return Ok(length); } else { log::debug!("received partial length-spec from {}", stream.peer_addr()?); @@ -114,9 +99,7 @@ fn receive_length( } } } - Err(Box::new(simple_error::simple_error!( - "system shutting down" - ))) + Err(Box::new(simple_error::simple_error!("system shutting down"))) } /// receives the data-value of a pending message over a client-server communications stream fn receive_payload( @@ -129,12 +112,7 @@ fn receive_payload( let mio_token = Token(0); let poll = Poll::new()?; - poll.register( - &cloned_stream, - mio_token, - Ready::readable(), - PollOpt::edge(), - )?; + poll.register(&cloned_stream, mio_token, Ready::readable(), PollOpt::edge())?; let mut events = Events::with_capacity(1); //only interacting with one stream let mut bytes_read = 0; @@ -162,9 +140,7 @@ fn receive_payload( return Err(Box::new(simple_error::simple_error!("connection lost"))); } else { // shutting down; a disconnect is expected - return Err(Box::new(simple_error::simple_error!( - "local shutdown requested" - ))); + return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); } } @@ -185,9 +161,7 @@ fn receive_payload( } } } - Err(Box::new(simple_error::simple_error!( - "system shutting down" - ))) + Err(Box::new(simple_error::simple_error!("system shutting down"))) } /// handles the full process of retrieving a message from a client-server communications stream pub fn receive( diff --git a/src/protocol/messaging.rs b/src/protocol/messaging.rs index d75cbce..1fbf206 100644 --- a/src/protocol/messaging.rs +++ b/src/protocol/messaging.rs @@ -83,12 +83,7 @@ fn prepare_configuration_tcp_upload( } /// prepares a message used to describe the download role of a TCP test -fn prepare_configuration_tcp_download( - test_id: &[u8; 16], - streams: u8, - length: usize, - receive_buffer: u32, -) -> serde_json::Value { +fn prepare_configuration_tcp_download(test_id: &[u8; 16], streams: u8, length: usize, receive_buffer: u32) -> serde_json::Value { serde_json::json!({ "kind": "configuration", @@ -132,12 +127,7 @@ fn prepare_configuration_udp_upload( } /// prepares a message used to describe the download role of a UDP test -fn prepare_configuration_udp_download( - test_id: &[u8; 16], - streams: u8, - length: u16, - receive_buffer: u32, -) -> serde_json::Value { +fn prepare_configuration_udp_download(test_id: &[u8; 16], streams: u8, length: u16, receive_buffer: u32) -> serde_json::Value { serde_json::json!({ "kind": "configuration", @@ -197,10 +187,7 @@ fn calculate_length_udp(length: u16) -> u16 { } /// prepares a message used to describe the upload role in a test -pub fn prepare_upload_configuration( - args: &crate::args::Args, - test_id: &[u8; 16], -) -> BoxResult { +pub fn prepare_upload_configuration(args: &crate::args::Args, test_id: &[u8; 16]) -> BoxResult { let parallel_streams: u8 = args.parallel as u8; let mut seconds: f32 = args.time as f32; let mut send_interval: f32 = args.send_interval as f32; @@ -254,20 +241,14 @@ pub fn prepare_upload_configuration( } Err(_) => { //invalid input; fall back to 1mbps - log::warn!( - "invalid bandwidth: {}; setting value to 1mbps", - args.bandwidth - ); + log::warn!("invalid bandwidth: {}; setting value to 1mbps", args.bandwidth); bandwidth = 125000; } } } None => { //invalid input; fall back to 1mbps - log::warn!( - "invalid bandwidth: {}; setting value to 1mbps", - args.bandwidth - ); + log::warn!("invalid bandwidth: {}; setting value to 1mbps", args.bandwidth); bandwidth = 125000; } } @@ -288,7 +269,11 @@ pub fn prepare_upload_configuration( length = 1024; } if send_buffer != 0 && send_buffer < length { - log::warn!("requested send-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", send_buffer, length * 2); + log::warn!( + "requested send-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", + send_buffer, + length * 2 + ); send_buffer = length * 2; } Ok(prepare_configuration_udp_upload( @@ -306,7 +291,11 @@ pub fn prepare_upload_configuration( length = 32 * 1024; } if send_buffer != 0 && send_buffer < length { - log::warn!("requested send-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", send_buffer, length * 2); + log::warn!( + "requested send-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", + send_buffer, + length * 2 + ); send_buffer = length * 2; } @@ -325,10 +314,7 @@ pub fn prepare_upload_configuration( } } /// prepares a message used to describe the download role in a test -pub fn prepare_download_configuration( - args: &crate::args::Args, - test_id: &[u8; 16], -) -> BoxResult { +pub fn prepare_download_configuration(args: &crate::args::Args, test_id: &[u8; 16]) -> BoxResult { let parallel_streams: u8 = args.parallel as u8; let mut length: u32 = args.length as u32; let mut receive_buffer: u32 = args.receive_buffer as u32; @@ -339,7 +325,11 @@ pub fn prepare_download_configuration( length = 1024; } if receive_buffer != 0 && receive_buffer < length { - log::warn!("requested receive-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", receive_buffer, length * 2); + log::warn!( + "requested receive-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", + receive_buffer, + length * 2 + ); receive_buffer = length * 2; } Ok(prepare_configuration_udp_download( @@ -354,7 +344,11 @@ pub fn prepare_download_configuration( length = 32 * 1024; } if receive_buffer != 0 && receive_buffer < length { - log::warn!("requested receive-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", receive_buffer, length * 2); + log::warn!( + "requested receive-buffer, {}, is too small to hold the data to be exchanged; it will be increased to {}", + receive_buffer, + length * 2 + ); receive_buffer = length * 2; } Ok(prepare_configuration_tcp_download( diff --git a/src/protocol/results.rs b/src/protocol/results.rs index d59f833..168ae0c 100644 --- a/src/protocol/results.rs +++ b/src/protocol/results.rs @@ -86,7 +86,7 @@ impl IntervalResult for ClientDoneResult { fn to_string(&self, _bit: bool) -> String { format!( "----------\n\ - End of stream from client | stream: {}", + End of stream from client | stream: {}", self.stream_idx, ) } @@ -114,7 +114,7 @@ impl IntervalResult for ServerDoneResult { fn to_string(&self, _bit: bool) -> String { format!( "----------\n\ - End of stream from server | stream: {}", + End of stream from server | stream: {}", self.stream_idx, ) } @@ -143,7 +143,7 @@ impl IntervalResult for ClientFailedResult { fn to_string(&self, _bit: bool) -> String { format!( "----------\n\ - Failure in client stream | stream: {}", + Failure in client stream | stream: {}", self.stream_idx, ) } @@ -171,7 +171,7 @@ impl IntervalResult for ServerFailedResult { fn to_string(&self, _bit: bool) -> String { format!( "----------\n\ - Failure in server stream | stream: {}", + Failure in server stream | stream: {}", self.stream_idx, ) } @@ -226,17 +226,14 @@ impl IntervalResult for TcpReceiveResult { let bytes_per_second = self.bytes_received as f32 / duration_divisor; let throughput = match bit { - true => format!( - "megabits/second: {:.3}", - bytes_per_second / (1_000_000.00 / 8.0) - ), + true => format!("megabits/second: {:.3}", bytes_per_second / (1_000_000.00 / 8.0)), false => format!("megabytes/second: {:.3}", bytes_per_second / 1_000_000.00), }; format!( "----------\n\ - TCP receive result over {:.2}s | stream: {}\n\ - bytes: {} | per second: {:.3} | {}", + TCP receive result over {:.2}s | stream: {}\n\ + bytes: {} | per second: {:.3} | {}", self.duration, self.stream_idx, self.bytes_received, bytes_per_second, throughput, ) } @@ -298,24 +295,18 @@ impl IntervalResult for TcpSendResult { let bytes_per_second = self.bytes_sent as f32 / duration_divisor; let throughput = match bit { - true => format!( - "megabits/second: {:.3}", - bytes_per_second / (1_000_000.00 / 8.0) - ), + true => format!("megabits/second: {:.3}", bytes_per_second / (1_000_000.00 / 8.0)), false => format!("megabytes/second: {:.3}", bytes_per_second / 1_000_000.00), }; let mut output = format!( "----------\n\ - TCP send result over {:.2}s | stream: {}\n\ - bytes: {} | per second: {:.3} | {}", + TCP send result over {:.2}s | stream: {}\n\ + bytes: {} | per second: {:.3} | {}", self.duration, self.stream_idx, self.bytes_sent, bytes_per_second, throughput, ); if self.sends_blocked > 0 { - output.push_str(&format!( - "\nstalls due to full send-buffer: {}", - self.sends_blocked - )); + output.push_str(&format!("\nstalls due to full send-buffer: {}", self.sends_blocked)); } output } @@ -377,20 +368,25 @@ impl IntervalResult for UdpReceiveResult { let bytes_per_second = self.bytes_received as f32 / duration_divisor; let throughput = match bit { - true => format!( - "megabits/second: {:.3}", - bytes_per_second / (1_000_000.00 / 8.0) - ), + true => format!("megabits/second: {:.3}", bytes_per_second / (1_000_000.00 / 8.0)), false => format!("megabytes/second: {:.3}", bytes_per_second / 1_000_000.00), }; - let mut output = format!("----------\n\ - UDP receive result over {:.2}s | stream: {}\n\ - bytes: {} | per second: {:.3} | {}\n\ - packets: {} | lost: {} | out-of-order: {} | duplicate: {} | per second: {:.3}", - self.duration, self.stream_idx, - self.bytes_received, bytes_per_second, throughput, - self.packets_received, self.packets_lost, self.packets_out_of_order, self.packets_duplicated, self.packets_received as f32 / duration_divisor, + let mut output = format!( + "----------\n\ + UDP receive result over {:.2}s | stream: {}\n\ + bytes: {} | per second: {:.3} | {}\n\ + packets: {} | lost: {} | out-of-order: {} | duplicate: {} | per second: {:.3}", + self.duration, + self.stream_idx, + self.bytes_received, + bytes_per_second, + throughput, + self.packets_received, + self.packets_lost, + self.packets_out_of_order, + self.packets_duplicated, + self.packets_received as f32 / duration_divisor, ); if self.jitter_seconds.is_some() { output.push_str(&format!( @@ -460,18 +456,15 @@ impl IntervalResult for UdpSendResult { let bytes_per_second = self.bytes_sent as f32 / duration_divisor; let throughput = match bit { - true => format!( - "megabits/second: {:.3}", - bytes_per_second / (1_000_000.00 / 8.0) - ), + true => format!("megabits/second: {:.3}", bytes_per_second / (1_000_000.00 / 8.0)), false => format!("megabytes/second: {:.3}", bytes_per_second / 1_000_000.00), }; let mut output = format!( "----------\n\ - UDP send result over {:.2}s | stream: {}\n\ - bytes: {} | per second: {:.3} | {}\n\ - packets: {} per second: {:.3}", + UDP send result over {:.2}s | stream: {}\n\ + bytes: {} | per second: {:.3} | {}\n\ + packets: {} per second: {:.3}", self.duration, self.stream_idx, self.bytes_sent, @@ -481,10 +474,7 @@ impl IntervalResult for UdpSendResult { self.packets_sent as f32 / duration_divisor, ); if self.sends_blocked > 0 { - output.push_str(&format!( - "\nstalls due to full send-buffer: {}", - self.sends_blocked - )); + output.push_str(&format!("\nstalls due to full send-buffer: {}", self.sends_blocked)); } output } @@ -499,49 +489,31 @@ pub fn interval_result_from_json(value: serde_json::Value) -> BoxResult match kind { "receive" => Ok(Box::new(TcpReceiveResult::from_json(value)?)), "send" => Ok(Box::new(TcpSendResult::from_json(value)?)), - _ => Err(Box::new(simple_error::simple_error!( - "unsupported interval-result kind: {}", - kind - ))), + _ => Err(Box::new(simple_error::simple_error!("unsupported interval-result kind: {}", kind))), }, - None => Err(Box::new(simple_error::simple_error!( - "interval-result's kind is not a string" - ))), + None => Err(Box::new(simple_error::simple_error!("interval-result's kind is not a string"))), }, - None => Err(Box::new(simple_error::simple_error!( - "interval-result has no kind" - ))), + None => Err(Box::new(simple_error::simple_error!("interval-result has no kind"))), }, "udp" => match value.get("kind") { Some(k) => match k.as_str() { Some(kind) => match kind { "receive" => Ok(Box::new(UdpReceiveResult::from_json(value)?)), "send" => Ok(Box::new(UdpSendResult::from_json(value)?)), - _ => Err(Box::new(simple_error::simple_error!( - "unsupported interval-result kind: {}", - kind - ))), + _ => Err(Box::new(simple_error::simple_error!("unsupported interval-result kind: {}", kind))), }, - None => Err(Box::new(simple_error::simple_error!( - "interval-result's kind is not a string" - ))), + None => Err(Box::new(simple_error::simple_error!("interval-result's kind is not a string"))), }, - None => Err(Box::new(simple_error::simple_error!( - "interval-result has no kind" - ))), + None => Err(Box::new(simple_error::simple_error!("interval-result has no kind"))), }, _ => Err(Box::new(simple_error::simple_error!( "unsupported interval-result family: {}", family ))), }, - None => Err(Box::new(simple_error::simple_error!( - "interval-result's family is not a string" - ))), + None => Err(Box::new(simple_error::simple_error!("interval-result's family is not a string"))), }, - None => Err(Box::new(simple_error::simple_error!( - "interval-result has no family" - ))), + None => Err(Box::new(simple_error::simple_error!("interval-result has no family"))), } } @@ -565,8 +537,7 @@ impl StreamResults for TcpStreamResults { Ok(()) } "receive" => { - self.receive_results - .push(TcpReceiveResult::from_json(value)?); + self.receive_results.push(TcpReceiveResult::from_json(value)?); Ok(()) } _ => Err(Box::new(simple_error::simple_error!( @@ -574,13 +545,9 @@ impl StreamResults for TcpStreamResults { kind ))), }, - None => Err(Box::new(simple_error::simple_error!( - "kind must be a string for TCP stream-result" - ))), + None => Err(Box::new(simple_error::simple_error!("kind must be a string for TCP stream-result"))), }, - None => Err(Box::new(simple_error::simple_error!( - "no kind specified for TCP stream-result" - ))), + None => Err(Box::new(simple_error::simple_error!("no kind specified for TCP stream-result"))), } } @@ -639,8 +606,7 @@ impl StreamResults for UdpStreamResults { Ok(()) } "receive" => { - self.receive_results - .push(UdpReceiveResult::from_json(value)?); + self.receive_results.push(UdpReceiveResult::from_json(value)?); Ok(()) } _ => Err(Box::new(simple_error::simple_error!( @@ -648,13 +614,9 @@ impl StreamResults for UdpStreamResults { kind ))), }, - None => Err(Box::new(simple_error::simple_error!( - "kind must be a string for UDP stream-result" - ))), + None => Err(Box::new(simple_error::simple_error!("kind must be a string for UDP stream-result"))), }, - None => Err(Box::new(simple_error::simple_error!( - "no kind specified for UDP stream-result" - ))), + None => Err(Box::new(simple_error::simple_error!("no kind specified for UDP stream-result"))), } } @@ -699,8 +661,7 @@ impl StreamResults for UdpStreamResults { packets_duplicated += rr.packets_duplicated; if rr.jitter_seconds.is_some() { - jitter_weight += - (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); + jitter_weight += (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); unbroken_sequence_count += rr.unbroken_sequence; jitter_calculated = true; @@ -713,7 +674,6 @@ impl StreamResults for UdpStreamResults { "bytes_sent": bytes_sent, "packets_sent": packets_sent, - "duration_receive": duration_receive, "bytes_received": bytes_received, @@ -726,8 +686,7 @@ impl StreamResults for UdpStreamResults { summary["framed_packet_size"] = serde_json::json!(bytes_sent / packets_sent); } if jitter_calculated { - summary["jitter_average"] = - serde_json::json!(jitter_weight / (unbroken_sequence_count as f64)); + summary["jitter_average"] = serde_json::json!(jitter_weight / (unbroken_sequence_count as f64)); summary["jitter_packets_consecutive"] = serde_json::json!(unbroken_sequence_count); } @@ -768,14 +727,7 @@ pub trait TestResults { common_config: serde_json::Value, additional_config: serde_json::Value, ) -> String { - serde_json::to_string_pretty(&self.to_json( - omit_seconds, - upload_config, - download_config, - common_config, - additional_config, - )) - .unwrap() + serde_json::to_string_pretty(&self.to_json(omit_seconds, upload_config, download_config, common_config, additional_config)).unwrap() } //produces test-results in tabular form @@ -849,26 +801,18 @@ impl TestResults for TcpTestResults { idx64 ))), }, - None => Err(Box::new(simple_error::simple_error!( - "stream-index is not an integer" - ))), + None => Err(Box::new(simple_error::simple_error!("stream-index is not an integer"))), }, - None => Err(Box::new(simple_error::simple_error!( - "no stream-index specified" - ))), + None => Err(Box::new(simple_error::simple_error!("no stream-index specified"))), }, _ => Err(Box::new(simple_error::simple_error!( "unsupported family for TCP stream-result: {}", family ))), }, - None => Err(Box::new(simple_error::simple_error!( - "kind must be a string for TCP stream-result" - ))), + None => Err(Box::new(simple_error::simple_error!("kind must be a string for TCP stream-result"))), }, - None => Err(Box::new(simple_error::simple_error!( - "no kind specified for TCP stream-result" - ))), + None => Err(Box::new(simple_error::simple_error!("no kind specified for TCP stream-result"))), } } @@ -983,14 +927,8 @@ impl TestResults for TcpTestResults { }; let send_bytes_per_second = bytes_sent as f64 / send_duration_divisor; let send_throughput = match bit { - true => format!( - "megabits/second: {:.3}", - send_bytes_per_second / (1_000_000.00 / 8.0) - ), - false => format!( - "megabytes/second: {:.3}", - send_bytes_per_second / 1_000_000.00 - ), + true => format!("megabits/second: {:.3}", send_bytes_per_second / (1_000_000.00 / 8.0)), + false => format!("megabytes/second: {:.3}", send_bytes_per_second / 1_000_000.00), }; let total_send_throughput = match bit { true => format!( @@ -1011,14 +949,8 @@ impl TestResults for TcpTestResults { }; let receive_bytes_per_second = bytes_received as f64 / receive_duration_divisor; let receive_throughput = match bit { - true => format!( - "megabits/second: {:.3}", - receive_bytes_per_second / (1_000_000.00 / 8.0) - ), - false => format!( - "megabytes/second: {:.3}", - receive_bytes_per_second / 1_000_000.00 - ), + true => format!("megabits/second: {:.3}", receive_bytes_per_second / (1_000_000.00 / 8.0)), + false => format!("megabytes/second: {:.3}", receive_bytes_per_second / 1_000_000.00), }; let total_receive_throughput = match bit { true => format!( @@ -1033,13 +965,13 @@ impl TestResults for TcpTestResults { let mut output = format!( "==========\n\ - TCP send result over {:.2}s | streams: {}\n\ - stream-average bytes per second: {:.3} | {}\n\ - total bytes: {} | per second: {:.3} | {}\n\ - ==========\n\ - TCP receive result over {:.2}s | streams: {}\n\ - stream-average bytes per second: {:.3} | {}\n\ - total bytes: {} | per second: {:.3} | {}", + TCP send result over {:.2}s | streams: {}\n\ + stream-average bytes per second: {:.3} | {}\n\ + total bytes: {} | per second: {:.3} | {}\n\ + ==========\n\ + TCP receive result over {:.2}s | streams: {}\n\ + stream-average bytes per second: {:.3} | {}\n\ + total bytes: {} | per second: {:.3} | {}", stream_send_durations[stream_send_durations.len() - 1], stream_count, send_bytes_per_second, @@ -1133,26 +1065,18 @@ impl TestResults for UdpTestResults { idx64 ))), }, - None => Err(Box::new(simple_error::simple_error!( - "stream-index is not an integer" - ))), + None => Err(Box::new(simple_error::simple_error!("stream-index is not an integer"))), }, - None => Err(Box::new(simple_error::simple_error!( - "no stream-index specified" - ))), + None => Err(Box::new(simple_error::simple_error!("no stream-index specified"))), }, _ => Err(Box::new(simple_error::simple_error!( "unsupported family for UDP stream-result: {}", family ))), }, - None => Err(Box::new(simple_error::simple_error!( - "kind must be a string for UDP stream-result" - ))), + None => Err(Box::new(simple_error::simple_error!("kind must be a string for UDP stream-result"))), }, - None => Err(Box::new(simple_error::simple_error!( - "no kind specified for UDP stream-result" - ))), + None => Err(Box::new(simple_error::simple_error!("no kind specified for UDP stream-result"))), } } @@ -1212,8 +1136,7 @@ impl TestResults for UdpTestResults { packets_duplicated += rr.packets_duplicated; if rr.jitter_seconds.is_some() { - jitter_weight += - (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); + jitter_weight += (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); unbroken_sequence_count += rr.unbroken_sequence; jitter_calculated = true; @@ -1227,7 +1150,6 @@ impl TestResults for UdpTestResults { "bytes_sent": bytes_sent, "packets_sent": packets_sent, - "duration_receive": duration_receive, "bytes_received": bytes_received, @@ -1240,8 +1162,7 @@ impl TestResults for UdpTestResults { summary["framed_packet_size"] = serde_json::json!(bytes_sent / packets_sent); } if jitter_calculated { - summary["jitter_average"] = - serde_json::json!(jitter_weight / (unbroken_sequence_count as f64)); + summary["jitter_average"] = serde_json::json!(jitter_weight / (unbroken_sequence_count as f64)); summary["jitter_packets_consecutive"] = serde_json::json!(unbroken_sequence_count); } @@ -1310,8 +1231,7 @@ impl TestResults for UdpTestResults { packets_duplicated += rr.packets_duplicated; if rr.jitter_seconds.is_some() { - jitter_weight += - (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); + jitter_weight += (rr.unbroken_sequence as f64) * (rr.jitter_seconds.unwrap() as f64); unbroken_sequence_count += rr.unbroken_sequence; jitter_calculated = true; @@ -1329,14 +1249,8 @@ impl TestResults for UdpTestResults { }; let send_bytes_per_second = bytes_sent as f64 / send_duration_divisor; let send_throughput = match bit { - true => format!( - "megabits/second: {:.3}", - send_bytes_per_second / (1_000_000.00 / 8.0) - ), - false => format!( - "megabytes/second: {:.3}", - send_bytes_per_second / 1_000_000.00 - ), + true => format!("megabits/second: {:.3}", send_bytes_per_second / (1_000_000.00 / 8.0)), + false => format!("megabytes/second: {:.3}", send_bytes_per_second / 1_000_000.00), }; let total_send_throughput = match bit { true => format!( @@ -1357,14 +1271,8 @@ impl TestResults for UdpTestResults { }; let receive_bytes_per_second = bytes_received as f64 / receive_duration_divisor; let receive_throughput = match bit { - true => format!( - "megabits/second: {:.3}", - receive_bytes_per_second / (1_000_000.00 / 8.0) - ), - false => format!( - "megabytes/second: {:.3}", - receive_bytes_per_second / 1_000_000.00 - ), + true => format!("megabits/second: {:.3}", receive_bytes_per_second / (1_000_000.00 / 8.0)), + false => format!("megabytes/second: {:.3}", receive_bytes_per_second / 1_000_000.00), }; let total_receive_throughput = match bit { true => format!( @@ -1384,24 +1292,39 @@ impl TestResults for UdpTestResults { } else { packets_sent as f64 }; - let mut output = format!("==========\n\ - UDP send result over {:.2}s | streams: {}\n\ - stream-average bytes per second: {:.3} | {}\n\ - total bytes: {} | per second: {:.3} | {}\n\ - packets: {} per second: {:.3}\n\ - ==========\n\ - UDP receive result over {:.2}s | streams: {}\n\ - stream-average bytes per second: {:.3} | {}\n\ - total bytes: {} | per second: {:.3} | {}\n\ - packets: {} | lost: {} ({:.1}%) | out-of-order: {} | duplicate: {} | per second: {:.3}", - stream_send_durations[stream_send_durations.len() - 1], stream_count, - send_bytes_per_second, send_throughput, - bytes_sent, send_bytes_per_second * stream_count as f64, total_send_throughput, - packets_sent, (packets_sent as f64 / send_duration_divisor) * stream_count as f64, - stream_receive_durations[stream_receive_durations.len() - 1], stream_count, - receive_bytes_per_second, receive_throughput, - bytes_received, receive_bytes_per_second * stream_count as f64, total_receive_throughput, - packets_received, packets_lost, (packets_lost as f64 / packets_sent_divisor) * 100.0, packets_out_of_order, packets_duplicated, (packets_received as f64 / receive_duration_divisor) * stream_count as f64, + let mut output = format!( + "==========\n\ + UDP send result over {:.2}s | streams: {}\n\ + stream-average bytes per second: {:.3} | {}\n\ + total bytes: {} | per second: {:.3} | {}\n\ + packets: {} per second: {:.3}\n\ + ==========\n\ + UDP receive result over {:.2}s | streams: {}\n\ + stream-average bytes per second: {:.3} | {}\n\ + total bytes: {} | per second: {:.3} | {}\n\ + packets: {} | lost: {} ({:.1}%) | out-of-order: {} | duplicate: {} | per second: {:.3}", + stream_send_durations[stream_send_durations.len() - 1], + stream_count, + send_bytes_per_second, + send_throughput, + bytes_sent, + send_bytes_per_second * stream_count as f64, + total_send_throughput, + packets_sent, + (packets_sent as f64 / send_duration_divisor) * stream_count as f64, + stream_receive_durations[stream_receive_durations.len() - 1], + stream_count, + receive_bytes_per_second, + receive_throughput, + bytes_received, + receive_bytes_per_second * stream_count as f64, + total_receive_throughput, + packets_received, + packets_lost, + (packets_lost as f64 / packets_sent_divisor) * 100.0, + packets_out_of_order, + packets_duplicated, + (packets_received as f64 / receive_duration_divisor) * stream_count as f64, ); if jitter_calculated { output.push_str(&format!( diff --git a/src/server.rs b/src/server.rs index 35e02ff..61fc9bc 100644 --- a/src/server.rs +++ b/src/server.rs @@ -87,48 +87,21 @@ fn handle_client( match kind.as_str().unwrap() { "configuration" => { //we either need to connect streams to the client or prepare to receive connections - if payload - .get("role") - .unwrap_or(&serde_json::json!("download")) - .as_str() - .unwrap() - == "download" - { - log::info!( - "[{}] running in forward-mode: server will be receiving data", - &peer_addr - ); - - let stream_count = payload - .get("streams") - .unwrap_or(&serde_json::json!(1)) - .as_i64() - .unwrap(); + if payload.get("role").unwrap_or(&serde_json::json!("download")).as_str().unwrap() == "download" { + log::info!("[{}] running in forward-mode: server will be receiving data", &peer_addr); + + let stream_count = payload.get("streams").unwrap_or(&serde_json::json!(1)).as_i64().unwrap(); //since we're receiving data, we're also responsible for letting the client know where to send it let mut stream_ports = Vec::with_capacity(stream_count as usize); - if payload - .get("family") - .unwrap_or(&serde_json::json!("tcp")) - .as_str() - .unwrap() - == "udp" - { - log::info!( - "[{}] preparing for UDP test with {} streams...", - &peer_addr, - stream_count - ); + if payload.get("family").unwrap_or(&serde_json::json!("tcp")).as_str().unwrap() == "udp" { + log::info!("[{}] preparing for UDP test with {} streams...", &peer_addr, stream_count); let mut c_udp_port_pool = udp_port_pool.lock().unwrap(); let test_definition = udp::UdpTestDefinition::new(&payload)?; for stream_idx in 0..stream_count { - log::debug!( - "[{}] preparing UDP-receiver for stream {}...", - &peer_addr, - stream_idx - ); + log::debug!("[{}] preparing UDP-receiver for stream {}...", &peer_addr, stream_idx); let test = udp::receiver::UdpReceiver::new( test_definition.clone(), &(stream_idx as u8), @@ -141,21 +114,13 @@ fn handle_client( } } else { //TCP - log::info!( - "[{}] preparing for TCP test with {} streams...", - &peer_addr, - stream_count - ); + log::info!("[{}] preparing for TCP test with {} streams...", &peer_addr, stream_count); let mut c_tcp_port_pool = tcp_port_pool.lock().unwrap(); let test_definition = tcp::TcpTestDefinition::new(&payload)?; for stream_idx in 0..stream_count { - log::debug!( - "[{}] preparing TCP-receiver for stream {}...", - &peer_addr, - stream_idx - ); + log::debug!("[{}] preparing TCP-receiver for stream {}...", &peer_addr, stream_idx); let test = tcp::receiver::TcpReceiver::new( test_definition.clone(), &(stream_idx as u8), @@ -172,68 +137,35 @@ fn handle_client( send(stream, &prepare_connect(&stream_ports))?; } else { //upload - log::info!( - "[{}] running in reverse-mode: server will be uploading data", - &peer_addr - ); + log::info!("[{}] running in reverse-mode: server will be uploading data", &peer_addr); let stream_ports = payload.get("stream_ports").unwrap().as_array().unwrap(); - if payload - .get("family") - .unwrap_or(&serde_json::json!("tcp")) - .as_str() - .unwrap() - == "udp" - { - log::info!( - "[{}] preparing for UDP test with {} streams...", - &peer_addr, - stream_ports.len() - ); + if payload.get("family").unwrap_or(&serde_json::json!("tcp")).as_str().unwrap() == "udp" { + log::info!("[{}] preparing for UDP test with {} streams...", &peer_addr, stream_ports.len()); let test_definition = udp::UdpTestDefinition::new(&payload)?; for (stream_idx, port) in stream_ports.iter().enumerate() { - log::debug!( - "[{}] preparing UDP-sender for stream {}...", - &peer_addr, - stream_idx - ); + log::debug!("[{}] preparing UDP-sender for stream {}...", &peer_addr, stream_idx); let test = udp::sender::UdpSender::new( test_definition.clone(), &(stream_idx as u8), &0, &peer_addr.ip(), &(port.as_i64().unwrap_or(0) as u16), - &(payload - .get("duration") - .unwrap_or(&serde_json::json!(0.0)) - .as_f64() - .unwrap() as f32), - &(payload - .get("send_interval") - .unwrap_or(&serde_json::json!(1.0)) - .as_f64() - .unwrap() as f32), + &(payload.get("duration").unwrap_or(&serde_json::json!(0.0)).as_f64().unwrap() as f32), + &(payload.get("send_interval").unwrap_or(&serde_json::json!(1.0)).as_f64().unwrap() as f32), &(payload["send_buffer"].as_i64().unwrap() as usize), )?; parallel_streams.push(Arc::new(Mutex::new(test))); } } else { //TCP - log::info!( - "[{}] preparing for TCP test with {} streams...", - &peer_addr, - stream_ports.len() - ); + log::info!("[{}] preparing for TCP test with {} streams...", &peer_addr, stream_ports.len()); let test_definition = tcp::TcpTestDefinition::new(&payload)?; for (stream_idx, port) in stream_ports.iter().enumerate() { - log::debug!( - "[{}] preparing TCP-sender for stream {}...", - &peer_addr, - stream_idx - ); + log::debug!("[{}] preparing TCP-sender for stream {}...", &peer_addr, stream_idx); let test = tcp::sender::TcpSender::new( test_definition.clone(), &(stream_idx as u8), @@ -257,11 +189,7 @@ fn handle_client( if !started { //a simple guard to protect against reinitialisaion for (stream_idx, parallel_stream) in parallel_streams.iter_mut().enumerate() { - log::info!( - "[{}] beginning execution of stream {}...", - &peer_addr, - stream_idx - ); + log::info!("[{}] beginning execution of stream {}...", &peer_addr, stream_idx); let c_ps = Arc::clone(parallel_stream); let c_results_tx = results_tx.clone(); let c_cam = cpu_affinity_manager.clone(); @@ -272,11 +200,7 @@ fn handle_client( } loop { let mut test = c_ps.lock().unwrap(); - log::debug!( - "[{}] beginning test-interval for stream {}", - &peer_addr, - test.get_idx() - ); + log::debug!("[{}] beginning test-interval for stream {}", &peer_addr, test.get_idx()); let interval_result = match test.run_interval() { Some(interval_result) => interval_result, None => { @@ -284,11 +208,7 @@ fn handle_client( stream_idx: test.get_idx(), })) { Ok(_) => (), - Err(e) => log::error!( - "[{}] unable to report interval-done-result: {}", - &peer_addr, - e - ), + Err(e) => log::error!("[{}] unable to report interval-done-result: {}", &peer_addr, e), } break; } @@ -298,31 +218,17 @@ fn handle_client( Ok(ir) => match c_results_tx.send(ir) { Ok(_) => (), Err(e) => { - log::error!( - "[{}] unable to process interval-result: {}", - &peer_addr, - e - ); + log::error!("[{}] unable to process interval-result: {}", &peer_addr, e); break; } }, Err(e) => { - log::error!( - "[{}] unable to process stream: {}", - peer_addr, - e - ); - match c_results_tx.send(Box::new( - crate::protocol::results::ServerFailedResult { - stream_idx: test.get_idx(), - }, - )) { + log::error!("[{}] unable to process stream: {}", peer_addr, e); + match c_results_tx.send(Box::new(crate::protocol::results::ServerFailedResult { + stream_idx: test.get_idx(), + })) { Ok(_) => (), - Err(e) => log::error!( - "[{}] unable to report interval-failed-result: {}", - &peer_addr, - e - ), + Err(e) => log::error!("[{}] unable to report interval-failed-result: {}", &peer_addr, e), } break; } @@ -401,9 +307,7 @@ pub fn serve(args: &Args) -> BoxResult<()> { args.udp6_port_pool.to_string(), ))); - let cpu_affinity_manager = Arc::new(Mutex::new( - crate::utils::cpu_affinity::CpuAffinityManager::new(&args.affinity)?, - )); + let cpu_affinity_manager = Arc::new(Mutex::new(crate::utils::cpu_affinity::CpuAffinityManager::new(&args.affinity)?)); let client_limit: u16 = args.client_limit as u16; if client_limit > 0 { @@ -412,8 +316,8 @@ pub fn serve(args: &Args) -> BoxResult<()> { //start listening for connections let port: u16 = args.port; - let listener: TcpListener = TcpListener::bind(&SocketAddr::new(args.bind, port)) - .unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)); + let listener: TcpListener = + TcpListener::bind(&SocketAddr::new(args.bind, port)).unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)); log::info!("server listening on {}", listener.local_addr()?); let mio_token = Token(0); @@ -439,20 +343,12 @@ pub fn serve(args: &Args) -> BoxResult<()> { log::info!("connection from {}", address); - stream - .set_nodelay(true) - .expect("cannot disable Nagle's algorithm"); - stream - .set_keepalive(Some(KEEPALIVE_DURATION)) - .expect("unable to set TCP keepalive"); + stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); + stream.set_keepalive(Some(KEEPALIVE_DURATION)).expect("unable to set TCP keepalive"); let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; if client_limit > 0 && client_count > client_limit { - log::warn!( - "client-limit ({}) reached; disconnecting {}...", - client_limit, - address.to_string() - ); + log::warn!("client-limit ({}) reached; disconnecting {}...", client_limit, address.to_string()); stream.shutdown(Shutdown::Both).unwrap_or_default(); CLIENTS.fetch_sub(1, Ordering::Relaxed); } else { diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 7856779..80a57e2 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -18,13 +18,9 @@ * along with rperf. If not, see . */ -extern crate nix; - use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; -use crate::protocol::results::{ - get_unix_timestamp, IntervalResult, TcpReceiveResult, TcpSendResult, -}; +use crate::protocol::results::{get_unix_timestamp, IntervalResult, TcpReceiveResult, TcpSendResult}; use super::{parse_port_spec, TestStream, INTERVAL}; @@ -74,11 +70,7 @@ impl TcpTestDefinition { Ok(TcpTestDefinition { test_id: test_id_bytes, - bandwidth: details - .get("bandwidth") - .unwrap_or(&serde_json::json!(0.0)) - .as_f64() - .unwrap() as u64, + bandwidth: details.get("bandwidth").unwrap_or(&serde_json::json!(0.0)).as_f64().unwrap() as u64, length, }) } @@ -137,97 +129,67 @@ pub mod receiver { match peer_ip { IpAddr::V6(_) => { if self.ports_ip6.is_empty() { - return Ok(TcpListener::bind(&SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - 0, - )) - .expect("failed to bind OS-assigned IPv6 TCP socket")); + return Ok(TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)) + .expect("failed to bind OS-assigned IPv6 TCP socket")); } else { let _guard = self.lock_ip6.lock().unwrap(); for port_idx in (self.pos_ip6 + 1)..self.ports_ip6.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine - let listener_result = TcpListener::bind(&SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - self.ports_ip6[port_idx], - )); + let listener_result = + TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; return Ok(listener_result); } else { - log::warn!( - "unable to bind IPv6 TCP port {}", - self.ports_ip6[port_idx] - ); + log::warn!("unable to bind IPv6 TCP port {}", self.ports_ip6[port_idx]); } } for port_idx in 0..=self.pos_ip6 { //circle back to where the search started - let listener_result = TcpListener::bind(&SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - self.ports_ip6[port_idx], - )); + let listener_result = + TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; return Ok(listener_result); } else { - log::warn!( - "unable to bind IPv6 TCP port {}", - self.ports_ip6[port_idx] - ); + log::warn!("unable to bind IPv6 TCP port {}", self.ports_ip6[port_idx]); } } } - Err(Box::new(simple_error::simple_error!( - "unable to allocate IPv6 TCP port" - ))) + Err(Box::new(simple_error::simple_error!("unable to allocate IPv6 TCP port"))) } IpAddr::V4(_) => { if self.ports_ip4.is_empty() { - return Ok(TcpListener::bind(&SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - 0, - )) - .expect("failed to bind OS-assigned IPv4 TCP socket")); + return Ok(TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)) + .expect("failed to bind OS-assigned IPv4 TCP socket")); } else { let _guard = self.lock_ip4.lock().unwrap(); for port_idx in (self.pos_ip4 + 1)..self.ports_ip4.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine - let listener_result = TcpListener::bind(&SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - self.ports_ip4[port_idx], - )); + let listener_result = + TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; return Ok(listener_result); } else { - log::warn!( - "unable to bind IPv4 TCP port {}", - self.ports_ip4[port_idx] - ); + log::warn!("unable to bind IPv4 TCP port {}", self.ports_ip4[port_idx]); } } for port_idx in 0..=self.pos_ip4 { //circle back to where the search started - let listener_result = TcpListener::bind(&SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - self.ports_ip4[port_idx], - )); + let listener_result = + TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; return Ok(listener_result); } else { - log::warn!( - "unable to bind IPv4 TCP port {}", - self.ports_ip4[port_idx] - ); + log::warn!("unable to bind IPv4 TCP port {}", self.ports_ip4[port_idx]); } } } - Err(Box::new(simple_error::simple_error!( - "unable to allocate IPv4 TCP port" - ))) + Err(Box::new(simple_error::simple_error!("unable to allocate IPv4 TCP port"))) } } } @@ -255,11 +217,7 @@ pub mod receiver { ) -> super::BoxResult { log::debug!("binding TCP listener for stream {}...", stream_idx); let listener: TcpListener = port_pool.bind(peer_ip).expect("failed to bind TCP socket"); - log::debug!( - "bound TCP listener for stream {}: {}", - stream_idx, - listener.local_addr()? - ); + log::debug!("bound TCP listener for stream {}: {}", stream_idx, listener.local_addr()?); let mio_poll_token = Token(0); let mio_poll = Poll::new()?; @@ -279,10 +237,7 @@ pub mod receiver { } fn process_connection(&mut self) -> super::BoxResult { - log::debug!( - "preparing to receive TCP stream {} connection...", - self.stream_idx - ); + log::debug!("preparing to receive TCP stream {} connection...", self.stream_idx); let listener = self.listener.as_mut().unwrap(); let mio_token = Token(0); @@ -315,21 +270,12 @@ pub mod receiver { } }; - log::debug!( - "received TCP stream {} connection from {}", - self.stream_idx, - address - ); + log::debug!("received TCP stream {} connection from {}", self.stream_idx, address); let mut verification_stream = stream.try_clone()?; let mio_token2 = Token(0); let poll2 = Poll::new()?; - poll2.register( - &verification_stream, - mio_token2, - Ready::readable(), - PollOpt::edge(), - )?; + poll2.register(&verification_stream, mio_token2, Ready::readable(), PollOpt::edge())?; let mut buffer = [0_u8; 16]; let mut events2 = Events::with_capacity(1); @@ -346,52 +292,29 @@ pub mod receiver { } if buffer == self.test_definition.test_id { - log::debug!( - "validated TCP stream {} connection from {}", - self.stream_idx, - address - ); + log::debug!("validated TCP stream {} connection from {}", self.stream_idx, address); if !cfg!(windows) { // NOTE: features unsupported on Windows if self.receive_buffer != 0 { - log::debug!( - "setting receive-buffer to {}...", - self.receive_buffer - ); - super::setsockopt( - stream.as_raw_fd(), - super::RcvBuf, - &self.receive_buffer, - )?; + log::debug!("setting receive-buffer to {}...", self.receive_buffer); + super::setsockopt(stream.as_raw_fd(), super::RcvBuf, &self.receive_buffer)?; } } - self.mio_poll.register( - &stream, - self.mio_poll_token, - Ready::readable(), - PollOpt::edge(), - )?; + self.mio_poll + .register(&stream, self.mio_poll_token, Ready::readable(), PollOpt::edge())?; return Ok(stream); } } - log::warn!( - "could not validate TCP stream {} connection from {}", - self.stream_idx, - address - ); + log::warn!("could not validate TCP stream {} connection from {}", self.stream_idx, address); } } } - Err(Box::new(simple_error::simple_error!( - "did not receive a connection" - ))) + Err(Box::new(simple_error::simple_error!("did not receive a connection"))) } } impl super::TestStream for TcpReceiver { - fn run_interval( - &mut self, - ) -> Option>> { + fn run_interval(&mut self) -> Option>> { let mut bytes_received: u64 = 0; if self.stream.is_none() { @@ -428,11 +351,7 @@ pub mod receiver { )))); } - log::trace!( - "awaiting TCP stream {} from {}...", - self.stream_idx, - peer_addr - ); + log::trace!("awaiting TCP stream {} from {}...", self.stream_idx, peer_addr); let poll_result = self.mio_poll.poll(&mut events, Some(POLL_TIMEOUT)); if let Err(err) = poll_result { return Some(Err(Box::new(err))); @@ -504,9 +423,7 @@ pub mod receiver { Some(listener) => Ok(listener.local_addr()?.port()), None => match &self.stream { Some(stream) => Ok(stream.local_addr()?.port()), - None => Err(Box::new(simple_error::simple_error!( - "no port currently bound" - ))), + None => Err(Box::new(simple_error::simple_error!("no port currently bound"))), }, } } @@ -565,11 +482,7 @@ pub mod sender { no_delay: &bool, ) -> super::BoxResult { let mut staged_buffer = vec![0_u8; test_definition.length]; - for (i, staged_buffer_i) in staged_buffer - .iter_mut() - .enumerate() - .skip(super::TEST_HEADER_SIZE) - { + for (i, staged_buffer_i) in staged_buffer.iter_mut().enumerate().skip(super::TEST_HEADER_SIZE) { //fill the packet with a fixed sequence *staged_buffer_i = (i % 256) as u8; } @@ -597,17 +510,16 @@ pub mod sender { fn process_connection(&mut self) -> super::BoxResult { log::debug!("preparing to connect TCP stream {}...", self.stream_idx); - let raw_stream = - match std::net::TcpStream::connect_timeout(&self.socket_addr, CONNECT_TIMEOUT) { - Ok(s) => s, - Err(e) => { - return Err(Box::new(simple_error::simple_error!( - "unable to connect stream {}: {}", - self.stream_idx, - e - ))) - } - }; + let raw_stream = match std::net::TcpStream::connect_timeout(&self.socket_addr, CONNECT_TIMEOUT) { + Ok(s) => s, + Err(e) => { + return Err(Box::new(simple_error::simple_error!( + "unable to connect stream {}: {}", + self.stream_idx, + e + ))) + } + }; raw_stream.set_write_timeout(Some(WRITE_TIMEOUT))?; let stream = match TcpStream::from_stream(raw_stream) { Ok(s) => s, @@ -619,11 +531,7 @@ pub mod sender { ))) } }; - log::debug!( - "connected TCP stream {} to {}", - self.stream_idx, - stream.peer_addr()? - ); + log::debug!("connected TCP stream {} to {}", self.stream_idx, stream.peer_addr()?); if self.no_delay { log::debug!("setting no-delay..."); @@ -640,9 +548,7 @@ pub mod sender { } } impl super::TestStream for TcpSender { - fn run_interval( - &mut self, - ) -> Option>> { + fn run_interval(&mut self) -> Option>> { if self.stream.is_none() { //if still in the setup phase, connect to the receiver match self.process_connection() { @@ -658,11 +564,9 @@ pub mod sender { let interval_duration = Duration::from_secs_f32(self.send_interval); let mut interval_iteration = 0; - let bytes_to_send = - ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; + let bytes_to_send = ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; let mut bytes_to_send_remaining = bytes_to_send; - let bytes_to_send_per_interval_slice = - ((bytes_to_send as f32) * self.send_interval) as i64; + let bytes_to_send_per_interval_slice = ((bytes_to_send as f32) * self.send_interval) as i64; let mut bytes_to_send_per_interval_slice_remaining = bytes_to_send_per_interval_slice; let mut sends_blocked: u64 = 0; @@ -697,12 +601,7 @@ pub mod sender { return Some(Err(Box::new(e))); } }; - log::trace!( - "wrote {} bytes in TCP stream {} to {}", - packet_size, - self.stream_idx, - peer_addr - ); + log::trace!("wrote {} bytes in TCP stream {} to {}", packet_size, self.stream_idx, peer_addr); let bytes_written = packet_size as i64; bytes_sent += bytes_written as u64; @@ -764,9 +663,7 @@ pub mod sender { fn get_port(&self) -> super::BoxResult { match &self.stream { Some(stream) => Ok(stream.local_addr()?.port()), - None => Err(Box::new(simple_error::simple_error!( - "no stream currently exists" - ))), + None => Err(Box::new(simple_error::simple_error!("no stream currently exists"))), } } diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 1ac63fe..5ce2032 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -25,9 +25,7 @@ use std::error::Error; use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; -use crate::protocol::results::{ - get_unix_timestamp, IntervalResult, UdpReceiveResult, UdpSendResult, -}; +use crate::protocol::results::{get_unix_timestamp, IntervalResult, UdpReceiveResult, UdpSendResult}; use super::{parse_port_spec, TestStream, INTERVAL}; @@ -77,11 +75,7 @@ impl UdpTestDefinition { Ok(UdpTestDefinition { test_id: test_id_bytes, - bandwidth: details - .get("bandwidth") - .unwrap_or(&serde_json::json!(0.0)) - .as_f64() - .unwrap() as u64, + bandwidth: details.get("bandwidth").unwrap_or(&serde_json::json!(0.0)).as_f64().unwrap() as u64, length, }) } @@ -141,97 +135,67 @@ pub mod receiver { match peer_ip { IpAddr::V6(_) => { if self.ports_ip6.is_empty() { - return Ok(UdpSocket::bind(SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - 0, - )) - .expect("failed to bind OS-assigned IPv6 UDP socket")); + return Ok(UdpSocket::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)) + .expect("failed to bind OS-assigned IPv6 UDP socket")); } else { let _guard = self.lock_ip6.lock().unwrap(); for port_idx in (self.pos_ip6 + 1)..self.ports_ip6.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine - let listener_result = UdpSocket::bind(SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - self.ports_ip6[port_idx], - )); + let listener_result = + UdpSocket::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; return Ok(listener_result); } else { - log::warn!( - "unable to bind IPv6 UDP port {}", - self.ports_ip6[port_idx] - ); + log::warn!("unable to bind IPv6 UDP port {}", self.ports_ip6[port_idx]); } } for port_idx in 0..=self.pos_ip6 { //circle back to where the search started - let listener_result = UdpSocket::bind(SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - self.ports_ip6[port_idx], - )); + let listener_result = + UdpSocket::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; return Ok(listener_result); } else { - log::warn!( - "unable to bind IPv6 UDP port {}", - self.ports_ip6[port_idx] - ); + log::warn!("unable to bind IPv6 UDP port {}", self.ports_ip6[port_idx]); } } } - Err(Box::new(simple_error::simple_error!( - "unable to allocate IPv6 UDP port" - ))) + Err(Box::new(simple_error::simple_error!("unable to allocate IPv6 UDP port"))) } IpAddr::V4(_) => { if self.ports_ip4.is_empty() { - return Ok(UdpSocket::bind(SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - 0, - )) - .expect("failed to bind OS-assigned IPv4 UDP socket")); + return Ok(UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)) + .expect("failed to bind OS-assigned IPv4 UDP socket")); } else { let _guard = self.lock_ip4.lock().unwrap(); for port_idx in (self.pos_ip4 + 1)..self.ports_ip4.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine - let listener_result = UdpSocket::bind(SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - self.ports_ip4[port_idx], - )); + let listener_result = + UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; return Ok(listener_result); } else { - log::warn!( - "unable to bind IPv4 UDP port {}", - self.ports_ip4[port_idx] - ); + log::warn!("unable to bind IPv4 UDP port {}", self.ports_ip4[port_idx]); } } for port_idx in 0..=self.pos_ip4 { //circle back to where the search started - let listener_result = UdpSocket::bind(SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - self.ports_ip4[port_idx], - )); + let listener_result = + UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; return Ok(listener_result); } else { - log::warn!( - "unable to bind IPv4 UDP port {}", - self.ports_ip4[port_idx] - ); + log::warn!("unable to bind IPv4 UDP port {}", self.ports_ip4[port_idx]); } } } - Err(Box::new(simple_error::simple_error!( - "unable to allocate IPv4 UDP port" - ))) + Err(Box::new(simple_error::simple_error!("unable to allocate IPv4 UDP port"))) } } } @@ -277,11 +241,7 @@ pub mod receiver { super::setsockopt(socket.as_raw_fd(), super::RcvBuf, receive_buffer)?; } } - log::debug!( - "bound UDP receive socket for stream {}: {}", - stream_idx, - socket.local_addr()? - ); + log::debug!("bound UDP receive socket for stream {}: {}", stream_idx, socket.local_addr()?); Ok(UdpReceiver { active: true, @@ -294,11 +254,7 @@ pub mod receiver { }) } - fn process_packets_ordering( - &mut self, - packet_id: u64, - history: &mut UdpReceiverIntervalHistory, - ) -> bool { + fn process_packets_ordering(&mut self, packet_id: u64, history: &mut UdpReceiverIntervalHistory) -> bool { /* the algorithm from iperf3 provides a pretty decent approximation * for tracking lost and out-of-order packets efficiently, so it's * been minimally reimplemented here, with corrections. @@ -338,39 +294,27 @@ pub mod receiver { false } - fn process_jitter( - &mut self, - timestamp: &NaiveDateTime, - history: &mut UdpReceiverIntervalHistory, - ) { + fn process_jitter(&mut self, timestamp: &NaiveDateTime, history: &mut UdpReceiverIntervalHistory) { /* this is a pretty straightforward implementation of RFC 1889, Appendix 8 * it works on an assumption that the timestamp delta between sender and receiver * will remain effectively constant during the testing window */ - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time before UNIX epoch"); - let current_timestamp = - NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()) - .unwrap(); + let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before UNIX epoch"); + let current_timestamp = NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos()).unwrap(); let time_delta = current_timestamp - *timestamp; let time_delta_nanoseconds = match time_delta.num_nanoseconds() { Some(ns) => ns, None => { - log::warn!( - "sender and receiver clocks are too out-of-sync to calculate jitter" - ); + log::warn!("sender and receiver clocks are too out-of-sync to calculate jitter"); return; } }; if history.unbroken_sequence > 1 { //do jitter calculation - let delta_seconds = - (time_delta_nanoseconds - history.previous_time_delta_nanoseconds).abs() as f32 - / 1_000_000_000.00; + let delta_seconds = (time_delta_nanoseconds - history.previous_time_delta_nanoseconds).abs() as f32 / 1_000_000_000.00; if history.unbroken_sequence > 2 { //normal jitter-calculation, per the RFC @@ -386,11 +330,7 @@ pub mod receiver { history.previous_time_delta_nanoseconds = time_delta_nanoseconds; } - fn process_packet( - &mut self, - packet: &[u8], - history: &mut UdpReceiverIntervalHistory, - ) -> bool { + fn process_packet(&mut self, packet: &[u8], history: &mut UdpReceiverIntervalHistory) -> bool { //the first sixteen bytes are the test's ID if packet[0..16] != self.test_definition.test_id { return false; @@ -407,8 +347,7 @@ pub mod receiver { let origin_seconds = i64::from_be_bytes(packet[24..32].try_into().unwrap()); //and the following four are the number of nanoseconds since the UNIX epoch let origin_nanoseconds = u32::from_be_bytes(packet[32..36].try_into().unwrap()); - let source_timestamp = - NaiveDateTime::from_timestamp_opt(origin_seconds, origin_nanoseconds).unwrap(); + let source_timestamp = NaiveDateTime::from_timestamp_opt(origin_seconds, origin_nanoseconds).unwrap(); history.unbroken_sequence += 1; self.process_jitter(&source_timestamp, history); @@ -425,9 +364,7 @@ pub mod receiver { } } impl super::TestStream for UdpReceiver { - fn run_interval( - &mut self, - ) -> Option>> { + fn run_interval(&mut self) -> Option>> { let mut buf = vec![0_u8; self.test_definition.length.into()]; let mut bytes_received: u64 = 0; @@ -450,7 +387,10 @@ pub mod receiver { while self.active { if start.elapsed() >= RECEIVE_TIMEOUT { - return Some(Err(Box::new(simple_error::simple_error!("UDP reception for stream {} timed out, likely because the end-signal was lost", self.stream_idx)))); + return Some(Err(Box::new(simple_error::simple_error!( + "UDP reception for stream {} timed out, likely because the end-signal was lost", + self.stream_idx + )))); } log::trace!("awaiting UDP packets on stream {}...", self.stream_idx); @@ -514,11 +454,7 @@ pub mod receiver { }))); } } else { - log::warn!( - "received packet unrelated to UDP stream {} from {}", - self.stream_idx, - peer_addr - ); + log::warn!("received packet unrelated to UDP stream {} from {}", self.stream_idx, peer_addr); continue; } } @@ -601,14 +537,10 @@ pub mod sender { log::debug!("preparing to connect UDP stream {}...", stream_idx); let socket_addr_receiver = SocketAddr::new(*receiver_ip, *receiver_port); let socket = match receiver_ip { - IpAddr::V6(_) => { - UdpSocket::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), *port)) - .unwrap_or_else(|_| panic!("failed to bind UDP socket, port {}", port)) - } - IpAddr::V4(_) => { - UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), *port)) - .unwrap_or_else(|_| panic!("failed to bind UDP socket, port {}", port)) - } + IpAddr::V6(_) => UdpSocket::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), *port)) + .unwrap_or_else(|_| panic!("failed to bind UDP socket, port {}", port)), + IpAddr::V4(_) => UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), *port)) + .unwrap_or_else(|_| panic!("failed to bind UDP socket, port {}", port)), }; socket.set_write_timeout(Some(WRITE_TIMEOUT))?; if !cfg!(windows) { @@ -619,11 +551,7 @@ pub mod sender { } } socket.connect(socket_addr_receiver)?; - log::debug!( - "connected UDP stream {} to {}", - stream_idx, - socket_addr_receiver - ); + log::debug!("connected UDP stream {} to {}", stream_idx, socket_addr_receiver); let mut staged_packet = vec![0_u8; test_definition.length.into()]; for i in super::TEST_HEADER_SIZE..(staged_packet.len() as u16) { @@ -649,9 +577,7 @@ pub mod sender { } fn prepare_packet(&mut self) { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time before UNIX epoch"); + let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before UNIX epoch"); //eight bytes after the test ID are the packet's ID, in big-endian order self.staged_packet[16..24].copy_from_slice(&self.next_packet_id.to_be_bytes()); @@ -662,16 +588,12 @@ pub mod sender { } } impl super::TestStream for UdpSender { - fn run_interval( - &mut self, - ) -> Option>> { + fn run_interval(&mut self) -> Option>> { let interval_duration = Duration::from_secs_f32(self.send_interval); let mut interval_iteration = 0; - let bytes_to_send = - ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; + let bytes_to_send = ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; let mut bytes_to_send_remaining = bytes_to_send; - let bytes_to_send_per_interval_slice = - ((bytes_to_send as f32) * self.send_interval) as i64; + let bytes_to_send_per_interval_slice = ((bytes_to_send as f32) * self.send_interval) as i64; let mut bytes_to_send_per_interval_slice_remaining = bytes_to_send_per_interval_slice; let mut packets_sent: u64 = 0; @@ -681,21 +603,13 @@ pub mod sender { let cycle_start = Instant::now(); while self.active && self.remaining_duration > 0.0 && bytes_to_send_remaining > 0 { - log::trace!( - "writing {} bytes in UDP stream {}...", - self.staged_packet.len(), - self.stream_idx - ); + log::trace!("writing {} bytes in UDP stream {}...", self.staged_packet.len(), self.stream_idx); let packet_start = Instant::now(); self.prepare_packet(); match self.socket.send(&self.staged_packet) { Ok(packet_size) => { - log::trace!( - "wrote {} bytes in UDP stream {}", - packet_size, - self.stream_idx - ); + log::trace!("wrote {} bytes in UDP stream {}", packet_size, self.stream_idx); packets_sent += 1; //reflect that a packet is in-flight @@ -771,11 +685,7 @@ pub mod sender { //do it a few times in case of loss match self.socket.send(&self.staged_packet[0..16]) { Ok(packet_size) => { - log::trace!( - "wrote {} bytes of test-end signal in UDP stream {}", - packet_size, - self.stream_idx - ); + log::trace!("wrote {} bytes of test-end signal in UDP stream {}", packet_size, self.stream_idx); remaining_announcements -= 1; } Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { diff --git a/src/utils/cpu_affinity.rs b/src/utils/cpu_affinity.rs index 4f29935..2b16146 100644 --- a/src/utils/cpu_affinity.rs +++ b/src/utils/cpu_affinity.rs @@ -30,10 +30,7 @@ pub struct CpuAffinityManager { impl CpuAffinityManager { pub fn new(cores: &str) -> BoxResult { let core_ids = core_affinity::get_core_ids().unwrap_or_default(); - log::debug!( - "enumerated CPU cores: {:?}", - core_ids.iter().map(|c| c.id).collect::>() - ); + log::debug!("enumerated CPU cores: {:?}", core_ids.iter().map(|c| c.id).collect::>()); let mut enabled_cores = Vec::new(); for cid in cores.split(',') { From 3d0f1e9946c5bae68fcded41e8b3d39d14796549 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:17:28 +0800 Subject: [PATCH 06/18] temp result --- Cargo.toml | 4 +-- src/client.rs | 10 +------- src/protocol/communication.rs | 18 ++++++-------- src/server.rs | 10 ++++---- src/stream/tcp.rs | 47 +++++++++++++++-------------------- src/stream/udp.rs | 9 ++----- 6 files changed, 37 insertions(+), 61 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3506e50..c84bb58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ core_affinity = "0.8" ctrlc2 = "3.5" env_logger = "0.10" log = { version = "0.4", features = ["std"] } -mio = "0.6" -nix = "0.20" +mio = { version = "0.8", features = ["log", "os-poll", "net"] } +nix = { version = "0.27", features = ["net"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" simple-error = "0.3" diff --git a/src/client.rs b/src/client.rs index b90c633..762a9f8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -64,15 +64,7 @@ fn connect_to_server(address: &str, port: &u16) -> BoxResult { Ok(s) => s, Err(e) => return Err(Box::new(simple_error::simple_error!("unable to connect: {}", e))), }; - let stream = match TcpStream::from_stream(raw_stream) { - Ok(s) => s, - Err(e) => { - return Err(Box::new(simple_error::simple_error!( - "unable to prepare TCP control-channel: {}", - e - ))) - } - }; + let stream = TcpStream::from_std(raw_stream); log::info!("connected to server"); stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index 1aec987..e5406aa 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -22,7 +22,7 @@ use std::io::{self, Read, Write}; use std::time::Duration; use mio::net::TcpStream; -use mio::{Events, Poll, PollOpt, Ready, Token}; +use mio::{Events, Interest, Poll, Token}; use std::error::Error; type BoxResult = Result>; @@ -52,11 +52,9 @@ pub fn send(stream: &mut TcpStream, message: &serde_json::Value) -> BoxResult<() /// receives the length-count of a pending message over a client-server communications stream fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_handler: &mut dyn FnMut() -> BoxResult<()>) -> BoxResult { - let mut cloned_stream = stream.try_clone()?; - let mio_token = Token(0); - let poll = Poll::new()?; - poll.register(&cloned_stream, mio_token, Ready::readable(), PollOpt::edge())?; + let mut poll = Poll::new()?; + poll.registry().register(stream, mio_token, Interest::READABLE)?; let mut events = Events::with_capacity(1); //only interacting with one stream let mut length_bytes_read = 0; @@ -68,7 +66,7 @@ fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_han for event in events.iter() { event.token(); loop { - let size = match cloned_stream.read(&mut length_spec[length_bytes_read..]) { + let size = match stream.read(&mut length_spec[length_bytes_read..]) { Ok(size) => size, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { //nothing left to process @@ -108,11 +106,9 @@ fn receive_payload( results_handler: &mut dyn FnMut() -> BoxResult<()>, length: u16, ) -> BoxResult { - let mut cloned_stream = stream.try_clone()?; - let mio_token = Token(0); - let poll = Poll::new()?; - poll.register(&cloned_stream, mio_token, Ready::readable(), PollOpt::edge())?; + let mut poll = Poll::new()?; + poll.registry().register(stream, mio_token, Interest::READABLE)?; let mut events = Events::with_capacity(1); //only interacting with one stream let mut bytes_read = 0; @@ -124,7 +120,7 @@ fn receive_payload( for event in events.iter() { event.token(); loop { - let size = match cloned_stream.read(&mut buffer[bytes_read..]) { + let size = match stream.read(&mut buffer[bytes_read..]) { Ok(size) => size, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { // nothing left to process diff --git a/src/server.rs b/src/server.rs index 61fc9bc..2d63efe 100644 --- a/src/server.rs +++ b/src/server.rs @@ -28,7 +28,7 @@ use std::thread; use std::time::Duration; use mio::net::{TcpListener, TcpStream}; -use mio::{Events, Poll, PollOpt, Ready, Token}; +use mio::{Events, Interest, Poll, Token}; use crate::args::Args; use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; @@ -316,13 +316,13 @@ pub fn serve(args: &Args) -> BoxResult<()> { //start listening for connections let port: u16 = args.port; - let listener: TcpListener = - TcpListener::bind(&SocketAddr::new(args.bind, port)).unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)); + let mut listener = + TcpListener::bind(SocketAddr::new(args.bind, port)).unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)); log::info!("server listening on {}", listener.local_addr()?); let mio_token = Token(0); - let poll = Poll::new()?; - poll.register(&listener, mio_token, Ready::readable(), PollOpt::edge())?; + let mut poll = Poll::new()?; + poll.registry().register(&mut listener, mio_token, Interest::READABLE)?; let mut events = Events::with_capacity(32); while is_alive() { diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 80a57e2..cf20b1a 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -79,13 +79,14 @@ impl TcpTestDefinition { pub mod receiver { use std::io::Read; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + use std::os::fd::FromRawFd; use std::os::unix::io::AsRawFd; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; use std::sync::Mutex; use std::time::{Duration, Instant}; use mio::net::{TcpListener, TcpStream}; - use mio::{Events, Poll, PollOpt, Ready, Token}; + use mio::{Events, Interest, Poll, Token}; const POLL_TIMEOUT: Duration = Duration::from_millis(250); const RECEIVE_TIMEOUT: Duration = Duration::from_secs(3); @@ -129,7 +130,7 @@ pub mod receiver { match peer_ip { IpAddr::V6(_) => { if self.ports_ip6.is_empty() { - return Ok(TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)) + return Ok(TcpListener::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)) .expect("failed to bind OS-assigned IPv6 TCP socket")); } else { let _guard = self.lock_ip6.lock().unwrap(); @@ -137,7 +138,7 @@ pub mod receiver { for port_idx in (self.pos_ip6 + 1)..self.ports_ip6.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine let listener_result = - TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); + TcpListener::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; return Ok(listener_result); @@ -148,7 +149,7 @@ pub mod receiver { for port_idx in 0..=self.pos_ip6 { //circle back to where the search started let listener_result = - TcpListener::bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); + TcpListener::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.ports_ip6[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip6 = port_idx; return Ok(listener_result); @@ -161,7 +162,7 @@ pub mod receiver { } IpAddr::V4(_) => { if self.ports_ip4.is_empty() { - return Ok(TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)) + return Ok(TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)) .expect("failed to bind OS-assigned IPv4 TCP socket")); } else { let _guard = self.lock_ip4.lock().unwrap(); @@ -169,7 +170,7 @@ pub mod receiver { for port_idx in (self.pos_ip4 + 1)..self.ports_ip4.len() { //iterate to the end of the pool; this will skip the first element in the pool initially, but that's fine let listener_result = - TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); + TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; return Ok(listener_result); @@ -180,7 +181,7 @@ pub mod receiver { for port_idx in 0..=self.pos_ip4 { //circle back to where the search started let listener_result = - TcpListener::bind(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); + TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), self.ports_ip4[port_idx])); if let Ok(listener_result) = listener_result { self.pos_ip4 = port_idx; return Ok(listener_result); @@ -241,8 +242,8 @@ pub mod receiver { let listener = self.listener.as_mut().unwrap(); let mio_token = Token(0); - let poll = Poll::new()?; - poll.register(listener, mio_token, Ready::readable(), PollOpt::edge())?; + let mut poll = Poll::new()?; + poll.registry().register(listener, mio_token, Interest::READABLE)?; let mut events = Events::with_capacity(1); let start = Instant::now(); @@ -259,7 +260,7 @@ pub mod receiver { for event in events.iter() { event.token(); loop { - let (stream, address) = match listener.accept() { + let (mut stream, address) = match listener.accept() { Ok((stream, address)) => (stream, address), Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { // nothing to do @@ -272,10 +273,9 @@ pub mod receiver { log::debug!("received TCP stream {} connection from {}", self.stream_idx, address); - let mut verification_stream = stream.try_clone()?; let mio_token2 = Token(0); - let poll2 = Poll::new()?; - poll2.register(&verification_stream, mio_token2, Ready::readable(), PollOpt::edge())?; + let mut poll2 = Poll::new()?; + poll2.registry().register(&mut stream, mio_token2, Interest::READABLE)?; let mut buffer = [0_u8; 16]; let mut events2 = Events::with_capacity(1); @@ -283,7 +283,7 @@ pub mod receiver { for event2 in events2.iter() { event2.token(); - if let Err(e) = verification_stream.read(&mut buffer) { + if let Err(e) = stream.read(&mut buffer) { if e.kind() == std::io::ErrorKind::WouldBlock { // client didn't provide anything break; @@ -297,12 +297,13 @@ pub mod receiver { // NOTE: features unsupported on Windows if self.receive_buffer != 0 { log::debug!("setting receive-buffer to {}...", self.receive_buffer); - super::setsockopt(stream.as_raw_fd(), super::RcvBuf, &self.receive_buffer)?; + super::setsockopt(&stream, super::RcvBuf, &self.receive_buffer)?; } } self.mio_poll - .register(&stream, self.mio_poll_token, Ready::readable(), PollOpt::edge())?; + .registry() + .register(&mut stream, self.mio_poll_token, Interest::READABLE)?; return Ok(stream); } } @@ -441,6 +442,7 @@ pub mod receiver { pub mod sender { use std::io::Write; use std::net::{IpAddr, SocketAddr}; + use std::os::fd::FromRawFd; use std::os::unix::io::AsRawFd; use std::time::{Duration, Instant}; @@ -521,16 +523,7 @@ pub mod sender { } }; raw_stream.set_write_timeout(Some(WRITE_TIMEOUT))?; - let stream = match TcpStream::from_stream(raw_stream) { - Ok(s) => s, - Err(e) => { - return Err(Box::new(simple_error::simple_error!( - "unable to prepare TCP stream {}: {}", - self.stream_idx, - e - ))) - } - }; + let stream = TcpStream::from_std(raw_stream); log::debug!("connected TCP stream {} to {}", self.stream_idx, stream.peer_addr()?); if self.no_delay { @@ -541,7 +534,7 @@ pub mod sender { //NOTE: features unsupported on Windows if self.send_buffer != 0 { log::debug!("setting send-buffer to {}...", self.send_buffer); - super::setsockopt(stream.as_raw_fd(), super::SndBuf, &self.send_buffer)?; + super::setsockopt(&stream, super::SndBuf, &self.send_buffer)?; } } Ok(stream) diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 5ce2032..2e83c93 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -18,9 +18,6 @@ * along with rperf. If not, see . */ -extern crate log; -extern crate nix; - use std::error::Error; use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; @@ -84,7 +81,6 @@ impl UdpTestDefinition { pub mod receiver { use std::convert::TryInto; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - use std::os::unix::io::AsRawFd; use std::sync::Mutex; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; @@ -238,7 +234,7 @@ pub mod receiver { //NOTE: features unsupported on Windows if *receive_buffer != 0 { log::debug!("setting receive-buffer to {}...", receive_buffer); - super::setsockopt(socket.as_raw_fd(), super::RcvBuf, receive_buffer)?; + super::setsockopt(&socket, super::RcvBuf, receive_buffer)?; } } log::debug!("bound UDP receive socket for stream {}: {}", stream_idx, socket.local_addr()?); @@ -498,7 +494,6 @@ pub mod receiver { pub mod sender { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - use std::os::unix::io::AsRawFd; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use std::net::UdpSocket; @@ -547,7 +542,7 @@ pub mod sender { //NOTE: features unsupported on Windows if *send_buffer != 0 { log::debug!("setting send-buffer to {}...", send_buffer); - super::setsockopt(socket.as_raw_fd(), super::SndBuf, send_buffer)?; + super::setsockopt(&socket, super::SndBuf, send_buffer)?; } } socket.connect(socket_addr_receiver)?; From 6262199c5b3b185d1e5c40a0368f7652a58fd8c4 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:53:05 +0800 Subject: [PATCH 07/18] socket2 imported --- Cargo.toml | 1 + src/client.rs | 21 ++++++++++++++++----- src/server.rs | 20 +++++++++++++++++--- src/stream/tcp.rs | 35 ++++++++++++++++++----------------- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c84bb58..f76e865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ nix = { version = "0.27", features = ["net"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" simple-error = "0.3" +socket2 = { version = "0.5", features = ["all"] } uuid = { version = "1.6", features = ["v4"] } #configuration for cargo-deb diff --git a/src/client.rs b/src/client.rs index 762a9f8..62d7e58 100644 --- a/src/client.rs +++ b/src/client.rs @@ -60,15 +60,26 @@ fn connect_to_server(address: &str, port: &u16) -> BoxResult { if server_addr.is_none() { return Err(Box::new(simple_error::simple_error!("unable to resolve {}", address))); } - let raw_stream = match std::net::TcpStream::connect_timeout(&server_addr.unwrap(), CONNECT_TIMEOUT) { + let stream = match std::net::TcpStream::connect_timeout(&server_addr.unwrap(), CONNECT_TIMEOUT) { Ok(s) => s, Err(e) => return Err(Box::new(simple_error::simple_error!("unable to connect: {}", e))), }; - let stream = TcpStream::from_std(raw_stream); - log::info!("connected to server"); - stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); - stream.set_keepalive(Some(KEEPALIVE_DURATION)).expect("unable to set TCP keepalive"); + let stream = { + let socket: socket2::Socket = socket2::Socket::from(stream); + let keepalive = socket2::TcpKeepalive::new() + .with_time(KEEPALIVE_DURATION) + .with_interval(KEEPALIVE_DURATION) + .with_retries(4); + socket.set_tcp_keepalive(&keepalive)?; + socket.set_nodelay(true)?; + + let stream: std::net::TcpStream = socket.into(); + + TcpStream::from_std(stream) + }; + + log::info!("connected to server"); Ok(stream) } diff --git a/src/server.rs b/src/server.rs index 2d63efe..82c5b8d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -330,7 +330,7 @@ pub fn serve(args: &Args) -> BoxResult<()> { for event in events.iter() { event.token(); loop { - let (mut stream, address) = match listener.accept() { + let (stream, address) = match listener.accept() { Ok((stream, address)) => (stream, address), Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { // nothing to do @@ -343,8 +343,22 @@ pub fn serve(args: &Args) -> BoxResult<()> { log::info!("connection from {}", address); - stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); - stream.set_keepalive(Some(KEEPALIVE_DURATION)).expect("unable to set TCP keepalive"); + let mut stream = { + use std::os::fd::{FromRawFd, IntoRawFd}; + let fd = stream.into_raw_fd(); + let socket: socket2::Socket = unsafe { socket2::Socket::from_raw_fd(fd) }; + + let keepalive = socket2::TcpKeepalive::new() + .with_time(KEEPALIVE_DURATION) + .with_interval(KEEPALIVE_DURATION) + .with_retries(4); + socket.set_tcp_keepalive(&keepalive)?; + + socket.set_nodelay(true)?; + + let stream: std::net::TcpStream = socket.into(); + mio::net::TcpStream::from_std(stream) + }; let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; if client_limit > 0 && client_count > client_limit { diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index cf20b1a..2ec4179 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -79,8 +79,6 @@ impl TcpTestDefinition { pub mod receiver { use std::io::Read; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - use std::os::fd::FromRawFd; - use std::os::unix::io::AsRawFd; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; use std::sync::Mutex; use std::time::{Duration, Instant}; @@ -293,12 +291,16 @@ pub mod receiver { if buffer == self.test_definition.test_id { log::debug!("validated TCP stream {} connection from {}", self.stream_idx, address); - if !cfg!(windows) { - // NOTE: features unsupported on Windows - if self.receive_buffer != 0 { - log::debug!("setting receive-buffer to {}...", self.receive_buffer); - super::setsockopt(&stream, super::RcvBuf, &self.receive_buffer)?; - } + + // NOTE: features unsupported on Windows + #[cfg(not(windows))] + if self.receive_buffer != 0 { + log::debug!("setting receive-buffer to {}...", self.receive_buffer); + + use std::os::fd::{FromRawFd, IntoRawFd}; + let raw_stream = unsafe { std::net::TcpStream::from_raw_fd(stream.into_raw_fd()) }; + super::setsockopt(&raw_stream, super::RcvBuf, &self.receive_buffer)?; + stream = unsafe { TcpStream::from_raw_fd(raw_stream.into_raw_fd()) }; } self.mio_poll @@ -442,8 +444,6 @@ pub mod receiver { pub mod sender { use std::io::Write; use std::net::{IpAddr, SocketAddr}; - use std::os::fd::FromRawFd; - use std::os::unix::io::AsRawFd; use std::time::{Duration, Instant}; use mio::net::TcpStream; @@ -522,6 +522,14 @@ pub mod sender { ))) } }; + + // NOTE: features unsupported on Windows + #[cfg(not(windows))] + if self.send_buffer != 0 { + log::debug!("setting send-buffer to {}...", self.send_buffer); + super::setsockopt(&raw_stream, super::SndBuf, &self.send_buffer)?; + } + raw_stream.set_write_timeout(Some(WRITE_TIMEOUT))?; let stream = TcpStream::from_std(raw_stream); log::debug!("connected TCP stream {} to {}", self.stream_idx, stream.peer_addr()?); @@ -530,13 +538,6 @@ pub mod sender { log::debug!("setting no-delay..."); stream.set_nodelay(true)?; } - if !cfg!(windows) { - //NOTE: features unsupported on Windows - if self.send_buffer != 0 { - log::debug!("setting send-buffer to {}...", self.send_buffer); - super::setsockopt(&stream, super::SndBuf, &self.send_buffer)?; - } - } Ok(stream) } } From 94568ba8d4c1d1bb1c0f9ce0817960f30a6900c9 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 18 Dec 2023 23:11:40 +0800 Subject: [PATCH 08/18] refactor BoxResult --- src/client.rs | 3 +-- src/protocol/communication.rs | 3 +-- src/protocol/messaging.rs | 3 +-- src/protocol/results.rs | 3 +-- src/server.rs | 3 +-- src/stream/mod.rs | 3 +-- src/stream/tcp.rs | 3 +-- src/stream/udp.rs | 4 +--- src/utils/cpu_affinity.rs | 5 +---- 9 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/client.rs b/src/client.rs index 62d7e58..e293fdd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -30,7 +30,6 @@ use crate::{ }; use mio::net::TcpStream; use std::{ - error::Error, net::{IpAddr, Shutdown, ToSocketAddrs}, sync::{ atomic::{AtomicBool, Ordering}, @@ -41,7 +40,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -type BoxResult = Result>; +type BoxResult = Result>; /// when false, the system is shutting down static ALIVE: AtomicBool = AtomicBool::new(true); diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index e5406aa..73d1094 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -24,8 +24,7 @@ use std::time::Duration; use mio::net::TcpStream; use mio::{Events, Interest, Poll, Token}; -use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; /// how long to wait for keepalive events // the communications channels typically exchange data every second, so 2s is reasonable to avoid excess noise diff --git a/src/protocol/messaging.rs b/src/protocol/messaging.rs index 1fbf206..9d55f33 100644 --- a/src/protocol/messaging.rs +++ b/src/protocol/messaging.rs @@ -18,8 +18,7 @@ * along with rperf. If not, see . */ -use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; /// prepares a message used to tell the server to begin operations pub fn prepare_begin() -> serde_json::Value { diff --git a/src/protocol/results.rs b/src/protocol/results.rs index 168ae0c..75aa298 100644 --- a/src/protocol/results.rs +++ b/src/protocol/results.rs @@ -23,8 +23,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use serde::{Deserialize, Serialize}; -use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; /* This module contains structures used to represent and collect the results of tests. * Since everything is basically just a data-container with representation methods, diff --git a/src/server.rs b/src/server.rs index 82c5b8d..86c05dc 100644 --- a/src/server.rs +++ b/src/server.rs @@ -18,7 +18,6 @@ * along with rperf. If not, see . */ -use std::error::Error; use std::io; use std::net::{Shutdown, SocketAddr}; use std::sync::atomic::{AtomicBool, AtomicU16, Ordering}; @@ -36,7 +35,7 @@ use crate::protocol::messaging::{prepare_connect, prepare_connect_ready}; use crate::protocol::results::ServerDoneResult; use crate::stream::{tcp, udp, TestStream}; -type BoxResult = Result>; +type BoxResult = Result>; const POLL_TIMEOUT: Duration = Duration::from_millis(500); diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 30eab74..6157656 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -21,8 +21,7 @@ pub mod tcp; pub mod udp; -use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; pub const INTERVAL: std::time::Duration = std::time::Duration::from_secs(1); diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 2ec4179..68a0e19 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -24,8 +24,7 @@ use crate::protocol::results::{get_unix_timestamp, IntervalResult, TcpReceiveRes use super::{parse_port_spec, TestStream, INTERVAL}; -use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; pub const TEST_HEADER_SIZE: usize = 16; diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 2e83c93..f022eee 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -18,15 +18,13 @@ * along with rperf. If not, see . */ -use std::error::Error; - use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; use crate::protocol::results::{get_unix_timestamp, IntervalResult, UdpReceiveResult, UdpSendResult}; use super::{parse_port_spec, TestStream, INTERVAL}; -type BoxResult = Result>; +type BoxResult = Result>; pub const TEST_HEADER_SIZE: u16 = 36; const UDP_HEADER_SIZE: u16 = 8; diff --git a/src/utils/cpu_affinity.rs b/src/utils/cpu_affinity.rs index 2b16146..25e680b 100644 --- a/src/utils/cpu_affinity.rs +++ b/src/utils/cpu_affinity.rs @@ -18,10 +18,7 @@ * along with rperf. If not, see . */ -extern crate core_affinity; - -use std::error::Error; -type BoxResult = Result>; +type BoxResult = Result>; pub struct CpuAffinityManager { enabled_cores: Vec, From bdc9b100fdb7149ed72acaa9c8736c50b2c831c2 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 18 Dec 2023 23:20:26 +0800 Subject: [PATCH 09/18] tcp_stream_try_clone function --- src/server.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index 86c05dc..4422cb6 100644 --- a/src/server.rs +++ b/src/server.rs @@ -45,6 +45,16 @@ static ALIVE: AtomicBool = AtomicBool::new(true); /// a count of connected clients static CLIENTS: AtomicU16 = AtomicU16::new(0); +fn tcp_stream_try_clone(stream: &TcpStream) -> BoxResult { + use std::os::fd::{AsRawFd, BorrowedFd}; + let fd = unsafe { BorrowedFd::borrow_raw(stream.as_raw_fd()) }; + let fd = fd.try_clone_to_owned()?; + let socket: socket2::Socket = socket2::Socket::from(fd); + let stream: std::net::TcpStream = socket.into(); + let socket = TcpStream::from_std(stream); + Ok(socket) +} + fn handle_client( stream: &mut TcpStream, cpu_affinity_manager: Arc>, @@ -64,7 +74,7 @@ fn handle_client( ) = channel(); //a closure used to pass results from stream-handlers to the client-communication stream - let mut forwarding_send_stream = stream.try_clone()?; + let mut forwarding_send_stream = tcp_stream_try_clone(stream)?; let mut results_handler = || -> BoxResult<()> { // drain all results every time this closer is invoked while let Ok(result) = results_rx.try_recv() { From b33c1f2ca01cf13e4e73d46bb66fc2b2eee5220c Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 19 Dec 2023 00:26:24 +0800 Subject: [PATCH 10/18] make rperf as a lib --- Cargo.toml | 2 +- src/client.rs | 3 +-- src/lib.rs | 8 ++++++++ src/main.rs | 10 +++------- src/protocol/communication.rs | 2 +- src/protocol/messaging.rs | 2 +- src/protocol/results.rs | 6 ++---- src/server.rs | 12 +++++++++--- src/stream/mod.rs | 2 +- src/stream/tcp.rs | 3 +-- src/stream/udp.rs | 3 +-- src/utils/cpu_affinity.rs | 2 +- 12 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index f76e865..669080c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" chrono = "0.4" clap = { version = "4.4", features = ["derive", "wrap_help"] } core_affinity = "0.8" -ctrlc2 = "3.5" +ctrlc2 = { version = "3.5", features = ["termination"] } env_logger = "0.10" log = { version = "0.4", features = ["std"] } mio = { version = "0.8", features = ["log", "os-poll", "net"] } diff --git a/src/client.rs b/src/client.rs index e293fdd..23dfb6d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -27,6 +27,7 @@ use crate::{ results::{IntervalResultBox, IntervalResultKind, TcpTestResults, TestResults, UdpTestResults}, }, stream::{tcp, udp, TestStream}, + BoxResult, }; use mio::net::TcpStream; use std::{ @@ -40,8 +41,6 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -type BoxResult = Result>; - /// when false, the system is shutting down static ALIVE: AtomicBool = AtomicBool::new(true); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..50cba77 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +pub mod args; +pub mod client; +pub(crate) mod protocol; +pub mod server; +pub(crate) mod stream; +pub(crate) mod utils; + +pub(crate) type BoxResult = Result>; diff --git a/src/main.rs b/src/main.rs index 12cdf80..9e1862b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,12 +18,7 @@ * along with rperf. If not, see . */ -mod args; -mod client; -mod protocol; -mod server; -mod stream; -mod utils; +use rperf::{args, client, server}; fn main() { use clap::Parser; @@ -37,7 +32,7 @@ fn main() { if args.server { log::debug!("registering SIGINT handler..."); - ctrlc2::set_handler(move || { + let exiting = ctrlc2::set_handler(move || { if server::kill() { log::warn!("shutdown requested; please allow a moment for any in-progress tests to stop"); } else { @@ -50,6 +45,7 @@ fn main() { log::debug!("beginning normal operation..."); let service = server::serve(&args); + exiting.join().expect("unable to join SIGINT handler thread"); if service.is_err() { log::error!("unable to run server: {}", service.unwrap_err()); std::process::exit(4); diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index 73d1094..e59f966 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -24,7 +24,7 @@ use std::time::Duration; use mio::net::TcpStream; use mio::{Events, Interest, Poll, Token}; -type BoxResult = Result>; +use crate::BoxResult; /// how long to wait for keepalive events // the communications channels typically exchange data every second, so 2s is reasonable to avoid excess noise diff --git a/src/protocol/messaging.rs b/src/protocol/messaging.rs index 9d55f33..90a4c71 100644 --- a/src/protocol/messaging.rs +++ b/src/protocol/messaging.rs @@ -18,7 +18,7 @@ * along with rperf. If not, see . */ -type BoxResult = Result>; +use crate::BoxResult; /// prepares a message used to tell the server to begin operations pub fn prepare_begin() -> serde_json::Value { diff --git a/src/protocol/results.rs b/src/protocol/results.rs index 75aa298..037e601 100644 --- a/src/protocol/results.rs +++ b/src/protocol/results.rs @@ -18,13 +18,11 @@ * along with rperf. If not, see . */ +use crate::BoxResult; +use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::time::{SystemTime, UNIX_EPOCH}; -use serde::{Deserialize, Serialize}; - -type BoxResult = Result>; - /* This module contains structures used to represent and collect the results of tests. * Since everything is basically just a data-container with representation methods, * it isn't extensively documented. diff --git a/src/server.rs b/src/server.rs index 4422cb6..ee9e836 100644 --- a/src/server.rs +++ b/src/server.rs @@ -34,8 +34,7 @@ use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; use crate::protocol::messaging::{prepare_connect, prepare_connect_ready}; use crate::protocol::results::ServerDoneResult; use crate::stream::{tcp, udp, TestStream}; - -type BoxResult = Result>; +use crate::BoxResult; const POLL_TIMEOUT: Duration = Duration::from_millis(500); @@ -335,7 +334,14 @@ pub fn serve(args: &Args) -> BoxResult<()> { let mut events = Events::with_capacity(32); while is_alive() { - poll.poll(&mut events, Some(POLL_TIMEOUT))?; + if let Err(err) = poll.poll(&mut events, Some(POLL_TIMEOUT)) { + if err.kind() == std::io::ErrorKind::Interrupted { + log::debug!("Poll interrupted: \"{err}\", ignored, continue polling"); + continue; + } + log::error!("Poll error: {}", err); + break; + } for event in events.iter() { event.token(); loop { diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 6157656..49767d2 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -21,7 +21,7 @@ pub mod tcp; pub mod udp; -type BoxResult = Result>; +use crate::BoxResult; pub const INTERVAL: std::time::Duration = std::time::Duration::from_secs(1); diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 68a0e19..486e764 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -21,11 +21,10 @@ use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; use crate::protocol::results::{get_unix_timestamp, IntervalResult, TcpReceiveResult, TcpSendResult}; +use crate::BoxResult; use super::{parse_port_spec, TestStream, INTERVAL}; -type BoxResult = Result>; - pub const TEST_HEADER_SIZE: usize = 16; #[derive(Clone)] diff --git a/src/stream/udp.rs b/src/stream/udp.rs index f022eee..c796604 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -21,11 +21,10 @@ use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; use crate::protocol::results::{get_unix_timestamp, IntervalResult, UdpReceiveResult, UdpSendResult}; +use crate::BoxResult; use super::{parse_port_spec, TestStream, INTERVAL}; -type BoxResult = Result>; - pub const TEST_HEADER_SIZE: u16 = 36; const UDP_HEADER_SIZE: u16 = 8; diff --git a/src/utils/cpu_affinity.rs b/src/utils/cpu_affinity.rs index 25e680b..729099f 100644 --- a/src/utils/cpu_affinity.rs +++ b/src/utils/cpu_affinity.rs @@ -18,7 +18,7 @@ * along with rperf. If not, see . */ -type BoxResult = Result>; +use crate::BoxResult; pub struct CpuAffinityManager { enabled_cores: Vec, From 59471a51d51adce2cf6b46d87eb71a382a097507 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 19 Dec 2023 00:54:40 +0800 Subject: [PATCH 11/18] verbosity --- src/args.rs | 28 ++++++++++++++++++++++++++++ src/main.rs | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index 356f43c..08c788f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -127,6 +127,10 @@ pub struct Args { /// if omitted, any OS-assignable port is used; format: 1-10,19,21 #[arg(long, value_name = "ports", default_value = "")] pub udp6_port_pool: String, + + /// Verbosity level + #[arg(short, long, value_name = "level", value_enum, default_value = "info")] + pub verbosity: ArgVerbosity, } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, Default)] @@ -146,3 +150,27 @@ impl std::fmt::Display for Format { } } } + +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] +pub enum ArgVerbosity { + Off, + Error, + Warn, + #[default] + Info, + Debug, + Trace, +} + +impl std::fmt::Display for ArgVerbosity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ArgVerbosity::Off => write!(f, "off"), + ArgVerbosity::Error => write!(f, "error"), + ArgVerbosity::Warn => write!(f, "warn"), + ArgVerbosity::Info => write!(f, "info"), + ArgVerbosity::Debug => write!(f, "debug"), + ArgVerbosity::Trace => write!(f, "trace"), + } + } +} diff --git a/src/main.rs b/src/main.rs index 9e1862b..78a3430 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,8 @@ fn main() { use clap::Parser; let args = args::Args::parse(); - let mut env = env_logger::Env::default().filter_or("RUST_LOG", "info"); + let default = args.verbosity.to_string(); + let mut env = env_logger::Env::default().filter_or("RUST_LOG", &default); if args.debug { env = env.filter_or("RUST_LOG", "debug"); } From 5365d90772594366f58cf587bc636dfcecc65217 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:53:35 +0800 Subject: [PATCH 12/18] fix mio poll issues --- src/args.rs | 34 ++++++++++++++++---------- src/lib.rs | 8 +++++- src/main.rs | 18 ++++---------- src/protocol/communication.rs | 46 +++++++++++++++++++++-------------- src/server.rs | 6 ++--- src/stream/tcp.rs | 41 +++++++++++++++++-------------- src/stream/udp.rs | 22 ++++++++--------- 7 files changed, 97 insertions(+), 78 deletions(-) diff --git a/src/args.rs b/src/args.rs index 08c788f..82f7c23 100644 --- a/src/args.rs +++ b/src/args.rs @@ -33,12 +33,12 @@ pub struct Args { /// enable IPv6 on the server (on most hosts, this will allow both IPv4 and IPv6, /// but it might limit to just IPv6 on some) - #[arg(short = '6', long)] + #[arg(short = '6', long, conflicts_with = "client")] pub version6: bool, /// limit the number of concurrent clients that can be processed by a server; /// any over this count will be immediately disconnected - #[arg(long, value_name = "number", default_value = "0")] + #[arg(long, value_name = "number", default_value = "0", conflicts_with = "client")] pub client_limit: usize, /// run in client mode; value is the server's address @@ -46,36 +46,44 @@ pub struct Args { pub client: Option, /// run in reverse-mode (server sends, client receives) - #[arg(short = 'R', long)] + #[arg(short = 'R', long, conflicts_with = "server")] pub reverse: bool, /// the format in which to deplay information (json, megabit/sec, megabyte/sec) - #[arg(short, long, value_enum, value_name = "format", default_value = "megabit")] + #[arg( + short, + long, + value_enum, + value_name = "format", + default_value = "megabit", + conflicts_with = "server" + )] pub format: Format, /// use UDP rather than TCP - #[arg(short, long)] + #[arg(short, long, conflicts_with = "server")] pub udp: bool, /// target bandwidth in bytes/sec; this value is applied to each stream, /// with a default target of 1 megabit/second for all protocols (note: megabit, not mebibit); /// the suffixes kKmMgG can also be used for xbit and xbyte, respectively - #[arg(short, long, default_value = "125000", value_name = "bytes/sec")] + #[arg(short, long, default_value = "125000", value_name = "bytes/sec", conflicts_with = "server")] pub bandwidth: String, /// the time in seconds for which to transmit - #[arg(short, long, default_value = "10.0", value_name = "seconds")] + #[arg(short, long, default_value = "10.0", value_name = "seconds", conflicts_with = "server")] pub time: f64, /// the interval at which to send batches of data, in seconds, between [0.0 and 1.0); /// this is used to evenly spread packets out over time - #[arg(long, default_value = "0.05", value_name = "seconds")] + #[arg(long, default_value = "0.05", value_name = "seconds", conflicts_with = "server")] pub send_interval: f64, /// length of the buffer to exchange; for TCP, this defaults to 32 kibibytes; for UDP, it's 1024 bytes #[arg( short, long, + conflicts_with = "server", default_value = "32768", default_value_if("udp", "true", Some("1024")), value_name = "bytes" @@ -85,27 +93,27 @@ pub struct Args { /// send buffer, in bytes (only supported on some platforms; /// if set too small, a 'resource unavailable' error may occur; /// affects TCP window-size) - #[arg(long, default_value = "0", value_name = "bytes")] + #[arg(long, default_value = "0", value_name = "bytes", conflicts_with = "server")] pub send_buffer: usize, /// receive buffer, in bytes (only supported on some platforms; /// if set too small, a 'resource unavailable' error may occur; affects TCP window-size) - #[arg(long, default_value = "0", value_name = "bytes")] + #[arg(long, default_value = "0", value_name = "bytes", conflicts_with = "server")] pub receive_buffer: usize, /// the number of parallel data-streams to use - #[arg(short = 'P', long, value_name = "number", default_value = "1")] + #[arg(short = 'P', long, value_name = "number", default_value = "1", conflicts_with = "server")] pub parallel: usize, /// omit a number of seconds from the start of calculations, /// primarily to avoid including TCP ramp-up in averages; /// using this option may result in disagreement between bytes sent and received, /// since data can be in-flight across time-boundaries - #[arg(short, long, default_value = "0", value_name = "seconds")] + #[arg(short, long, default_value = "0", value_name = "seconds", conflicts_with = "server")] pub omit: usize, /// use no-delay mode for TCP tests, disabling Nagle's Algorithm - #[arg(short = 'N', long)] + #[arg(short = 'N', long, conflicts_with = "server")] pub no_delay: bool, /// an optional pool of IPv4 TCP ports over which data will be accepted; diff --git a/src/lib.rs b/src/lib.rs index 50cba77..60440dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,4 +5,10 @@ pub mod server; pub(crate) mod stream; pub(crate) mod utils; -pub(crate) type BoxResult = Result>; +pub type BoxResult = Result>; + +/// a global token generator +pub(crate) fn get_global_token() -> mio::Token { + mio::Token(TOKEN_SEED.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1) +} +static TOKEN_SEED: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); diff --git a/src/main.rs b/src/main.rs index 78a3430..7652014 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,9 +18,9 @@ * along with rperf. If not, see . */ -use rperf::{args, client, server}; +use rperf::{args, client, server, BoxResult}; -fn main() { +fn main() -> BoxResult<()> { use clap::Parser; let args = args::Args::parse(); @@ -45,12 +45,8 @@ fn main() { .expect("unable to set SIGINT handler"); log::debug!("beginning normal operation..."); - let service = server::serve(&args); + server::serve(&args)?; exiting.join().expect("unable to join SIGINT handler thread"); - if service.is_err() { - log::error!("unable to run server: {}", service.unwrap_err()); - std::process::exit(4); - } } else if args.client.is_some() { log::debug!("registering SIGINT handler..."); ctrlc2::set_handler(move || { @@ -65,15 +61,11 @@ fn main() { .expect("unable to set SIGINT handler"); log::debug!("connecting to server..."); - let execution = client::execute(&args); - if execution.is_err() { - log::error!("unable to run client: {}", execution.unwrap_err()); - std::process::exit(4); - } + client::execute(&args)?; } else { use clap::CommandFactory; let mut cmd = args::Args::command(); cmd.print_help().unwrap(); - std::process::exit(2); } + Ok(()) } diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index e59f966..871722c 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -22,7 +22,7 @@ use std::io::{self, Read, Write}; use std::time::Duration; use mio::net::TcpStream; -use mio::{Events, Interest, Poll, Token}; +use mio::{Events, Interest, Poll}; use crate::BoxResult; @@ -51,14 +51,17 @@ pub fn send(stream: &mut TcpStream, message: &serde_json::Value) -> BoxResult<() /// receives the length-count of a pending message over a client-server communications stream fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_handler: &mut dyn FnMut() -> BoxResult<()>) -> BoxResult { - let mio_token = Token(0); + let mio_token = crate::get_global_token(); let mut poll = Poll::new()?; poll.registry().register(stream, mio_token, Interest::READABLE)?; let mut events = Events::with_capacity(1); //only interacting with one stream let mut length_bytes_read = 0; let mut length_spec: [u8; 2] = [0; 2]; - while alive_check() { + let result: BoxResult = 'exiting: loop { + if !alive_check() { + break 'exiting Ok(0); + } //waiting to find out how long the next message is results_handler()?; //send any outstanding results between cycles poll.poll(&mut events, Some(POLL_TIMEOUT))?; @@ -72,16 +75,16 @@ fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_han break; } Err(e) => { - return Err(Box::new(e)); + break 'exiting Err(Box::new(e)); } }; if size == 0 { if alive_check() { - return Err(Box::new(simple_error::simple_error!("connection lost"))); + break 'exiting Err(Box::new(simple_error::simple_error!("connection lost"))); } else { //shutting down; a disconnect is expected - return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); + break 'exiting Err(Box::new(simple_error::simple_error!("local shutdown requested"))); } } @@ -89,14 +92,16 @@ fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_han if length_bytes_read == 2 { let length = u16::from_be_bytes(length_spec); log::debug!("received length-spec of {} from {}", length, stream.peer_addr()?); - return Ok(length); + break 'exiting Ok(length); } else { log::debug!("received partial length-spec from {}", stream.peer_addr()?); } } } - } - Err(Box::new(simple_error::simple_error!("system shutting down"))) + }; + poll.registry().deregister(stream)?; + result + // Err(Box::new(simple_error::simple_error!("system shutting down"))) } /// receives the data-value of a pending message over a client-server communications stream fn receive_payload( @@ -105,14 +110,17 @@ fn receive_payload( results_handler: &mut dyn FnMut() -> BoxResult<()>, length: u16, ) -> BoxResult { - let mio_token = Token(0); + let mio_token = crate::get_global_token(); let mut poll = Poll::new()?; poll.registry().register(stream, mio_token, Interest::READABLE)?; let mut events = Events::with_capacity(1); //only interacting with one stream let mut bytes_read = 0; let mut buffer = vec![0_u8; length.into()]; - while alive_check() { + let result: BoxResult = 'exiting: loop { + if !alive_check() { + break 'exiting Ok(serde_json::from_slice(&buffer[0..0])?); + } //waiting to receive the payload results_handler()?; //send any outstanding results between cycles poll.poll(&mut events, Some(POLL_TIMEOUT))?; @@ -126,16 +134,16 @@ fn receive_payload( break; } Err(e) => { - return Err(Box::new(e)); + break 'exiting Err(Box::new(e)); } }; if size == 0 { if alive_check() { - return Err(Box::new(simple_error::simple_error!("connection lost"))); + break 'exiting Err(Box::new(simple_error::simple_error!("connection lost"))); } else { // shutting down; a disconnect is expected - return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); + break 'exiting Err(Box::new(simple_error::simple_error!("local shutdown requested"))); } } @@ -144,10 +152,10 @@ fn receive_payload( match serde_json::from_slice(&buffer) { Ok(v) => { log::debug!("received {:?} from {}", v, stream.peer_addr()?); - return Ok(v); + break 'exiting Ok(v); } Err(e) => { - return Err(Box::new(e)); + break 'exiting Err(Box::new(e)); } } } else { @@ -155,8 +163,10 @@ fn receive_payload( } } } - } - Err(Box::new(simple_error::simple_error!("system shutting down"))) + }; + poll.registry().deregister(stream)?; + result + // Err(Box::new(simple_error::simple_error!("system shutting down"))) } /// handles the full process of retrieving a message from a client-server communications stream pub fn receive( diff --git a/src/server.rs b/src/server.rs index ee9e836..a52d1ee 100644 --- a/src/server.rs +++ b/src/server.rs @@ -27,7 +27,7 @@ use std::thread; use std::time::Duration; use mio::net::{TcpListener, TcpStream}; -use mio::{Events, Interest, Poll, Token}; +use mio::{Events, Interest, Poll}; use crate::args::Args; use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; @@ -328,7 +328,7 @@ pub fn serve(args: &Args) -> BoxResult<()> { TcpListener::bind(SocketAddr::new(args.bind, port)).unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)); log::info!("server listening on {}", listener.local_addr()?); - let mio_token = Token(0); + let mio_token = crate::get_global_token(); let mut poll = Poll::new()?; poll.registry().register(&mut listener, mio_token, Interest::READABLE)?; let mut events = Events::with_capacity(32); @@ -336,7 +336,7 @@ pub fn serve(args: &Args) -> BoxResult<()> { while is_alive() { if let Err(err) = poll.poll(&mut events, Some(POLL_TIMEOUT)) { if err.kind() == std::io::ErrorKind::Interrupted { - log::debug!("Poll interrupted: \"{err}\", ignored, continue polling"); + log::debug!("Poll interrupted: \"{err}\""); continue; } log::error!("Poll error: {}", err); diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 486e764..0177ba6 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -82,7 +82,7 @@ pub mod receiver { use std::time::{Duration, Instant}; use mio::net::{TcpListener, TcpStream}; - use mio::{Events, Interest, Poll, Token}; + use mio::{Events, Interest, Poll}; const POLL_TIMEOUT: Duration = Duration::from_millis(250); const RECEIVE_TIMEOUT: Duration = Duration::from_secs(3); @@ -199,11 +199,12 @@ pub mod receiver { listener: Option, stream: Option, - mio_poll_token: Token, + mio_poll_token: mio::Token, mio_poll: Poll, receive_buffer: usize, } + impl TcpReceiver { pub fn new( test_definition: super::TcpTestDefinition, @@ -216,9 +217,6 @@ pub mod receiver { let listener: TcpListener = port_pool.bind(peer_ip).expect("failed to bind TCP socket"); log::debug!("bound TCP listener for stream {}: {}", stream_idx, listener.local_addr()?); - let mio_poll_token = Token(0); - let mio_poll = Poll::new()?; - Ok(TcpReceiver { active: AtomicBool::new(true), test_definition, @@ -226,8 +224,8 @@ pub mod receiver { listener: Some(listener), stream: None, - mio_poll_token, - mio_poll, + mio_poll_token: crate::get_global_token(), + mio_poll: Poll::new()?, receive_buffer: receive_buffer.to_owned(), }) @@ -237,16 +235,19 @@ pub mod receiver { log::debug!("preparing to receive TCP stream {} connection...", self.stream_idx); let listener = self.listener.as_mut().unwrap(); - let mio_token = Token(0); + let mio_token = crate::get_global_token(); let mut poll = Poll::new()?; poll.registry().register(listener, mio_token, Interest::READABLE)?; let mut events = Events::with_capacity(1); let start = Instant::now(); - while self.active.load(Relaxed) { + let result: super::BoxResult = 'exiting: loop { + if !self.active.load(Relaxed) { + break 'exiting Err(Box::new(simple_error::simple_error!("local shutdown requested"))); + } if start.elapsed() >= RECEIVE_TIMEOUT { - return Err(Box::new(simple_error::simple_error!( + break 'exiting Err(Box::new(simple_error::simple_error!( "TCP listening for stream {} timed out", self.stream_idx ))); @@ -263,13 +264,13 @@ pub mod receiver { break; } Err(e) => { - return Err(Box::new(e)); + break 'exiting Err(Box::new(e)); } }; log::debug!("received TCP stream {} connection from {}", self.stream_idx, address); - let mio_token2 = Token(0); + let mio_token2 = crate::get_global_token(); let mut poll2 = Poll::new()?; poll2.registry().register(&mut stream, mio_token2, Interest::READABLE)?; @@ -284,14 +285,16 @@ pub mod receiver { // client didn't provide anything break; } - return Err(Box::new(e)); + break 'exiting Err(Box::new(e)); } if buffer == self.test_definition.test_id { log::debug!("validated TCP stream {} connection from {}", self.stream_idx, address); + poll2.registry().deregister(&mut stream)?; + // NOTE: features unsupported on Windows - #[cfg(not(windows))] + #[cfg(unix)] if self.receive_buffer != 0 { log::debug!("setting receive-buffer to {}...", self.receive_buffer); @@ -304,16 +307,18 @@ pub mod receiver { self.mio_poll .registry() .register(&mut stream, self.mio_poll_token, Interest::READABLE)?; - return Ok(stream); + break 'exiting Ok(stream); } } log::warn!("could not validate TCP stream {} connection from {}", self.stream_idx, address); } } - } - Err(Box::new(simple_error::simple_error!("did not receive a connection"))) + }; + poll.registry().deregister(listener)?; + result } } + impl super::TestStream for TcpReceiver { fn run_interval(&mut self) -> Option>> { let mut bytes_received: u64 = 0; @@ -522,7 +527,7 @@ pub mod sender { }; // NOTE: features unsupported on Windows - #[cfg(not(windows))] + #[cfg(unix)] if self.send_buffer != 0 { log::debug!("setting send-buffer to {}...", self.send_buffer); super::setsockopt(&raw_stream, super::SndBuf, &self.send_buffer)?; diff --git a/src/stream/udp.rs b/src/stream/udp.rs index c796604..0f5c0e6 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -227,12 +227,11 @@ pub mod receiver { log::debug!("binding UDP receive socket for stream {}...", stream_idx); let socket: UdpSocket = port_pool.bind(peer_ip).expect("failed to bind UDP socket"); socket.set_read_timeout(Some(READ_TIMEOUT))?; - if !cfg!(windows) { - //NOTE: features unsupported on Windows - if *receive_buffer != 0 { - log::debug!("setting receive-buffer to {}...", receive_buffer); - super::setsockopt(&socket, super::RcvBuf, receive_buffer)?; - } + // NOTE: features unsupported on Windows + #[cfg(unix)] + if *receive_buffer != 0 { + log::debug!("setting receive-buffer to {}...", receive_buffer); + super::setsockopt(&socket, super::RcvBuf, receive_buffer)?; } log::debug!("bound UDP receive socket for stream {}: {}", stream_idx, socket.local_addr()?); @@ -535,12 +534,11 @@ pub mod sender { .unwrap_or_else(|_| panic!("failed to bind UDP socket, port {}", port)), }; socket.set_write_timeout(Some(WRITE_TIMEOUT))?; - if !cfg!(windows) { - //NOTE: features unsupported on Windows - if *send_buffer != 0 { - log::debug!("setting send-buffer to {}...", send_buffer); - super::setsockopt(&socket, super::SndBuf, send_buffer)?; - } + // NOTE: features unsupported on Windows + #[cfg(unix)] + if *send_buffer != 0 { + log::debug!("setting send-buffer to {}...", send_buffer); + super::setsockopt(&socket, super::SndBuf, send_buffer)?; } socket.connect(socket_addr_receiver)?; log::debug!("connected UDP stream {} to {}", stream_idx, socket_addr_receiver); From 4cbf40b86a719b9edfeb2c401e899fbadb399802 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:16:19 +0800 Subject: [PATCH 13/18] windows issues --- .github/workflows/rust.yml | 2 +- Cargo.toml | 1 + src/client.rs | 8 ++++-- src/main.rs | 6 ++--- src/protocol/communication.rs | 6 ++--- src/server.rs | 46 ++++++++++++++++++++++++++--------- src/stream/tcp.rs | 10 +++++--- src/stream/udp.rs | 11 +++++---- 8 files changed, 59 insertions(+), 31 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d6447b1..5b86d40 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,7 +13,7 @@ jobs: host_os: - ubuntu-latest - macos-latest - # - windows-latest + - windows-latest runs-on: ${{ matrix.host_os }} diff --git a/Cargo.toml b/Cargo.toml index 669080c..8439edf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ categories = ["network-utilities"] readme = "README.md" [dependencies] +cfg-if = "1.0" chrono = "0.4" clap = { version = "4.4", features = ["derive", "wrap_help"] } core_affinity = "0.8" diff --git a/src/client.rs b/src/client.rs index 23dfb6d..79286f3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -67,8 +67,12 @@ fn connect_to_server(address: &str, port: &u16) -> BoxResult { let socket: socket2::Socket = socket2::Socket::from(stream); let keepalive = socket2::TcpKeepalive::new() .with_time(KEEPALIVE_DURATION) - .with_interval(KEEPALIVE_DURATION) - .with_retries(4); + .with_interval(KEEPALIVE_DURATION); + cfg_if::cfg_if! { + if #[cfg(unix)] { + let keepalive = keepalive.with_retries(4); + } + } socket.set_tcp_keepalive(&keepalive)?; socket.set_nodelay(true)?; diff --git a/src/main.rs b/src/main.rs index 7652014..f3460c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,8 +41,7 @@ fn main() -> BoxResult<()> { std::process::exit(3); } true - }) - .expect("unable to set SIGINT handler"); + })?; log::debug!("beginning normal operation..."); server::serve(&args)?; @@ -57,8 +56,7 @@ fn main() -> BoxResult<()> { std::process::exit(3); } true - }) - .expect("unable to set SIGINT handler"); + })?; log::debug!("connecting to server..."); client::execute(&args)?; diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index 871722c..38c1159 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -27,7 +27,7 @@ use mio::{Events, Interest, Poll}; use crate::BoxResult; /// how long to wait for keepalive events -// the communications channels typically exchange data every second, so 2s is reasonable to avoid excess noise +/// the communications channels typically exchange data every second, so 2s is reasonable to avoid excess noise pub const KEEPALIVE_DURATION: Duration = Duration::from_secs(2); /// how long to block on polling operations @@ -101,8 +101,8 @@ fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_han }; poll.registry().deregister(stream)?; result - // Err(Box::new(simple_error::simple_error!("system shutting down"))) } + /// receives the data-value of a pending message over a client-server communications stream fn receive_payload( stream: &mut TcpStream, @@ -166,8 +166,8 @@ fn receive_payload( }; poll.registry().deregister(stream)?; result - // Err(Box::new(simple_error::simple_error!("system shutting down"))) } + /// handles the full process of retrieving a message from a client-server communications stream pub fn receive( stream: &mut TcpStream, diff --git a/src/server.rs b/src/server.rs index a52d1ee..c87637a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -45,10 +45,20 @@ static ALIVE: AtomicBool = AtomicBool::new(true); static CLIENTS: AtomicU16 = AtomicU16::new(0); fn tcp_stream_try_clone(stream: &TcpStream) -> BoxResult { - use std::os::fd::{AsRawFd, BorrowedFd}; - let fd = unsafe { BorrowedFd::borrow_raw(stream.as_raw_fd()) }; - let fd = fd.try_clone_to_owned()?; - let socket: socket2::Socket = socket2::Socket::from(fd); + cfg_if::cfg_if! { + if #[cfg(unix)] { + use std::os::fd::{AsRawFd, BorrowedFd}; + let fd = unsafe { BorrowedFd::borrow_raw(stream.as_raw_fd()) }; + let fd = fd.try_clone_to_owned()?; + let socket: socket2::Socket = socket2::Socket::from(fd); + } else { + use std::os::windows::io::{AsRawSocket, BorrowedSocket}; + let socket = unsafe { BorrowedSocket::borrow_raw(stream.as_raw_socket()) }; + let socket = socket.try_clone_to_owned()?; + let socket: socket2::Socket = socket2::Socket::from(socket); + } + } + let stream: std::net::TcpStream = socket.into(); let socket = TcpStream::from_std(stream); Ok(socket) @@ -359,14 +369,26 @@ pub fn serve(args: &Args) -> BoxResult<()> { log::info!("connection from {}", address); let mut stream = { - use std::os::fd::{FromRawFd, IntoRawFd}; - let fd = stream.into_raw_fd(); - let socket: socket2::Socket = unsafe { socket2::Socket::from_raw_fd(fd) }; - - let keepalive = socket2::TcpKeepalive::new() - .with_time(KEEPALIVE_DURATION) - .with_interval(KEEPALIVE_DURATION) - .with_retries(4); + cfg_if::cfg_if! { + if #[cfg(unix)] { + use std::os::fd::{FromRawFd, IntoRawFd}; + let fd = stream.into_raw_fd(); + let socket: socket2::Socket = unsafe { socket2::Socket::from_raw_fd(fd) }; + + let keepalive = socket2::TcpKeepalive::new() + .with_time(KEEPALIVE_DURATION) + .with_interval(KEEPALIVE_DURATION) + .with_retries(4); + } else { + // Only Windows supports raw sockets + use std::os::windows::io::{FromRawSocket, IntoRawSocket}; + let socket = stream.into_raw_socket(); + let socket: socket2::Socket = unsafe { socket2::Socket::from_raw_socket(socket) }; + let keepalive = socket2::TcpKeepalive::new() + .with_time(KEEPALIVE_DURATION) + .with_interval(KEEPALIVE_DURATION); + } + } socket.set_tcp_keepalive(&keepalive)?; socket.set_nodelay(true)?; diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 0177ba6..ab42963 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -18,8 +18,6 @@ * along with rperf. If not, see . */ -use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; - use crate::protocol::results::{get_unix_timestamp, IntervalResult, TcpReceiveResult, TcpSendResult}; use crate::BoxResult; @@ -192,6 +190,7 @@ pub mod receiver { } } + #[allow(dead_code)] pub struct TcpReceiver { active: AtomicBool, test_definition: super::TcpTestDefinition, @@ -300,7 +299,8 @@ pub mod receiver { use std::os::fd::{FromRawFd, IntoRawFd}; let raw_stream = unsafe { std::net::TcpStream::from_raw_fd(stream.into_raw_fd()) }; - super::setsockopt(&raw_stream, super::RcvBuf, &self.receive_buffer)?; + use nix::sys::socket::{setsockopt, sockopt::RcvBuf}; + setsockopt(&raw_stream, RcvBuf, &self.receive_buffer)?; stream = unsafe { TcpStream::from_raw_fd(raw_stream.into_raw_fd()) }; } @@ -457,6 +457,7 @@ pub mod sender { const WRITE_TIMEOUT: Duration = Duration::from_millis(50); const BUFFER_FULL_TIMEOUT: Duration = Duration::from_millis(1); + #[allow(dead_code)] pub struct TcpSender { active: bool, test_definition: super::TcpTestDefinition, @@ -530,7 +531,8 @@ pub mod sender { #[cfg(unix)] if self.send_buffer != 0 { log::debug!("setting send-buffer to {}...", self.send_buffer); - super::setsockopt(&raw_stream, super::SndBuf, &self.send_buffer)?; + use nix::sys::socket::{setsockopt, sockopt::SndBuf}; + setsockopt(&raw_stream, SndBuf, &self.send_buffer)?; } raw_stream.set_write_timeout(Some(WRITE_TIMEOUT))?; diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 0f5c0e6..539ebee 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -18,8 +18,6 @@ * along with rperf. If not, see . */ -use nix::sys::socket::{setsockopt, sockopt::RcvBuf, sockopt::SndBuf}; - use crate::protocol::results::{get_unix_timestamp, IntervalResult, UdpReceiveResult, UdpSendResult}; use crate::BoxResult; @@ -217,6 +215,7 @@ pub mod receiver { socket: UdpSocket, } impl UdpReceiver { + #[allow(unused_variables)] pub fn new( test_definition: super::UdpTestDefinition, stream_idx: &u8, @@ -231,7 +230,8 @@ pub mod receiver { #[cfg(unix)] if *receive_buffer != 0 { log::debug!("setting receive-buffer to {}...", receive_buffer); - super::setsockopt(&socket, super::RcvBuf, receive_buffer)?; + use nix::sys::socket::{setsockopt, sockopt::RcvBuf}; + setsockopt(&socket, RcvBuf, receive_buffer)?; } log::debug!("bound UDP receive socket for stream {}: {}", stream_idx, socket.local_addr()?); @@ -514,7 +514,7 @@ pub mod sender { staged_packet: Vec, } impl UdpSender { - #[allow(clippy::too_many_arguments)] + #[allow(clippy::too_many_arguments, unused_variables)] pub fn new( test_definition: super::UdpTestDefinition, stream_idx: &u8, @@ -538,7 +538,8 @@ pub mod sender { #[cfg(unix)] if *send_buffer != 0 { log::debug!("setting send-buffer to {}...", send_buffer); - super::setsockopt(&socket, super::SndBuf, send_buffer)?; + use nix::sys::socket::{setsockopt, sockopt::SndBuf}; + setsockopt(&socket, SndBuf, send_buffer)?; } socket.connect(socket_addr_receiver)?; log::debug!("connected UDP stream {} to {}", stream_idx, socket_addr_receiver); From f5778a447d6d388989e35794610e76370a953b45 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:38:00 +0800 Subject: [PATCH 14/18] Complete adjustments as recommended. --- src/main.rs | 11 +++++++++-- src/server.rs | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index f3460c3..07dc205 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,10 @@ fn main() -> BoxResult<()> { })?; log::debug!("beginning normal operation..."); - server::serve(&args)?; + if let Err(err) = server::serve(&args) { + log::error!("unable to run server: {}", err); + std::process::exit(4); + } exiting.join().expect("unable to join SIGINT handler thread"); } else if args.client.is_some() { log::debug!("registering SIGINT handler..."); @@ -59,11 +62,15 @@ fn main() -> BoxResult<()> { })?; log::debug!("connecting to server..."); - client::execute(&args)?; + if let Err(err) = client::execute(&args) { + log::error!("unable to run client: {}", err); + std::process::exit(4); + } } else { use clap::CommandFactory; let mut cmd = args::Args::command(); cmd.print_help().unwrap(); + std::process::exit(2); } Ok(()) } diff --git a/src/server.rs b/src/server.rs index c87637a..932ca82 100644 --- a/src/server.rs +++ b/src/server.rs @@ -346,10 +346,10 @@ pub fn serve(args: &Args) -> BoxResult<()> { while is_alive() { if let Err(err) = poll.poll(&mut events, Some(POLL_TIMEOUT)) { if err.kind() == std::io::ErrorKind::Interrupted { - log::debug!("Poll interrupted: \"{err}\""); + log::debug!("poll interrupted, \"{err}\" ignored; resuming poll"); continue; } - log::error!("Poll error: {}", err); + log::error!("poll error: {}", err); break; } for event in events.iter() { From ec65bb943556acfcbc430d714ff040c3ad9f5074 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 20 Dec 2023 08:11:27 +0800 Subject: [PATCH 15/18] fix issues that always timeout in windows --- Cargo.toml | 1 - src/args.rs | 4 +- src/client.rs | 39 ++--- src/protocol/communication.rs | 192 +++++++++++----------- src/server.rs | 107 +++---------- src/stream/tcp.rs | 289 ++++++++++++++++++++-------------- src/stream/udp.rs | 68 ++++---- 7 files changed, 331 insertions(+), 369 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8439edf..a835a01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ ctrlc2 = { version = "3.5", features = ["termination"] } env_logger = "0.10" log = { version = "0.4", features = ["std"] } mio = { version = "0.8", features = ["log", "os-poll", "net"] } -nix = { version = "0.27", features = ["net"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" simple-error = "0.3" diff --git a/src/args.rs b/src/args.rs index 82f7c23..88f3a5e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -92,12 +92,12 @@ pub struct Args { /// send buffer, in bytes (only supported on some platforms; /// if set too small, a 'resource unavailable' error may occur; - /// affects TCP window-size) + /// affects UDP and TCP window-size) #[arg(long, default_value = "0", value_name = "bytes", conflicts_with = "server")] pub send_buffer: usize, /// receive buffer, in bytes (only supported on some platforms; - /// if set too small, a 'resource unavailable' error may occur; affects TCP window-size) + /// if set too small, a 'resource unavailable' error may occur; affects UDP) #[arg(long, default_value = "0", value_name = "bytes", conflicts_with = "server")] pub receive_buffer: usize, diff --git a/src/client.rs b/src/client.rs index 79286f3..f70f506 100644 --- a/src/client.rs +++ b/src/client.rs @@ -21,7 +21,7 @@ use crate::{ args, protocol::{ - communication::{receive, send, KEEPALIVE_DURATION}, + communication::{receive, send}, messaging::{prepare_begin, prepare_download_configuration, prepare_end, prepare_upload_configuration}, results::{ClientDoneResult, ClientFailedResult}, results::{IntervalResultBox, IntervalResultKind, TcpTestResults, TestResults, UdpTestResults}, @@ -29,9 +29,8 @@ use crate::{ stream::{tcp, udp, TestStream}, BoxResult, }; -use mio::net::TcpStream; use std::{ - net::{IpAddr, Shutdown, ToSocketAddrs}, + net::{IpAddr, Shutdown, TcpStream, ToSocketAddrs}, sync::{ atomic::{AtomicBool, Ordering}, mpsc::channel, @@ -63,23 +62,16 @@ fn connect_to_server(address: &str, port: &u16) -> BoxResult { Err(e) => return Err(Box::new(simple_error::simple_error!("unable to connect: {}", e))), }; - let stream = { - let socket: socket2::Socket = socket2::Socket::from(stream); - let keepalive = socket2::TcpKeepalive::new() - .with_time(KEEPALIVE_DURATION) - .with_interval(KEEPALIVE_DURATION); - cfg_if::cfg_if! { - if #[cfg(unix)] { - let keepalive = keepalive.with_retries(4); - } - } - socket.set_tcp_keepalive(&keepalive)?; - socket.set_nodelay(true)?; - - let stream: std::net::TcpStream = socket.into(); + log::debug!("connected TCP control-channel to {}", destination); + stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); - TcpStream::from_std(stream) - }; + #[cfg(unix)] + { + use crate::protocol::communication::KEEPALIVE_DURATION; + let keepalive_parameters = socket2::TcpKeepalive::new().with_time(KEEPALIVE_DURATION); + let raw_socket = socket2::SockRef::from(&stream); + raw_socket.set_tcp_keepalive(&keepalive_parameters)?; + } log::info!("connected to server"); @@ -222,13 +214,8 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { let test_definition = tcp::TcpTestDefinition::new(&download_config)?; for stream_idx in 0..stream_count { log::debug!("preparing TCP-receiver for stream {}...", stream_idx); - let test = tcp::receiver::TcpReceiver::new( - test_definition.clone(), - &(stream_idx as u8), - &mut tcp_port_pool, - &server_addr.ip(), - &(download_config["receive_buffer"].as_i64().unwrap() as usize), - )?; + let test = + tcp::receiver::TcpReceiver::new(test_definition.clone(), &(stream_idx as u8), &mut tcp_port_pool, &server_addr.ip())?; stream_ports.push(test.get_port()?); parallel_streams.push(Arc::new(Mutex::new(test))); } diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index 38c1159..ab90bfc 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -19,22 +19,26 @@ */ use std::io::{self, Read, Write}; -use std::time::Duration; - -use mio::net::TcpStream; -use mio::{Events, Interest, Poll}; +use std::net::TcpStream; +use std::time::{Duration, Instant}; use crate::BoxResult; /// how long to wait for keepalive events /// the communications channels typically exchange data every second, so 2s is reasonable to avoid excess noise -pub const KEEPALIVE_DURATION: Duration = Duration::from_secs(2); +#[cfg(unix)] +pub const KEEPALIVE_DURATION: Duration = Duration::from_secs(3); /// how long to block on polling operations const POLL_TIMEOUT: Duration = Duration::from_millis(50); +/// how long to allow for send-operations to complete +const SEND_TIMEOUT: Duration = Duration::from_secs(5); + /// sends JSON data over a client-server communications stream pub fn send(stream: &mut TcpStream, message: &serde_json::Value) -> BoxResult<()> { + stream.set_write_timeout(Some(POLL_TIMEOUT))?; + let serialised_message = serde_json::to_vec(message)?; log::debug!( @@ -46,61 +50,72 @@ pub fn send(stream: &mut TcpStream, message: &serde_json::Value) -> BoxResult<() let mut output_buffer = vec![0_u8; serialised_message.len() + 2]; output_buffer[..2].copy_from_slice(&(serialised_message.len() as u16).to_be_bytes()); output_buffer[2..].copy_from_slice(serialised_message.as_slice()); - Ok(stream.write_all(&output_buffer)?) + + let start = Instant::now(); + let mut total_bytes_written: usize = 0; + + while start.elapsed() < SEND_TIMEOUT { + match stream.write(&output_buffer[total_bytes_written..]) { + Ok(bytes_written) => { + total_bytes_written += bytes_written; + if total_bytes_written == output_buffer.len() { + return Ok(()); + } + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => { + // unable to write at the moment; keep trying until the full timeout is reached + continue; + } + Err(e) => { + return Err(Box::new(e)); + } + } + } + Err(Box::new(simple_error::simple_error!( + "timed out while attempting to send status-message to {}", + stream.peer_addr()? + ))) } /// receives the length-count of a pending message over a client-server communications stream fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_handler: &mut dyn FnMut() -> BoxResult<()>) -> BoxResult { - let mio_token = crate::get_global_token(); - let mut poll = Poll::new()?; - poll.registry().register(stream, mio_token, Interest::READABLE)?; - let mut events = Events::with_capacity(1); //only interacting with one stream + stream.set_read_timeout(Some(POLL_TIMEOUT)).expect("unable to set TCP read-timeout"); let mut length_bytes_read = 0; let mut length_spec: [u8; 2] = [0; 2]; - let result: BoxResult = 'exiting: loop { - if !alive_check() { - break 'exiting Ok(0); - } + while alive_check() { //waiting to find out how long the next message is results_handler()?; //send any outstanding results between cycles - poll.poll(&mut events, Some(POLL_TIMEOUT))?; - for event in events.iter() { - event.token(); - loop { - let size = match stream.read(&mut length_spec[length_bytes_read..]) { - Ok(size) => size, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - //nothing left to process - break; - } - Err(e) => { - break 'exiting Err(Box::new(e)); - } - }; - - if size == 0 { - if alive_check() { - break 'exiting Err(Box::new(simple_error::simple_error!("connection lost"))); - } else { - //shutting down; a disconnect is expected - break 'exiting Err(Box::new(simple_error::simple_error!("local shutdown requested"))); - } - } - length_bytes_read += size; - if length_bytes_read == 2 { - let length = u16::from_be_bytes(length_spec); - log::debug!("received length-spec of {} from {}", length, stream.peer_addr()?); - break 'exiting Ok(length); - } else { - log::debug!("received partial length-spec from {}", stream.peer_addr()?); - } + let size = match stream.read(&mut length_spec[length_bytes_read..]) { + Ok(size) => size, + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => { + // nothing available to process + continue; + } + Err(e) => { + return Err(Box::new(e)); + } + }; + if size == 0 { + if alive_check() { + return Err(Box::new(simple_error::simple_error!("connection lost"))); + } else { + // shutting down; a disconnect is expected + return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); } } - }; - poll.registry().deregister(stream)?; - result + + length_bytes_read += size; + if length_bytes_read == 2 { + let length = u16::from_be_bytes(length_spec); + log::debug!("received length-spec of {} from {}", length, stream.peer_addr()?); + return Ok(length); + } else { + log::debug!("received partial length-spec from {}", stream.peer_addr()?); + } + } + Err(Box::new(simple_error::simple_error!("system shutting down"))) } /// receives the data-value of a pending message over a client-server communications stream @@ -110,62 +125,49 @@ fn receive_payload( results_handler: &mut dyn FnMut() -> BoxResult<()>, length: u16, ) -> BoxResult { - let mio_token = crate::get_global_token(); - let mut poll = Poll::new()?; - poll.registry().register(stream, mio_token, Interest::READABLE)?; - let mut events = Events::with_capacity(1); //only interacting with one stream + stream.set_read_timeout(Some(POLL_TIMEOUT)).expect("unable to set TCP read-timeout"); let mut bytes_read = 0; let mut buffer = vec![0_u8; length.into()]; - let result: BoxResult = 'exiting: loop { - if !alive_check() { - break 'exiting Ok(serde_json::from_slice(&buffer[0..0])?); - } + while alive_check() { //waiting to receive the payload results_handler()?; //send any outstanding results between cycles - poll.poll(&mut events, Some(POLL_TIMEOUT))?; - for event in events.iter() { - event.token(); - loop { - let size = match stream.read(&mut buffer[bytes_read..]) { - Ok(size) => size, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - // nothing left to process - break; - } - Err(e) => { - break 'exiting Err(Box::new(e)); - } - }; - - if size == 0 { - if alive_check() { - break 'exiting Err(Box::new(simple_error::simple_error!("connection lost"))); - } else { - // shutting down; a disconnect is expected - break 'exiting Err(Box::new(simple_error::simple_error!("local shutdown requested"))); - } - } - bytes_read += size; - if bytes_read == length as usize { - match serde_json::from_slice(&buffer) { - Ok(v) => { - log::debug!("received {:?} from {}", v, stream.peer_addr()?); - break 'exiting Ok(v); - } - Err(e) => { - break 'exiting Err(Box::new(e)); - } - } - } else { - log::debug!("received partial payload from {}", stream.peer_addr()?); + let size = match stream.read(&mut buffer[bytes_read..]) { + Ok(size) => size, + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => { + // nothing available to process + continue; + } + Err(e) => { + return Err(Box::new(e)); + } + }; + if size == 0 { + if alive_check() { + return Err(Box::new(simple_error::simple_error!("connection lost"))); + } else { + //shutting down; a disconnect is expected + return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); + } + } + + bytes_read += size; + if bytes_read == length as usize { + match serde_json::from_slice(&buffer) { + Ok(v) => { + log::debug!("received {:?} from {}", v, stream.peer_addr()?); + return Ok(v); + } + Err(e) => { + return Err(Box::new(e)); } } + } else { + log::debug!("received partial payload from {}", stream.peer_addr()?); } - }; - poll.registry().deregister(stream)?; - result + } + Err(Box::new(simple_error::simple_error!("system shutting down"))) } /// handles the full process of retrieving a message from a client-server communications stream diff --git a/src/server.rs b/src/server.rs index 932ca82..9bfeda0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -26,11 +26,10 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use mio::net::{TcpListener, TcpStream}; -use mio::{Events, Interest, Poll}; +use std::net::{TcpListener, TcpStream}; use crate::args::Args; -use crate::protocol::communication::{receive, send, KEEPALIVE_DURATION}; +use crate::protocol::communication::{receive, send}; use crate::protocol::messaging::{prepare_connect, prepare_connect_ready}; use crate::protocol::results::ServerDoneResult; use crate::stream::{tcp, udp, TestStream}; @@ -44,26 +43,6 @@ static ALIVE: AtomicBool = AtomicBool::new(true); /// a count of connected clients static CLIENTS: AtomicU16 = AtomicU16::new(0); -fn tcp_stream_try_clone(stream: &TcpStream) -> BoxResult { - cfg_if::cfg_if! { - if #[cfg(unix)] { - use std::os::fd::{AsRawFd, BorrowedFd}; - let fd = unsafe { BorrowedFd::borrow_raw(stream.as_raw_fd()) }; - let fd = fd.try_clone_to_owned()?; - let socket: socket2::Socket = socket2::Socket::from(fd); - } else { - use std::os::windows::io::{AsRawSocket, BorrowedSocket}; - let socket = unsafe { BorrowedSocket::borrow_raw(stream.as_raw_socket()) }; - let socket = socket.try_clone_to_owned()?; - let socket: socket2::Socket = socket2::Socket::from(socket); - } - } - - let stream: std::net::TcpStream = socket.into(); - let socket = TcpStream::from_std(stream); - Ok(socket) -} - fn handle_client( stream: &mut TcpStream, cpu_affinity_manager: Arc>, @@ -83,7 +62,7 @@ fn handle_client( ) = channel(); //a closure used to pass results from stream-handlers to the client-communication stream - let mut forwarding_send_stream = tcp_stream_try_clone(stream)?; + let mut forwarding_send_stream = stream.try_clone()?; let mut results_handler = || -> BoxResult<()> { // drain all results every time this closer is invoked while let Ok(result) = results_rx.try_recv() { @@ -144,7 +123,6 @@ fn handle_client( &(stream_idx as u8), &mut c_tcp_port_pool, &peer_addr.ip(), - &(payload["receive_buffer"].as_i64().unwrap() as usize), )?; stream_ports.push(test.get_port()?); parallel_streams.push(Arc::new(Mutex::new(test))); @@ -276,7 +254,7 @@ fn handle_client( log::debug!("[{}] stopping any still-in-progress streams", &peer_addr); for ps in parallel_streams.iter_mut() { - let mut stream = match (*ps).lock() { + let mut test_stream = match (*ps).lock() { Ok(guard) => guard, Err(poisoned) => { log::error!( @@ -286,7 +264,7 @@ fn handle_client( poisoned.into_inner() } }; - stream.stop(); + test_stream.stop(); } log::debug!("[{}] waiting for all streams to end", &peer_addr); for jh in parallel_streams_joinhandles { @@ -334,68 +312,24 @@ pub fn serve(args: &Args) -> BoxResult<()> { //start listening for connections let port: u16 = args.port; - let mut listener = - TcpListener::bind(SocketAddr::new(args.bind, port)).unwrap_or_else(|_| panic!("failed to bind TCP socket, port {}", port)); + let listener = TcpListener::bind(SocketAddr::new(args.bind, port))?; + listener.set_nonblocking(true)?; log::info!("server listening on {}", listener.local_addr()?); - let mio_token = crate::get_global_token(); - let mut poll = Poll::new()?; - poll.registry().register(&mut listener, mio_token, Interest::READABLE)?; - let mut events = Events::with_capacity(32); - while is_alive() { - if let Err(err) = poll.poll(&mut events, Some(POLL_TIMEOUT)) { - if err.kind() == std::io::ErrorKind::Interrupted { - log::debug!("poll interrupted, \"{err}\" ignored; resuming poll"); - continue; - } - log::error!("poll error: {}", err); - break; - } - for event in events.iter() { - event.token(); - loop { - let (stream, address) = match listener.accept() { - Ok((stream, address)) => (stream, address), - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - // nothing to do - break; - } - Err(e) => { - return Err(Box::new(e)); - } - }; - + match listener.accept() { + Ok((mut stream, address)) => { log::info!("connection from {}", address); - let mut stream = { - cfg_if::cfg_if! { - if #[cfg(unix)] { - use std::os::fd::{FromRawFd, IntoRawFd}; - let fd = stream.into_raw_fd(); - let socket: socket2::Socket = unsafe { socket2::Socket::from_raw_fd(fd) }; - - let keepalive = socket2::TcpKeepalive::new() - .with_time(KEEPALIVE_DURATION) - .with_interval(KEEPALIVE_DURATION) - .with_retries(4); - } else { - // Only Windows supports raw sockets - use std::os::windows::io::{FromRawSocket, IntoRawSocket}; - let socket = stream.into_raw_socket(); - let socket: socket2::Socket = unsafe { socket2::Socket::from_raw_socket(socket) }; - let keepalive = socket2::TcpKeepalive::new() - .with_time(KEEPALIVE_DURATION) - .with_interval(KEEPALIVE_DURATION); - } - } - socket.set_tcp_keepalive(&keepalive)?; - - socket.set_nodelay(true)?; + stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); - let stream: std::net::TcpStream = socket.into(); - mio::net::TcpStream::from_std(stream) - }; + #[cfg(unix)] + { + use crate::protocol::communication::KEEPALIVE_DURATION; + let keepalive_parameters = socket2::TcpKeepalive::new().with_time(KEEPALIVE_DURATION); + let raw_socket = socket2::SockRef::from(&stream); + raw_socket.set_tcp_keepalive(&keepalive_parameters)?; + } let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; if client_limit > 0 && client_count > client_limit { @@ -423,6 +357,13 @@ pub fn serve(args: &Args) -> BoxResult<()> { })?; } } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + //no pending clients + thread::sleep(POLL_TIMEOUT); + } + Err(e) => { + return Err(Box::new(e)); + } } } diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index ab42963..0d2ca85 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -25,6 +25,9 @@ use super::{parse_port_spec, TestStream, INTERVAL}; pub const TEST_HEADER_SIZE: usize = 16; +#[cfg(unix)] +const KEEPALIVE_DURATION: std::time::Duration = std::time::Duration::from_secs(5); + #[derive(Clone)] pub struct TcpTestDefinition { //a UUID used to identify packets associated with this test @@ -80,9 +83,9 @@ pub mod receiver { use std::time::{Duration, Instant}; use mio::net::{TcpListener, TcpStream}; - use mio::{Events, Interest, Poll}; const POLL_TIMEOUT: Duration = Duration::from_millis(250); + const CONNECTION_TIMEOUT: Duration = Duration::from_secs(1); const RECEIVE_TIMEOUT: Duration = Duration::from_secs(3); pub struct TcpPortPool { @@ -198,10 +201,10 @@ pub mod receiver { listener: Option, stream: Option, - mio_poll_token: mio::Token, - mio_poll: Poll, - receive_buffer: usize, + mio_events: mio::Events, + mio_poll: mio::Poll, + mio_token: mio::Token, } impl TcpReceiver { @@ -210,12 +213,18 @@ pub mod receiver { stream_idx: &u8, port_pool: &mut TcpPortPool, peer_ip: &IpAddr, - receive_buffer: &usize, ) -> super::BoxResult { log::debug!("binding TCP listener for stream {}...", stream_idx); - let listener: TcpListener = port_pool.bind(peer_ip).expect("failed to bind TCP socket"); + let mut listener: TcpListener = port_pool.bind(peer_ip).expect("failed to bind TCP socket"); log::debug!("bound TCP listener for stream {}: {}", stream_idx, listener.local_addr()?); + let _port = listener.local_addr()?.port(); + + let mio_events = mio::Events::with_capacity(1); + let mio_poll = mio::Poll::new()?; + let mio_token = crate::get_global_token(); + mio_poll.registry().register(&mut listener, mio_token, mio::Interest::READABLE)?; + Ok(TcpReceiver { active: AtomicBool::new(true), test_definition, @@ -223,99 +232,127 @@ pub mod receiver { listener: Some(listener), stream: None, - mio_poll_token: crate::get_global_token(), - mio_poll: Poll::new()?, - - receive_buffer: receive_buffer.to_owned(), + mio_events, + mio_token, + mio_poll, }) } - fn process_connection(&mut self) -> super::BoxResult { + fn process_connection(&mut self) -> super::BoxResult<(TcpStream, u64, f32)> { log::debug!("preparing to receive TCP stream {} connection...", self.stream_idx); let listener = self.listener.as_mut().unwrap(); - let mio_token = crate::get_global_token(); - let mut poll = Poll::new()?; - poll.registry().register(listener, mio_token, Interest::READABLE)?; - let mut events = Events::with_capacity(1); + let mio_token = self.mio_token; let start = Instant::now(); - let result: super::BoxResult = 'exiting: loop { + loop { if !self.active.load(Relaxed) { - break 'exiting Err(Box::new(simple_error::simple_error!("local shutdown requested"))); + return Err(Box::new(simple_error::simple_error!("local shutdown requested"))); } if start.elapsed() >= RECEIVE_TIMEOUT { - break 'exiting Err(Box::new(simple_error::simple_error!( - "TCP listening for stream {} timed out", - self.stream_idx - ))); + let err = simple_error::simple_error!("TCP listening for stream {} timed out", self.stream_idx); + return Err(Box::new(err)); } - poll.poll(&mut events, Some(POLL_TIMEOUT))?; - for event in events.iter() { - event.token(); - loop { - let (mut stream, address) = match listener.accept() { - Ok((stream, address)) => (stream, address), - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - // nothing to do - break; - } - Err(e) => { - break 'exiting Err(Box::new(e)); - } - }; + // assigned upon establishing a connection + let mut stream: Option = None; - log::debug!("received TCP stream {} connection from {}", self.stream_idx, address); + self.mio_poll.poll(&mut self.mio_events, Some(POLL_TIMEOUT))?; + for event in self.mio_events.iter() { + if event.token() != mio_token { + log::warn!("got event for unbound token: {:?}", event); + continue; + } - let mio_token2 = crate::get_global_token(); - let mut poll2 = Poll::new()?; - poll2.registry().register(&mut stream, mio_token2, Interest::READABLE)?; + let (mut new_stream, address) = match listener.accept() { + Ok((new_stream, address)) => (new_stream, address), + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // no pending connections available + break; + } + Err(e) => { + return Err(Box::new(e)); + } + }; - let mut buffer = [0_u8; 16]; - let mut events2 = Events::with_capacity(1); - poll2.poll(&mut events2, Some(RECEIVE_TIMEOUT))?; - for event2 in events2.iter() { - event2.token(); + log::debug!("received TCP stream {} connection from {}", self.stream_idx, address); - if let Err(e) = stream.read(&mut buffer) { - if e.kind() == std::io::ErrorKind::WouldBlock { - // client didn't provide anything - break; - } - break 'exiting Err(Box::new(e)); - } + // hand over flow to the new connection + self.mio_poll.registry().deregister(listener)?; + self.mio_poll + .registry() + .register(&mut new_stream, mio_token, mio::Interest::READABLE)?; + stream = Some(new_stream); + break; + } - if buffer == self.test_definition.test_id { - log::debug!("validated TCP stream {} connection from {}", self.stream_idx, address); + if stream.is_none() { + // no pending connections available + continue; + } + let mut unwrapped_stream = stream.unwrap(); - poll2.registry().deregister(&mut stream)?; + // process the stream + let mut buf = vec![0_u8; self.test_definition.length]; + let mut validated: bool = false; + let mut bytes_received: u64 = 0; - // NOTE: features unsupported on Windows - #[cfg(unix)] - if self.receive_buffer != 0 { - log::debug!("setting receive-buffer to {}...", self.receive_buffer); + let start_validation = Instant::now(); - use std::os::fd::{FromRawFd, IntoRawFd}; - let raw_stream = unsafe { std::net::TcpStream::from_raw_fd(stream.into_raw_fd()) }; - use nix::sys::socket::{setsockopt, sockopt::RcvBuf}; - setsockopt(&raw_stream, RcvBuf, &self.receive_buffer)?; - stream = unsafe { TcpStream::from_raw_fd(raw_stream.into_raw_fd()) }; + self.mio_poll.poll(&mut self.mio_events, Some(CONNECTION_TIMEOUT))?; + for event in self.mio_events.iter() { + if event.token() == mio_token { + loop { + let packet_size = match unwrapped_stream.read(&mut buf) { + Ok(packet_size) => packet_size, + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => { + // end of input reached + break; + } + Err(e) => { + let _ = unwrapped_stream.shutdown(std::net::Shutdown::Both); + self.mio_poll.registry().deregister(&mut unwrapped_stream)?; + return Err(Box::new(e)); } + }; + + if !validated { + if buf[..16] == self.test_definition.test_id { + log::debug!( + "validated TCP stream {} connection from {}", + self.stream_idx, + unwrapped_stream.peer_addr()? + ); + validated = true; + } else { + log::warn!( + "unexpected ID in stream {} connection from {}", + self.stream_idx, + unwrapped_stream.peer_addr()? + ); + break; + } + } - self.mio_poll - .registry() - .register(&mut stream, self.mio_poll_token, Interest::READABLE)?; - break 'exiting Ok(stream); + if validated { + bytes_received += packet_size as u64; } } - log::warn!("could not validate TCP stream {} connection from {}", self.stream_idx, address); } } - }; - poll.registry().deregister(listener)?; - result + if validated { + return Ok((unwrapped_stream, bytes_received, start_validation.elapsed().as_secs_f32())); + } + + let _ = unwrapped_stream.shutdown(std::net::Shutdown::Both); + self.mio_poll.registry().deregister(&mut unwrapped_stream)?; + log::warn!( + "could not validate TCP stream {} connection from {}", + self.stream_idx, + unwrapped_stream.peer_addr()? + ); + } } } @@ -323,13 +360,15 @@ pub mod receiver { fn run_interval(&mut self) -> Option>> { let mut bytes_received: u64 = 0; + let mut additional_time_elapsed: f32 = 0.0; if self.stream.is_none() { //if still in the setup phase, receive the sender match self.process_connection() { - Ok(stream) => { + Ok((stream, bytes_received_in_validation, time_spent_in_validation)) => { self.stream = Some(stream); - //NOTE: the connection process consumes the test-header; account for those bytes - bytes_received += super::TEST_HEADER_SIZE as u64; + // NOTE: the connection process consumes packets; account for those bytes + bytes_received += bytes_received_in_validation; + additional_time_elapsed += time_spent_in_validation; } Err(e) => { return Some(Err(e)); @@ -339,7 +378,7 @@ pub mod receiver { } let stream = self.stream.as_mut().unwrap(); - let mut events = Events::with_capacity(1); //only watching one socket + let mio_token = self.mio_token; let mut buf = vec![0_u8; self.test_definition.length]; let peer_addr = match stream.peer_addr() { @@ -348,26 +387,15 @@ pub mod receiver { }; let start = Instant::now(); - while self.active.load(Relaxed) { - if start.elapsed() >= RECEIVE_TIMEOUT { - return Some(Err(Box::new(simple_error::simple_error!( - "TCP reception for stream {} from {} timed out", - self.stream_idx, - peer_addr - )))); - } - + while self.active.load(Relaxed) && start.elapsed() < super::INTERVAL { log::trace!("awaiting TCP stream {} from {}...", self.stream_idx, peer_addr); - let poll_result = self.mio_poll.poll(&mut events, Some(POLL_TIMEOUT)); - if let Err(err) = poll_result { - return Some(Err(Box::new(err))); - } - for event in events.iter() { - if event.token() == self.mio_poll_token { + self.mio_poll.poll(&mut self.mio_events, Some(POLL_TIMEOUT)).ok()?; + for event in self.mio_events.iter() { + if event.token() == mio_token { loop { let packet_size = match stream.read(&mut buf) { Ok(packet_size) => packet_size, - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => { // receive timeout break; } @@ -390,19 +418,6 @@ pub mod receiver { } bytes_received += packet_size as u64; - - let elapsed_time = start.elapsed(); - if elapsed_time >= super::INTERVAL { - return Some(Ok(Box::new(super::TcpReceiveResult { - timestamp: super::get_unix_timestamp(), - - stream_idx: self.stream_idx, - - duration: elapsed_time.as_secs_f32(), - - bytes_received, - }))); - } } } else { log::warn!("got event for unbound token: {:?}", event); @@ -410,16 +425,27 @@ pub mod receiver { } } if bytes_received > 0 { + log::debug!( + "{} bytes received via TCP stream {} from {} in this interval; reporting...", + bytes_received, + self.stream_idx, + peer_addr + ); Some(Ok(Box::new(super::TcpReceiveResult { timestamp: super::get_unix_timestamp(), stream_idx: self.stream_idx, - duration: start.elapsed().as_secs_f32(), + duration: start.elapsed().as_secs_f32() + additional_time_elapsed, bytes_received, }))) } else { + log::debug!( + "no bytes received via TCP stream {} from {} in this interval", + self.stream_idx, + peer_addr + ); None } } @@ -446,12 +472,9 @@ pub mod receiver { pub mod sender { use std::io::Write; - use std::net::{IpAddr, SocketAddr}; - use std::time::{Duration, Instant}; - - use mio::net::TcpStream; - + use std::net::{IpAddr, SocketAddr, TcpStream}; use std::thread::sleep; + use std::time::{Duration, Instant}; const CONNECT_TIMEOUT: Duration = Duration::from_secs(2); const WRITE_TIMEOUT: Duration = Duration::from_millis(50); @@ -516,33 +539,38 @@ pub mod sender { fn process_connection(&mut self) -> super::BoxResult { log::debug!("preparing to connect TCP stream {}...", self.stream_idx); - let raw_stream = match std::net::TcpStream::connect_timeout(&self.socket_addr, CONNECT_TIMEOUT) { + let stream = match TcpStream::connect_timeout(&self.socket_addr, CONNECT_TIMEOUT) { Ok(s) => s, Err(e) => { - return Err(Box::new(simple_error::simple_error!( - "unable to connect stream {}: {}", - self.stream_idx, - e - ))) + let err = simple_error::simple_error!("unable to connect stream {}: {}", self.stream_idx, e); + return Err(Box::new(err)); } }; + stream.set_nonblocking(true)?; + stream.set_write_timeout(Some(WRITE_TIMEOUT))?; + // NOTE: features unsupported on Windows #[cfg(unix)] - if self.send_buffer != 0 { - log::debug!("setting send-buffer to {}...", self.send_buffer); - use nix::sys::socket::{setsockopt, sockopt::SndBuf}; - setsockopt(&raw_stream, SndBuf, &self.send_buffer)?; + { + use crate::stream::tcp::KEEPALIVE_DURATION; + let keepalive_parameters = socket2::TcpKeepalive::new().with_time(KEEPALIVE_DURATION); + let raw_socket = socket2::SockRef::from(&stream); + raw_socket.set_tcp_keepalive(&keepalive_parameters)?; } - raw_stream.set_write_timeout(Some(WRITE_TIMEOUT))?; - let stream = TcpStream::from_std(raw_stream); log::debug!("connected TCP stream {} to {}", self.stream_idx, stream.peer_addr()?); if self.no_delay { log::debug!("setting no-delay..."); stream.set_nodelay(true)?; } + #[cfg(unix)] + if self.send_buffer != 0 { + log::debug!("setting send-buffer to {}...", self.send_buffer); + let raw_socket = socket2::SockRef::from(&stream); + raw_socket.set_send_buffer_size(self.send_buffer)?; + } Ok(stream) } } @@ -611,6 +639,12 @@ pub mod sender { if elapsed_time >= super::INTERVAL { self.remaining_duration -= packet_start.elapsed().as_secs_f32(); + log::debug!( + "{} bytes sent via TCP stream {} to {} in this interval; reporting...", + bytes_sent, + self.stream_idx, + peer_addr + ); return Some(Ok(Box::new(super::TcpSendResult { timestamp: super::get_unix_timestamp(), @@ -642,6 +676,12 @@ pub mod sender { self.remaining_duration -= packet_start.elapsed().as_secs_f32(); } if bytes_sent > 0 { + log::debug!( + "{} bytes sent via TCP stream {} to {} in this interval; reporting...", + bytes_sent, + self.stream_idx, + peer_addr + ); Some(Ok(Box::new(super::TcpSendResult { timestamp: super::get_unix_timestamp(), @@ -653,6 +693,11 @@ pub mod sender { sends_blocked, }))) } else { + log::debug!( + "no bytes sent via TCP stream {} to {} in this interval; shutting down...", + self.stream_idx, + peer_addr + ); //indicate that the test is over by dropping the stream self.stream = None; None diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 539ebee..82c27c4 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -74,17 +74,13 @@ impl UdpTestDefinition { } pub mod receiver { + use chrono::NaiveDateTime; use std::convert::TryInto; - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; use std::sync::Mutex; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; - use chrono::NaiveDateTime; - - use std::net::UdpSocket; - const READ_TIMEOUT: Duration = Duration::from_millis(50); - const RECEIVE_TIMEOUT: Duration = Duration::from_secs(3); pub struct UdpPortPool { pub ports_ip4: Vec, @@ -230,8 +226,8 @@ pub mod receiver { #[cfg(unix)] if *receive_buffer != 0 { log::debug!("setting receive-buffer to {}...", receive_buffer); - use nix::sys::socket::{setsockopt, sockopt::RcvBuf}; - setsockopt(&socket, RcvBuf, receive_buffer)?; + let raw_socket = socket2::SockRef::from(&socket); + raw_socket.set_recv_buffer_size(*receive_buffer)?; } log::debug!("bound UDP receive socket for stream {}: {}", stream_idx, socket.local_addr()?); @@ -377,19 +373,12 @@ pub mod receiver { let start = Instant::now(); - while self.active { - if start.elapsed() >= RECEIVE_TIMEOUT { - return Some(Err(Box::new(simple_error::simple_error!( - "UDP reception for stream {} timed out, likely because the end-signal was lost", - self.stream_idx - )))); - } - + while self.active && start.elapsed() < super::INTERVAL { log::trace!("awaiting UDP packets on stream {}...", self.stream_idx); loop { let (packet_size, peer_addr) = match self.socket.recv_from(&mut buf) { Ok((packet_size, peer_addr)) => (packet_size, peer_addr), - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => { // receive timeout break; } @@ -425,26 +414,6 @@ pub mod receiver { if self.process_packet(&buf, &mut history) { // NOTE: duplicate packets increase this count; this is intentional because the stack still processed data bytes_received += packet_size as u64 + super::UDP_HEADER_SIZE as u64; - - let elapsed_time = start.elapsed(); - if elapsed_time >= super::INTERVAL { - return Some(Ok(Box::new(super::UdpReceiveResult { - timestamp: super::get_unix_timestamp(), - - stream_idx: self.stream_idx, - - duration: elapsed_time.as_secs_f32(), - - bytes_received, - packets_received: history.packets_received, - packets_lost: history.packets_lost, - packets_out_of_order: history.packets_out_of_order, - packets_duplicated: history.packets_duplicated, - - unbroken_sequence: history.longest_unbroken_sequence, - jitter_seconds: history.longest_jitter_seconds, - }))); - } } else { log::warn!("received packet unrelated to UDP stream {} from {}", self.stream_idx, peer_addr); continue; @@ -452,6 +421,11 @@ pub mod receiver { } } if bytes_received > 0 { + log::debug!( + "{} bytes received via UDP stream {} in this interval; reporting...", + bytes_received, + self.stream_idx + ); Some(Ok(Box::new(super::UdpReceiveResult { timestamp: super::get_unix_timestamp(), @@ -469,6 +443,7 @@ pub mod receiver { jitter_seconds: history.longest_jitter_seconds, }))) } else { + log::debug!("no bytes received via UDP stream {} in this interval", self.stream_idx); None } } @@ -538,8 +513,8 @@ pub mod sender { #[cfg(unix)] if *send_buffer != 0 { log::debug!("setting send-buffer to {}...", send_buffer); - use nix::sys::socket::{setsockopt, sockopt::SndBuf}; - setsockopt(&socket, SndBuf, send_buffer)?; + let raw_socket = socket2::SockRef::from(&socket); + raw_socket.set_send_buffer_size(*send_buffer)?; } socket.connect(socket_addr_receiver)?; log::debug!("connected UDP stream {} to {}", stream_idx, socket_addr_receiver); @@ -614,7 +589,11 @@ pub mod sender { let elapsed_time = cycle_start.elapsed(); if elapsed_time >= super::INTERVAL { self.remaining_duration -= packet_start.elapsed().as_secs_f32(); - + log::debug!( + "{} bytes sent via UDP stream {} in this interval; reporting...", + bytes_sent, + self.stream_idx + ); return Some(Ok(Box::new(super::UdpSendResult { timestamp: super::get_unix_timestamp(), @@ -658,6 +637,11 @@ pub mod sender { self.remaining_duration -= packet_start.elapsed().as_secs_f32(); } if bytes_sent > 0 { + log::debug!( + "{} bytes sent via UDP stream {} in this interval; reporting...", + bytes_sent, + self.stream_idx + ); Some(Ok(Box::new(super::UdpSendResult { timestamp: super::get_unix_timestamp(), @@ -670,6 +654,10 @@ pub mod sender { sends_blocked, }))) } else { + log::debug!( + "no bytes sent via UDP stream {} in this interval; shutting down...", + self.stream_idx + ); //indicate that the test is over by sending the test ID by itself let mut remaining_announcements = 5; while remaining_announcements > 0 { From db7106be313547f3d47caf55e6afaeb8bde9355b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 20 Dec 2023 22:49:49 +0800 Subject: [PATCH 16/18] refine code --- Cargo.toml | 1 - src/client.rs | 4 +- src/protocol/communication.rs | 16 +++--- src/protocol/results.rs | 4 +- src/server.rs | 101 +++++++++++++++++----------------- src/stream/mod.rs | 6 +- src/stream/tcp.rs | 35 ++++++------ src/stream/udp.rs | 34 +++++++----- 8 files changed, 100 insertions(+), 101 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a835a01..207c59a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ categories = ["network-utilities"] readme = "README.md" [dependencies] -cfg-if = "1.0" chrono = "0.4" clap = { version = "4.4", features = ["derive", "wrap_help"] } core_affinity = "0.8" diff --git a/src/client.rs b/src/client.rs index f70f506..2b59d8b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -100,8 +100,8 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { let mut complete = false; //config-parsing and pre-connection setup - let mut tcp_port_pool = tcp::receiver::TcpPortPool::new(args.tcp_port_pool.to_string(), args.tcp6_port_pool.to_string()); - let mut udp_port_pool = udp::receiver::UdpPortPool::new(args.udp_port_pool.to_string(), args.udp6_port_pool.to_string()); + let mut tcp_port_pool = tcp::receiver::TcpPortPool::new(&args.tcp_port_pool, &args.tcp6_port_pool); + let mut udp_port_pool = udp::receiver::UdpPortPool::new(&args.udp_port_pool, &args.udp6_port_pool); let cpu_affinity_manager = Arc::new(Mutex::new(crate::utils::cpu_affinity::CpuAffinityManager::new(&args.affinity)?)); diff --git a/src/protocol/communication.rs b/src/protocol/communication.rs index ab90bfc..8316b79 100644 --- a/src/protocol/communication.rs +++ b/src/protocol/communication.rs @@ -42,10 +42,10 @@ pub fn send(stream: &mut TcpStream, message: &serde_json::Value) -> BoxResult<() let serialised_message = serde_json::to_vec(message)?; log::debug!( - "sending message of length {}, {:?}, to {}...", + "sending message to {}, length {}, {:?}...", + stream.peer_addr()?, serialised_message.len(), message, - stream.peer_addr()? ); let mut output_buffer = vec![0_u8; serialised_message.len() + 2]; output_buffer[..2].copy_from_slice(&(serialised_message.len() as u16).to_be_bytes()); @@ -71,21 +71,19 @@ pub fn send(stream: &mut TcpStream, message: &serde_json::Value) -> BoxResult<() } } } - Err(Box::new(simple_error::simple_error!( - "timed out while attempting to send status-message to {}", - stream.peer_addr()? - ))) + let err = simple_error::simple_error!("timed out while attempting to send status-message to {}", stream.peer_addr()?); + Err(Box::new(err)) } /// receives the length-count of a pending message over a client-server communications stream -fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, results_handler: &mut dyn FnMut() -> BoxResult<()>) -> BoxResult { +fn receive_length(stream: &mut TcpStream, alive_check: fn() -> bool, handler: &mut dyn FnMut() -> BoxResult<()>) -> BoxResult { stream.set_read_timeout(Some(POLL_TIMEOUT)).expect("unable to set TCP read-timeout"); let mut length_bytes_read = 0; let mut length_spec: [u8; 2] = [0; 2]; while alive_check() { //waiting to find out how long the next message is - results_handler()?; //send any outstanding results between cycles + handler()?; //send any outstanding results between cycles let size = match stream.read(&mut length_spec[length_bytes_read..]) { Ok(size) => size, @@ -156,7 +154,7 @@ fn receive_payload( if bytes_read == length as usize { match serde_json::from_slice(&buffer) { Ok(v) => { - log::debug!("received {:?} from {}", v, stream.peer_addr()?); + log::debug!("received message from {}: {:?}", stream.peer_addr()?, v); return Ok(v); } Err(e) => { diff --git a/src/protocol/results.rs b/src/protocol/results.rs index 037e601..88db0cd 100644 --- a/src/protocol/results.rs +++ b/src/protocol/results.rs @@ -58,7 +58,7 @@ pub trait IntervalResult { fn to_string(&self, bit: bool) -> String; } -pub type IntervalResultBox = Box; +pub type IntervalResultBox = Box; pub struct ClientDoneResult { pub stream_idx: u8, @@ -477,7 +477,7 @@ impl IntervalResult for UdpSendResult { } } -pub fn interval_result_from_json(value: serde_json::Value) -> BoxResult> { +pub fn interval_result_from_json(value: serde_json::Value) -> BoxResult { match value.get("family") { Some(f) => match f.as_str() { Some(family) => match family { diff --git a/src/server.rs b/src/server.rs index 9bfeda0..e686c31 100644 --- a/src/server.rs +++ b/src/server.rs @@ -21,8 +21,7 @@ use std::io; use std::net::{Shutdown, SocketAddr}; use std::sync::atomic::{AtomicBool, AtomicU16, Ordering}; -use std::sync::mpsc::channel; -use std::sync::{Arc, Mutex}; +use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::Duration; @@ -31,7 +30,7 @@ use std::net::{TcpListener, TcpStream}; use crate::args::Args; use crate::protocol::communication::{receive, send}; use crate::protocol::messaging::{prepare_connect, prepare_connect_ready}; -use crate::protocol::results::ServerDoneResult; +use crate::protocol::results::{IntervalResultBox, ServerDoneResult}; use crate::stream::{tcp, udp, TestStream}; use crate::BoxResult; @@ -56,10 +55,7 @@ fn handle_client( let mut parallel_streams: Vec>> = Vec::new(); let mut parallel_streams_joinhandles = Vec::new(); - let (results_tx, results_rx): ( - std::sync::mpsc::Sender, - std::sync::mpsc::Receiver, - ) = channel(); + let (results_tx, results_rx) = mpsc::channel::(); //a closure used to pass results from stream-handlers to the client-communication stream let mut forwarding_send_stream = stream.try_clone()?; @@ -295,12 +291,12 @@ impl Drop for ClientThreadMonitor { pub fn serve(args: &Args) -> BoxResult<()> { //config-parsing and pre-connection setup let tcp_port_pool = Arc::new(Mutex::new(tcp::receiver::TcpPortPool::new( - args.tcp_port_pool.to_string(), - args.tcp6_port_pool.to_string(), + &args.tcp_port_pool, + &args.tcp6_port_pool, ))); let udp_port_pool = Arc::new(Mutex::new(udp::receiver::UdpPortPool::new( - args.udp_port_pool.to_string(), - args.udp6_port_pool.to_string(), + &args.udp_port_pool, + &args.udp6_port_pool, ))); let cpu_affinity_manager = Arc::new(Mutex::new(crate::utils::cpu_affinity::CpuAffinityManager::new(&args.affinity)?)); @@ -317,53 +313,54 @@ pub fn serve(args: &Args) -> BoxResult<()> { log::info!("server listening on {}", listener.local_addr()?); while is_alive() { - match listener.accept() { - Ok((mut stream, address)) => { - log::info!("connection from {}", address); - - stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); - - #[cfg(unix)] - { - use crate::protocol::communication::KEEPALIVE_DURATION; - let keepalive_parameters = socket2::TcpKeepalive::new().with_time(KEEPALIVE_DURATION); - let raw_socket = socket2::SockRef::from(&stream); - raw_socket.set_tcp_keepalive(&keepalive_parameters)?; - } - - let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; - if client_limit > 0 && client_count > client_limit { - log::warn!("client-limit ({}) reached; disconnecting {}...", client_limit, address.to_string()); - stream.shutdown(Shutdown::Both).unwrap_or_default(); - CLIENTS.fetch_sub(1, Ordering::Relaxed); - } else { - let c_cam = cpu_affinity_manager.clone(); - let c_tcp_port_pool = tcp_port_pool.clone(); - let c_udp_port_pool = udp_port_pool.clone(); - let thread_builder = thread::Builder::new().name(address.to_string()); - thread_builder.spawn(move || { - //ensure the client is accounted-for even if the handler panics - let _client_thread_monitor = ClientThreadMonitor { - client_address: address.to_string(), - }; - - match handle_client(&mut stream, c_cam, c_tcp_port_pool, c_udp_port_pool) { - Ok(_) => (), - Err(e) => log::error!("error in client-handler: {}", e), - } - - //in the event of panic, this will happen when the stream is dropped - stream.shutdown(Shutdown::Both).unwrap_or_default(); - })?; - } - } + let (mut stream, address) = match listener.accept() { + Ok((stream, address)) => (stream, address), Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - //no pending clients + // no pending clients thread::sleep(POLL_TIMEOUT); + continue; } Err(e) => { return Err(Box::new(e)); } + }; + + log::info!("connection from {}", address); + + stream.set_nodelay(true).expect("cannot disable Nagle's algorithm"); + + #[cfg(unix)] + { + use crate::protocol::communication::KEEPALIVE_DURATION; + let keepalive_parameters = socket2::TcpKeepalive::new().with_time(KEEPALIVE_DURATION); + let raw_socket = socket2::SockRef::from(&stream); + raw_socket.set_tcp_keepalive(&keepalive_parameters)?; + } + + let client_count = CLIENTS.fetch_add(1, Ordering::Relaxed) + 1; + if client_limit > 0 && client_count > client_limit { + log::warn!("client-limit ({}) reached; disconnecting {}...", client_limit, address.to_string()); + stream.shutdown(Shutdown::Both).unwrap_or_default(); + CLIENTS.fetch_sub(1, Ordering::Relaxed); + } else { + let c_cam = cpu_affinity_manager.clone(); + let c_tcp_port_pool = tcp_port_pool.clone(); + let c_udp_port_pool = udp_port_pool.clone(); + let thread_builder = thread::Builder::new().name(address.to_string()); + thread_builder.spawn(move || { + // ensure the client is accounted-for even if the handler panics + let _client_thread_monitor = ClientThreadMonitor { + client_address: address.to_string(), + }; + + match handle_client(&mut stream, c_cam, c_tcp_port_pool, c_udp_port_pool) { + Ok(_) => (), + Err(e) => log::error!("error in client-handler: {}", e), + } + + //in the event of panic, this will happen when the stream is dropped + stream.shutdown(Shutdown::Both).unwrap_or_default(); + })?; } } diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 49767d2..1cd0455 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -21,7 +21,7 @@ pub mod tcp; pub mod udp; -use crate::BoxResult; +use crate::{protocol::results::IntervalResultBox, BoxResult}; pub const INTERVAL: std::time::Duration = std::time::Duration::from_secs(1); @@ -30,7 +30,7 @@ pub const INTERVAL: std::time::Duration = std::time::Duration::from_secs(1); /// INTERVAL while gathering data. pub trait TestStream { /// gather data; returns None when the test is over - fn run_interval(&mut self) -> Option>; + fn run_interval(&mut self) -> Option>; /// return the port associated with the test-stream; this may vary over the test's lifetime fn get_port(&self) -> BoxResult; /// returns the index of the test, used to match client and server data @@ -39,7 +39,7 @@ pub trait TestStream { fn stop(&mut self); } -fn parse_port_spec(port_spec: String) -> Vec { +fn parse_port_spec(port_spec: &str) -> Vec { let mut ports = Vec::::new(); if !port_spec.is_empty() { for range in port_spec.split(',') { diff --git a/src/stream/tcp.rs b/src/stream/tcp.rs index 0d2ca85..fdae5b0 100644 --- a/src/stream/tcp.rs +++ b/src/stream/tcp.rs @@ -18,11 +18,10 @@ * along with rperf. If not, see . */ -use crate::protocol::results::{get_unix_timestamp, IntervalResult, TcpReceiveResult, TcpSendResult}; +use crate::protocol::results::{get_unix_timestamp, TcpReceiveResult, TcpSendResult}; +use crate::stream::{parse_port_spec, TestStream, INTERVAL}; use crate::BoxResult; -use super::{parse_port_spec, TestStream, INTERVAL}; - pub const TEST_HEADER_SIZE: usize = 16; #[cfg(unix)] @@ -38,7 +37,7 @@ pub struct TcpTestDefinition { pub length: usize, } impl TcpTestDefinition { - pub fn new(details: &serde_json::Value) -> super::BoxResult { + pub fn new(details: &serde_json::Value) -> BoxResult { let mut test_id_bytes = [0_u8; 16]; for (i, v) in details .get("test_id") @@ -76,13 +75,14 @@ impl TcpTestDefinition { } pub mod receiver { + use mio::net::{TcpListener, TcpStream}; use std::io::Read; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; use std::sync::Mutex; use std::time::{Duration, Instant}; - use mio::net::{TcpListener, TcpStream}; + use crate::{protocol::results::IntervalResultBox, BoxResult}; const POLL_TIMEOUT: Duration = Duration::from_millis(250); const CONNECTION_TIMEOUT: Duration = Duration::from_secs(1); @@ -97,7 +97,7 @@ pub mod receiver { lock_ip6: Mutex, } impl TcpPortPool { - pub fn new(port_spec: String, port_spec6: String) -> TcpPortPool { + pub fn new(port_spec: &str, port_spec6: &str) -> TcpPortPool { let ports = super::parse_port_spec(port_spec); if !ports.is_empty() { log::debug!("configured IPv4 TCP port pool: {:?}", ports); @@ -123,7 +123,7 @@ pub mod receiver { } } - pub fn bind(&mut self, peer_ip: &IpAddr) -> super::BoxResult { + pub fn bind(&mut self, peer_ip: &IpAddr) -> BoxResult { match peer_ip { IpAddr::V6(_) => { if self.ports_ip6.is_empty() { @@ -193,7 +193,6 @@ pub mod receiver { } } - #[allow(dead_code)] pub struct TcpReceiver { active: AtomicBool, test_definition: super::TcpTestDefinition, @@ -213,7 +212,7 @@ pub mod receiver { stream_idx: &u8, port_pool: &mut TcpPortPool, peer_ip: &IpAddr, - ) -> super::BoxResult { + ) -> BoxResult { log::debug!("binding TCP listener for stream {}...", stream_idx); let mut listener: TcpListener = port_pool.bind(peer_ip).expect("failed to bind TCP socket"); log::debug!("bound TCP listener for stream {}: {}", stream_idx, listener.local_addr()?); @@ -238,7 +237,7 @@ pub mod receiver { }) } - fn process_connection(&mut self) -> super::BoxResult<(TcpStream, u64, f32)> { + fn process_connection(&mut self) -> BoxResult<(TcpStream, u64, f32)> { log::debug!("preparing to receive TCP stream {} connection...", self.stream_idx); let listener = self.listener.as_mut().unwrap(); @@ -357,7 +356,7 @@ pub mod receiver { } impl super::TestStream for TcpReceiver { - fn run_interval(&mut self) -> Option>> { + fn run_interval(&mut self) -> Option> { let mut bytes_received: u64 = 0; let mut additional_time_elapsed: f32 = 0.0; @@ -450,7 +449,7 @@ pub mod receiver { } } - fn get_port(&self) -> super::BoxResult { + fn get_port(&self) -> BoxResult { match &self.listener { Some(listener) => Ok(listener.local_addr()?.port()), None => match &self.stream { @@ -476,6 +475,8 @@ pub mod sender { use std::thread::sleep; use std::time::{Duration, Instant}; + use crate::{protocol::results::IntervalResultBox, BoxResult}; + const CONNECT_TIMEOUT: Duration = Duration::from_secs(2); const WRITE_TIMEOUT: Duration = Duration::from_millis(50); const BUFFER_FULL_TIMEOUT: Duration = Duration::from_millis(1); @@ -509,7 +510,7 @@ pub mod sender { send_interval: &f32, send_buffer: &usize, no_delay: &bool, - ) -> super::BoxResult { + ) -> BoxResult { let mut staged_buffer = vec![0_u8; test_definition.length]; for (i, staged_buffer_i) in staged_buffer.iter_mut().enumerate().skip(super::TEST_HEADER_SIZE) { //fill the packet with a fixed sequence @@ -536,8 +537,8 @@ pub mod sender { }) } - fn process_connection(&mut self) -> super::BoxResult { - log::debug!("preparing to connect TCP stream {}...", self.stream_idx); + fn process_connection(&mut self) -> BoxResult { + log::debug!("preparing to connect TCP stream {} to {} ...", self.stream_idx, self.socket_addr); let stream = match TcpStream::connect_timeout(&self.socket_addr, CONNECT_TIMEOUT) { Ok(s) => s, @@ -575,7 +576,7 @@ pub mod sender { } } impl super::TestStream for TcpSender { - fn run_interval(&mut self) -> Option>> { + fn run_interval(&mut self) -> Option> { if self.stream.is_none() { //if still in the setup phase, connect to the receiver match self.process_connection() { @@ -704,7 +705,7 @@ pub mod sender { } } - fn get_port(&self) -> super::BoxResult { + fn get_port(&self) -> BoxResult { match &self.stream { Some(stream) => Ok(stream.local_addr()?.port()), None => Err(Box::new(simple_error::simple_error!("no stream currently exists"))), diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 82c27c4..70fb450 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -18,8 +18,10 @@ * along with rperf. If not, see . */ -use crate::protocol::results::{get_unix_timestamp, IntervalResult, UdpReceiveResult, UdpSendResult}; -use crate::BoxResult; +use crate::{ + protocol::results::{get_unix_timestamp, UdpReceiveResult, UdpSendResult}, + BoxResult, +}; use super::{parse_port_spec, TestStream, INTERVAL}; @@ -36,7 +38,7 @@ pub struct UdpTestDefinition { pub length: u16, } impl UdpTestDefinition { - pub fn new(details: &serde_json::Value) -> super::BoxResult { + pub fn new(details: &serde_json::Value) -> BoxResult { let mut test_id_bytes = [0_u8; 16]; for (i, v) in details .get("test_id") @@ -74,6 +76,8 @@ impl UdpTestDefinition { } pub mod receiver { + use crate::protocol::results::IntervalResultBox; + use crate::BoxResult; use chrono::NaiveDateTime; use std::convert::TryInto; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}; @@ -92,7 +96,7 @@ pub mod receiver { lock_ip6: Mutex, } impl UdpPortPool { - pub fn new(port_spec: String, port_spec6: String) -> UdpPortPool { + pub fn new(port_spec: &str, port_spec6: &str) -> UdpPortPool { let ports = super::parse_port_spec(port_spec); if !ports.is_empty() { log::debug!("configured IPv4 UDP port pool: {:?}", ports); @@ -118,7 +122,7 @@ pub mod receiver { } } - pub fn bind(&mut self, peer_ip: &IpAddr) -> super::BoxResult { + pub fn bind(&mut self, peer_ip: &IpAddr) -> BoxResult { match peer_ip { IpAddr::V6(_) => { if self.ports_ip6.is_empty() { @@ -218,7 +222,7 @@ pub mod receiver { port_pool: &mut UdpPortPool, peer_ip: &IpAddr, receive_buffer: &usize, - ) -> super::BoxResult { + ) -> BoxResult { log::debug!("binding UDP receive socket for stream {}...", stream_idx); let socket: UdpSocket = port_pool.bind(peer_ip).expect("failed to bind UDP socket"); socket.set_read_timeout(Some(READ_TIMEOUT))?; @@ -352,7 +356,7 @@ pub mod receiver { } } impl super::TestStream for UdpReceiver { - fn run_interval(&mut self) -> Option>> { + fn run_interval(&mut self) -> Option> { let mut buf = vec![0_u8; self.test_definition.length.into()]; let mut bytes_received: u64 = 0; @@ -448,7 +452,7 @@ pub mod receiver { } } - fn get_port(&self) -> super::BoxResult { + fn get_port(&self) -> BoxResult { let socket_addr = self.socket.local_addr()?; Ok(socket_addr.port()) } @@ -464,12 +468,12 @@ pub mod receiver { } pub mod sender { - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; - + use crate::protocol::results::IntervalResultBox; + use crate::BoxResult; use std::net::UdpSocket; - + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::thread::sleep; + use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; const WRITE_TIMEOUT: Duration = Duration::from_millis(50); const BUFFER_FULL_TIMEOUT: Duration = Duration::from_millis(1); @@ -499,7 +503,7 @@ pub mod sender { send_duration: &f32, send_interval: &f32, send_buffer: &usize, - ) -> super::BoxResult { + ) -> BoxResult { log::debug!("preparing to connect UDP stream {}...", stream_idx); let socket_addr_receiver = SocketAddr::new(*receiver_ip, *receiver_port); let socket = match receiver_ip { @@ -554,7 +558,7 @@ pub mod sender { } } impl super::TestStream for UdpSender { - fn run_interval(&mut self) -> Option>> { + fn run_interval(&mut self) -> Option> { let interval_duration = Duration::from_secs_f32(self.send_interval); let mut interval_iteration = 0; let bytes_to_send = ((self.test_definition.bandwidth as f32) * super::INTERVAL.as_secs_f32()) as i64; @@ -681,7 +685,7 @@ pub mod sender { } } - fn get_port(&self) -> super::BoxResult { + fn get_port(&self) -> BoxResult { let socket_addr = self.socket.local_addr()?; Ok(socket_addr.port()) } From 5c7fa18634bf406458984404b779f4719e09bbc2 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 23 Dec 2023 01:31:06 +0800 Subject: [PATCH 17/18] unit-tests issues fixing --- src/args.rs | 4 ++-- src/client.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/args.rs b/src/args.rs index 88f3a5e..8944b1e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -43,7 +43,7 @@ pub struct Args { /// run in client mode; value is the server's address #[arg(short, long, value_name = "host", conflicts_with = "server")] - pub client: Option, + pub client: Option, /// run in reverse-mode (server sends, client receives) #[arg(short = 'R', long, conflicts_with = "server")] @@ -109,7 +109,7 @@ pub struct Args { /// primarily to avoid including TCP ramp-up in averages; /// using this option may result in disagreement between bytes sent and received, /// since data can be in-flight across time-boundaries - #[arg(short, long, default_value = "0", value_name = "seconds", conflicts_with = "server")] + #[arg(short = 'O', long, default_value = "0", value_name = "seconds", conflicts_with = "server")] pub omit: usize, /// use no-delay mode for TCP tests, disabling Nagle's Algorithm diff --git a/src/client.rs b/src/client.rs index 2b59d8b..72b9a85 100644 --- a/src/client.rs +++ b/src/client.rs @@ -129,7 +129,7 @@ pub fn execute(args: &args::Args) -> BoxResult<()> { let mut download_config = prepare_download_configuration(args, test_id.as_bytes())?; //connect to the server - let mut stream = connect_to_server(&args.client.unwrap().to_string(), &args.port)?; + let mut stream = connect_to_server(args.client.as_ref().unwrap(), &args.port)?; let server_addr = stream.peer_addr()?; //scaffolding to track and relay the streams and stream-results associated with this test From fb68b59543f6dbaa17746797b9648b7acf4e03a3 Mon Sep 17 00:00:00 2001 From: Neil Tallim Date: Mon, 25 Dec 2023 10:58:46 -0700 Subject: [PATCH 18/18] UDP-receive logic grouped all packets into a single interval, which broke resolution-granularity --- src/stream/udp.rs | 73 +++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/stream/udp.rs b/src/stream/udp.rs index 70fb450..4342b09 100644 --- a/src/stream/udp.rs +++ b/src/stream/udp.rs @@ -379,51 +379,50 @@ pub mod receiver { while self.active && start.elapsed() < super::INTERVAL { log::trace!("awaiting UDP packets on stream {}...", self.stream_idx); - loop { - let (packet_size, peer_addr) = match self.socket.recv_from(&mut buf) { - Ok((packet_size, peer_addr)) => (packet_size, peer_addr), - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => { - // receive timeout - break; - } - Err(e) => { - return Some(Err(Box::new(e))); - } - }; + let (packet_size, peer_addr) = match self.socket.recv_from(&mut buf) { + Ok((packet_size, peer_addr)) => (packet_size, peer_addr), + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock || e.kind() == std::io::ErrorKind::TimedOut => { + // receive timeout + continue; + } + Err(e) => { + return Some(Err(Box::new(e))); + } + }; - log::trace!( - "received {} bytes in UDP packet {} from {}", + log::trace!( + "received {} bytes in UDP packet {} from {}", + packet_size, + self.stream_idx, + peer_addr + ); + if packet_size == 16 { + // possible end-of-test message + if buf[0..16] == self.test_definition.test_id { + // test's over + self.stop(); + break; + } + } + if packet_size < super::TEST_HEADER_SIZE as usize { + log::warn!( + "received malformed packet with size {} for UDP stream {} from {}", packet_size, self.stream_idx, peer_addr ); - if packet_size == 16 { - // possible end-of-test message - if buf[0..16] == self.test_definition.test_id { - // test's over - self.stop(); - break; - } - } - if packet_size < super::TEST_HEADER_SIZE as usize { - log::warn!( - "received malformed packet with size {} for UDP stream {} from {}", - packet_size, - self.stream_idx, - peer_addr - ); - continue; - } + continue; + } - if self.process_packet(&buf, &mut history) { - // NOTE: duplicate packets increase this count; this is intentional because the stack still processed data - bytes_received += packet_size as u64 + super::UDP_HEADER_SIZE as u64; - } else { - log::warn!("received packet unrelated to UDP stream {} from {}", self.stream_idx, peer_addr); - continue; - } + if self.process_packet(&buf, &mut history) { + // NOTE: duplicate packets increase this count; this is intentional because the stack still processed data + bytes_received += packet_size as u64 + super::UDP_HEADER_SIZE as u64; + } else { + log::warn!("received packet unrelated to UDP stream {} from {}", self.stream_idx, peer_addr); + continue; } } + if bytes_received > 0 { log::debug!( "{} bytes received via UDP stream {} in this interval; reporting...",