criterion/plot/gnuplot_backend/
mod.rs

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}