markup.rs
1 use crate::benchmark::relative_speed::BenchmarkResultWithRelativeSpeed; 2 use crate::benchmark::{benchmark_result::BenchmarkResult, relative_speed}; 3 use crate::options::SortOrder; 4 use crate::output::format::format_duration_value; 5 use crate::util::units::Unit; 6 7 use super::Exporter; 8 use anyhow::Result; 9 10 pub enum Alignment { 11 Left, 12 Right, 13 } 14 15 pub trait MarkupExporter { 16 fn table_results(&self, entries: &[BenchmarkResultWithRelativeSpeed], unit: Unit) -> String { 17 // prepare table header strings 18 let notation = format!("[{}]", unit.short_name()); 19 20 // prepare table cells alignment 21 let cells_alignment = [ 22 Alignment::Left, 23 Alignment::Right, 24 Alignment::Right, 25 Alignment::Right, 26 Alignment::Right, 27 ]; 28 29 // emit table header format 30 let mut table = self.table_header(&cells_alignment); 31 32 // emit table header data 33 table.push_str(&self.table_row(&[ 34 "Command", 35 &format!("Mean {notation}"), 36 &format!("Min {notation}"), 37 &format!("Max {notation}"), 38 "Relative", 39 ])); 40 41 // emit horizontal line 42 table.push_str(&self.table_divider(&cells_alignment)); 43 44 for entry in entries { 45 let measurement = &entry.result; 46 // prepare data row strings 47 let cmd_str = measurement 48 .command_with_unused_parameters 49 .replace('|', "\\|"); 50 let mean_str = format_duration_value(measurement.mean, Some(unit)).0; 51 let stddev_str = if let Some(stddev) = measurement.stddev { 52 format!(" ± {}", format_duration_value(stddev, Some(unit)).0) 53 } else { 54 "".into() 55 }; 56 let min_str = format_duration_value(measurement.min, Some(unit)).0; 57 let max_str = format_duration_value(measurement.max, Some(unit)).0; 58 let rel_str = format!("{:.2}", entry.relative_speed); 59 let rel_stddev_str = if entry.is_reference { 60 "".into() 61 } else if let Some(stddev) = entry.relative_speed_stddev { 62 format!(" ± {stddev:.2}") 63 } else { 64 "".into() 65 }; 66 67 // prepare table row entries 68 table.push_str(&self.table_row(&[ 69 &self.command(&cmd_str), 70 &format!("{mean_str}{stddev_str}"), 71 &min_str, 72 &max_str, 73 &format!("{rel_str}{rel_stddev_str}"), 74 ])) 75 } 76 77 // emit table footer format 78 table.push_str(&self.table_footer(&cells_alignment)); 79 80 table 81 } 82 83 fn table_row(&self, cells: &[&str]) -> String; 84 85 fn table_divider(&self, cell_aligmnents: &[Alignment]) -> String; 86 87 fn table_header(&self, _cell_aligmnents: &[Alignment]) -> String { 88 "".to_string() 89 } 90 91 fn table_footer(&self, _cell_aligmnents: &[Alignment]) -> String { 92 "".to_string() 93 } 94 95 fn command(&self, size: &str) -> String; 96 } 97 98 fn determine_unit_from_results(results: &[BenchmarkResult]) -> Unit { 99 if let Some(first_result) = results.first() { 100 // Use the first BenchmarkResult entry to determine the unit for all entries. 101 format_duration_value(first_result.mean, None).1 102 } else { 103 // Default to `Second`. 104 Unit::Second 105 } 106 } 107 108 impl<T: MarkupExporter> Exporter for T { 109 fn serialize( 110 &self, 111 results: &[BenchmarkResult], 112 unit: Option<Unit>, 113 sort_order: SortOrder, 114 ) -> Result<Vec<u8>> { 115 let unit = unit.unwrap_or_else(|| determine_unit_from_results(results)); 116 let entries = relative_speed::compute(results, sort_order); 117 118 let table = self.table_results(&entries, unit); 119 Ok(table.as_bytes().to_vec()) 120 } 121 }