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