1 //! This module defines a set of traits that can be used to plug different measurements (eg.
2 //! Unix's Processor Time, CPU or GPU performance counters, etc.) into Criterion.rs. It also
3 //! includes the [WallTime](struct.WallTime.html) struct which defines the default wall-clock time
4 //! measurement.
5 
6 use crate::format::short;
7 use crate::DurationExt;
8 use crate::Throughput;
9 use std::time::{Duration, Instant};
10 
11 /// Trait providing functions to format measured values to string so that they can be displayed on
12 /// the command line or in the reports. The functions of this trait take measured values in f64
13 /// form; implementors can assume that the values are of the same scale as those produced by the
14 /// associated [MeasuredValue](trait.MeasuredValue.html) (eg. if your measurement produces values in
15 /// nanoseconds, the values passed to the formatter will be in nanoseconds).
16 ///
17 /// Implementors are encouraged to format the values in a way that is intuitive for humans and
18 /// uses the SI prefix system. For example, the format used by [WallTime](struct.WallTime.html)
19 /// can display the value in units ranging from picoseconds to seconds depending on the magnitude
20 /// of the elapsed time in nanoseconds.
21 pub trait ValueFormatter {
22     /// Format the value (with appropriate unit) and return it as a string.
format_value(&self, value: f64) -> String23     fn format_value(&self, value: f64) -> String {
24         let mut values = [value];
25         let unit = self.scale_values(value, &mut values);
26         format!("{:>6} {}", short(values[0]), unit)
27     }
28 
29     /// Format the value as a throughput measurement. The value represents the measurement value;
30     /// the implementor will have to calculate bytes per second, iterations per cycle, etc.
format_throughput(&self, throughput: &Throughput, value: f64) -> String31     fn format_throughput(&self, throughput: &Throughput, value: f64) -> String {
32         let mut values = [value];
33         let unit = self.scale_throughputs(value, throughput, &mut values);
34         format!("{:>6} {}", short(values[0]), unit)
35     }
36 
37     /// Scale the given values to some appropriate unit and return the unit string.
38     ///
39     /// The given typical value should be used to choose the unit. This function may be called
40     /// multiple times with different datasets; the typical value will remain the same to ensure
41     /// that the units remain consistent within a graph. The typical value will not be NaN.
42     /// Values will not contain NaN as input, and the transformed values must not contain NaN.
scale_values(&self, typical_value: f64, values: &mut [f64]) -> &'static str43     fn scale_values(&self, typical_value: f64, values: &mut [f64]) -> &'static str;
44 
45     /// Convert the given measured values into throughput numbers based on the given throughput
46     /// value, scale them to some appropriate unit, and return the unit string.
47     ///
48     /// The given typical value should be used to choose the unit. This function may be called
49     /// multiple times with different datasets; the typical value will remain the same to ensure
50     /// that the units remain consistent within a graph. The typical value will not be NaN.
51     /// Values will not contain NaN as input, and the transformed values must not contain NaN.
scale_throughputs( &self, typical_value: f64, throughput: &Throughput, values: &mut [f64], ) -> &'static str52     fn scale_throughputs(
53         &self,
54         typical_value: f64,
55         throughput: &Throughput,
56         values: &mut [f64],
57     ) -> &'static str;
58 
59     /// Scale the values and return a unit string designed for machines.
60     ///
61     /// For example, this is used for the CSV file output. Implementations should modify the given
62     /// values slice to apply the desired scaling (if any) and return a string representing the unit
63     /// the modified values are in.
scale_for_machines(&self, values: &mut [f64]) -> &'static str64     fn scale_for_machines(&self, values: &mut [f64]) -> &'static str;
65 }
66 
67 /// Trait for all types which define something Criterion.rs can measure. The only measurement
68 /// currently provided is [WallTime](struct.WallTime.html), but third party crates or benchmarks
69 /// may define more.
70 ///
71 /// This trait defines two core methods, `start` and `end`. `start` is called at the beginning of
72 /// a measurement to produce some intermediate value (for example, the wall-clock time at the start
73 /// of that set of iterations) and `end` is called at the end of the measurement with the value
74 /// returned by `start`.
75 ///
76 pub trait Measurement {
77     /// This type represents an intermediate value for the measurements. It will be produced by the
78     /// start function and passed to the end function. An example might be the wall-clock time as
79     /// of the `start` call.
80     type Intermediate;
81 
82     /// This type is the measured value. An example might be the elapsed wall-clock time between the
83     /// `start` and `end` calls.
84     type Value;
85 
86     /// Criterion.rs will call this before iterating the benchmark.
start(&self) -> Self::Intermediate87     fn start(&self) -> Self::Intermediate;
88 
89     /// Criterion.rs will call this after iterating the benchmark to get the measured value.
end(&self, i: Self::Intermediate) -> Self::Value90     fn end(&self, i: Self::Intermediate) -> Self::Value;
91 
92     /// Combine two values. Criterion.rs sometimes needs to perform measurements in multiple batches
93     /// of iterations, so the value from one batch must be added to the sum of the previous batches.
add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value94     fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value;
95 
96     /// Return a "zero" value for the Value type which can be added to another value.
zero(&self) -> Self::Value97     fn zero(&self) -> Self::Value;
98 
99     /// Converts the measured value to f64 so that it can be used in statistical analysis.
to_f64(&self, value: &Self::Value) -> f64100     fn to_f64(&self, value: &Self::Value) -> f64;
101 
102     /// Return a trait-object reference to the value formatter for this measurement.
formatter(&self) -> &dyn ValueFormatter103     fn formatter(&self) -> &dyn ValueFormatter;
104 }
105 
106 pub(crate) struct DurationFormatter;
107 impl DurationFormatter {
bytes_per_second(&self, bytes: f64, typical: f64, values: &mut [f64]) -> &'static str108     fn bytes_per_second(&self, bytes: f64, typical: f64, values: &mut [f64]) -> &'static str {
109         let bytes_per_second = bytes * (1e9 / typical);
110         let (denominator, unit) = if bytes_per_second < 1024.0 {
111             (1.0, "  B/s")
112         } else if bytes_per_second < 1024.0 * 1024.0 {
113             (1024.0, "KiB/s")
114         } else if bytes_per_second < 1024.0 * 1024.0 * 1024.0 {
115             (1024.0 * 1024.0, "MiB/s")
116         } else {
117             (1024.0 * 1024.0 * 1024.0, "GiB/s")
118         };
119 
120         for val in values {
121             let bytes_per_second = bytes * (1e9 / *val);
122             *val = bytes_per_second / denominator;
123         }
124 
125         unit
126     }
127 
elements_per_second(&self, elems: f64, typical: f64, values: &mut [f64]) -> &'static str128     fn elements_per_second(&self, elems: f64, typical: f64, values: &mut [f64]) -> &'static str {
129         let elems_per_second = elems * (1e9 / typical);
130         let (denominator, unit) = if elems_per_second < 1000.0 {
131             (1.0, " elem/s")
132         } else if elems_per_second < 1000.0 * 1000.0 {
133             (1000.0, "Kelem/s")
134         } else if elems_per_second < 1000.0 * 1000.0 * 1000.0 {
135             (1000.0 * 1000.0, "Melem/s")
136         } else {
137             (1000.0 * 1000.0 * 1000.0, "Gelem/s")
138         };
139 
140         for val in values {
141             let elems_per_second = elems * (1e9 / *val);
142             *val = elems_per_second / denominator;
143         }
144 
145         unit
146     }
147 }
148 impl ValueFormatter for DurationFormatter {
scale_throughputs( &self, typical: f64, throughput: &Throughput, values: &mut [f64], ) -> &'static str149     fn scale_throughputs(
150         &self,
151         typical: f64,
152         throughput: &Throughput,
153         values: &mut [f64],
154     ) -> &'static str {
155         match *throughput {
156             Throughput::Bytes(bytes) => self.bytes_per_second(bytes as f64, typical, values),
157             Throughput::Elements(elems) => self.elements_per_second(elems as f64, typical, values),
158         }
159     }
160 
scale_values(&self, ns: f64, values: &mut [f64]) -> &'static str161     fn scale_values(&self, ns: f64, values: &mut [f64]) -> &'static str {
162         let (factor, unit) = if ns < 10f64.powi(0) {
163             (10f64.powi(3), "ps")
164         } else if ns < 10f64.powi(3) {
165             (10f64.powi(0), "ns")
166         } else if ns < 10f64.powi(6) {
167             (10f64.powi(-3), "us")
168         } else if ns < 10f64.powi(9) {
169             (10f64.powi(-6), "ms")
170         } else {
171             (10f64.powi(-9), "s")
172         };
173 
174         for val in values {
175             *val *= factor;
176         }
177 
178         unit
179     }
180 
scale_for_machines(&self, _values: &mut [f64]) -> &'static str181     fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str {
182         // no scaling is needed
183         "ns"
184     }
185 }
186 
187 /// `WallTime` is the default measurement in Criterion.rs. It measures the elapsed time from the
188 /// beginning of a series of iterations to the end.
189 pub struct WallTime;
190 impl Measurement for WallTime {
191     type Intermediate = Instant;
192     type Value = Duration;
193 
start(&self) -> Self::Intermediate194     fn start(&self) -> Self::Intermediate {
195         Instant::now()
196     }
end(&self, i: Self::Intermediate) -> Self::Value197     fn end(&self, i: Self::Intermediate) -> Self::Value {
198         i.elapsed()
199     }
add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value200     fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value {
201         *v1 + *v2
202     }
zero(&self) -> Self::Value203     fn zero(&self) -> Self::Value {
204         Duration::from_secs(0)
205     }
to_f64(&self, val: &Self::Value) -> f64206     fn to_f64(&self, val: &Self::Value) -> f64 {
207         val.to_nanos() as f64
208     }
formatter(&self) -> &dyn ValueFormatter209     fn formatter(&self) -> &dyn ValueFormatter {
210         &DurationFormatter
211     }
212 }
213