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