use std::borrow::Cow; use csv::WriterBuilder; use super::Exporter; use crate::benchmark::benchmark_result::BenchmarkResult; use crate::options::SortOrder; use crate::quantity::{FormatQuantity, TimeUnit}; use anyhow::Result; #[derive(Default)] pub struct CsvExporter {} impl Exporter for CsvExporter { fn serialize( &self, results: &[BenchmarkResult], _unit: Option, _sort_order: SortOrder, ) -> Result> { const CSV_UNIT: TimeUnit = TimeUnit::Second; const CSV_PRECISION: usize = 6; let mut writer = WriterBuilder::new().from_writer(vec![]); { let mut headers: Vec> = [ // The list of times and exit codes cannot be exported to the CSV file - omit them. "command", "mean", "stddev", "median", "user", "system", "min", "max", ] .iter() .map(|x| Cow::Borrowed(x.as_bytes())) .collect(); if let Some(res) = results.first() { for param_name in res.parameters.keys() { headers.push(Cow::Owned(format!("parameter_{param_name}").into_bytes())); } } writer.write_record(headers)?; } for res in results { let mut fields = vec![Cow::Borrowed(res.command.as_bytes())]; for f in &[ res.mean_wall_clock_time(), res.measurements.stddev().unwrap_or_default(), res.measurements.median(), res.measurements.time_user_mean(), res.measurements.time_system_mean(), res.measurements.min(), res.measurements.max(), ] { fields.push(Cow::Owned( f.format_with_precision(CSV_UNIT, CSV_PRECISION) .into_bytes(), )) } for v in res.parameters.values() { fields.push(Cow::Borrowed(v.value.as_bytes())) } writer.write_record(fields)?; } Ok(writer.into_inner()?) } } #[test] fn test_csv() { use crate::benchmark::benchmark_result::Parameter; use crate::benchmark::measurement::{Measurement, Measurements}; use crate::quantity::{byte, second, Information, Time, Zero}; use std::collections::BTreeMap; use std::process::ExitStatus; let exporter = CsvExporter::default(); let results = vec![ BenchmarkResult { command: String::from("command_a"), command_with_unused_parameters: String::from("command_a"), measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(7.0), time_user: Time::new::(7.0), time_system: Time::zero(), peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(8.0), time_user: Time::new::(8.0), time_system: Time::zero(), peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(12.0), time_user: Time::new::(12.0), time_system: Time::zero(), peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), parameters: { let mut params = BTreeMap::new(); params.insert( "foo".into(), Parameter { value: "one".into(), is_unused: false, }, ); params.insert( "bar".into(), Parameter { value: "two".into(), is_unused: false, }, ); params }, }, BenchmarkResult { command: String::from("command_b"), command_with_unused_parameters: String::from("command_b"), measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(17.0), time_user: Time::new::(17.0), time_system: Time::zero(), peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(18.0), time_user: Time::new::(18.0), time_system: Time::zero(), peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(19.0), time_user: Time::new::(19.0), time_system: Time::zero(), peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), parameters: { let mut params = BTreeMap::new(); params.insert( "foo".into(), Parameter { value: "one".into(), is_unused: false, }, ); params.insert( "bar".into(), Parameter { value: "seven".into(), is_unused: false, }, ); params }, }, ]; let actual = String::from_utf8( exporter .serialize(&results, Some(TimeUnit::Second), SortOrder::Command) .unwrap(), ) .unwrap(); insta::assert_snapshot!(actual, @r#" command,mean,stddev,median,user,system,min,max,parameter_bar,parameter_foo command_a,9.000000,2.645751,8.000000,9.000000,0.000000,7.000000,12.000000,two,one command_b,18.000000,1.000000,18.000000,18.000000,0.000000,17.000000,19.000000,seven,one "#); }