Skip to content

Commit 63e9074

Browse files
committed
feat(memtrack): detect and abort on ring buffer overflow
Add a single-slot BPF array that bpf_ringbuf_reserve() failures atomically increment. Userspace reads it after the run completes and bails with a guidance message if any events were dropped, since a truncated trace would silently misreport memory usage.
1 parent 5268681 commit 63e9074

4 files changed

Lines changed: 47 additions & 0 deletions

File tree

crates/memtrack/src/ebpf/c/memtrack.bpf.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ BPF_HASH_MAP(pids_ppid, __u32, __u32, 10000);
4343
BPF_RINGBUF(events, 16 * 1024 * 1024);
4444
/* Map to control whether tracking is enabled (0 = disabled, 1 = enabled) */
4545
BPF_ARRAY_MAP(tracking_enabled, __u8, 1);
46+
/* Counter for events that couldn't be added to the ring buffer */
47+
BPF_ARRAY_MAP(dropped_events, __u64, 1);
4648

4749
/* == Code that tracks process forks and execs == */
4850

@@ -140,6 +142,11 @@ static __always_inline __u64* take_param(void* map) {
140142
\
141143
struct event* e = bpf_ringbuf_reserve(&events, sizeof(*e), 0); \
142144
if (!e) { \
145+
__u32 zero = 0; \
146+
__u64* drops = bpf_map_lookup_elem(&dropped_events, &zero);\
147+
if (drops) { \
148+
__sync_fetch_and_add(drops, 1); \
149+
} \
143150
return 0; \
144151
} \
145152
\

crates/memtrack/src/ebpf/memtrack.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,24 @@ impl MemtrackBpf {
228228
Ok(())
229229
}
230230

231+
/// Read the count of events dropped because the ring buffer was full.
232+
pub fn dropped_events_count(&self) -> Result<u64> {
233+
let key = 0u32;
234+
let value = self
235+
.skel
236+
.maps
237+
.dropped_events
238+
.lookup(&key.to_le_bytes(), libbpf_rs::MapFlags::ANY)
239+
.context("Failed to read dropped_events counter")?
240+
.ok_or_else(|| anyhow!("dropped_events slot 0 missing"))?;
241+
242+
let bytes: [u8; 8] = value
243+
.as_slice()
244+
.try_into()
245+
.map_err(|_| anyhow!("dropped_events value has unexpected size"))?;
246+
Ok(u64::from_le_bytes(bytes))
247+
}
248+
231249
/// Disable event tracking
232250
pub fn disable_tracking(&mut self) -> Result<()> {
233251
let key = 0u32;

crates/memtrack/src/ebpf/tracker.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,10 @@ impl Tracker {
9999
pub fn disable(&mut self) -> anyhow::Result<()> {
100100
self.bpf.disable_tracking()
101101
}
102+
103+
/// Number of events the kernel dropped because the ring buffer was full.
104+
/// A non-zero value means the resulting trace is incomplete.
105+
pub fn dropped_events_count(&self) -> anyhow::Result<u64> {
106+
self.bpf.dropped_events_count()
107+
}
102108
}

crates/memtrack/src/main.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,22 @@ fn track_command(
200200
.join()
201201
.map_err(|_| anyhow::anyhow!("Failed to join writer thread"))??;
202202

203+
// Read the eBPF dropped-event counter after the run is complete.
204+
// A non-zero value means the ring buffer overflowed and the trace is
205+
// incomplete.
206+
let dropped_events = tracker_arc
207+
.lock()
208+
.map_err(|_| anyhow!("tracker mutex poisoned"))?
209+
.dropped_events_count()
210+
.context("Failed to read memtrack dropped-event counter")?;
211+
if dropped_events > 0 {
212+
bail!(
213+
"Memtrack ring buffer overflowed: {dropped_events} events lost, aborting since the trace is incomplete.\n\
214+
Try reducing the benchmark's allocation rate (fewer iterations or smaller inputs), \
215+
or report it at https://github.com/CodSpeedHQ/codspeed/issues."
216+
);
217+
}
218+
203219
// IPC thread will exit when channel closes
204220
drop(ipc_handle);
205221

0 commit comments

Comments
 (0)