1use std::iter;
2use std::path::Path;
3use std::process::Child;
4
5use crate::stats::univariate::Sample;
6use criterion_plot::prelude::*;
7
8mod distributions;
9mod iteration_times;
10mod pdf;
11mod regression;
12mod summary;
13mod t_test;
14use self::distributions::*;
15use self::iteration_times::*;
16use self::pdf::*;
17use self::regression::*;
18use self::summary::*;
19use self::t_test::*;
20
21use crate::measurement::ValueFormatter;
22use crate::report::{BenchmarkId, ValueType};
23use crate::stats::bivariate::Data;
24
25use super::{PlotContext, PlotData, Plotter};
26use crate::format;
27
28fn gnuplot_escape(string: &str) -> String {
29 string.replace('_', "\\_").replace('\'', "''")
30}
31
32static DEFAULT_FONT: &str = "Helvetica";
33static KDE_POINTS: usize = 500;
34static SIZE: Size = Size(1280, 720);
35
36const LINEWIDTH: LineWidth = LineWidth(2.);
37const POINT_SIZE: PointSize = PointSize(0.75);
38
39const DARK_BLUE: Color = Color::Rgb(31, 120, 180);
40const DARK_ORANGE: Color = Color::Rgb(255, 127, 0);
41const DARK_RED: Color = Color::Rgb(227, 26, 28);
42
43fn 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
56trait Append<T> {
57 /// Private
58 fn append_(self, item: T) -> Self;
59}
60
61// NB I wish this was in the standard library
62impl<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)]
70pub(crate) struct Gnuplot {
71 process_list: Vec<Child>,
72}
73
74impl 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