diff --git a/crates/memtrack/src/ebpf/libbpf_log.rs b/crates/memtrack/src/ebpf/libbpf_log.rs new file mode 100644 index 000000000..7eb735ee6 --- /dev/null +++ b/crates/memtrack/src/ebpf/libbpf_log.rs @@ -0,0 +1,48 @@ +use crate::prelude::*; +use libbpf_rs::PrintLevel; + +fn strip(msg: &str) -> &str { + let msg = msg.trim_end(); + msg.strip_prefix("libbpf: ").unwrap_or(msg) +} + +fn to_log(level: PrintLevel, msg: String) { + let msg = strip(&msg); + match level { + PrintLevel::Warn => warn!(target: "libbpf", "{msg}"), + PrintLevel::Info => info!(target: "libbpf", "{msg}"), + PrintLevel::Debug => debug!(target: "libbpf", "{msg}"), + } +} + +fn to_debug(_level: PrintLevel, msg: String) { + debug!(target: "libbpf", "{}", strip(&msg)); +} + +/// Route libbpf's output through the logger, so `CODSPEED_LOG` filters it like +/// any other target (e.g. `libbpf=debug`). The minimum level follows the +/// logger's so libbpf skips formatting messages that would be dropped. +pub fn init_libbpf_logging() { + let min_level = match log::max_level() { + log::LevelFilter::Off => { + libbpf_rs::set_print(None); + return; + } + log::LevelFilter::Error | log::LevelFilter::Warn => PrintLevel::Warn, + log::LevelFilter::Info => PrintLevel::Info, + log::LevelFilter::Debug | log::LevelFilter::Trace => PrintLevel::Debug, + }; + libbpf_rs::set_print(Some((min_level, to_log))); +} + +/// Run `f` with every libbpf message logged at debug, for calls whose failure +/// the caller handles and reports itself. +pub(crate) fn with_libbpf_demoted(f: impl FnOnce() -> T) -> T { + let prev = libbpf_rs::get_print(); + if let Some((min_level, _)) = prev { + libbpf_rs::set_print(Some((min_level, to_debug))); + } + let result = f(); + libbpf_rs::set_print(prev); + result +} diff --git a/crates/memtrack/src/ebpf/memtrack/mod.rs b/crates/memtrack/src/ebpf/memtrack/mod.rs index dbe4680ad..a9ca745de 100644 --- a/crates/memtrack/src/ebpf/memtrack/mod.rs +++ b/crates/memtrack/src/ebpf/memtrack/mod.rs @@ -105,6 +105,27 @@ fn page_shift() -> Result { Ok((page_size as u32).trailing_zeros()) } +/// Bump RLIMIT_MEMLOCK for kernels older than 5.11. Newer kernels account BPF +/// memory against the cgroup, so a denied raise (no CAP_SYS_RESOURCE) is harmless. +/// libbpf's own attempt is disabled, as it warns on that same harmless denial. +fn bump_memlock_rlimit() { + let rlimit = libc::rlimit { + rlim_cur: libc::RLIM_INFINITY, + rlim_max: libc::RLIM_INFINITY, + }; + + let ret = unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlimit) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + debug!( + "Could not raise RLIMIT_MEMLOCK ({err}); continuing since kernels >= 5.11 don't require it" + ); + } + + // Fails with EBUSY once libbpf has run its bump check, which is then moot. + let _ = unsafe { libbpf_rs::libbpf_sys::libbpf_set_memlock_rlim(0) }; +} + /// Attach targets resolved from a library's symbol tables. pub struct ResolvedSymbols { offsets: HashMap, @@ -132,6 +153,7 @@ pub struct MemtrackBpf { impl MemtrackBpf { pub fn new(options: &TrackerOptions) -> Result { crate::kernel::KernelBtf::ensure_available()?; + bump_memlock_rlimit(); let variant = options.variant.unwrap_or_else(|| { if has_delegated_bpf_token() { diff --git a/crates/memtrack/src/ebpf/memtrack/tracking.rs b/crates/memtrack/src/ebpf/memtrack/tracking.rs index 0054d9903..dfe39496d 100644 --- a/crates/memtrack/src/ebpf/memtrack/tracking.rs +++ b/crates/memtrack/src/ebpf/memtrack/tracking.rs @@ -13,8 +13,9 @@ impl MemtrackBpf { self.attach_sched_process_exec()?; self.attach_sched_process_exit()?; if self.physical { - if let Err(e) = self.attach_rss_stat() { - warn!("Failed to attach rss_stat tracepoint, RSS collection disabled: {e:#}"); + let attached = crate::ebpf::libbpf_log::with_libbpf_demoted(|| self.attach_rss_stat()); + if let Err(e) = attached { + debug!("Failed to attach rss_stat tracepoint, RSS collection disabled: {e:#}"); } } diff --git a/crates/memtrack/src/ebpf/mod.rs b/crates/memtrack/src/ebpf/mod.rs index 3aba823ac..a30d96204 100644 --- a/crates/memtrack/src/ebpf/mod.rs +++ b/crates/memtrack/src/ebpf/mod.rs @@ -1,5 +1,6 @@ mod attach_worker; mod events; +pub(crate) mod libbpf_log; mod memtrack; pub(crate) mod pause; pub(crate) mod poller; @@ -8,6 +9,7 @@ mod spawn; mod stacks; mod tracker; +pub use libbpf_log::init_libbpf_logging; pub use memtrack::{ BpfVariant, MemtrackBpf, OwnershipMaps, ResolvedSymbols, RmapSupport, resolve_symbol_offsets, }; diff --git a/crates/memtrack/src/ebpf/tracker.rs b/crates/memtrack/src/ebpf/tracker.rs index 1280733a9..0e7308306 100644 --- a/crates/memtrack/src/ebpf/tracker.rs +++ b/crates/memtrack/src/ebpf/tracker.rs @@ -100,8 +100,6 @@ impl Tracker { } fn build(mut bpf: MemtrackBpf, options: TrackerOptions) -> Result { - Self::bump_memlock_rlimit()?; - bpf.attach_tracepoints()?; if options.allocators { bpf.attach_exec_watcher()?; @@ -242,23 +240,4 @@ impl Tracker { pub fn detach(&self) { self.bpf.lock().detach_probes(); } - - /// Bump RLIMIT_MEMLOCK for kernels older than 5.11. Newer kernels account BPF - /// memory against the cgroup, so a denied raise (no CAP_SYS_RESOURCE) is harmless. - fn bump_memlock_rlimit() -> Result<()> { - let rlimit = libc::rlimit { - rlim_cur: libc::RLIM_INFINITY, - rlim_max: libc::RLIM_INFINITY, - }; - - let ret = unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlimit) }; - if ret != 0 { - let err = std::io::Error::last_os_error(); - debug!( - "Could not raise RLIMIT_MEMLOCK ({err}); continuing since kernels >= 5.11 don't require it" - ); - } - - Ok(()) - } } diff --git a/crates/memtrack/src/main.rs b/crates/memtrack/src/main.rs index 5ff58cb41..359634412 100644 --- a/crates/memtrack/src/main.rs +++ b/crates/memtrack/src/main.rs @@ -49,6 +49,7 @@ fn main() -> Result<()> { .parse_env(env_logger::Env::new().filter_or("CODSPEED_LOG", "info")) .format_timestamp(None) .init(); + memtrack::init_libbpf_logging(); let cli = Cli::parse();