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
10 changes: 10 additions & 0 deletions crates/memtrack/src/ebpf/memtrack/tracking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ impl MemtrackBpf {
attach_tracepoint!(task_newtask);
attach_tracepoint!(sched_process_exec);
attach_tracepoint!(sched_process_exit);
attach_tracepoint!(sys_enter_mmap);
attach_tracepoint!(sys_exit_mmap);
attach_tracepoint!(sys_enter_munmap);
attach_tracepoint!(sys_enter_brk);
attach_tracepoint!(sys_exit_brk);

fn rmap_target_enabled(&self, target: &str) -> bool {
!self.btf_disabled_rmap_targets.contains(&target)
Expand All @@ -16,6 +21,11 @@ impl MemtrackBpf {
self.attach_task_newtask()?;
self.attach_sched_process_exec()?;
self.attach_sched_process_exit()?;
self.attach_sys_enter_mmap()?;
self.attach_sys_exit_mmap()?;
self.attach_sys_enter_munmap()?;
self.attach_sys_enter_brk()?;
self.attach_sys_exit_brk()?;
if let Err(e) = self.attach_rss_stat() {
warn!("Failed to attach rss_stat tracepoint, RSS collection disabled: {e:#}");
}
Expand Down
4 changes: 2 additions & 2 deletions crates/memtrack/src/ebpf/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ pub struct Tracker {
impl Tracker {
/// Create a new tracker. The exec-mapping watcher discovers and attaches
/// allocator probes as the tracked process tree maps executable files.
pub fn new() -> Result<Self> {
pub fn new(track_allocators: bool) -> Result<Self> {
let track_rmap = std::env::var("CODSPEED_MEMTRACK_TRACK_RMAP").is_ok_and(|v| v == "1");
Self::build(track_rmap, true)
Self::build(track_rmap, track_allocators)
}

/// Track per-process RSS via the rss_stat tracepoint and folio rmap fentry
Expand Down
19 changes: 16 additions & 3 deletions crates/memtrack/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ enum Commands {
/// Optional IPC server name for receiving control commands
#[arg(long)]
ipc_server: Option<String>,

/// Track individual heap allocations (malloc/free/calloc/...). Disable to
/// reduce overhead on allocation-heavy programs; mmap/brk events are still
/// collected either way.
#[arg(
long,
env = "CODSPEED_TRACK_ALLOCATORS",
default_value_t = true,
action = clap::ArgAction::Set
)]
track_allocators: bool,
},
}

Expand All @@ -54,11 +65,12 @@ fn main() -> Result<()> {
command,
output: out_dir,
ipc_server,
track_allocators,
} => {
debug!("Starting memtrack for command: {command}");

let status =
track_command(&command, ipc_server, &out_dir).context("Failed to track command")?;
let status = track_command(&command, ipc_server, &out_dir, track_allocators)
.context("Failed to track command")?;

std::process::exit(status.code().unwrap_or(1));
}
Expand All @@ -69,6 +81,7 @@ fn track_command(
cmd_string: &str,
ipc_server_name: Option<String>,
out_dir: &Path,
track_allocators: bool,
) -> anyhow::Result<std::process::ExitStatus> {
// First, establish IPC connection if needed to avoid timeouts on the runner because
// creating the Tracker instance takes some time.
Expand All @@ -84,7 +97,7 @@ fn track_command(
None
};

let tracker = Arc::new(Tracker::new()?);
let tracker = Arc::new(Tracker::new(track_allocators)?);

// Spawn IPC handler thread with the now-available tracker
let ipc_handle = if let Some(rx) = ipc_channel {
Expand Down
15 changes: 15 additions & 0 deletions crates/memtrack/testdata/malloc_mmap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

int main(void) {
void *p = malloc(1 << 20);
memset(p, 1, 1 << 20);
free(p);

void *m = mmap(NULL, 4 << 20, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memset(m, 1, 4 << 20);
munmap(m, 4 << 20);
return 0;
}
45 changes: 45 additions & 0 deletions crates/memtrack/tests/c_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,48 @@ fn test_allocation_tracking(

Ok(())
}

#[test_with::env(GITHUB_ACTIONS)]
#[test_log::test]
fn test_track_allocators_disabled_skips_allocations() -> Result<(), Box<dyn std::error::Error>> {
use runner_shared::artifacts::MemtrackEventKind;

let temp_dir = TempDir::new()?;
let binary = shared::compile_c_source(
include_str!("../testdata/malloc_mmap.c"),
"malloc_mmap",
temp_dir.path(),
)?;

let (events, thread_handle) =
shared::track_command_with_opts(std::process::Command::new(&binary), false)?;

let has_mmap = events
.iter()
.any(|e| matches!(e.kind, MemtrackEventKind::Mmap { .. }));
assert!(
has_mmap,
"expected at least one Mmap event with allocators disabled"
);

let alloc_events: Vec<_> = events
.iter()
.filter(|e| {
matches!(
e.kind,
MemtrackEventKind::Malloc { .. }
| MemtrackEventKind::Free
| MemtrackEventKind::Calloc { .. }
| MemtrackEventKind::Realloc { .. }
| MemtrackEventKind::AlignedAlloc { .. }
)
})
.collect();
assert!(
alloc_events.is_empty(),
"expected no allocation events with allocators disabled, got {alloc_events:?}"
);

thread_handle.join().unwrap();
Ok(())
}
31 changes: 19 additions & 12 deletions crates/memtrack/tests/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,19 @@ macro_rules! assert_events_snapshot {
use runner_shared::artifacts::MemtrackEventKind;
use std::mem::discriminant;

// Dedup events by address and type to remove duplicates
// Keep only allocator events. mmap/munmap/brk sizes reflect allocator
// arena reservations that vary per run, so including them here would
// make these snapshots nondeterministic.
let events = $events
.iter()
.filter(|e| {
// Allocation snapshots track only allocator events; RSS and
// process-lifecycle events are asserted by dedicated tests.
!matches!(
matches!(
e.kind,
MemtrackEventKind::Rss { .. }
| MemtrackEventKind::Fork { .. }
| MemtrackEventKind::Exec
| MemtrackEventKind::Exit
MemtrackEventKind::Malloc { .. }
| MemtrackEventKind::Free
| MemtrackEventKind::Calloc { .. }
| MemtrackEventKind::Realloc { .. }
| MemtrackEventKind::AlignedAlloc { .. }
)
})
.sorted_by_key(|e| e.timestamp)
Expand Down Expand Up @@ -182,19 +183,25 @@ pub fn compile_c_source(
/// No allocators are pre-attached: the exec-mapping watcher discovers and
/// attaches them as the tracked tree maps executable files.
pub fn track_command(command: Command) -> TrackResult {
track_command_impl(command, false)
track_command_impl(command, true, false)
}

/// Track a command with folio rmap hooks enabled, reconstructing per-process RSS.
pub fn track_command_with_rmap(command: Command) -> TrackResult {
track_command_impl(command, true)
track_command_impl(command, false, true)
}

fn track_command_impl(command: Command, track_rmap: bool) -> TrackResult {
/// Track a command, collecting all memory events, with allocator tracking
/// optionally disabled.
pub fn track_command_with_opts(command: Command, track_allocators: bool) -> TrackResult {
track_command_impl(command, track_allocators, false)
}

fn track_command_impl(command: Command, track_allocators: bool, track_rmap: bool) -> TrackResult {
let tracker = if track_rmap {
Tracker::new_without_allocators_with_rmap(true)?
} else {
Tracker::new()?
Tracker::new(track_allocators)?
};
tracker.enable_tracking()?;

Expand Down