1 | use super::*; |
2 | use crate::kde; |
3 | use crate::measurement::ValueFormatter; |
4 | use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext}; |
5 | use std::process::Child; |
6 | |
7 | pub(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 | |
227 | pub(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 | |
288 | fn 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 | |
364 | pub(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 | |
379 | pub(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 | |