diff --git a/aya/src/lib.rs b/aya/src/lib.rs index 361a5fe9..c35bf597 100644 --- a/aya/src/lib.rs +++ b/aya/src/lib.rs @@ -78,20 +78,20 @@ )] mod bpf; -use aya_obj::generated; pub mod maps; -use aya_obj as obj; pub mod pin; pub mod programs; -pub use programs::loaded_programs; -mod sys; +pub mod sys; pub mod util; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; +use aya_obj as obj; +use aya_obj::generated; pub use bpf::*; pub use obj::btf::{Btf, BtfError}; pub use object::Endianness; +pub use programs::loaded_programs; #[doc(hidden)] pub use sys::netlink_set_link_up; @@ -139,6 +139,16 @@ impl MockableFd { fd.as_ref().unwrap() } + #[cfg(not(test))] + fn into_inner(self) -> OwnedFd { + self.fd + } + + #[cfg(test)] + fn into_inner(mut self) -> OwnedFd { + self.fd.take().unwrap() + } + fn try_clone(&self) -> std::io::Result { let fd = self.inner(); let fd = fd.try_clone()?; @@ -175,13 +185,8 @@ impl FromRawFd for MockableFd { } } +#[cfg(test)] impl Drop for MockableFd { - #[cfg(not(test))] - fn drop(&mut self) { - // Intentional no-op. - } - - #[cfg(test)] fn drop(&mut self) { use std::os::fd::AsRawFd as _; diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 4b12c6bb..898070e5 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -1000,6 +1000,7 @@ impl_info!( ); /// Provides information about a loaded program, like name, id and statistics +#[doc(alias = "bpf_prog_info")] #[derive(Debug)] pub struct ProgramInfo(bpf_prog_info); @@ -1099,6 +1100,27 @@ impl ProgramInfo { Ok(ProgramFd(fd)) } + /// The accumulated time that the program has been actively running. + /// + /// This is not to be confused with the duration since the program was + /// first loaded on the host. + /// + /// Note this field is only updated for as long as + /// [`enable_stats`](crate::sys::enable_stats) is enabled + /// with [`Stats::RunTime`](crate::sys::Stats::RunTime). + pub fn run_time(&self) -> Duration { + Duration::from_nanos(self.0.run_time_ns) + } + + /// The accumulated execution count of the program. + /// + /// Note this field is only updated for as long as + /// [`enable_stats`](crate::sys::enable_stats) is enabled + /// with [`Stats::RunTime`](crate::sys::Stats::RunTime). + pub fn run_count(&self) -> u64 { + self.0.run_cnt + } + /// Loads a program from a pinned path in bpffs. pub fn from_pin>(path: P) -> Result { use std::os::unix::ffi::OsStrExt as _; diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 66a1e89c..01bad1a3 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -11,6 +11,7 @@ use assert_matches::assert_matches; use libc::{ENOENT, ENOSPC}; use obj::{ btf::{BtfEnum64, Enum64}, + generated::bpf_stats_type, maps::{bpf_map_def, LegacyMap}, EbpfSectionKind, VerifierLog, }; @@ -1104,6 +1105,22 @@ pub(crate) fn iter_map_ids() -> impl Iterator> iter_obj_ids(bpf_cmd::BPF_MAP_GET_NEXT_ID, "bpf_map_get_next_id") } +/// Introduced in kernel v5.8. +pub(crate) fn bpf_enable_stats( + stats_type: bpf_stats_type, +) -> Result { + let mut attr = unsafe { mem::zeroed::() }; + attr.enable_stats.type_ = stats_type as u32; + + // SAFETY: BPF_ENABLE_STATS returns a new file descriptor. + unsafe { fd_sys_bpf(bpf_cmd::BPF_ENABLE_STATS, &mut attr) }.map_err(|(_, io_error)| { + SyscallError { + call: "bpf_enable_stats", + io_error, + } + }) +} + pub(crate) fn retry_with_verifier_logs( max_retries: usize, f: impl Fn(&mut [u8]) -> SysResult, diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs index ab486195..05f8b4dd 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -1,3 +1,5 @@ +//! A collection of system calls for performing eBPF related operations. + mod bpf; mod netlink; mod perf_event; @@ -8,7 +10,7 @@ mod fake; use std::{ ffi::{c_int, c_void}, io, mem, - os::fd::{AsRawFd as _, BorrowedFd}, + os::fd::{AsRawFd as _, BorrowedFd, OwnedFd}, }; pub(crate) use bpf::*; @@ -44,6 +46,7 @@ pub(crate) enum Syscall<'a> { }, } +/// A system call error. #[derive(Debug, Error)] #[error("`{call}` failed")] pub struct SyscallError { @@ -137,3 +140,54 @@ pub(crate) unsafe fn mmap( #[cfg(test)] TEST_MMAP_RET.with(|ret| *ret.borrow()) } + +/// The type of eBPF statistic to enable. +#[non_exhaustive] +#[doc(alias = "bpf_stats_type")] +#[derive(Copy, Clone, Debug)] +pub enum Stats { + /// Tracks [`run_time`](crate::programs::ProgramInfo::run_time) and + /// [`run_count`](crate::programs::ProgramInfo::run_count) fields. + #[doc(alias = "BPF_STATS_RUN_TIME")] + RunTime, +} + +impl From for crate::generated::bpf_stats_type { + fn from(value: Stats) -> Self { + use crate::generated::bpf_stats_type::*; + + match value { + Stats::RunTime => BPF_STATS_RUN_TIME, + } + } +} + +/// Enable global statistics tracking for eBPF programs and returns a +/// [file descriptor](`OwnedFd`) handler. +/// +/// Statistics tracking is disabled when the [file descriptor](`OwnedFd`) is +/// dropped (either automatically when the variable goes out of scope or +/// manually through [`Drop`]). +/// +/// Usage: +/// 1. Obtain fd from [`enable_stats`] and bind it to a variable. +/// 2. Record the statistic of interest. +/// 3. Wait for a recorded period of time. +/// 4. Record the statistic of interest again, and calculate the difference. +/// 5. Close/release fd automatically or manually. +/// +/// Introduced in kernel v5.8. +/// +/// # Examples +/// +/// ```no_run +/// # use aya::sys::{SyscallError}; +/// use aya::sys::{enable_stats, Stats}; +/// +/// let _fd = enable_stats(Stats::RunTime)?; +/// # Ok::<(), SyscallError>(()) +/// ``` +#[doc(alias = "BPF_ENABLE_STATS")] +pub fn enable_stats(stats_type: Stats) -> Result { + bpf_enable_stats(stats_type.into()).map(|fd| fd.into_inner()) +} diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 708b889b..b5bb30a2 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -98,6 +98,8 @@ fn list_loaded_programs() { prog.verified_instruction_count(); prog.loaded_at(); prog.fd().unwrap(); + prog.run_time(); + prog.run_count(); } #[test] diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 532d83c9..b47fc579 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -1360,7 +1360,7 @@ pub aya::maps::MapError::PinError::error: aya::pin::PinError pub aya::maps::MapError::PinError::name: core::option::Option pub aya::maps::MapError::ProgIdNotSupported pub aya::maps::MapError::ProgramNotLoaded -pub aya::maps::MapError::SyscallError(crate::sys::SyscallError) +pub aya::maps::MapError::SyscallError(aya::sys::SyscallError) pub aya::maps::MapError::Unsupported pub aya::maps::MapError::Unsupported::map_type: u32 impl core::convert::From for aya::EbpfError @@ -1369,6 +1369,8 @@ impl core::convert::From for aya::maps::xdp::XdpMapError pub fn aya::maps::xdp::XdpMapError::from(source: aya::maps::MapError) -> Self impl core::convert::From for aya::programs::ProgramError pub fn aya::programs::ProgramError::from(source: aya::maps::MapError) -> Self +impl core::convert::From for aya::maps::MapError +pub fn aya::maps::MapError::from(source: aya::sys::SyscallError) -> Self impl core::convert::From for aya::maps::MapError pub fn aya::maps::MapError::from(e: aya_obj::maps::InvalidMapTypeError) -> Self impl core::convert::From for aya::maps::MapError @@ -2408,7 +2410,9 @@ pub aya::pin::PinError::InvalidPinPath::error: alloc::ffi::c_str::NulError pub aya::pin::PinError::InvalidPinPath::path: std::path::PathBuf pub aya::pin::PinError::NoFd pub aya::pin::PinError::NoFd::name: alloc::string::String -pub aya::pin::PinError::SyscallError(crate::sys::SyscallError) +pub aya::pin::PinError::SyscallError(aya::sys::SyscallError) +impl core::convert::From for aya::pin::PinError +pub fn aya::pin::PinError::from(source: aya::sys::SyscallError) -> Self impl core::error::Error for aya::pin::PinError pub fn aya::pin::PinError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)> impl core::fmt::Debug for aya::pin::PinError @@ -3731,7 +3735,9 @@ pub fn aya::programs::kprobe::KProbeLinkId::from(t: T) -> T pub mod aya::programs::links pub enum aya::programs::links::LinkError pub aya::programs::links::LinkError::InvalidLink -pub aya::programs::links::LinkError::SyscallError(crate::sys::SyscallError) +pub aya::programs::links::LinkError::SyscallError(aya::sys::SyscallError) +impl core::convert::From for aya::programs::links::LinkError +pub fn aya::programs::links::LinkError::from(source: aya::sys::SyscallError) -> Self impl core::error::Error for aya::programs::links::LinkError pub fn aya::programs::links::LinkError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)> impl core::fmt::Debug for aya::programs::links::LinkError @@ -6824,7 +6830,7 @@ pub aya::programs::ProgramError::MapError(aya::maps::MapError) pub aya::programs::ProgramError::NotAttached pub aya::programs::ProgramError::NotLoaded pub aya::programs::ProgramError::SocketFilterError(aya::programs::socket_filter::SocketFilterError) -pub aya::programs::ProgramError::SyscallError(crate::sys::SyscallError) +pub aya::programs::ProgramError::SyscallError(aya::sys::SyscallError) pub aya::programs::ProgramError::TcError(aya::programs::tc::TcError) pub aya::programs::ProgramError::TracePointError(aya::programs::trace_point::TracePointError) pub aya::programs::ProgramError::UProbeError(aya::programs::uprobe::UProbeError) @@ -6850,6 +6856,8 @@ impl core::convert::From for aya::programs:: pub fn aya::programs::ProgramError::from(source: aya::programs::uprobe::UProbeError) -> Self impl core::convert::From for aya::programs::ProgramError pub fn aya::programs::ProgramError::from(source: aya::programs::xdp::XdpError) -> Self +impl core::convert::From for aya::programs::ProgramError +pub fn aya::programs::ProgramError::from(source: aya::sys::SyscallError) -> Self impl core::convert::From for aya::programs::ProgramError pub fn aya::programs::ProgramError::from(source: aya_obj::btf::btf::BtfError) -> Self impl core::convert::From for aya::programs::ProgramError @@ -7915,6 +7923,8 @@ pub fn aya::programs::ProgramInfo::memory_locked(&self) -> core::result::Result< pub fn aya::programs::ProgramInfo::name(&self) -> &[u8] pub fn aya::programs::ProgramInfo::name_as_str(&self) -> core::option::Option<&str> pub fn aya::programs::ProgramInfo::program_type(&self) -> u32 +pub fn aya::programs::ProgramInfo::run_count(&self) -> u64 +pub fn aya::programs::ProgramInfo::run_time(&self) -> core::time::Duration pub fn aya::programs::ProgramInfo::size_jitted(&self) -> u32 pub fn aya::programs::ProgramInfo::size_translated(&self) -> u32 pub fn aya::programs::ProgramInfo::tag(&self) -> u64 @@ -8660,6 +8670,86 @@ pub type aya::programs::xdp::XdpLink::Id = aya::programs::xdp::XdpLinkId pub fn aya::programs::xdp::XdpLink::detach(self) -> core::result::Result<(), aya::programs::ProgramError> pub fn aya::programs::xdp::XdpLink::id(&self) -> Self::Id pub fn aya::programs::loaded_programs() -> impl core::iter::traits::iterator::Iterator> +pub mod aya::sys +#[non_exhaustive] pub enum aya::sys::Stats +pub aya::sys::Stats::RunTime +impl core::clone::Clone for aya::sys::Stats +pub fn aya::sys::Stats::clone(&self) -> aya::sys::Stats +impl core::convert::From for aya_obj::generated::linux_bindings_x86_64::bpf_stats_type +pub fn aya_obj::generated::linux_bindings_x86_64::bpf_stats_type::from(value: aya::sys::Stats) -> Self +impl core::fmt::Debug for aya::sys::Stats +pub fn aya::sys::Stats::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Copy for aya::sys::Stats +impl core::marker::Freeze for aya::sys::Stats +impl core::marker::Send for aya::sys::Stats +impl core::marker::Sync for aya::sys::Stats +impl core::marker::Unpin for aya::sys::Stats +impl core::panic::unwind_safe::RefUnwindSafe for aya::sys::Stats +impl core::panic::unwind_safe::UnwindSafe for aya::sys::Stats +impl core::convert::Into for aya::sys::Stats where U: core::convert::From +pub fn aya::sys::Stats::into(self) -> U +impl core::convert::TryFrom for aya::sys::Stats where U: core::convert::Into +pub type aya::sys::Stats::Error = core::convert::Infallible +pub fn aya::sys::Stats::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::sys::Stats where U: core::convert::TryFrom +pub type aya::sys::Stats::Error = >::Error +pub fn aya::sys::Stats::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for aya::sys::Stats where T: core::clone::Clone +pub type aya::sys::Stats::Owned = T +pub fn aya::sys::Stats::clone_into(&self, target: &mut T) +pub fn aya::sys::Stats::to_owned(&self) -> T +impl core::any::Any for aya::sys::Stats where T: 'static + core::marker::Sized +pub fn aya::sys::Stats::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::sys::Stats where T: core::marker::Sized +pub fn aya::sys::Stats::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::sys::Stats where T: core::marker::Sized +pub fn aya::sys::Stats::borrow_mut(&mut self) -> &mut T +impl core::clone::CloneToUninit for aya::sys::Stats where T: core::clone::Clone +pub unsafe fn aya::sys::Stats::clone_to_uninit(&self, dst: *mut T) +impl core::convert::From for aya::sys::Stats +pub fn aya::sys::Stats::from(t: T) -> T +pub struct aya::sys::SyscallError +pub aya::sys::SyscallError::call: &'static str +pub aya::sys::SyscallError::io_error: std::io::error::Error +impl core::convert::From for aya::maps::MapError +pub fn aya::maps::MapError::from(source: aya::sys::SyscallError) -> Self +impl core::convert::From for aya::pin::PinError +pub fn aya::pin::PinError::from(source: aya::sys::SyscallError) -> Self +impl core::convert::From for aya::programs::ProgramError +pub fn aya::programs::ProgramError::from(source: aya::sys::SyscallError) -> Self +impl core::convert::From for aya::programs::links::LinkError +pub fn aya::programs::links::LinkError::from(source: aya::sys::SyscallError) -> Self +impl core::error::Error for aya::sys::SyscallError +pub fn aya::sys::SyscallError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)> +impl core::fmt::Debug for aya::sys::SyscallError +pub fn aya::sys::SyscallError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for aya::sys::SyscallError +pub fn aya::sys::SyscallError::fmt(&self, __formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for aya::sys::SyscallError +impl core::marker::Send for aya::sys::SyscallError +impl core::marker::Sync for aya::sys::SyscallError +impl core::marker::Unpin for aya::sys::SyscallError +impl !core::panic::unwind_safe::RefUnwindSafe for aya::sys::SyscallError +impl !core::panic::unwind_safe::UnwindSafe for aya::sys::SyscallError +impl core::convert::Into for aya::sys::SyscallError where U: core::convert::From +pub fn aya::sys::SyscallError::into(self) -> U +impl core::convert::TryFrom for aya::sys::SyscallError where U: core::convert::Into +pub type aya::sys::SyscallError::Error = core::convert::Infallible +pub fn aya::sys::SyscallError::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::sys::SyscallError where U: core::convert::TryFrom +pub type aya::sys::SyscallError::Error = >::Error +pub fn aya::sys::SyscallError::try_into(self) -> core::result::Result>::Error> +impl alloc::string::ToString for aya::sys::SyscallError where T: core::fmt::Display + core::marker::Sized +pub fn aya::sys::SyscallError::to_string(&self) -> alloc::string::String +impl core::any::Any for aya::sys::SyscallError where T: 'static + core::marker::Sized +pub fn aya::sys::SyscallError::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::sys::SyscallError where T: core::marker::Sized +pub fn aya::sys::SyscallError::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::sys::SyscallError where T: core::marker::Sized +pub fn aya::sys::SyscallError::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya::sys::SyscallError +pub fn aya::sys::SyscallError::from(t: T) -> T +pub fn aya::sys::enable_stats(stats_type: aya::sys::Stats) -> core::result::Result pub mod aya::util pub struct aya::util::KernelVersion impl aya::util::KernelVersion