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}