| //! 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); |
| } |
| } |