Skip to content
Open
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: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
# Each memtrack integration test binary runs its cases serially
# (eBPF tracker can't overlap with itself in one process), so we
# shard at the test-binary level to parallelize across jobs.
test: [c_tests, cpp_tests, rust_tests, spawn_tests]
test: [c_tests, cpp_tests, rust_tests, spawn_tests, dlopen_tests]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/memtrack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ static_assertions = "1.1"
itertools = { workspace = true }
paste = "1.0.15"
libbpf-rs = { version = "0.26", features = ["vendored"], optional = true }
glob = "0.3.3"
object = { workspace = true }
parking_lot = "0.12"

[build-dependencies]
libbpf-cargo = { version = "0.26", optional = true }
Expand Down
76 changes: 0 additions & 76 deletions crates/memtrack/src/allocators/dynamic.rs

This file was deleted.

61 changes: 0 additions & 61 deletions crates/memtrack/src/allocators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

use std::path::PathBuf;

mod dynamic;
mod static_linked;

/// Represents the different allocator types we support.
Expand Down Expand Up @@ -67,63 +66,3 @@ pub struct AllocatorLib {
pub kind: AllocatorKind,
pub path: PathBuf,
}

impl AllocatorLib {
pub fn find_all() -> anyhow::Result<Vec<AllocatorLib>> {
let mut allocators = static_linked::find_all()?;
allocators.extend(Self::find_from_env());
allocators.extend(dynamic::find_all()?);
Ok(allocators)
}

/// Discover allocators from binaries listed in the `CODSPEED_MEMTRACK_BINARIES` env var.
///
/// The variable is a platform path-list. Each path is checked for
/// a statically linked allocator via [`AllocatorLib::from_path_static`].
/// Invalid or missing paths are silently skipped (with a debug log).
fn find_from_env() -> Vec<AllocatorLib> {
let Some(raw) = std::env::var_os("CODSPEED_MEMTRACK_BINARIES") else {
return vec![];
};

std::env::split_paths(&raw)
.filter(|p| !p.as_os_str().is_empty())
.filter_map(|p| {
let path = p.as_path();
match Self::from_path_static(path) {
Ok(alloc) => {
log::debug!(
"Found {} allocator in env-specified binary: {}",
alloc.kind.name(),
path.display()
);
Some(alloc)
}
Err(e) => {
log::debug!("Skipping env-specified binary {}: {e}", path.display());
None
}
}
})
.collect()
}
}

/// Check if a file is an ELF binary by reading its magic bytes.
fn is_elf(path: &std::path::Path) -> bool {
use std::fs;
use std::io::Read;

let mut file = match fs::File::open(path) {
Ok(f) => f,
Err(_) => return false,
};

let mut magic = [0u8; 4];
if file.read_exact(&mut magic).is_err() {
return false;
}

// ELF magic: 0x7F 'E' 'L' 'F'
magic == [0x7F, b'E', b'L', b'F']
}
104 changes: 7 additions & 97 deletions crates/memtrack/src/allocators/static_linked.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use std::path::Path;

use crate::allocators::{AllocatorKind, AllocatorLib};

Expand All @@ -10,86 +10,18 @@ impl AllocatorKind {
match self {
AllocatorKind::Libc => &["malloc", "free"],
AllocatorKind::LibCpp => &["_Znwm", "_Znam", "_ZdlPv", "_ZdaPv"],
AllocatorKind::Jemalloc => &["_rjem_malloc", "je_malloc", "je_malloc_default"],
// "mallocx" covers unprefixed jemalloc builds (e.g. distro
// libjemalloc.so): the extended API keeps its name when the je_
// prefix is disabled, and no other allocator exports it.
AllocatorKind::Jemalloc => {
&["_rjem_malloc", "je_malloc", "je_malloc_default", "mallocx"]
}
AllocatorKind::Mimalloc => &["mi_malloc_aligned", "mi_malloc", "mi_free"],
AllocatorKind::Tcmalloc => &["tc_malloc", "tc_free", "tc_version"],
}
}
}

/// Walk upward and downward from current directory to find build directories.
/// Returns all found build directories in order of preference.
fn find_build_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
let Ok(current_dir) = std::env::current_dir() else {
return dirs;
};

let patterns = ["target/codspeed/analysis", "bazel-bin", "build"];
let mut check_patterns = |dir: &Path| {
for pattern in &patterns {
let path = dir.join(pattern);
if path.is_dir() {
dirs.push(path);
}
}
};

// Walk upward from parent directories
// Note: We skip current_dir here since the downward walk (below) already checks it
let mut current = current_dir.clone();
while current.pop() {
check_patterns(&current);
}

// Walk downward from current directory
let mut stack = vec![current_dir];
while let Some(dir) = stack.pop() {
check_patterns(&dir);

// Read subdirectories
let Ok(entries) = fs::read_dir(&dir) else {
continue;
};

for entry in entries.filter_map(Result::ok) {
let path = entry.path();

let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
continue;
};

// Skip hidden dirs and common excludes
if name.starts_with('.') || matches!(name, "node_modules" | "vendor" | "venv") {
continue;
}

// Don't recursive into dirs that we want to match.
// This can happen with `target` as it contains build dirs for statically linked crates.
if matches!(name, "target" | "bazel-bin" | "build") {
continue;
}

if path.is_file() {
continue;
}

stack.push(path);
}
}

dirs
}

fn find_binaries_in_dir(dir: &Path) -> Vec<PathBuf> {
glob::glob(&format!("{}/**/*", dir.display()))
.into_iter()
.flatten()
.filter_map(Result::ok)
.filter(|p| p.is_file() && super::is_elf(p))
.collect::<Vec<_>>()
}

fn find_statically_linked_allocator(path: &Path) -> Option<AllocatorKind> {
use object::{Object, ObjectSymbol};

Expand All @@ -111,28 +43,6 @@ fn find_statically_linked_allocator(path: &Path) -> Option<AllocatorKind> {
.copied()
}

pub fn find_all() -> anyhow::Result<Vec<AllocatorLib>> {
let build_dirs = find_build_dirs();
if build_dirs.is_empty() {
return Ok(vec![]);
}

let mut allocators = Vec::new();
for build_dir in build_dirs {
let bins = find_binaries_in_dir(&build_dir);

for bin in bins {
let Some(kind) = find_statically_linked_allocator(&bin) else {
continue;
};

allocators.push(AllocatorLib { kind, path: bin });
}
}

Ok(allocators)
}

impl AllocatorLib {
pub fn from_path_static(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
let kind = find_statically_linked_allocator(path).ok_or("No allocator found")?;
Expand Down
Loading
Loading