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