Skip to main content

criterion_perf_events/
lib.rs

1//! `Perf` measures the selected perf events using the perf interface of the Linux kernel.
2//!
3//! # Example
4//!
5//! ```rust
6//! extern crate criterion_perf_events;
7//! extern crate perfcnt;
8//!
9//! # fn fibonacci_slow(_: usize) {}
10//! # fn fibonacci_fast(_: usize) {}
11//!
12//! use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
13//! use criterion_perf_events::Perf;
14//! use perfcnt::linux::HardwareEventType as Hardware;
15//! use perfcnt::linux::PerfCounterBuilderLinux as Builder;
16//!
17//! fn bench(c: &mut Criterion<Perf>) {
18//!     let mut group = c.benchmark_group("fibonacci");
19//!
20//!     for i in 0..20 {
21//!         group.bench_function(BenchmarkId::new("slow", i), |b| b.iter(|| fibonacci_slow(i)));
22//!         group.bench_function(BenchmarkId::new("fast", i), |b| b.iter(|| fibonacci_fast(i)));
23//!     }
24//!
25//!     group.finish()
26//! }
27//!
28//! criterion_group!(
29//!     name = my_bench;
30//!     config = Criterion::default().with_measurement(Perf::new(Builder::from_hardware_event(Hardware::CacheMisses)));
31//!     targets = bench
32//! );
33//! criterion_main!(my_bench);
34//! ```
35
36extern crate perfcnt;
37
38use criterion::{
39    measurement::{Measurement, ValueFormatter},
40    Throughput,
41};
42use std::cell::RefCell;
43
44use perfcnt::linux::PerfCounter;
45use perfcnt::linux::PerfCounterBuilderLinux;
46use perfcnt::AbstractPerfCounter;
47
48/// `perf` implements `criterion::measurement::Measurement` so it can be used in criterion to measure perf events.
49/// Create a struct via `Perf::new()`.
50pub struct Perf {
51    counter: RefCell<PerfCounter>,
52}
53
54impl Perf {
55    /// Creates a new criterion measurement plugin that measures perf events.
56    ///
57    /// # Argument
58    ///
59    /// * `builder` - A PerfCounterBuilderLinux from the crate perfcnt that is configured for the selected counter.
60    ///
61    /// # Remarks
62    ///
63    /// Should only fail if you select a counter that is not available on your system or you do not have the necessarry access rights.
64    pub fn new(mut builder: PerfCounterBuilderLinux) -> Perf {
65        Perf {
66            counter: RefCell::new(
67                builder
68                    .for_pid(std::process::id() as i32)
69                    .disable()
70                    .finish()
71                    .expect("Could not create counter"),
72            ),
73        }
74    }
75}
76
77impl Measurement for Perf {
78    type Intermediate = u64;
79    type Value = u64;
80
81    fn start(&self) -> Self::Intermediate {
82        self.counter
83            .borrow()
84            .start()
85            .expect("Could not read perf counter");
86        0
87    }
88
89    fn end(&self, _i: Self::Intermediate) -> Self::Value {
90        self.counter
91            .borrow()
92            .stop()
93            .expect("Could not stop perf counter");
94        let ret = self
95            .counter
96            .borrow_mut()
97            .read()
98            .expect("Could not read perf counter");
99        self.counter
100            .borrow_mut()
101            .reset()
102            .expect("Could not reset perf counter");
103        ret
104    }
105
106    fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value {
107        v1 + v2
108    }
109
110    fn zero(&self) -> Self::Value {
111        0
112    }
113
114    fn to_f64(&self, value: &Self::Value) -> f64 {
115        *value as f64
116    }
117
118    fn formatter(&self) -> &dyn ValueFormatter {
119        &PerfFormatter
120    }
121}
122
123struct PerfFormatter;
124
125impl ValueFormatter for PerfFormatter {
126    fn format_value(&self, value: f64) -> String {
127        format!("{value:.4} cycles")
128    }
129
130    fn format_throughput(&self, throughput: &Throughput, value: f64) -> String {
131        match throughput {
132            Throughput::Bytes(bytes) => format!("{:.4} events/byte", value / *bytes as f64),
133            Throughput::BytesDecimal(bytes) => {
134                let event_per_byte = value / *bytes as f64;
135
136                let (denominator, unit) = if *bytes < 1000 {
137                    (1.0, "events/byte")
138                } else if *bytes < 1000 * 1000 {
139                    (1000.0, "events/kilobyte")
140                } else if *bytes < 1000 * 1000 * 1000 {
141                    (1000.0 * 1000.0, "events/megabyte")
142                } else {
143                    (1000.0 * 1000.0 * 1000.0, "events/gigabyte")
144                };
145
146                format!("{:.4} {}", event_per_byte / denominator, unit)
147            }
148            Throughput::Elements(bytes) => format!("{:.4} events/element", value / *bytes as f64),
149        }
150    }
151
152    fn scale_values(&self, _typical_value: f64, _values: &mut [f64]) -> &'static str {
153        "events"
154    }
155
156    fn scale_throughputs(
157        &self,
158        _typical_value: f64,
159        throughput: &Throughput,
160        values: &mut [f64],
161    ) -> &'static str {
162        match throughput {
163            Throughput::Bytes(bytes) => {
164                for val in values {
165                    *val /= *bytes as f64;
166                }
167                "events/byte"
168            }
169            Throughput::BytesDecimal(bytes) => {
170                let bytes_per_second = *bytes;
171                let (denominator, unit) = if bytes_per_second < 1000 {
172                    (1.0, "events/byte")
173                } else if bytes_per_second < 1000 * 1000 {
174                    (1000.0, "events/kilobyte")
175                } else if bytes_per_second < 1000 * 1000 * 1000 {
176                    (1000.0 * 1000.0, "events/megabyte")
177                } else {
178                    (1000.0 * 1000.0 * 1000.0, "events/gigabyte")
179                };
180
181                for val in values {
182                    *val /= *bytes as f64;
183                    *val /= denominator;
184                }
185
186                unit
187            }
188            Throughput::Elements(bytes) => {
189                for val in values {
190                    *val /= *bytes as f64;
191                }
192                "events/element"
193            }
194        }
195    }
196
197    fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str {
198        "events"
199    }
200}