Skip to content

Commit

Permalink
aya: Implement RingBuf
Browse files Browse the repository at this point in the history
This implements the userspace binding for RingBuf.

Instead of streaming the samples as heap buffers, the process_ring
function takes a callback to which we pass the event's byte region,
roughly following [libbpf]'s API design. This avoids a copy and allows
marking the consumer pointer in a timely manner.

[libbpf]: https://github.com/libbpf/libbpf/blob/master/src/ringbuf.c

Additionally, integration tests are added to demonstrate the usage
of the new APIs and to ensure that they work end-to-end.

Co-authored-by: William Findlay <william@williamfindlay.com>
Co-authored-by: Tatsuyuki Ishi <ishitatsuyuki@gmail.com>
  • Loading branch information
3 people committed Sep 28, 2023
1 parent ead7066 commit b3ebefb
Show file tree
Hide file tree
Showing 16 changed files with 1,404 additions and 21 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Expand Up @@ -62,10 +62,13 @@ bitflags = { version = "2.2.1", default-features = false }
bytes = { version = "1", default-features = false }
cargo_metadata = { version = "0.18.0", default-features = false }
clap = { version = "4", default-features = false }
const-assert = { version = "1.0.1", default-features = false }
core-error = { version = "0.0.0", default-features = false }
dialoguer = { version = "0.11", default-features = false }
diff = { version = "0.1.13", default-features = false }
env_logger = { version = "0.10", default-features = false }
epoll = { version = "4.3.3", default-features = false }
futures = { version = "0.3.28", default-features = false }
hashbrown = { version = "0.14", default-features = false }
indoc = { version = "2.0", default-features = false }
integration-ebpf = { path = "test/integration-ebpf", default-features = false }
Expand All @@ -80,6 +83,7 @@ proc-macro-error = { version = "1.0", default-features = false }
proc-macro2 = { version = "1", default-features = false }
public-api = { version = "0.32.0", default-features = false }
quote = { version = "1", default-features = false }
rand = { version = "0.8", default-features = false }
rbpf = { version = "0.2.0", default-features = false }
rustdoc-json = { version = "0.8.6", default-features = false }
rustup-toolchain = { version = "0.1.5", default-features = false }
Expand Down
136 changes: 118 additions & 18 deletions aya/src/bpf.rs
Expand Up @@ -43,7 +43,7 @@ use crate::{
is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
retry_with_verifier_logs,
},
util::{bytes_of, bytes_of_slice, possible_cpus, POSSIBLE_CPUS},
util::{bytes_of, bytes_of_slice, page_size, possible_cpus, POSSIBLE_CPUS},
};

