diff --git a/Cargo.lock b/Cargo.lock index 3221d3f..44beec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,6 +84,23 @@ dependencies = [ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "csv" +version = "1.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "csv-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "csv-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -107,8 +124,11 @@ dependencies = [ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)", "colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "csv 1.0.0-beta.5 (registry+https://github.com/rust-lang/crates.io-index)", "indicatif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", "statistical 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -250,6 +270,22 @@ dependencies = [ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.3.22" @@ -305,6 +341,31 @@ name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "serde" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive_internals 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive_internals" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "smallvec" version = "0.6.0" @@ -329,6 +390,16 @@ name = "strsim" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "syn" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "term_size" version = "0.3.1" @@ -380,6 +451,11 @@ name = "unicode-width" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unreachable" version = "1.0.0" @@ -438,6 +514,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum clicolors-control 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f84dec9bc083ce2503908cd305af98bd363da6f54bf8d4bf0ac14ee749ad5d1" "checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc" "checksum console 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7649ca90478264b9686aac8d269fcb014f14c2bed7c79a7e51b9f6afd4d783eb" +"checksum csv 1.0.0-beta.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e7a9e063dcebdb56c306f23e672bfd31df3da8ec5f6d696b35f2c29c2a9572f0" +"checksum csv-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dd8e6d86f7ba48b4276ef1317edc8cc36167546d8972feb4a2b5fec0b374105" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum indicatif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a29b2fa6f00010c268bface64c18bb0310aaa70d46a195d5382d288c477fb016" @@ -456,6 +534,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" "checksum parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd9d732f2de194336fb02fe11f9eed13d9e76f13f4315b4d88a14ca411750cd" "checksum parking_lot_core 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "538ef00b7317875071d5e00f603f24d16f0b474c1a5fc0ccb8b454ca72eafa79" +"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" +"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" @@ -463,16 +543,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "5be5347bde0c48cfd8c3fdc0766cdfe9d8a755ef84d620d6794c778c91de8b2b" "checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +"checksum serde 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "4fe95aa0d46f04ce5c3a88bdcd4114ecd6144ed0b2725ebca2f1127744357807" +"checksum serde_derive 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "23b163a6ce7e1aa897919f9d8e40bd1f8a6f95342ed57727ae31387a01a7a356" +"checksum serde_derive_internals 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "370aa477297975243dc914d0b0e1234927520ec311de507a560fbd1c80f7ab8c" "checksum smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44db0ecb22921ef790d17ae13a3f6d15784183ff5f2a01aa32098c7498d2b4b9" "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" "checksum statistical 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c139942f46d96c53b28420a2cdfb374629f122656bd9daef7fc221ed4d8ec228" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8c5bc2d6ff27891209efa5f63e9de78648d7801f085e4653701a692ce938d6fd" "checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index 01f82e3..dadd567 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ indicatif = "0.9" statistical = "0.1" atty = "0.2.2" cfg-if = "0.1.2" +csv = "1.0.0-beta.5" +serde = "1.0.33" +serde_derive = "1.0.33" [target.'cfg(not(windows))'.dependencies] libc = "0.2" diff --git a/src/hyperfine/benchmark.rs b/src/hyperfine/benchmark.rs index 4fbc2e5..7f23c75 100644 --- a/src/hyperfine/benchmark.rs +++ b/src/hyperfine/benchmark.rs @@ -11,6 +11,7 @@ use hyperfine::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD}; use hyperfine::timer::{TimerStart, TimerStop}; use hyperfine::timer::wallclocktimer::WallClockTimer; use hyperfine::shell::execute_and_time; +use hyperfine::export::{ExportEntry, ExportManager}; /// Results from timing a single shell command #[derive(Debug, Default, Copy, Clone)] @@ -136,6 +137,7 @@ pub fn run_benchmark( cmd: &str, shell_spawning_time: TimingResult, options: &HyperfineOptions, + exporter: &mut Option>, ) -> io::Result<()> { println!( "{}{}: {}", @@ -265,6 +267,11 @@ pub fn run_benchmark( // Warnings let mut warnings = vec![]; + if let &mut Some(ref mut mgr) = exporter { + let entry = ExportEntry::new(cmd.to_string(), t_mean, t_stddev, user_mean, system_mean); + mgr.add_result(entry); + } + // Check execution time if times_real.iter().any(|&t| t < MIN_EXECUTION_TIME) { warnings.push(Warnings::FastExecutionTime); diff --git a/src/hyperfine/export/csv.rs b/src/hyperfine/export/csv.rs new file mode 100644 index 0000000..bea110e --- /dev/null +++ b/src/hyperfine/export/csv.rs @@ -0,0 +1,32 @@ +use super::{ExportEntry, ResultExporter}; + +use csv::WriterBuilder; +use std::io::Result; + +pub struct CsvExporter { + results: Vec, + out_file: String, +} + +impl ResultExporter for CsvExporter { + fn add_entry(&mut self, entry: ExportEntry) { + self.results.push(entry); + } + + fn write(&self) -> Result<()> { + let mut writer = WriterBuilder::new().from_path(&self.out_file)?; + for res in &self.results { + writer.serialize(res)?; + } + Ok(()) + } +} + +impl CsvExporter { + pub fn new(file_name: String) -> Self { + CsvExporter { + results: Vec::new(), + out_file: file_name, + } + } +} diff --git a/src/hyperfine/export/mod.rs b/src/hyperfine/export/mod.rs new file mode 100644 index 0000000..0a520ed --- /dev/null +++ b/src/hyperfine/export/mod.rs @@ -0,0 +1,96 @@ +mod csv; + +use self::csv::CsvExporter; + +use std::io::Result; + +/// The ExportEntry is the main set of values that will +/// be exported to files when requested. +#[derive(Debug, Default, Clone, Serialize)] +pub struct ExportEntry { + /// The command that was run + command: String, + /// The mean run time + mean: f64, + /// The standard deviation of all run times + stddev: f64, + /// Time spend in user space + user: f64, + /// Time spent in system space + system: f64, +} + +impl ExportEntry { + /// Create a new ExportEntry with the given values + pub fn new(command: String, mean: f64, stddev: f64, user: f64, system: f64) -> Self { + ExportEntry { + command, + mean, + stddev, + user, + system, + } + } +} + +/// The ResultExportType enum is used to denote the desired form +/// of exporter to use for a given file. +pub enum ResultExportType { + /// Export to a csv file with the provided name + Csv(String), +} + +/// A ResultExporter is responsible for writing all results to the +/// appropriate file +pub trait ResultExporter { + /// Add an additional entry to the exporter for writing + fn add_entry(&mut self, entry: ExportEntry); + /// Write all entries to the target destination + fn write(&self) -> Result<()>; +} + +/// The ExportManager handles coordination of multiple ResultExporters +pub trait ExportManager { + /// Add a new exporter to this manager for the given type + fn add_exporter(&mut self, for_type: &ResultExportType); + + /// Add a new result to all exporters contained in the manager + fn add_result(&mut self, result: ExportEntry); + + /// Trigger writes from all exporters + fn write_results(&self) -> Result<()>; +} + +/// Create a new ExportManager +pub fn create_export_manager() -> Box { + Box::new(Exporter { + exporters: Vec::new(), + }) +} + +/// The Exporter is the internal implementation of the ExportManager +struct Exporter { + exporters: Vec>, +} + +impl ExportManager for Exporter { + fn add_exporter(&mut self, for_type: &ResultExportType) { + match for_type { + &ResultExportType::Csv(ref file_name) => self.exporters + .push(Box::from(CsvExporter::new(file_name.clone()))), + }; + } + + fn add_result(&mut self, result: ExportEntry) { + for exp in self.exporters.iter_mut() { + exp.add_entry(result.clone()); + } + } + + fn write_results(&self) -> Result<()> { + for exp in &self.exporters { + exp.write()?; + } + Ok(()) + } +} diff --git a/src/hyperfine/mod.rs b/src/hyperfine/mod.rs index df4f9a9..89b0500 100644 --- a/src/hyperfine/mod.rs +++ b/src/hyperfine/mod.rs @@ -5,3 +5,4 @@ pub mod outlier_detection; pub mod warnings; pub mod timer; pub mod shell; +pub mod export; diff --git a/src/main.rs b/src/main.rs index 9b81fe0..4aa4f4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,12 @@ extern crate cfg_if; #[macro_use] extern crate clap; extern crate colored; +extern crate csv; extern crate indicatif; +extern crate serde; + +#[macro_use] +extern crate serde_derive; extern crate statistical; cfg_if! { @@ -33,6 +38,7 @@ mod hyperfine; use hyperfine::internal::{CmdFailureAction, HyperfineOptions, OutputStyleOption}; use hyperfine::benchmark::{mean_shell_spawning_time, run_benchmark}; +use hyperfine::export::{create_export_manager, ExportManager, ResultExportType}; /// Print error message to stderr and terminate pub fn error(message: &str) -> ! { @@ -41,12 +47,16 @@ pub fn error(message: &str) -> ! { } /// Runs the benchmark for the given commands -fn run(commands: &Vec<&str>, options: &HyperfineOptions) -> io::Result<()> { +fn run( + commands: &Vec<&str>, + options: &HyperfineOptions, + exporter: &mut Option>, +) -> io::Result<()> { let shell_spawning_time = mean_shell_spawning_time(&options.output_style)?; // Run the benchmarks for (num, cmd) in commands.iter().enumerate() { - run_benchmark(num, cmd, shell_spawning_time, options)?; + run_benchmark(num, cmd, shell_spawning_time, options, exporter)?; } Ok(()) @@ -129,6 +139,13 @@ fn main() { .short("i") .help("Ignore non-zero exit codes."), ) + .arg( + Arg::with_name("export-csv") + .long("export-csv") + .takes_value(true) + .value_name("FILE") + .help("Export the timing results to the given file in csv format."), + ) .help_message("Print this help message.") .version_message("Show version information.") .get_matches(); @@ -158,6 +175,18 @@ fn main() { }, }; + // Initial impl since we're only doing csv files, expand once we have multiple + // export types. Simplest probably to do the same for each, but check for + // None manager on later additions (JSON, Markdown, etc.) + let mut export_manager = match matches.value_of("export-csv") { + Some(filename) => { + let mut export_manager = create_export_manager(); + export_manager.add_exporter(&ResultExportType::Csv(filename.to_string())); + Some(export_manager) + } + None => None, + }; + // We default Windows to NoColor if full had been specified. if cfg!(windows) && options.output_style == OutputStyleOption::Full { options.output_style = OutputStyleOption::NoColor; @@ -172,9 +201,13 @@ fn main() { } let commands = matches.values_of("command").unwrap().collect(); - let res = run(&commands, &options); + let res = run(&commands, &options, &mut export_manager); if let Err(e) = res { error(e.description()); } + + if let Some(exporter) = export_manager { + exporter.write_results().unwrap(); + } }