Skip to main content

aya_friday/maps/perf/
perf_event_array.rs

1//! A map that can be used to receive events from eBPF programs using the linux [`perf`] API
2//!
3//! [`perf`]: https://perf.wiki.kernel.org/index.php/Main_Page.
4
5use std::{
6    borrow::{Borrow, BorrowMut},
7    ops::{ControlFlow, Deref as _},
8    os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd},
9    path::Path,
10    sync::Arc,
11};
12
13use crate::{
14    maps::{
15        MapData, MapError, PinError,
16        perf::{PerfBuffer, PerfBufferError, PerfEvent},
17    },
18    sys::bpf_map_update_elem,
19    util::page_size,
20};
21
22/// A ring buffer that can receive events from eBPF programs.
23///
24/// [`PerfEventArrayBuffer`] is a ring buffer that can receive events from eBPF
25/// programs that use `bpf_perf_event_output()`. It's returned by [`PerfEventArray::open`].
26///
27/// See the [`PerfEventArray` documentation](PerfEventArray) for an overview of how to use
28/// perf buffers.
29pub struct PerfEventArrayBuffer<T> {
30    _map: Arc<T>,
31    buf: PerfBuffer,
32}
33
34impl<T: BorrowMut<MapData>> PerfEventArrayBuffer<T> {
35    /// Returns true if the buffer contains events that haven't been read.
36    pub fn readable(&self) -> bool {
37        self.buf.readable()
38    }
39
40    /// Processes events available in the buffer with `f`.
41    ///
42    /// For each available event, `f` receives the accumulator and event:
43    /// * [`ControlFlow::Continue(next)`](ControlFlow::Continue) keeps draining with `next`.
44    /// * [`ControlFlow::Break(break_value)`](ControlFlow::Break) stops early and returns `break_value`.
45    ///
46    /// If the buffer is fully drained, returns [`ControlFlow::Continue`]
47    /// containing the final accumulator.
48    ///
49    /// The slices in [`PerfEvent::Sample`] are borrowed directly from the perf
50    /// ring buffer; the borrow is bounded by the closure invocation. The
51    /// kernel-visible `data_tail` is advanced once at the end of the call,
52    /// amortizing the `SeqCst` store across the drain.
53    pub fn try_fold<B, C, F>(&mut self, init: C, f: F) -> ControlFlow<B, C>
54    where
55        F: FnMut(C, PerfEvent<'_>) -> ControlFlow<B, C>,
56    {
57        self.buf.try_fold(init, f)
58    }
59
60    /// Processes events available in the buffer with `f`.
61    ///
62    /// For each available event, `f` receives the accumulator and event, and
63    /// returns the next accumulator. Unlike [`PerfEventArrayBuffer::try_fold`],
64    /// this function cannot short-circuit: it always processes events until the
65    /// buffer is fully drained, then returns the final accumulator.
66    pub fn fold<C, F>(&mut self, init: C, f: F) -> C
67    where
68        F: FnMut(C, PerfEvent<'_>) -> C,
69    {
70        self.buf.fold(init, f)
71    }
72
73    /// Processes events available in the buffer with `f`.
74    pub fn for_each<F>(&mut self, f: F)
75    where
76        F: FnMut(PerfEvent<'_>),
77    {
78        self.buf.for_each(f)
79    }
80}
81
82impl<T: BorrowMut<MapData>> AsFd for PerfEventArrayBuffer<T> {
83    fn as_fd(&self) -> BorrowedFd<'_> {
84        self.buf.as_fd()
85    }
86}
87
88impl<T: BorrowMut<MapData>> AsRawFd for PerfEventArrayBuffer<T> {
89    fn as_raw_fd(&self) -> RawFd {
90        self.as_fd().as_raw_fd()
91    }
92}
93
94/// A map that can be used to receive events from eBPF programs using the linux [`perf`] API.
95///
96/// Each element of a [`PerfEventArray`] is a separate [`PerfEventArrayBuffer`] which can be used
97/// to receive events sent by eBPF programs that use `bpf_perf_event_output()`.
98///
99/// To receive events you need to:
100/// * call [`PerfEventArray::open`]
101/// * poll the returned [`PerfEventArrayBuffer`] to be notified when events are
102///   inserted in the buffer
103/// * drain events with [`PerfEventArrayBuffer::for_each`] (or [`fold`]/[`try_fold`])
104///
105/// [`fold`]: PerfEventArrayBuffer::fold
106/// [`try_fold`]: PerfEventArrayBuffer::try_fold
107///
108/// # Minimum kernel version
109///
110/// The minimum kernel version required to use this feature is 4.3.
111///
112/// # Examples
113///
114/// A common way to use a perf array is to have one perf buffer for each
115/// available CPU:
116///
117/// ```no_run
118/// # use aya::maps::perf::{PerfEvent, PerfEventArrayBuffer};
119/// # use aya::maps::MapData;
120/// # use std::borrow::BorrowMut;
121/// # struct Poll<T> { _t: std::marker::PhantomData<T> };
122/// # impl<T: BorrowMut<MapData>> Poll<T> {
123/// #    fn poll_readable(&self) -> &mut [PerfEventArrayBuffer<T>] {
124/// #        &mut []
125/// #    }
126/// # }
127/// # fn poll_buffers<T: BorrowMut<MapData>>(bufs: Vec<PerfEventArrayBuffer<T>>) -> Poll<T> {
128/// #    Poll { _t: std::marker::PhantomData }
129/// # }
130/// # #[derive(thiserror::Error, Debug)]
131/// # enum Error {
132/// #    #[error(transparent)]
133/// #    IO(#[from] std::io::Error),
134/// #    #[error(transparent)]
135/// #    Map(#[from] aya::maps::MapError),
136/// #    #[error(transparent)]
137/// #    Ebpf(#[from] aya::EbpfError),
138/// #    #[error(transparent)]
139/// #    PerfBuf(#[from] aya::maps::perf::PerfBufferError),
140/// # }
141/// # let mut bpf = aya::Ebpf::load(&[])?;
142/// use aya::maps::PerfEventArray;
143/// use aya::util::online_cpus;
144///
145/// let mut perf_array = PerfEventArray::try_from(bpf.map_mut("EVENTS").unwrap())?;
146///
147/// // eBPF programs are going to write to the EVENTS perf array, using the id of the CPU they're
148/// // running on as the array index.
149/// let mut perf_buffers = Vec::new();
150/// for cpu_id in online_cpus().map_err(|(_, error)| error)? {
151///     // this perf buffer will receive events generated on the CPU with id cpu_id
152///     perf_buffers.push(perf_array.open(cpu_id, None)?);
153/// }
154///
155/// // poll the buffers to know when they have queued events
156/// let poll = poll_buffers(perf_buffers);
157/// loop {
158///     for perf_buf in poll.poll_readable() {
159///         perf_buf.for_each(|event| match event {
160///             PerfEvent::Sample { head, tail } => {
161///                 // process the sample bytes (`tail` is empty unless the sample wraps)
162///             }
163///             PerfEvent::Lost { count } => {
164///                 // record the dropped-events counter
165///             }
166///         });
167///     }
168/// }
169///
170/// # Ok::<(), Error>(())
171/// ```
172///
173/// # Polling and avoiding lost events
174///
175/// In the example above the implementation of `poll_buffers()` and `poll.poll_readable()` is not
176/// given. [`PerfEventArrayBuffer`] implements the [`AsRawFd`] trait, so you can implement polling
177/// using any crate that can poll file descriptors, like [epoll], [mio] etc.
178///
179/// Perf buffers are internally implemented as ring buffers. If your eBPF programs produce large
180/// amounts of data, in order not to lose events you might want to process each
181/// [`PerfEventArrayBuffer`] on a different thread.
182///
183/// [`perf`]: https://perf.wiki.kernel.org/index.php/Main_Page
184/// [epoll]: https://docs.rs/epoll
185/// [mio]: https://docs.rs/mio
186#[doc(alias = "BPF_MAP_TYPE_PERF_EVENT_ARRAY")]
187pub struct PerfEventArray<T> {
188    map: Arc<T>,
189    page_size: usize,
190}
191
192impl<T: Borrow<MapData>> PerfEventArray<T> {
193    #[expect(
194        clippy::unnecessary_wraps,
195        reason = "keeps constructor signatures consistent across map types"
196    )]
197    pub(crate) fn new(map: T) -> Result<Self, MapError> {
198        Ok(Self {
199            map: Arc::new(map),
200            page_size: page_size(),
201        })
202    }
203
204    /// Pins the map to a BPF filesystem.
205    ///
206    /// When a map is pinned it will remain loaded until the corresponding file
207    /// is deleted. All parent directories in the given `path` must already exist.
208    pub fn pin<P: AsRef<Path>>(&self, path: P) -> Result<(), PinError> {
209        let data: &MapData = self.map.deref().borrow();
210        data.pin(path)
211    }
212
213    pub(crate) fn map_data(&self) -> &MapData {
214        self.map.deref().borrow()
215    }
216}
217
218impl<T: BorrowMut<MapData>> PerfEventArray<T> {
219    /// Opens the perf buffer at the given index.
220    ///
221    /// The returned buffer will receive all the events eBPF programs send at the given index.
222    pub fn open(
223        &mut self,
224        index: u32,
225        page_count: Option<usize>,
226    ) -> Result<PerfEventArrayBuffer<T>, PerfBufferError> {
227        // FIXME: keep track of open buffers
228
229        let map_data: &MapData = self.map.deref().borrow();
230        let map_fd = map_data.fd().as_fd();
231        let buf = PerfBuffer::open(index, self.page_size, page_count.unwrap_or(2))?;
232        bpf_map_update_elem(map_fd, Some(&index), &buf.as_fd().as_raw_fd(), 0)?;
233
234        Ok(PerfEventArrayBuffer {
235            buf,
236            _map: Arc::clone(&self.map),
237        })
238    }
239}