-
Notifications
You must be signed in to change notification settings - Fork 4
feat: eBPF process lifecycle tracking + ProcessMetadataCache library API #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
14cab1a
f77b7ac
45821a1
1cda34f
7d3ca02
276964c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,6 +72,12 @@ static TARGET_PID_MAP: Array<u32> = Array::with_max_entries(1, 0); | |
| #[map(name = "monitor_exit_pid_map")] | ||
| static MONITOR_EXIT_PID_MAP: Array<u32> = Array::with_max_entries(1, 0); | ||
|
|
||
| /// Whether process lifecycle tracking is enabled (0 = disabled, 1 = enabled). | ||
| /// When enabled, exit events fire for ALL process exits (not just DWARF-tracked | ||
| /// or monitored PIDs), and the exec tracepoint sends exec events. | ||
| #[map(name = "lifecycle_tracking_map")] | ||
| static LIFECYCLE_TRACKING_MAP: Array<u32> = Array::with_max_entries(1, 0); | ||
|
|
||
| #[inline] | ||
| unsafe fn skip_idle() -> bool { | ||
| let skip = core::ptr::read_volatile(&SKIP_IDLE); | ||
|
|
@@ -119,6 +125,14 @@ unsafe fn monitor_exit_pid() -> u32 { | |
| } | ||
| } | ||
|
|
||
| #[inline] | ||
| unsafe fn lifecycle_tracking_enabled() -> bool { | ||
| match LIFECYCLE_TRACKING_MAP.get(0) { | ||
| Some(&v) => v != 0, | ||
| None => false, | ||
| } | ||
| } | ||
|
|
||
| /* Setup maps */ | ||
| #[map] | ||
| static mut STORAGE: PerCpuArray<FramePointers> = PerCpuArray::with_max_entries(1, 0); | ||
|
|
@@ -136,6 +150,9 @@ static RING_BUF_STACKS: RingBuf = RingBuf::with_byte_size(STACK_SIZE, 0); | |
| #[map(name = "process_exit_events")] | ||
| static RING_BUF_PROCESS_EXIT: RingBuf = RingBuf::with_byte_size(4096, 0); | ||
|
|
||
| #[map(name = "process_exec_events")] | ||
| static RING_BUF_PROCESS_EXEC: RingBuf = RingBuf::with_byte_size(4096, 0); | ||
|
|
||
| #[map(name = "stack_traces")] | ||
| pub static STACK_TRACES: StackTrace = StackTrace::with_max_entries(STACK_SIZE, 0); | ||
| // DWARF unwind maps — single outer ArrayOfMaps containing per-binary inner Array maps. | ||
|
|
@@ -1382,19 +1399,34 @@ unsafe fn collect_off_cpu_trace_percpu<C: EbpfContext>(ctx: &C, now: u64) { | |
| /// Handle sched_process_exit tracepoint for process exit monitoring. | ||
| /// Sends a ProcessExitEvent when: | ||
| /// - The monitored PID exits (--pid mode: stops profiling), OR | ||
| /// - A DWARF-tracked process exits (cleanup LPM trie entries). | ||
| /// - A DWARF-tracked process exits (cleanup LPM trie entries), OR | ||
| /// - Lifecycle tracking is enabled (system-wide process awareness). | ||
| /// | ||
| /// Only fires for process exits (tid == tgid), not individual thread exits. | ||
| /// sched_process_exit fires for every thread exit; without this filter, | ||
| /// thread-heavy workloads (Java, Go) would generate thousands of | ||
| /// duplicate events per second for the same tgid. | ||
| #[inline(always)] | ||
| pub unsafe fn handle_process_exit<C: EbpfContext>(ctx: C) { | ||
| use profile_bee_common::ProcessExitEvent; | ||
|
|
||
| let tid = ctx.pid(); | ||
| let tgid = ctx.tgid(); | ||
|
|
||
| // Skip thread exits — only fire for the main thread (process exit). | ||
| if tid != tgid { | ||
| return; | ||
| } | ||
|
Comment on lines
+1413
to
+1419
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The thread-group leader can exit via |
||
|
|
||
| let monitor_pid = monitor_exit_pid(); | ||
|
|
||
| // Send notification if this is either the monitored PID or a DWARF-tracked process | ||
| // Send notification if this is a monitored PID, DWARF-tracked process, | ||
| // or lifecycle tracking is enabled (for system-wide process awareness). | ||
| let is_monitored = monitor_pid != 0 && tgid == monitor_pid; | ||
| let is_dwarf_tracked = unsafe { DWARF_TGIDS.get(&tgid).is_some() }; | ||
| let lifecycle = unsafe { lifecycle_tracking_enabled() }; | ||
|
|
||
| if is_monitored || is_dwarf_tracked { | ||
| if is_monitored || is_dwarf_tracked || lifecycle { | ||
| if let Some(mut entry) = RING_BUF_PROCESS_EXIT.reserve::<ProcessExitEvent>(0) { | ||
| let exit_event = ProcessExitEvent { | ||
| pid: tgid, | ||
|
|
@@ -1406,6 +1438,25 @@ pub unsafe fn handle_process_exit<C: EbpfContext>(ctx: C) { | |
| } | ||
| } | ||
|
|
||
| /// Handle sched_process_exec tracepoint for process exec monitoring. | ||
| /// Sends a ProcessExecEvent when a process calls execve(), enabling | ||
| /// proactive DWARF table loading and metadata cache invalidation. | ||
| /// | ||
| /// Note: sched_process_exec only fires once per execve() (not per-thread), | ||
| /// so no tid == tgid filter is needed here. | ||
| #[inline(always)] | ||
| pub unsafe fn handle_process_exec<C: EbpfContext>(ctx: C) { | ||
| use profile_bee_common::ProcessExecEvent; | ||
|
|
||
| let tgid = ctx.tgid(); | ||
|
|
||
| if let Some(mut entry) = RING_BUF_PROCESS_EXEC.reserve::<ProcessExecEvent>(0) { | ||
| let exec_event = ProcessExecEvent { pid: tgid, _pad: 0 }; | ||
| let _writable = entry.write(exec_event); | ||
| entry.submit(0); | ||
| } | ||
| } | ||
|
|
||
| // Make this simple now - checking for valid pointers can include | ||
| // checking with stack pointer address or getting valid ranges | ||
| // from from /proc/[pid]/maps | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gate exec notifications on
lifecycle_tracking_enabled().LIFECYCLE_TRACKING_MAPis documented as the runtime switch for exec notifications, buthandle_process_exec()ignores it and emits on every attached exec. That creates avoidable host-wide ring-buffer pressure and cache churn when lifecycle tracking is off.Suggested guard
pub unsafe fn handle_process_exec<C: EbpfContext>(ctx: C) { use profile_bee_common::ProcessExecEvent; + if !lifecycle_tracking_enabled() { + return; + } + let tgid = ctx.tgid(); if let Some(mut entry) = RING_BUF_PROCESS_EXEC.reserve::<ProcessExecEvent>(0) { let exec_event = ProcessExecEvent { pid: tgid, _pad: 0 }; let _writable = entry.write(exec_event);Also applies to: 1448-1457
🤖 Prompt for AI Agents