1 use std::iter;
2 use std::path::PathBuf;
3 use std::process::Child;
4 
5 use crate::stats::univariate::Sample;
6 use criterion_plot::prelude::*;
7 
8 mod distributions;
9 mod iteration_times;
10 mod pdf;
11 mod regression;
12 mod summary;
13 mod t_test;
14 use self::distributions::*;
15 use self::iteration_times::*;
16 use self::pdf::*;
17 use self::regression::*;
18 use self::summary::*;
19 use self::t_test::*;
20 
21 use crate::measurement::ValueFormatter;
22 use crate::report::{BenchmarkId, ValueType};
23 use crate::stats::bivariate::Data;
24 
25 use super::{PlotContext, PlotData, Plotter};
26 use crate::format;
27 
gnuplot_escape(string: &str) -> String28 fn gnuplot_escape(string: &str) -> String {
29     string.replace("_", "\\_").replace("'", "''")
30 }
31 
32 static DEFAULT_FONT: &str = "Helvetica";
33 static KDE_POINTS: usize = 500;
34 static SIZE: Size = Size(1280, 720);
35 
36 const LINEWIDTH: LineWidth = LineWidth(2.);
37 const POINT_SIZE: PointSize = PointSize(0.75);
38 
39 const DARK_BLUE: Color = Color::Rgb(31, 120, 180);
40 const DARK_ORANGE: Color = Color::Rgb(255, 127, 0);
41 const DARK_RED: Color = Color::Rgb(227, 26, 28);
42 
debug_script(path: &PathBuf, figure: &Figure)43 fn debug_script(path: &PathBuf, figure: &Figure) {
44     if crate::debug_enabled() {
45         let mut script_path = path.clone();
46         script_path.set_extension("gnuplot");
47         info!("Writing gnuplot script to {:?}", script_path);
48         let result = figure.save(script_path.as_path());
49         if let Err(e) = result {
50             error!("Failed to write debug output: {}", e);
51         }
52     }
53 }
54 
55 /// Private
56 trait Append<T> {
57     /// Private
append_(self, item: T) -> Self58     fn append_(self, item: T) -> Self;
59 }
60 
61 // NB I wish this was in the standard library
62 impl<T> Append<T> for Vec<T> {
append_(mut self, item: T) -> Vec<T>63     fn append_(mut self, item: T) -> Vec<T> {
64         self.push(item);
65         self
66     }
67 }
68 
69 #[derive(Default)]
70 pub(crate) struct Gnuplot {
71     process_list: Vec<Child>,
72 }
73 
74 impl Plotter for Gnuplot {
pdf(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>)75     fn pdf(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) {
76         let size = ctx.size.map(|(w, h)| Size(w, h));
77         self.process_list.push(if ctx.is_thumbnail {
78             if let Some(cmp) = data.comparison {
79                 pdf_comparison_small(
80                     ctx.id,
81                     ctx.context,
82                     data.formatter,
83                     data.measurements,
84                     cmp,
85                     size,
86                 )
87             } else {
88                 pdf_small(ctx.id, ctx.context, data.formatter, data.measurements, size)
89             }
90         } else if let Some(cmp) = data.comparison {
91             pdf_comparison(
92                 ctx.id,
93                 ctx.context,
94                 data.formatter,
95                 data.measurements,
96                 cmp,
97                 size,
98             )
99         } else {
100             pdf(ctx.id, ctx.context, data.formatter, data.measurements, size)
101         });
102     }
103 
regression(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>)104     fn regression(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) {
105         let size = ctx.size.map(|(w, h)| Size(w, h));
106         self.process_list.push(if ctx.is_thumbnail {
107             if let Some(cmp) = data.comparison {
108                 let base_data = Data::new(&cmp.base_iter_counts, &cmp.base_sample_times);
109                 regression_comparison_small(
110                     ctx.id,
111                     ctx.context,
112                     data.formatter,
113                     data.measurements,
114                     cmp,
115                     &base_data,
116                     size,
117                 )
118             } else {
119                 regression_small(ctx.id, ctx.context, data.formatter, data.measurements, size)
120             }
121         } else if let Some(cmp) = data.comparison {
122             let base_data = Data::new(&cmp.base_iter_counts, &cmp.base_sample_times);
123             regression_comparison(
124                 ctx.id,
125                 ctx.context,
126                 data.formatter,
127                 data.measurements,
128                 cmp,
129                 &base_data,
130                 size,
131             )
132         } else {
133             regression(ctx.id, ctx.context, data.formatter, data.measurements, size)
134         });
135     }
136 
iteration_times(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>)137     fn iteration_times(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) {
138         let size = ctx.size.map(|(w, h)| Size(w, h));
139         self.process_list.push(if ctx.is_thumbnail {
140             if let Some(cmp) = data.comparison {
141                 iteration_times_comparison_small(
142                     ctx.id,
143                     ctx.context,
144                     data.formatter,
145                     data.measurements,
146                     cmp,
147                     size,
148                 )
149             } else {
150                 iteration_times_small(ctx.id, ctx.context, data.formatter, data.measurements, size)
151             }
152         } else if let Some(cmp) = data.comparison {
153             iteration_times_comparison(
154                 ctx.id,
155                 ctx.context,
156                 data.formatter,
157                 data.measurements,
158                 cmp,
159                 size,
160             )
161         } else {
162             iteration_times(ctx.id, ctx.context, data.formatter, data.measurements, size)
163         });
164     }
165 
abs_distributions(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>)166     fn abs_distributions(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) {
167         let size = ctx.size.map(|(w, h)| Size(w, h));
168         self.process_list.extend(abs_distributions(
169             ctx.id,
170             ctx.context,
171             data.formatter,
172             data.measurements,
173             size,
174         ));
175     }
176 
rel_distributions(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>)177     fn rel_distributions(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) {
178         let size = ctx.size.map(|(w, h)| Size(w, h));
179         if let Some(cmp) = data.comparison {
180             self.process_list.extend(rel_distributions(
181                 ctx.id,
182                 ctx.context,
183                 data.measurements,
184                 cmp,
185                 size,
186             ));
187         } else {
188             error!("Comparison data is not provided for a relative distribution figure");
189         }
190     }
191 
t_test(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>)192     fn t_test(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) {
193         let size = ctx.size.map(|(w, h)| Size(w, h));
194         if let Some(cmp) = data.comparison {
195             self.process_list
196                 .push(t_test(ctx.id, ctx.context, data.measurements, cmp, size));
197         } else {
198             error!("Comparison data is not provided for t_test plot");
199         }
200     }
201 
line_comparison( &mut self, ctx: PlotContext<'_>, formatter: &dyn ValueFormatter, all_curves: &[&(&BenchmarkId, Vec<f64>)], value_type: ValueType, )202     fn line_comparison(
203         &mut self,
204         ctx: PlotContext<'_>,
205         formatter: &dyn ValueFormatter,
206         all_curves: &[&(&BenchmarkId, Vec<f64>)],
207         value_type: ValueType,
208     ) {
209         let path = ctx.line_comparison_path();
210         self.process_list.push(line_comparison(
211             formatter,
212             ctx.id.as_title(),
213             all_curves,
214             &path,
215             value_type,
216             ctx.context.plot_config.summary_scale,
217         ));
218     }
219 
violin( &mut self, ctx: PlotContext<'_>, formatter: &dyn ValueFormatter, all_curves: &[&(&BenchmarkId, Vec<f64>)], )220     fn violin(
221         &mut self,
222         ctx: PlotContext<'_>,
223         formatter: &dyn ValueFormatter,
224         all_curves: &[&(&BenchmarkId, Vec<f64>)],
225     ) {
226         let violin_path = ctx.violin_path();
227 
228         self.process_list.push(violin(
229             formatter,
230             ctx.id.as_title(),
231             all_curves,
232             &violin_path,
233             ctx.context.plot_config.summary_scale,
234         ));
235     }
236 
wait(&mut self)237     fn wait(&mut self) {
238         let start = std::time::Instant::now();
239         let child_count = self.process_list.len();
240         for child in self.process_list.drain(..) {
241             match child.wait_with_output() {
242                 Ok(ref out) if out.status.success() => {}
243                 Ok(out) => error!("Error in Gnuplot: {}", String::from_utf8_lossy(&out.stderr)),
244                 Err(e) => error!("Got IO error while waiting for Gnuplot to complete: {}", e),
245             }
246         }
247         let elapsed = &start.elapsed();
248         info!(
249             "Waiting for {} gnuplot processes took {}",
250             child_count,
251             format::time(crate::DurationExt::to_nanos(elapsed) as f64)
252         );
253     }
254 }
255