feat(memtrack): attach allocator probes on demand#445
Conversation
Merging this PR will not alter performance
|
Greptile SummaryThis PR replaces the eager startup allocator discovery scan (system-wide glob +
Confidence Score: 4/5Safe to merge; the stop-the-world drain correctness is sound for the intended use case of well-behaved benchmarks that clean up children before the root exits. The core attach correctness argument holds: ring-buffer entries are submitted before SIGSTOP is sent, the fixpoint loop stops all observed pids before draining, and ContGuard guarantees resume on every exit path. The crates/memtrack/src/ebpf/proc_fs.rs (wait_all_stopped all_stopped default) and crates/memtrack/src/ebpf/attach_worker.rs (shutdown drain loop bound) Important Files Changed
|
8bce092 to
16ffc38
Compare
16ffc38 to
5b76788
Compare
GuillaumeLagrange
left a comment
There was a problem hiding this comment.
Stopping here becuase I want us to dig the TRACEME thing before leaning in the stop wrapper
a0a194d to
4d3fbb0
Compare
GuillaumeLagrange
left a comment
There was a problem hiding this comment.
lgtm, I recommend you rebase on top of #455 before merging though 😅
|
Yeah, that was the plan :) |
An ARRAY-map lookup on a valid index cannot fail, but if one ever does is_enabled() now returns 0 (disabled) instead of 1, so tracking never runs unguarded.
…tcher Instead of pre-attaching every allocator found on the system, a BPF fentry on security_mmap_file signals the first mapping of each unknown executable inode. A background worker stops the mapping processes, classifies the file by its symbols, attaches probes, and resumes them. This covers dlopen'd and statically linked allocators in spawned children, and skips libraries the benchmark never loads. The public API shrinks to Tracker (owns the attach worker) and Session (owns the spawned child and its event pipeline); ring buffers are polled through a generic RingBufferPoller with caller-supplied parsing. Fixes COD-1801
On-demand attach supersedes the startup scan: delete the system-wide library glob, the build-dir walk, and the CODSPEED_MEMTRACK_BINARIES env var (the runner no longer resolves exec target binaries for it). BREAKING CHANGE: CODSPEED_MEMTRACK_BINARIES is no longer read Refs COD-1801
Distro libjemalloc is built without the je_ symbol prefix, so symbol classification missed it and fell through to libc++ (jemalloc exports operator new), leaving plain malloc/aligned_alloc unprobed. Match on mallocx, which keeps its name in unprefixed builds and is unique to jemalloc.
4d3fbb0 to
a7e1666
Compare
…pings The /proc/<pid>/maps range column is zero-padded to a minimum of 8 hex digits, but /proc/<pid>/map_files/ dirents use the kernel's canonical no-leading-zero hex. Reusing the padded range built a non-existent path for any mapping starting below 0x10000000 (e.g. non-PIE executables at 0x400000), so its allocator probes were silently never attached. Normalize each half of the range before building the map_files path, via an extracted map_files_name helper with a deterministic unit test.
…ve process The watcher fires once per inode, so a mapping it could not resolve is never retried. Treating that as a benign miss (Ok(false)) silently resumed the process with no allocator probes and let the run succeed with incomplete coverage. Split resolve_mapping's outcome into a Resolution enum: ProcessGone (the process exited before classification, non-fatal) versus Unresolved (maps was read but no row matches the watcher's (dev, ino)). Since the process is stopped with the mapping necessarily present, an Unresolved mapping is a real coverage gap (e.g. an overlayfs inode-namespace mismatch) and now aborts the run via record_fatal instead of failing silently. The deeper overlayfs correlation fix (matching new exec VMAs directly rather than the watcher's overlay inode) still needs an overlayfs test environment and is left as a follow-up; this change makes the failure loud and diagnosable rather than a silent empty trace.
finish() was the only path that set the shutdown flag and joined the worker thread. On any error path that drops the Tracker without calling finish() (e.g. an early return in main before teardown), the worker kept spinning on its recv loop and held an Arc<Mutex<MemtrackBpf>> clone alive, so probe links were never detached and fell back to the slow serial close-at-exit path this worker exists to eliminate. Add a Drop impl that sets shutdown and joins the taken handle; it is idempotent with finish() (which already take()s the handle first).
1a7255c to
6794d28
Compare
Attach allocator uprobes only to libraries the tracked process tree actually maps, instead of pre-attaching every allocator discovered on the system at startup.
A BPF fentry on
security_mmap_filesignals the first mapping of each unknown executable inode. A background worker SIGSTOPs the mapping processes, classifies the file by its allocator symbols, attaches probes, and resumes them. This:dlopen'd allocators and statically linked allocators in spawned children (previously missed entirely)The eager discovery path and
CODSPEED_MEMTRACK_BINARIESare removed since the watcher supersedes them (breaking: the env var is no longer read).The crate API is now
Tracker(owns the attach worker) andSession(owns the spawned child and its event pipeline); ring buffers are polled through a genericRingBufferPollerwith caller-supplied parsing, replacing the keepalive forwarding thread.Review focus: the stop-the-world attach in
attach_worker.rs(fixpoint stop + synchronous ring buffer drain) — its correctness argument is that a fullconsume()after all producers are stopped cannot miss requests.Fixes COD-1801