@cryptotaxi247 / netdata-1 / commits / fe4eec719

Optimize EBPF memory handling and PID indexing. (#22050)

- Replace heap allocations with inline Judy array storage for efficiency. - Add macros for value conversion, ensuring safer and more compact logic. - Reduce fragmentation and improve memory reuse in allocator settings. - Trim unused memory after BPF initialization to minimize memory footprint.

Stelios Fragkakis committed Mar 29, 2026 at 22:52 UTC fe4eec7199f88322cff0d26f33574c1b1ffe8da3
2 files changed +44 -27
src/collectors/collectors-ipc/ebpf-ipc.c
+25 -27
@@ -9,21 +9,29 @@ sem_t *shm_mutex_ebpf_integration = SEM_FAILED;
9 static Pvoid_t ebpf_ipc_JudyL = NULL;
10 ebpf_user_mem_stat_t ebpf_stat_values;
11
12 -static uint32_t *ebpf_shm_find_index_unsafe(uint32_t pid)
12 +// Judy stores index+1 directly in the pointer slot to avoid heap allocation.
13 +// 0 means "not found" (Judy returns NULL for missing keys),
14 +// so we offset by 1: stored = index+1, retrieved = stored-1.
15 +#define IDX_TO_JVALUE(idx) ((Pvoid_t)((Word_t)(idx) + 1))
16 +#define JVALUE_TO_IDX(pv) ((uint32_t)((Word_t)(pv) - 1))
17 +#define JVALUE_IS_VALID(pv) ((pv) != NULL)
18 +
19 +static bool ebpf_shm_find_index_unsafe(uint32_t pid, uint32_t *result)
20 {
21 Pvoid_t *Pvalue = JudyLGet(ebpf_ipc_JudyL, (Word_t)pid, PJE0);
15 - if (Pvalue)
16 - return *Pvalue;
17 - return NULL;
22 + if (Pvalue && JVALUE_IS_VALID(*Pvalue)) {
23 + *result = JVALUE_TO_IDX(*Pvalue);
24 + return true;
25 + }
26 + return false;
27 }
28
29 static bool ebpf_find_pid_shm_del_unsafe(uint32_t pid, enum ebpf_pids_index shm_idx)
30 {
22 - uint32_t *lpid = ebpf_shm_find_index_unsafe(pid);
23 - if (!lpid)
31 + uint32_t idx;
32 + if (!ebpf_shm_find_index_unsafe(pid, &idx))
33 return false;
34
26 - uint32_t idx = *lpid;
35 if (idx >= ebpf_stat_values.current)
36 return false;
37
@@ -35,17 +43,17 @@ static bool ebpf_find_pid_shm_del_unsafe(uint32_t pid, enum ebpf_pids_index shm_
43 if (ptr->threads)
44 return true;
45
38 - freez(lpid);
46 (void)JudyLDel(&ebpf_ipc_JudyL, (Word_t)pid, PJE0);
47 ebpf_stat_values.current--;
48
49 if (idx == ebpf_stat_values.current)
50 return false;
51
52 + // Compact: move last entry into the freed slot
53 uint32_t last_pid = integration_shm[ebpf_stat_values.current].pid;
46 - uint32_t *last_lpid = ebpf_shm_find_index_unsafe(last_pid);
47 - if (last_lpid) {
48 - *last_lpid = idx;
54 + Pvoid_t *Pvalue = JudyLGet(ebpf_ipc_JudyL, (Word_t)last_pid, PJE0);
55 + if (Pvalue && JVALUE_IS_VALID(*Pvalue)) {
56 + *Pvalue = IDX_TO_JVALUE(idx);
57 memcpy(ptr, &integration_shm[ebpf_stat_values.current], sizeof(*ptr));
58 }
59
@@ -54,9 +62,9 @@ static bool ebpf_find_pid_shm_del_unsafe(uint32_t pid, enum ebpf_pids_index shm_
62
63 static uint32_t ebpf_find_or_create_index_pid(uint32_t pid)
64 {
57 - uint32_t *idx = ebpf_shm_find_index_unsafe(pid);
58 - if (idx)
59 - return *idx;
65 + uint32_t idx;
66 + if (ebpf_shm_find_index_unsafe(pid, &idx))
67 + return idx;
68
69 if (ebpf_stat_values.current >= ebpf_stat_values.total)
70 return UINT32_MAX;
@@ -65,9 +73,7 @@ static uint32_t ebpf_find_or_create_index_pid(uint32_t pid)
73 internal_fatal(!Pvalue || Pvalue == PJERR, "EBPF: pid judy index");
74
75 uint32_t new_idx = ebpf_stat_values.current++;
68 - uint32_t *stored_idx = callocz(1, sizeof(uint32_t));
69 - *stored_idx = new_idx;
70 - *Pvalue = stored_idx;
76 + *Pvalue = IDX_TO_JVALUE(new_idx);
77
78 return new_idx;
79 }
@@ -108,16 +114,8 @@ void netdata_integration_cleanup_shm()
114 integration_shm = NULL;
115 }
116
111 - Word_t index = 0;
112 - Word_t next_index;
113 - PPvoid_t pid_ptr;
114 - while ((pid_ptr = JudyLFirst(ebpf_ipc_JudyL, &index, PJE0)) != NULL) {
115 - uint32_t *pid = *(uint32_t **)pid_ptr;
116 - next_index = index;
117 - freez(pid);
118 - JudyLDel(&ebpf_ipc_JudyL, next_index, PJE0);
119 - index = next_index;
120 - }
117 + // Values are stored inline (no heap allocation), just free the Judy array
118 + (void)JudyLFreeArray(&ebpf_ipc_JudyL, PJE0);
119 ebpf_ipc_JudyL = NULL;
120
121 if (shm_fd_ebpf_integration > 0) {
src/collectors/ebpf.plugin/ebpf.c
+19
@@ -2299,6 +2299,19 @@ static void ebpf_signal_stop_handler(int sig)
2299 */
2300 int main(int argc, char **argv)
2301 {
2302 + // Reduce memory footprint:
2303 + // - Single malloc arena avoids fragmentation across 24+ threads
2304 + // - Allocations >1MB use mmap so they're returned to OS on free,
2305 + // preventing 30MB+ of brk heap holes from libbpf's temp buffers
2306 + // - THP disabled prevents 2MB huge page waste in sparse allocations
2307 +#if defined(HAVE_C_MALLOPT)
2308 + mallopt(M_ARENA_MAX, 1);
2309 + mallopt(M_MMAP_THRESHOLD, 1024 * 1024);
2310 +#endif
2311 +#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_THP_DISABLE)
2312 + prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0);
2313 +#endif
2314 +
2315 nd_log_initialize_for_external_plugins(NETDATA_EBPF_PLUGIN_NAME);
2316 netdata_threads_init_for_external_plugins(0);
2317
@@ -2373,6 +2386,12 @@ int main(int argc, char **argv)
2386 }
2387 }
2388
2389 + // BPF loading allocated ~50MB then freed most of it.
2390 + // glibc keeps freed pages in its free list; trim returns them to the OS.
2391 +#if defined(HAVE_C_MALLOC_TRIM)
2392 + malloc_trim(0);
2393 +#endif
2394 +
2395 heartbeat_t hb;
2396 heartbeat_init(&hb, USEC_PER_SEC);
2397 int update_apps_every = (int)EBPF_CFG_UPDATE_APPS_EVERY_DEFAULT;