use crate::benchmark::relative_speed::BenchmarkResultWithRelativeSpeed; use crate::benchmark::{benchmark_result::BenchmarkResult, relative_speed}; use crate::options::SortOrder; use crate::quantity::{FormatQuantity, IsUnit, Time, TimeUnit, Zero}; use super::Exporter; use anyhow::Result; use std::cmp::Ordering; pub enum Alignment { Left, Right, } pub trait MarkupExporter { fn table_results( &self, entries: &[BenchmarkResultWithRelativeSpeed], time_unit: TimeUnit, reference_pending: bool, explicit_reference: bool, ) -> String { // prepare table header strings let notation = format!("[{}]", time_unit.short_name()); // prepare table cells alignment let cells_alignment = [ Alignment::Left, Alignment::Right, Alignment::Right, Alignment::Right, Alignment::Right, ]; // emit table header format let mut table = self.table_header(&cells_alignment); // emit table header data table.push_str(&self.table_row(&[ "Command", &format!("Mean {notation}"), &format!("Min {notation}"), &format!("Max {notation}"), "Relative", ])); // emit horizontal line table.push_str(&self.table_divider(&cells_alignment)); for entry in entries { let result = &entry.result; // prepare data row strings let cmd_str = result.command_with_unused_parameters().replace('|', "\\|"); let mean_str = format_duration_value(result.mean_wall_clock_time(), Some(time_unit)).0; let stddev_str = if let Some(stddev) = result.measurements.stddev() { format!(" ± {}", format_duration_value(stddev, Some(time_unit)).0) } else { "".into() }; let min_str = result.measurements.min().format_value(time_unit); let max_str = result.measurements.max().format_value(time_unit); // The ratio of two zero times is undefined, even if they compare equal. let relative_unavailable = reference_pending || (explicit_reference && !entry.is_reference && entry.relative_ordering == Ordering::Equal && result.mean_wall_clock_time() == Time::zero()); let relative = if relative_unavailable { "N/A".to_string() } else { let stddev = match (entry.is_reference, entry.relative_speed_stddev) { (false, Some(stddev)) => format!(" ± {stddev:.2}"), _ => String::new(), }; let label = if explicit_reference { entry.reference_label() } else { "" }; format!("{:.2}{stddev}{label}", entry.relative_speed) }; // prepare table row entries table.push_str(&self.table_row(&[ &self.command(&cmd_str), &format!("{mean_str}{stddev_str}"), &min_str, &max_str, &relative, ])) } // emit table footer format table.push_str(&self.table_footer(&cells_alignment)); table } fn table_row(&self, cells: &[&str]) -> String; fn table_divider(&self, cell_aligmnents: &[Alignment]) -> String; fn table_header(&self, _cell_aligmnents: &[Alignment]) -> String { "".to_string() } fn table_footer(&self, _cell_aligmnents: &[Alignment]) -> String { "".to_string() } fn command(&self, size: &str) -> String; } fn determine_unit_from_results(results: &[BenchmarkResult]) -> TimeUnit { if let Some(first_result) = results.first() { // Use the first BenchmarkResult entry to determine the unit for all entries. format_duration_value(first_result.mean_wall_clock_time(), None).1 } else { // Default to `Second`. TimeUnit::Second } } impl Exporter for T { fn serialize( &self, results: &[BenchmarkResult], unit: Option, sort_order: SortOrder, reference_index: Option, ) -> Result> { let unit = unit.unwrap_or_else(|| determine_unit_from_results(results)); // Do not report ratios against another benchmark while the selected // reference is still pending in an intermediate export. let reference_pending = reference_index.is_some_and(|i| i >= results.len()); let entries = relative_speed::compute( results, sort_order, reference_index.and_then(|i| results.get(i)), ); let table = self.table_results(&entries, unit, reference_pending, reference_index.is_some()); Ok(table.as_bytes().to_vec()) } }