master
c 604 lines 17.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "ebpf.h"
4 #include "ebpf_oomkill.h"
5 #include "libbpf_api/ebpf_library.h"
6
7 struct config oomkill_config = APPCONFIG_INITIALIZER;
8
9 static ebpf_local_maps_t oomkill_maps[] = {
10 {.name = "tbl_oomkill",
11 .internal_input = NETDATA_OOMKILL_MAX_ENTRIES,
12 .user_input = 0,
13 .type = NETDATA_EBPF_MAP_STATIC,
14 .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED,
15 #ifdef LIBBPF_MAJOR_VERSION
16 .map_type = BPF_MAP_TYPE_PERCPU_HASH
17 #endif
18 },
19 /* end */
20 {.name = NULL,
21 .internal_input = 0,
22 .user_input = 0,
23 .type = NETDATA_EBPF_MAP_CONTROLLER,
24 .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED,
25 #ifdef LIBBPF_MAJOR_VERSION
26 .map_type = BPF_MAP_TYPE_PERCPU_HASH
27 #endif
28 }};
29
30 static ebpf_tracepoint_t oomkill_tracepoints[] = {
31 {.enabled = false, .class = "oom", .event = "mark_victim"},
32 /* end */
33 {.enabled = false, .class = NULL, .event = NULL}};
34
35 static netdata_publish_syscall_t oomkill_publish_aggregated =
36 {.name = "kills", .dimension = "kills", .algorithm = "absolute", .next = NULL};
37
38 static void ebpf_obsolete_specific_oomkill_charts(char *type, int update_every);
39
40 /**
41 * Obsolete services
42 *
43 * Obsolete all service charts created
44 *
45 * @param em a pointer to `struct ebpf_module`
46 */
47 static void ebpf_obsolete_oomkill_services(ebpf_module_t *em, char *id)
48 {
49 ebpf_write_chart_obsolete(
50 id,
51 NETDATA_OOMKILL_CHART,
52 "",
53 "Systemd service OOM kills.",
54 EBPF_OOMKILL_UNIT_KILLS,
55 NETDATA_EBPF_MEMORY_GROUP,
56 NETDATA_EBPF_CHART_TYPE_STACKED,
57 NETDATA_CGROUP_OOMKILLS_CONTEXT,
58 20191,
59 em->update_every);
60 }
61
62 /**
63 * Obsolete cgroup chart
64 *
65 * Send obsolete for all charts created before to close.
66 *
67 * @param em a pointer to `struct ebpf_module`
68 */
69 static inline void ebpf_obsolete_oomkill_cgroup_charts(ebpf_module_t *em)
70 {
71 netdata_mutex_lock(&mutex_cgroup_shm);
72
73 ebpf_cgroup_target_t *ect;
74 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
75 if (ect->systemd) {
76 ebpf_obsolete_oomkill_services(em, ect->name);
77
78 continue;
79 }
80
81 ebpf_obsolete_specific_oomkill_charts(ect->name, em->update_every);
82 }
83 netdata_mutex_unlock(&mutex_cgroup_shm);
84 }
85
86 /**
87 * Obsolete global
88 *
89 * Obsolete global charts created by thread.
90 *
91 * @param em a pointer to `struct ebpf_module`
92 */
93 static void ebpf_obsolete_oomkill_apps(ebpf_module_t *em)
94 {
95 struct ebpf_target *w;
96 int update_every = em->update_every;
97 netdata_mutex_lock(&collect_data_mutex);
98 for (w = apps_groups_root_target; w; w = w->next) {
99 if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_OOMKILL_IDX))))
100 continue;
101
102 ebpf_write_chart_obsolete(
103 NETDATA_APP_FAMILY,
104 w->clean_name,
105 NETDATA_OOMKILL_CHART,
106 "Processes OOM kills.",
107 EBPF_OOMKILL_UNIT_KILLS,
108 NETDATA_EBPF_MEMORY_GROUP,
109 NETDATA_EBPF_CHART_TYPE_STACKED,
110 "ebpf.app_oomkill",
111 20072,
112 update_every);
113
114 w->charts_created &= ~(1 << EBPF_MODULE_OOMKILL_IDX);
115 }
116 netdata_mutex_unlock(&collect_data_mutex);
117 }
118
119 static void oomkill_cleanup(void *pptr)
120 {
121 ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr);
122 if (!em)
123 return;
124
125 netdata_mutex_lock(&lock);
126 collect_pids &= ~(1 << EBPF_MODULE_OOMKILL_IDX);
127 netdata_mutex_unlock(&lock);
128
129 if (ebpf_module_enabled_get(em) == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
130 netdata_mutex_lock(&lock);
131
132 if (em->cgroup_charts) {
133 ebpf_obsolete_oomkill_cgroup_charts(em);
134 }
135
136 ebpf_obsolete_oomkill_apps(em);
137
138 fflush(stdout);
139 netdata_mutex_unlock(&lock);
140 }
141
142 if (!ebpf_plugin_stop() && em->functions.bpf_unload)
143 em->functions.bpf_unload(em);
144
145 netdata_mutex_lock(&ebpf_exit_cleanup);
146 ebpf_module_enabled_set(em, NETDATA_THREAD_EBPF_STOPPED);
147 netdata_mutex_unlock(&ebpf_exit_cleanup);
148 }
149
150 static void oomkill_write_data(int32_t *keys, uint32_t total)
151 {
152 // for each app, see if it was OOM killed. record as 1 if so otherwise 0.
153 struct ebpf_target *w;
154 uint32_t used_pid = 0;
155 netdata_mutex_lock(&collect_data_mutex);
156 for (w = apps_groups_root_target; w != NULL; w = w->next) {
157 if (ebpf_plugin_stop())
158 break;
159
160 if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_OOMKILL_IDX))))
161 continue;
162
163 bool was_oomkilled = false;
164 if (total) {
165 struct ebpf_pid_on_target *pids = w->root_pid;
166 while (pids) {
167 uint32_t j;
168 for (j = 0; j < total; j++) {
169 if (pids->pid == keys[j]) {
170 used_pid++;
171 was_oomkilled = true;
172 // set to 0 so we consider it "done".
173 keys[j] = 0;
174 goto write_dim;
175 }
176 }
177 pids = pids->next;
178 }
179 }
180 write_dim:
181 ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, NETDATA_OOMKILL_CHART);
182 write_chart_dimension(oomkill_publish_aggregated.dimension, was_oomkilled);
183 ebpf_write_end_chart();
184 }
185
186 if (total != used_pid) {
187 // for any remaining keys for which we couldn't find a group, this could be
188 // for various reasons, but the primary one is that the PID has not yet
189 // been picked up by the process thread when parsing the proc filesystem.
190 // since it's been OOM killed, it will never be parsed in the future, so
191 // we have no choice but to dump it into `other`.
192 uint32_t rem_count = total - used_pid;
193 ebpf_write_begin_chart(NETDATA_APP_FAMILY, "other", NETDATA_OOMKILL_CHART);
194 write_chart_dimension(oomkill_publish_aggregated.dimension, rem_count);
195 ebpf_write_end_chart();
196 }
197
198 netdata_mutex_unlock(&collect_data_mutex);
199 }
200
201 /**
202 * Create specific OOMkill charts
203 *
204 * Create charts for cgroup/application.
205 *
206 * @param type the chart type.
207 * @param update_every value to overwrite the update frequency set by the server.
208 */
209 static void ebpf_create_specific_oomkill_charts(char *type, int update_every)
210 {
211 ebpf_create_chart(
212 type,
213 NETDATA_OOMKILL_CHART,
214 "Cgroup OOM kills.",
215 EBPF_OOMKILL_UNIT_KILLS,
216 NETDATA_EBPF_MEMORY_GROUP,
217 NETDATA_CGROUP_OOMKILLS_CONTEXT,
218 NETDATA_EBPF_CHART_TYPE_LINE,
219 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5600,
220 ebpf_create_global_dimension,
221 &oomkill_publish_aggregated,
222 1,
223 update_every,
224 NETDATA_EBPF_MODULE_NAME_OOMKILL);
225 }
226
227 /**
228 * Create Systemd OOMkill Charts
229 *
230 * Create charts when systemd is enabled
231 *
232 * @param update_every value to overwrite the update frequency set by the server.
233 **/
234 static void ebpf_create_systemd_oomkill_charts(int update_every)
235 {
236 static ebpf_systemd_args_t data_oom = {
237 .title = "Systemd service OOM kills.",
238 .units = EBPF_OOMKILL_UNIT_KILLS,
239 .family = NETDATA_EBPF_MEMORY_GROUP,
240 .charttype = NETDATA_EBPF_CHART_TYPE_STACKED,
241 .order = 20191,
242 .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL,
243 .context = NETDATA_SYSTEMD_OOMKILLS_CONTEXT,
244 .module = NETDATA_EBPF_MODULE_NAME_OOMKILL,
245 .update_every = 0,
246 .suffix = NETDATA_OOMKILL_CHART,
247 .dimension = "kills"};
248
249 if (!data_oom.update_every)
250 data_oom.update_every = update_every;
251
252 ebpf_cgroup_target_t *w;
253 for (w = ebpf_cgroup_pids; w; w = w->next) {
254 if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_OOMKILL_CHART))
255 continue;
256
257 data_oom.id = w->name;
258 ebpf_create_charts_on_systemd(&data_oom);
259
260 w->flags |= NETDATA_EBPF_SERVICES_HAS_OOMKILL_CHART;
261 }
262 }
263
264 /**
265 * Send Systemd charts
266 *
267 * Send collected data to Netdata.
268 */
269 static void ebpf_send_systemd_oomkill_charts()
270 {
271 ebpf_cgroup_target_t *ect;
272 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
273 if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_OOMKILL_CHART))) {
274 continue;
275 }
276 ebpf_write_begin_chart(ect->name, NETDATA_OOMKILL_CHART, "");
277 write_chart_dimension(oomkill_publish_aggregated.dimension, (long long)ect->oomkill);
278 ect->oomkill = 0;
279 ebpf_write_end_chart();
280 }
281 }
282
283 /*
284 * Send Specific OOMkill data
285 *
286 * Send data for specific cgroup/apps.
287 *
288 * @param type chart type
289 * @param value value for oomkill
290 */
291 static void ebpf_send_specific_oomkill_data(char *type, int value)
292 {
293 ebpf_write_begin_chart(type, NETDATA_OOMKILL_CHART, "");
294 write_chart_dimension(oomkill_publish_aggregated.dimension, (long long)value);
295 ebpf_write_end_chart();
296 }
297
298 /**
299 * Create specific OOMkill charts
300 *
301 * Create charts for cgroup/application.
302 *
303 * @param type the chart type.
304 * @param update_every value to overwrite the update frequency set by the server.
305 */
306 static void ebpf_obsolete_specific_oomkill_charts(char *type, int update_every)
307 {
308 ebpf_write_chart_obsolete(
309 type,
310 NETDATA_OOMKILL_CHART,
311 "",
312 "Cgroup OOM kills.",
313 EBPF_OOMKILL_UNIT_KILLS,
314 NETDATA_EBPF_MEMORY_GROUP,
315 NETDATA_EBPF_CHART_TYPE_LINE,
316 NETDATA_CGROUP_OOMKILLS_CONTEXT,
317 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5600,
318 update_every);
319 }
320
321 /**
322 * Send data to Netdata calling auxiliary functions.
323 *
324 * @param update_every value to overwrite the update frequency set by the server.
325 */
326 void ebpf_oomkill_send_cgroup_data(int update_every)
327 {
328 netdata_mutex_lock(&mutex_cgroup_shm);
329 ebpf_cgroup_target_t *ect;
330
331 if (ebpf_cgroup_systemd_enabled_get()) {
332 if (ebpf_send_cgroup_chart_get()) {
333 ebpf_create_systemd_oomkill_charts(update_every);
334 }
335 ebpf_send_systemd_oomkill_charts();
336 }
337
338 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
339 if (ect->systemd)
340 continue;
341
342 if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART) && ect->updated) {
343 ebpf_create_specific_oomkill_charts(ect->name, update_every);
344 ect->flags |= NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART;
345 }
346
347 if (ect->flags & NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART) {
348 if (ect->updated) {
349 ebpf_send_specific_oomkill_data(ect->name, ect->oomkill);
350 } else {
351 ebpf_obsolete_specific_oomkill_charts(ect->name, update_every);
352 ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART;
353 }
354 }
355 }
356
357 netdata_mutex_unlock(&mutex_cgroup_shm);
358 }
359
360 /**
361 * Read data
362 *
363 * Read OOMKILL events from table.
364 *
365 * @param keys vector where data will be stored
366 *
367 * @return It returns the number of read elements
368 */
369 static uint32_t oomkill_read_data(int32_t *keys)
370 {
371 // the first `i` entries of `keys` will contain the currently active PIDs
372 // in the eBPF map.
373 uint32_t i = 0;
374
375 uint32_t curr_key = 0;
376 uint32_t key;
377 int mapfd = oomkill_maps[0].map_fd;
378 while (bpf_map_get_next_key(mapfd, &curr_key, &key) == 0) {
379 if (ebpf_plugin_stop())
380 break;
381
382 curr_key = key;
383
384 keys[i] = (int32_t)key;
385 i += 1;
386
387 if (unlikely(bpf_map_delete_elem(mapfd, &key) < 0)) {
388 netdata_log_error("key unexpectedly not available for deletion.");
389 }
390 if (i >= NETDATA_OOMKILL_MAX_ENTRIES)
391 break;
392 }
393
394 return i;
395 }
396
397 /**
398 * Update cgroup
399 *
400 * Update cgroup data based in
401 *
402 * @param keys vector with pids that had oomkill event
403 * @param total number of elements in keys vector.
404 */
405 static void ebpf_update_oomkill_cgroup(int32_t *keys, uint32_t total)
406 {
407 ebpf_cgroup_target_t *ect;
408 netdata_mutex_lock(&mutex_cgroup_shm);
409 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
410 ect->oomkill = 0;
411 struct pid_on_target2 *pids;
412 for (pids = ect->pids; pids; pids = pids->next) {
413 uint32_t j;
414 int32_t pid = pids->pid;
415 for (j = 0; j < total; j++) {
416 if (pid == keys[j]) {
417 ect->oomkill = 1;
418 break;
419 }
420 }
421 }
422 }
423 netdata_mutex_unlock(&mutex_cgroup_shm);
424 }
425
426 /**
427 * Update OOMkill period
428 *
429 * Update oomkill period according function arguments.
430 *
431 * @param running_time current value of running_value.
432 * @param em the thread main structure.
433 *
434 * @return It returns new running_time value.
435 */
436 static int ebpf_update_oomkill_period(int running_time, ebpf_module_t *em)
437 {
438 netdata_mutex_lock(&ebpf_exit_cleanup);
439 running_time += em->update_every;
440 em->running_time = running_time;
441 netdata_mutex_unlock(&ebpf_exit_cleanup);
442
443 return running_time;
444 }
445
446 /**
447 * Main loop for this collector.
448 *
449 * @param em the thread main structure.
450 */
451 static void oomkill_collector(ebpf_module_t *em)
452 {
453 int cgroups = em->cgroup_charts;
454 int update_every = em->update_every;
455 int32_t keys[NETDATA_OOMKILL_MAX_ENTRIES];
456
457 // loop and read until ebpf plugin is closed.
458 int counter = update_every - 1;
459 uint32_t running_time = 0;
460 uint32_t lifetime = em->lifetime;
461 netdata_idx_t *stats = em->hash_table_stats;
462 heartbeat_t hb;
463 heartbeat_init(&hb, USEC_PER_SEC);
464 while (!ebpf_plugin_stop() && running_time < lifetime) {
465 if (ebpf_plugin_stop())
466 break;
467
468 (void)heartbeat_next(&hb);
469 if (ebpf_plugin_stop())
470 break;
471
472 if (++counter != update_every)
473 continue;
474
475 counter = 0;
476
477 uint32_t count = oomkill_read_data(keys);
478
479 stats[NETDATA_CONTROLLER_PID_TABLE_ADD] += (uint64_t)count;
480 stats[NETDATA_CONTROLLER_PID_TABLE_DEL] += (uint64_t)count;
481
482 if (cgroups && ebpf_cgroup_integration_active_get())
483 ebpf_update_oomkill_cgroup(keys, count);
484
485 if (ebpf_plugin_stop())
486 break;
487
488 netdata_apps_integration_flags_t apps = em->apps_charts;
489 netdata_mutex_lock(&lock);
490 // write everything from the ebpf map.
491 if (cgroups && ebpf_cgroup_integration_active_get())
492 ebpf_oomkill_send_cgroup_data(update_every);
493
494 if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED)
495 oomkill_write_data(keys, count);
496
497 netdata_mutex_unlock(&lock);
498
499 running_time = ebpf_update_oomkill_period(running_time, em);
500 }
501 }
502
503 /**
504 * Create apps charts
505 *
506 * Call ebpf_create_chart to create the charts on apps submenu.
507 *
508 * @param em a pointer to the structure with the default values.
509 */
510 void ebpf_oomkill_create_apps_charts(struct ebpf_module *em, void *ptr)
511 {
512 struct ebpf_target *root = ptr;
513 struct ebpf_target *w;
514 int update_every = em->update_every;
515 for (w = root; w; w = w->next) {
516 if (unlikely(!w->exposed))
517 continue;
518
519 ebpf_write_chart_cmd(
520 NETDATA_APP_FAMILY,
521 w->clean_name,
522 NETDATA_OOMKILL_CHART,
523 "Processes OOM kills.",
524 EBPF_OOMKILL_UNIT_KILLS,
525 NETDATA_EBPF_MEMORY_GROUP,
526 NETDATA_EBPF_CHART_TYPE_STACKED,
527 "app.ebpf_oomkill",
528 20072,
529 update_every,
530 NETDATA_EBPF_MODULE_NAME_OOMKILL);
531 ebpf_create_chart_labels("app_group", w->clean_name, RRDLABEL_SRC_AUTO);
532 ebpf_commit_label();
533 fprintf(
534 stdout,
535 "DIMENSION '%s' '' %s 1 1\n",
536 oomkill_publish_aggregated.dimension,
537 ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]);
538
539 w->charts_created |= 1 << EBPF_MODULE_OOMKILL_IDX;
540 }
541
542 em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED;
543 }
544
545 /**
546 * OOM kill tracking thread.
547 *
548 * @param ptr a `ebpf_module_t *`.
549 * @return always NULL.
550 */
551 void ebpf_oomkill_thread(void *ptr)
552 {
553 ebpf_module_t *em = (ebpf_module_t *)ptr;
554
555 CLEANUP_FUNCTION_REGISTER(oomkill_cleanup) cleanup_ptr = em;
556
557 if (!ebpf_module_thread_has_valid_state(em)) {
558 goto endoomkill;
559 }
560
561 em->maps = oomkill_maps;
562
563 #define NETDATA_DEFAULT_OOM_DISABLED_MSG "Disabling OOMKILL thread, because"
564 if (unlikely(!em->apps_charts)) {
565 // When we are not running integration with apps, we won't fill necessary variables for this thread to run, so
566 // we need to disable it.
567 netdata_mutex_lock(&ebpf_exit_cleanup);
568 if (ebpf_module_enabled_get(em))
569 netdata_log_info("%s apps integration is completely disabled.", NETDATA_DEFAULT_OOM_DISABLED_MSG);
570 netdata_mutex_unlock(&ebpf_exit_cleanup);
571
572 goto endoomkill;
573 } else if (running_on_kernel < NETDATA_EBPF_KERNEL_4_14) {
574 netdata_mutex_lock(&ebpf_exit_cleanup);
575 if (ebpf_module_enabled_get(em))
576 netdata_log_info("%s kernel does not have necessary tracepoints.", NETDATA_DEFAULT_OOM_DISABLED_MSG);
577 netdata_mutex_unlock(&ebpf_exit_cleanup);
578
579 goto endoomkill;
580 }
581
582 if (ebpf_enable_tracepoints(oomkill_tracepoints) == 0) {
583 goto endoomkill;
584 }
585
586 #ifdef LIBBPF_MAJOR_VERSION
587 ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel);
588 #endif
589 em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects);
590 if (!em->probe_links) {
591 goto endoomkill;
592 }
593 ebpf_mark_program_loaded();
594
595 netdata_mutex_lock(&lock);
596 ebpf_update_stats(&plugin_statistics, em);
597 ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD);
598 netdata_mutex_unlock(&lock);
599
600 oomkill_collector(em);
601
602 endoomkill:
603 ebpf_update_disabled_plugin_stats(em);
604 }