pub(crate) const BPF_OBJ_NAME_LEN: usize = 16;
Expand Down Expand Up @@ -461,23 +461,23 @@ impl<'a> BpfLoader<'a> {
{
continue;
}

match max_entries.get(name.as_str()) {
Some(size) => obj.set_max_entries(*size),
None => {
if obj.map_type() == BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32
&& obj.max_entries() == 0
{
obj.set_max_entries(
possible_cpus()
.map_err(|error| BpfError::FileError {
path: PathBuf::from(POSSIBLE_CPUS),
error,
})?
.len() as u32,
);
}
}
let num_cpus = || -> Result<u32, BpfError> {
Ok(possible_cpus()
.map_err(|error| BpfError::FileError {
path: PathBuf::from(POSSIBLE_CPUS),
error,
})?
.len() as u32)
};
let map_type: bpf_map_type = obj.map_type().try_into().map_err(MapError::from)?;
if let Some(max_entries) = max_entries_override(
map_type,
max_entries.get(name.as_str()).copied(),
|| obj.max_entries(),
num_cpus,
|| page_size() as u32,
)? {
obj.set_max_entries(max_entries)
}
match obj.map_type().try_into() {
Ok(BPF_MAP_TYPE_CPUMAP) => {
Expand Down Expand Up @@ -716,6 +716,7 @@ fn parse_map(data: (String, MapData)) -> Result<(String, Map), BpfError> {
BPF_MAP_TYPE_PERCPU_HASH => Map::PerCpuHashMap(map),
BPF_MAP_TYPE_LRU_PERCPU_HASH => Map::PerCpuLruHashMap(map),
BPF_MAP_TYPE_PERF_EVENT_ARRAY => Map::PerfEventArray(map),
BPF_MAP_TYPE_RINGBUF => Map::RingBuf(map),
BPF_MAP_TYPE_SOCKHASH => Map::SockHash(map),
BPF_MAP_TYPE_SOCKMAP => Map::SockMap(map),
BPF_MAP_TYPE_BLOOM_FILTER => Map::BloomFilter(map),
Expand All @@ -736,6 +737,105 @@ fn parse_map(data: (String, MapData)) -> Result<(String, Map), BpfError> {
Ok((name, map))
}

/// Computes the value which should be used to override the max_entries value of the map
/// based on the user-provided override and the rules for that map type.
fn max_entries_override(
map_type: bpf_map_type,
user_override: Option<u32>,
current_value: impl Fn() -> u32,
num_cpus: impl Fn() -> Result<u32, BpfError>,
page_size: impl Fn() -> u32,
) -> Result<Option<u32>, BpfError> {
let max_entries = || user_override.unwrap_or_else(&current_value);
Ok(match map_type {
BPF_MAP_TYPE_PERF_EVENT_ARRAY if max_entries() == 0 => Some(num_cpus()?),
BPF_MAP_TYPE_RINGBUF => Some(adjust_to_page_size(max_entries(), page_size()))
.filter(|adjusted| *adjusted != max_entries())
.or(user_override),
_ => user_override,
})
}

// Adjusts the byte size of a RingBuf map to match a power-of-two multiple of the page size.
//
// This mirrors the logic used by libbpf.
// See https://github.com/libbpf/libbpf/blob/ec6f716eda43/src/libbpf.c#L2461-L2463
fn adjust_to_page_size(byte_size: u32, page_size: u32) -> u32 {
// If the byte_size is zero, return zero and let the verifier reject the map
// when it is loaded. This is the behavior of libbpf.
if byte_size == 0 {
return 0;
}
// TODO: Replace with primitive method when int_roundings (https://github.com/rust-lang/rust/issues/88581)
// is stabilized.
fn div_ceil(n: u32, rhs: u32) -> u32 {
let d = n / rhs;
let r = n % rhs;
if r > 0 && rhs > 0 {
d + 1
} else {
d
}
}
let pages_needed = div_ceil(byte_size, page_size);
page_size * pages_needed.next_power_of_two()
}

#[cfg(test)]
mod tests {
use crate::generated::bpf_map_type::*;

const PAGE_SIZE: u32 = 4096;
const NUM_CPUS: u32 = 4;

#[test]
fn test_adjust_to_page_size() {
use super::adjust_to_page_size;
[
(0, 0),
(4096, 1),
(4096, 4095),
(4096, 4096),
(8192, 4097),
(8192, 8192),
(16384, 8193),
]
.into_iter()
.for_each(|(exp, input)| assert_eq!(exp, adjust_to_page_size(input, PAGE_SIZE)))
}

#[test]
fn test_max_entries_override() {
use super::max_entries_override;
[
(BPF_MAP_TYPE_RINGBUF, Some(1), 1, Some(PAGE_SIZE)),
(BPF_MAP_TYPE_RINGBUF, None, 1, Some(PAGE_SIZE)),
(BPF_MAP_TYPE_RINGBUF, None, PAGE_SIZE, None),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 1, None),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, Some(42), 1, Some(42)),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, Some(0), 1, Some(NUM_CPUS)),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 0, Some(NUM_CPUS)),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 42, None),
(BPF_MAP_TYPE_ARRAY, None, 1, None),
(BPF_MAP_TYPE_ARRAY, Some(2), 1, Some(2)),
]
.into_iter()
.for_each(|(map_type, user_override, current_value, exp)| {
assert_eq!(
exp,
max_entries_override(
map_type,
user_override,
|| { current_value },
|| Ok(NUM_CPUS),
|| PAGE_SIZE
)
.unwrap()
)
})
}
}

impl Default for BpfLoader<'_> {
fn default() -> Self {
BpfLoader::new()
Expand Down
8 changes: 7 additions & 1 deletion aya/src/maps/mod.rs
Expand Up @@ -80,6 +80,7 @@ pub mod hash_map;
pub mod lpm_trie;
pub mod perf;
pub mod queue;
pub mod ring_buf;
pub mod sock;
pub mod stack;
pub mod stack_trace;
Expand All @@ -94,6 +95,7 @@ pub use lpm_trie::LpmTrie;
pub use perf::AsyncPerfEventArray;
pub use perf::PerfEventArray;
pub use queue::Queue;
pub use ring_buf::RingBuf;
pub use sock::{SockHash, SockMap};
pub use stack::Stack;
pub use stack_trace::StackTraceMap;
Expand Down Expand Up @@ -279,7 +281,9 @@ pub enum Map {
ProgramArray(MapData),
/// A [`Queue`] map.
Queue(MapData),
/// A [`SockHash`] map.
/// A [`RingBuf`] map
RingBuf(MapData),
/// A [`SockHash`] map
SockHash(MapData),
/// A [`SockMap`] map.
SockMap(MapData),
Expand Down Expand Up @@ -311,6 +315,7 @@ impl Map {
Self::PerfEventArray(map) => map.obj.map_type(),
Self::ProgramArray(map) => map.obj.map_type(),
Self::Queue(map) => map.obj.map_type(),
Self::RingBuf(map) => map.obj.map_type(),
Self::SockHash(map) => map.obj.map_type(),
Self::SockMap(map) => map.obj.map_type(),
Self::Stack(map) => map.obj.map_type(),
Expand Down Expand Up @@ -376,6 +381,7 @@ impl_try_from_map!(() {
DevMapHash,
PerfEventArray,
ProgramArray,
RingBuf,
SockMap,
StackTraceMap,
XskMap,
Expand Down

0 comments on commit b3ebefb

Please sign in to comment.