@cryptotaxi247 / netdata-1 / commits / bd385505c

ebpf.plugin: fix PID accounting shared-memory pool leak and 100% CPU spin (#22232)

* ebpf.plugin: fix PID accounting shared-memory pool leak and 100% CPU spin The eBPF plugin's per-PID shared-memory pool (32,768 slots backing /dev/shm/netdata_shm_integration_ebpf) monotonically fills on any host with normal process churn, and once the pool is exhausted each module's apps-read loop spins one CPU core at 100% indefinitely. ROOT CAUSE netdata_ebpf_get_shm_pointer_unsafe() is an allocate-AND-set-bit API, but it is called from two incompatible contexts: 1. The per-module BPF map iteration (ebpf_read_*_apps_table), which is paired with netdata_ebpf_reset_shm_pointer_unsafe() via the kill(pid, 0) == ESRCH branch. This is a correct producer/consumer. 2. The apps and cgroup aggregation helpers (*_sum_pids, *_update_*_cgroup, *_resume_apps_data), which iterate live-PID lists built from /proc and cgroup snapshots and call the same allocating accessor. These paths have NO reset counterpart. Every second, for every live PID seen in /proc or a tracked cgroup, every enabled eBPF module unconditionally sets its bit in the shm slot for that PID. A dead PID's bit is only cleared by the owning module's BPF-map iteration - but the BPF map's entry was already deleted in the same cycle (by bpf_map_delete_elem inside reset_shm_pointer_unsafe), so the next iteration cannot find the PID and the bit is stuck forever. The slot therefore never reaches threads == 0 and is never compacted out. Over 15h on a workstation with ~1,300 live PIDs the pool reaches 100% with ~96% stale entries. Once the pool is full, get_shm_pointer_unsafe() returns NULL. Every apps_read loop had a bare `continue;` after the NULL check, which skipped the `key = next_key;` advance, so bpf_map_get_next_key() re-returned the same key forever and the thread spun at 100% CPU. FIXES A. Split allocation from lookup. Introduce netdata_ebpf_lookup_shm_pointer_unsafe(pid) that returns the existing slot or NULL and sets no bit. Replace the 16 aggregation call sites with the new API. Keep get_shm_pointer_unsafe() at the 8 BPF-map iteration call sites where allocation is legitimate and paired with reset. Aggregation paths additionally gate on the module bit being set so they do not consume zero-initialised per-module sub-structs. B. Replace `continue;` with `goto end_*_loop;` in all 8 apps-read loops so `key = next_key;` always runs, preventing the infinite loop even when the pool is legitimately exhausted. C. Zero freshly-allocated slots. ebpf_find_or_create_index_pid() now memset()s the slot on the create branch, so compacted tails and PID reuse cannot leak stale bits or stale per-module counters into new occupants. D. Use the correct kill() error check. Five modules used `if (kill(pid, 0))`, which treats EPERM (cross-UID processes) as "process is dead" and erroneously deletes the live process's kernel BPF map entry and zeroes its shm data. Switched all of them to `if (kill(pid, 0) == -1 && errno == ESRCH)`, matching the two modules that already did it correctly. Also applied to ebpf_parse_proc_files(). E. Collapse ebpf_reset_specific_pid_data() to its effective body. thread_collecting only ever has NETDATA_EBPF_PIDS_PROC_FILE set, so the `idx < PROC_FILE` switch was dead code. The function now just forwards to ebpf_del_pid_entry(). The only other reader of that bitmask, ebpf_release_pid_data(), had zero callers and was removed. F. Sweep the shm pool on module exit. New helper netdata_ebpf_sweep_shm_for_module_unsafe() clears this module's bit across every shm slot and lets the existing compaction path release slots that become empty. All eight per-PID modules now call it under the shm semaphore in their *_exit path, so runtime module restarts no longer leak bits. VERIFICATION - Root cause was captured on a live ebpf.plugin pinned at 64% CPU. gdb showed an EBPF_READ_FD thread permanently alternating bpf_map_get_next_key/bpf_map_lookup_elem at ebpf_fd.c:770-774, and ebpf_stat_values = {total = 32768, current = 32768} while ps showed 1,307 live PIDs. - The diagnosis was independently confirmed by a parallel review (codex, qwen, glm, minimax, opus), all converging on the alloc-without-reset asymmetry in the aggregation paths. - The modified tree builds cleanly (`cmake --build build --target ebpf.plugin`). No new warnings in the changed files; only the pre-existing vendored-Judy stringop-overflow warnings remain. * ebpf.plugin: wipe shm on init, unlink on cleanup, drop unused out-param Close two residual issues around the per-PID shared-memory integration pool, spotted post-merge: 1. The POSIX shm object `netdata_shm_integration_ebpf` was neither truncated on open nor unlinked on shutdown. `shm_open(..., O_CREAT)` on an existing object just reopens it, and `ftruncate` to the previous size is a no-op, so the 14 MB mapped region retained the previous plugin run's bytes. The slot-allocation path memsets each new slot, so this was not a correctness bug — but every slot in `[current, total)` kept stale PID numbers and per-module counters until system reboot. Now: - `netdata_integration_initialize_shm()` memsets the whole mapped region immediately after `nd_mmap`, so the next plugin instance always starts from a clean slab. - `netdata_integration_cleanup_shm()` calls `shm_unlink` so a clean shutdown removes the backing object from `/dev/shm`. 2. Drop the unused `bool *created` out-parameter on `ebpf_find_or_create_index_pid()`. It was added when the memset was factored out of the allocation path; since the memset now lives inside the function, no caller needs to know whether the slot was freshly allocated. Reported by Copilot on PR #22232. * ebpf.plugin: address Copilot findings on 22232 Four residual issues spotted by Copilot after the initial round of changes. All valid. 1. get_shm_pointer_unsafe() no longer bails out when the pool is full. The caller path for an already-tracked PID must still reach its slot so module bits can be updated or cleared. The inner ebpf_find_or_create_index_pid() already returns existing slots unconditionally and only rejects *new* allocations when full, so the redundant outer guard was both dead weight and actively harmful — it made existing PIDs unreachable the moment the pool filled. 2. Semaphore lifecycle. netdata_integration_initialize_shm() now sem_unlinks NETDATA_EBPF_SHM_INTEGRATION_NAME before sem_open, and netdata_integration_cleanup_shm() sem_unlinks on the way out. If a previous plugin instance crashed while holding the semaphore, sem_open(O_CREAT) on the same name would reuse the existing semaphore at its last value (O_CREAT's initial-value argument is ignored when the semaphore already exists), and the next run would spin on sem_timedwait() timeouts forever. Unlinking guarantees the initial value of 1 is honoured. 3. Socket apps iteration: the kernel-map delete for a dead socket tuple used to run inside `if (local_pid) { ... }`. When the shm pool is full and local_pid is NULL, `deleted` sockets were left in the BPF map to be re-iterated every cycle, growing the map unbounded. Moved bpf_map_delete_elem(fd, &key) outside the local_pid branch; it is conditioned only on `deleted`, which captures the socket's own freshness signal and is independent of shm pool state. (For SOCKET_IDX the generic reset_shm_pointer_unsafe() intentionally skips the delete, since the socket map is keyed by a tuple, not by pid — comment added.)

Costa Tsaousis committed Apr 21, 2026 at 12:12 UTC bd385505c111ee6684738878b84cac7e113edfb6
12 files changed +201 -119
src/collectors/collectors-ipc/ebpf-ipc.c
+75 -1
@@ -60,6 +60,10 @@ static bool ebpf_find_pid_shm_del_unsafe(uint32_t pid, enum ebpf_pids_index shm_
60 return false;
61 }
62
63 +// Returns the slot index for pid, allocating a new slot if needed. A fresh
64 +// slot is memset to zero so callers never inherit stale bits or counters from
65 +// a prior PID that used the same index (prevents the compaction stale-tail
66 +// and PID-reuse contamination paths).
67 static uint32_t ebpf_find_or_create_index_pid(uint32_t pid)
68 {
69 uint32_t idx;
@@ -75,6 +79,8 @@ static uint32_t ebpf_find_or_create_index_pid(uint32_t pid)
79 uint32_t new_idx = ebpf_stat_values.current++;
80 *Pvalue = IDX_TO_JVALUE(new_idx);
81
82 + memset(&integration_shm[new_idx], 0, sizeof(integration_shm[new_idx]));
83 +
84 return new_idx;
85 }
86
@@ -88,9 +94,13 @@ bool netdata_ebpf_reset_shm_pointer_unsafe(int fd, uint32_t pid, enum ebpf_pids_
94
95 netdata_ebpf_pid_stats_t *netdata_ebpf_get_shm_pointer_unsafe(uint32_t pid, enum ebpf_pids_index idx)
96 {
91 - if (!integration_shm || ebpf_stat_values.current >= ebpf_stat_values.total)
97 + if (!integration_shm)
98 return NULL;
99
100 + // Do NOT short-circuit on a full pool here: an already-tracked PID
101 + // must still be reachable so its module bits can be updated or
102 + // cleared. ebpf_find_or_create_index_pid() returns the existing slot
103 + // regardless of pool saturation and only rejects *new* allocations.
104 uint32_t shm_idx = ebpf_find_or_create_index_pid(pid);
105 if (shm_idx == UINT32_MAX || shm_idx >= ebpf_stat_values.total)
106 return NULL;
@@ -102,6 +112,52 @@ netdata_ebpf_pid_stats_t *netdata_ebpf_get_shm_pointer_unsafe(uint32_t pid, enum
112 return ptr;
113 }
114
115 +// Read-only lookup: returns the existing slot for pid or NULL. Does not
116 +// allocate, does not set any bit. Aggregation paths that iterate PID lists
117 +// from /proc or cgroup snapshots MUST use this variant, otherwise every live
118 +// PID acquires module bits for modules that may never observe it in their own
119 +// BPF map and the bits can never be cleared — the shm pool then fills
120 +// monotonically.
121 +netdata_ebpf_pid_stats_t *netdata_ebpf_lookup_shm_pointer_unsafe(uint32_t pid)
122 +{
123 + if (!integration_shm)
124 + return NULL;
125 +
126 + uint32_t shm_idx;
127 + if (!ebpf_shm_find_index_unsafe(pid, &shm_idx))
128 + return NULL;
129 +
130 + if (shm_idx >= ebpf_stat_values.current)
131 + return NULL;
132 +
133 + return &integration_shm[shm_idx];
134 +}
135 +
136 +// Module teardown helper: clear this module's bit across every slot that has
137 +// it set. For slots that become empty the existing del path compacts in place,
138 +// which swaps the last slot into the freed index — so we do not advance i when
139 +// the current counter drops.
140 +void netdata_ebpf_sweep_shm_for_module_unsafe(enum ebpf_pids_index idx)
141 +{
142 + if (!integration_shm)
143 + return;
144 +
145 + const uint32_t mask = (1U << (idx << 1));
146 + uint32_t i = 0;
147 + while (i < ebpf_stat_values.current) {
148 + netdata_ebpf_pid_stats_t *ptr = &integration_shm[i];
149 + if (!(ptr->threads & mask)) {
150 + i++;
151 + continue;
152 + }
153 +
154 + uint32_t before = ebpf_stat_values.current;
155 + (void)ebpf_find_pid_shm_del_unsafe(ptr->pid, idx);
156 + if (ebpf_stat_values.current >= before)
157 + i++;
158 + }
159 +}
160 +
161 void netdata_integration_cleanup_shm()
162 {
163 if (shm_mutex_ebpf_integration != SEM_FAILED) {
@@ -122,6 +178,15 @@ void netdata_integration_cleanup_shm()
178 close(shm_fd_ebpf_integration);
179 shm_fd_ebpf_integration = -1;
180 }
181 +
182 + // Drop the POSIX shm object and the named semaphore so a subsequent
183 + // plugin run starts from a fresh region and a freshly-initialised
184 + // semaphore. Without the sem_unlink, a crashed previous instance can
185 + // leave the semaphore at 0 and the next run will spin on
186 + // sem_timedwait timeouts (sem_open(O_CREAT) ignores the initial value
187 + // when the named semaphore already exists).
188 + (void)shm_unlink(NETDATA_EBPF_INTEGRATION_NAME);
189 + (void)sem_unlink(NETDATA_EBPF_SHM_INTEGRATION_NAME);
190 }
191
192 int netdata_integration_initialize_shm(size_t pids)
@@ -153,6 +218,15 @@ int netdata_integration_initialize_shm(size_t pids)
218 goto end_shm;
219 }
220
221 + // Wipe any bytes left over from a prior plugin run — shm_open with
222 + // O_CREAT on an existing object does not truncate, and ftruncate to the
223 + // current size is a no-op.
224 + memset(integration_shm, 0, length);
225 +
226 + // Drop any leftover named semaphore from a previous (possibly crashed)
227 + // run so sem_open honours the initial value below instead of reusing
228 + // whatever state the previous instance left it in.
229 + (void)sem_unlink(NETDATA_EBPF_SHM_INTEGRATION_NAME);
230 shm_mutex_ebpf_integration = sem_open(
231 NETDATA_EBPF_SHM_INTEGRATION_NAME, O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, 1);
232 if (shm_mutex_ebpf_integration != SEM_FAILED) {
src/collectors/collectors-ipc/ebpf-ipc.h
+2
@@ -339,7 +339,9 @@ typedef struct netdata_ebpf_pid_stats {
339 int netdata_integration_initialize_shm(size_t pids);
340 void netdata_integration_cleanup_shm();
341 netdata_ebpf_pid_stats_t *netdata_ebpf_get_shm_pointer_unsafe(uint32_t pid, enum ebpf_pids_index idx);
342 +netdata_ebpf_pid_stats_t *netdata_ebpf_lookup_shm_pointer_unsafe(uint32_t pid);
343 bool netdata_ebpf_reset_shm_pointer_unsafe(int fd, uint32_t pid, enum ebpf_pids_index idx);
344 +void netdata_ebpf_sweep_shm_for_module_unsafe(enum ebpf_pids_index idx);
345 void netdata_integration_current_ipc_data(ebpf_user_mem_stat_t *values);
346
347 extern sem_t *shm_mutex_ebpf_integration;
src/collectors/ebpf.plugin/ebpf_apps.c
+1 -1
@@ -914,7 +914,7 @@ void ebpf_parse_proc_files()
914 if (ebpf_plugin_stop())
915 break;
916
917 - if (kill(pids->pid, 0)) { // No PID found
917 + if (kill(pids->pid, 0) == -1 && errno == ESRCH) {
918 ebpf_pid_data_t *next = pids->next;
919 ebpf_reset_specific_pid_data(pids);
920 pids = next;
src/collectors/ebpf.plugin/ebpf_apps.h
+7 -57
@@ -188,65 +188,15 @@ static inline ebpf_pid_data_t *ebpf_get_pid_data(uint32_t pid, uint32_t tgid, ch
188 return ptr;
189 }
190
191 -static inline void ebpf_release_pid_data(ebpf_pid_data_t *eps, int fd, uint32_t key, uint32_t idx)
192 -{
193 - if (fd) {
194 - bpf_map_delete_elem(fd, &key);
195 - }
196 - eps->thread_collecting &= ~(1 << idx);
197 - if (!eps->thread_collecting && !eps->has_proc_file) {
198 - ebpf_del_pid_entry((pid_t)key);
199 - }
200 -}
201 -
191 +// The only caller of ebpf_get_pid_data() passes NETDATA_EBPF_PIDS_PROC_FILE,
192 +// so `thread_collecting` in an ebpf_pid_data_t only ever has that single high
193 +// bit set. The per-module (idx < PROC_FILE) branch in the old
194 +// ebpf_reset_specific_pid_data() was therefore unreachable. Collapse the
195 +// function to its effective behaviour so a future reader is not confused by
196 +// dead BPF/freez housekeeping that never ran.
197 static inline void ebpf_reset_specific_pid_data(ebpf_pid_data_t *ptr)
198 {
204 - int idx;
205 - uint32_t pid = ptr->pid;
206 - for (idx = NETDATA_EBPF_PIDS_PROCESS_IDX; idx < NETDATA_EBPF_PIDS_PROC_FILE; idx++) {
207 - if (!(ptr->thread_collecting & (1 << idx))) {
208 - continue;
209 - }
210 - // Check if we still have the map loaded
211 - int fd = ebpf_get_pid_map_fd(idx);
212 - if (fd <= STDERR_FILENO)
213 - continue;
214 -
215 - bpf_map_delete_elem(fd, &pid);
216 - ebpf_hash_table_pids_count--;
217 - void *clean;
218 - switch (idx) {
219 - case NETDATA_EBPF_PIDS_PROCESS_IDX:
220 - clean = ptr->process;
221 - break;
222 - case NETDATA_EBPF_PIDS_SOCKET_IDX:
223 - clean = ptr->socket;
224 - break;
225 - case NETDATA_EBPF_PIDS_CACHESTAT_IDX:
226 - clean = ptr->cachestat;
227 - break;
228 - case NETDATA_EBPF_PIDS_DCSTAT_IDX:
229 - clean = ptr->dc;
230 - break;
231 - case NETDATA_EBPF_PIDS_SWAP_IDX:
232 - clean = ptr->swap;
233 - break;
234 - case NETDATA_EBPF_PIDS_VFS_IDX:
235 - clean = ptr->vfs;
236 - break;
237 - case NETDATA_EBPF_PIDS_FD_IDX:
238 - clean = ptr->fd;
239 - break;
240 - case NETDATA_EBPF_PIDS_SHM_IDX:
241 - clean = ptr->shm;
242 - break;
243 - default:
244 - clean = NULL;
245 - }
246 - freez(clean);
247 - }
248 -
249 - ebpf_del_pid_entry(pid);
199 + ebpf_del_pid_entry(ptr->pid);
200 }
201
202 typedef struct ebpf_pid_stat {
src/collectors/ebpf.plugin/ebpf_cachestat.c
+13 -8
@@ -602,6 +602,13 @@ static void ebpf_cachestat_exit(void *pptr)
602 if (ebpf_read_cachestat.thread)
603 nd_thread_signal_cancel(ebpf_read_cachestat.thread);
604
605 + // Drop this module's bits from the shared PID pool so its slots don't
606 + // stay pinned if the plugin keeps running after the module stops.
607 + if (integration_shm && ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
608 + netdata_ebpf_sweep_shm_for_module_unsafe(NETDATA_EBPF_PIDS_CACHESTAT_IDX);
609 + sem_post(shm_mutex_ebpf_integration);
610 + }
611 +
612 if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
613 netdata_mutex_lock(&lock);
614 if (em->cgroup_charts) {
@@ -834,13 +841,13 @@ static void ebpf_read_cachestat_apps_table(int maps_per_core)
841
842 netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(key, NETDATA_EBPF_PIDS_CACHESTAT_IDX);
843 if (!local_pid)
837 - continue;
844 + goto end_cachestat_loop;
845 netdata_publish_cachestat_t *publish = &local_pid->cachestat;
846
847 if (!publish->ct || publish->ct != cv->ct) {
848 cachestat_save_pid_values(publish, cv);
849 } else {
843 - if (kill((pid_t)key, 0)) { // No PID found
850 + if (kill((pid_t)key, 0) == -1 && errno == ESRCH) {
851 if (netdata_ebpf_reset_shm_pointer_unsafe(fd, key, NETDATA_EBPF_PIDS_CACHESTAT_IDX))
852 memset(publish, 0, sizeof(*publish));
853 }
@@ -876,9 +883,8 @@ static void ebpf_update_cachestat_cgroup()
883 uint32_t pid = pids->pid;
884 netdata_publish_cachestat_t *out = &pids->cachestat;
885
879 - netdata_ebpf_pid_stats_t *local_pid =
880 - netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_CACHESTAT_IDX);
881 - if (!local_pid)
886 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
887 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_CACHESTAT_IDX << 1))))
888 continue;
889
890 netdata_publish_cachestat_t *in = &local_pid->cachestat;
@@ -916,9 +922,8 @@ static void cachestat_sum_pids_internal(netdata_publish_cachestat_t *publish, vo
922 break;
923
924 uint32_t pid = r->pid;
919 - netdata_ebpf_pid_stats_t *local_pid =
920 - netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_CACHESTAT_IDX);
921 - if (!local_pid)
925 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
926 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_CACHESTAT_IDX << 1))))
927 continue;
928 netdata_publish_cachestat_t *w = &local_pid->cachestat;
929 sum_single_pid_cachestat(dst, &w->current);
src/collectors/ebpf.plugin/ebpf_dcstat.c
+12 -6
@@ -498,6 +498,13 @@ static void ebpf_dcstat_exit(void *pptr)
498 if (ebpf_read_dcstat.thread)
499 nd_thread_signal_cancel(ebpf_read_dcstat.thread);
500
501 + // Drop this module's bits from the shared PID pool so its slots don't
502 + // stay pinned if the plugin keeps running after the module stops.
503 + if (integration_shm && ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
504 + netdata_ebpf_sweep_shm_for_module_unsafe(NETDATA_EBPF_PIDS_DCSTAT_IDX);
505 + sem_post(shm_mutex_ebpf_integration);
506 + }
507 +
508 if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
509 netdata_mutex_lock(&lock);
510 if (em->cgroup_charts) {
@@ -588,7 +595,7 @@ static void ebpf_read_dc_apps_table(int maps_per_core)
595
596 netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(key, NETDATA_EBPF_PIDS_DCSTAT_IDX);
597 if (!local_pid)
591 - continue;
598 + goto end_dc_loop;
599 netdata_publish_dcstat_t *publish = &local_pid->directory_cache;
600 if (!publish->ct || publish->ct != cv->ct) {
601 publish->ct = cv->ct;
@@ -625,8 +632,8 @@ void ebpf_dcstat_sum_pids(netdata_publish_dcstat_t *publish, struct ebpf_pid_on_
632 break;
633
634 uint32_t pid = root->pid;
628 - netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_DCSTAT_IDX);
629 - if (!local_pid)
635 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
636 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_DCSTAT_IDX << 1))))
637 continue;
638 netdata_publish_dcstat_t *w = &local_pid->directory_cache;
639
@@ -680,9 +687,8 @@ static void ebpf_update_dc_cgroup()
687 for (pids = ect->pids; pids; pids = pids->next) {
688 uint32_t pid = pids->pid;
689 netdata_dcstat_pid_t *out = &pids->dc;
683 - netdata_ebpf_pid_stats_t *local_pid =
684 - netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_DCSTAT_IDX);
685 - if (!local_pid)
690 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
691 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_DCSTAT_IDX << 1))))
692 continue;
693 netdata_publish_dcstat_t *in = &local_pid->directory_cache;
694
src/collectors/ebpf.plugin/ebpf_fd.c
+12 -5
@@ -635,6 +635,13 @@ static void ebpf_fd_exit(void *pptr)
635 nd_thread_join(ebpf_read_fd.thread);
636 }
637
638 + // Drop this module's bits from the shared PID pool so its slots don't
639 + // stay pinned if the plugin keeps running after the module stops.
640 + if (integration_shm && ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
641 + netdata_ebpf_sweep_shm_for_module_unsafe(NETDATA_EBPF_PIDS_FD_IDX);
642 + sem_post(shm_mutex_ebpf_integration);
643 + }
644 +
645 if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
646 netdata_mutex_lock(&lock);
647 if (em->cgroup_charts) {
@@ -780,7 +787,7 @@ static void ebpf_read_fd_apps_table(int maps_per_core)
787
788 netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(key, NETDATA_EBPF_PIDS_FD_IDX);
789 if (!local_pid)
783 - continue;
790 + goto end_fd_loop;
791 netdata_publish_fd_stat_t *publish_fd = &local_pid->fd;
792
793 if (kill((pid_t)key, 0) == -1 && errno == ESRCH) {
@@ -815,8 +822,8 @@ static void ebpf_fd_sum_pids(netdata_fd_stat_t *fd, struct ebpf_pid_on_target *r
822
823 for (; root; root = root->next) {
824 uint32_t pid = root->pid;
818 - netdata_ebpf_pid_stats_t *pid_stat = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_FD_IDX);
819 - if (!pid_stat)
825 + netdata_ebpf_pid_stats_t *pid_stat = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
826 + if (!pid_stat || !(pid_stat->threads & (1U << (NETDATA_EBPF_PIDS_FD_IDX << 1))))
827 continue;
828 netdata_publish_fd_stat_t *w = &pid_stat->fd;
829
@@ -868,8 +875,8 @@ static void ebpf_update_fd_cgroup(void)
875 uint32_t pid = pids->pid;
876 netdata_publish_fd_stat_t *out = &pids->fd;
877
871 - netdata_ebpf_pid_stats_t *pid_stat = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_FD_IDX);
872 - if (!pid_stat)
878 + netdata_ebpf_pid_stats_t *pid_stat = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
879 + if (!pid_stat || !(pid_stat->threads & (1U << (NETDATA_EBPF_PIDS_FD_IDX << 1))))
880 continue;
881
882 netdata_publish_fd_stat_t *in = &pid_stat->fd;
src/collectors/ebpf.plugin/ebpf_process.c
+13 -7
@@ -464,9 +464,8 @@ static void ebpf_update_process_cgroup()
464 for (pids = ect->pids; pids; pids = pids->next) {
465 uint32_t pid = pids->pid;
466 ebpf_publish_process_t *out = &pids->ps;
467 - netdata_ebpf_pid_stats_t *local_pid =
468 - netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_PROCESS_IDX);
469 - if (!local_pid)
467 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
468 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_PROCESS_IDX << 1))))
469 continue;
470
471 ebpf_publish_process_t *in = &local_pid->process;
@@ -971,6 +970,13 @@ static void ebpf_process_exit(void *pptr)
970 collect_pids &= ~(1 << EBPF_MODULE_PROCESS_IDX);
971 netdata_mutex_unlock(&lock);
972
973 + // Drop this module's bits from the shared PID pool so its slots don't
974 + // stay pinned if the plugin keeps running after the module stops.
975 + if (integration_shm && ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
976 + netdata_ebpf_sweep_shm_for_module_unsafe(NETDATA_EBPF_PIDS_PROCESS_IDX);
977 + sem_post(shm_mutex_ebpf_integration);
978 + }
979 +
980 if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
981 netdata_mutex_lock(&lock);
982 if (em->cgroup_charts) {
@@ -1508,8 +1514,8 @@ void ebpf_process_sum_values_for_pids(ebpf_process_stat_t *process, struct ebpf_
1514 break;
1515
1516 uint32_t pid = root->pid;
1511 - netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_PROCESS_IDX);
1512 - if (!local_pid)
1517 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
1518 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_PROCESS_IDX << 1))))
1519 continue;
1520
1521 ebpf_publish_process_t *in = &local_pid->process;
@@ -1556,7 +1562,7 @@ void collect_data_for_all_processes(int tbl_pid_stats_fd, int maps_per_core)
1562 netdata_ebpf_pid_stats_t *local_pid =
1563 netdata_ebpf_get_shm_pointer_unsafe(key, NETDATA_EBPF_PIDS_PROCESS_IDX);
1564 if (!local_pid)
1559 - continue;
1565 + goto end_process_loop;
1566
1567 ebpf_publish_process_t *w = &local_pid->process;
1568
@@ -1568,7 +1574,7 @@ void collect_data_for_all_processes(int tbl_pid_stats_fd, int maps_per_core)
1574 w->release_call = process_stat_vector[0].release_call;
1575 w->task_err = process_stat_vector[0].task_err;
1576 } else {
1571 - if (kill((pid_t)key, 0)) { // No PID found
1577 + if (kill((pid_t)key, 0) == -1 && errno == ESRCH) {
1578 if (netdata_ebpf_reset_shm_pointer_unsafe(tbl_pid_stats_fd, key, NETDATA_EBPF_PIDS_PROCESS_IDX))
1579 memset(w, 0, sizeof(*w));
1580 }
src/collectors/ebpf.plugin/ebpf_shm.c
+13 -6
@@ -481,6 +481,13 @@ static void ebpf_shm_exit(void *pptr)
481 nd_thread_join(ebpf_read_shm.thread);
482 }
483
484 + // Drop this module's bits from the shared PID pool so its slots don't
485 + // stay pinned if the plugin keeps running after the module stops.
486 + if (integration_shm && ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
487 + netdata_ebpf_sweep_shm_for_module_unsafe(NETDATA_EBPF_PIDS_SHM_IDX);
488 + sem_post(shm_mutex_ebpf_integration);
489 + }
490 +
491 if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
492 netdata_mutex_lock(&lock);
493 if (em->cgroup_charts) {
@@ -559,8 +566,8 @@ static void ebpf_update_shm_cgroup(void)
566 for (pids = ect->pids; pids; pids = pids->next) {
567 uint32_t pid = pids->pid;
568 netdata_publish_shm_t *out = &pids->shm;
562 - netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_SHM_IDX);
563 - if (!local_pid)
569 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
570 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_SHM_IDX << 1))))
571 continue;
572
573 netdata_publish_shm_t *in = &local_pid->shm;
@@ -599,13 +606,13 @@ static void ebpf_read_shm_apps_table(int maps_per_core)
606
607 netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(key, NETDATA_EBPF_PIDS_SHM_IDX);
608 if (!local_pid)
602 - continue;
609 + goto end_shm_loop;
610 netdata_publish_shm_t *publish = &local_pid->shm;
611
612 if (!publish->ct || publish->ct != cv->ct) {
613 memcpy(publish, &cv[0], sizeof(netdata_publish_shm_t));
614 } else {
608 - if (kill((pid_t)key, 0)) { // No PID found
615 + if (kill((pid_t)key, 0) == -1 && errno == ESRCH) {
616 if (netdata_ebpf_reset_shm_pointer_unsafe(fd, key, NETDATA_EBPF_PIDS_SHM_IDX))
617 memset(publish, 0, sizeof(*publish));
618 }
@@ -672,8 +679,8 @@ static void ebpf_shm_sum_pids(netdata_publish_shm_t *shm, struct ebpf_pid_on_tar
679 memset(shm, 0, sizeof(netdata_publish_shm_t));
680 for (; root; root = root->next) {
681 uint32_t pid = root->pid;
675 - netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_SHM_IDX);
676 - if (!local_pid)
682 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
683 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_SHM_IDX << 1))))
684 continue;
685
686 netdata_publish_shm_t *w = &local_pid->shm;
src/collectors/ebpf.plugin/ebpf_socket.c
+27 -16
@@ -930,6 +930,13 @@ static void ebpf_socket_exit(void *pptr)
930 nd_thread_join(ebpf_read_socket.thread);
931 }
932
933 + // Drop this module's bits from the shared PID pool so its slots don't
934 + // stay pinned if the plugin keeps running after the module stops.
935 + if (integration_shm && ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
936 + netdata_ebpf_sweep_shm_for_module_unsafe(NETDATA_EBPF_PIDS_SOCKET_IDX);
937 + sem_post(shm_mutex_ebpf_integration);
938 + }
939 +
940 if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
941 netdata_mutex_lock(&lock);
942
@@ -1890,17 +1897,23 @@ static void ebpf_update_array_vectors(ebpf_module_t *em)
1897 end_socket_loop:; // the empty statement is here to allow code to be compiled by old compilers
1898 netdata_ebpf_pid_stats_t *local_pid =
1899 netdata_ebpf_get_shm_pointer_unsafe(key.pid, NETDATA_EBPF_PIDS_SOCKET_IDX);
1893 - if (!local_pid)
1894 - continue;
1895 - ebpf_socket_publish_apps_t *curr = &local_pid->socket;
1896 -
1897 - if (!deleted)
1898 - ebpf_socket_fill_publish_apps(curr, values);
1899 - else {
1900 - netdata_ebpf_reset_shm_pointer_unsafe(fd, key.pid, NETDATA_EBPF_PIDS_SOCKET_IDX);
1901 - memset(curr, 0, sizeof(*curr));
1902 - bpf_map_delete_elem(fd, &key);
1900 + if (local_pid) {
1901 + ebpf_socket_publish_apps_t *curr = &local_pid->socket;
1902 +
1903 + if (!deleted)
1904 + ebpf_socket_fill_publish_apps(curr, values);
1905 + else {
1906 + netdata_ebpf_reset_shm_pointer_unsafe(fd, key.pid, NETDATA_EBPF_PIDS_SOCKET_IDX);
1907 + memset(curr, 0, sizeof(*curr));
1908 + }
1909 }
1910 + // The socket map is keyed by a tuple (not by pid), so the generic
1911 + // reset_shm_pointer_unsafe() deliberately skips bpf_map_delete_elem()
1912 + // for SOCKET_IDX. Delete the stale socket here regardless of whether
1913 + // we had a shm slot, otherwise a full shm pool would strand dead
1914 + // socket entries in the kernel map and we'd re-iterate them forever.
1915 + if (deleted)
1916 + bpf_map_delete_elem(fd, &key);
1917 memset(values, 0, length);
1918 memcpy(&key, &next_key, sizeof(key));
1919 }
@@ -1926,9 +1939,8 @@ void ebpf_socket_resume_apps_data()
1939 memset(&w->socket, 0, sizeof(ebpf_socket_publish_apps_t));
1940 for (; move; move = move->next) {
1941 uint32_t pid = move->pid;
1929 - netdata_ebpf_pid_stats_t *local_pid =
1930 - netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_SOCKET_IDX);
1931 - if (!local_pid)
1942 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
1943 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_SOCKET_IDX << 1))))
1944 continue;
1945
1946 ebpf_socket_publish_apps_t *ws = &local_pid->socket;
@@ -1965,9 +1977,8 @@ static void ebpf_update_socket_cgroup()
1977 for (pids = ect->pids; pids; pids = pids->next) {
1978 uint32_t pid = pids->pid;
1979 ebpf_socket_publish_apps_t *publish = &ect->publish_socket;
1968 - netdata_ebpf_pid_stats_t *local_pid =
1969 - netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_SOCKET_IDX);
1970 - if (!local_pid)
1980 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
1981 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_SOCKET_IDX << 1))))
1982 continue;
1983
1984 ebpf_socket_publish_apps_t *in = &local_pid->socket;
src/collectors/ebpf.plugin/ebpf_swap.c
+13 -6
@@ -452,6 +452,13 @@ static void ebpf_swap_exit(void *pptr)
452 nd_thread_join(ebpf_read_swap.thread);
453 }
454
455 + // Drop this module's bits from the shared PID pool so its slots don't
456 + // stay pinned if the plugin keeps running after the module stops.
457 + if (integration_shm && ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
458 + netdata_ebpf_sweep_shm_for_module_unsafe(NETDATA_EBPF_PIDS_SWAP_IDX);
459 + sem_post(shm_mutex_ebpf_integration);
460 + }
461 +
462 if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
463 netdata_mutex_lock(&lock);
464 if (em->cgroup_charts) {
@@ -537,8 +544,8 @@ static void ebpf_update_swap_cgroup(void)
544 for (pids = ect->pids; pids; pids = pids->next) {
545 uint32_t pid = pids->pid;
546 netdata_publish_swap_t *out = &pids->swap;
540 - netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_SWAP_IDX);
541 - if (!local_pid)
547 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
548 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_SWAP_IDX << 1))))
549 continue;
550 netdata_publish_swap_t *in = &local_pid->swap;
551
@@ -563,8 +570,8 @@ static void ebpf_swap_sum_pids(netdata_publish_swap_t *swap, struct ebpf_pid_on_
570
571 for (; root; root = root->next) {
572 uint32_t pid = root->pid;
566 - netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_SWAP_IDX);
567 - if (!local_pid)
573 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
574 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_SWAP_IDX << 1))))
575 continue;
576 netdata_publish_swap_t *w = &local_pid->swap;
577
@@ -624,13 +631,13 @@ static void ebpf_read_swap_apps_table(int maps_per_core)
631
632 netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(key, NETDATA_EBPF_PIDS_SWAP_IDX);
633 if (!local_pid)
627 - continue;
634 + goto end_swap_loop;
635 netdata_publish_swap_t *publish = &local_pid->swap;
636
637 if (!publish->ct || publish->ct != cv->ct) {
638 memcpy(publish, cv, sizeof(netdata_publish_swap_t));
639 } else {
633 - if (kill((pid_t)key, 0)) { // No PID found
640 + if (kill((pid_t)key, 0) == -1 && errno == ESRCH) {
641 if (netdata_ebpf_reset_shm_pointer_unsafe(fd, key, NETDATA_EBPF_PIDS_SWAP_IDX))
642 memset(publish, 0, sizeof(*publish));
643 }
src/collectors/ebpf.plugin/ebpf_vfs.c
+13 -6
@@ -909,6 +909,13 @@ static void ebpf_vfs_exit(void *pptr)
909 if (ebpf_read_vfs.thread)
910 nd_thread_signal_cancel(ebpf_read_vfs.thread);
911
912 + // Drop this module's bits from the shared PID pool so its slots don't
913 + // stay pinned if the plugin keeps running after the module stops.
914 + if (integration_shm && ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
915 + netdata_ebpf_sweep_shm_for_module_unsafe(NETDATA_EBPF_PIDS_VFS_IDX);
916 + sem_post(shm_mutex_ebpf_integration);
917 + }
918 +
919 if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
920 netdata_mutex_lock(&lock);
921 if (em->cgroup_charts) {
@@ -1135,8 +1142,8 @@ static void ebpf_vfs_sum_pids(netdata_publish_vfs_t *vfs, struct ebpf_pid_on_tar
1142
1143 for (; root; root = root->next) {
1144 uint32_t pid = root->pid;
1138 - netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_VFS_IDX);
1139 - if (!local_pid)
1145 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
1146 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_VFS_IDX << 1))))
1147 continue;
1148
1149 netdata_publish_vfs_t *w = &local_pid->vfs;
@@ -1303,13 +1310,13 @@ static void ebpf_vfs_read_apps(int maps_per_core)
1310
1311 netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(key, NETDATA_EBPF_PIDS_VFS_IDX);
1312 if (!local_pid)
1306 - continue;
1313 + goto end_vfs_loop;
1314 netdata_publish_vfs_t *publish = &local_pid->vfs;
1315
1316 if (!publish->ct || publish->ct != vv->ct) {
1317 vfs_aggregate_set_vfs(publish, vv);
1318 } else {
1312 - if (kill((pid_t)key, 0)) { // No PID found
1319 + if (kill((pid_t)key, 0) == -1 && errno == ESRCH) {
1320 if (netdata_ebpf_reset_shm_pointer_unsafe(fd, key, NETDATA_EBPF_PIDS_VFS_IDX))
1321 memset(publish, 0, sizeof(*publish));
1322 }
@@ -1343,8 +1350,8 @@ static void read_update_vfs_cgroup()
1350 netdata_publish_vfs_t *out = &pids->vfs;
1351 memset(out, 0, sizeof(netdata_publish_vfs_t));
1352
1346 - netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_get_shm_pointer_unsafe(pid, NETDATA_EBPF_PIDS_VFS_IDX);
1347 - if (!local_pid)
1353 + netdata_ebpf_pid_stats_t *local_pid = netdata_ebpf_lookup_shm_pointer_unsafe(pid);
1354 + if (!local_pid || !(local_pid->threads & (1U << (NETDATA_EBPF_PIDS_VFS_IDX << 1))))
1355 continue;
1356 netdata_publish_vfs_t *in = &local_pid->vfs;
1357