use std::iter; use std::path::PathBuf; use std::process::Child; use crate::stats::univariate::Sample; use criterion_plot::prelude::*; mod distributions; mod iteration_times; mod pdf; mod regression; mod summary; mod t_test; use self::distributions::*; use self::iteration_times::*; use self::pdf::*; use self::regression::*; use self::summary::*; use self::t_test::*; use crate::measurement::ValueFormatter; use crate::report::{BenchmarkId, ValueType}; use crate::stats::bivariate::Data; use super::{PlotContext, PlotData, Plotter}; use crate::format; fn gnuplot_escape(string: &str) -> String { string.replace("_", "\\_").replace("'", "''") } static DEFAULT_FONT: &str = "Helvetica"; static KDE_POINTS: usize = 500; static SIZE: Size = Size(1280, 720); const LINEWIDTH: LineWidth = LineWidth(2.); const POINT_SIZE: PointSize = PointSize(0.75); const DARK_BLUE: Color = Color::Rgb(31, 120, 180); const DARK_ORANGE: Color = Color::Rgb(255, 127, 0); const DARK_RED: Color = Color::Rgb(227, 26, 28); fn debug_script(path: &PathBuf, figure: &Figure) { if crate::debug_enabled() { let mut script_path = path.clone(); script_path.set_extension("gnuplot"); info!("Writing gnuplot script to {:?}", script_path); let result = figure.save(script_path.as_path()); if let Err(e) = result { error!("Failed to write debug output: {}", e); } } } /// Private trait Append { /// Private fn append_(self, item: T) -> Self; } // NB I wish this was in the standard library impl Append for Vec { fn append_(mut self, item: T) -> Vec { self.push(item); self } } #[derive(Default)] pub(crate) struct Gnuplot { process_list: Vec, } impl Plotter for Gnuplot { fn pdf(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) { let size = ctx.size.map(|(w, h)| Size(w, h)); self.process_list.push(if ctx.is_thumbnail { if let Some(cmp) = data.comparison { pdf_comparison_small( ctx.id, ctx.context, data.formatter, data.measurements, cmp, size, ) } else { pdf_small(ctx.id, ctx.context, data.formatter, data.measurements, size) } } else if let Some(cmp) = data.comparison { pdf_comparison( ctx.id, ctx.context, data.formatter, data.measurements, cmp, size, ) } else { pdf(ctx.id, ctx.context, data.formatter, data.measurements, size) }); } fn regression(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) { let size = ctx.size.map(|(w, h)| Size(w, h)); self.process_list.push(if ctx.is_thumbnail { if let Some(cmp) = data.comparison { let base_data = Data::new(&cmp.base_iter_counts, &cmp.base_sample_times); regression_comparison_small( ctx.id, ctx.context, data.formatter, data.measurements, cmp, &base_data, size, ) } else { regression_small(ctx.id, ctx.context, data.formatter, data.measurements, size) } } else if let Some(cmp) = data.comparison { let base_data = Data::new(&cmp.base_iter_counts, &cmp.base_sample_times); regression_comparison( ctx.id, ctx.context, data.formatter, data.measurements, cmp, &base_data, size, ) } else { regression(ctx.id, ctx.context, data.formatter, data.measurements, size) }); } fn iteration_times(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) { let size = ctx.size.map(|(w, h)| Size(w, h)); self.process_list.push(if ctx.is_thumbnail { if let Some(cmp) = data.comparison { iteration_times_comparison_small( ctx.id, ctx.context, data.formatter, data.measurements, cmp, size, ) } else { iteration_times_small(ctx.id, ctx.context, data.formatter, data.measurements, size) } } else if let Some(cmp) = data.comparison { iteration_times_comparison( ctx.id, ctx.context, data.formatter, data.measurements, cmp, size, ) } else { iteration_times(ctx.id, ctx.context, data.formatter, data.measurements, size) }); } fn abs_distributions(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) { let size = ctx.size.map(|(w, h)| Size(w, h)); self.process_list.extend(abs_distributions( ctx.id, ctx.context, data.formatter, data.measurements, size, )); } fn rel_distributions(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) { let size = ctx.size.map(|(w, h)| Size(w, h)); if let Some(cmp) = data.comparison { self.process_list.extend(rel_distributions( ctx.id, ctx.context, data.measurements, cmp, size, )); } else { error!("Comparison data is not provided for a relative distribution figure"); } } fn t_test(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>) { let size = ctx.size.map(|(w, h)| Size(w, h)); if let Some(cmp) = data.comparison { self.process_list .push(t_test(ctx.id, ctx.context, data.measurements, cmp, size)); } else { error!("Comparison data is not provided for t_test plot"); } } fn line_comparison( &mut self, ctx: PlotContext<'_>, formatter: &dyn ValueFormatter, all_curves: &[&(&BenchmarkId, Vec)], value_type: ValueType, ) { let path = ctx.line_comparison_path(); self.process_list.push(line_comparison( formatter, ctx.id.as_title(), all_curves, &path, value_type, ctx.context.plot_config.summary_scale, )); } fn violin( &mut self, ctx: PlotContext<'_>, formatter: &dyn ValueFormatter, all_curves: &[&(&BenchmarkId, Vec)], ) { let violin_path = ctx.violin_path(); self.process_list.push(violin( formatter, ctx.id.as_title(), all_curves, &violin_path, ctx.context.plot_config.summary_scale, )); } fn wait(&mut self) { let start = std::time::Instant::now(); let child_count = self.process_list.len(); for child in self.process_list.drain(..) { match child.wait_with_output() { Ok(ref out) if out.status.success() => {} Ok(out) => error!("Error in Gnuplot: {}", String::from_utf8_lossy(&out.stderr)), Err(e) => error!("Got IO error while waiting for Gnuplot to complete: {}", e), } } let elapsed = &start.elapsed(); info!( "Waiting for {} gnuplot processes took {}", child_count, format::time(crate::DurationExt::to_nanos(elapsed) as f64) ); } }