Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/memtrack/src/ebpf/memtrack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ impl MemtrackBpf {
/// would detect. Either attaches given host privileges; the token only
/// matters when `bpf()` is called from an unprivileged user namespace.
pub fn with_variant(variant: BpfVariant, track_rmap: bool) -> Result<Self> {
crate::kernel::KernelBtf::ensure_available()?;

let page_shift = page_shift()?;
let rmap = if track_rmap {
RmapSupport::detect()
Expand Down
52 changes: 46 additions & 6 deletions crates/memtrack/src/kernel.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
use crate::prelude::*;
use std::fmt;

/// The running kernel's full release, e.g. `6.12.8+`.
fn kernel_release() -> Result<String> {
const OSRELEASE_PATH: &str = "/proc/sys/kernel/osrelease";

std::fs::read_to_string(OSRELEASE_PATH)
.map(|release| release.trim().to_owned())
.with_context(|| format!("Failed to read {OSRELEASE_PATH}"))
}

/// A kernel release, ordered by `(major, minor)`. The patch level is ignored:
/// features are introduced in merge windows, never in a stable point release.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
Expand All @@ -16,12 +25,8 @@ impl KernelVersion {

/// The running kernel's release.
pub fn current() -> Result<Self> {
const PATH: &str = "/proc/sys/kernel/osrelease";

let release =
std::fs::read_to_string(PATH).with_context(|| format!("Failed to read {PATH}"))?;
Self::parse(&release)
.with_context(|| format!("Failed to parse kernel release {:?}", release.trim()))
let release = kernel_release()?;
Self::parse(&release).with_context(|| format!("Failed to parse kernel release {release:?}"))
}

/// Parse the leading `<major>.<minor>` of a release string, ignoring
Expand All @@ -46,6 +51,41 @@ impl fmt::Display for KernelVersion {
}
}

/// Whether the running kernel exposes its own BTF.
///
/// libbpf needs it to resolve CO-RE relocations, and the kernel resolves the
/// attach target of every `fentry`/`tp_btf` program against it, so a kernel
/// without BTF cannot load the programs at all. Detecting it up front replaces
/// libbpf's bare `-ESRCH` with something the reader can act on.
///
/// Minimal kernels built for fast boot — microVM images in particular — commonly
/// drop `CONFIG_DEBUG_INFO_BTF`, so the message has to name the option.
pub struct KernelBtf;

impl KernelBtf {
/// Present only on a kernel built with `CONFIG_DEBUG_INFO_BTF`.
const PATH: &'static str = "/sys/kernel/btf/vmlinux";

pub fn is_available() -> bool {
std::fs::metadata(Self::PATH).is_ok()
}

pub fn ensure_available() -> Result<()> {
let release = kernel_release().unwrap_or_else(|_| "unknown".to_owned());
match std::fs::metadata(Self::PATH) {
Ok(_) => Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => bail!(
"Memory profiling is not supported on this runner: its kernel ({release}) \
was built without BTF support."
),
Err(error) => bail!(
"Memory profiling is unavailable on this runner: failed to access BTF at {}: {error}",
Self::PATH
),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/memtrack/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use ipc::{
IpcCommand as MemtrackIpcCommand, IpcMessage as MemtrackIpcMessage,
IpcResponse as MemtrackIpcResponse, MemtrackIpcClient, MemtrackIpcServer,
};
pub use kernel::KernelVersion;
pub use kernel::{KernelBtf, KernelVersion};

#[cfg(feature = "ebpf")]
pub use ebpf::*;
Expand Down