use crate ::measurement :: { BenchmarkResult, MeasurementConfig };
use crate ::analysis ::RegressionAnalysis;
use std ::collections ::HashMap;
type Result< T > = std ::result ::Result< T, Box<dyn std ::error ::Error >>;
pub struct BenchmarkSuite
{
pub name: String,
benchmarks: HashMap< String, Box< dyn FnMut() + Send > >,
config: MeasurementConfig,
results: HashMap< String, BenchmarkResult >,
}
impl std ::fmt ::Debug for BenchmarkSuite
{
fn fmt( &self, f: &mut std ::fmt ::Formatter< '_ > ) -> std ::fmt ::Result
{
f.debug_struct( "BenchmarkSuite" )
.field( "name", &self.name )
.field( "benchmarks", &format!( "{} benchmarks", self.benchmarks.len() ) )
.field( "config", &self.config )
.field( "results", &format!( "{} results", self.results.len() ) )
.finish()
}
}
impl BenchmarkSuite
{
pub fn new( name: impl Into< String > ) -> Self
{
crate ::check_directory_recommendations();
Self
{
name: name.into(),
benchmarks: HashMap ::new(),
config: MeasurementConfig ::default(),
results: HashMap ::new(),
}
}
#[ must_use ]
pub fn with_config( mut self, config: MeasurementConfig ) -> Self
{
self.config = config;
self
}
pub fn benchmark< F >( &mut self, name: impl Into< String >, f: F ) -> &mut Self
where
F: FnMut() + Send + 'static,
{
self.benchmarks.insert( name.into(), Box ::new( f ) );
self
}
#[ must_use ]
pub fn add_benchmark< F >(mut self, name: impl Into< String >, f: F) -> Self
where
F: FnMut() + Send + 'static,
{
self.benchmark(name, f);
self
}
pub fn run_all( &mut self ) -> SuiteResults
{
let mut results = HashMap ::new();
println!("Running benchmark suite: {}", self.name);
for (name, benchmark) in &mut self.benchmarks
{
print!(" Running {name} ... ");
let result = crate ::measurement ::bench_function_with_config(
name,
&self.config,
benchmark
);
println!("{:.2?}", result.mean_time());
results.insert(name.clone(), result);
}
self.results.clone_from(&results);
SuiteResults {
suite_name: self.name.clone(),
results,
}
}
pub fn run_analysis( &mut self ) -> SuiteResults
{
self.run_all()
}
#[ must_use ]
pub fn results( &self ) -> &HashMap< String, BenchmarkResult >
{
&self.results
}
pub fn from_baseline(_baseline_file: impl AsRef< std ::path ::Path >) -> Self
{
Self ::new("baseline_comparison")
}
pub fn from_config(_config_file: impl AsRef< std ::path ::Path >) -> Self
{
Self ::new("configured_suite")
}
}
#[ derive(Debug) ]
pub struct SuiteResults
{
pub suite_name: String,
pub results: HashMap< String, BenchmarkResult >,
}
impl SuiteResults
{
#[ must_use ]
pub fn generate_markdown_report( &self ) -> MarkdownReport
{
MarkdownReport ::new(&self.suite_name, &self.results)
}
#[ must_use ]
pub fn regression_analysis(&self, baseline: &HashMap< String, BenchmarkResult >) -> RegressionAnalysis
{
RegressionAnalysis ::new(baseline.clone(), self.results.clone())
}
#[ must_use ]
pub fn regression_percentage( &self ) -> f64
{
0.0
}
pub fn save_as_baseline(&self, _baseline_file: impl AsRef< std ::path ::Path >) -> Result< () >
{
Ok(())
}
pub fn print_summary( &self )
{
println!("=== {} Results ===", self.suite_name);
let mut sorted_results: Vec< _ > = self.results.iter().collect();
sorted_results.sort_by(|a, b| a.1.mean_time().cmp(&b.1.mean_time()));
for (name, result) in sorted_results
{
println!(" {} : {:.2?} (±{:.2?})",
name,
result.mean_time(),
result.std_deviation());
}
}
}
#[ derive(Debug) ]
pub struct MarkdownReport
{
suite_name: String,
results: HashMap< String, BenchmarkResult >,
include_raw_data: bool,
include_statistics: bool,
}
impl MarkdownReport
{
#[ must_use ]
pub fn new(suite_name: &str, results: &HashMap< String, BenchmarkResult >) -> Self
{
Self {
suite_name: suite_name.to_string(),
results: results.clone(),
include_raw_data: false,
include_statistics: true,
}
}
#[ must_use ]
pub fn with_raw_data(mut self) -> Self
{
self.include_raw_data = true;
self
}
#[ must_use ]
pub fn with_statistics(mut self) -> Self
{
self.include_statistics = true;
self
}
#[ must_use ]
pub fn generate( &self ) -> String
{
let mut output = String ::new();
output.push_str(&format!("## {} Results\n\n", self.suite_name));
if self.results.is_empty()
{
output.push_str("No benchmark results available.\n");
return output;
}
output.push_str("| Benchmark | Mean Time | Ops/sec | Min | Max | Std Dev |\n");
output.push_str("|-----------|-----------|---------|-----|-----|----------|\n");
let mut sorted_results: Vec< _ > = self.results.iter().collect();
sorted_results.sort_by(|a, b| a.1.mean_time().cmp(&b.1.mean_time()));
for (name, result) in &sorted_results
{
output.push_str(&format!(
"| {} | {:.2?} | {:.0} | {:.2?} | {:.2?} | {:.2?} |\n",
name,
result.mean_time(),
result.operations_per_second(),
result.min_time(),
result.max_time(),
result.std_deviation()
));
}
output.push('\n');
if let Some((fastest_name, fastest_result)) = sorted_results.first()
{
output.push_str("### Key Insights\n\n");
output.push_str(&format!("- **Fastest operation** : {} ({:.2?})\n",
fastest_name,
fastest_result.mean_time()));
if sorted_results.len() > 1
{
let slowest = sorted_results.last().unwrap();
let ratio = slowest.1.mean_time().as_secs_f64() / fastest_result.mean_time().as_secs_f64();
output.push_str(&format!("- **Performance range** : {:.1}x difference between fastest and slowest\n", ratio));
}
output.push('\n');
}
output
}
pub fn update_file(
&self,
file_path: impl AsRef< std ::path ::Path >,
section_name: &str
) -> Result< () > {
println!("Would update {section_name} section in {}", file_path.as_ref().display());
Ok(())
}
pub fn save(&self, file_path: impl AsRef< std ::path ::Path >) -> Result< () >
{
let content = self.generate();
std ::fs ::write(file_path, content)?;
Ok(())
}
}