Browse Source

Merge pull request #959 from tyrone-wu/aya/program_info_stats

aya,aya-obj: expose run_time_ns & run_cnt fields, and add bpf_enable_stats util function
Alessandro Decina 6 months ago
parent
commit
ab000ad7c3
6 changed files with 205 additions and 15 deletions
  1. 15 10
      aya/src/lib.rs
  2. 22 0
      aya/src/programs/mod.rs
  3. 17 0
      aya/src/sys/bpf.rs
  4. 55 1
      aya/src/sys/mod.rs
  5. 2 0
      test/integration-test/src/tests/smoke.rs
  6. 94 4
      xtask/public-api/aya.txt

+ 15 - 10
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<Self> {
         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 _;
 

+ 22 - 0
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<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
         use std::os::unix::ffi::OsStrExt as _;

+ 17 - 0
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<Item = Result<u32, SyscallError>>
     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<crate::MockableFd, SyscallError> {
+    let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
+    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<T>(
     max_retries: usize,
     f: impl Fn(&mut [u8]) -> SysResult<T>,

+ 55 - 1
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<Stats> 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<OwnedFd, SyscallError> {
+    bpf_enable_stats(stats_type.into()).map(|fd| fd.into_inner())
+}

+ 2 - 0
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]

+ 94 - 4
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<alloc::string::String>
 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<aya::maps::MapError> for aya::EbpfError
@@ -1369,6 +1369,8 @@ impl core::convert::From<aya::maps::MapError> for aya::maps::xdp::XdpMapError
 pub fn aya::maps::xdp::XdpMapError::from(source: aya::maps::MapError) -> Self
 impl core::convert::From<aya::maps::MapError> for aya::programs::ProgramError
 pub fn aya::programs::ProgramError::from(source: aya::maps::MapError) -> Self
+impl core::convert::From<aya::sys::SyscallError> for aya::maps::MapError
+pub fn aya::maps::MapError::from(source: aya::sys::SyscallError) -> Self
 impl core::convert::From<aya_obj::maps::InvalidMapTypeError> for aya::maps::MapError
 pub fn aya::maps::MapError::from(e: aya_obj::maps::InvalidMapTypeError) -> Self
 impl core::convert::From<std::io::error::Error> 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<aya::sys::SyscallError> 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<aya::sys::SyscallError> 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<aya::programs::uprobe::UProbeError> for aya::programs::
 pub fn aya::programs::ProgramError::from(source: aya::programs::uprobe::UProbeError) -> Self
 impl core::convert::From<aya::programs::xdp::XdpError> for aya::programs::ProgramError
 pub fn aya::programs::ProgramError::from(source: aya::programs::xdp::XdpError) -> Self
+impl core::convert::From<aya::sys::SyscallError> for aya::programs::ProgramError
+pub fn aya::programs::ProgramError::from(source: aya::sys::SyscallError) -> Self
 impl core::convert::From<aya_obj::btf::btf::BtfError> for aya::programs::ProgramError
 pub fn aya::programs::ProgramError::from(source: aya_obj::btf::btf::BtfError) -> Self
 impl core::convert::From<std::io::error::Error> 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<Item = core::result::Result<aya::programs::ProgramInfo, aya::programs::ProgramError>>
+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<aya::sys::Stats> 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<T, U> core::convert::Into<U> for aya::sys::Stats where U: core::convert::From<T>
+pub fn aya::sys::Stats::into(self) -> U
+impl<T, U> core::convert::TryFrom<U> for aya::sys::Stats where U: core::convert::Into<T>
+pub type aya::sys::Stats::Error = core::convert::Infallible
+pub fn aya::sys::Stats::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
+impl<T, U> core::convert::TryInto<U> for aya::sys::Stats where U: core::convert::TryFrom<T>
+pub type aya::sys::Stats::Error = <U as core::convert::TryFrom<T>>::Error
+pub fn aya::sys::Stats::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
+impl<T> 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<T> 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<T> core::borrow::Borrow<T> for aya::sys::Stats where T: core::marker::Sized
+pub fn aya::sys::Stats::borrow(&self) -> &T
+impl<T> core::borrow::BorrowMut<T> for aya::sys::Stats where T: core::marker::Sized
+pub fn aya::sys::Stats::borrow_mut(&mut self) -> &mut T
+impl<T> 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<T> core::convert::From<T> 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<aya::sys::SyscallError> for aya::maps::MapError
+pub fn aya::maps::MapError::from(source: aya::sys::SyscallError) -> Self
+impl core::convert::From<aya::sys::SyscallError> for aya::pin::PinError
+pub fn aya::pin::PinError::from(source: aya::sys::SyscallError) -> Self
+impl core::convert::From<aya::sys::SyscallError> for aya::programs::ProgramError
+pub fn aya::programs::ProgramError::from(source: aya::sys::SyscallError) -> Self
+impl core::convert::From<aya::sys::SyscallError> 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<T, U> core::convert::Into<U> for aya::sys::SyscallError where U: core::convert::From<T>
+pub fn aya::sys::SyscallError::into(self) -> U
+impl<T, U> core::convert::TryFrom<U> for aya::sys::SyscallError where U: core::convert::Into<T>
+pub type aya::sys::SyscallError::Error = core::convert::Infallible
+pub fn aya::sys::SyscallError::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
+impl<T, U> core::convert::TryInto<U> for aya::sys::SyscallError where U: core::convert::TryFrom<T>
+pub type aya::sys::SyscallError::Error = <U as core::convert::TryFrom<T>>::Error
+pub fn aya::sys::SyscallError::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
+impl<T> 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<T> 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<T> core::borrow::Borrow<T> for aya::sys::SyscallError where T: core::marker::Sized
+pub fn aya::sys::SyscallError::borrow(&self) -> &T
+impl<T> core::borrow::BorrowMut<T> for aya::sys::SyscallError where T: core::marker::Sized
+pub fn aya::sys::SyscallError::borrow_mut(&mut self) -> &mut T
+impl<T> core::convert::From<T> 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<std::os::fd::owned::OwnedFd, aya::sys::SyscallError>
 pub mod aya::util
 pub struct aya::util::KernelVersion
 impl aya::util::KernelVersion