1use crate ::measurement ::BenchmarkResult;
7use std ::collections ::HashMap;
8use std ::time ::SystemTime;
9
10type Result< T > = std ::result ::Result< T, Box< dyn std ::error ::Error > >;
11
12#[ derive( Debug, Clone ) ]
14pub struct HistoricalResults
15{
16 baseline_data: HashMap< String, BenchmarkResult >,
17 historical_runs: Vec< TimestampedResults >,
18}
19
20#[ derive( Debug, Clone ) ]
22pub struct TimestampedResults
23{
24 timestamp: SystemTime,
25 results: HashMap< String, BenchmarkResult >,
26}
27
28impl TimestampedResults
29{
30 #[ must_use ]
32 pub fn new( timestamp: SystemTime, results: HashMap< String, BenchmarkResult > ) -> Self
33 {
34 Self { timestamp, results }
35 }
36
37 #[ must_use ]
39 pub fn timestamp( &self ) -> SystemTime
40 {
41 self.timestamp
42 }
43
44 #[ must_use ]
46 pub fn results( &self ) -> &HashMap< String, BenchmarkResult >
47 {
48 &self.results
49 }
50}
51
52impl HistoricalResults
53{
54 #[ must_use ]
56 pub fn new() -> Self
57 {
58 Self
59 {
60 baseline_data: HashMap ::new(),
61 historical_runs: Vec ::new(),
62 }
63 }
64
65 #[ must_use ]
67 pub fn with_baseline( mut self, baseline: HashMap< String, BenchmarkResult > ) -> Self
68 {
69 self.baseline_data = baseline;
70 self
71 }
72
73 #[ must_use ]
75 pub fn with_historical_run( mut self, timestamp: SystemTime, results: HashMap< String, BenchmarkResult > ) -> Self
76 {
77 self.historical_runs.push( TimestampedResults ::new( timestamp, results ) );
78 self
79 }
80
81 #[ must_use ]
83 pub fn with_historical_runs( mut self, runs: Vec< TimestampedResults > ) -> Self
84 {
85 self.historical_runs = runs;
86 self
87 }
88
89 #[ must_use ]
91 pub fn with_previous_run( mut self, run: TimestampedResults ) -> Self
92 {
93 self.historical_runs = vec![ run ];
94 self
95 }
96
97 #[ must_use ]
99 pub fn baseline_data( &self ) -> &HashMap< String, BenchmarkResult >
100 {
101 &self.baseline_data
102 }
103
104 #[ must_use ]
106 pub fn historical_runs( &self ) -> &Vec< TimestampedResults >
107 {
108 &self.historical_runs
109 }
110}
111
112impl Default for HistoricalResults
113{
114 fn default() -> Self
115 {
116 Self ::new()
117 }
118}
119
120#[ derive( Debug, Clone, PartialEq ) ]
122pub enum BaselineStrategy
123{
124 FixedBaseline,
126 RollingAverage,
128 PreviousRun,
130}
131
132#[ derive( Debug, Clone, PartialEq ) ]
134pub enum PerformanceTrend
135{
136 Improving,
138 Degrading,
140 Stable,
142}
143
144#[ derive( Debug, Clone ) ]
146pub struct RegressionAnalyzer
147{
148 significance_threshold: f64,
150 trend_window: usize,
152 baseline_strategy: BaselineStrategy,
154}
155
156impl RegressionAnalyzer
157{
158 #[ must_use ]
160 pub fn new() -> Self
161 {
162 Self
163 {
164 significance_threshold: 0.05,
165 trend_window: 5,
166 baseline_strategy: BaselineStrategy ::FixedBaseline,
167 }
168 }
169
170 #[ must_use ]
172 pub fn with_baseline_strategy( mut self, strategy: BaselineStrategy ) -> Self
173 {
174 self.baseline_strategy = strategy;
175 self
176 }
177
178 #[ must_use ]
180 pub fn with_significance_threshold( mut self, threshold: f64 ) -> Self
181 {
182 self.significance_threshold = threshold;
183 self
184 }
185
186 #[ must_use ]
188 pub fn with_trend_window( mut self, window: usize ) -> Self
189 {
190 self.trend_window = window;
191 self
192 }
193
194 #[ must_use ]
196 pub fn analyze( &self, results: &HashMap< String, BenchmarkResult >, historical: &HistoricalResults ) -> RegressionReport
197 {
198 let mut report = RegressionReport ::new();
199
200 for ( operation_name, current_result ) in results
201 {
202 let analysis = self.analyze_single_operation( operation_name, current_result, historical );
203 report.add_operation_analysis( operation_name.clone(), analysis );
204 }
205
206 report
207 }
208
209 fn analyze_single_operation( &self, operation_name: &str, current_result: &BenchmarkResult, historical: &HistoricalResults ) -> OperationAnalysis
211 {
212 match self.baseline_strategy
213 {
214 BaselineStrategy ::FixedBaseline => self.analyze_against_fixed_baseline( operation_name, current_result, historical ),
215 BaselineStrategy ::RollingAverage => self.analyze_against_rolling_average( operation_name, current_result, historical ),
216 BaselineStrategy ::PreviousRun => self.analyze_against_previous_run( operation_name, current_result, historical ),
217 }
218 }
219
220 fn analyze_against_fixed_baseline( &self, operation_name: &str, current_result: &BenchmarkResult, historical: &HistoricalResults ) -> OperationAnalysis
222 {
223 if let Some( baseline_result ) = historical.baseline_data().get( operation_name )
224 {
225 let current_time = current_result.mean_time().as_secs_f64();
226 let baseline_time = baseline_result.mean_time().as_secs_f64();
227 let improvement_ratio = baseline_time / current_time;
228
229 let trend = if improvement_ratio > 1.0 + self.significance_threshold
230 {
231 PerformanceTrend ::Improving
232 }
233 else if improvement_ratio < 1.0 - self.significance_threshold
234 {
235 PerformanceTrend ::Degrading
236 }
237 else
238 {
239 PerformanceTrend ::Stable
240 };
241
242 let is_significant = ( improvement_ratio - 1.0 ).abs() > self.significance_threshold;
243
244 OperationAnalysis
245 {
246 trend,
247 improvement_ratio,
248 is_statistically_significant: is_significant,
249 baseline_time: Some( baseline_time ),
250 has_historical_data: true,
251 }
252 }
253 else
254 {
255 OperationAnalysis ::no_data()
256 }
257 }
258
259 fn analyze_against_rolling_average( &self, operation_name: &str, current_result: &BenchmarkResult, historical: &HistoricalResults ) -> OperationAnalysis
261 {
262 let historical_runs = historical.historical_runs();
263 if historical_runs.is_empty()
264 {
265 return OperationAnalysis ::no_data();
266 }
267
268 let recent_runs: Vec< _ > = historical_runs
270 .iter()
271 .rev() .take( self.trend_window )
273 .filter_map( | run | run.results().get( operation_name ) )
274 .collect();
275
276 if recent_runs.is_empty()
277 {
278 return OperationAnalysis ::no_data();
279 }
280
281 let avg_time = recent_runs.iter()
282 .map( | result | result.mean_time().as_secs_f64() )
283 .sum :: < f64 >() / recent_runs.len() as f64;
284
285 let current_time = current_result.mean_time().as_secs_f64();
286 let improvement_ratio = avg_time / current_time;
287
288 let trend = if improvement_ratio > 1.0 + self.significance_threshold
289 {
290 PerformanceTrend ::Improving
291 }
292 else if improvement_ratio < 1.0 - self.significance_threshold
293 {
294 PerformanceTrend ::Degrading
295 }
296 else
297 {
298 PerformanceTrend ::Stable
299 };
300
301 let is_significant = ( improvement_ratio - 1.0 ).abs() > self.significance_threshold;
302
303 OperationAnalysis
304 {
305 trend,
306 improvement_ratio,
307 is_statistically_significant: is_significant,
308 baseline_time: Some( avg_time ),
309 has_historical_data: true,
310 }
311 }
312
313 fn analyze_against_previous_run( &self, operation_name: &str, current_result: &BenchmarkResult, historical: &HistoricalResults ) -> OperationAnalysis
315 {
316 let historical_runs = historical.historical_runs();
317 if let Some( previous_run ) = historical_runs.last()
318 {
319 if let Some( previous_result ) = previous_run.results().get( operation_name )
320 {
321 let current_time = current_result.mean_time().as_secs_f64();
322 let previous_time = previous_result.mean_time().as_secs_f64();
323 let improvement_ratio = previous_time / current_time;
324
325 let trend = if improvement_ratio > 1.0 + self.significance_threshold
326 {
327 PerformanceTrend ::Improving
328 }
329 else if improvement_ratio < 1.0 - self.significance_threshold
330 {
331 PerformanceTrend ::Degrading
332 }
333 else
334 {
335 PerformanceTrend ::Stable
336 };
337
338 let is_significant = ( improvement_ratio - 1.0 ).abs() > self.significance_threshold;
339
340 OperationAnalysis
341 {
342 trend,
343 improvement_ratio,
344 is_statistically_significant: is_significant,
345 baseline_time: Some( previous_time ),
346 has_historical_data: true,
347 }
348 }
349 else
350 {
351 OperationAnalysis ::no_data()
352 }
353 }
354 else
355 {
356 OperationAnalysis ::no_data()
357 }
358 }
359}
360
361impl Default for RegressionAnalyzer
362{
363 fn default() -> Self
364 {
365 Self ::new()
366 }
367}
368
369#[ derive( Debug, Clone ) ]
371pub struct OperationAnalysis
372{
373 trend: PerformanceTrend,
374 improvement_ratio: f64,
375 is_statistically_significant: bool,
376 baseline_time: Option< f64 >,
377 has_historical_data: bool,
378}
379
380impl OperationAnalysis
381{
382 #[ must_use ]
384 fn no_data() -> Self
385 {
386 Self
387 {
388 trend: PerformanceTrend ::Stable,
389 improvement_ratio: 1.0,
390 is_statistically_significant: false,
391 baseline_time: None,
392 has_historical_data: false,
393 }
394 }
395}
396
397#[ derive( Debug, Clone ) ]
399pub struct RegressionReport
400{
401 operations: HashMap< String, OperationAnalysis >,
402}
403
404impl RegressionReport
405{
406 #[ must_use ]
408 fn new() -> Self
409 {
410 Self
411 {
412 operations: HashMap ::new(),
413 }
414 }
415
416 fn add_operation_analysis( &mut self, operation: String, analysis: OperationAnalysis )
418 {
419 self.operations.insert( operation, analysis );
420 }
421
422 #[ must_use ]
424 pub fn has_significant_changes( &self ) -> bool
425 {
426 self.operations.values().any( | analysis | analysis.is_statistically_significant )
427 }
428
429 #[ must_use ]
431 pub fn get_trend_for( &self, operation: &str ) -> Option< PerformanceTrend >
432 {
433 self.operations.get( operation ).map( | analysis | analysis.trend.clone() )
434 }
435
436 #[ must_use ]
438 pub fn is_statistically_significant( &self, operation: &str ) -> bool
439 {
440 self.operations.get( operation )
441 .is_some_and( | analysis | analysis.is_statistically_significant )
442 }
443
444 #[ must_use ]
446 pub fn has_historical_data( &self, operation: &str ) -> bool
447 {
448 self.operations.get( operation )
449 .is_some_and( | analysis | analysis.has_historical_data )
450 }
451
452 #[ must_use ]
454 pub fn has_previous_run_data( &self ) -> bool
455 {
456 self.operations.values().any( | analysis | analysis.has_historical_data )
457 }
458
459 #[ must_use ]
461 pub fn format_markdown( &self ) -> String
462 {
463 let mut output = String ::new();
464
465 output.push_str( "### Performance Comparison Against Baseline\n\n" );
466
467 for ( operation_name, analysis ) in &self.operations
468 {
469 if !analysis.has_historical_data
470 {
471 output.push_str( &format!(
472 "**{}** : âšī¸ **New operation** - no baseline data available for comparison\n\n",
473 operation_name
474 ) );
475 continue;
476 }
477
478 if let Some( _baseline_time ) = analysis.baseline_time
479 {
480 let improvement_percent = ( analysis.improvement_ratio - 1.0 ) * 100.0;
481
482 match analysis.trend
483 {
484 PerformanceTrend ::Improving =>
485 {
486 output.push_str( &format!(
487 "**{}** : đ **Performance improvement detected** - {:.1}% faster than baseline\n\n",
488 operation_name,
489 improvement_percent
490 ) );
491 },
492 PerformanceTrend ::Degrading =>
493 {
494 output.push_str( &format!(
495 "**{}** : â ī¸ **Performance regression detected** - {:.1}% slower than baseline\n\n",
496 operation_name,
497 improvement_percent.abs()
498 ) );
499 },
500 PerformanceTrend ::Stable =>
501 {
502 output.push_str( &format!(
503 "**{}** : â
**Performance stable** - within normal variation of baseline\n\n",
504 operation_name
505 ) );
506 },
507 }
508 }
509 }
510
511 output.push_str( "### Analysis Summary & Recommendations\n\n" );
512 output.push_str( "Regression analysis complete. See individual operation results above for detailed findings.\n\n" );
513
514 output
515 }
516}
517
518pub trait ReportTemplate
520{
521 fn generate( &self, results: &HashMap< String, BenchmarkResult > ) -> Result< String >;
523}
524
525#[ derive( Debug, Clone ) ]
527pub struct PerformanceReport
528{
529 title: String,
531 context: Option< String >,
533 include_statistical_analysis: bool,
535 include_regression_analysis: bool,
537 custom_sections: Vec< CustomSection >,
539 historical_data: Option< HistoricalResults >,
541}
542
543impl PerformanceReport
544{
545 #[ must_use ]
547 pub fn new() -> Self
548 {
549 Self
550 {
551 title: "Performance Analysis".to_string(),
552 context: None,
553 include_statistical_analysis: true,
554 include_regression_analysis: false,
555 custom_sections: Vec ::new(),
556 historical_data: None,
557 }
558 }
559
560 #[ must_use ]
562 pub fn title( mut self, title: impl Into< String > ) -> Self
563 {
564 self.title = title.into();
565 self
566 }
567
568 #[ must_use ]
570 pub fn add_context( mut self, context: impl Into< String > ) -> Self
571 {
572 self.context = Some( context.into() );
573 self
574 }
575
576 #[ must_use ]
578 pub fn include_statistical_analysis( mut self, include: bool ) -> Self
579 {
580 self.include_statistical_analysis = include;
581 self
582 }
583
584 #[ must_use ]
586 pub fn include_regression_analysis( mut self, include: bool ) -> Self
587 {
588 self.include_regression_analysis = include;
589 self
590 }
591
592 #[ must_use ]
594 pub fn add_custom_section( mut self, section: CustomSection ) -> Self
595 {
596 self.custom_sections.push( section );
597 self
598 }
599
600 #[ must_use ]
602 pub fn with_historical_data( mut self, historical: HistoricalResults ) -> Self
603 {
604 self.historical_data = Some( historical );
605 self
606 }
607}
608
609impl Default for PerformanceReport
610{
611 fn default() -> Self
612 {
613 Self ::new()
614 }
615}
616
617impl ReportTemplate for PerformanceReport
618{
619 fn generate( &self, results: &HashMap< String, BenchmarkResult > ) -> Result< String >
620 {
621 let mut output = String ::new();
622
623 output.push_str( &format!( "# {}\n\n", self.title ) );
625
626 if let Some( ref context ) = self.context
627 {
628 output.push_str( &format!( "*{}*\n\n", context ) );
629 }
630
631 if results.is_empty()
632 {
633 output.push_str( "No benchmark results available.\n" );
634 return Ok( output );
635 }
636
637 output.push_str( "## Executive Summary\n\n" );
639 self.add_executive_summary( &mut output, results );
640
641 output.push_str( "## Performance Results\n\n" );
643 self.add_performance_table( &mut output, results );
644
645 if self.include_statistical_analysis
647 {
648 output.push_str( "## Statistical Analysis\n\n" );
649 self.add_statistical_analysis( &mut output, results );
650 }
651
652 if self.include_regression_analysis
654 {
655 output.push_str( "## Regression Analysis\n\n" );
656 self.add_regression_analysis( &mut output, results );
657 }
658
659 for section in &self.custom_sections
661 {
662 output.push_str( &format!( "## {}\n\n", section.title ) );
663 output.push_str( §ion.content );
664 output.push_str( "\n\n" );
665 }
666
667 output.push_str( "## Methodology\n\n" );
669 self.add_methodology_note( &mut output );
670
671 Ok( output )
672 }
673}
674
675impl PerformanceReport
676{
677 fn add_executive_summary( &self, output: &mut String, results: &HashMap< String, BenchmarkResult > )
679 {
680 let total_tests = results.len();
681 let reliable_tests = results.values().filter( | r | r.is_reliable() ).count();
682 let reliability_rate = ( reliable_tests as f64 / total_tests as f64 ) * 100.0;
683
684 output.push_str( &format!( "- **Total operations benchmarked** : {}\n", total_tests ) );
685 output.push_str( &format!( "- **Statistically reliable results** : {}/{} ({:.1}%)\n",
686 reliable_tests, total_tests, reliability_rate ) );
687
688 if let Some( ( fastest_name, fastest_result ) ) = self.find_fastest( results )
689 {
690 output.push_str( &format!( "- **Best performing operation** : {} ({:.2?})\n",
691 fastest_name, fastest_result.mean_time() ) );
692 }
693
694 if results.len() > 1
695 {
696 if let Some( ( slowest_name, slowest_result ) ) = self.find_slowest( results )
697 {
698 if let Some( ( fastest_name_inner, fastest_result ) ) = self.find_fastest( results )
699 {
700 let ratio = slowest_result.mean_time().as_secs_f64() / fastest_result.mean_time().as_secs_f64();
701 output.push_str( &format!( "- **Performance range** : {:.1}x difference ({} vs {})\n",
702 ratio, fastest_name_inner, slowest_name ) );
703 }
704 }
705 }
706
707 output.push_str( "\n" );
708 }
709
710 fn add_performance_table( &self, output: &mut String, results: &HashMap< String, BenchmarkResult > )
712 {
713 output.push_str( "| Operation | Mean Time | 95% CI | Ops/sec | CV | Reliability | Samples |\n" );
714 output.push_str( "|-----------|-----------|--------|---------|----|-----------|---------|\n" );
715
716 let mut sorted_results: Vec< _ > = results.iter().collect();
718 sorted_results.sort_by( | a, b | a.1.mean_time().cmp( &b.1.mean_time() ) );
719
720 for ( name, result ) in sorted_results
721 {
722 let ( ci_lower, ci_upper ) = result.confidence_interval_95();
723 let cv = result.coefficient_of_variation();
724 let reliability = if result.is_reliable() { "â
" } else { "â ī¸" };
725
726 output.push_str( &format!(
727 "| {} | {:.2?} | [{:.2?} - {:.2?}] | {:.0} | {:.1}% | {} | {} |\n",
728 name,
729 result.mean_time(),
730 ci_lower,
731 ci_upper,
732 result.operations_per_second(),
733 cv * 100.0,
734 reliability,
735 result.times.len()
736 ) );
737 }
738
739 output.push_str( "\n" );
740 }
741
742 fn add_statistical_analysis( &self, output: &mut String, results: &HashMap< String, BenchmarkResult > )
744 {
745 let mut high_quality = Vec ::new();
746 let mut needs_improvement = Vec ::new();
747
748 for ( name, result ) in results
749 {
750 if result.is_reliable()
751 {
752 high_quality.push( name );
753 }
754 else
755 {
756 let cv = result.coefficient_of_variation();
757 let sample_size = result.times.len();
758 let mut issues = Vec ::new();
759
760 if sample_size < 10
761 {
762 issues.push( "insufficient samples" );
763 }
764 if cv > 0.1
765 {
766 issues.push( "high variability" );
767 }
768
769 needs_improvement.push( ( name, issues ) );
770 }
771 }
772
773 if !high_quality.is_empty()
774 {
775 output.push_str( "### â
Reliable Results\n" );
776 output.push_str( "*These measurements meet research-grade statistical standards*\n\n" );
777 for name in high_quality
778 {
779 let result = &results[ name ];
780 output.push_str( &format!( "- **{}** : {} samples, CV={:.1}%\n",
781 name,
782 result.times.len(),
783 result.coefficient_of_variation() * 100.0 ) );
784 }
785 output.push_str( "\n" );
786 }
787
788 if !needs_improvement.is_empty()
789 {
790 output.push_str( "### â ī¸ Measurements Needing Attention\n" );
791 output.push_str( "*Consider additional measurements for more reliable conclusions*\n\n" );
792 for ( name, issues ) in needs_improvement
793 {
794 output.push_str( &format!( "- **{}** : {}\n", name, issues.join( ", " ) ) );
795 }
796 output.push_str( "\n" );
797 }
798 }
799
800 fn add_regression_analysis( &self, output: &mut String, results: &HashMap< String, BenchmarkResult > )
802 {
803 if let Some( ref historical ) = self.historical_data
804 {
805 let analyzer = RegressionAnalyzer ::new()
807 .with_baseline_strategy( BaselineStrategy ::FixedBaseline )
808 .with_significance_threshold( 0.05 );
809
810 let regression_report = analyzer.analyze( results, historical );
811 let markdown_output = regression_report.format_markdown();
812
813 output.push_str( &markdown_output );
814
815 self.add_enhanced_recommendations( output, ®ression_report, results );
817 }
818 else
819 {
820 output.push_str( "**Regression Analysis** : Not yet implemented. Historical baseline data required.\n\n" );
822 output.push_str( "**đ Setup Guide** : See [`usage.md`](usage.md) for mandatory standards and requirements on: \n" );
823 output.push_str( "- Historical data collection and baseline management\n" );
824 output.push_str( "- Statistical analysis requirements and validation criteria\n" );
825 output.push_str( "- Integration with CI/CD pipelines for automated regression detection\n" );
826 output.push_str( "- Documentation automation best practices\n\n" );
827 }
828 }
829
830
831 fn add_enhanced_recommendations( &self, output: &mut String, regression_report: &RegressionReport, results: &HashMap< String, BenchmarkResult > )
833 {
834 let mut improving_ops = Vec ::new();
836 let mut degrading_ops = Vec ::new();
837 let mut stable_ops = Vec ::new();
838 let mut new_ops = Vec ::new();
839
840 for operation_name in results.keys()
841 {
842 match regression_report.get_trend_for( operation_name )
843 {
844 Some( PerformanceTrend ::Improving ) =>
845 {
846 if regression_report.is_statistically_significant( operation_name )
847 {
848 improving_ops.push( operation_name );
849 }
850 },
851 Some( PerformanceTrend ::Degrading ) =>
852 {
853 if regression_report.is_statistically_significant( operation_name )
854 {
855 degrading_ops.push( operation_name );
856 }
857 },
858 Some( PerformanceTrend ::Stable ) =>
859 {
860 stable_ops.push( operation_name );
861 },
862 None =>
863 {
864 if !regression_report.has_historical_data( operation_name )
865 {
866 new_ops.push( operation_name );
867 }
868 },
869 }
870 }
871
872 if !improving_ops.is_empty() || !degrading_ops.is_empty() || regression_report.has_significant_changes()
873 {
874 output.push_str( "### đ **Statistical Analysis Summary**\n\n" );
875
876 if regression_report.has_significant_changes()
877 {
878 output.push_str( "**Statistically Significant Changes Detected** : This analysis identified performance changes that exceed normal measurement variance.\n\n" );
879 }
880 else
881 {
882 output.push_str( "**No Statistically Significant Changes** : All performance variations are within expected measurement noise.\n\n" );
883 }
884 }
885
886 if !improving_ops.is_empty()
887 {
888 output.push_str( "### đ¯ **Performance Optimization Insights**\n\n" );
889 output.push_str( "The following operations show statistically significant improvements: \n" );
890 for op in &improving_ops
891 {
892 output.push_str( &format!( "- **{}** : Consider documenting optimization techniques for knowledge sharing\n", op ) );
893 }
894 output.push_str( "\n**Next Steps** : Update performance baselines and validate improvements under production conditions.\n\n" );
895 }
896
897 if !degrading_ops.is_empty()
898 {
899 output.push_str( "### â ī¸ **Regression Investigation Required**\n\n" );
900 output.push_str( "**Critical** : The following operations show statistically significant performance degradation: \n" );
901 for op in °rading_ops
902 {
903 output.push_str( &format!( "- **{}** : Requires immediate investigation\n", op ) );
904 }
905 output.push_str( "\n**Recommended Actions** : \n" );
906 output.push_str( "1. **Profile regressed operations** to identify bottlenecks\n" );
907 output.push_str( "2. **Review recent code changes** affecting these operations\n" );
908 output.push_str( "3. **Run additional validation** with increased sample sizes\n" );
909 output.push_str( "4. **Consider deployment hold** until regressions are resolved\n\n" );
910 }
911
912 output.push_str( "### đ **Integration Resources**\n\n" );
914 output.push_str( "For enhanced regression analysis capabilities: \n" );
915 output.push_str( "- **Configure baseline strategies** : Use `RegressionAnalyzer ::with_baseline_strategy()` for rolling averages or previous-run comparisons\n" );
916 output.push_str( "- **Adjust significance thresholds** : Use `with_significance_threshold()` for domain-specific sensitivity\n" );
917 output.push_str( "- **Historical data management** : Implement `TimestampedResults` for comprehensive trend analysis\n" );
918 output.push_str( "- **Automated monitoring** : Integrate with CI/CD pipelines for continuous performance validation\n\n" );
919 }
920
921 fn add_methodology_note( &self, output: &mut String )
923 {
924 output.push_str( "**Statistical Reliability Criteria** : \n" );
925 output.push_str( "- Sample size âĨ 10 measurements\n" );
926 output.push_str( "- Coefficient of variation ⤠10%\n" );
927 output.push_str( "- Maximum/minimum time ratio < 3.0x\n\n" );
928
929 output.push_str( "**Confidence Intervals** : 95% CI calculated using t-distribution\n" );
930 output.push_str( "**CV** : Coefficient of Variation (relative standard deviation)\n\n" );
931
932 output.push_str( "---\n" );
933 output.push_str( "*Generated by benchkit - Professional benchmarking toolkit*\n" );
934 }
935
936 fn find_fastest< 'a >( &self, results: &'a HashMap< String, BenchmarkResult > ) -> Option< ( &'a String, &'a BenchmarkResult ) >
938 {
939 results.iter().min_by( | a, b | a.1.mean_time().cmp( &b.1.mean_time() ) )
940 }
941
942 fn find_slowest< 'a >( &self, results: &'a HashMap< String, BenchmarkResult > ) -> Option< ( &'a String, &'a BenchmarkResult ) >
944 {
945 results.iter().max_by( | a, b | a.1.mean_time().cmp( &b.1.mean_time() ) )
946 }
947}
948
949#[ derive( Debug, Clone ) ]
951pub struct ComparisonReport
952{
953 title: String,
955 baseline: String,
957 candidate: String,
959 significance_threshold: f64,
961 practical_significance_threshold: f64,
963}
964
965impl ComparisonReport
966{
967 #[ must_use ]
969 pub fn new() -> Self
970 {
971 Self
972 {
973 title: "Performance Comparison".to_string(),
974 baseline: "Baseline".to_string(),
975 candidate: "Candidate".to_string(),
976 significance_threshold: 0.05,
977 practical_significance_threshold: 0.10,
978 }
979 }
980
981 #[ must_use ]
983 pub fn title( mut self, title: impl Into< String > ) -> Self
984 {
985 self.title = title.into();
986 self
987 }
988
989 #[ must_use ]
991 pub fn baseline( mut self, baseline: impl Into< String > ) -> Self
992 {
993 self.baseline = baseline.into();
994 self
995 }
996
997 #[ must_use ]
999 pub fn candidate( mut self, candidate: impl Into< String > ) -> Self
1000 {
1001 self.candidate = candidate.into();
1002 self
1003 }
1004
1005 #[ must_use ]
1007 pub fn significance_threshold( mut self, threshold: f64 ) -> Self
1008 {
1009 self.significance_threshold = threshold;
1010 self
1011 }
1012
1013 #[ must_use ]
1015 pub fn practical_significance_threshold( mut self, threshold: f64 ) -> Self
1016 {
1017 self.practical_significance_threshold = threshold;
1018 self
1019 }
1020}
1021
1022impl Default for ComparisonReport
1023{
1024 fn default() -> Self
1025 {
1026 Self ::new()
1027 }
1028}
1029
1030impl ComparisonReport
1031{
1032 #[ must_use ]
1034 pub fn baseline_name( &self ) -> &str
1035 {
1036 &self.baseline
1037 }
1038
1039 #[ must_use ]
1041 pub fn candidate_name( &self ) -> &str
1042 {
1043 &self.candidate
1044 }
1045
1046 #[ must_use ]
1048 pub fn significance_threshold_value( &self ) -> f64
1049 {
1050 self.significance_threshold
1051 }
1052
1053 #[ must_use ]
1055 pub fn practical_significance_threshold_value( &self ) -> f64
1056 {
1057 self.practical_significance_threshold
1058 }
1059}
1060
1061impl ReportTemplate for ComparisonReport
1062{
1063 fn generate( &self, results: &HashMap< String, BenchmarkResult > ) -> Result< String >
1064 {
1065 let mut output = String ::new();
1066
1067 output.push_str( &format!( "# {}\n\n", self.title ) );
1068
1069 let baseline_result = results.get( &self.baseline )
1071 .ok_or_else( || -> Box< dyn std ::error ::Error > { format!( "Baseline result '{}' not found", self.baseline ).into() } )?;
1072 let candidate_result = results.get( &self.candidate )
1073 .ok_or_else( || -> Box< dyn std ::error ::Error > { format!( "Candidate result '{}' not found", self.candidate ).into() } )?;
1074
1075 let baseline_time = baseline_result.mean_time().as_secs_f64();
1077 let candidate_time = candidate_result.mean_time().as_secs_f64();
1078 let improvement_ratio = baseline_time / candidate_time;
1079 let improvement_percent = ( improvement_ratio - 1.0 ) * 100.0;
1080
1081 output.push_str( "## Comparison Summary\n\n" );
1083
1084 if improvement_ratio > 1.0 + self.practical_significance_threshold
1085 {
1086 output.push_str( &format!( "â
**{} is {:.1}% faster** than {}\n\n",
1087 self.candidate, improvement_percent, self.baseline ) );
1088 }
1089 else if improvement_ratio < 1.0 - self.practical_significance_threshold
1090 {
1091 let regression_percent = ( 1.0 - improvement_ratio ) * 100.0;
1092 output.push_str( &format!( "đ¨ **{} is {:.1}% slower** than {}\n\n",
1093 self.candidate, regression_percent, self.baseline ) );
1094 }
1095 else
1096 {
1097 output.push_str( &format!( "âī¸ **No significant difference** between {} and {}\n\n",
1098 self.baseline, self.candidate ) );
1099 }
1100
1101 output.push_str( "## Detailed Comparison\n\n" );
1103 output.push_str( "| Algorithm | Mean Time | 95% CI | Ops/sec | CV | Samples | Reliability |\n" );
1104 output.push_str( "|-----------|-----------|--------|---------|----|---------|-----------|\n" );
1105
1106 for ( name, result ) in [ ( &self.baseline, baseline_result ), ( &self.candidate, candidate_result ) ]
1107 {
1108 let ( ci_lower, ci_upper ) = result.confidence_interval_95();
1109 let cv = result.coefficient_of_variation();
1110 let reliability = if result.is_reliable() { "â
" } else { "â ī¸" };
1111
1112 output.push_str( &format!(
1113 "| {} | {:.2?} | [{:.2?} - {:.2?}] | {:.0} | {:.1}% | {} | {} |\n",
1114 name,
1115 result.mean_time(),
1116 ci_lower,
1117 ci_upper,
1118 result.operations_per_second(),
1119 cv * 100.0,
1120 result.times.len(),
1121 reliability
1122 ) );
1123 }
1124
1125 output.push_str( "\n" );
1126
1127 output.push_str( "## Statistical Analysis\n\n" );
1129 output.push_str( &format!( "- **Performance ratio** : {:.3}x\n", improvement_ratio ) );
1130 output.push_str( &format!( "- **Improvement** : {:.1}%\n", improvement_percent ) );
1131
1132 let baseline_ci = baseline_result.confidence_interval_95();
1134 let candidate_ci = candidate_result.confidence_interval_95();
1135 let ci_overlap = baseline_ci.1 >= candidate_ci.0 && candidate_ci.1 >= baseline_ci.0;
1136
1137 if ci_overlap
1138 {
1139 output.push_str( "- **Statistical significance** : â ī¸ Confidence intervals overlap - difference may not be statistically significant\n" );
1140 }
1141 else
1142 {
1143 output.push_str( "- **Statistical significance** : â
No confidence interval overlap - difference is likely statistically significant\n" );
1144 }
1145
1146 if improvement_percent.abs() >= self.practical_significance_threshold * 100.0
1148 {
1149 output.push_str( &format!( "- **Practical significance** : â
Difference exceeds {:.1}% threshold\n",
1150 self.practical_significance_threshold * 100.0 ) );
1151 }
1152 else
1153 {
1154 output.push_str( &format!( "- **Practical significance** : â ī¸ Difference below {:.1}% threshold\n",
1155 self.practical_significance_threshold * 100.0 ) );
1156 }
1157
1158 output.push_str( "\n" );
1159
1160 output.push_str( "## Reliability Assessment\n\n" );
1162
1163 if baseline_result.is_reliable() && candidate_result.is_reliable()
1164 {
1165 output.push_str( "â
**Both measurements are statistically reliable** - conclusions can be drawn with confidence.\n\n" );
1166 }
1167 else
1168 {
1169 output.push_str( "â ī¸ **One or both measurements have reliability concerns** - consider additional sampling.\n\n" );
1170
1171 if !baseline_result.is_reliable()
1172 {
1173 output.push_str( &format!( "- **{}** : {} samples, CV={:.1}%\n",
1174 self.baseline,
1175 baseline_result.times.len(),
1176 baseline_result.coefficient_of_variation() * 100.0 ) );
1177 }
1178
1179 if !candidate_result.is_reliable()
1180 {
1181 output.push_str( &format!( "- **{}** : {} samples, CV={:.1}%\n",
1182 self.candidate,
1183 candidate_result.times.len(),
1184 candidate_result.coefficient_of_variation() * 100.0 ) );
1185 }
1186
1187 output.push_str( "\n" );
1188 }
1189
1190 output.push_str( "## Methodology\n\n" );
1192 output.push_str( &format!( "**Significance Thresholds** : Statistical p < {}, Practical > {:.1}%\n",
1193 self.significance_threshold,
1194 self.practical_significance_threshold * 100.0 ) );
1195 output.push_str( "**Confidence Intervals** : 95% CI using t-distribution\n" );
1196 output.push_str( "**Reliability Criteria** : âĨ10 samples, CV â¤10%, max/min ratio <3x\n\n" );
1197
1198 output.push_str( "---\n" );
1199 output.push_str( "*Generated by benchkit - Professional benchmarking toolkit*\n" );
1200
1201 Ok( output )
1202 }
1203}
1204
1205#[ derive( Debug, Clone ) ]
1207pub struct CustomSection
1208{
1209 pub title: String,
1211 pub content: String,
1213}
1214
1215impl CustomSection
1216{
1217 #[ must_use ]
1219 pub fn new( title: impl Into< String >, content: impl Into< String > ) -> Self
1220 {
1221 Self
1222 {
1223 title: title.into(),
1224 content: content.into(),
1225 }
1226 }
1227}