libbpf_rs/
perf_buffer.rs

1use core::ffi::c_void;
2use std::fmt::Debug;
3use std::fmt::Formatter;
4use std::fmt::Result as FmtResult;
5use std::os::unix::prelude::AsRawFd;
6use std::ptr;
7use std::ptr::NonNull;
8use std::slice;
9use std::time::Duration;
10
11use crate::util;
12use crate::util::validate_bpf_ret;
13use crate::AsRawLibbpf;
14use crate::Error;
15use crate::ErrorExt as _;
16use crate::MapCore;
17use crate::MapType;
18use crate::Result;
19
20type SampleCb<'b> = Box<dyn FnMut(i32, &[u8]) + 'b>;
21type LostCb<'b> = Box<dyn FnMut(i32, u64) + 'b>;
22
23struct CbStruct<'b> {
24    sample_cb: Option<SampleCb<'b>>,
25    lost_cb: Option<LostCb<'b>>,
26}
27
28impl Debug for CbStruct<'_> {
29    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
30        let Self { sample_cb, lost_cb } = self;
31        f.debug_struct("CbStruct")
32            .field("sample_cb", &sample_cb.as_ref().map(|cb| &cb as *const _))
33            .field("lost_cb", &lost_cb.as_ref().map(|cb| &cb as *const _))
34            .finish()
35    }
36}
37
38/// Builds [`PerfBuffer`] instances.
39pub struct PerfBufferBuilder<'a, 'b, M>
40where
41    M: MapCore,
42{
43    map: &'a M,
44    pages: usize,
45    sample_cb: Option<SampleCb<'b>>,
46    lost_cb: Option<LostCb<'b>>,
47}
48
49impl<'a, M> PerfBufferBuilder<'a, '_, M>
50where
51    M: MapCore,
52{
53    /// Create a new `PerfBufferBuilder` using the provided `MapCore`
54    /// object.
55    pub fn new(map: &'a M) -> Self {
56        Self {
57            map,
58            pages: 64,
59            sample_cb: None,
60            lost_cb: None,
61        }
62    }
63}
64
65impl<'a, 'b, M> PerfBufferBuilder<'a, 'b, M>
66where
67    M: MapCore,
68{
69    /// Callback to run when a sample is received.
70    ///
71    /// This callback provides a raw byte slice. You may find libraries such as
72    /// [`plain`](https://crates.io/crates/plain) helpful.
73    ///
74    /// Callback arguments are: `(cpu, data)`.
75    pub fn sample_cb<F>(self, cb: F) -> PerfBufferBuilder<'a, 'b, M>
76    where
77        F: FnMut(i32, &[u8]) + 'b,
78    {
79        PerfBufferBuilder {
80            map: self.map,
81            pages: self.pages,
82            sample_cb: Some(Box::new(cb)),
83            lost_cb: self.lost_cb,
84        }
85    }
86
87    /// Callback to run when a sample is received.
88    ///
89    /// Callback arguments are: `(cpu, lost_count)`.
90    pub fn lost_cb<F>(self, cb: F) -> PerfBufferBuilder<'a, 'b, M>
91    where
92        F: FnMut(i32, u64) + 'b,
93    {
94        PerfBufferBuilder {
95            map: self.map,
96            pages: self.pages,
97            sample_cb: self.sample_cb,
98            lost_cb: Some(Box::new(cb)),
99        }
100    }
101
102    /// The number of pages to size the ring buffer.
103    pub fn pages(self, pages: usize) -> PerfBufferBuilder<'a, 'b, M> {
104        PerfBufferBuilder {
105            map: self.map,
106            pages,
107            sample_cb: self.sample_cb,
108            lost_cb: self.lost_cb,
109        }
110    }
111
112    /// Build the `PerfBuffer` object as configured.
113    pub fn build(self) -> Result<PerfBuffer<'b>> {
114        if self.map.map_type() != MapType::PerfEventArray {
115            return Err(Error::with_invalid_data("Must use a PerfEventArray map"));
116        }
117
118        if !self.pages.is_power_of_two() {
119            return Err(Error::with_invalid_data("Page count must be power of two"));
120        }
121
122        let c_sample_cb: libbpf_sys::perf_buffer_sample_fn = if self.sample_cb.is_some() {
123            Some(Self::call_sample_cb)
124        } else {
125            None
126        };
127
128        let c_lost_cb: libbpf_sys::perf_buffer_lost_fn = if self.lost_cb.is_some() {
129            Some(Self::call_lost_cb)
130        } else {
131            None
132        };
133
134        let callback_struct_ptr = Box::into_raw(Box::new(CbStruct {
135            sample_cb: self.sample_cb,
136            lost_cb: self.lost_cb,
137        }));
138
139        let ptr = unsafe {
140            libbpf_sys::perf_buffer__new(
141                self.map.as_fd().as_raw_fd(),
142                self.pages as libbpf_sys::size_t,
143                c_sample_cb,
144                c_lost_cb,
145                callback_struct_ptr as *mut _,
146                ptr::null(),
147            )
148        };
149        let ptr = validate_bpf_ret(ptr).context("failed to create perf buffer")?;
150        let pb = PerfBuffer {
151            ptr,
152            _cb_struct: unsafe { Box::from_raw(callback_struct_ptr) },
153        };
154        Ok(pb)
155    }
156
157    unsafe extern "C" fn call_sample_cb(ctx: *mut c_void, cpu: i32, data: *mut c_void, size: u32) {
158        let callback_struct = ctx as *mut CbStruct<'_>;
159
160        if let Some(cb) = unsafe { &mut (*callback_struct).sample_cb } {
161            let slice = unsafe { slice::from_raw_parts(data as *const u8, size as usize) };
162            cb(cpu, slice);
163        }
164    }
165
166    unsafe extern "C" fn call_lost_cb(ctx: *mut c_void, cpu: i32, count: u64) {
167        let callback_struct = ctx as *mut CbStruct<'_>;
168
169        if let Some(cb) = unsafe { &mut (*callback_struct).lost_cb } {
170            cb(cpu, count);
171        }
172    }
173}
174
175impl<M> Debug for PerfBufferBuilder<'_, '_, M>
176where
177    M: MapCore,
178{
179    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
180        let Self {
181            map,
182            pages,
183            sample_cb,
184            lost_cb,
185        } = self;
186        f.debug_struct("PerfBufferBuilder")
187            .field("map", map)
188            .field("pages", pages)
189            .field("sample_cb", &sample_cb.as_ref().map(|cb| &cb as *const _))
190            .field("lost_cb", &lost_cb.as_ref().map(|cb| &cb as *const _))
191            .finish()
192    }
193}
194
195/// Represents a special kind of [`MapCore`]. Typically used to transfer data between
196/// [`Program`][crate::Program]s and userspace.
197#[derive(Debug)]
198pub struct PerfBuffer<'b> {
199    ptr: NonNull<libbpf_sys::perf_buffer>,
200    // Hold onto the box so it'll get dropped when PerfBuffer is dropped
201    _cb_struct: Box<CbStruct<'b>>,
202}
203
204// TODO: Document methods.
205#[allow(missing_docs)]
206impl PerfBuffer<'_> {
207    pub fn epoll_fd(&self) -> i32 {
208        unsafe { libbpf_sys::perf_buffer__epoll_fd(self.ptr.as_ptr()) }
209    }
210
211    pub fn poll(&self, timeout: Duration) -> Result<()> {
212        let ret =
213            unsafe { libbpf_sys::perf_buffer__poll(self.ptr.as_ptr(), timeout.as_millis() as i32) };
214        util::parse_ret(ret)
215    }
216
217    pub fn consume(&self) -> Result<()> {
218        let ret = unsafe { libbpf_sys::perf_buffer__consume(self.ptr.as_ptr()) };
219        util::parse_ret(ret)
220    }
221
222    pub fn consume_buffer(&self, buf_idx: usize) -> Result<()> {
223        let ret = unsafe {
224            libbpf_sys::perf_buffer__consume_buffer(
225                self.ptr.as_ptr(),
226                buf_idx as libbpf_sys::size_t,
227            )
228        };
229        util::parse_ret(ret)
230    }
231
232    pub fn buffer_cnt(&self) -> usize {
233        unsafe { libbpf_sys::perf_buffer__buffer_cnt(self.ptr.as_ptr()) as usize }
234    }
235
236    pub fn buffer_fd(&self, buf_idx: usize) -> Result<i32> {
237        let ret = unsafe {
238            libbpf_sys::perf_buffer__buffer_fd(self.ptr.as_ptr(), buf_idx as libbpf_sys::size_t)
239        };
240        util::parse_ret_i32(ret)
241    }
242}
243
244impl AsRawLibbpf for PerfBuffer<'_> {
245    type LibbpfType = libbpf_sys::perf_buffer;
246
247    /// Retrieve the underlying [`libbpf_sys::perf_buffer`].
248    fn as_libbpf_object(&self) -> NonNull<Self::LibbpfType> {
249        self.ptr
250    }
251}
252
253// SAFETY: `perf_buffer` objects can safely be polled from any thread.
254unsafe impl Send for PerfBuffer<'_> {}
255
256impl Drop for PerfBuffer<'_> {
257    fn drop(&mut self) {
258        unsafe {
259            libbpf_sys::perf_buffer__free(self.ptr.as_ptr());
260        }
261    }
262}
263
264#[cfg(test)]
265mod test {
266    use super::*;
267
268    /// Check that `PerfBuffer` is `Send`.
269    #[test]
270    fn perfbuffer_is_send() {
271        fn test<T>()
272        where
273            T: Send,
274        {
275        }
276
277        test::<PerfBuffer<'_>>();
278    }
279}