-
-
Notifications
You must be signed in to change notification settings - Fork 382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add export manager and CSV export capability #41
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much!! This looks great.
I have added a few review comments. Curious to hear what you think.
@@ -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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to rely on a beta version here? Would the latest stable version work, too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll check, our use case is pretty simple so I'm hopeful older versions would work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears that serde support is not available in pre 1.0.0 versions. I tried 0.15.0 but it is requiring some rather significant changes to serialization, writing, and so on. Not to mention that I can no longer find all the documentation for that version (Encodable trait results in 404). It may be reasonable to use the beta for now, which will also allow keeping serde support betwen json and csv
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, thanks for checking!
src/hyperfine/benchmark.rs
Outdated
@@ -136,6 +137,7 @@ pub fn run_benchmark( | |||
cmd: &str, | |||
shell_spawning_time: TimingResult, | |||
options: &HyperfineOptions, | |||
exporter: &mut Option<Box<ExportManager>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if we could change the API a little bit. Instead of run_benchmark
taking a mutable ExportManager
and calling add_result
on it, couldn't the function simply return the results (The ExportEntry
)?
} | ||
|
||
impl CsvExporter { | ||
pub fn new(file_name: String) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the file-handling part be generalized and moved to export/mod.rs
? Otherwsise, every exporter type would have to handle this on its own. Instead, each exporter could probably just return a String
with the file contents.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could, I wanted to let particular serializer libraries handle output to files in their own way. I can look into refactoring this later to unify file handling however
src/hyperfine/export/mod.rs
Outdated
/// The command that was run | ||
command: String, | ||
/// The mean run time | ||
mean: f64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Second
instead of f64
here? Does this work with Serialize
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll check
|
||
/// The ResultExportType enum is used to denote the desired form | ||
/// of exporter to use for a given file. | ||
pub enum ResultExportType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this enum? ResultExporter
is a trait, so we should be able to pass the trait objects directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to take away all reasoning about what would be added as an exporter and leave it up to the manager to create the appropriate type. The internal operation of exporting isn't really important to external calls, only that they want a json, csv, or other file format. It makes a single entry point for all exports instead of making a new exporter and passing it in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sounds reasonable.
src/hyperfine/export/mod.rs
Outdated
} | ||
|
||
/// The ExportManager handles coordination of multiple ResultExporters | ||
pub trait ExportManager { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need ExportManager
to be a trait? Couldn't this just be a struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I was just trying to extract away all implementation, but I can switch that one out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer a simple struct. I don't think we need ExportManager
as an abstraction/interface here. There is just one of them. I think it would also simplify the code and the readability.
src/main.rs
Outdated
Arg::with_name("export-csv") | ||
.long("export-csv") | ||
.takes_value(true) | ||
.multiple(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to allow for multiple options here? Wouldn't all these files have the same contents?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They would yes, however as there is no down side to writing to multiple files, I figured offering the option to write to more than one is pretty easy. I don't generally want to try and guess user's desires since we can easily write 1 or more files, in multiple formats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point. However, I don't think that this will be an actual use case. Even if it would, the user could simply copy the file afterwards. Also, I think there is a downside to defining it multiple = true
: the generated help text will look like --export-csv <file>...
(or similar) which might confuse some users ("do I have to specify multiple files? one for each benchmark command?").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll remove multiples from this one, and from the json branch (rebasing back on these changes anyway)
src/main.rs
Outdated
fn run( | ||
commands: &Vec<&str>, | ||
options: &HyperfineOptions, | ||
exporter: &mut Option<Box<ExportManager>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above: I would rather like run
to return the results instead of passing a mutable ExportManager
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that'll simplify everything
Manager is now a simple struct, that no longer stores the results, but instead takes in a Vec<ExportEntry> and has all exporters write from this collection. Benchmark and run both return their results, no more mut manager. Change f64 in ExportEntry to hyperfine::internal::Second. Remove multiple export files for a single type.
The only things left out from review:
|
Fantastic - thank you! |
The work done here is toward #38 and includes an exporter for CSV and a manager to handle multiple exporters.
It is done to allow multiple exporters (csv, json, etc.) to be used at one time with the only requirement being to add a new enum value to export types and the exporter itself that is responsible for writing. JSON should be a very easy output type to add at this point, and I will move to that next once this PR is closed.