Skip to content

Commit

Permalink
aya+ebpf: Add integration test for reading perf_events from bpf program
Browse files Browse the repository at this point in the history
  • Loading branch information
TheElectronWill committed Oct 31, 2023
1 parent 6cba3c5 commit 352ec0e
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 42 deletions.
112 changes: 83 additions & 29 deletions aya/src/programs/perf_event.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Perf event programs.

use std::os::fd::AsFd as _;
use std::os::fd::{AsFd as _, OwnedFd};

pub use crate::generated::{
perf_hw_cache_id, perf_hw_cache_op_id, perf_hw_cache_op_result_id, perf_hw_id, perf_sw_ids,
Expand All @@ -20,7 +20,9 @@ use crate::{
perf_attach::{PerfLinkIdInner, PerfLinkInner},
FdLink, LinkError, ProgramData, ProgramError,
},
sys::{bpf_link_get_info_by_fd, perf_event_open, SyscallError},
sys::{
self, bpf_link_get_info_by_fd, SyscallError,
},
};

/// The type of perf event
Expand Down Expand Up @@ -50,6 +52,20 @@ pub enum SamplePolicy {
Frequency(u64),
}

/// Fields included in the event samples
#[derive(Debug, Clone)]
pub struct SampleType(u64);

/// "Wake up" overflow notification policy.
/// Overflows are generated only by sampling events.
#[derive(Debug, Clone)]
pub enum WakeUpPolicy {
/// Wake up after n events
WakeupEvents(u32),
/// Wake up after n bytes
WakeupWatermark(u32),
}

/// The scope of a PerfEvent
#[derive(Debug, Clone)]
#[allow(clippy::enum_variant_names)]
Expand Down Expand Up @@ -147,33 +163,11 @@ impl PerfEvent {
) -> Result<PerfEventLinkId, ProgramError> {
let prog_fd = self.fd()?;
let prog_fd = prog_fd.as_fd();
let (sample_period, sample_frequency) = match sample_policy {
SamplePolicy::Period(period) => (period, None),
SamplePolicy::Frequency(frequency) => (0, Some(frequency)),
};
let (pid, cpu) = match scope {
PerfEventScope::CallingProcessAnyCpu => (0, -1),
PerfEventScope::CallingProcessOneCpu { cpu } => (0, cpu as i32),
PerfEventScope::OneProcessAnyCpu { pid } => (pid as i32, -1),
PerfEventScope::OneProcessOneCpu { cpu, pid } => (pid as i32, cpu as i32),
PerfEventScope::AllProcessesOneCpu { cpu } => (-1, cpu as i32),
};
let fd = perf_event_open(
perf_type as u32,
config,
pid,
cpu,
sample_period,
sample_frequency,
false,
0,
)
.map_err(|(_code, io_error)| SyscallError {
call: "perf_event_open",
io_error,
})?;

let link = perf_attach(prog_fd, fd)?;

let sampling = Some((sample_policy, SampleType(PERF_TYPE_RAW as u64)));
let event_fd = perf_event_open(perf_type as u32, config, scope, sampling, None, 0)?;

let link = perf_attach(prog_fd, event_fd)?;
self.data.links.insert(PerfEventLink::new(link))
}

Expand Down Expand Up @@ -225,3 +219,63 @@ define_link_wrapper!(
PerfLinkInner,
PerfLinkIdInner
);

