perf_event_open/sample/
mod.rs

1use std::fs::File;
2use std::io::Result;
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::Arc;
5
6use arena::Arena;
7use auxiliary::AuxTracer;
8use iter::{CowIter, Iter};
9use rb::Rb;
10use record::{Parser, UnsafeParser};
11
12use crate::ffi::syscall::ioctl_arg;
13use crate::ffi::{bindings as b, Attr, Metadata, PAGE_SIZE};
14
15mod arena;
16pub mod auxiliary;
17pub mod iter;
18pub mod rb;
19pub mod record;
20
21/// Event sampler.
22///
23/// This type provides the event sampling function of `perf_event_open`,
24/// which can capture the context when the event happens, helpling us to
25/// gain in-depth understanding of the system status at that time,
26/// similar to the `perf record` command.
27///
28/// # Examples
29///
30/// ```rust
31/// use std::thread;
32/// use std::time::Duration;
33///
34/// use perf_event_open::config::{Cpu, Opts, Proc, Size};
35/// use perf_event_open::count::Counter;
36/// use perf_event_open::event::hw::Hardware;
37/// # use perf_event_open::sample::record::Record;
38///
39/// // Count retired instructions on any process, CPU 0.
40/// let event = Hardware::Instr;
41/// let target = (Proc::ALL, Cpu(0));
42///
43/// let mut opts = Opts::default();
44/// opts.sample_format.user_stack = Some(Size(32)); // Dump 32-bytes user stack.
45///
46/// let counter = Counter::new(event, target, opts).unwrap();
47/// let sampler = counter.sampler(10).unwrap(); // Allocate 2^10 pages to store samples.
48///
49/// counter.enable().unwrap();
50/// thread::sleep(Duration::from_millis(10));
51/// counter.disable().unwrap();
52///
53/// for it in sampler.iter() {
54///     println!("{:-?}", it);
55///     # if let (_, Record::Sample(s)) = it {
56///     #     assert!(s.user_stack.is_some());
57///     # }
58/// }
59/// ```
60pub struct Sampler {
61    perf: Arc<File>,
62    arena: Arena,
63    parser: Parser,
64}
65
66impl Sampler {
67    pub(super) fn new(perf: Arc<File>, attr: &Attr, exp: u8) -> Result<Self> {
68        let len = (1 + 2_usize.pow(exp as _)) * *PAGE_SIZE;
69        let arena = Arena::new(&perf, len, 0)?;
70
71        Ok(Sampler {
72            perf,
73            arena,
74            parser: Parser(UnsafeParser::from_attr(attr)),
75        })
76    }
77
78    /// Returns a record iterator over the kernel ring-buffer.
79    pub fn iter(&self) -> Iter<'_> {
80        let alloc = self.arena.as_slice();
81        let metadata = unsafe { &mut *(alloc.as_ptr() as *mut Metadata) };
82        let rb = Rb::new(
83            // https://github.com/torvalds/linux/blob/v6.13/kernel/events/core.c#L6212
84            &alloc[*PAGE_SIZE..],
85            unsafe { AtomicU64::from_ptr(&mut metadata.data_tail as _) },
86            unsafe { AtomicU64::from_ptr(&mut metadata.data_head as _) },
87        );
88        Iter(CowIter {
89            rb,
90            perf: &self.perf,
91            parser: &self.parser,
92        })
93    }
94
95    /// Record parser of the sampler.
96    pub fn parser(&self) -> &UnsafeParser {
97        &self.parser.0
98    }
99
100    /// Create an AUX tracer for this sampler.
101    ///
102    /// The AUX tracer needs a ring-buffer to store data,
103    /// and 1 + 2^`exp` pages will be allocated for this.
104    ///
105    /// Multiple calls to this method just duplicates the existing AUX tracer,
106    /// AUX tracers from the same sampler shares the same ring-buffer in the
107    /// kernel space, so `exp` should be the same.
108    pub fn aux_tracer(&self, exp: u8) -> Result<AuxTracer<'_>> {
109        let alloc = self.arena.as_slice();
110        let metadata = unsafe { &mut *(alloc.as_ptr() as *mut Metadata) };
111        AuxTracer::new(&self.perf, metadata, exp)
112    }
113
114    /// Pause the ring-buffer output.
115    ///
116    /// A paused ring-buffer does not prevent generation of samples, but simply
117    /// discards them. The discarded samples are considered lost, and cause a
118    /// [`LostRecords`][record::lost::LostRecords] to be generated when possible.
119    ///
120    /// An overflow signal may still be triggered by the discarded sample even
121    /// though the ring-buffer remains empty.
122    ///
123    /// Since `linux-4.7`: <https://github.com/torvalds/linux/commit/86e7972f690c1017fd086cdfe53d8524e68c661c>
124    #[cfg(feature = "linux-4.7")]
125    pub fn pause(&self) -> Result<()> {
126        ioctl_arg(&self.perf, b::PERF_IOC_OP_PAUSE_OUTPUT as _, 1)?;
127        Ok(())
128    }
129
130    #[cfg(not(feature = "linux-4.7"))]
131    pub fn pause(&self) -> Result<()> {
132        crate::config::unsupported!()
133    }
134
135    /// Resume the ring-buffer output.
136    ///
137    /// Since `linux-4.7`: <https://github.com/torvalds/linux/commit/86e7972f690c1017fd086cdfe53d8524e68c661c>
138    #[cfg(feature = "linux-4.7")]
139    pub fn resume(&self) -> Result<()> {
140        ioctl_arg(&self.perf, b::PERF_IOC_OP_PAUSE_OUTPUT as _, 0)?;
141        Ok(())
142    }
143
144    #[cfg(not(feature = "linux-4.7"))]
145    pub fn resume(&self) -> Result<()> {
146        crate::config::unsupported!()
147    }
148
149    /// Enables the counter until the maximum number of samples has been generated.
150    ///
151    /// The counter will be disabled if `max_samples` is reached.
152    ///
153    /// # Examples
154    ///
155    /// ```rust
156    /// use std::{thread, time::Duration};
157    ///
158    /// use perf_event_open::config::{Cpu, Opts, Proc, SampleOn};
159    /// use perf_event_open::count::Counter;
160    /// use perf_event_open::event::sw::Software;
161    ///
162    /// let event = Software::TaskClock;
163    /// let target = (Proc::ALL, Cpu(0));
164    /// let mut opts = Opts::default();
165    /// opts.sample_on = SampleOn::Count(1_000_000); // 1ms
166    ///
167    /// let counter = Counter::new(event, target, opts).unwrap();
168    /// let sampler = counter.sampler(5).unwrap();
169    ///
170    /// sampler.enable_counter_with(10).unwrap();
171    /// thread::sleep(Duration::from_millis(20));
172    ///
173    /// assert_eq!(sampler.iter().count(), 10);
174    /// ```
175    ///
176    /// Furthermore, we can capture the overflow events by enabling I/O signaling from
177    /// the perf event fd.
178    ///
179    /// On each overflow, `POLL_IN` is indicated if `max_samples` has not been reached.
180    /// Otherwise, `POLL_HUP` is indicated.
181    ///
182    ///```rust
183    /// # // Fork to avoid signal handler conflicts.
184    /// # unsafe {
185    /// #     let child = libc::fork();
186    /// #     if child > 0 {
187    /// #         let mut code = 0;
188    /// #         libc::waitpid(child, &mut code as _, 0);
189    /// #         assert_eq!(code, 0);
190    /// #         return;
191    /// #     }
192    /// # }
193    /// #
194    /// # unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
195    /// #
196    /// # let result = std::panic::catch_unwind(|| {
197    /// use perf_event_open::config::{Cpu, Opts, Proc, SampleOn};
198    /// use perf_event_open::count::Counter;
199    /// use perf_event_open::event::sw::Software;
200    /// use std::mem::MaybeUninit;
201    /// use std::os::fd::AsRawFd;
202    /// use std::ptr::null_mut;
203    /// use std::sync::atomic::AtomicBool;
204    /// use std::sync::atomic::Ordering as MemOrd;
205    ///
206    /// const MAX_SAMPLES: usize = 3;
207    ///
208    /// let event = Software::TaskClock;
209    /// let target = (Proc::CURRENT, Cpu::ALL);
210    /// let mut opts = Opts::default();
211    /// opts.sample_on = SampleOn::Count(1_000_000); // 1ms
212    ///
213    /// let counter = Counter::new(event, target, opts).unwrap();
214    ///
215    /// // Enable I/O signals from perf event fd to the current process.
216    /// let fd = counter.file().as_raw_fd();
217    /// unsafe {
218    ///     libc::fcntl(fd, libc::F_SETFL, libc::O_ASYNC);
219    ///     // The value of `F_SETSIG` is 10, and libc crate does not have
220    ///     // that binding (same as `POLL_IN` and `POLL_HUP` below).
221    ///     libc::fcntl(fd, 10, libc::SIGIO);
222    ///     libc::fcntl(fd, libc::F_SETOWN, libc::getpid());
223    /// }
224    ///
225    /// static IN: AtomicBool = AtomicBool::new(false);
226    /// static HUP: AtomicBool = AtomicBool::new(false);
227    ///
228    /// fn handler(num: i32, info: *const libc::siginfo_t) {
229    ///     assert_eq!(num, libc::SIGIO);
230    ///     match unsafe { *info }.si_code {
231    ///         1 => IN.store(true, MemOrd::Relaxed),  // POLL_IN
232    ///         6 => HUP.store(true, MemOrd::Relaxed), // POLL_HUP
233    ///         _ => unreachable!(),
234    ///     }
235    /// }
236    ///
237    /// let act = libc::sigaction {
238    ///     sa_sigaction: handler as _,
239    ///     sa_mask: unsafe { MaybeUninit::zeroed().assume_init() },
240    ///     sa_flags: libc::SA_SIGINFO,
241    ///     sa_restorer: None,
242    /// };
243    /// unsafe { libc::sigaction(libc::SIGIO, &act as _, null_mut()) };
244    ///
245    /// let sampler = counter.sampler(5).unwrap();
246    /// sampler.enable_counter_with(MAX_SAMPLES as _).unwrap();
247    ///
248    /// let iter = &mut sampler.iter();
249    /// let mut count = 0;
250    /// while !HUP.load(MemOrd::Relaxed) {
251    ///     while IN.swap(false, MemOrd::Relaxed) {
252    ///         count += iter.count();
253    ///     }
254    /// }
255    /// count += iter.count();
256    /// assert_eq!(count, MAX_SAMPLES);
257    /// # });
258    /// # if result.is_err() {
259    /// #     unsafe { libc::abort() };
260    /// # }
261    /// #
262    /// # unsafe { libc::exit(0) };
263    /// ```
264    pub fn enable_counter_with(&self, max_samples: u32) -> Result<()> {
265        ioctl_arg(&self.perf, b::PERF_IOC_OP_REFRESH as _, max_samples as _)?;
266        Ok(())
267    }
268
269    /// Reset overflow condition.
270    ///
271    /// How to interpret `freq_or_count` depends on how the counter was created.
272    /// This means that the new frequency will be applied if the counter was
273    /// created with [`SampleOn::Freq`][crate::config::SampleOn], and so will the count.
274    pub fn sample_on(&self, freq_or_count: u64) -> Result<()> {
275        ioctl_arg(&self.perf, b::PERF_IOC_OP_PERIOD as _, freq_or_count)?;
276        Ok(())
277    }
278
279    fn metadata_inner(&self) -> *mut Metadata {
280        let alloc_ptr = self.arena.as_slice().as_ptr();
281        alloc_ptr as *mut Metadata
282    }
283
284    /// Counter's enabled time.
285    ///
286    /// Same as [time][crate::count::Stat::time_enabled] returned by
287    /// [`Counter::stat`][crate::count::Counter::stat], but much cheaper
288    /// since the value is read from memory instead of system call.
289    pub fn counter_time_enabled(&self) -> u64 {
290        let metadata = self.metadata_inner();
291        let metadata = unsafe { &mut *metadata };
292        let time_enabled = unsafe { AtomicU64::from_ptr(&mut metadata.time_enabled as _) };
293        time_enabled.load(Ordering::Relaxed)
294    }
295
296    /// Counter's running time.
297    ///
298    /// Same as [time][crate::count::Stat::time_running] returned by
299    /// [`Counter::stat`][crate::count::Counter::stat], but much cheaper
300    /// since the value is read from memory instead of system call.
301    pub fn counter_time_running(&self) -> u64 {
302        let metadata = self.metadata_inner();
303        let metadata = unsafe { &mut *metadata };
304        let time_running = unsafe { AtomicU64::from_ptr(&mut metadata.time_running as _) };
305        time_running.load(Ordering::Relaxed)
306    }
307}
308
309// `Arena::ptr` is valid during the lifetime of `Sampler`.
310unsafe impl Send for Sampler {}