Skip to content

Commit

Permalink
Adds ability to plot throughput on summary page.
Browse files Browse the repository at this point in the history
Fixes #149.
  • Loading branch information
h33p committed May 27, 2023
1 parent dc2b06c commit 491e5b5
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 36 deletions.
27 changes: 23 additions & 4 deletions src/html/mod.rs
Expand Up @@ -5,7 +5,7 @@ use crate::estimate::Estimate;
use crate::format;
use crate::fs;
use crate::measurement::ValueFormatter;
use crate::plot::{PlotContext, PlotData, Plotter};
use crate::plot::{LinePlotConfig, PlotContext, PlotData, Plotter};
use crate::SavedSample;
use criterion_plot::Size;
use serde::Serialize;
Expand Down Expand Up @@ -84,6 +84,7 @@ struct SummaryContext {

violin_plot: Option<String>,
line_chart: Option<String>,
line_throughput_chart: Option<String>,

benchmarks: Vec<IndividualBenchmark>,
}
Expand Down Expand Up @@ -759,15 +760,32 @@ impl Html {

let value_types: Vec<_> = data.iter().map(|&&(id, _)| id.value_type()).collect();
let mut line_path = None;
let mut line_throughput_path = None;

if value_types.iter().all(|x| x == &value_types[0]) {
if let Some(value_type) = value_types[0] {
let values: Vec<_> = data.iter().map(|&&(id, _)| id.as_number()).collect();
if values.iter().any(|x| x != &values[0]) {
self.plotter
.borrow_mut()
.line_comparison(plot_ctx, formatter, data, value_type);
self.plotter.borrow_mut().line_comparison(
LinePlotConfig::time(),
plot_ctx,
formatter,
data,
value_type,
);
line_path = Some(plot_ctx.line_comparison_path());

// value_types being all equal implies throughput types being all equal
if data[0].0.throughput.is_some() {
self.plotter.borrow_mut().line_comparison(
LinePlotConfig::throughput(),
plot_ctx,
formatter,
data,
value_type,
);
line_throughput_path = Some(plot_ctx.line_throughput_comparison_path());
}
}
}
}
Expand All @@ -788,6 +806,7 @@ impl Html {

violin_plot: Some(plot_ctx.violin_path().to_string_lossy().into_owned()),
line_chart: line_path.map(|p| p.to_string_lossy().into_owned()),
line_throughput_chart: line_throughput_path.map(|p| p.to_string_lossy().into_owned()),

benchmarks,
};
Expand Down
7 changes: 6 additions & 1 deletion src/html/summary_report.html.tt
Expand Up @@ -66,6 +66,11 @@
<img src="lines.svg" alt="Line Chart" />
<p>This chart shows the mean measured time for each function as the input (or the size of the input) increases.</p>
{{- endif }}
{{- if line_throughput_chart }}
<h3>Throughput Chart</h3>
<img src="lines_throughput.svg" alt="Line Chart" />
<p>This chart shows the mean measured throughput for each function as the input (or the size of the input) increases.</p>
{{- endif }}
{{- for bench in benchmarks }}
<section class="plots">
<a href="{bench.path}/report/index.html">
Expand Down Expand Up @@ -106,4 +111,4 @@
</div>
</body>

</html>
</html>
2 changes: 1 addition & 1 deletion src/lib.rs
Expand Up @@ -1168,7 +1168,7 @@ https://bheisler.github.io/criterion.rs/book/faq.html
/// // Now we can perform benchmarks with this group
/// group.bench_function("Bench 1", |b| b.iter(|| 1 ));
/// group.bench_function("Bench 2", |b| b.iter(|| 2 ));
///
///
/// group.finish();
/// }
/// criterion_group!(benches, bench_simple);
Expand Down
6 changes: 4 additions & 2 deletions src/plot/gnuplot_backend/mod.rs
Expand Up @@ -22,7 +22,7 @@ use crate::measurement::ValueFormatter;
use crate::report::{BenchmarkId, ValueType};
use crate::stats::bivariate::Data;

use super::{PlotContext, PlotData, Plotter};
use super::{LinePlotConfig, PlotContext, PlotData, Plotter};
use crate::format;

fn gnuplot_escape(string: &str) -> String {
Expand Down Expand Up @@ -201,13 +201,15 @@ impl Plotter for Gnuplot {

fn line_comparison(
&mut self,
line_config: LinePlotConfig,
ctx: PlotContext<'_>,
formatter: &dyn ValueFormatter,
all_curves: &[&(&BenchmarkId, Vec<f64>)],
value_type: ValueType,
) {
let path = ctx.line_comparison_path();
let path = (line_config.path)(&ctx);
self.process_list.push(line_comparison(
line_config,
formatter,
ctx.id.as_title(),
all_curves,
Expand Down
29 changes: 18 additions & 11 deletions src/plot/gnuplot_backend/summary.rs
Expand Up @@ -2,6 +2,7 @@ use super::{debug_script, gnuplot_escape};
use super::{DARK_BLUE, DEFAULT_FONT, KDE_POINTS, LINEWIDTH, POINT_SIZE, SIZE};
use crate::kde;
use crate::measurement::ValueFormatter;
use crate::plot::LinePlotConfig;
use crate::report::{BenchmarkId, ValueType};
use crate::stats::univariate::Sample;
use crate::AxisScale;
Expand Down Expand Up @@ -33,7 +34,8 @@ impl AxisScale {
}

#[cfg_attr(feature = "cargo-clippy", allow(clippy::explicit_counter_loop))]
pub fn line_comparison(
pub(crate) fn line_comparison(
line_cfg: LinePlotConfig,
formatter: &dyn ValueFormatter,
title: &str,
all_curves: &[&(&BenchmarkId, Vec<f64>)],
Expand Down Expand Up @@ -65,18 +67,22 @@ pub fn line_comparison(

let mut i = 0;

let max = all_curves
let (max_id, max) = all_curves
.iter()
.map(|&(_, data)| Sample::new(data).mean())
.fold(::std::f64::NAN, f64::max);
.map(|&(id, data)| (*id, Sample::new(data).mean()))
.fold(None, |prev: Option<(&BenchmarkId, f64)>, next| match prev {
Some(prev) if prev.1 >= next.1 => Some(prev),
_ => Some(next),
})
.unwrap();

let mut dummy = [1.0];
let unit = formatter.scale_values(max, &mut dummy);
let mut max_formatted = [max];
let unit = (line_cfg.scale)(formatter, max_id, max, max_id, &mut max_formatted);

f.configure(Axis::LeftY, |a| {
a.configure(Grid::Major, |g| g.show())
.configure(Grid::Minor, |g| g.hide())
.set(Label(format!("Average time ({})", unit)))
.set(Label(format!("Average {} ({})", line_cfg.label, unit)))
.set(axis_scale.to_gnuplot())
});

Expand All @@ -89,14 +95,15 @@ pub fn line_comparison(
// Unwrap is fine here because it will only fail if the assumptions above are not true
// ie. programmer error.
let x = id.as_number().unwrap();
let y = Sample::new(sample).mean();
let mut y = [Sample::new(sample).mean()];

(line_cfg.scale)(formatter, max_id, max, id, &mut y);

(x, y)
(x, y[0])
})
.collect();
tuples.sort_by(|&(ax, _), &(bx, _)| (ax.partial_cmp(&bx).unwrap_or(Ordering::Less)));
let (xs, mut ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();
formatter.scale_values(max, &mut ys);
let (xs, ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();

let function_name = key.as_ref().map(|string| gnuplot_escape(string));

Expand Down
57 changes: 57 additions & 0 deletions src/plot/mod.rs
Expand Up @@ -9,6 +9,7 @@ pub(crate) use plotters_backend::PlottersBackend;
use crate::estimate::Statistic;
use crate::measurement::ValueFormatter;
use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext, ValueType};
use crate::Throughput;
use std::path::PathBuf;

const REPORT_STATS: [Statistic; 7] = [
Expand Down Expand Up @@ -50,6 +51,14 @@ impl<'a> PlotContext<'a> {
path
}

pub fn line_throughput_comparison_path(&self) -> PathBuf {
let mut path = self.context.output_directory.clone();
path.push(self.id.as_directory_name());
path.push("report");
path.push("lines_throughput.svg");
path
}

pub fn violin_path(&self) -> PathBuf {
let mut path = self.context.output_directory.clone();
path.push(self.id.as_directory_name());
Expand All @@ -73,6 +82,53 @@ impl<'a> PlotData<'a> {
}
}

#[derive(Clone, Copy)]
pub(crate) struct LinePlotConfig {
label: &'static str,
scale: fn(&dyn ValueFormatter, &BenchmarkId, f64, &BenchmarkId, &mut [f64]) -> &'static str,
path: fn(&PlotContext<'_>) -> PathBuf,
}

impl LinePlotConfig {
pub fn time() -> Self {
Self {
label: "time",
scale: |formatter, _, max, _, vals| formatter.scale_values(max, vals),
path: |ctx| ctx.line_comparison_path(),
}
}

pub fn throughput() -> Self {
Self {
label: "throughput",
scale: |formatter, max_id, max, id, vals| {
// Scale values to be in line with max_id throughput
let from = id
.throughput
.as_ref()
.expect("Throughput chart expects throughput to be defined");
let to = max_id.throughput.as_ref().unwrap();

let (from_bytes, to_bytes) = match (from, to) {
(Throughput::Bytes(from), Throughput::Bytes(to)) => (from, to),
(Throughput::BytesDecimal(from), Throughput::BytesDecimal(to)) => (from, to),
(Throughput::Elements(from), Throughput::Elements(to)) => (from, to),
_ => unreachable!("throughput types expected to be equal"),
};

let mul = *to_bytes as f64 / *from_bytes as f64;

for val in vals.iter_mut() {
*val *= mul;
}

formatter.scale_throughputs(max, to, vals)
},
path: |ctx| ctx.line_throughput_comparison_path(),
}
}
}

pub(crate) trait Plotter {
fn pdf(&mut self, ctx: PlotContext<'_>, data: PlotData<'_>);

Expand All @@ -86,6 +142,7 @@ pub(crate) trait Plotter {

fn line_comparison(
&mut self,
line_config: LinePlotConfig,
ctx: PlotContext<'_>,
formatter: &dyn ValueFormatter,
all_curves: &[&(&BenchmarkId, Vec<f64>)],
Expand Down
6 changes: 4 additions & 2 deletions src/plot/plotters_backend/mod.rs
@@ -1,4 +1,4 @@
use super::{PlotContext, PlotData, Plotter};
use super::{LinePlotConfig, PlotContext, PlotData, Plotter};
use crate::measurement::ValueFormatter;
use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ValueType};
use plotters::data::float::pretty_print_float;
Expand Down Expand Up @@ -184,13 +184,15 @@ impl Plotter for PlottersBackend {

fn line_comparison(
&mut self,
line_config: LinePlotConfig,
ctx: PlotContext<'_>,
formatter: &dyn ValueFormatter,
all_curves: &[&(&BenchmarkId, Vec<f64>)],
value_type: ValueType,
) {
let path = ctx.line_comparison_path();
let path = (line_config.path)(&ctx);
summary::line_comparison(
line_config,
formatter,
ctx.id.as_title(),
all_curves,
Expand Down
45 changes: 30 additions & 15 deletions src/plot/plotters_backend/summary.rs
Expand Up @@ -20,15 +20,16 @@ static COMPARISON_COLORS: [RGBColor; NUM_COLORS] = [
RGBColor(0, 255, 127),
];

pub fn line_comparison(
pub(crate) fn line_comparison(
line_cfg: LinePlotConfig,
formatter: &dyn ValueFormatter,
title: &str,
all_curves: &[&(&BenchmarkId, Vec<f64>)],
path: &Path,
value_type: ValueType,
axis_scale: AxisScale,
) {
let (unit, series_data) = line_comparison_series_data(formatter, all_curves);
let (unit, series_data) = line_comparison_series_data(line_cfg, formatter, all_curves);

let x_range =
plotters::data::fitting_range(series_data.iter().flat_map(|(_, xs, _)| xs.iter()));
Expand All @@ -40,10 +41,17 @@ pub fn line_comparison(
.unwrap();

match axis_scale {
AxisScale::Linear => {
draw_line_comarision_figure(root_area, unit, x_range, y_range, value_type, series_data)
}
AxisScale::Linear => draw_line_comarision_figure(
line_cfg,
root_area,
unit,
x_range,
y_range,
value_type,
series_data,
),
AxisScale::Logarithmic => draw_line_comarision_figure(
line_cfg,
root_area,
unit,
x_range.log_scale(),
Expand All @@ -55,6 +63,7 @@ pub fn line_comparison(
}

fn draw_line_comarision_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord<Value = f64>>(
line_cfg: LinePlotConfig,
root_area: DrawingArea<SVGBackend, Shift>,
y_unit: &str,
x_range: XR,
Expand Down Expand Up @@ -82,7 +91,7 @@ fn draw_line_comarision_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord
.configure_mesh()
.disable_mesh()
.x_desc(format!("Input{}", input_suffix))
.y_desc(format!("Average time ({})", y_unit))
.y_desc(format!("Average {} ({})", line_cfg.label, y_unit))
.draw()
.unwrap();

Expand Down Expand Up @@ -115,16 +124,21 @@ fn draw_line_comarision_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord

#[allow(clippy::type_complexity)]
fn line_comparison_series_data<'a>(
line_cfg: LinePlotConfig,
formatter: &dyn ValueFormatter,
all_curves: &[&(&'a BenchmarkId, Vec<f64>)],
) -> (&'static str, Vec<(Option<&'a String>, Vec<f64>, Vec<f64>)>) {
let max = all_curves
let (max_id, max) = all_curves
.iter()
.map(|&(_, data)| Sample::new(data).mean())
.fold(::std::f64::NAN, f64::max);
.map(|&(id, data)| (*id, Sample::new(data).mean()))
.fold(None, |prev: Option<(&BenchmarkId, f64)>, next| match prev {
Some(prev) if prev.1 >= next.1 => Some(prev),
_ => Some(next),
})
.unwrap();

let mut dummy = [1.0];
let unit = formatter.scale_values(max, &mut dummy);
let mut max_formatted = [max];
let unit = (line_cfg.scale)(formatter, max_id, max, max_id, &mut max_formatted);

let mut series_data = vec![];

Expand All @@ -137,15 +151,16 @@ fn line_comparison_series_data<'a>(
// Unwrap is fine here because it will only fail if the assumptions above are not true
// ie. programmer error.
let x = id.as_number().unwrap();
let y = Sample::new(sample).mean();
let mut y = [Sample::new(sample).mean()];

(line_cfg.scale)(formatter, max_id, max, id, &mut y);

(x, y)
(x, y[0])
})
.collect();
tuples.sort_by(|&(ax, _), &(bx, _)| (ax.partial_cmp(&bx).unwrap_or(Ordering::Less)));
let function_name = key.as_ref();
let (xs, mut ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();
formatter.scale_values(max, &mut ys);
let (xs, ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();
series_data.push((function_name, xs, ys));
}
(unit, series_data)
Expand Down

0 comments on commit 491e5b5

Please sign in to comment.