From 5557d68b6edbccbbf4ed622924d402885821982a Mon Sep 17 00:00:00 2001 From: Steve Pentland Date: Mon, 19 Mar 2018 17:37:56 -0400 Subject: [PATCH] Add option to export JSON, change file handling No longer serialize into strings, instead use Vec as it is what we're writing to the files. Fixup comments --- Cargo.lock | 25 ++++++++++++++++++++ Cargo.toml | 1 + src/hyperfine/export/csv.rs | 22 ++++++++--------- src/hyperfine/export/json.rs | 29 +++++++++++++++++++++++ src/hyperfine/export/mod.rs | 39 ++++++++++++++++++++++-------- src/main.rs | 46 +++++++++++++++++++++++++++--------- 6 files changed, 130 insertions(+), 32 deletions(-) create mode 100644 src/hyperfine/export/json.rs diff --git a/Cargo.lock b/Cargo.lock index 44beec5c8..25b610e9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,6 +101,11 @@ dependencies = [ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dtoa" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -129,6 +134,7 @@ dependencies = [ "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)", + "serde_json 1.0.11 (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)", ] @@ -144,6 +150,11 @@ dependencies = [ "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itoa" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -366,6 +377,17 @@ dependencies = [ "syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde_json" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "smallvec" version = "0.6.0" @@ -516,9 +538,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "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 dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" "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" +"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" @@ -546,6 +570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "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 serde_json 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "fab6c4d75bedcf880711c85e39ebf8ccc70d0eba259899047ec5d7436643ee17" "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" diff --git a/Cargo.toml b/Cargo.toml index dadd567f0..3f1028867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ cfg-if = "0.1.2" csv = "1.0.0-beta.5" serde = "1.0.33" serde_derive = "1.0.33" +serde_json = "1.0.11" [target.'cfg(not(windows))'.dependencies] libc = "0.2" diff --git a/src/hyperfine/export/csv.rs b/src/hyperfine/export/csv.rs index 635d2cd55..ac1bae7b4 100644 --- a/src/hyperfine/export/csv.rs +++ b/src/hyperfine/export/csv.rs @@ -1,26 +1,26 @@ use super::{ExportEntry, ResultExporter}; use csv::WriterBuilder; -use std::io::Result; +use std::io::{Error, ErrorKind, Result}; -pub struct CsvExporter { - out_file: String, -} +pub struct CsvExporter {} impl ResultExporter for CsvExporter { - fn write(&self, results: &Vec) -> Result<()> { - let mut writer = WriterBuilder::new().from_path(&self.out_file)?; + fn write(&self, results: &Vec) -> Result> { + let mut writer = WriterBuilder::new().from_writer(vec![]); for res in results { writer.serialize(res)?; } - Ok(()) + + if let Ok(inner) = writer.into_inner() { + return Ok(inner); + } + Err(Error::new(ErrorKind::Other, "Error serializing to CSV")) } } impl CsvExporter { - pub fn new(file_name: String) -> Self { - CsvExporter { - out_file: file_name, - } + pub fn new() -> Self { + CsvExporter {} } } diff --git a/src/hyperfine/export/json.rs b/src/hyperfine/export/json.rs new file mode 100644 index 000000000..f3fc1a1f7 --- /dev/null +++ b/src/hyperfine/export/json.rs @@ -0,0 +1,29 @@ +use super::{ExportEntry, ResultExporter}; + +use std::io::{Error, ErrorKind, Result}; + +use serde_json::to_vec_pretty; + +#[derive(Serialize, Debug)] +struct HyperfineSummary<'a> { + results: &'a Vec, +} + +pub struct JsonExporter {} + +impl ResultExporter for JsonExporter { + fn write(&self, results: &Vec) -> Result> { + let serialized = to_vec_pretty(&HyperfineSummary { results }); + + match serialized { + Ok(file_content) => Ok(file_content), + Err(e) => Err(Error::new(ErrorKind::Other, format!("{:?}", e))), + } + } +} + +impl JsonExporter { + pub fn new() -> Self { + JsonExporter {} + } +} diff --git a/src/hyperfine/export/mod.rs b/src/hyperfine/export/mod.rs index 1c1e2f870..5b5620152 100644 --- a/src/hyperfine/export/mod.rs +++ b/src/hyperfine/export/mod.rs @@ -1,8 +1,11 @@ mod csv; +mod json; use self::csv::CsvExporter; +use self::json::JsonExporter; -use std::io::Result; +use std::io::{Result, Write}; +use std::fs::File; use hyperfine::internal::Second; @@ -51,16 +54,19 @@ impl ExportEntry { /// The ResultExportType enum is used to denote the desired form /// of exporter to use for a given file. +#[derive(Clone)] pub enum ResultExportType { /// Export to a csv file with the provided name Csv(String), + /// Export to a JSON file + Json(String), } /// A ResultExporter is responsible for writing all results to the /// appropriate file trait ResultExporter { /// Write all entries to the target destination - fn write(&self, values: &Vec) -> Result<()>; + fn write(&self, values: &Vec) -> Result>; } /// Create a new ExportManager @@ -70,23 +76,36 @@ pub fn create_export_manager() -> ExportManager { } } -/// The Exporter is the internal implementation of the ExportManager +/// The ExportManager handles the management of multiple file +/// exporters. pub struct ExportManager { - exporters: Vec>, + exporters: Vec, } impl ExportManager { - pub 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()))), - }; + /// Add an additional exporter to the ExportManager + pub fn add_exporter(&mut self, for_type: ResultExportType) { + self.exporters.push(for_type); } + /// Write the given results to all Exporters contained within this manager pub fn write_results(&self, to_write: Vec) -> Result<()> { for exp in &self.exporters { - exp.write(&to_write)?; + let (exporter, filename): (Box, &str) = match exp { + &ResultExportType::Csv(ref file) => (Box::from(CsvExporter::new()), file), + &ResultExportType::Json(ref file) => (Box::from(JsonExporter::new()), file), + }; + + let file_content = exporter.write(&to_write)?; + write_to_file(filename, file_content)?; } Ok(()) } } + +/// Write the given content to a file with the specified name +fn write_to_file(filename: &str, content: Vec) -> Result<()> { + let mut file = File::create(filename)?; + file.write_all(&content)?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index dd5ef5340..fc6524408 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ extern crate serde; #[macro_use] extern crate serde_derive; +extern crate serde_json; extern crate statistical; cfg_if! { @@ -38,7 +39,7 @@ mod hyperfine; use hyperfine::internal::{CmdFailureAction, HyperfineOptions, OutputStyleOption}; use hyperfine::benchmark::{mean_shell_spawning_time, run_benchmark}; -use hyperfine::export::{create_export_manager, ExportEntry, ResultExportType}; +use hyperfine::export::{create_export_manager, ExportEntry, ExportManager, ResultExportType}; /// Print error message to stderr and terminate pub fn error(message: &str) -> ! { @@ -144,6 +145,13 @@ fn main() { .value_name("FILE") .help("Export the timing results to the given file in csv format."), ) + .arg( + Arg::with_name("export-json") + .long("export-json") + .takes_value(true) + .value_name("FILE") + .help("Export the timing results to the given file in JSON format."), + ) .help_message("Print this help message.") .version_message("Show version information.") .get_matches(); @@ -173,17 +181,11 @@ 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 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, + let export_targets = ExportTargetList { + json_file: matches.value_of("export-json"), + csv_file: matches.value_of("export-csv"), }; + let export_manager = create_exporter(export_targets); // We default Windows to NoColor if full had been specified. if cfg!(windows) && options.output_style == OutputStyleOption::Full { @@ -210,3 +212,25 @@ fn main() { Err(e) => error(e.description()), } } + +struct ExportTargetList<'a> { + json_file: Option<&'a str>, + csv_file: Option<&'a str>, +} + +fn create_exporter(targets: ExportTargetList) -> Option { + if targets.json_file.is_none() && targets.csv_file.is_none() { + return None; + } + + let mut export_manager = create_export_manager(); + + if let Some(filename) = targets.json_file { + export_manager.add_exporter(ResultExportType::Json(filename.to_string())); + } + + if let Some(filename) = targets.csv_file { + export_manager.add_exporter(ResultExportType::Csv(filename.to_string())); + } + Some(export_manager) +}