/// Performs a call to `perf_event_open` and returns the event's file descriptor.
///
/// # Arguments
///
/// * `perf_type` - the type of event, see [`crate::generated::perf_type_id`] for a list of types. Note that this list is non-exhaustive, because PMUs (Performance Monitoring Units) can be added to the system. Their ids can be read from the sysfs (see the kernel documentation on perf_event_open).
/// * `config` - the event that we want to open
/// * `scope` - which process and cpu to monitor (logical cpu, not physical socket)
/// * `sampling` - if not None, enables the sampling mode with the given parameters
/// * `wakeup` - if not None, sets up the wake-up for the overflow notifications
/// * `flags` - various flags combined with a binary OR (for ex. `FLAG_A | FLAG_B`), zero means no flag
pub fn perf_event_open(
perf_type: u32,
config: u64,
scope: PerfEventScope,
sampling: Option<(SamplePolicy, SampleType)>,
wakeup: Option<WakeUpPolicy>,
flags: u32,
) -> Result<OwnedFd, SyscallError> {
let mut attr = sys::init_perf_event_attr();

// Fill in the attributes
attr.type_ = perf_type;
attr.config = config;
match sampling {
Some((SamplePolicy::Frequency(f), SampleType(t))) => {
attr.set_freq(1);
attr.__bindgen_anon_1.sample_freq = f;
attr.sample_type = t;
}
Some((SamplePolicy::Period(p), SampleType(t))) => {
attr.__bindgen_anon_1.sample_period = p;
attr.sample_type = t;
}
None => (),
};
match wakeup {
Some(WakeUpPolicy::WakeupEvents(n)) => {
attr.__bindgen_anon_2.wakeup_events = n;
}
Some(WakeUpPolicy::WakeupWatermark(n)) => {
attr.set_watermark(1);
attr.__bindgen_anon_2.wakeup_watermark = n;
}
None => (),
};

let (pid, cpu) = match scope {
PerfEventScope::CallingProcessAnyCpu => (0, -1),
PerfEventScope::CallingProcessOneCpu { cpu } => (0, cpu as i32),
PerfEventScope::OneProcessAnyCpu { pid } => (pid as i32, -1),
PerfEventScope::OneProcessOneCpu { cpu, pid } => (pid as i32, cpu as i32),
PerfEventScope::AllProcessesOneCpu { cpu } => (-1, cpu as i32),
};

sys::perf_event_sys(attr, pid, cpu, flags).map_err(|(_, io_error)| SyscallError {
call: "perf_event_open",
io_error,
})
}
23 changes: 12 additions & 11 deletions aya/src/sys/perf_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ use crate::generated::{
PERF_FLAG_FD_CLOEXEC,
};

