1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 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);
}
}