blob: 490683a4ba5220eabfcea3af446aea4dbcf8535b [file] [log] [blame] [edit]
use core::ffi::c_void;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::os::unix::io::AsFd;
use std::os::unix::prelude::AsRawFd;
use std::ptr;
use std::ptr::NonNull;
use std::slice;
use std::time::Duration;
use crate::util;
use crate::util::validate_bpf_ret;
use crate::AsRawLibbpf;
use crate::Error;
use crate::ErrorExt as _;
use crate::Map;
use crate::MapCore as _;
use crate::MapType;
use crate::Result;
// Workaround for `trait_alias`
// (https://doc.rust-lang.org/unstable-book/language-features/trait-alias.html)
// not being available yet. This is just a custom trait plus a blanket implementation.
pub trait SampleCb: FnMut(i32, &[u8]) {}
impl<T> SampleCb for T where T: FnMut(i32, &[u8]) {}
pub trait LostCb: FnMut(i32, u64) {}
impl<T> LostCb for T where T: FnMut(i32, u64) {}
struct CbStruct<'b> {
sample_cb: Option<Box<dyn SampleCb + 'b>>,
lost_cb: Option<Box<dyn LostCb + 'b>>,
}
impl Debug for CbStruct<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let Self { sample_cb, lost_cb } = self;
f.debug_struct("CbStruct")
.field("sample_cb", &sample_cb.as_ref().map(|cb| &cb as *const _))
.field("lost_cb", &lost_cb.as_ref().map(|cb| &cb as *const _))
.finish()
}
}
/// Builds [`PerfBuffer`] instances.
pub struct PerfBufferBuilder<'a, 'b> {
map: &'a Map<'a>,
pages: usize,
sample_cb: Option<Box<dyn SampleCb + 'b>>,
lost_cb: Option<Box<dyn LostCb + 'b>>,
}
impl<'a> PerfBufferBuilder<'a, '_> {
/// Create a new `PerfBufferBuilder` using the provided `Map`.
pub fn new(map: &'a Map<'a>) -> Self {
Self {
map,
pages: 64,
sample_cb: None,
lost_cb: None,
}
}
}
impl<'a, 'b> PerfBufferBuilder<'a, 'b> {
/// Callback to run when a sample is received.
///
/// This callback provides a raw byte slice. You may find libraries such as
/// [`plain`](https://crates.io/crates/plain) helpful.
///
/// Callback arguments are: `(cpu, data)`.
pub fn sample_cb<NewCb: SampleCb + 'b>(self, cb: NewCb) -> PerfBufferBuilder<'a, 'b> {
PerfBufferBuilder {
map: self.map,
pages: self.pages,
sample_cb: Some(Box::new(cb)),
lost_cb: self.lost_cb,
}
}
/// Callback to run when a sample is received.
///
/// Callback arguments are: `(cpu, lost_count)`.
pub fn lost_cb<NewCb: LostCb + 'b>(self, cb: NewCb) -> PerfBufferBuilder<'a, 'b> {
PerfBufferBuilder {
map: self.map,
pages: self.pages,
sample_cb: self.sample_cb,
lost_cb: Some(Box::new(cb)),
}
}
/// The number of pages to size the ring buffer.
pub fn pages(self, pages: usize) -> PerfBufferBuilder<'a, 'b> {
PerfBufferBuilder {
map: self.map,
pages,
sample_cb: self.sample_cb,
lost_cb: self.lost_cb,
}
}
/// Build the `PerfBuffer` object as configured.
pub fn build(self) -> Result<PerfBuffer<'b>> {
if self.map.map_type() != MapType::PerfEventArray {
return Err(Error::with_invalid_data("Must use a PerfEventArray map"));
}
if !self.pages.is_power_of_two() {
return Err(Error::with_invalid_data("Page count must be power of two"));
}
let c_sample_cb: libbpf_sys::perf_buffer_sample_fn = if self.sample_cb.is_some() {
Some(Self::call_sample_cb)
} else {
None
};
let c_lost_cb: libbpf_sys::perf_buffer_lost_fn = if self.lost_cb.is_some() {
Some(Self::call_lost_cb)
} else {
None
};
let callback_struct_ptr = Box::into_raw(Box::new(CbStruct {
sample_cb: self.sample_cb,
lost_cb: self.lost_cb,
}));
let ptr = unsafe {
libbpf_sys::perf_buffer__new(
self.map.as_fd().as_raw_fd(),
self.pages as libbpf_sys::size_t,
c_sample_cb,
c_lost_cb,
callback_struct_ptr as *mut _,
ptr::null(),
)
};
let ptr = validate_bpf_ret(ptr).context("failed to create perf buffer")?;
let pb = PerfBuffer {
ptr,
_cb_struct: unsafe { Box::from_raw(callback_struct_ptr) },
};
Ok(pb)
}
unsafe extern "C" fn call_sample_cb(ctx: *mut c_void, cpu: i32, data: *mut c_void, size: u32) {
let callback_struct = ctx as *mut CbStruct<'_>;
if let Some(cb) = unsafe { &mut (*callback_struct).sample_cb } {
let slice = unsafe { slice::from_raw_parts(data as *const u8, size as usize) };
cb(cpu, slice);
}
}
unsafe extern "C" fn call_lost_cb(ctx: *mut c_void, cpu: i32, count: u64) {
let callback_struct = ctx as *mut CbStruct<'_>;
if let Some(cb) = unsafe { &mut (*callback_struct).lost_cb } {
cb(cpu, count);
}
}
}
impl Debug for PerfBufferBuilder<'_, '_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let Self {
map,
pages,
sample_cb,
lost_cb,
} = self;
f.debug_struct("PerfBufferBuilder")
.field("map", map)
.field("pages", pages)
.field("sample_cb", &sample_cb.as_ref().map(|cb| &cb as *const _))
.field("lost_cb", &lost_cb.as_ref().map(|cb| &cb as *const _))
.finish()
}
}
/// Represents a special kind of [`Map`]. Typically used to transfer data between
/// [`Program`][crate::Program]s and userspace.
#[derive(Debug)]
pub struct PerfBuffer<'b> {
ptr: NonNull<libbpf_sys::perf_buffer>,
// Hold onto the box so it'll get dropped when PerfBuffer is dropped
_cb_struct: Box<CbStruct<'b>>,
}
// TODO: Document methods.
#[allow(missing_docs)]
impl PerfBuffer<'_> {
pub fn epoll_fd(&self) -> i32 {
unsafe { libbpf_sys::perf_buffer__epoll_fd(self.ptr.as_ptr()) }
}
pub fn poll(&self, timeout: Duration) -> Result<()> {
let ret =
unsafe { libbpf_sys::perf_buffer__poll(self.ptr.as_ptr(), timeout.as_millis() as i32) };
util::parse_ret(ret)
}
pub fn consume(&self) -> Result<()> {
let ret = unsafe { libbpf_sys::perf_buffer__consume(self.ptr.as_ptr()) };
util::parse_ret(ret)
}
pub fn consume_buffer(&self, buf_idx: usize) -> Result<()> {
let ret = unsafe {
libbpf_sys::perf_buffer__consume_buffer(
self.ptr.as_ptr(),
buf_idx as libbpf_sys::size_t,
)
};
util::parse_ret(ret)
}
pub fn buffer_cnt(&self) -> usize {
unsafe { libbpf_sys::perf_buffer__buffer_cnt(self.ptr.as_ptr()) as usize }
}
pub fn buffer_fd(&self, buf_idx: usize) -> Result<i32> {
let ret = unsafe {
libbpf_sys::perf_buffer__buffer_fd(self.ptr.as_ptr(), buf_idx as libbpf_sys::size_t)
};
util::parse_ret_i32(ret)
}
}
impl AsRawLibbpf for PerfBuffer<'_> {
type LibbpfType = libbpf_sys::perf_buffer;
/// Retrieve the underlying [`libbpf_sys::perf_buffer`].
fn as_libbpf_object(&self) -> NonNull<Self::LibbpfType> {
self.ptr
}
}
// SAFETY: `perf_buffer` objects can safely be polled from any thread.
unsafe impl Send for PerfBuffer<'_> {}
impl Drop for PerfBuffer<'_> {
fn drop(&mut self) {
unsafe {
libbpf_sys::perf_buffer__free(self.ptr.as_ptr());
}
}
}
#[cfg(test)]
mod test {
use super::*;
/// Check that `PerfBuffer` is `Send`.
#[test]
fn perfbuffer_is_send() {
fn test<T>()
where
T: Send,
{
}
test::<PerfBuffer<'_>>();
}
}