,
}
@@ -801,6 +802,39 @@ impl Html {
}
}
+ let throughput_types: Vec<_> = data
+ .iter()
+ .map(|&&(ref id, _)| id.throughput.as_ref())
+ .collect();
+ let mut line_tput_path = None;
+
+ if value_types.iter().all(|x| x == &value_types[0])
+ && throughput_types[0].is_some()
+ && throughput_types.iter().all(|x| x == &throughput_types[0])
+ {
+ if let Some(value_type) = value_types[0] {
+ let values: Vec<_> = data.iter().map(|&&(ref id, _)| id.as_number()).collect();
+ if values.iter().any(|x| x != &values[0]) {
+ let path = format!(
+ "{}/{}/report/lines_tput.svg",
+ report_context.output_directory,
+ id.as_directory_name()
+ );
+
+ gnuplots.push(plot::line_comparison_throughput(
+ formatter,
+ id.as_title(),
+ data,
+ &path,
+ value_type,
+ report_context.plot_config.summary_scale,
+ ));
+
+ line_tput_path = Some(path);
+ }
+ }
+ }
+
let path_prefix = if full_summary { "../.." } else { "../../.." };
let benchmarks = data
.iter()
@@ -815,6 +849,7 @@ impl Html {
violin_plot: Some(violin_path),
line_chart: line_path,
+ line_chart_tput: line_tput_path,
benchmarks,
};
diff --git a/src/html/summary_report.html.tt b/src/html/summary_report.html.tt
index a839a4420..b875e14fb 100644
--- a/src/html/summary_report.html.tt
+++ b/src/html/summary_report.html.tt
@@ -66,6 +66,11 @@
This chart shows the mean measured time for each function as the input (or the size of the input) increases.
{{- endif }}
+ {{- if line_chart_tput }}
+ Throughput Chart
+
+ This chart shows the mean measured throughput for each function as the input (or the size of the input) increases.
+ {{- endif }}
{{- for bench in benchmarks }}
diff --git a/src/plot/summary.rs b/src/plot/summary.rs
index 9f085fd1e..9bdad437b 100644
--- a/src/plot/summary.rs
+++ b/src/plot/summary.rs
@@ -121,6 +121,106 @@ pub fn line_comparison(
f.set(Output(path)).draw().unwrap()
}
+#[cfg_attr(feature = "cargo-clippy", allow(clippy::explicit_counter_loop))]
+pub fn line_comparison_throughput(
+ formatter: &dyn ValueFormatter,
+ title: &str,
+ all_curves: &[&(&BenchmarkId, Vec)],
+ path: &str,
+ value_type: ValueType,
+ axis_scale: AxisScale,
+) -> Child {
+ let path = PathBuf::from(path);
+ let mut f = Figure::new();
+
+ let input_suffix = match value_type {
+ ValueType::Bytes => " Size (Bytes)",
+ ValueType::Elements => " Size (Elements)",
+ ValueType::Value => "",
+ };
+
+ f.set(Font(DEFAULT_FONT))
+ .set(SIZE)
+ .configure(Key, |k| {
+ k.set(Justification::Left)
+ .set(Order::SampleText)
+ .set(Position::Outside(Vertical::Top, Horizontal::Right))
+ })
+ .set(Title(format!("{}: Comparison", escape_underscores(title))))
+ .configure(Axis::BottomX, |a| {
+ a.set(Label(format!("Input{}", input_suffix)))
+ .set(axis_scale.to_gnuplot())
+ });
+
+ let mut i = 0;
+
+ let max = all_curves
+ .iter()
+ .map(|&&(_, ref data)| Sample::new(data).mean())
+ .fold(::std::f64::NAN, f64::max);
+
+ let mut dummy = [1.0];
+ assert!(
+ all_curves[0].0.throughput.is_some(),
+ "1st BenchmarkID has throughput."
+ );
+ let unit = formatter.scale_throughputs(
+ max,
+ all_curves[0].0.throughput.as_ref().unwrap(),
+ &mut dummy,
+ );
+
+ f.configure(Axis::LeftY, |a| {
+ a.configure(Grid::Major, |g| g.show())
+ .configure(Grid::Minor, |g| g.hide())
+ .set(Label(format!("Average throughput ({})", unit)))
+ .set(axis_scale.to_gnuplot())
+ });
+
+ // This assumes the curves are sorted. It also assumes that the benchmark IDs all have numeric
+ // values or throughputs and that value is sensible (ie. not a mix of bytes and elements
+ // or whatnot)
+ for (key, group) in &all_curves.iter().group_by(|&&&(ref id, _)| &id.function_id) {
+ let mut tuples: Vec<_> = group
+ .map(|&&(ref id, ref sample)| {
+ // Unwrap is fine here because it will only fail if the assumptions above are not true
+ // ie. programmer error.
+ assert!(id.as_number().is_some(), "All BenchmarkIDs have as_number.");
+ assert!(id.throughput.is_some(), "All BenchmarkIDs have throughput.");
+
+ let x = id.as_number().unwrap();
+ let mut y = [Sample::new(sample).mean()];
+ formatter.scale_throughputs(max, id.throughput.as_ref().unwrap(), &mut y);
+ (x, y[0])
+ })
+ .collect();
+
+ tuples.sort_by(|&(ax, _), &(bx, _)| (ax.partial_cmp(&bx).unwrap_or(Ordering::Less)));
+ let (xs, ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();
+
+ let function_name = key.as_ref().map(|string| escape_underscores(string));
+
+ f.plot(Lines { x: &xs, y: &ys }, |c| {
+ if let Some(name) = function_name {
+ c.set(Label(name));
+ }
+ c.set(LINEWIDTH)
+ .set(LineType::Solid)
+ .set(COMPARISON_COLORS[i % NUM_COLORS])
+ })
+ .plot(Points { x: &xs, y: &ys }, |p| {
+ p.set(PointType::FilledCircle)
+ .set(POINT_SIZE)
+ .set(COMPARISON_COLORS[i % NUM_COLORS])
+ });
+
+ i += 1;
+ }
+
+ debug_script(&path, &f);
+ f.set(Output(path)).draw().unwrap()
+}
+
pub fn violin(
formatter: &dyn ValueFormatter,
title: &str,