use std::os::fd::AsFd as _;
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,
};
use crate::{
generated::{
bpf_link_type,
bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT,
perf_type_id::{
PERF_TYPE_BREAKPOINT, PERF_TYPE_HARDWARE, PERF_TYPE_HW_CACHE, PERF_TYPE_RAW,
PERF_TYPE_SOFTWARE, PERF_TYPE_TRACEPOINT,
},
},
programs::{
links::define_link_wrapper,
load_program, perf_attach,
perf_attach::{PerfLinkIdInner, PerfLinkInner},
FdLink, LinkError, ProgramData, ProgramError,
},
sys::{bpf_link_get_info_by_fd, perf_event_open, SyscallError},
};
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum PerfTypeId {
Hardware = PERF_TYPE_HARDWARE as u32,
Software = PERF_TYPE_SOFTWARE as u32,
TracePoint = PERF_TYPE_TRACEPOINT as u32,
HwCache = PERF_TYPE_HW_CACHE as u32,
Raw = PERF_TYPE_RAW as u32,
Breakpoint = PERF_TYPE_BREAKPOINT as u32,
}
#[derive(Debug, Clone)]
pub enum SamplePolicy {
Period(u64),
Frequency(u64),
}
#[derive(Debug, Clone)]
#[allow(clippy::enum_variant_names)]
pub enum PerfEventScope {
CallingProcessAnyCpu,
CallingProcessOneCpu {
cpu: u32,
},
OneProcessAnyCpu {
pid: u32,
},
OneProcessOneCpu {
cpu: u32,
pid: u32,
},
AllProcessesOneCpu {
cpu: u32,
},
}
#[derive(Debug)]
#[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")]
pub struct PerfEvent {
pub(crate) data: ProgramData<PerfEventLink>,
}
impl PerfEvent {
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_PERF_EVENT, &mut self.data)
}
pub fn attach(
&mut self,
perf_type: PerfTypeId,
config: u64,
scope: PerfEventScope,
sample_policy: SamplePolicy,
inherit: bool,
) -> 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,
inherit,
0,
)
.map_err(|(_code, io_error)| SyscallError {
call: "perf_event_open",
io_error,
})?;
let link = perf_attach(prog_fd, fd)?;
self.data.links.insert(PerfEventLink::new(link))
}
pub fn detach(&mut self, link_id: PerfEventLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
pub fn take_link(&mut self, link_id: PerfEventLinkId) -> Result<PerfEventLink, ProgramError> {
self.data.take_link(link_id)
}
}
impl TryFrom<PerfEventLink> for FdLink {
type Error = LinkError;
fn try_from(value: PerfEventLink) -> Result<Self, Self::Error> {
if let PerfLinkInner::FdLink(fd) = value.into_inner() {
Ok(fd)
} else {
Err(LinkError::InvalidLink)
}
}
}
impl TryFrom<FdLink> for PerfEventLink {
type Error = LinkError;
fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?;
if info.type_ == (bpf_link_type::BPF_LINK_TYPE_PERF_EVENT as u32) {
return Ok(Self::new(PerfLinkInner::FdLink(fd_link)));
}
Err(LinkError::InvalidLink)
}
}
define_link_wrapper!(
PerfEventLink,
PerfEventLinkId,
PerfLinkInner,
PerfLinkIdInner
);