ebpf_oomkill.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "ebpf.h"
  3. #include "ebpf_oomkill.h"
  4. struct config oomkill_config = { .first_section = NULL,
  5. .last_section = NULL,
  6. .mutex = NETDATA_MUTEX_INITIALIZER,
  7. .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare },
  8. .rwlock = AVL_LOCK_INITIALIZER } };
  9. #define OOMKILL_MAP_KILLCNT 0
  10. static ebpf_local_maps_t oomkill_maps[] = {
  11. {
  12. .name = "tbl_oomkill",
  13. .internal_input = NETDATA_OOMKILL_MAX_ENTRIES,
  14. .user_input = 0,
  15. .type = NETDATA_EBPF_MAP_STATIC,
  16. .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED
  17. },
  18. /* end */
  19. {
  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. }
  26. };
  27. static ebpf_tracepoint_t oomkill_tracepoints[] = {
  28. {.enabled = false, .class = "oom", .event = "mark_victim"},
  29. /* end */
  30. {.enabled = false, .class = NULL, .event = NULL}
  31. };
  32. static netdata_publish_syscall_t oomkill_publish_aggregated = {.name = "oomkill", .dimension = "oomkill",
  33. .algorithm = "absolute",
  34. .next = NULL};
  35. /**
  36. * Clean up the main thread.
  37. *
  38. * @param ptr thread data.
  39. */
  40. static void oomkill_cleanup(void *ptr)
  41. {
  42. ebpf_module_t *em = (ebpf_module_t *)ptr;
  43. em->enabled = NETDATA_MAIN_THREAD_EXITED;
  44. }
  45. static void oomkill_write_data(int32_t *keys, uint32_t total)
  46. {
  47. // for each app, see if it was OOM killed. record as 1 if so otherwise 0.
  48. struct target *w;
  49. for (w = apps_groups_root_target; w != NULL; w = w->next) {
  50. if (likely(w->exposed && w->processes)) {
  51. bool was_oomkilled = false;
  52. struct pid_on_target *pids = w->root_pid;
  53. while (pids) {
  54. uint32_t j;
  55. for (j = 0; j < total; j++) {
  56. if (pids->pid == keys[j]) {
  57. was_oomkilled = true;
  58. // set to 0 so we consider it "done".
  59. keys[j] = 0;
  60. goto write_dim;
  61. }
  62. }
  63. pids = pids->next;
  64. }
  65. write_dim:;
  66. write_chart_dimension(w->name, was_oomkilled);
  67. }
  68. }
  69. // for any remaining keys for which we couldn't find a group, this could be
  70. // for various reasons, but the primary one is that the PID has not yet
  71. // been picked up by the process thread when parsing the proc filesystem.
  72. // since it's been OOM killed, it will never be parsed in the future, so
  73. // we have no choice but to dump it into `other`.
  74. uint32_t j;
  75. uint32_t rem_count = 0;
  76. for (j = 0; j < total; j++) {
  77. int32_t key = keys[j];
  78. if (key != 0) {
  79. rem_count += 1;
  80. }
  81. }
  82. if (rem_count > 0) {
  83. write_chart_dimension("other", rem_count);
  84. }
  85. }
  86. /**
  87. * Create specific OOMkill charts
  88. *
  89. * Create charts for cgroup/application.
  90. *
  91. * @param type the chart type.
  92. * @param update_every value to overwrite the update frequency set by the server.
  93. */
  94. static void ebpf_create_specific_oomkill_charts(char *type, int update_every)
  95. {
  96. ebpf_create_chart(type, NETDATA_OOMKILL_CHART, "OOM kills. This chart is provided by eBPF plugin.",
  97. EBPF_COMMON_DIMENSION_KILLS, NETDATA_EBPF_MEMORY_GROUP,
  98. NETDATA_CGROUP_OOMKILLS_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE,
  99. NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5600,
  100. ebpf_create_global_dimension,
  101. &oomkill_publish_aggregated, 1, update_every, NETDATA_EBPF_MODULE_NAME_OOMKILL);
  102. }
  103. /**
  104. * Create Systemd OOMkill Charts
  105. *
  106. * Create charts when systemd is enabled
  107. *
  108. * @param update_every value to overwrite the update frequency set by the server.
  109. **/
  110. static void ebpf_create_systemd_oomkill_charts(int update_every)
  111. {
  112. ebpf_create_charts_on_systemd(NETDATA_OOMKILL_CHART, "OOM kills. This chart is provided by eBPF plugin.",
  113. EBPF_COMMON_DIMENSION_KILLS, NETDATA_EBPF_MEMORY_GROUP,
  114. NETDATA_EBPF_CHART_TYPE_LINE, 20191,
  115. ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], NULL,
  116. NETDATA_EBPF_MODULE_NAME_OOMKILL, update_every);
  117. }
  118. /**
  119. * Send Systemd charts
  120. *
  121. * Send collected data to Netdata.
  122. *
  123. * @return It returns the status for chart creation, if it is necessary to remove a specific dimension, zero is returned
  124. * otherwise function returns 1 to avoid chart recreation
  125. */
  126. static int ebpf_send_systemd_oomkill_charts()
  127. {
  128. int ret = 1;
  129. ebpf_cgroup_target_t *ect;
  130. write_begin_chart(NETDATA_SERVICE_FAMILY, NETDATA_OOMKILL_CHART);
  131. for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) {
  132. if (unlikely(ect->systemd) && unlikely(ect->updated)) {
  133. write_chart_dimension(ect->name, (long long) ect->oomkill);
  134. ect->oomkill = 0;
  135. } else if (unlikely(ect->systemd))
  136. ret = 0;
  137. }
  138. write_end_chart();
  139. return ret;
  140. }
  141. /*
  142. * Send Specific OOMkill data
  143. *
  144. * Send data for specific cgroup/apps.
  145. *
  146. * @param type chart type
  147. * @param value value for oomkill
  148. */
  149. static void ebpf_send_specific_oomkill_data(char *type, int value)
  150. {
  151. write_begin_chart(type, NETDATA_OOMKILL_CHART);
  152. write_chart_dimension(oomkill_publish_aggregated.name, (long long)value);
  153. write_end_chart();
  154. }
  155. /**
  156. * Create specific OOMkill charts
  157. *
  158. * Create charts for cgroup/application.
  159. *
  160. * @param type the chart type.
  161. * @param update_every value to overwrite the update frequency set by the server.
  162. */
  163. static void ebpf_obsolete_specific_oomkill_charts(char *type, int update_every)
  164. {
  165. ebpf_write_chart_obsolete(type, NETDATA_OOMKILL_CHART, "OOM kills. This chart is provided by eBPF plugin.",
  166. EBPF_COMMON_DIMENSION_KILLS, NETDATA_EBPF_MEMORY_GROUP,
  167. NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_OOMKILLS_CONTEXT,
  168. NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5600, update_every);
  169. }
  170. /**
  171. * Send data to Netdata calling auxiliary functions.
  172. *
  173. * @param update_every value to overwrite the update frequency set by the server.
  174. */
  175. void ebpf_oomkill_send_cgroup_data(int update_every)
  176. {
  177. if (!ebpf_cgroup_pids)
  178. return;
  179. pthread_mutex_lock(&mutex_cgroup_shm);
  180. ebpf_cgroup_target_t *ect;
  181. int has_systemd = shm_ebpf_cgroup.header->systemd_enabled;
  182. if (has_systemd) {
  183. static int systemd_charts = 0;
  184. if (!systemd_charts) {
  185. ebpf_create_systemd_oomkill_charts(update_every);
  186. systemd_charts = 1;
  187. }
  188. systemd_charts = ebpf_send_systemd_oomkill_charts();
  189. }
  190. for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) {
  191. if (ect->systemd)
  192. continue;
  193. if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART) && ect->updated) {
  194. ebpf_create_specific_oomkill_charts(ect->name, update_every);
  195. ect->flags |= NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART;
  196. }
  197. if (ect->flags & NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART && ect->updated) {
  198. ebpf_send_specific_oomkill_data(ect->name, ect->oomkill);
  199. } else {
  200. ebpf_obsolete_specific_oomkill_charts(ect->name, update_every);
  201. ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART;
  202. }
  203. }
  204. pthread_mutex_unlock(&mutex_cgroup_shm);
  205. }
  206. /**
  207. * Read data
  208. *
  209. * Read OOMKILL events from table.
  210. *
  211. * @param keys vector where data will be stored
  212. *
  213. * @return It returns the number of read elements
  214. */
  215. static uint32_t oomkill_read_data(int32_t *keys)
  216. {
  217. // the first `i` entries of `keys` will contain the currently active PIDs
  218. // in the eBPF map.
  219. uint32_t i = 0;
  220. uint32_t curr_key = 0;
  221. uint32_t key = 0;
  222. int mapfd = oomkill_maps[OOMKILL_MAP_KILLCNT].map_fd;
  223. while (bpf_map_get_next_key(mapfd, &curr_key, &key) == 0) {
  224. curr_key = key;
  225. keys[i] = (int32_t)key;
  226. i += 1;
  227. // delete this key now that we've recorded its existence. there's no
  228. // race here, as the same PID will only get OOM killed once.
  229. int test = bpf_map_delete_elem(mapfd, &key);
  230. if (unlikely(test < 0)) {
  231. // since there's only 1 thread doing these deletions, it should be
  232. // impossible to get this condition.
  233. error("key unexpectedly not available for deletion.");
  234. }
  235. }
  236. return i;
  237. }
  238. /**
  239. * Update cgroup
  240. *
  241. * Update cgroup data based in
  242. *
  243. * @param keys vector with pids that had oomkill event
  244. * @param total number of elements in keys vector.
  245. */
  246. static void ebpf_update_oomkill_cgroup(int32_t *keys, uint32_t total)
  247. {
  248. ebpf_cgroup_target_t *ect;
  249. for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
  250. ect->oomkill = 0;
  251. struct pid_on_target2 *pids;
  252. for (pids = ect->pids; pids; pids = pids->next) {
  253. uint32_t j;
  254. int32_t pid = pids->pid;
  255. for (j = 0; j < total; j++) {
  256. if (pid == keys[j]) {
  257. ect->oomkill = 1;
  258. break;
  259. }
  260. }
  261. }
  262. }
  263. }
  264. /**
  265. * Main loop for this collector.
  266. */
  267. static void oomkill_collector(ebpf_module_t *em)
  268. {
  269. int cgroups = em->cgroup_charts;
  270. int update_every = em->update_every;
  271. int32_t keys[NETDATA_OOMKILL_MAX_ENTRIES];
  272. memset(keys, 0, sizeof(keys));
  273. // loop and read until ebpf plugin is closed.
  274. heartbeat_t hb;
  275. heartbeat_init(&hb);
  276. usec_t step = update_every * USEC_PER_SEC;
  277. while (!ebpf_exit_plugin) {
  278. (void)heartbeat_next(&hb, step);
  279. if (ebpf_exit_plugin)
  280. break;
  281. pthread_mutex_lock(&collect_data_mutex);
  282. pthread_mutex_lock(&lock);
  283. uint32_t count = oomkill_read_data(keys);
  284. if (cgroups && count)
  285. ebpf_update_oomkill_cgroup(keys, count);
  286. // write everything from the ebpf map.
  287. if (cgroups)
  288. ebpf_oomkill_send_cgroup_data(update_every);
  289. if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) {
  290. write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_OOMKILL_CHART);
  291. oomkill_write_data(keys, count);
  292. write_end_chart();
  293. }
  294. pthread_mutex_unlock(&lock);
  295. pthread_mutex_unlock(&collect_data_mutex);
  296. }
  297. }
  298. /**
  299. * Create apps charts
  300. *
  301. * Call ebpf_create_chart to create the charts on apps submenu.
  302. *
  303. * @param em a pointer to the structure with the default values.
  304. */
  305. void ebpf_oomkill_create_apps_charts(struct ebpf_module *em, void *ptr)
  306. {
  307. struct target *root = ptr;
  308. ebpf_create_charts_on_apps(NETDATA_OOMKILL_CHART,
  309. "OOM kills",
  310. EBPF_COMMON_DIMENSION_KILLS,
  311. "mem",
  312. NETDATA_EBPF_CHART_TYPE_STACKED,
  313. 20020,
  314. ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX],
  315. root, em->update_every, NETDATA_EBPF_MODULE_NAME_OOMKILL);
  316. em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED;
  317. }
  318. /**
  319. * OOM kill tracking thread.
  320. *
  321. * @param ptr a `ebpf_module_t *`.
  322. * @return always NULL.
  323. */
  324. void *ebpf_oomkill_thread(void *ptr)
  325. {
  326. netdata_thread_cleanup_push(oomkill_cleanup, ptr);
  327. ebpf_module_t *em = (ebpf_module_t *)ptr;
  328. em->maps = oomkill_maps;
  329. if (unlikely(!all_pids || !em->apps_charts)) {
  330. // When we are not running integration with apps, we won't fill necessary variables for this thread to run, so
  331. // we need to disable it.
  332. if (em->enabled)
  333. info("Disabling OOMKILL thread, because apps integration is completely disabled.");
  334. em->enabled = 0;
  335. }
  336. if (!em->enabled) {
  337. goto endoomkill;
  338. }
  339. if (ebpf_enable_tracepoints(oomkill_tracepoints) == 0) {
  340. em->enabled = CONFIG_BOOLEAN_NO;
  341. goto endoomkill;
  342. }
  343. em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects);
  344. if (!em->probe_links) {
  345. em->enabled = CONFIG_BOOLEAN_NO;
  346. goto endoomkill;
  347. }
  348. pthread_mutex_lock(&lock);
  349. ebpf_update_stats(&plugin_statistics, em);
  350. pthread_mutex_unlock(&lock);
  351. oomkill_collector(em);
  352. endoomkill:
  353. if (!em->enabled)
  354. ebpf_update_disabled_plugin_stats(em);
  355. netdata_thread_cleanup_pop(1);
  356. return NULL;
  357. }