1 | use std::iter; |
2 | use std::path::Path; |
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 | |
28 | 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 | |
43 | fn debug_script(path: &Path, figure: &Figure) { |
44 | if crate::debug_enabled() { |
45 | let mut script_path = path.to_path_buf(); |
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 |
58 | 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> { |
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 { |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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(elapsed.as_nanos() as f64) |
252 | ); |
253 | } |
254 | } |
255 | |