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 {}