use std::time::{Duration, Instant};
use clap::Parser;
use serialport::SerialPort;
#[derive(Parser)]
struct Args {
port: String,
#[clap(short, long, default_value = "100")]
iterations: usize,
#[clap(short, long, default_value = "8")]
length: usize,
#[clap(short, long, default_value = "115200")]
baudrate: u32,
#[clap(long, use_value_delimiter = true)]
bytes: Option<Vec<u8>>,
#[clap(long)]
split_port: bool,
}
fn main() {
let args = Args::parse();
let mut port = match serialport::new(&args.port, args.baudrate)
.timeout(Duration::MAX)
.open()
{
Err(e) => {
eprintln!("Failed to open \"{}\". Error: {}", args.port, e);
::std::process::exit(1);
}
Ok(p) => p,
};
let length = args.length;
let data: Vec<u8> = args
.bytes
.unwrap_or_else(|| (0..length).map(|i| i as u8).collect());
let (mut read_stats, mut write_stats) = Stats::new(args.iterations, &data);
if args.split_port {
loopback_split(&mut port, &mut read_stats, &mut write_stats);
} else {
loopback_standard(&mut port, &mut read_stats, &mut write_stats);
}
println!("Loopback {}:", args.port);
println!(" data-length: {} bytes", read_stats.data.len());
println!(" iterations: {}", read_stats.iterations);
println!(" read:");
println!(" total: {:.6}s", read_stats.total());
println!(" average: {:.6}s", read_stats.average());
println!(" max: {:.6}s", read_stats.max());
println!(" write:");
println!(" total: {:.6}s", write_stats.total());
println!(" average: {:.6}s", write_stats.average());
println!(" max: {:.6}s", write_stats.max());
println!(" total: {:.6}s", read_stats.total() + write_stats.total());
println!(
" bytes/s: {:.6}",
(read_stats.data.len() as f32) / (read_stats.average() + write_stats.average())
)
}
#[derive(Clone)]
struct Stats<'a> {
pub data: &'a [u8],
pub times: Vec<Duration>,
pub iterations: usize,
now: Instant,
}
impl<'a> Stats<'a> {
fn new(iterations: usize, data: &'a [u8]) -> (Self, Self) {
(
Self {
data,
times: Vec::with_capacity(iterations),
iterations,
now: Instant::now(),
},
Self {
data,
times: Vec::with_capacity(iterations),
iterations,
now: Instant::now(),
},
)
}
fn start(&mut self) {
self.now = Instant::now();
}
fn stop(&mut self) {
self.times.push(self.now.elapsed());
}
fn total(&self) -> f32 {
self.times.iter().map(|d| d.as_secs_f32()).sum()
}
fn average(&self) -> f32 {
self.total() / (self.times.len() as f32)
}
fn max(&self) -> f32 {
self.times
.iter()
.max()
.map(|d| d.as_secs_f32())
.unwrap_or(0.0)
}
}
fn loopback_standard<'a>(
port: &mut Box<dyn SerialPort>,
read_stats: &mut Stats<'a>,
write_stats: &mut Stats<'a>,
) {
let mut buf = vec![0u8; read_stats.data.len()];
for _ in 0..read_stats.iterations {
write_stats.start();
port.write_all(write_stats.data)
.expect("failed to write to serialport");
write_stats.stop();
read_stats.start();
port.read_exact(&mut buf)
.expect("failed to read from serialport");
read_stats.stop();
for (i, x) in buf.iter().enumerate() {
if read_stats.data[i] != *x {
eprintln!(
"Expected byte '{:02X}' but got '{:02X}'",
read_stats.data[i], x
);
::std::process::exit(2);
}
}
}
}
fn loopback_split<'a>(
port: &mut Box<dyn SerialPort>,
read_stats: &mut Stats<'a>,
write_stats: &mut Stats<'a>,
) {
let mut buf = vec![0u8; read_stats.data.len()];
let mut rport = match port.try_clone() {
Ok(p) => p,
Err(e) => {
eprintln!("Failed to clone port: {}", e);
::std::process::exit(3);
}
};
std::thread::scope(|scope| {
let wr_thread = std::thread::current();
let handle = scope.spawn(move || {
for _ in 0..read_stats.iterations {
std::thread::park();
read_stats.start();
rport
.read_exact(&mut buf)
.expect("failed to read from serialport");
read_stats.stop();
for (i, x) in buf.iter().enumerate() {
if read_stats.data[i] != *x {
eprintln!(
"Expected byte '{:02X}' but got '{:02X}'",
read_stats.data[i], x
);
::std::process::exit(2);
}
}
wr_thread.unpark();
}
});
for _ in 0..write_stats.iterations {
write_stats.start();
port.write_all(write_stats.data)
.expect("failed to write to serialport");
write_stats.stop();
handle.thread().unpark();
std::thread::park();
}
});
}