From 690169f1f6462124facf261030ca50c3443f235f Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Thu, 6 Jan 2022 09:03:32 +0200 Subject: feat: build and run Obnam from git, or run installed version Sponsored-by: author --- src/bin/obnam-benchmark.rs | 12 ++- src/builder.rs | 177 +++++++++++++++++++++++++++++++++++++++++++++ src/client.rs | 26 ++----- src/lib.rs | 1 + src/server.rs | 7 +- src/suite.rs | 25 +++++-- 6 files changed, 218 insertions(+), 30 deletions(-) create mode 100644 src/builder.rs diff --git a/src/bin/obnam-benchmark.rs b/src/bin/obnam-benchmark.rs index 4ac3e4d..8b75229 100644 --- a/src/bin/obnam-benchmark.rs +++ b/src/bin/obnam-benchmark.rs @@ -1,4 +1,5 @@ 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; @@ -75,14 +76,21 @@ struct Run { /// 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 mut suite = Suite::new()?; - let obnam_version = obnam_benchmark::client::ObnamClient::version()?; + 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)?); diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..2de2085 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,177 @@ +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::str::FromStr; +use tempfile::{tempdir, TempDir}; + +const OBNAM_URL: &str = "https://gitlab.com/obnam/obnam.git"; +const COMMIT_DIGITS: usize = 7; + +#[derive(Debug, Clone)] +pub enum WhichObnam { + Installed, + Built(String), +} + +impl FromStr for WhichObnam { + type Err = ObnamBuilderError; + + fn from_str(s: &str) -> Result { + match s { + "installed" => Ok(WhichObnam::Installed), + _ => Ok(WhichObnam::Built(s.to_string())), + } + } +} + +pub fn which_obnam(s: &str) -> Result { + WhichObnam::from_str(s) +} + +#[derive(Debug, thiserror::Error)] +pub enum ObnamBuilderError { + #[error("don't understand which Obnam to use: {0}")] + Unknown(String), + + #[error("failed to create temporary directory for building Obnam: {0}")] + TempDir(std::io::Error), + + #[error("failed to run client binary {0}: {1}")] + Client(PathBuf, std::io::Error), + + #[error("failed to run git: {0}")] + Git(std::io::Error), + + #[error("failed to run cargo: {0}")] + Cargo(std::io::Error), +} + +pub struct ObnamBuilder { + #[allow(dead_code)] + tempdir: TempDir, + client: PathBuf, + server: PathBuf, + commit: Option, +} + +impl ObnamBuilder { + pub fn new(which: &WhichObnam) -> Result { + let tempdir = tempdir().map_err(ObnamBuilderError::TempDir)?; + let builder = match which { + WhichObnam::Installed => Self { + tempdir, + client: Path::new("/usr/bin/obnam").to_path_buf(), + server: Path::new("/usr/bin/obnam-server").to_path_buf(), + commit: None, + }, + WhichObnam::Built(commit) => { + let (commit, bin) = build_obnam(tempdir.path(), commit)?; + let client = bin.join("obnam"); + let server = bin.join("obnam-server"); + assert!(client.exists()); + assert!(server.exists()); + Self { + tempdir, + client, + server, + commit: Some(commit[..COMMIT_DIGITS].to_string()), + } + } + }; + Ok(builder) + } + + pub fn version(&self) -> Result { + if let Some(commit) = &self.commit { + return Ok(commit.to_string()); + } + + let binary = self.client_binary(); + let output = Command::new(binary) + .arg("--version") + .output() + .map_err(|err| ObnamBuilderError::Client(binary.to_path_buf(), err))?; + if output.status.code() != Some(0) { + eprintln!("{}", String::from_utf8_lossy(&output.stdout)); + eprintln!("{}", String::from_utf8_lossy(&output.stderr)); + std::process::exit(1); + } + + let v = String::from_utf8_lossy(&output.stdout); + let v = v.strip_suffix('\n').or(Some(&v)).unwrap().to_string(); + Ok(v) + } + + pub fn client_binary(&self) -> &Path { + &self.client + } + + pub fn server_binary(&self) -> &Path { + &self.server + } +} + +fn build_obnam(dir: &Path, commit: &str) -> Result<(String, PathBuf), ObnamBuilderError> { + let src = dir.join("git"); + let bin = dir.join("bin"); + git_clone(OBNAM_URL, &src)?; + git_create_branch(&src, "build", commit)?; + let commit = git_resolve(&src, commit)?; + cargo_build(&src)?; + cargo_install(&src, dir)?; + Ok((commit, bin)) +} + +fn git_clone(url: &str, dir: &Path) -> Result<(), ObnamBuilderError> { + eprintln!("cloning {} to {}", url, dir.display()); + run( + "git", + &["clone", url, &dir.display().to_string()], + Path::new("."), + ) + .map_err(ObnamBuilderError::Git) + .map(|_| ()) +} + +fn git_create_branch(dir: &Path, branch: &str, commit: &str) -> Result<(), ObnamBuilderError> { + eprintln!("checking out {}", commit); + run("git", &["checkout", "-b", branch, commit], dir) + .map_err(ObnamBuilderError::Git) + .map(|_| ()) +} + +fn git_resolve(dir: &Path, commit: &str) -> Result { + run("git", &["rev-parse", commit], dir) + .map(|s| s.strip_suffix('\n').or(Some("")).unwrap().to_string()) + .map_err(ObnamBuilderError::Git) +} + +fn cargo_build(dir: &Path) -> Result<(), ObnamBuilderError> { + eprintln!("building in {}", dir.display()); + run("cargo", &["build", "--release"], dir) + .map_err(ObnamBuilderError::Git) + .map(|_| ()) +} + +fn cargo_install(src: &Path, bin: &Path) -> Result<(), ObnamBuilderError> { + eprintln!("install to {}", bin.display()); + run( + "cargo", + &["install", "--path=.", "--root", &bin.display().to_string()], + src, + ) + .map_err(ObnamBuilderError::Git) + .map(|_| ()) +} + +fn run(cmd: &str, args: &[&str], cwd: &Path) -> Result { + let output = Command::new(cmd).args(args).current_dir(cwd).output()?; + + if output.status.code() != Some(0) { + eprintln!("{}", String::from_utf8_lossy(&output.stdout)); + eprintln!("{}", String::from_utf8_lossy(&output.stderr)); + std::process::exit(1); + } + + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + Ok(stdout) +} diff --git a/src/client.rs b/src/client.rs index 97b4c61..dd2a624 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,6 +18,7 @@ pub enum ObnamClientError { #[derive(Debug)] pub struct ObnamClient { + binary: PathBuf, #[allow(dead_code)] tempdir: TempDir, #[allow(dead_code)] @@ -25,7 +26,11 @@ pub struct ObnamClient { } impl ObnamClient { - pub fn new(server_url: String, root: PathBuf) -> Result { + pub fn new( + client_binary: &Path, + server_url: String, + root: PathBuf, + ) -> Result { debug!("creating ObnamClient"); let tempdir = tempdir().map_err(ObnamClientError::TempDir)?; let config_filename = tempdir.path().join("client.yaml"); @@ -34,29 +39,14 @@ impl ObnamClient { config.write(&config_filename)?; Ok(Self { + binary: client_binary.to_path_buf(), tempdir, config: config_filename, }) } - pub fn version() -> Result { - let output = Command::new("obnam") - .arg("--version") - .output() - .map_err(ObnamClientError::Run)?; - if output.status.code() != Some(0) { - eprintln!("{}", String::from_utf8_lossy(&output.stdout)); - eprintln!("{}", String::from_utf8_lossy(&output.stderr)); - std::process::exit(1); - } - - let v = String::from_utf8_lossy(&output.stdout); - let v = v.strip_suffix('\n').or(Some(&v)).unwrap().to_string(); - Ok(v) - } - pub fn run(&self, args: &[&str]) -> Result { - let output = Command::new("obnam") + let output = Command::new(&self.binary) .arg("--config") .arg(&self.config) .args(args) diff --git a/src/lib.rs b/src/lib.rs index 5f0a522..75634e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ //! This crate only collects data from a set of benchmarks. It does //! not analyze the data. The data can be stored for later analysis. +pub mod builder; pub mod client; pub mod daemon; pub mod junk; diff --git a/src/server.rs b/src/server.rs index 26f418b..f1536d2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -48,7 +48,7 @@ pub struct ObnamServer { } impl ObnamServer { - pub fn new(manager: &DaemonManager) -> Result { + pub fn new(server_binary: &Path, manager: &DaemonManager) -> Result { debug!("creating ObnamServer"); let tempdir = tempdir().map_err(ObnamServerError::TempDir)?; let config_filename = tempdir.path().join("server.yaml"); @@ -70,10 +70,7 @@ impl ObnamServer { let daemon = manager .start( - &[ - OsStr::new("/usr/bin/obnam-server"), - OsStr::new(&config_filename), - ], + &[OsStr::new(server_binary), OsStr::new(&config_filename)], Path::new("server.out"), Path::new("server.log"), ) diff --git a/src/suite.rs b/src/suite.rs index 43fca8f..86c4c6d 100644 --- a/src/suite.rs +++ b/src/suite.rs @@ -1,3 +1,4 @@ +use crate::builder::{ObnamBuilder, ObnamBuilderError}; use crate::client::{ObnamClient, ObnamClientError}; use crate::daemon::DaemonManager; use crate::junk::junk; @@ -19,6 +20,8 @@ use walkdir::WalkDir; /// This manages temporary data created for the benchmarks, and /// executes individual steps in the suite. pub struct Suite { + client: PathBuf, + server: PathBuf, manager: DaemonManager, benchmark: Option, } @@ -26,6 +29,10 @@ pub struct Suite { /// Possible errors from running a benchmark suite. #[derive(Debug, thiserror::Error)] pub enum SuiteError { + /// Error building Obnam. + #[error(transparent)] + Build(ObnamBuilderError), + /// Error creating a temporary directory. #[error(transparent)] TempDir(#[from] std::io::Error), @@ -72,8 +79,10 @@ pub enum SuiteError { } impl Suite { - pub fn new() -> Result { + pub fn new(builder: &ObnamBuilder) -> Result { Ok(Self { + client: builder.client_binary().to_path_buf(), + server: builder.server_binary().to_path_buf(), manager: DaemonManager::new(), benchmark: None, }) @@ -88,7 +97,8 @@ impl Suite { let mut om = match step { Step::Start(name) => { assert!(self.benchmark.is_none()); - let mut benchmark = Benchmark::new(name, &self.manager)?; + let mut benchmark = + Benchmark::new(&self.client, &self.server, name, &self.manager)?; let om = benchmark.start()?; self.benchmark = Some(benchmark); om @@ -158,11 +168,16 @@ struct Benchmark { } impl Benchmark { - fn new(name: &str, manager: &DaemonManager) -> Result { - let server = ObnamServer::new(manager)?; + fn new( + client: &Path, + server: &Path, + name: &str, + manager: &DaemonManager, + ) -> Result { + let server = ObnamServer::new(server, manager)?; let live = tempdir().map_err(SuiteError::TempDir)?; let restored = tempdir().map_err(SuiteError::TempDir)?; - let client = ObnamClient::new(server.url(), live.path().to_path_buf())?; + let client = ObnamClient::new(client, server.url(), live.path().to_path_buf())?; Ok(Self { name: name.to_string(), client, -- cgit v1.2.1