//! 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` //! 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> = 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) { 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)] /// # Safety 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); } }