1use std::time::{Duration, Instant};
4
5use plotters::prelude::*;
6
7use crate::PerfPlotterError;
8
9pub fn generate_performance_png<F, E, I>(
10 func: F,
11 mut input_iter: I,
12 output: &std::path::Path,
13 caption: &str,
14) -> Result<(), PerfPlotterError>
15where
16 F: Fn(E) -> Option<()>,
17 I: Iterator<Item = E>,
18{
19 let mut perf_data: Vec<Duration> = Vec::new();
20 let mut i = 0usize;
21 while let Some(input) = input_iter.next() {
22 i += 1;
23 let now = Instant::now();
24 if func(input).is_some() {
25 let elapsed = now.elapsed();
26 println!("Round {}: {} secs", i, elapsed.as_secs_f32());
27 perf_data.push(elapsed);
28 } else {
29 break;
30 }
31 }
32
33 gen_png(&perf_data, output, caption)
34}
35
36fn gen_png(
37 data: &[Duration],
38 output: &std::path::Path,
39 caption: &str,
40) -> Result<(), PerfPlotterError> {
41 let root = BitMapBackend::new(output, (1920, 1080)).into_drawing_area();
42 root.fill(&WHITE)
43 .map_err(|e| PerfPlotterError::new(e.to_string()))?;
44 if data.is_empty() {
45 return Err(PerfPlotterError::new("Empty performance data".into()));
46 }
47
48 let min_dur = data.iter().min().unwrap();
50 let max_dur = data.iter().max().unwrap();
51
52 let mut chart = ChartBuilder::on(&root)
53 .caption(caption, ("sans-serif", 50).into_font())
54 .margin(20)
55 .x_label_area_size(30)
56 .y_label_area_size(30)
57 .build_cartesian_2d(
58 1.0f32..(data.len() as f32 + 1.0f32),
59 min_dur.as_secs_f32()..max_dur.as_secs_f32(),
60 )
61 .map_err(|e| PerfPlotterError::new(e.to_string()))?;
62
63 chart
64 .configure_mesh()
65 .x_desc("Round")
66 .y_desc("Sec")
67 .axis_desc_style(("sans-serif", 15))
68 .draw()
69 .map_err(|e| PerfPlotterError::new(e.to_string()))?;
70
71 chart
72 .draw_series(
73 LineSeries::new(
74 data.iter()
75 .enumerate()
76 .map(|(i, d)| (i as f32 + 1.0f32, d.as_secs_f32())),
77 RED,
78 )
79 .point_size(2),
80 )
81 .map_err(|e| PerfPlotterError::new(e.to_string()))?;
82
83 Ok(())
84}