pub(crate) fn init_perf_event_attr() -> perf_event_attr {
let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };
attr.size = mem::size_of::<perf_event_attr>() as u32;
attr
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn perf_event_open(
pub(crate) fn perf_event_open_sampled(
perf_type: u32,
config: u64,
pid: pid_t,
Expand All @@ -26,10 +32,8 @@ pub(crate) fn perf_event_open(
wakeup: bool,
flags: u32,
) -> SysResult<OwnedFd> {
let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };

let mut attr = init_perf_event_attr();
attr.config = config;
attr.size = mem::size_of::<perf_event_attr>() as u32;
attr.type_ = perf_type;
attr.sample_type = PERF_SAMPLE_RAW as u64;
// attr.inherits = if pid > 0 { 1 } else { 0 };
Expand All @@ -46,7 +50,7 @@ pub(crate) fn perf_event_open(
}

pub(crate) fn perf_event_open_bpf(cpu: c_int) -> SysResult<OwnedFd> {
perf_event_open(
perf_event_open_sampled(
PERF_TYPE_SOFTWARE as u32,
PERF_COUNT_SW_BPF_OUTPUT as u64,
-1,
Expand All @@ -67,15 +71,14 @@ pub(crate) fn perf_event_open_probe(
) -> SysResult<OwnedFd> {
use std::os::unix::ffi::OsStrExt as _;

let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };
let mut attr = init_perf_event_attr();

if let Some(ret_bit) = ret_bit {
attr.config = 1 << ret_bit;
}

let c_name = CString::new(name.as_bytes()).unwrap();

attr.size = mem::size_of::<perf_event_attr>() as u32;
attr.type_ = ty;
attr.__bindgen_anon_3.config1 = c_name.as_ptr() as u64;
attr.__bindgen_anon_4.config2 = offset;
Expand All @@ -87,9 +90,7 @@ pub(crate) fn perf_event_open_probe(
}

pub(crate) fn perf_event_open_trace_point(id: u32, pid: Option<pid_t>) -> SysResult<OwnedFd> {
let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };

attr.size = mem::size_of::<perf_event_attr>() as u32;
let mut attr = init_perf_event_attr();
attr.type_ = PERF_TYPE_TRACEPOINT as u32;
attr.config = id as u64;

Expand All @@ -112,7 +113,7 @@ pub(crate) fn perf_event_ioctl(
return crate::sys::TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });
}

fn perf_event_sys(attr: perf_event_attr, pid: pid_t, cpu: i32, flags: u32) -> SysResult<OwnedFd> {
pub(crate) fn perf_event_sys(attr: perf_event_attr, pid: pid_t, cpu: i32, flags: u32) -> SysResult<OwnedFd> {
let fd = syscall(Syscall::PerfEventOpen {
attr,
pid,
Expand Down
4 changes: 2 additions & 2 deletions bpf/aya-bpf/src/maps/perf/perf_event_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
/// # Minimum kernel version
///
/// The minimum kernel version required to read perf_event values using [PerfEventArray] is 4.15.
/// This concerns the functions [`read_current_cpu()`], [`read_at_index()`] and [`read()`].
/// This concerns the functions [`PerfEventArray::read_current_cpu()`] and [`PerfEventArray::read_at_index()`].
///
#[repr(transparent)]
pub struct PerfEventArray<T> {
Expand Down Expand Up @@ -68,7 +68,7 @@ impl<T> PerfEventArray<T> {
}

pub fn output_at_index<C: BpfContext>(&self, ctx: &C, data: &T, index: u32) -> Result<(), i64> {
self.output(ctx, data, (index as u64) & BPF_F_INDEX_MASK)
self.output(ctx, data, u64::from(index) & BPF_F_INDEX_MASK)
}

fn output<C: BpfContext>(&self, ctx: &C, data: &T, flags: u64) -> Result<(), i64> {
Expand Down
4 changes: 4 additions & 0 deletions test/integration-ebpf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@ path = "src/xdp_sec.rs"
[[bin]]
name = "ring_buf"
path = "src/ring_buf.rs"

[[bin]]
name = "perf_events"
path = "src/perf_events.rs"
56 changes: 56 additions & 0 deletions test/integration-ebpf/src/perf_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#![no_std]
#![no_main]

use aya_bpf::{
bindings::bpf_perf_event_value,
helpers::bpf_get_smp_processor_id,
macros::{map, perf_event},
maps::PerfEventArray,
programs::PerfEventContext,
};

#[repr(C)]
struct EventData {
value: u64,
cpu_id: u32,
tag: u8,
}

/// Input map: file descriptors of the perf events, obtained by calling
/// `perf_event_open` in user space.
#[map]
static mut DESCRIPTORS: PerfEventArray<i32> = PerfEventArray::with_max_entries(1, 0);

#[map]
static mut OUTPUT: PerfEventArray<EventData> = PerfEventArray::with_max_entries(1, 0);

#[perf_event]
pub fn on_perf_event(ctx: PerfEventContext) -> i64 {
match read_event(&ctx).map(|res| write_output(&ctx, res)) {
Ok(_) => 0,
Err(e) => e,
}
}

fn read_event(ctx: &PerfEventContext) -> Result<EventData, i64> {
// read the event value using the file descriptor in the DESCRIPTORS array
let event: bpf_perf_event_value = unsafe { DESCRIPTORS.read_current_cpu() }?;

let cpu_id = unsafe { bpf_get_smp_processor_id() };
let res = EventData {
value: event.counter,
cpu_id,
tag: 0xAB,
};
Ok(res)
}

fn write_output(ctx: &PerfEventContext, output: EventData) -> Result<(), i64> {
unsafe { OUTPUT.output_current_cpu(ctx, &output) }
}

#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
1 change: 1 addition & 0 deletions test/integration-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ assert_matches = { workspace = true }
aya = { workspace = true }
aya-log = { workspace = true }
aya-obj = { workspace = true }
bytes = { workspace = true }
env_logger = { workspace = true }
epoll = { workspace = true }
futures = { workspace = true, features = ["std"] }
Expand Down
1 change: 1 addition & 0 deletions test/integration-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const BPF_PROBE_READ: &[u8] =
pub const REDIRECT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/redirect"));
pub const XDP_SEC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/xdp_sec"));
pub const RING_BUF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ring_buf"));
pub const PERF_EVENTS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/perf_events"));

#[cfg(test)]
mod tests;
Expand Down
1 change: 1 addition & 0 deletions test/integration-test/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod btf_relocations;
mod elf;
mod load;
mod log;
mod perf_events;
mod rbpf;
mod relocations;
mod ring_buf;
Expand Down
Loading

0 comments on commit 352ec0e

Please sign in to comment.