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
gz committed Aug 26, 2019
1 parent 5c3a418 commit e9bd64c
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/html/mod.rs
Expand Up @@ -92,6 +92,7 @@ struct SummaryContext {

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

benchmarks: Vec<IndividualBenchmark>,
}
Expand Down Expand Up @@ -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()
Expand All @@ -815,6 +849,7 @@ impl Html {

violin_plot: Some(violin_path),
line_chart: line_path,
line_chart_tput: line_tput_path,

benchmarks,
};
Expand Down
5 changes: 5 additions & 0 deletions 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_chart_tput }}
<h3>Throughput Chart</h3>
<img src="lines_tput.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
100 changes: 100 additions & 0 deletions src/plot/summary.rs
Expand Up @@ -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<f64>)],
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,
Expand Down

0 comments on commit e9bd64c

Please sign in to comment.