master
c 541 lines 16.2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include <sys/resource.h>
4
5 #include "ebpf.h"
6 #include "ebpf_cgroup.h"
7 #include "libbpf_api/ebpf_library.h"
8
9 ebpf_cgroup_target_t *ebpf_cgroup_pids = NULL;
10 _Atomic int send_cgroup_chart = 0;
11
12 #ifdef OS_LINUX
13 static nipc_cgroups_cache_t ebpf_cgroup_cache;
14 static bool ebpf_cgroup_cache_initialized = false;
15
16 static const char *ebpf_netipc_client_state_name(nipc_client_state_t state)
17 {
18 switch (state) {
19 case NIPC_CLIENT_DISCONNECTED:
20 return "disconnected";
21 case NIPC_CLIENT_CONNECTING:
22 return "connecting";
23 case NIPC_CLIENT_READY:
24 return "ready";
25 case NIPC_CLIENT_NOT_FOUND:
26 return "not-found";
27 case NIPC_CLIENT_AUTH_FAILED:
28 return "auth-failed";
29 case NIPC_CLIENT_INCOMPATIBLE:
30 return "incompatible";
31 case NIPC_CLIENT_BROKEN:
32 return "broken";
33 default:
34 return "unknown";
35 }
36 }
37
38 static const char *ebpf_netipc_profile_name(uint32_t profile)
39 {
40 switch (profile) {
41 case NIPC_PROFILE_BASELINE:
42 return "baseline";
43 case NIPC_PROFILE_SHM_HYBRID:
44 return "shm-hybrid";
45 case NIPC_PROFILE_SHM_FUTEX:
46 return "shm-futex";
47 default:
48 return "none";
49 }
50 }
51
52 static void ebpf_log_cgroup_transport_state(uint64_t generation, uint32_t count, uint32_t enabled_count)
53 {
54 static nipc_client_state_t previous_state = NIPC_CLIENT_DISCONNECTED;
55 static bool previous_session_valid = false;
56 static uint32_t previous_profile = 0;
57 static bool previous_using_shm = false;
58 static uint64_t previous_session_id = 0;
59
60 nipc_client_state_t state = ebpf_cgroup_cache.client.state;
61 bool session_valid = ebpf_cgroup_cache.client.session_valid;
62 uint32_t profile = session_valid ? ebpf_cgroup_cache.client.session.selected_profile : 0;
63 bool using_shm = session_valid && ebpf_cgroup_cache.client.shm != NULL;
64 uint64_t session_id = session_valid ? ebpf_cgroup_cache.client.session.session_id : 0;
65
66 if (previous_state == state &&
67 previous_session_valid == session_valid &&
68 previous_profile == profile &&
69 previous_using_shm == using_shm &&
70 previous_session_id == session_id)
71 return;
72
73 collector_info(
74 "EBPF CGROUP: netipc transport state=%s session_valid=%d session=%016llx "
75 "selected_profile=%s data_plane=%s generation=%llu items=%u enabled=%u",
76 ebpf_netipc_client_state_name(state),
77 session_valid,
78 (unsigned long long)session_id,
79 ebpf_netipc_profile_name(profile),
80 using_shm ? "shm" : (session_valid ? "baseline" : "none"),
81 (unsigned long long)generation,
82 count,
83 enabled_count);
84
85 previous_state = state;
86 previous_session_valid = session_valid;
87 previous_profile = profile;
88 previous_using_shm = using_shm;
89 previous_session_id = session_id;
90 }
91 #endif
92
93 // --------------------------------------------------------------------------------------------------------------------
94 // Close and Cleanup
95
96 /**
97 * Clean Specific cgroup pid
98 *
99 * Clean all PIDs associated with cgroup.
100 *
101 * @param pt structure pid on target that will have your PRs removed
102 */
103 static inline void ebpf_clean_specific_cgroup_pids(struct pid_on_target2 *pt)
104 {
105 while (pt) {
106 struct pid_on_target2 *next_pid = pt->next;
107
108 freez(pt);
109 pt = next_pid;
110 }
111 }
112
113 /**
114 * Remove Cgroup Update Target Update List
115 *
116 * Remove from cgroup target and update the link list
117 */
118 static void ebpf_remove_cgroup_target_update_list()
119 {
120 ebpf_cgroup_target_t *next, *ect = ebpf_cgroup_pids;
121 ebpf_cgroup_target_t *prev = ebpf_cgroup_pids;
122 while (ect) {
123 next = ect->next;
124 if (!ect->updated) {
125 if (ect == ebpf_cgroup_pids) {
126 ebpf_cgroup_pids = next;
127 prev = next;
128 } else {
129 prev->next = next;
130 }
131
132 ebpf_clean_specific_cgroup_pids(ect->pids);
133 freez(ect);
134 } else {
135 prev = ect;
136 }
137
138 ect = next;
139 }
140 }
141
142 static size_t ebpf_count_cgroup_targets_unsafe(void)
143 {
144 size_t count = 0;
145
146 for (ebpf_cgroup_target_t *ect = ebpf_cgroup_pids; ect; ect = ect->next)
147 count++;
148
149 return count;
150 }
151
152 static size_t ebpf_count_cgroup_pids_unsafe(void)
153 {
154 size_t count = 0;
155
156 for (ebpf_cgroup_target_t *ect = ebpf_cgroup_pids; ect; ect = ect->next) {
157 for (struct pid_on_target2 *pt = ect->pids; pt; pt = pt->next)
158 count++;
159 }
160
161 return count;
162 }
163
164 // --------------------------------------------------------------------------------------------------------------------
165 // Fill variables
166
167 /**
168 * Set Target Data
169 *
170 * Set local variable values from a netipc cache item.
171 *
172 * @param out local output variable.
173 * @param item netipc cache item.
174 */
175 static inline void ebpf_cgroup_set_target_data(ebpf_cgroup_target_t *out, const nipc_cgroups_cache_item_t *item)
176 {
177 out->hash = item->hash;
178 snprintfz(out->name, 255, "%s", item->name);
179 out->systemd = item->options & CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE;
180 out->updated = 1;
181 }
182
183 /**
184 * Find or create
185 *
186 * Find the structure inside the link list or allocate and link when it is not present.
187 *
188 * @param item netipc cache item.
189 *
190 * @return It returns a pointer for the structure associated with the input.
191 */
192 static ebpf_cgroup_target_t *ebpf_cgroup_find_or_create(const nipc_cgroups_cache_item_t *item)
193 {
194 for (ebpf_cgroup_target_t *ect = ebpf_cgroup_pids; ect; ect = ect->next) {
195 if (ect->hash == item->hash && !strcmp(ect->name, item->name)) {
196 ect->updated = 1;
197 return ect;
198 }
199 }
200
201 ebpf_cgroup_target_t *new_ect = callocz(1, sizeof(*new_ect));
202 ebpf_cgroup_set_target_data(new_ect, item);
203 new_ect->next = ebpf_cgroup_pids;
204 ebpf_cgroup_pids = new_ect;
205
206 return new_ect;
207 }
208
209 /**
210 * Update pid link list
211 *
212 * Update PIDs list associated with specific cgroup.
213 *
214 * @param ect cgroup structure where pids will be stored
215 * @param path file with PIDs associated to cgroup.
216 */
217 static void ebpf_update_pid_link_list(ebpf_cgroup_target_t *ect, const char *path)
218 {
219 procfile *ff = procfile_open_no_log(path, " \t:", PROCFILE_FLAG_DEFAULT);
220 if (!ff)
221 return;
222
223 ff = procfile_readall(ff);
224 if (!ff)
225 return;
226
227 for (size_t l = 0; l < procfile_lines(ff); l++) {
228 int pid = (int)str2l(procfile_lineword(ff, l, 0));
229 if (!pid)
230 continue;
231
232 int found = 0;
233 for (struct pid_on_target2 *pt = ect->pids; pt; pt = pt->next) {
234 if (pt->pid == pid) {
235 pt->updated = 1;
236 found = 1;
237 break;
238 }
239 }
240
241 if (!found) {
242 struct pid_on_target2 *w = callocz(1, sizeof(*w));
243 w->pid = pid;
244 w->updated = 1;
245 w->next = ect->pids;
246 ect->pids = w;
247 }
248 }
249
250 struct pid_on_target2 **pt = &ect->pids;
251 while (*pt) {
252 if (!(*pt)->updated) {
253 struct pid_on_target2 *tmp = *pt;
254 *pt = tmp->next;
255 freez(tmp);
256 } else {
257 (*pt)->updated = 0;
258 pt = &(*pt)->next;
259 }
260 }
261
262 procfile_close(ff);
263 }
264
265 /**
266 * Set remove var
267 *
268 * Set variable remove. If this variable is not reset, the structure will be removed from link list.
269 */
270 void ebpf_reset_updated_var()
271 {
272 ebpf_cgroup_target_t *ect;
273 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
274 ect->updated = 0;
275 }
276 }
277
278 /**
279 * Initialize netipc cgroup cache
280 *
281 * Connect to the cgroups-snapshot service via netipc.
282 */
283 static void ebpf_cgroup_cache_init(void)
284 {
285 #ifdef OS_LINUX
286 if (ebpf_cgroup_cache_initialized)
287 return;
288
289 uint64_t auth = netipc_auth_token();
290
291 nipc_client_config_t config = {
292 .supported_profiles = NIPC_PROFILE_BASELINE | NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX,
293 .preferred_profiles = NIPC_PROFILE_SHM_FUTEX,
294 .auth_token = auth,
295 };
296
297 nipc_cgroups_cache_init(&ebpf_cgroup_cache,
298 os_run_dir(false),
299 "cgroups-snapshot",
300 &config);
301
302 ebpf_cgroup_cache_initialized = true;
303 #endif
304 }
305
306 /**
307 * Close the netipc cgroup cache and release resources.
308 */
309 void ebpf_cgroup_cache_cleanup(void)
310 {
311 #ifdef OS_LINUX
312 if (ebpf_cgroup_cache_initialized) {
313 nipc_cgroups_cache_close(&ebpf_cgroup_cache);
314 ebpf_cgroup_cache_initialized = false;
315 }
316 #endif
317 }
318
319 /**
320 * Refresh cgroup data from netipc cache
321 *
322 * Replaces the legacy SHM parse function.
323 */
324 static void ebpf_parse_cgroup_netipc_data(void)
325 {
326 #ifdef OS_LINUX
327 static uint32_t previous_count = 0;
328 static uint32_t previous_enabled_count = 0;
329 static size_t previous_imported_targets = 0;
330 static size_t previous_total_pids = 0;
331 static int previous_integration_active = -1;
332 static int previous_systemd_enabled = -1;
333
334 if (!ebpf_cgroup_cache_initialized)
335 return;
336
337 static int refresh_fail_count = 0;
338 if (!nipc_cgroups_cache_refresh(&ebpf_cgroup_cache)) {
339 if (++refresh_fail_count % 10 == 1)
340 collector_error("EBPF CGROUP: netipc refresh failed (%d consecutive failures)", refresh_fail_count);
341 return;
342 }
343 refresh_fail_count = 0;
344
345 uint32_t last_count = previous_count;
346 uint32_t count = ebpf_cgroup_cache.item_count;
347 uint64_t generation = ebpf_cgroup_cache.generation;
348 uint32_t enabled_count = 0;
349
350 int systemd_enabled = (int)ebpf_cgroup_cache.systemd_enabled;
351 int integration_active = (count > 0) ? 1 : 0;
352
353 for (uint32_t i = 0; i < count; i++) {
354 const nipc_cgroups_cache_item_t *item = &ebpf_cgroup_cache.items[i];
355 if (item->enabled)
356 enabled_count++;
357 }
358
359 ebpf_log_cgroup_transport_state(generation, count, enabled_count);
360
361 // nothing to process; preserve existing targets rather than wiping them.
362 // reset previous_count so the next non-zero snapshot triggers send_cgroup_chart=1,
363 // ensuring systemd charts are recreated after a cgroup list becomes empty.
364 if (count == 0) {
365 size_t preserved_targets = 0;
366 size_t preserved_pids = 0;
367
368 netdata_mutex_lock(&mutex_cgroup_shm);
369 preserved_targets = ebpf_count_cgroup_targets_unsafe();
370 preserved_pids = ebpf_count_cgroup_pids_unsafe();
371 // Publish flags while holding the mutex so collectors never observe a flag
372 // change before the pid/target lists match it.
373 ebpf_cgroup_systemd_enabled_set(systemd_enabled);
374 ebpf_cgroup_integration_active_set(integration_active);
375 netdata_mutex_unlock(&mutex_cgroup_shm);
376
377 if (last_count != 0 ||
378 previous_integration_active != integration_active ||
379 previous_systemd_enabled != systemd_enabled) {
380 collector_info(
381 "EBPF CGROUP: empty netipc snapshot generation=%llu items=%u enabled=%u preserved_targets=%zu "
382 "preserved_pids=%zu integration_active=%d systemd_enabled=%d refresh_failures=%d",
383 (unsigned long long)generation,
384 count,
385 enabled_count,
386 preserved_targets,
387 preserved_pids,
388 integration_active,
389 systemd_enabled,
390 refresh_fail_count);
391 }
392
393 previous_count = 0;
394 previous_enabled_count = enabled_count;
395 previous_imported_targets = preserved_targets;
396 previous_total_pids = preserved_pids;
397 previous_integration_active = integration_active;
398 previous_systemd_enabled = systemd_enabled;
399 return;
400 }
401
402 netdata_mutex_lock(&mutex_cgroup_shm);
403 ebpf_remove_cgroup_target_update_list();
404 ebpf_reset_updated_var();
405
406 for (uint32_t i = 0; i < count; i++) {
407 const nipc_cgroups_cache_item_t *item = &ebpf_cgroup_cache.items[i];
408 if (item->enabled) {
409 ebpf_cgroup_target_t *ect = ebpf_cgroup_find_or_create(item);
410 ebpf_update_pid_link_list(ect, item->path);
411 }
412 }
413
414 size_t imported_targets = ebpf_count_cgroup_targets_unsafe();
415 size_t total_pids = ebpf_count_cgroup_pids_unsafe();
416
417 int chart_refresh_needed = previous_count != count;
418 ebpf_send_cgroup_chart_set(chart_refresh_needed);
419 previous_count = count;
420 // Publish integration flags only after the target/pid lists have been rebuilt,
421 // so collectors never observe integration_active=1 with stale pid state.
422 ebpf_cgroup_systemd_enabled_set(systemd_enabled);
423 ebpf_cgroup_integration_active_set(integration_active);
424 netdata_mutex_unlock(&mutex_cgroup_shm);
425
426 if (last_count != count ||
427 previous_enabled_count != enabled_count ||
428 previous_imported_targets != imported_targets ||
429 previous_total_pids != total_pids ||
430 previous_integration_active != integration_active ||
431 previous_systemd_enabled != systemd_enabled) {
432 collector_info(
433 "EBPF CGROUP: netipc snapshot generation=%llu items=%u enabled=%u imported_targets=%zu total_pids=%zu "
434 "send_cgroup_chart=%d integration_active=%d systemd_enabled=%d refresh_failures=%d",
435 (unsigned long long)generation,
436 count,
437 enabled_count,
438 imported_targets,
439 total_pids,
440 chart_refresh_needed,
441 integration_active,
442 systemd_enabled,
443 refresh_fail_count);
444 }
445
446 previous_enabled_count = enabled_count;
447 previous_imported_targets = imported_targets;
448 previous_total_pids = total_pids;
449 previous_integration_active = integration_active;
450 previous_systemd_enabled = systemd_enabled;
451 #endif
452 }
453
454 // --------------------------------------------------------------------------------------------------------------------
455 // Create charts
456
457 /**
458 * Create charts on systemd submenu
459 *
460 * @param id the chart id
461 * @param title the value displayed on vertical axis.
462 * @param units the value displayed on vertical axis.
463 * @param family Submenu that the chart will be attached on dashboard.
464 * @param charttype chart type
465 * @param order the chart order
466 * @param algorithm the algorithm used by dimension
467 * @param context add context for chart
468 * @param module chart module name, this is the eBPF thread.
469 * @param update_every value to overwrite the update frequency set by the server.
470 */
471 void ebpf_create_charts_on_systemd(ebpf_systemd_args_t *chart)
472 {
473 ebpf_write_chart_cmd(
474 chart->id,
475 chart->suffix,
476 "",
477 chart->title,
478 chart->units,
479 chart->family,
480 chart->charttype,
481 chart->context,
482 chart->order,
483 chart->update_every,
484 chart->module);
485 char service_name[512];
486 snprintfz(service_name, 511, "%s", (!strstr(chart->id, "systemd_")) ? chart->id : (chart->id + 8));
487 ebpf_create_chart_labels("service_name", service_name, RRDLABEL_SRC_AUTO);
488 ebpf_commit_label();
489 // Let us keep original string that can be used in another place. Chart creation does not happen frequently.
490 char *move = strdupz(chart->dimension);
491 char *ptr = move;
492 while (ptr) {
493 char *next_dim = strchr(ptr, ',');
494 if (next_dim) {
495 *next_dim = '\0';
496 next_dim++;
497 }
498
499 fprintf(stdout, "DIMENSION %s '' %s 1 1\n", ptr, chart->algorithm);
500 ptr = next_dim;
501 }
502 freez(move);
503 }
504
505 // --------------------------------------------------------------------------------------------------------------------
506 // Cgroup main thread
507
508 /**
509 * Cgroup integration
510 *
511 * Thread responsible to call functions responsible to sync data between plugins.
512 *
513 * @param ptr It is a NULL value for this thread.
514 *
515 * @return It always returns NULL.
516 */
517 void ebpf_cgroup_integration(void *ptr __maybe_unused)
518 {
519 int counter = NETDATA_EBPF_CGROUP_UPDATE - 1;
520 heartbeat_t hb;
521 heartbeat_init(&hb, USEC_PER_SEC);
522
523 while (!ebpf_plugin_stop()) {
524 if (ebpf_plugin_stop())
525 break;
526
527 heartbeat_next(&hb);
528
529 if (ebpf_plugin_stop())
530 break;
531
532 // refresh every NETDATA_EBPF_CGROUP_UPDATE seconds
533 if (++counter >= NETDATA_EBPF_CGROUP_UPDATE) {
534 counter = 0;
535 if (!ebpf_cgroup_cache_initialized)
536 ebpf_cgroup_cache_init();
537
538 ebpf_parse_cgroup_netipc_data();
539 }
540 }
541 }