use log::{debug, error, info}; use obnam_benchmark::builder::{which_obnam, ObnamBuilder, WhichObnam}; use obnam_benchmark::junk::junk; use obnam_benchmark::report::Reporter; use obnam_benchmark::result::SuiteMeasurements; use obnam_benchmark::specification::Specification; use obnam_benchmark::suite::Suite; use std::fs::File; use std::io::Write; use std::path::PathBuf; use std::process::exit; use structopt::StructOpt; fn main() { pretty_env_logger::init_custom_env("OBNAM_BENCHMARK_LOG"); info!("obnam-benchmark starts"); if let Err(err) = real_main() { eprintln!("ERROR: {}", err); error!("{}", err); exit(1); } info!("obnam-benchmark ends successfully"); } fn real_main() -> anyhow::Result<()> { debug!("parsing command line"); let opt = Opt::from_args(); debug!("parsed: {:#?}", opt); match opt.cmd { Command::GenerateJunk(x) => x.run()?, Command::Run(x) => x.run()?, Command::Spec(x) => x.run()?, Command::Steps(x) => x.run()?, Command::Report(x) => x.run()?, } Ok(()) } #[derive(Debug, StructOpt)] struct Opt { #[structopt(subcommand)] cmd: Command, } #[derive(Debug, StructOpt)] enum Command { /// Run benchmarks Run(Run), /// Dump the specification as JSON Spec(Spec), /// Generate some junk data in a file. GenerateJunk(GenerateJunk), /// Show the steps for running a benchmark. Steps(Steps), /// Report results for several benchmarks. Report(Report), } #[derive(Debug, StructOpt)] struct Run { /// Name of the specification file #[structopt(parse(from_os_str))] spec: PathBuf, /// Name of file where the results will be written. Default is /// stdout. #[structopt(long, parse(from_os_str))] output: Option, /// Construct output filename from benchmark run metadata. #[structopt(short, long)] auto: bool, /// Which Obnam to run? Defaults to installed. Value is /// "installed" or a git commit ref. #[structopt(long, default_value = "installed", parse(try_from_str = which_obnam))] obnam: WhichObnam, } impl Run { fn run(&self) -> anyhow::Result<()> { info!("running benchmarks from {}", self.spec.display()); let spec = Specification::from_file(&self.spec)?; let builder = ObnamBuilder::new(&self.obnam)?; let obnam_version = builder.version()?; let mut suite = Suite::new(&builder)?; let mut result = SuiteMeasurements::new(obnam_version)?; for step in spec.steps().iter() { result.push(suite.execute(step)?); } let output = serde_json::to_string_pretty(&result)?; if let Some(filename) = &self.output { std::fs::write(filename, output)? } else if self.auto { let filename = format!("{}_{}.json", result.hostname(), result.timestamp()); std::fs::write(filename, output)? } else { std::io::stdout().write_all(output.as_bytes())?; }; Ok(()) } } #[derive(Debug, StructOpt)] struct Spec { /// Name of the specification file #[structopt(parse(from_os_str))] spec: PathBuf, /// Name of JSON file to write #[structopt(long, parse(from_os_str))] output: PathBuf, } impl Spec { fn run(&self) -> anyhow::Result<()> { info!("dumping specification file as JSON"); debug!("reading specification from {}", self.spec.display()); let input = File::open(&self.spec)?; let spec: Specification = serde_yaml::from_reader(&input)?; debug!("writing specification as JSON to {}", self.output.display()); let output = File::create(&self.output)?; serde_json::to_writer(&output, &spec)?; Ok(()) } } #[derive(Debug, StructOpt)] struct GenerateJunk { /// Number of bytes of junk to create #[structopt()] bytes: u64, /// Name of the output file #[structopt(parse(from_os_str))] filename: PathBuf, } impl GenerateJunk { fn run(&self) -> anyhow::Result<()> { info!( "generating {} bytes of junk into {}", self.bytes, self.filename.display() ); let mut output = File::create(&self.filename)?; junk(&mut output, self.bytes)?; Ok(()) } } #[derive(Debug, StructOpt)] struct Steps { /// Name of the specification file #[structopt(parse(from_os_str))] spec: PathBuf, } impl Steps { fn run(&self) -> anyhow::Result<()> { info!( "showing steps to run benchmarks from {}", self.spec.display() ); let spec = Specification::from_file(&self.spec)?; for step in spec.steps().iter() { println!("{:?}", step); } Ok(()) } } #[derive(Debug, StructOpt)] struct Report { /// Names of the results file for which to produce a report. #[structopt(parse(from_os_str))] filenames: Vec, } impl Report { fn run(&self) -> anyhow::Result<()> { info!("Reporting results"); let mut report = Reporter::default(); for filename in self.filenames.iter() { report.push(SuiteMeasurements::from_file(filename)?); } report.write(&mut std::io::stdout())?; Ok(()) } }