diff options
| author | Dennis Kobert <dennis@kobert.dev> | 2025-03-08 21:06:20 +0100 |
|---|---|---|
| committer | Dennis Kobert <dennis@kobert.dev> | 2025-03-08 21:07:27 +0100 |
| commit | b4eae244826a0fb8907ef50c8ef9a38da40b719d (patch) | |
| tree | b57014575b3dfc38c5f25115357253d037b3189c | |
| parent | 06dd6966ad27f0c52c56092dd2f45a06541e5c6c (diff) | |
Vendor perf event
| -rw-r--r-- | perf-event/.cargo-checksum.json | 1 | ||||
| -rw-r--r-- | perf-event/Cargo.lock | 26 | ||||
| -rw-r--r-- | perf-event/Cargo.toml | 41 | ||||
| -rw-r--r-- | perf-event/README.md | 50 | ||||
| -rwxr-xr-x | perf-event/coverage.sh | 17 | ||||
| -rw-r--r-- | perf-event/examples/big-group.rs | 95 | ||||
| -rw-r--r-- | perf-event/examples/group.rs | 58 | ||||
| -rw-r--r-- | perf-event/examples/insns-for-pid.rs | 27 | ||||
| -rw-r--r-- | perf-event/examples/println-cpi.rs | 30 | ||||
| -rw-r--r-- | perf-event/examples/println.rs | 15 | ||||
| -rw-r--r-- | perf-event/src/events.rs | 320 | ||||
| -rw-r--r-- | perf-event/src/hooks.rs | 272 | ||||
| -rw-r--r-- | perf-event/src/lib.rs | 1078 |
13 files changed, 2030 insertions, 0 deletions
diff --git a/perf-event/.cargo-checksum.json b/perf-event/.cargo-checksum.json new file mode 100644 index 0000000..5f428cf --- /dev/null +++ b/perf-event/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"e6f2a4c1610000e632fdc57a37c3f62b7415122af2c59f9cdf92c072e5f19744","Cargo.toml":"be3be360d6679285e16c47a0b356232859586f951812e093ecb6d6c3bd78a347","README.md":"38725818104bc29f1da50456c60482ffb9437fa7909425a1cfa546cbe455d489","coverage.sh":"499e0ba26d5b1402d2de552afde9c93a010055dbc534c21ed4a1bd19e4829f52","examples/big-group.rs":"0b6298724710fc0b8af26ab477303aecdd93d2d3941408b42d2641bfc4cb880f","examples/group.rs":"a78d9628e99a069b96e94b05d10be243eb297645960bf8a8aab307ca91eac92b","examples/insns-for-pid.rs":"a44090feca2459bc23e6e0511e5cecfce33aa3de44a4393b4d18505a2b7784f0","examples/println-cpi.rs":"5da5bf3b8c2ce698ea8c0f7bfbc3d1ff73162ad276ff5ea40ff4823338a2e14e","examples/println.rs":"98503038ba2bd155a76a440593ce082e5a176cdb479f865c843f4b960e4c815a","src/events.rs":"38426362ba6c74266429d2807c9b17849e6938d6ad33b1b9868cfa7a270246b6","src/hooks.rs":"48bbd22d42347f57482399ed1d42519b3e412b653fd48125927afb79bd458710","src/lib.rs":"3845d00546c53be0a9ca1bd0961772acf8ffc2c62e118e5f224a947c7db682c1"},"package":"b4d6393d9238342159080d79b78cb59c67399a8e7ecfa5d410bd614169e4e823"}
\ No newline at end of file diff --git a/perf-event/Cargo.lock b/perf-event/Cargo.lock new file mode 100644 index 0000000..144d5e3 --- /dev/null +++ b/perf-event/Cargo.lock @@ -0,0 +1,26 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "perf-event" +version = "0.4.8" +dependencies = [ + "libc", + "perf-event-open-sys", +] + +[[package]] +name = "perf-event-open-sys" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c44fb1c7651a45a3652c4afc6e754e40b3d6e6556f1487e2b230bfc4f33c2a8" +dependencies = [ + "libc", +] diff --git a/perf-event/Cargo.toml b/perf-event/Cargo.toml new file mode 100644 index 0000000..8228f47 --- /dev/null +++ b/perf-event/Cargo.toml @@ -0,0 +1,41 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "perf-event" +version = "0.4.8" +authors = ["Jim Blandy <jimb@red-bean.com>"] +description = "A Rust interface to Linux performance monitoring" +documentation = "https://docs.rs/perf-event/" +readme = "README.md" +keywords = [ + "linux", + "perf", +] +categories = [ + "development-tools::profiling", + "hardware-support", + "os::linux-apis", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/jimblandy/perf-event.git" +resolver = "2" + +[dependencies.libc] +version = "0.2" + +[dependencies.perf-event-open-sys] +version = "4.0" + +[features] +default = ["hooks"] +hooks = [] diff --git a/perf-event/README.md b/perf-event/README.md new file mode 100644 index 0000000..b6754cd --- /dev/null +++ b/perf-event/README.md @@ -0,0 +1,50 @@ +## perf-event: a Rust interface to Linux performance monitoring + +This uses the Linux [`perf_event_open`][man] API to access performance monitoring +hardware and software. Use `Builder` to create a perf event counter, then use +`enable` and `disable` to start and stop counting. Call `read` to get your +count. + +For example, this counts the number of cycles used by the call to `println!`. +Try adjusting the length of the vector to see the cycle count change. + + use perf_event::Builder; + + fn main() -> std::io::Result<()> { + let mut counter = Builder::new().build()?; + + let vec = (0..=51).collect::<Vec<_>>(); + + counter.enable()?; + println!("{:?}", vec); + counter.disable()?; + + println!("{} instructions retired", counter.read()?); + + Ok(()) + } + +Since we don't specify what sort of event we want to count, `Builder` defaults +to `PERF_COUNT_HW_INSTRUCTIONS` events, whose documentation says: + +> Retired instructions. Be careful, these can be affected by various issues, +> most notably hardware interrupt counts. + +The `examples` directory includes programs that count other sorts of events. + +[man]: http://man7.org/linux/man-pages/man2/perf_event_open.2.html + +## See also + +The [`perfcnt`] crate provides more extensive coverage of the Linux +`perf_event_open` API than this crate. + +Markus Stange's [`linux-perf-event-reader`][lper] supports events. +This crate only handles counters for now. + +The [`not-perf`] project is a rewrite of `perf` in Rust, and has a +bunch of code for dealing with the Linux perf API. + +[`perfcnt`]: https://crates.io/crates/perfcnt +[lper]: https://crates.io/crates/linux-perf-event-reader +[`not-perf`]: https://github.com/koute/not-perf diff --git a/perf-event/coverage.sh b/perf-event/coverage.sh new file mode 100755 index 0000000..de8d660 --- /dev/null +++ b/perf-event/coverage.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +cargo +nightly clean +export RUSTFLAGS="\ + -Zprofile \ + -Ccodegen-units=1 \ + -Copt-level=0 \ + -Clink-dead-code \ + -Coverflow-checks=off \ +" +export CARGO_INCREMENTAL=0 + +cargo +nightly test + +grcov ./target/debug/ -s . -t html --llvm --branch --ignore-not-existing -o ./target/debug/coverage/ + +xdg-open target/debug/coverage/index.html diff --git a/perf-event/examples/big-group.rs b/perf-event/examples/big-group.rs new file mode 100644 index 0000000..71551b7 --- /dev/null +++ b/perf-event/examples/big-group.rs @@ -0,0 +1,95 @@ +use perf_event::events::{Cache, CacheOp, CacheResult, Hardware, WhichCache}; +use perf_event::{Builder, Group}; + +fn main() -> std::io::Result<()> { + const ACCESS: Cache = Cache { + which: WhichCache::L1D, + operation: CacheOp::READ, + result: CacheResult::ACCESS, + }; + const MISS: Cache = Cache { + result: CacheResult::MISS, + ..ACCESS + }; + + let mut group = Group::new()?; + let access_counter = Builder::new().group(&mut group).kind(ACCESS).build()?; + let miss_counter = Builder::new().group(&mut group).kind(MISS).build()?; + let branches = Builder::new() + .group(&mut group) + .kind(Hardware::BRANCH_INSTRUCTIONS) + .build()?; + let missed_branches = Builder::new() + .group(&mut group) + .kind(Hardware::BRANCH_MISSES) + .build()?; + let insns = Builder::new() + .group(&mut group) + .kind(Hardware::INSTRUCTIONS) + .build()?; + let cycles = Builder::new() + .group(&mut group) + .kind(Hardware::CPU_CYCLES) + .build()?; + + // Note that if you add more counters than you actually have hardware for, + // the kernel will time-slice them, which means you may get no coverage for + // short measurements. See the documentation. + // + // On my machine, this program won't collect any data unless I disable the + // NMI watchdog, as described in the documentation for `Group`. My machine + // has four counters, and this program tries to use all of them, but the NMI + // watchdog uses one up. + + let mut vec = (0..=100000).collect::<Vec<_>>(); + + group.enable()?; + vec.sort(); + println!("{:?}", &vec[0..10]); + group.disable()?; + + let counts = group.read()?; + + println!( + "enabled for {}ns, actually running for {}ns", + counts.time_enabled(), + counts.time_running() + ); + + if counts.time_running() == 0 { + println!("Group was never running; no results available."); + return Ok(()); + } + + if counts.time_running() < counts.time_enabled() { + println!("Counts cover only a portion of the execution."); + } + + println!( + "L1D cache misses/references: {} / {} ({:.0}%)", + counts[&miss_counter], + counts[&access_counter], + (counts[&miss_counter] as f64 / counts[&access_counter] as f64) * 100.0 + ); + + println!( + "branch prediction misses/total: {} / {} ({:.0}%)", + counts[&missed_branches], + counts[&branches], + (counts[&missed_branches] as f64 / counts[&branches] as f64) * 100.0 + ); + + println!( + "{} instructions, {} cycles ({:.2} cpi)", + counts[&insns], + counts[&cycles], + counts[&cycles] as f64 / counts[&insns] as f64 + ); + + // You can iterate over a `Counts` value: + for (id, value) in &counts { + println!("Counter id {} has value {}", id, value); + } + + Ok(()) +} diff --git a/perf-event/examples/group.rs b/perf-event/examples/group.rs new file mode 100644 index 0000000..1aa2629 --- /dev/null +++ b/perf-event/examples/group.rs @@ -0,0 +1,58 @@ +use perf_event::events::{Cache, CacheOp, CacheResult, Hardware, WhichCache}; +use perf_event::{Builder, Group}; + +fn main() -> std::io::Result<()> { + const ACCESS: Cache = Cache { + which: WhichCache::L1D, + operation: CacheOp::READ, + result: CacheResult::ACCESS, + }; + const MISS: Cache = Cache { + result: CacheResult::MISS, + ..ACCESS + }; + + let mut group = Group::new()?; + let access_counter = Builder::new().group(&mut group).kind(ACCESS).build()?; + let miss_counter = Builder::new().group(&mut group).kind(MISS).build()?; + let branches = Builder::new() + .group(&mut group) + .kind(Hardware::BRANCH_INSTRUCTIONS) + .build()?; + let missed_branches = Builder::new() + .group(&mut group) + .kind(Hardware::BRANCH_MISSES) + .build()?; + + // Note that if you add more counters than you actually have hardware for, + // the kernel will time-slice them, which means you may get no coverage for + // short measurements. See the documentation. + + let vec = (0..=51).collect::<Vec<_>>(); + + group.enable()?; + println!("{:?}", vec); + group.disable()?; + + let counts = group.read()?; + println!( + "L1D cache misses/references: {} / {} ({:.0}%)", + counts[&miss_counter], + counts[&access_counter], + (counts[&miss_counter] as f64 / counts[&access_counter] as f64) * 100.0 + ); + + println!( + "branch prediction misses/total: {} / {} ({:.0}%)", + counts[&missed_branches], + counts[&branches], + (counts[&missed_branches] as f64 / counts[&branches] as f64) * 100.0 + ); + + // You can iterate over a `Counts` value: + for (id, value) in &counts { + println!("Counter id {} has value {}", id, value); + } + + Ok(()) +} diff --git a/perf-event/examples/insns-for-pid.rs b/perf-event/examples/insns-for-pid.rs new file mode 100644 index 0000000..3fc73ae --- /dev/null +++ b/perf-event/examples/insns-for-pid.rs @@ -0,0 +1,27 @@ +use libc::pid_t; +use perf_event::events::Hardware; +use perf_event::Builder; +use std::thread::sleep; +use std::time::Duration; + +fn main() -> std::io::Result<()> { + let pid: pid_t = std::env::args() + .nth(1) + .expect("Usage: insns-for-pid PID") + .parse() + .expect("Usage: insns-for-pid PID"); + + let mut insns = Builder::new() + .observe_pid(pid) + .kind(Hardware::BRANCH_INSTRUCTIONS) + .build()?; + + // Count instructions in PID for five seconds. + insns.enable()?; + sleep(Duration::from_secs(5)); + insns.disable()?; + + println!("instructions in last five seconds: {}", insns.read()?); + + Ok(()) +} diff --git a/perf-event/examples/println-cpi.rs b/perf-event/examples/println-cpi.rs new file mode 100644 index 0000000..6524c37 --- /dev/null +++ b/perf-event/examples/println-cpi.rs @@ -0,0 +1,30 @@ +fn main() -> std::io::Result<()> { + use perf_event::events::Hardware; + use perf_event::{Builder, Group}; + + let mut group = Group::new()?; + let cycles = Builder::new() + .group(&mut group) + .kind(Hardware::CPU_CYCLES) + .build()?; + let insns = Builder::new() + .group(&mut group) + .kind(Hardware::INSTRUCTIONS) + .build()?; + + let vec = (0..=51).collect::<Vec<_>>(); + + group.enable()?; + println!("{:?}", vec); + group.disable()?; + + let counts = group.read()?; + println!( + "cycles / instructions: {} / {} ({:.2} cpi)", + counts[&cycles], + counts[&insns], + (counts[&cycles] as f64 / counts[&insns] as f64) + ); + + Ok(()) +} diff --git a/perf-event/examples/println.rs b/perf-event/examples/println.rs new file mode 100644 index 0000000..8760e3d --- /dev/null +++ b/perf-event/examples/println.rs @@ -0,0 +1,15 @@ +use perf_event::Builder; + +fn main() -> std::io::Result<()> { + let mut counter = Builder::new().build()?; + + let vec = (0..=51).collect::<Vec<_>>(); + + counter.enable()?; + println!("{:?}", vec); + counter.disable()?; + + println!("{} instructions retired", counter.read()?); + + Ok(()) +} diff --git a/perf-event/src/events.rs b/perf-event/src/events.rs new file mode 100644 index 0000000..d79b749 --- /dev/null +++ b/perf-event/src/events.rs @@ -0,0 +1,320 @@ +//! Events we can monitor or count. +//! +//! There are three general categories of event: +//! +//! - [`Hardware`] events are counted by the processor itself. This +//! includes things like clock cycles, instructions retired, and cache and +//! branch prediction statistics. +//! +//! - [`Cache`] events, also counted by the processor, offer a more +//! detailed view of the processor's cache counters. You can +//! select which level of the cache hierarchy to observe, +//! discriminate between data and instruction caches, and so on. +//! +//! - [`Software`] events are counted by the kernel. This includes things +//! like context switches, page faults, and so on. +//! +//! The `Event` type is just an enum with a variant for each of the above types, +//! which all implement `Into<Event>`. +//! +//! Linux supports many more kinds of events than this module covers, including +//! events specific to particular make and model of processor, and events that +//! are dynamically registered by drivers and kernel modules. If something you +//! want is missing, think about the best API to expose it, and submit a pull +//! request! +//! +//! [`Hardware`]: enum.Hardware.html +//! [`Software`]: enum.Software.html +//! [`Cache`]: struct.Cache.html + +#![allow(non_camel_case_types)] +use perf_event_open_sys::bindings; + +/// Any sort of event. This is a sum of the [`Hardware`], +/// [`Software`], and [`Cache`] types, which all implement +/// `Into<Event>`. +/// +/// [`Hardware`]: enum.Hardware.html +/// [`Software`]: enum.Software.html +/// [`Cache`]: struct.Cache.html +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Event { + #[allow(missing_docs)] + Hardware(Hardware), + + #[allow(missing_docs)] + Software(Software), + + #[allow(missing_docs)] + Cache(Cache), +} + +impl Event { + pub(crate) fn r#type(&self) -> bindings::perf_type_id { + match self { + Event::Hardware(_) => bindings::PERF_TYPE_HARDWARE, + Event::Software(_) => bindings::PERF_TYPE_SOFTWARE, + Event::Cache(_) => bindings::PERF_TYPE_HW_CACHE, + } + } + + pub(crate) fn config(self) -> u64 { + match self { + Event::Hardware(hw) => hw as _, + Event::Software(sw) => sw as _, + Event::Cache(cache) => cache.as_config(), + } + } +} + +/// Hardware counters. +/// +/// These are counters implemented by the processor itself. Such counters vary +/// from one architecture to the next, and even different models within a +/// particular architecture will often change the way they expose this data. +/// This is a selection of portable names for values that can be obtained on a +/// wide variety of systems. +/// +/// Each variant of this enum corresponds to a particular `PERF_COUNT_HW_`... +/// value supported by the [`perf_event_open`][man] system call. +/// +/// [man]: http://man7.org/linux/man-pages/man2/perf_event_open.2.html +#[repr(u32)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Hardware { + /// Total cycles. Be wary of what happens during CPU frequency scaling. + CPU_CYCLES = bindings::PERF_COUNT_HW_CPU_CYCLES, + + /// Retired instructions. Be careful, these can be affected by various + /// issues, most notably hardware interrupt counts. + INSTRUCTIONS = bindings::PERF_COUNT_HW_INSTRUCTIONS, + + /// Cache accesses. Usually this indicates Last Level Cache accesses but + /// this may vary depending on your CPU. This may include prefetches and + /// coherency messages; again this depends on the design of your CPU. + CACHE_REFERENCES = bindings::PERF_COUNT_HW_CACHE_REFERENCES, + + /// Cache misses. Usually this indicates Last Level Cache misses; this is + /// intended to be used in conjunction with the + /// PERF_COUNT_HW_CACHE_REFERENCES event to calculate cache miss rates. + CACHE_MISSES = bindings::PERF_COUNT_HW_CACHE_MISSES, + + /// Retired branch instructions. Prior to Linux 2.6.35, this used the wrong + /// event on AMD processors. + BRANCH_INSTRUCTIONS = bindings::PERF_COUNT_HW_BRANCH_INSTRUCTIONS, + + /// Mispredicted branch instructions. + BRANCH_MISSES = bindings::PERF_COUNT_HW_BRANCH_MISSES, + + /// Bus cycles, which can be different from total cycles. + BUS_CYCLES = bindings::PERF_COUNT_HW_BUS_CYCLES, + + /// Stalled cycles during issue. (since Linux 3.0) + STALLED_CYCLES_FRONTEND = bindings::PERF_COUNT_HW_STALLED_CYCLES_FRONTEND, + + /// Stalled cycles during retirement. (since Linux 3.0) + STALLED_CYCLES_BACKEND = bindings::PERF_COUNT_HW_STALLED_CYCLES_BACKEND, + + /// Total cycles; not affected by CPU frequency scaling. (since Linux 3.3) + REF_CPU_CYCLES = bindings::PERF_COUNT_HW_REF_CPU_CYCLES, +} + +impl From<Hardware> for Event { + fn from(hw: Hardware) -> Event { + Event::Hardware(hw) + } +} + +/// Software counters, implemented by the kernel. +/// +/// Each variant of this enum corresponds to a particular `PERF_COUNT_SW_`... +/// value supported by the [`perf_event_open`][man] system call. +/// +/// [man]: http://man7.org/linux/man-pages/man2/perf_event_open.2.html +#[repr(u32)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Software { + /// This reports the CPU clock, a high-resolution per-CPU timer. + CPU_CLOCK = bindings::PERF_COUNT_SW_CPU_CLOCK, + + /// This reports a clock count specific to the task that is running. + TASK_CLOCK = bindings::PERF_COUNT_SW_TASK_CLOCK, + + /// This reports the number of page faults. + PAGE_FAULTS = bindings::PERF_COUNT_SW_PAGE_FAULTS, + + /// This counts context switches. Until Linux 2.6.34, these were all + /// reported as user-space events, after that they are reported as happening + /// in the kernel. + CONTEXT_SWITCHES = bindings::PERF_COUNT_SW_CONTEXT_SWITCHES, + + /// This reports the number of times the process has migrated to a new CPU. + CPU_MIGRATIONS = bindings::PERF_COUNT_SW_CPU_MIGRATIONS, + + /// This counts the number of minor page faults. These did not require disk + /// I/O to handle. + PAGE_FAULTS_MIN = bindings::PERF_COUNT_SW_PAGE_FAULTS_MIN, + + /// This counts the number of major page faults. These required disk I/O to + /// handle. + PAGE_FAULTS_MAJ = bindings::PERF_COUNT_SW_PAGE_FAULTS_MAJ, + + /// (since Linux 2.6.33) This counts the number of alignment faults. These + /// happen when unaligned memory accesses happen; the kernel can handle + /// these but it reduces performance. This happens only on some + /// architectures (never on x86). + ALIGNMENT_FAULTS = bindings::PERF_COUNT_SW_ALIGNMENT_FAULTS, + + /// (since Linux 2.6.33) This counts the number of emulation faults. The + /// kernel sometimes traps on unimplemented instructions and emulates them + /// for user space. This can negatively impact performance. + EMULATION_FAULTS = bindings::PERF_COUNT_SW_EMULATION_FAULTS, + + /// (since Linux 3.12) This is a placeholder event that counts nothing. + /// Informational sample record types such as mmap or comm must be + /// associated with an active event. This dummy event allows gathering such + /// records without requiring a counting event. + DUMMY = bindings::PERF_COUNT_SW_DUMMY, +} + +impl From<Software> for Event { + fn from(hw: Software) -> Event { + Event::Software(hw) + } +} + +/// A cache event. +/// +/// A cache event has three identifying characteristics: +/// +/// - which cache to observe ([`which`]) +/// +/// - what sort of request it's handling ([`operation`]) +/// +/// - whether we want to count all cache accesses, or just misses +/// ([`result`]). +/// +/// For example, to measure the L1 data cache's miss rate: +/// +/// # use perf_event::{Builder, Group}; +/// # use perf_event::events::{Cache, CacheOp, CacheResult, Hardware, WhichCache}; +/// # fn main() -> std::io::Result<()> { +/// // A `Cache` value representing L1 data cache read accesses. +/// const ACCESS: Cache = Cache { +/// which: WhichCache::L1D, +/// operation: CacheOp::READ, +/// result: CacheResult::ACCESS, +/// }; +/// +/// // A `Cache` value representing L1 data cache read misses. +/// const MISS: Cache = Cache { result: CacheResult::MISS, ..ACCESS }; +/// +/// // Construct a `Group` containing the two new counters, from which we +/// // can get counts over matching periods of time. +/// let mut group = Group::new()?; +/// let access_counter = Builder::new().group(&mut group).kind(ACCESS).build()?; +/// let miss_counter = Builder::new().group(&mut group).kind(MISS).build()?; +/// # Ok(()) } +/// +/// [`which`]: enum.WhichCache.html +/// [`operation`]: enum.CacheOp.html +/// [`result`]: enum.CacheResult.html +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Cache { + /// Which cache is being monitored? (data, instruction, ...) + pub which: WhichCache, + + /// What operation is being monitored? (read, write, etc.) + pub operation: CacheOp, + + /// All accesses, or just misses? + pub result: CacheResult, +} + +impl From<Cache> for Event { + fn from(hw: Cache) -> Event { + Event::Cache(hw) + } +} + +impl Cache { + fn as_config(&self) -> u64 { + self.which as u64 | ((self.operation as u64) << 8) | ((self.result as u64) << 16) + } +} + +/// A cache whose events we would like to count. +/// +/// This is used in the `Cache` type as part of the identification of a cache +/// event. Each variant here corresponds to a particular +/// `PERF_COUNT_HW_CACHE_...` constant supported by the [`perf_event_open`][man] +/// system call. +/// +/// [man]: http://man7.org/linux/man-pages/man2/perf_event_open.2.html +#[repr(u32)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum WhichCache { + /// for measuring Level 1 Data Cache + L1D = bindings::PERF_COUNT_HW_CACHE_L1D, + + /// for measuring Level 1 Instruction Cache + L1I = bindings::PERF_COUNT_HW_CACHE_L1I, + + /// for measuring Last-Level Cache + LL = bindings::PERF_COUNT_HW_CACHE_LL, + + /// for measuring the Data TLB + DTLB = bindings::PERF_COUNT_HW_CACHE_DTLB, + + /// for measuring the Instruction TLB + ITLB = bindings::PERF_COUNT_HW_CACHE_ITLB, + + /// for measuring the branch prediction unit + BPU = bindings::PERF_COUNT_HW_CACHE_BPU, + + /// (since Linux 3.1) for measuring local memory accesses + NODE = bindings::PERF_COUNT_HW_CACHE_NODE, +} + +/// What sort of cache operation we would like to observe. +/// +/// This is used in the `Cache` type as part of the identification of a cache +/// event. Each variant here corresponds to a particular +/// `PERF_COUNT_HW_CACHE_OP_...` constant supported by the +/// [`perf_event_open`][man] system call. +/// +/// [man]: http://man7.org/linux/man-pages/man2/perf_event_open.2.html +#[repr(u32)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum CacheOp { + /// Read accesses. + READ = bindings::PERF_COUNT_HW_CACHE_OP_READ, + + /// Write accesses. + WRITE = bindings::PERF_COUNT_HW_CACHE_OP_WRITE, + + /// Prefetch accesses. + PREFETCH = bindings::PERF_COUNT_HW_CACHE_OP_PREFETCH, +} + +#[repr(u32)] +/// What sort of cache result we're interested in observing. +/// +/// `ACCESS` counts the total number of operations performed on the cache, +/// whereas `MISS` counts only those requests that the cache could not satisfy. +/// Treating `MISS` as a fraction of `ACCESS` gives you the cache's miss rate. +/// +/// This is used used in the `Cache` type as part of the identification of a +/// cache event. Each variant here corresponds to a particular +/// `PERF_COUNT_HW_CACHE_RESULT_...` constant supported by the +/// [`perf_event_open`][man] system call. +/// +/// [man]: http://man7.org/linux/man-pages/man2/perf_event_open.2.html +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum CacheResult { + /// to measure accesses + ACCESS = bindings::PERF_COUNT_HW_CACHE_RESULT_ACCESS, + + /// to measure misses + MISS = bindings::PERF_COUNT_HW_CACHE_RESULT_MISS, +} diff --git a/perf-event/src/hooks.rs b/perf-event/src/hooks.rs new file mode 100644 index 0000000..d79331f --- /dev/null +++ b/perf-event/src/hooks.rs @@ -0,0 +1,272 @@ +//! Intercepting perf-event system calls, for testing and logging. +//! +//! Note: this module is only available when the `"hooks"` feature is enabled. +//! +//! Many performance counters' behavior is inherently +//! non-deterministic, making it difficult to write tests for code +//! that uses the `perf_event` crate. There may be no way to reliably +//! provoke the Linux kernel into exhibiting the behavior you want to +//! test against. Or you may want to test functionality like +//! whole-system profiling, which requires elevated privileges that +//! one would prefer to avoid granting to tests. +//! +//! This module lets you interpose your own implementation of all the +//! system calls and ioctls that `perf_event` uses, granting you +//! complete control over `perf_event`'s interactions with the outside +//! world. You can verify that the system calls receive the parameters +//! you expect, and provide whatever sorts of interesting responses +//! you need. +//! +//! There are three main pieces: +//! +//! - The [`Hooks`] trait has a method for every system call and ioctl +//! that the `perf_event` crate uses. +//! +//! - The [`set_thread_hooks`] function lets you provide a `Box<dyn Hooks>` +//! trait object whose methods the calling thread will use for all subsequent +//! `perf_event` operations. +//! +//! - The [`clear_thread_hooks`] function restores the thread's +//! original state, so that subsequent `perf_event` operations use +//! the real Linux system calls. +//! +//! This functionality is too low-level for direct use in tests, but +//! it does provide the means with which one can build more ergonomic +//! test harnesses. +//! +//! ## Stability +//! +//! Using `set_thread_hooks`, you can observe the exact sequence of +//! system operations that the `perf_event` crate performs to carry +//! out requests from the user. Even if the interface remains the +//! same, the implementation of those requests can change without +//! notice, possibly causing a [`Hooks`] implementation to see a +//! different set of calls. +//! +//! The `perf_event` crate will not treat such implementation changes +//! as breaking changes for semver purposes, despite the fact that +//! they may break code using this module's functionality. +use libc::pid_t; +use perf_event_open_sys as real; +use perf_event_open_sys::bindings; +use std::cell::RefCell; +use std::os::raw::{c_char, c_int, c_uint, c_ulong}; + +std::thread_local! { + static HOOKS: RefCell<Box<dyn Hooks + 'static>> = RefCell::new(Box::new(RealHooks)); +} + +/// Direct all perf-event system calls on this thread to `hooks`. +/// +/// All subsequent uses by this crate of the underlying system calls +/// and ioctls from the `perf_event_open_sys` crate are redirected to +/// `hooks`' implementations of the correspoding methods from the +/// [`Hooks`] trait. +/// +/// This affects only the calling thread. Any previously established +/// hooks on that thread are dropped. +/// +/// # Safety +/// +/// The specified `hooks` trait object intercepts calls provoked by +/// previously created [`Counter`] and [`Group`] objects, regardless +/// of which hooks were in effect when they were created. This could +/// make a hash of things. +/// +/// [`Counter`]: crate::Counter +/// [`Group`]: crate::Group +pub unsafe fn set_thread_hooks(hooks: Box<dyn Hooks + 'static>) { + HOOKS.with(|per_thread| { + *per_thread.borrow_mut() = hooks; + }) +} + +/// Direct all perf-event system calls on this thread to the real system calls. +/// +/// All subsequent uses by this crate of the underlying system calls +/// and ioctls from the `perf_event_open_sys` crate are directed to +/// the underlying Linux operations, without interference. +/// +/// This affects only the calling thread. Any previously established +/// hooks on that thread are dropped. +/// +/// # Safety +/// +/// The specified `hooks` trait object intercepts calls provoked by +/// previously created [`Counter`] and [`Group`] values, regardless of +/// which hooks were in effect when they were created. Letting values +/// created using hooked system calls suddenly see the real kernel +/// could make a hash of things. +/// +/// [`Counter`]: crate::Counter +/// [`Group`]: crate::Group +pub unsafe fn clear_thread_hooks() { + HOOKS.with(|per_thread| { + *per_thread.borrow_mut() = Box::new(RealHooks); + }) +} + +/// List of ioctls we need wrappers for. +/// +/// We use this macro to generate the [`Hooks`] trait's definition, +/// the [`RealHooks`] implementation, and the functions in the `sys` +/// module that are actually used by callers. +macro_rules! define_ioctls { + ( $expand:ident ) => { + $expand ! { ENABLE, perf_event_ioctls_ENABLE, c_uint } + $expand ! { DISABLE, perf_event_ioctls_DISABLE, c_uint } + $expand ! { REFRESH, perf_event_ioctls_REFRESH, c_int } + $expand ! { RESET, perf_event_ioctls_RESET, c_uint } + $expand ! { PERIOD, perf_event_ioctls_PERIOD, u64 } + $expand ! { SET_OUTPUT, perf_event_ioctls_SET_OUTPUT, c_int } + $expand ! { SET_FILTER, perf_event_ioctls_SET_FILTER, *mut c_char } + $expand ! { ID, perf_event_ioctls_ID, *mut u64 } + $expand ! { SET_BPF, perf_event_ioctls_SET_BPF, u32 } + $expand ! { PAUSE_OUTPUT, perf_event_ioctls_PAUSE_OUTPUT, u32 } + $expand ! { QUERY_BPF, perf_event_ioctls_QUERY_BPF, *mut bindings::perf_event_query_bpf } + $expand ! { MODIFY_ATTRIBUTES, perf_event_ioctls_MODIFY_ATTRIBUTES, *mut bindings::perf_event_attr } + } +} + +macro_rules! expand_trait_method { + ( $name:ident, $ioctl:ident, $arg_type:ty ) => { + /// Wrapper for perf_event ioctl + #[doc = stringify!($ioctl)] + /// . + #[allow(non_snake_case)] + unsafe fn $name(&mut self, _fd: c_int, _arg: $arg_type) -> c_int { + panic!( + "unimplemented `perf_event::hooks::Hooks` method: {}", + stringify!($name) + ); + } + }; +} + +/// A trait with a method for every system call and ioctl used by this crate. +/// +/// The methods of this trait correspond to the public functions of +/// the [`perf_event_open_sys`][peos] crate used to implement this +/// crate's functionality. For testing purposes, you can redirect this +/// crate to a value of your own design that implements this trait by +/// calling [`set_thread_hooks`]. +/// +/// Each method has a default definition that panics. This means that +/// you only need to provide definitions for the operations your tests +/// actually use; if they touch anything else, you'll get a failure. +/// +/// The [`RealHooks`] type implements this trait in terms of the real +/// Linux system calls and ioctls. +/// +/// [peos]: https://docs.rs/perf-event-open-sys/latest/perf_event_open_sys/ +#[allow(dead_code)] +pub trait Hooks { + /// See [`perf_event_open_sys::perf_event_open`][peo]. + /// + /// [peo]: https://docs.rs/perf-event-open-sys/latest/perf_event_open_sys/fn.perf_event_open.html + #[allow(clippy::missing_safety_doc)] + unsafe fn perf_event_open( + &mut self, + attrs: *mut bindings::perf_event_attr, + pid: pid_t, + cpu: c_int, + group_fd: c_int, + flags: c_ulong, + ) -> c_int; + define_ioctls!(expand_trait_method); +} + +macro_rules! expand_realhooks_impl { + ( $name:ident, $ioctl_:ident, $arg_type:ty ) => { + #[allow(clippy::missing_safety_doc)] + unsafe fn $name(&mut self, fd: c_int, arg: $arg_type) -> c_int { + real::ioctls::$name(fd, arg) + } + }; +} + +/// An implementation of the [`Hooks`] trait in terms of the real Linux system calls. +/// +/// This type implements each methods of the [`Hooks`] trait by +/// calling the underlying system call or ioctl. The following call +/// is equivalent to calling [`clear_thread_hooks`]: +/// +/// # use perf_event::hooks; +/// # use perf_event::hooks::*; +/// unsafe { +/// set_thread_hooks(Box::new(RealHooks)); +/// } +/// +/// If what you want is non-intercepted access to the underlying +/// system calls, it's probably better to just access the +/// [`perf_event_open_sys`][peos] crate directly, rather than using this type. +/// +/// [peos]: https://docs.rs/perf-event-open-sys/latest/perf_event_open_sys/ +pub struct RealHooks; +impl Hooks for RealHooks { + unsafe fn perf_event_open( + &mut self, + attrs: *mut bindings::perf_event_attr, + pid: pid_t, + cpu: c_int, + group_fd: c_int, + flags: c_ulong, + ) -> c_int { + real::perf_event_open(attrs, pid, cpu, group_fd, flags) + } + + define_ioctls!(expand_realhooks_impl); +} + +/// Wrapper around the `perf_event_open_sys` crate that supports +/// intercepting system calls and returning simulated results, for +/// testing. +pub mod sys { + use super::HOOKS; + use libc::pid_t; + use std::os::raw::{c_int, c_ulong}; + + pub use perf_event_open_sys::bindings; + + /// See [`perf_event_open_sys::perf_event_open`][peo]. + /// + /// [peo]: https://docs.rs/perf-event-open-sys/latest/perf_event_open_sys/fn.perf_event_open.html + #[allow(clippy::missing_safety_doc)] + pub unsafe fn perf_event_open( + attrs: *mut bindings::perf_event_attr, + pid: pid_t, + cpu: c_int, + group_fd: c_int, + flags: c_ulong, + ) -> c_int { + HOOKS.with(|hooks| { + hooks + .borrow_mut() + .perf_event_open(attrs, pid, cpu, group_fd, flags) + }) + } + + #[allow(dead_code, non_snake_case)] + /// See the [`perf_event_open_sys::ioctl` module][peosi]. + /// + /// [peosi]: https://docs.rs/perf-event-open-sys/latest/perf_event_open_sys/ioctls/index.html + pub mod ioctls { + use super::HOOKS; + use perf_event_open_sys::bindings; + use std::os::raw::{c_char, c_int, c_uint}; + + macro_rules! expand_hooked_ioctl { + ( $name:ident, $ioctl_:ident, $arg_type:ty ) => { + /// See the [`perf_event_open_sys::ioctl` module][peosi]. + /// + /// [peosi]: https://docs.rs/perf-event-open-sys/latest/perf_event_open_sys/ioctls/index.html + #[allow(clippy::missing_safety_doc)] + pub unsafe fn $name(fd: c_int, arg: $arg_type) -> c_int { + HOOKS.with(|hooks| hooks.borrow_mut().$name(fd, arg)) + } + }; + } + + define_ioctls!(expand_hooked_ioctl); + } +} diff --git a/perf-event/src/lib.rs b/perf-event/src/lib.rs new file mode 100644 index 0000000..03ef643 --- /dev/null +++ b/perf-event/src/lib.rs @@ -0,0 +1,1078 @@ +//! A performance monitoring API for Linux. +//! +//! This crate provides access to processor and kernel counters for things like +//! instruction completions, cache references and misses, branch predictions, +//! context switches, page faults, and so on. +//! +//! For example, to compare the number of clock cycles elapsed with the number +//! of instructions completed during one call to `println!`: +//! +//! use perf_event::{Builder, Group}; +//! use perf_event::events::Hardware; +//! +//! fn main() -> std::io::Result<()> { +//! // A `Group` lets us enable and disable several counters atomically. +//! let mut group = Group::new()?; +//! let cycles = Builder::new().group(&mut group).kind(Hardware::CPU_CYCLES).build()?; +//! let insns = Builder::new().group(&mut group).kind(Hardware::INSTRUCTIONS).build()?; +//! +//! let vec = (0..=51).collect::<Vec<_>>(); +//! +//! group.enable()?; +//! println!("{:?}", vec); +//! group.disable()?; +//! +//! let counts = group.read()?; +//! println!("cycles / instructions: {} / {} ({:.2} cpi)", +//! counts[&cycles], +//! counts[&insns], +//! (counts[&cycles] as f64 / counts[&insns] as f64)); +//! +//! Ok(()) +//! } +//! +//! This crate is built on top of the Linux [`perf_event_open`][man] system +//! call; that documentation has the authoritative explanations of exactly what +//! all the counters mean. +//! +//! There are two main types for measurement: +//! +//! - A [`Counter`] is an individual counter. Use [`Builder`] to +//! construct one. +//! +//! - A [`Group`] is a collection of counters that can be enabled and +//! disabled atomically, so that they cover exactly the same period of +//! execution, allowing meaningful comparisons of the individual values. +//! +//! If you're familiar with the kernel API already: +//! +//! - A `Builder` holds the arguments to a `perf_event_open` call: +//! a `struct perf_event_attr` and a few other fields. +//! +//! - `Counter` and `Group` objects are just event file descriptors, together +//! with their kernel id numbers, and some other details you need to +//! actually use them. They're different types because they yield different +//! types of results, and because you can't retrieve a `Group`'s counts +//! without knowing how many members it has. +//! +//! ### Call for PRs +//! +//! Linux's `perf_event_open` API can report all sorts of things this crate +//! doesn't yet understand: stack traces, logs of executable and shared library +//! activity, tracepoints, kprobes, uprobes, and so on. And beyond the counters +//! in the kernel header files, there are others that can only be found at +//! runtime by consulting `sysfs`, specific to particular processors and +//! devices. For example, modern Intel processors have counters that measure +//! power consumption in Joules. +//! +//! If you find yourself in need of something this crate doesn't support, please +//! consider submitting a pull request. +//! +//! [man]: http://man7.org/linux/man-pages/man2/perf_event_open.2.html + +#![deny(missing_docs)] + +use events::Event; +use libc::pid_t; +use perf_event_open_sys::bindings::perf_event_attr; +use std::fs::File; +use std::io::{self, Read}; +use std::os::raw::{c_int, c_uint, c_ulong}; +use std::os::unix::io::{AsRawFd, FromRawFd}; + +pub mod events; + +#[cfg(feature = "hooks")] +pub mod hooks; + +// When the `"hooks"` feature is not enabled, call directly into +// `perf-event-open-sys`. +#[cfg(not(feature = "hooks"))] +use perf_event_open_sys as sys; + +// When the `"hooks"` feature is enabled, `sys` functions allow for +// interposed functions that provide simulated results for testing. +#[cfg(feature = "hooks")] +use hooks::sys; + +/// A counter for one kind of kernel or hardware event. +/// +/// A `Counter` represents a single performance monitoring counter. You select +/// what sort of event you'd like to count when the `Counter` is created, then +/// you can enable and disable the counter, call its [`read`] method to +/// retrieve the current count, and reset it to zero. +/// +/// A `Counter`'s value is always a `u64`. +/// +/// For example, this counts the number of instructions retired (completed) +/// during a call to `println!`. +/// +/// use perf_event::Builder; +/// +/// fn main() -> std::io::Result<()> { +/// let mut counter = Builder::new().build()?; +/// +/// let vec = (0..=51).collect::<Vec<_>>(); +/// +/// counter.enable()?; +/// println!("{:?}", vec); +/// counter.disable()?; +/// +/// println!("{} instructions retired", counter.read()?); +/// +/// Ok(()) +/// } +/// +/// It is often useful to count several different quantities over the same +/// period of time. For example, if you want to measure the average number of +/// clock cycles used per instruction, you must count both clock cycles and +/// instructions retired, for the same range of execution. The [`Group`] type +/// lets you enable, disable, read, and reset any number of counters +/// simultaneously. +/// +/// When a counter is dropped, its kernel resources are freed along with it. +/// +/// Internally, a `Counter` is just a wrapper around an event file descriptor. +/// +/// [`read`]: Counter::read +pub struct Counter { + /// The file descriptor for this counter, returned by `perf_event_open`. + /// + /// When a `Counter` is dropped, this `File` is dropped, and the kernel + /// removes the counter from any group it belongs to. + file: File, + + /// The unique id assigned to this counter by the kernel. + id: u64, +} + +/// A builder for [`Counter`]s. +/// +/// There are dozens of parameters that influence a `Counter`'s behavior. +/// `Builder` lets you construct a `Counter` by specifying only those parameters +/// for which you don't want the default value. +/// +/// A freshly built `Counter` is disabled. To begin counting events, you must +/// call [`enable`] on the `Counter` or the `Group` to which it belongs. +/// +/// For example, if you want a `Counter` for instructions retired by the current +/// process, those are `Builder`'s defaults, so you need only write: +/// +/// # use perf_event::Builder; +/// # fn main() -> std::io::Result<()> { +/// let mut insns = Builder::new().build()?; +/// # Ok(()) } +/// +/// The [`kind`] method lets you specify what sort of event you want to +/// count. So if you'd rather count branch instructions: +/// +/// # use perf_event::Builder; +/// # use perf_event::events::Hardware; +/// # fn main() -> std::io::Result<()> { +/// let mut insns = Builder::new() +/// .kind(Hardware::BRANCH_INSTRUCTIONS) +/// .build()?; +/// # Ok(()) } +/// +/// The [`group`] method lets you gather individual counters into `Group` +/// that can be enabled or disabled atomically: +/// +/// # use perf_event::{Builder, Group}; +/// # use perf_event::events::Hardware; +/// # fn main() -> std::io::Result<()> { +/// let mut group = Group::new()?; +/// let cycles = Builder::new().group(&mut group).kind(Hardware::CPU_CYCLES).build()?; +/// let insns = Builder::new().group(&mut group).kind(Hardware::INSTRUCTIONS).build()?; +/// # Ok(()) } +/// +/// Other methods let you select: +/// +/// - specific processes or cgroups to observe +/// - specific CPU cores to observe +/// +/// `Builder` supports only a fraction of the many knobs and dials Linux offers, +/// but hopefully it will acquire methods to support more of them as time goes +/// on. +/// +/// Internally, a `Builder` is just a wrapper around the kernel's `struct +/// perf_event_attr` type. +/// +/// [`enable`]: Counter::enable +/// [`kind`]: Builder::kind +/// [`group`]: Builder::group +pub struct Builder<'a> { + attrs: perf_event_attr, + who: EventPid<'a>, + cpu: Option<usize>, + group: Option<&'a mut Group>, +} + +#[derive(Debug)] +enum EventPid<'a> { + /// Monitor the calling process. + ThisProcess, + + /// Monitor the given pid. + Other(pid_t), + + /// Monitor members of the given cgroup. + CGroup(&'a File), +} + +/// A group of counters that can be managed as a unit. +/// +/// A `Group` represents a group of [`Counter`]s that can be enabled, +/// disabled, reset, or read as a single atomic operation. This is necessary if +/// you want to compare counter values, produce ratios, and so on, since those +/// operations are only meaningful on counters that cover exactly the same +/// period of execution. +/// +/// A `Counter` is placed in a group when it is created, by calling the +/// `Builder`'s [`group`] method. A `Group`'s [`read`] method returns values +/// of all its member counters at once as a [`Counts`] value, which can be +/// indexed by `Counter` to retrieve a specific value. +/// +/// For example, the following program computes the average number of cycles +/// used per instruction retired for a call to `println!`: +/// +/// # fn main() -> std::io::Result<()> { +/// use perf_event::{Builder, Group}; +/// use perf_event::events::Hardware; +/// +/// let mut group = Group::new()?; +/// let cycles = Builder::new().group(&mut group).kind(Hardware::CPU_CYCLES).build()?; +/// let insns = Builder::new().group(&mut group).kind(Hardware::INSTRUCTIONS).build()?; +/// +/// let vec = (0..=51).collect::<Vec<_>>(); +/// +/// group.enable()?; +/// println!("{:?}", vec); +/// group.disable()?; +/// +/// let counts = group.read()?; +/// println!("cycles / instructions: {} / {} ({:.2} cpi)", +/// counts[&cycles], +/// counts[&insns], +/// (counts[&cycles] as f64 / counts[&insns] as f64)); +/// # Ok(()) } +/// +/// The lifetimes of `Counter`s and `Group`s are independent: placing a +/// `Counter` in a `Group` does not take ownership of the `Counter`, nor must +/// the `Counter`s in a group outlive the `Group`. If a `Counter` is dropped, it +/// is simply removed from its `Group`, and omitted from future results. If a +/// `Group` is dropped, its individual counters continue to count. +/// +/// Enabling or disabling a `Group` affects each `Counter` that belongs to it. +/// Subsequent reads from the `Counter` will not reflect activity while the +/// `Group` was disabled, unless the `Counter` is re-enabled individually. +/// +/// A `Group` and its members must all observe the same tasks and cpus; mixing +/// these makes building the `Counter` return an error. Unfortunately, there is +/// no way at present to specify a `Group`'s task and cpu, so you can only use +/// `Group` on the calling task. If this is a problem, please file an issue. +/// +/// Internally, a `Group` is just a wrapper around an event file descriptor. +/// +/// ## Limits on group size +/// +/// Hardware counters are implemented using special-purpose registers on the +/// processor, of which there are only a fixed number. (For example, an Intel +/// high-end laptop processor from 2015 has four such registers per virtual +/// processor.) Without using groups, if you request more hardware counters than +/// the processor can actually support, a complete count isn't possible, but the +/// kernel will rotate the processor's real registers amongst the measurements +/// you've requested to at least produce a sample. +/// +/// But since the point of a counter group is that its members all cover exactly +/// the same period of time, this tactic can't be applied to support large +/// groups. If the kernel cannot schedule a group, its counters remain zero. I +/// think you can detect this situation by comparing the group's [`time_enabled`] +/// and [`time_running`] values. It might also be useful to set the `pinned` bit, +/// which puts the counter in an error state if it's not able to be put on the +/// CPU; see [#10]. +/// +/// According to the `perf_list(1)` man page, you may be able to free up a +/// hardware counter by disabling the kernel's NMI watchdog, which reserves one +/// for detecting kernel hangs: +/// +/// ```ignore +/// $ echo 0 > /proc/sys/kernel/nmi_watchdog +/// ``` +/// +/// You can reenable the watchdog when you're done like this: +/// +/// ```ignore +/// $ echo 1 > /proc/sys/kernel/nmi_watchdog +/// ``` +/// +/// [`group`]: Builder::group +/// [`read`]: Group::read +/// [`#5`]: https://github.com/jimblandy/perf-event/issues/5 +/// [`#10`]: https://github.com/jimblandy/perf-event/issues/10 +/// [`time_enabled`]: Counts::time_enabled +/// [`time_running`]: Counts::time_running +pub struct Group { + /// The file descriptor for this counter, returned by `perf_event_open`. + /// This counter itself is for the dummy software event, so it's not + /// interesting. + file: File, + + /// The unique id assigned to this group by the kernel. We only use this for + /// assertions. + id: u64, + + /// An upper bound on the number of Counters in this group. This lets us + /// allocate buffers of sufficient size for for PERF_FORMAT_GROUP reads. + /// + /// There's no way to ask the kernel how many members a group has. And if we + /// pass a group read a buffer that's too small, the kernel won't just + /// return a truncated result; it returns ENOSPC and leaves the buffer + /// untouched. So the buffer just has to be large enough. + /// + /// Since we're borrowed while building group members, adding members can + /// increment this counter. But it's harder to decrement it when a member + /// gets dropped: we don't require that a Group outlive its members, so they + /// can't necessarily update their `Group`'s count from a `Drop` impl. So we + /// just increment, giving us an overestimate, and then correct the count + /// when we actually do a read. + /// + /// This includes the dummy counter for the group itself. + max_members: usize, +} + +/// A collection of counts from a [`Group`] of counters. +/// +/// This is the type returned by calling [`read`] on a [`Group`]. +/// You can index it with a reference to a specific `Counter`: +/// +/// # fn main() -> std::io::Result<()> { +/// # use perf_event::{Builder, Group}; +/// # let mut group = Group::new()?; +/// # let cycles = Builder::new().group(&mut group).build()?; +/// # let insns = Builder::new().group(&mut group).build()?; +/// let counts = group.read()?; +/// println!("cycles / instructions: {} / {} ({:.2} cpi)", +/// counts[&cycles], +/// counts[&insns], +/// (counts[&cycles] as f64 / counts[&insns] as f64)); +/// # Ok(()) } +/// +/// Or you can iterate over the results it contains: +/// +/// # fn main() -> std::io::Result<()> { +/// # use perf_event::Group; +/// # let counts = Group::new()?.read()?; +/// for (id, value) in &counts { +/// println!("Counter id {} has value {}", id, value); +/// } +/// # Ok(()) } +/// +/// The `id` values produced by this iteration are internal identifiers assigned +/// by the kernel. You can use the [`Counter::id`] method to find a +/// specific counter's id. +/// +/// For some kinds of events, the kernel may use timesharing to give all +/// counters access to scarce hardware registers. You can see how long a group +/// was actually running versus the entire time it was enabled using the +/// `time_enabled` and `time_running` methods: +/// +/// # fn main() -> std::io::Result<()> { +/// # use perf_event::{Builder, Group}; +/// # let mut group = Group::new()?; +/// # let insns = Builder::new().group(&mut group).build()?; +/// # let counts = group.read()?; +/// let scale = counts.time_enabled() as f64 / +/// counts.time_running() as f64; +/// for (id, value) in &counts { +/// print!("Counter id {} has value {}", +/// id, (*value as f64 * scale) as u64); +/// if scale > 1.0 { +/// print!(" (estimated)"); +/// } +/// println!(); +/// } +/// +/// # Ok(()) } +/// +/// [`read`]: Group::read +pub struct Counts { + // Raw results from the `read`. + data: Vec<u64>, +} + +/// The value of a counter, along with timesharing data. +/// +/// Some counters are implemented in hardware, and the processor can run +/// only a fixed number of them at a time. If more counters are requested +/// than the hardware can support, the kernel timeshares them on the +/// hardware. +/// +/// This struct holds the value of a counter, together with the time it was +/// enabled, and the proportion of that for which it was actually running. +#[repr(C)] +pub struct CountAndTime { + /// The counter value. + /// + /// The meaning of this field depends on how the counter was configured when + /// it was built; see ['Builder']. + pub count: u64, + + /// How long this counter was enabled by the program, in nanoseconds. + pub time_enabled: u64, + + /// How long the kernel actually ran this counter, in nanoseconds. + /// + /// If `time_enabled == time_running`, then the counter ran for the entire + /// period it was enabled, without interruption. Otherwise, the counter + /// shared the underlying hardware with others, and you should prorate its + /// value accordingly. + pub time_running: u64, +} + +impl<'a> EventPid<'a> { + // Return the `pid` arg and the `flags` bits representing `self`. + fn as_args(&self) -> (pid_t, u32) { + match self { + EventPid::ThisProcess => (0, 0), + EventPid::Other(pid) => (*pid, 0), + EventPid::CGroup(file) => (file.as_raw_fd(), sys::bindings::PERF_FLAG_PID_CGROUP), + } + } +} + +impl<'a> Default for Builder<'a> { + fn default() -> Builder<'a> { + let mut attrs = perf_event_attr { + // Setting `size` accurately will not prevent the code from working + // on older kernels. The module comments for `perf_event_open_sys` + // explain why in far too much detail. + size: std::mem::size_of::<perf_event_attr>() as u32, + ..perf_event_attr::default() + }; + + attrs.set_disabled(1); + attrs.set_exclude_kernel(1); // don't count time in kernel + attrs.set_exclude_hv(1); // don't count time in hypervisor + + // Request data for `time_enabled` and `time_running`. + attrs.read_format |= sys::bindings::PERF_FORMAT_TOTAL_TIME_ENABLED as u64 + | sys::bindings::PERF_FORMAT_TOTAL_TIME_RUNNING as u64; + + let kind = Event::Hardware(events::Hardware::INSTRUCTIONS); + attrs.type_ = kind.r#type(); + attrs.config = kind.config(); + + Builder { + attrs, + who: EventPid::ThisProcess, + cpu: None, + group: None, + } + } +} + +impl<'a> Builder<'a> { + /// Return a new `Builder`, with all parameters set to their defaults. + pub fn new() -> Builder<'a> { + Builder::default() + } + + /// Observe the calling process. (This is the default.) + pub fn observe_self(mut self) -> Builder<'a> { + self.who = EventPid::ThisProcess; + self + } + + /// Observe the process with the given process id. This requires + /// [`CAP_SYS_PTRACE`][man-capabilities] capabilities. + /// + /// [man-capabilities]: http://man7.org/linux/man-pages/man7/capabilities.7.html + pub fn observe_pid(mut self, pid: pid_t) -> Builder<'a> { + self.who = EventPid::Other(pid); + self + } + + /// Observe code running in the given [cgroup][man-cgroups] (container). The + /// `cgroup` argument should be a `File` referring to the cgroup's directory + /// in the cgroupfs filesystem. + /// + /// [man-cgroups]: http://man7.org/linux/man-pages/man7/cgroups.7.html + pub fn observe_cgroup(mut self, cgroup: &'a File) -> Builder<'a> { + self.who = EventPid::CGroup(cgroup); + self + } + + /// Observe only code running on the given CPU core. + pub fn one_cpu(mut self, cpu: usize) -> Builder<'a> { + self.cpu = Some(cpu); + self + } + + /// Observe code running on any CPU core. (This is the default.) + pub fn any_cpu(mut self) -> Builder<'a> { + self.cpu = None; + self + } + + /// Set whether this counter is inherited by new threads. + /// + /// When this flag is set, this counter observes activity in new threads + /// created by any thread already being observed. + /// + /// By default, the flag is unset: counters are not inherited, and observe + /// only the threads specified when they are created. + /// + /// This flag cannot be set if the counter belongs to a `Group`. Doing so + /// will result in an error when the counter is built. This is a kernel + /// limitation. + pub fn inherit(mut self, inherit: bool) -> Builder<'a> { + let flag = if inherit { 1 } else { 0 }; + self.attrs.set_inherit(flag); + self + } + + /// Count events of the given kind. This accepts an [`Event`] value, + /// or any type that can be converted to one, so you can pass [`Hardware`], + /// [`Software`] and [`Cache`] values directly. + /// + /// The default is to count retired instructions, or + /// `Hardware::INSTRUCTIONS` events. + /// + /// For example, to count level 1 data cache references and misses, pass the + /// appropriate `events::Cache` values: + /// + /// # fn main() -> std::io::Result<()> { + /// use perf_event::{Builder, Group}; + /// use perf_event::events::{Cache, CacheOp, CacheResult, WhichCache}; + /// + /// const ACCESS: Cache = Cache { + /// which: WhichCache::L1D, + /// operation: CacheOp::READ, + /// result: CacheResult::ACCESS, + /// }; + /// const MISS: Cache = Cache { result: CacheResult::MISS, ..ACCESS }; + /// + /// let mut group = Group::new()?; + /// let access_counter = Builder::new().group(&mut group).kind(ACCESS).build()?; + /// let miss_counter = Builder::new().group(&mut group).kind(MISS).build()?; + /// # Ok(()) } + /// + /// [`Hardware`]: events::Hardware + /// [`Software`]: events::Software + /// [`Cache`]: events::Cache + pub fn kind<K: Into<Event>>(mut self, kind: K) -> Builder<'a> { + let kind = kind.into(); + self.attrs.type_ = kind.r#type(); + self.attrs.config = kind.config(); + self + } + + /// Place the counter in the given [`Group`]. Groups allow a set of counters + /// to be enabled, disabled, or read as a single atomic operation, so that + /// the counts can be usefully compared. + /// + /// [`Group`]: struct.Group.html + pub fn group(mut self, group: &'a mut Group) -> Builder<'a> { + self.group = Some(group); + + // man page: "Members of a group are usually initialized with disabled + // set to zero." + self.attrs.set_disabled(0); + + self + } + + /// Construct a [`Counter`] according to the specifications made on this + /// `Builder`. + /// + /// A freshly built `Counter` is disabled. To begin counting events, you + /// must call [`enable`] on the `Counter` or the `Group` to which it belongs. + /// + /// If the `Builder` requests features that the running kernel does not + /// support, it returns `Err(e)` where `e.kind() == ErrorKind::Other` and + /// `e.raw_os_error() == Some(libc::E2BIG)`. + /// + /// Unfortunately, problems in counter configuration are detected at this + /// point, by the kernel, not earlier when the offending request is made on + /// the `Builder`. The kernel's returned errors are not always helpful. + /// + /// [`Counter`]: struct.Counter.html + /// [`enable`]: struct.Counter.html#method.enable + pub fn build(mut self) -> std::io::Result<Counter> { + let cpu = match self.cpu { + Some(cpu) => cpu as c_int, + None => -1, + }; + let (pid, flags) = self.who.as_args(); + let group_fd = match self.group { + Some(ref mut g) => { + g.max_members += 1; + g.file.as_raw_fd() as c_int + } + None => -1, + }; + + let file = unsafe { + File::from_raw_fd(check_errno_syscall(|| { + sys::perf_event_open(&mut self.attrs, pid, cpu, group_fd, flags as c_ulong) + })?) + }; + + // If we're going to be part of a Group, retrieve the ID the kernel + // assigned us, so we can find our results in a Counts structure. Even + // if we're not part of a group, we'll use it in `Debug` output. + let mut id = 0_u64; + check_errno_syscall(|| unsafe { sys::ioctls::ID(file.as_raw_fd(), &mut id) })?; + + Ok(Counter { file, id }) + } +} + +impl Counter { + /// Return this counter's kernel-assigned unique id. + /// + /// This can be useful when iterating over [`Counts`]. + /// + /// [`Counts`]: struct.Counts.html + pub fn id(&self) -> u64 { + self.id + } + + /// Allow this `Counter` to begin counting its designated event. + /// + /// This does not affect whatever value the `Counter` had previously; new + /// events add to the current count. To clear a `Counter`, use the + /// [`reset`] method. + /// + /// Note that `Group` also has an [`enable`] method, which enables all + /// its member `Counter`s as a single atomic operation. + /// + /// [`reset`]: #method.reset + /// [`enable`]: struct.Group.html#method.enable + pub fn enable(&mut self) -> io::Result<()> { + check_errno_syscall(|| unsafe { sys::ioctls::ENABLE(self.file.as_raw_fd(), 0) }).map(|_| ()) + } + + /// Make this `Counter` stop counting its designated event. Its count is + /// unaffected. + /// + /// Note that `Group` also has a [`disable`] method, which disables all + /// its member `Counter`s as a single atomic operation. + /// + /// [`disable`]: struct.Group.html#method.disable + pub fn disable(&mut self) -> io::Result<()> { + check_errno_syscall(|| unsafe { sys::ioctls::DISABLE(self.file.as_raw_fd(), 0) }) + .map(|_| ()) + } + + /// Reset the value of this `Counter` to zero. + /// + /// Note that `Group` also has a [`reset`] method, which resets all + /// its member `Counter`s as a single atomic operation. + /// + /// [`reset`]: struct.Group.html#method.reset + pub fn reset(&mut self) -> io::Result<()> { + check_errno_syscall(|| unsafe { sys::ioctls::RESET(self.file.as_raw_fd(), 0) }).map(|_| ()) + } + + /// Return this `Counter`'s current value as a `u64`. + /// + /// Consider using the [`read_count_and_time`] method instead of this one. Some + /// counters are implemented in hardware, and the processor can support only + /// a certain number running at a time. If more counters are requested than + /// the hardware can support, the kernel timeshares them on the hardware. + /// This method gives you no indication whether this has happened; + /// `read_count_and_time` does. + /// + /// Note that `Group` also has a [`read`] method, which reads all + /// its member `Counter`s' values at once. + /// + /// [`read`]: Group::read + /// [`read_count_and_time`]: Counter::read_count_and_time + pub fn read(&mut self) -> io::Result<u64> { + Ok(self.read_count_and_time()?.count) + } + + /// Return this `Counter`'s current value and timesharing data. + /// + /// Some counters are implemented in hardware, and the processor can run + /// only a fixed number of them at a time. If more counters are requested + /// than the hardware can support, the kernel timeshares them on the + /// hardware. + /// + /// This method returns a [`CountAndTime`] struct, whose `count` field holds + /// the counter's value, and whose `time_enabled` and `time_running` fields + /// indicate how long you had enabled the counter, and how long the counter + /// was actually scheduled on the processor. This lets you detect whether + /// the counter was timeshared, and adjust your use accordingly. Times + /// are reported in nanoseconds. + /// + /// # use perf_event::Builder; + /// # fn main() -> std::io::Result<()> { + /// # let mut counter = Builder::new().build()?; + /// let cat = counter.read_count_and_time()?; + /// if cat.time_running == 0 { + /// println!("No data collected."); + /// } else if cat.time_running < cat.time_enabled { + /// // Note: this way of scaling is accurate, but `u128` division + /// // is usually implemented in software, which may be slow. + /// println!("{} instructions (estimated)", + /// (cat.count as u128 * + /// cat.time_enabled as u128 / cat.time_running as u128) as u64); + /// } else { + /// println!("{} instructions", cat.count); + /// } + /// # Ok(()) } + /// + /// Note that `Group` also has a [`read`] method, which reads all + /// its member `Counter`s' values at once. + /// + /// [`read`]: Group::read + pub fn read_count_and_time(&mut self) -> io::Result<CountAndTime> { + let mut buf = [0_u64; 3]; + self.file.read_exact(u64::slice_as_bytes_mut(&mut buf))?; + + let cat = CountAndTime { + count: buf[0], + time_enabled: buf[1], + time_running: buf[2], + }; + + // Does the kernel ever return nonsense? + assert!(cat.time_running <= cat.time_enabled); + + Ok(cat) + } +} + +impl std::fmt::Debug for Counter { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + fmt, + "Counter {{ fd: {}, id: {} }}", + self.file.as_raw_fd(), + self.id + ) + } +} + +impl Group { + /// Construct a new, empty `Group`. + #[allow(unused_parens)] + pub fn new() -> io::Result<Group> { + // Open a placeholder perf counter that we can add other events to. + let mut attrs = perf_event_attr { + size: std::mem::size_of::<perf_event_attr>() as u32, + type_: sys::bindings::PERF_TYPE_SOFTWARE, + config: sys::bindings::PERF_COUNT_SW_DUMMY as u64, + ..perf_event_attr::default() + }; + + attrs.set_disabled(1); + attrs.set_exclude_kernel(1); + attrs.set_exclude_hv(1); + + // Arrange to be able to identify the counters we read back. + attrs.read_format = (sys::bindings::PERF_FORMAT_TOTAL_TIME_ENABLED + | sys::bindings::PERF_FORMAT_TOTAL_TIME_RUNNING + | sys::bindings::PERF_FORMAT_ID + | sys::bindings::PERF_FORMAT_GROUP) as u64; + + let file = unsafe { + File::from_raw_fd(check_errno_syscall(|| { + sys::perf_event_open(&mut attrs, 0, -1, -1, 0) + })?) + }; + + // Retrieve the ID the kernel assigned us. + let mut id = 0_u64; + check_errno_syscall(|| unsafe { sys::ioctls::ID(file.as_raw_fd(), &mut id) })?; + + Ok(Group { + file, + id, + max_members: 1, + }) + } + + /// Allow all `Counter`s in this `Group` to begin counting their designated + /// events, as a single atomic operation. + /// + /// This does not affect whatever values the `Counter`s had previously; new + /// events add to the current counts. To clear the `Counter`s, use the + /// [`reset`] method. + /// + /// [`reset`]: #method.reset + pub fn enable(&mut self) -> io::Result<()> { + self.generic_ioctl(sys::ioctls::ENABLE) + } + + /// Make all `Counter`s in this `Group` stop counting their designated + /// events, as a single atomic operation. Their counts are unaffected. + pub fn disable(&mut self) -> io::Result<()> { + self.generic_ioctl(sys::ioctls::DISABLE) + } + + /// Reset all `Counter`s in this `Group` to zero, as a single atomic operation. + pub fn reset(&mut self) -> io::Result<()> { + self.generic_ioctl(sys::ioctls::RESET) + } + + /// Perform some group ioctl. + /// + /// `f` must be a syscall that sets `errno` and returns `-1` on failure. + fn generic_ioctl(&mut self, f: unsafe fn(c_int, c_uint) -> c_int) -> io::Result<()> { + check_errno_syscall(|| unsafe { + f(self.file.as_raw_fd(), sys::bindings::PERF_IOC_FLAG_GROUP) + }) + .map(|_| ()) + } + + /// Return the values of all the `Counter`s in this `Group` as a [`Counts`] + /// value. + /// + /// A `Counts` value is a map from specific `Counter`s to their values. You + /// can find a specific `Counter`'s value by indexing: + /// + /// ```ignore + /// let mut group = Group::new()?; + /// let counter1 = Builder::new().group(&mut group).kind(...).build()?; + /// let counter2 = Builder::new().group(&mut group).kind(...).build()?; + /// ... + /// let counts = group.read()?; + /// println!("Rhombus inclinations per taxi medallion: {} / {} ({:.0}%)", + /// counts[&counter1], + /// counts[&counter2], + /// (counts[&counter1] as f64 / counts[&counter2] as f64) * 100.0); + /// ``` + /// + /// [`Counts`]: struct.Counts.html + pub fn read(&mut self) -> io::Result<Counts> { + // Since we passed `PERF_FORMAT_{ID,GROUP,TOTAL_TIME_{ENABLED,RUNNING}}`, + // the data we'll read has the form: + // + // struct read_format { + // u64 nr; /* The number of events */ + // u64 time_enabled; /* if PERF_FORMAT_TOTAL_TIME_ENABLED */ + // u64 time_running; /* if PERF_FORMAT_TOTAL_TIME_RUNNING */ + // struct { + // u64 value; /* The value of the event */ + // u64 id; /* if PERF_FORMAT_ID */ + // } values[nr]; + // }; + let mut data = vec![0_u64; 3 + 2 * self.max_members]; + assert_eq!( + self.file.read(u64::slice_as_bytes_mut(&mut data))?, + std::mem::size_of_val(&data[..]) + ); + + let counts = Counts { data }; + + // CountsIter assumes that the group's dummy count appears first. + assert_eq!(counts.nth_ref(0).0, self.id); + + // Does the kernel ever return nonsense? + assert!(counts.time_running() <= counts.time_enabled()); + + // Update `max_members` for the next read. + self.max_members = counts.len(); + + Ok(counts) + } +} + +impl std::fmt::Debug for Group { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + fmt, + "Group {{ fd: {}, id: {} }}", + self.file.as_raw_fd(), + self.id + ) + } +} + +impl Counts { + /// Return the number of counters this `Counts` holds results for. + #[allow(clippy::len_without_is_empty)] // Groups are never empty. + pub fn len(&self) -> usize { + self.data[0] as usize + } + + /// Return the number of nanoseconds the `Group` was enabled that + /// contributed to this `Counts`' contents. + pub fn time_enabled(&self) -> u64 { + self.data[1] + } + + /// Return the number of nanoseconds the `Group` was actually collecting + /// counts that contributed to this `Counts`' contents. + pub fn time_running(&self) -> u64 { + self.data[2] + } + + /// Return a range of indexes covering the count and id of the `n`'th counter. + fn nth_index(n: usize) -> std::ops::Range<usize> { + let base = 3 + 2 * n; + base..base + 2 + } + + /// Return the id and count of the `n`'th counter. This returns a reference + /// to the count, for use by the `Index` implementation. + fn nth_ref(&self, n: usize) -> (u64, &u64) { + let id_val = &self.data[Counts::nth_index(n)]; + + // (id, &value) + (id_val[1], &id_val[0]) + } +} + +/// An iterator over the counter values in a [`Counts`], returned by +/// [`Group::read`]. +/// +/// Each item is a pair `(id, &value)`, where `id` is the number assigned to the +/// counter by the kernel (see `Counter::id`), and `value` is that counter's +/// value. +/// +/// [`Counts`]: struct.Counts.html +/// [`Counter::id`]: struct.Counter.html#method.id +/// [`Group::read`]: struct.Group.html#method.read +pub struct CountsIter<'c> { + counts: &'c Counts, + next: usize, +} + +impl<'c> Iterator for CountsIter<'c> { + type Item = (u64, &'c u64); + fn next(&mut self) -> Option<(u64, &'c u64)> { + if self.next >= self.counts.len() { + return None; + } + let result = self.counts.nth_ref(self.next); + self.next += 1; + Some(result) + } +} + +impl<'c> IntoIterator for &'c Counts { + type Item = (u64, &'c u64); + type IntoIter = CountsIter<'c>; + fn into_iter(self) -> CountsIter<'c> { + CountsIter { + counts: self, + next: 1, // skip the `Group` itself, it's just a dummy. + } + } +} + +impl Counts { + /// Return the value recorded for `member` in `self`, or `None` if `member` + /// is not present. + /// + /// If you know that `member` is in the group, you can simply index: + /// + /// # fn main() -> std::io::Result<()> { + /// # use perf_event::{Builder, Group}; + /// # let mut group = Group::new()?; + /// # let cycle_counter = Builder::new().group(&mut group).build()?; + /// # let counts = group.read()?; + /// let cycles = counts[&cycle_counter]; + /// # Ok(()) } + pub fn get(&self, member: &Counter) -> Option<&u64> { + self.into_iter() + .find(|&(id, _)| id == member.id) + .map(|(_, value)| value) + } + + /// Return an iterator over the counts in `self`. + /// + /// # fn main() -> std::io::Result<()> { + /// # use perf_event::Group; + /// # let counts = Group::new()?.read()?; + /// for (id, value) in &counts { + /// println!("Counter id {} has value {}", id, value); + /// } + /// # Ok(()) } + /// + /// Each item is a pair `(id, &value)`, where `id` is the number assigned to + /// the counter by the kernel (see `Counter::id`), and `value` is that + /// counter's value. + pub fn iter(&self) -> CountsIter { + <&Counts as IntoIterator>::into_iter(self) + } +} + +impl std::ops::Index<&Counter> for Counts { + type Output = u64; + fn index(&self, index: &Counter) -> &u64 { + self.get(index).unwrap() + } +} + +impl std::fmt::Debug for Counts { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_map().entries(self.into_iter()).finish() + } +} + +/// A type whose values can be safely accessed as a slice of bytes. +/// +/// # Safety +/// +/// `Self` must be a type such that storing a value in memory +/// initializes all the bytes of that memory, so that +/// `slice_as_bytes_mut` can never expose uninitialized bytes to the +/// caller. +unsafe trait SliceAsBytesMut: Sized { + fn slice_as_bytes_mut(slice: &mut [Self]) -> &mut [u8] { + unsafe { + std::slice::from_raw_parts_mut( + slice.as_mut_ptr() as *mut u8, + std::mem::size_of_val(slice), + ) + } + } +} + +unsafe impl SliceAsBytesMut for u64 {} + +/// Produce an `io::Result` from an errno-style system call. +/// +/// An 'errno-style' system call is one that reports failure by returning -1 and +/// setting the C `errno` value when an error occurs. +fn check_errno_syscall<F, R>(f: F) -> io::Result<R> +where + F: FnOnce() -> R, + R: PartialOrd + Default, +{ + let result = f(); + if result < R::default() { + Err(io::Error::last_os_error()) + } else { + Ok(result) + } +} + +#[test] +fn simple_build() { + Builder::new() + .build() + .expect("Couldn't build default Counter"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_error_code_is_correct() { + // This configuration should always result in EINVAL + let builder = Builder::new() + // CPU_CLOCK is literally always supported so we don't have to worry + // about test failures when in VMs. + .kind(events::Software::CPU_CLOCK) + // There should _hopefully_ never be a system with this many CPUs. + .one_cpu(i32::MAX as usize); + + match builder.build() { + Ok(_) => panic!("counter construction was not supposed to succeed"), + Err(e) => assert_eq!(e.raw_os_error(), Some(libc::EINVAL)), + } +} |
