1use super::*;
2use crate::kde;
3use crate::measurement::ValueFormatter;
4use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext};
5use std::process::Child;
6
7pub(crate) fn pdf(
8 id: &BenchmarkId,
9 context: &ReportContext,
10 formatter: &dyn ValueFormatter,
11 measurements: &MeasurementData<'_>,
12 size: Option<Size>,
13) -> Child {
14 let avg_times = &measurements.avg_times;
15 let typical = avg_times.max();
16 let mut scaled_avg_times: Vec<f64> = (avg_times as &Sample<f64>).iter().cloned().collect();
17 let unit = formatter.scale_values(typical, &mut scaled_avg_times);
18 let scaled_avg_times = Sample::new(&scaled_avg_times);
19
20 let mean = scaled_avg_times.mean();
21
22 let iter_counts = measurements.iter_counts();
23 let &max_iters = iter_counts
24 .iter()
25 .max_by_key(|&&iters| iters as u64)
26 .unwrap();
27 let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
28 let y_scale = 10f64.powi(-exponent);
29
30 let y_label = if exponent == 0 {
31 "Iterations".to_owned()
32 } else {
33 format!("Iterations (x 10^{})", exponent)
34 };
35
36 let (xs, ys) = kde::sweep(scaled_avg_times, KDE_POINTS, None);
37 let (lost, lomt, himt, hist) = avg_times.fences();
38 let mut fences = [lost, lomt, himt, hist];
39 let _ = formatter.scale_values(typical, &mut fences);
40 let [lost, lomt, himt, hist] = fences;
41
42 let vertical = &[0., max_iters];
43 let zeros = iter::repeat(0);
44
45 let mut figure = Figure::new();
46 figure
47 .set(Font(DEFAULT_FONT))
48 .set(size.unwrap_or(SIZE))
49 .configure(Axis::BottomX, |a| {
50 let xs_ = Sample::new(&xs);
51 a.set(Label(format!("Average time ({})", unit)))
52 .set(Range::Limits(xs_.min(), xs_.max()))
53 })
54 .configure(Axis::LeftY, |a| {
55 a.set(Label(y_label))
56 .set(Range::Limits(0., max_iters * y_scale))
57 .set(ScaleFactor(y_scale))
58 })
59 .configure(Axis::RightY, |a| a.set(Label("Density (a.u.)")))
60 .configure(Key, |k| {
61 k.set(Justification::Left)
62 .set(Order::SampleText)
63 .set(Position::Outside(Vertical::Top, Horizontal::Right))
64 })
65 .plot(
66 FilledCurve {
67 x: &*xs,
68 y1: &*ys,
69 y2: zeros,
70 },
71 |c| {
72 c.set(Axes::BottomXRightY)
73 .set(DARK_BLUE)
74 .set(Label("PDF"))
75 .set(Opacity(0.25))
76 },
77 )
78 .plot(
79 Lines {
80 x: &[mean, mean],
81 y: vertical,
82 },
83 |c| {
84 c.set(DARK_BLUE)
85 .set(LINEWIDTH)
86 .set(LineType::Dash)
87 .set(Label("Mean"))
88 },
89 )
90 .plot(
91 Points {
92 x: avg_times
93 .iter()
94 .zip(scaled_avg_times.iter())
95 .filter_map(
96 |((_, label), t)| {
97 if label.is_outlier() {
98 None
99 } else {
100 Some(t)
101 }
102 },
103 ),
104 y: avg_times
105 .iter()
106 .zip(iter_counts.iter())
107 .filter_map(
108 |((_, label), i)| {
109 if label.is_outlier() {
110 None
111 } else {
112 Some(i)
113 }
114 },
115 ),
116 },
117 |c| {
118 c.set(DARK_BLUE)
119 .set(Label("\"Clean\" sample"))
120 .set(PointType::FilledCircle)
121 .set(POINT_SIZE)
122 },
123 )
124 .plot(
125 Points {
126 x: avg_times
127 .iter()
128 .zip(scaled_avg_times.iter())
129 .filter_map(
130 |((_, label), t)| {
131 if label.is_mild() {
132 Some(t)
133 } else {
134 None
135 }
136 },
137 ),
138 y: avg_times
139 .iter()
140 .zip(iter_counts.iter())
141 .filter_map(
142 |((_, label), i)| {
143 if label.is_mild() {
144 Some(i)
145 } else {
146 None
147 }
148 },
149 ),
150 },
151 |c| {
152 c.set(DARK_ORANGE)
153 .set(Label("Mild outliers"))
154 .set(POINT_SIZE)
155 .set(PointType::FilledCircle)
156 },
157 )
158 .plot(
159 Points {
160 x: avg_times
161 .iter()
162 .zip(scaled_avg_times.iter())
163 .filter_map(
164 |((_, label), t)| {
165 if label.is_severe() {
166 Some(t)
167 } else {
168 None
169 }
170 },
171 ),
172 y: avg_times
173 .iter()
174 .zip(iter_counts.iter())
175 .filter_map(
176 |((_, label), i)| {
177 if label.is_severe() {
178 Some(i)
179 } else {
180 None
181 }
182 },
183 ),
184 },
185 |c| {
186 c.set(DARK_RED)
187 .set(Label("Severe outliers"))
188 .set(POINT_SIZE)
189 .set(PointType::FilledCircle)
190 },
191 )
192 .plot(
193 Lines {
194 x: &[lomt, lomt],
195 y: vertical,
196 },
197 |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash),
198 )
199 .plot(
200 Lines {
201 x: &[himt, himt],
202 y: vertical,
203 },
204 |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash),
205 )
206 .plot(
207 Lines {
208 x: &[lost, lost],
209 y: vertical,
210 },
211 |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash),
212 )
213 .plot(
214 Lines {
215 x: &[hist, hist],
216 y: vertical,
217 },
218 |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash),
219 );
220 figure.set(Title(gnuplot_escape(id.as_title())));
221
222 let path = context.report_path(id, "pdf.svg");
223 debug_script(&path, &figure);
224 figure.set(Output(path)).draw().unwrap()
225}
226
227pub(crate) fn pdf_small(
228 id: &BenchmarkId,
229 context: &ReportContext,
230 formatter: &dyn ValueFormatter,
231 measurements: &MeasurementData<'_>,
232 size: Option<Size>,
233) -> Child {
234 let avg_times = &*measurements.avg_times;
235 let typical = avg_times.max();
236 let mut scaled_avg_times: Vec<f64> = (avg_times as &Sample<f64>).iter().cloned().collect();
237 let unit = formatter.scale_values(typical, &mut scaled_avg_times);
238 let scaled_avg_times = Sample::new(&scaled_avg_times);
239 let mean = scaled_avg_times.mean();
240
241 let (xs, ys, mean_y) = kde::sweep_and_estimate(scaled_avg_times, KDE_POINTS, None, mean);
242 let xs_ = Sample::new(&xs);
243 let ys_ = Sample::new(&ys);
244
245 let y_limit = ys_.max() * 1.1;
246 let zeros = iter::repeat(0);
247
248 let mut figure = Figure::new();
249 figure
250 .set(Font(DEFAULT_FONT))
251 .set(size.unwrap_or(SIZE))
252 .configure(Axis::BottomX, |a| {
253 a.set(Label(format!("Average time ({})", unit)))
254 .set(Range::Limits(xs_.min(), xs_.max()))
255 })
256 .configure(Axis::LeftY, |a| {
257 a.set(Label("Density (a.u.)"))
258 .set(Range::Limits(0., y_limit))
259 })
260 .configure(Axis::RightY, |a| a.hide())
261 .configure(Key, |k| k.hide())
262 .plot(
263 FilledCurve {
264 x: &*xs,
265 y1: &*ys,
266 y2: zeros,
267 },
268 |c| {
269 c.set(Axes::BottomXRightY)
270 .set(DARK_BLUE)
271 .set(Label("PDF"))
272 .set(Opacity(0.25))
273 },
274 )
275 .plot(
276 Lines {
277 x: &[mean, mean],
278 y: &[0., mean_y],
279 },
280 |c| c.set(DARK_BLUE).set(LINEWIDTH).set(Label("Mean")),
281 );
282
283 let path = context.report_path(id, "pdf_small.svg");
284 debug_script(&path, &figure);
285 figure.set(Output(path)).draw().unwrap()
286}
287
288fn pdf_comparison_figure(
289 formatter: &dyn ValueFormatter,
290 measurements: &MeasurementData<'_>,
291 comparison: &ComparisonData,
292 size: Option<Size>,
293) -> Figure {
294 let base_avg_times = Sample::new(&comparison.base_avg_times);
295 let typical = base_avg_times.max().max(measurements.avg_times.max());
296 let mut scaled_base_avg_times: Vec<f64> = comparison.base_avg_times.clone();
297 let unit = formatter.scale_values(typical, &mut scaled_base_avg_times);
298 let scaled_base_avg_times = Sample::new(&scaled_base_avg_times);
299
300 let mut scaled_new_avg_times: Vec<f64> = (&measurements.avg_times as &Sample<f64>)
301 .iter()
302 .cloned()
303 .collect();
304 let _ = formatter.scale_values(typical, &mut scaled_new_avg_times);
305 let scaled_new_avg_times = Sample::new(&scaled_new_avg_times);
306
307 let base_mean = scaled_base_avg_times.mean();
308 let new_mean = scaled_new_avg_times.mean();
309
310 let (base_xs, base_ys, base_y_mean) =
311 kde::sweep_and_estimate(scaled_base_avg_times, KDE_POINTS, None, base_mean);
312 let (xs, ys, y_mean) =
313 kde::sweep_and_estimate(scaled_new_avg_times, KDE_POINTS, None, new_mean);
314
315 let zeros = iter::repeat(0);
316
317 let mut figure = Figure::new();
318 figure
319 .set(Font(DEFAULT_FONT))
320 .set(size.unwrap_or(SIZE))
321 .configure(Axis::BottomX, |a| {
322 a.set(Label(format!("Average time ({})", unit)))
323 })
324 .configure(Axis::LeftY, |a| a.set(Label("Density (a.u.)")))
325 .configure(Axis::RightY, |a| a.hide())
326 .configure(Key, |k| {
327 k.set(Justification::Left)
328 .set(Order::SampleText)
329 .set(Position::Outside(Vertical::Top, Horizontal::Right))
330 })
331 .plot(
332 FilledCurve {
333 x: &*base_xs,
334 y1: &*base_ys,
335 y2: zeros.clone(),
336 },
337 |c| c.set(DARK_RED).set(Label("Base PDF")).set(Opacity(0.5)),
338 )
339 .plot(
340 Lines {
341 x: &[base_mean, base_mean],
342 y: &[0., base_y_mean],
343 },
344 |c| c.set(DARK_RED).set(Label("Base Mean")).set(LINEWIDTH),
345 )
346 .plot(
347 FilledCurve {
348 x: &*xs,
349 y1: &*ys,
350 y2: zeros,
351 },
352 |c| c.set(DARK_BLUE).set(Label("New PDF")).set(Opacity(0.5)),
353 )
354 .plot(
355 Lines {
356 x: &[new_mean, new_mean],
357 y: &[0., y_mean],
358 },
359 |c| c.set(DARK_BLUE).set(Label("New Mean")).set(LINEWIDTH),
360 );
361 figure
362}
363
364pub(crate) fn pdf_comparison(
365 id: &BenchmarkId,
366 context: &ReportContext,
367 formatter: &dyn ValueFormatter,
368 measurements: &MeasurementData<'_>,
369 comparison: &ComparisonData,
370 size: Option<Size>,
371) -> Child {
372 let mut figure = pdf_comparison_figure(formatter, measurements, comparison, size);
373 figure.set(Title(gnuplot_escape(id.as_title())));
374 let path = context.report_path(id, "both/pdf.svg");
375 debug_script(&path, &figure);
376 figure.set(Output(path)).draw().unwrap()
377}
378
379pub(crate) fn pdf_comparison_small(
380 id: &BenchmarkId,
381 context: &ReportContext,
382 formatter: &dyn ValueFormatter,
383 measurements: &MeasurementData<'_>,
384 comparison: &ComparisonData,
385 size: Option<Size>,
386) -> Child {
387 let mut figure = pdf_comparison_figure(formatter, measurements, comparison, size);
388 figure.configure(Key, |k| k.hide());
389 let path = context.report_path(id, "relative_pdf_small.svg");
390 debug_script(&path, &figure);
391 figure.set(Output(path)).draw().unwrap()
392}
393