Ver Fonte

Add support for PerfEvent programs.

Markus Stange há 3 anos atrás
pai
commit
c39dff6025

+ 4 - 3
aya/src/bpf.rs

@@ -19,9 +19,9 @@ use crate::{
         Object, ParseError, ProgramSection,
     },
     programs::{
-        CgroupSkb, CgroupSkbAttachType, KProbe, LircMode2, ProbeKind, Program, ProgramData,
-        ProgramError, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint,
-        UProbe, Xdp,
+        CgroupSkb, CgroupSkbAttachType, KProbe, LircMode2, PerfEvent, ProbeKind, Program,
+        ProgramData, ProgramError, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter,
+        TracePoint, UProbe, Xdp,
     },
     sys::bpf_map_update_elem_ptr,
     util::{possible_cpus, POSSIBLE_CPUS},
@@ -202,6 +202,7 @@ impl Bpf {
                         expected_attach_type: Some(CgroupSkbAttachType::Egress),
                     }),
                     ProgramSection::LircMode2 { .. } => Program::LircMode2(LircMode2 { data }),
+                    ProgramSection::PerfEvent { .. } => Program::PerfEvent(PerfEvent { data }),
                 };
 
                 (name, program)

+ 2 - 2
aya/src/maps/perf/perf_buffer.rs

@@ -15,7 +15,7 @@ use crate::{
         perf_event_header, perf_event_mmap_page,
         perf_event_type::{PERF_RECORD_LOST, PERF_RECORD_SAMPLE},
     },
-    sys::{perf_event_ioctl, perf_event_open},
+    sys::{perf_event_ioctl, perf_event_open_bpf},
     PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE,
 };
 
@@ -87,7 +87,7 @@ impl PerfBuffer {
             return Err(PerfBufferError::InvalidPageCount { page_count });
         }
 
-        let fd = perf_event_open(cpu_id as i32)
+        let fd = perf_event_open_bpf(cpu_id as i32)
             .map_err(|(_, io_error)| PerfBufferError::OpenError { io_error })?
             as RawFd;
         let size = page_size * page_count;

+ 4 - 0
aya/src/obj/mod.rs

@@ -84,6 +84,7 @@ pub enum ProgramSection {
     CgroupSkbIngress { name: String },
     CgroupSkbEgress { name: String },
     LircMode2 { name: String },
+    PerfEvent { name: String },
 }
 
 impl ProgramSection {
@@ -104,6 +105,7 @@ impl ProgramSection {
             ProgramSection::CgroupSkbIngress { name } => name,
             ProgramSection::CgroupSkbEgress { name } => name,
             ProgramSection::LircMode2 { name } => name,
+            ProgramSection::PerfEvent { name } => name,
         }
     }
 }
