From b4eae244826a0fb8907ef50c8ef9a38da40b719d Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 8 Mar 2025 21:06:20 +0100 Subject: Vendor perf event --- perf-event/src/hooks.rs | 272 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 perf-event/src/hooks.rs (limited to 'perf-event/src/hooks.rs') 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` +//! 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)] + 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); + } +} -- cgit v1.3.1