use hrx::{Result, Stream};
use std::time::{Duration, Instant};
struct Options {
seconds: f64,
mib: usize,
mode: Mode,
batch: usize,
}
#[derive(Clone, Copy, PartialEq)]
enum Mode {
Copy,
Fill,
}
impl Mode {
fn traffic(self, size: usize) -> usize {
match self {
Mode::Copy => 2 * size,
Mode::Fill => size,
}
}
fn name(self) -> &'static str {
match self {
Mode::Copy => "copy",
Mode::Fill => "fill",
}
}
}
fn parse() -> std::result::Result<Options, String> {
let mut options = Options {
seconds: 10.0,
mib: 512,
mode: Mode::Copy,
batch: 16,
};
let args: Vec<String> = std::env::args().skip(1).collect();
let mut index = 0;
while index < args.len() {
let value = || -> std::result::Result<String, String> {
args.get(index + 1)
.cloned()
.ok_or_else(|| format!("{} needs a value", args[index]))
};
match args[index].as_str() {
"--seconds" => options.seconds = value()?.parse().map_err(|e| format!("{e}"))?,
"--mib" => options.mib = value()?.parse().map_err(|e| format!("{e}"))?,
"--batch" => options.batch = value()?.parse().map_err(|e| format!("{e}"))?,
"--mode" => {
options.mode = match value()?.as_str() {
"copy" => Mode::Copy,
"fill" => Mode::Fill,
other => return Err(format!("unknown mode {other}")),
}
}
other => return Err(format!("unknown argument {other}")),
}
index += 2;
}
if options.mib == 0
|| options.batch == 0
|| (!options.seconds.is_finite() || options.seconds <= 0.0)
{
return Err("seconds, mib and batch must all be positive".into());
}
Ok(options)
}
fn main() -> Result<()> {
let options = match parse() {
Ok(options) => options,
Err(message) => {
eprintln!("gpu_load: {message}");
std::process::exit(2);
}
};
let size = options.mib * 1024 * 1024;
let mut stream = Stream::open()?;
let source = stream.allocate(size)?;
let destination = stream.allocate(size)?;
stream.fill(source.binding(), 0x3c)?;
stream.fill(destination.binding(), 0)?;
stream.synchronize()?;
let run_batch = |stream: &mut Stream| -> Result<()> {
for _ in 0..options.batch {
match options.mode {
Mode::Copy => stream.copy(destination.binding(), source.binding())?,
Mode::Fill => stream.fill(destination.binding(), 0x5a)?,
}
}
stream.synchronize()
};
run_batch(&mut stream)?;
let window = Duration::from_secs_f64(options.seconds);
let start = Instant::now();
let mut iterations = 0usize;
let mut batch_rates = Vec::new();
while start.elapsed() < window {
let batch_start = Instant::now();
run_batch(&mut stream)?;
let seconds = batch_start.elapsed().as_secs_f64();
iterations += options.batch;
let bytes = options.mode.traffic(size) * options.batch;
batch_rates.push(bytes as f64 / seconds / 1e9);
}
let elapsed = start.elapsed().as_secs_f64();
let total = options.mode.traffic(size) as f64 * iterations as f64;
let mean = total / elapsed / 1e9;
batch_rates.sort_by(f64::total_cmp);
let median = batch_rates[batch_rates.len() / 2];
let low = batch_rates[batch_rates.len() / 20];
println!(
concat!(
r#"{{"device":"gpu","mode":"{}","buffer_mib":{},"batch":{},"#,
r#""seconds":{:.4},"iterations":{},"gb_s_mean":{:.3},"#,
r#""gb_s_median":{:.3},"gb_s_p5":{:.3}}}"#
),
options.mode.name(),
options.mib,
options.batch,
elapsed,
iterations,
mean,
median,
low
);
eprintln!(
"gpu_load: {} {:.1} GB/s mean over {:.1}s ({} iterations)",
options.mode.name(),
mean,
elapsed,
iterations
);
Ok(())
}