@@ -144,6 +146,7 @@ impl FromStr for ProgramSection {
             "cgroup_skb/ingress" => CgroupSkbIngress { name },
             "cgroup_skb/egress" => CgroupSkbEgress { name },
             "lirc_mode2" => LircMode2 { name },
+            "perf_event" => PerfEvent { name },
             _ => {
                 return Err(ParseError::InvalidProgramSection {
                     section: section.to_owned(),
@@ -556,6 +559,7 @@ fn is_program_section(name: &str) -> bool {
         "kprobe",
         "kretprobe",
         "lirc_mode2",
+        "perf_event",
         "sk_msg",
         "sk_skb/stream_parser",
         "sk_skb/stream_verdict",

+ 10 - 2
aya/src/programs/mod.rs

@@ -40,6 +40,7 @@ mod cgroup_skb;
 mod kprobe;
 mod lirc_mode2;
 mod perf_attach;
+mod perf_event;
 mod probe;
 mod sk_msg;
 mod sk_skb;
@@ -66,6 +67,7 @@ pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType};
 pub use kprobe::{KProbe, KProbeError};
 pub use lirc_mode2::LircMode2;
 use perf_attach::*;
+pub use perf_event::{PerfEvent, PerfEventScope, SamplePolicy};
 pub use probe::ProbeKind;
 pub use sk_msg::SkMsg;
 pub use sk_skb::{SkSkb, SkSkbKind};
@@ -181,6 +183,7 @@ pub enum Program {
     SchedClassifier(SchedClassifier),
     CgroupSkb(CgroupSkb),
     LircMode2(LircMode2),
+    PerfEvent(PerfEvent),
 }
 
 impl Program {
@@ -213,6 +216,7 @@ impl Program {
             Program::SchedClassifier(_) => BPF_PROG_TYPE_SCHED_CLS,
             Program::CgroupSkb(_) => BPF_PROG_TYPE_CGROUP_SKB,
             Program::LircMode2(_) => BPF_PROG_TYPE_LIRC_MODE2,
+            Program::PerfEvent(_) => BPF_PROG_TYPE_PERF_EVENT,
         }
     }
 
@@ -234,6 +238,7 @@ impl Program {
             Program::SchedClassifier(p) => &p.data,
             Program::CgroupSkb(p) => &p.data,
             Program::LircMode2(p) => &p.data,
+            Program::PerfEvent(p) => &p.data,
         }
     }
 
@@ -250,6 +255,7 @@ impl Program {
             Program::SchedClassifier(p) => &mut p.data,
             Program::CgroupSkb(p) => &mut p.data,
             Program::LircMode2(p) => &mut p.data,
+            Program::PerfEvent(p) => &mut p.data,
         }
     }
 }
@@ -535,7 +541,8 @@ impl_program_fd!(
     SkSkb,
     SchedClassifier,
     CgroupSkb,
-    LircMode2
+    LircMode2,
+    PerfEvent
 );
 
 macro_rules! impl_try_from_program {
@@ -577,7 +584,8 @@ impl_try_from_program!(
     SockOps,
     SchedClassifier,
     CgroupSkb,
-    LircMode2
+    LircMode2,
+    PerfEvent
 );
 
 /// Provides information about a loaded program, like name, id and statistics

+ 115 - 0
aya/src/programs/perf_event.rs

@@ -0,0 +1,115 @@
+use crate::{generated::bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT, sys::perf_event_open};
+
+pub use crate::generated::perf_type_id;
+
+use super::{load_program, perf_attach, LinkRef, ProgramData, ProgramError};
+
+#[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 },
+}
+
+/// A program that can be attached at a perf event.
+///
+/// TODO: Explain the different types of perf events and how to get a list.
+/// Maybe just link to the man page of `perf list`.
+/// (But it's not clear to me how to translate those strings into numbers.)
+///
+/// # Minimum kernel version
+///
+/// TODO: minimum kernel version?
+///
+/// # Examples
+///
+/// ```no_run
+/// # #[derive(Debug, thiserror::Error)]
+/// # enum Error {
+/// #     #[error(transparent)]
+/// #     IO(#[from] std::io::Error),
+/// #     #[error(transparent)]
+/// #     Map(#[from] aya::maps::MapError),
+/// #     #[error(transparent)]
+/// #     Program(#[from] aya::programs::ProgramError),
+/// #     #[error(transparent)]
+/// #     Bpf(#[from] aya::BpfError)
+/// # }
+/// # let mut bpf = aya::Bpf::load(&[], None)?;
+/// use std::convert::TryInto;
+/// use aya::programs::{PerfEvent, PerfEventScope, SamplePolicy };
+/// use aya::util::online_cpus;
+///
+/// let prog: &mut PerfEvent = bpf.program_mut("observe_cpu_clock")?.try_into()?;
+/// prog.load()?;
+///
+/// for cpu in online_cpus()? {
+///     prog.attach(
+///         1, /* PERF_TYPE_SOFTWARE */
+///         0, /* PERF_COUNT_SW_CPU_CLOCK */
+///         PerfEventScope::AllProcessesOneCpu { cpu },
+///         SamplePolicy::Period(1000000),
+///     )?;
+/// }
+/// # Ok::<(), Error>(())
+/// ```
+#[derive(Debug)]
+#[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")]
+pub struct PerfEvent {
+    pub(crate) data: ProgramData,
+}
+
+impl PerfEvent {
+    /// Loads the program inside the kernel.
+    ///
+    /// See also [`Program::load`](crate::programs::Program::load).
+    pub fn load(&mut self) -> Result<(), ProgramError> {
+        load_program(BPF_PROG_TYPE_PERF_EVENT, &mut self.data)
+    }
+
+    /// Attaches to a given perf event.
+    pub fn attach(
+        &mut self,
+        perf_type: u32, // perf_type_id
+        config: u64,
+        scope: PerfEventScope,
+        sample_policy: SamplePolicy,
+    ) -> Result<LinkRef, ProgramError> {
+        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,
+            config,
+            pid,
+            cpu,
+            sample_period,
+            sample_frequency,
+            false,
+            0,
+        )
+        .map_err(|(_code, io_error)| ProgramError::SyscallError {
+            call: "perf_event_open".to_owned(),
+            io_error,
+        })? as i32;
+
+        perf_attach(&mut self.data, fd)
+    }
+}

+ 37 - 7
aya/src/sys/perf_event.rs

@@ -12,25 +12,55 @@ use crate::generated::{
 
 use super::{syscall, SysResult, Syscall};
 
-pub(crate) fn perf_event_open(cpu: c_int) -> SysResult {
+#[allow(clippy::too_many_arguments)]
+pub(crate) fn perf_event_open(
+    perf_type: u32,
+    config: u64,
+    pid: pid_t,
+    cpu: c_int,
+    sample_period: u64,
+    sample_frequency: Option<u64>,
+    wakeup: bool,
+    flags: u32,
+) -> SysResult {
     let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };
 
-    attr.config = PERF_COUNT_SW_BPF_OUTPUT as u64;
+    attr.config = config;
     attr.size = mem::size_of::<perf_event_attr>() as u32;
-    attr.type_ = PERF_TYPE_SOFTWARE as u32;
+    attr.type_ = perf_type;
     attr.sample_type = PERF_SAMPLE_RAW as u64;
-    attr.__bindgen_anon_1.sample_period = 1;
-    attr.__bindgen_anon_2.wakeup_events = 1;
+    // attr.inherits = if pid > 0 { 1 } else { 0 };
+    attr.__bindgen_anon_2.wakeup_events = if wakeup { 1 } else { 0 };
+
+    if let Some(frequency) = sample_frequency {
+        attr.set_freq(1);
+        attr.__bindgen_anon_1.sample_freq = frequency;
+    } else {
+        attr.__bindgen_anon_1.sample_period = sample_period;
+    }
 
     syscall(Syscall::PerfEventOpen {
         attr,
-        pid: -1,
+        pid,
         cpu,
         group: -1,
-        flags: PERF_FLAG_FD_CLOEXEC,
+        flags,
     })
 }
 
+pub(crate) fn perf_event_open_bpf(cpu: c_int) -> SysResult {
+    perf_event_open(
+        PERF_TYPE_SOFTWARE as u32,
+        PERF_COUNT_SW_BPF_OUTPUT as u64,
+        -1,
+        cpu,
+        1,
+        None,
+        true,
+        PERF_FLAG_FD_CLOEXEC,
+    )
+}
+
 pub(crate) fn perf_event_open_probe(
     ty: u32,
     ret_bit: Option<u32>,

+ 29 - 0
bpf/aya-bpf-macros/src/expand.rs

@@ -323,3 +323,32 @@ impl TracePoint {
         })
     }
 }
+
+pub struct PerfEvent {
+    item: ItemFn,
+    name: String,
+}
+
+impl PerfEvent {
+    pub fn from_syn(mut args: Args, item: ItemFn) -> Result<PerfEvent> {
+        let name = name_arg(&mut args)?.unwrap_or_else(|| item.sig.ident.to_string());
+
+        Ok(PerfEvent { item, name })
+    }
+
+    pub fn expand(&self) -> Result<TokenStream> {
+        let section_name = format!("perf_event/{}", self.name);
+        let fn_name = &self.item.sig.ident;
+        let item = &self.item;
+        Ok(quote! {
+            #[no_mangle]
+            #[link_section = #section_name]
+            fn #fn_name(ctx: *mut ::core::ffi::c_void) -> u32 {
+               let _ = #fn_name(::aya_bpf::programs::PerfEventContext::new(ctx));
+               return 0;
+
+               #item
+            }
+        })
+    }
+}

+ 14 - 1
bpf/aya-bpf-macros/src/lib.rs

@@ -1,6 +1,8 @@
 mod expand;
 
-use expand::{Args, Map, Probe, ProbeKind, SchedClassifier, SkMsg, SockOps, TracePoint, Xdp};
+use expand::{
+    Args, Map, PerfEvent, Probe, ProbeKind, SchedClassifier, SkMsg, SockOps, TracePoint, Xdp,
+};
 use proc_macro::TokenStream;
 use syn::{parse_macro_input, ItemFn, ItemStatic};
 
@@ -112,3 +114,14 @@ pub fn tracepoint(attrs: TokenStream, item: TokenStream) -> TokenStream {
         .unwrap_or_else(|err| err.to_compile_error())
         .into()
 }
+
+#[proc_macro_attribute]
+pub fn perf_event(attrs: TokenStream, item: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(attrs as Args);
+    let item = parse_macro_input!(item as ItemFn);
+
+    PerfEvent::from_syn(args, item)
+        .and_then(|u| u.expand())
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}

+ 2 - 0
bpf/aya-bpf/src/programs/mod.rs

@@ -1,3 +1,4 @@
+pub mod perf_event;
 pub mod probe;
 pub mod sk_msg;
 pub mod sk_skb;
@@ -5,6 +6,7 @@ pub mod sock_ops;
 pub mod tracepoint;
 pub mod xdp;
 
+pub use perf_event::PerfEventContext;
 pub use probe::ProbeContext;
 pub use sk_msg::SkMsgContext;
 pub use sk_skb::SkSkbContext;

+ 18 - 0
bpf/aya-bpf/src/programs/perf_event.rs

@@ -0,0 +1,18 @@
+use crate::BpfContext;
+use core::ffi::c_void;
+
+pub struct PerfEventContext {
+    ctx: *mut c_void,
+}
+
+impl PerfEventContext {
+    pub fn new(ctx: *mut c_void) -> PerfEventContext {
+        PerfEventContext { ctx }
+    }
+}
+
+impl BpfContext for PerfEventContext {
+    fn as_ptr(&self) -> *mut c_void {
+        self.ctx
+    }
+}