master
c 1,883 lines 59.5 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "ebpf.h"
4 #include "ebpf_cachestat.h"
5 #include "libbpf_api/ebpf_library.h"
6
7 static char *cachestat_counter_dimension_name[NETDATA_CACHESTAT_END] = {"ratio", "dirty", "hit", "miss"};
8 static netdata_syscall_stat_t cachestat_counter_aggregated_data[NETDATA_CACHESTAT_END];
9 static netdata_publish_syscall_t cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_END];
10
11 netdata_cachestat_pid_t *cachestat_vector = NULL;
12
13 static netdata_idx_t cachestat_hash_values[NETDATA_CACHESTAT_END];
14 static netdata_idx_t *cachestat_values = NULL;
15 static bool cachestat_safe_clean = false;
16
17 ebpf_local_maps_t cachestat_maps[] = {
18 {.name = "cstat_global",
19 .internal_input = NETDATA_CACHESTAT_END,
20 .user_input = 0,
21 .type = NETDATA_EBPF_MAP_STATIC,
22 .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED,
23 #ifdef LIBBPF_MAJOR_VERSION
24 .map_type = BPF_MAP_TYPE_PERCPU_ARRAY
25 #endif
26 },
27 {.name = "cstat_pid",
28 .internal_input = ND_EBPF_DEFAULT_PID_SIZE,
29 .user_input = 0,
30 .type = NETDATA_EBPF_MAP_RESIZABLE | NETDATA_EBPF_MAP_PID,
31 .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED,
32 #ifdef LIBBPF_MAJOR_VERSION
33 .map_type = BPF_MAP_TYPE_PERCPU_HASH
34 #endif
35 },
36 {.name = "cstat_ctrl",
37 .internal_input = NETDATA_CONTROLLER_END,
38 .user_input = 0,
39 .type = NETDATA_EBPF_MAP_CONTROLLER,
40 .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED,
41 #ifdef LIBBPF_MAJOR_VERSION
42 .map_type = BPF_MAP_TYPE_PERCPU_ARRAY
43 #endif
44 },
45 {.name = NULL,
46 .internal_input = 0,
47 .user_input = 0,
48 .type = NETDATA_EBPF_MAP_CONTROLLER,
49 .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED,
50 #ifdef LIBBPF_MAJOR_VERSION
51 .map_type = BPF_MAP_TYPE_PERCPU_ARRAY
52 #endif
53 }};
54
55 struct config cachestat_config = APPCONFIG_INITIALIZER;
56
57 netdata_ebpf_targets_t cachestat_targets[] = {
58 {.name = "add_to_page_cache_lru", .mode = EBPF_LOAD_TRAMPOLINE},
59 {.name = "mark_page_accessed", .mode = EBPF_LOAD_TRAMPOLINE},
60 {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}, // slot ACCOUNT_PAGE_DIRTIED: resolved dynamically at runtime
61 {.name = "mark_buffer_dirty", .mode = EBPF_LOAD_TRAMPOLINE},
62 {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}};
63
64 static char *account_page[NETDATA_CACHESTAT_ACCOUNT_DIRTY_END] = {
65 "account_page_dirtied",
66 "__set_page_dirty",
67 "__folio_mark_dirty"};
68
69 static int cached_dirty_account_idx = -1;
70
71 static inline void netdata_init_dirty_account_idx(void)
72 {
73 if (cached_dirty_account_idx != -1)
74 return;
75
76 if (!strcmp(
77 cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
78 account_page[NETDATA_CACHESTAT_FOLIO_DIRTY]))
79 cached_dirty_account_idx = NETDATA_CACHESTAT_FOLIO_DIRTY;
80 else if (!strcmp(
81 cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name,
82 account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY]))
83 cached_dirty_account_idx = NETDATA_CACHESTAT_SET_PAGE_DIRTY;
84 else
85 cached_dirty_account_idx = NETDATA_CACHESTAT_ACCOUNT_PAGE_DIRTY;
86 }
87
88 static inline int netdata_get_dirty_account_idx(void)
89 {
90 if (cached_dirty_account_idx == -1)
91 netdata_init_dirty_account_idx();
92 return cached_dirty_account_idx;
93 }
94
95 struct netdata_static_thread ebpf_read_cachestat = {
96 .name = "EBPF_READ_CACHESTAT",
97 .config_section = NULL,
98 .config_name = NULL,
99 .env_name = NULL,
100 .enabled = 1,
101 .thread = NULL,
102 .init_routine = NULL,
103 .start_routine = NULL};
104
105 #ifdef LIBBPF_MAJOR_VERSION
106 /**
107 * Disable probe
108 *
109 * Disable all probes to use exclusively another method.
110 *
111 * @param obj is the main structure for bpf objects
112 */
113 static void ebpf_cachestat_disable_probe(struct cachestat_bpf *obj)
114 {
115 bpf_program__set_autoload(obj->progs.netdata_add_to_page_cache_lru_kprobe, false);
116 bpf_program__set_autoload(obj->progs.netdata_mark_page_accessed_kprobe, false);
117 bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_kprobe, false);
118 bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_kprobe, false);
119 bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_kprobe, false);
120 bpf_program__set_autoload(obj->progs.netdata_mark_buffer_dirty_kprobe, false);
121 }
122
123 /*
124 * Disable specific probe
125 *
126 * Disable probes according the kernel version
127 *
128 * @param obj is the main structure for bpf objects
129 */
130 static void ebpf_cachestat_disable_specific_probe(struct cachestat_bpf *obj)
131 {
132 int idx = netdata_get_dirty_account_idx();
133 if (idx == NETDATA_CACHESTAT_FOLIO_DIRTY) {
134 bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_kprobe, false);
135 bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_kprobe, false);
136 } else if (idx == NETDATA_CACHESTAT_SET_PAGE_DIRTY) {
137 bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_kprobe, false);
138 bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_kprobe, false);
139 } else {
140 bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_kprobe, false);
141 bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_kprobe, false);
142 }
143 }
144
145 /*
146 * Disable trampoline
147 *
148 * Disable all trampoline to use exclusively another method.
149 *
150 * @param obj is the main structure for bpf objects.
151 */
152 static void ebpf_cachestat_disable_trampoline(struct cachestat_bpf *obj)
153 {
154 bpf_program__set_autoload(obj->progs.netdata_add_to_page_cache_lru_fentry, false);
155 bpf_program__set_autoload(obj->progs.netdata_mark_page_accessed_fentry, false);
156 bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_fentry, false);
157 bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_fentry, false);
158 bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_fentry, false);
159 bpf_program__set_autoload(obj->progs.netdata_mark_buffer_dirty_fentry, false);
160 }
161
162 /*
163 * Disable specific trampoline
164 *
165 * Disable trampoline according to kernel version.
166 *
167 * @param obj is the main structure for bpf objects.
168 */
169 static void ebpf_cachestat_disable_specific_trampoline(struct cachestat_bpf *obj)
170 {
171 int idx = netdata_get_dirty_account_idx();
172 if (idx == NETDATA_CACHESTAT_FOLIO_DIRTY) {
173 bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_fentry, false);
174 bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_fentry, false);
175 } else if (idx == NETDATA_CACHESTAT_SET_PAGE_DIRTY) {
176 bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_fentry, false);
177 bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_fentry, false);
178 } else {
179 bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_fentry, false);
180 bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_fentry, false);
181 }
182 }
183
184 /**
185 * Set trampoline target
186 *
187 * Set the targets we will monitor.
188 *
189 * @param obj is the main structure for bpf objects.
190 */
191 static inline void netdata_set_trampoline_target(struct cachestat_bpf *obj)
192 {
193 bpf_program__set_attach_target(
194 obj->progs.netdata_add_to_page_cache_lru_fentry,
195 0,
196 cachestat_targets[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU].name);
197
198 bpf_program__set_attach_target(
199 obj->progs.netdata_mark_page_accessed_fentry, 0, cachestat_targets[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED].name);
200
201 int idx = netdata_get_dirty_account_idx();
202 const char *target_name = cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name;
203 if (idx == NETDATA_CACHESTAT_FOLIO_DIRTY) {
204 bpf_program__set_attach_target(obj->progs.netdata_folio_mark_dirty_fentry, 0, target_name);
205 } else if (idx == NETDATA_CACHESTAT_SET_PAGE_DIRTY) {
206 bpf_program__set_attach_target(obj->progs.netdata_set_page_dirty_fentry, 0, target_name);
207 } else {
208 bpf_program__set_attach_target(obj->progs.netdata_account_page_dirtied_fentry, 0, target_name);
209 }
210
211 bpf_program__set_attach_target(
212 obj->progs.netdata_mark_buffer_dirty_fentry, 0, cachestat_targets[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY].name);
213 }
214
215 /**
216 * Mount Attach Probe
217 *
218 * Attach probes to target
219 *
220 * @param obj is the main structure for bpf objects.
221 *
222 * @return It returns 0 on success and -1 otherwise.
223 */
224 static int ebpf_cachestat_attach_probe(struct cachestat_bpf *obj)
225 {
226 obj->links.netdata_add_to_page_cache_lru_kprobe = bpf_program__attach_kprobe(
227 obj->progs.netdata_add_to_page_cache_lru_kprobe,
228 false,
229 cachestat_targets[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU].name);
230 long ret = libbpf_get_error(obj->links.netdata_add_to_page_cache_lru_kprobe);
231 if (ret)
232 return -1;
233
234 obj->links.netdata_mark_page_accessed_kprobe = bpf_program__attach_kprobe(
235 obj->progs.netdata_mark_page_accessed_kprobe,
236 false,
237 cachestat_targets[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED].name);
238 ret = libbpf_get_error(obj->links.netdata_mark_page_accessed_kprobe);
239 if (ret)
240 return -1;
241
242 int idx = netdata_get_dirty_account_idx();
243 const char *target_name = cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name;
244 if (idx == NETDATA_CACHESTAT_FOLIO_DIRTY) {
245 obj->links.netdata_folio_mark_dirty_kprobe =
246 bpf_program__attach_kprobe(obj->progs.netdata_folio_mark_dirty_kprobe, false, target_name);
247 ret = libbpf_get_error(obj->links.netdata_folio_mark_dirty_kprobe);
248 } else if (idx == NETDATA_CACHESTAT_SET_PAGE_DIRTY) {
249 obj->links.netdata_set_page_dirty_kprobe =
250 bpf_program__attach_kprobe(obj->progs.netdata_set_page_dirty_kprobe, false, target_name);
251 ret = libbpf_get_error(obj->links.netdata_set_page_dirty_kprobe);
252 } else {
253 obj->links.netdata_account_page_dirtied_kprobe =
254 bpf_program__attach_kprobe(obj->progs.netdata_account_page_dirtied_kprobe, false, target_name);
255 ret = libbpf_get_error(obj->links.netdata_account_page_dirtied_kprobe);
256 }
257
258 if (ret)
259 return -1;
260
261 obj->links.netdata_mark_buffer_dirty_kprobe = bpf_program__attach_kprobe(
262 obj->progs.netdata_mark_buffer_dirty_kprobe,
263 false,
264 cachestat_targets[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY].name);
265 ret = libbpf_get_error(obj->links.netdata_mark_buffer_dirty_kprobe);
266 if (ret)
267 return -1;
268
269 return 0;
270 }
271
272 /**
273 * Adjust Map Size
274 *
275 * Resize maps according input from users.
276 *
277 * @param obj is the main structure for bpf objects.
278 * @param em structure with configuration
279 */
280 static void ebpf_cachestat_adjust_map(struct cachestat_bpf *obj, ebpf_module_t *em)
281 {
282 ebpf_update_map_size(
283 obj->maps.cstat_pid, &cachestat_maps[NETDATA_CACHESTAT_PID_STATS], em, bpf_map__name(obj->maps.cstat_pid));
284
285 ebpf_update_map_type(obj->maps.cstat_global, &cachestat_maps[NETDATA_CACHESTAT_GLOBAL_STATS]);
286 ebpf_update_map_type(obj->maps.cstat_pid, &cachestat_maps[NETDATA_CACHESTAT_PID_STATS]);
287 ebpf_update_map_type(obj->maps.cstat_ctrl, &cachestat_maps[NETDATA_CACHESTAT_CTRL]);
288 }
289
290 /**
291 * Set hash tables
292 *
293 * Set the values for maps according the value given by kernel.
294 *
295 * @param obj is the main structure for bpf objects.
296 */
297 static void ebpf_cachestat_set_hash_tables(struct cachestat_bpf *obj)
298 {
299 cachestat_maps[NETDATA_CACHESTAT_GLOBAL_STATS].map_fd = bpf_map__fd(obj->maps.cstat_global);
300 cachestat_maps[NETDATA_CACHESTAT_PID_STATS].map_fd = bpf_map__fd(obj->maps.cstat_pid);
301 cachestat_maps[NETDATA_CACHESTAT_CTRL].map_fd = bpf_map__fd(obj->maps.cstat_ctrl);
302 }
303
304 /**
305 * Load and attach
306 *
307 * Load and attach the eBPF code in kernel.
308 *
309 * @param obj is the main structure for bpf objects.
310 * @param em structure with configuration
311 *
312 * @return it returns 0 on success and -1 otherwise
313 */
314 static inline int ebpf_cachestat_load_and_attach(struct cachestat_bpf *obj, ebpf_module_t *em)
315 {
316 netdata_ebpf_targets_t *mt = em->targets;
317 netdata_ebpf_program_loaded_t test = mt[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU].mode;
318
319 if (test == EBPF_LOAD_TRAMPOLINE) {
320 ebpf_cachestat_disable_probe(obj);
321 ebpf_cachestat_disable_specific_trampoline(obj);
322
323 netdata_set_trampoline_target(obj);
324 } else {
325 ebpf_cachestat_disable_trampoline(obj);
326 ebpf_cachestat_disable_specific_probe(obj);
327 }
328
329 ebpf_cachestat_adjust_map(obj, em);
330
331 int ret = cachestat_bpf__load(obj);
332 if (ret) {
333 return ret;
334 }
335
336 ret = (test == EBPF_LOAD_TRAMPOLINE) ? cachestat_bpf__attach(obj) : ebpf_cachestat_attach_probe(obj);
337 if (!ret) {
338 ebpf_cachestat_set_hash_tables(obj);
339
340 ebpf_update_controller(cachestat_maps[NETDATA_CACHESTAT_CTRL].map_fd, em);
341 }
342
343 return ret;
344 }
345 #endif
346 /*****************************************************************
347 *
348 * FUNCTIONS TO CLOSE THE THREAD
349 *
350 *****************************************************************/
351
352 static void ebpf_obsolete_specific_cachestat_charts(char *type, int update_every);
353
354 /**
355 * Obsolete services
356 *
357 * Obsolete all service charts created
358 *
359 * @param em a pointer to `struct ebpf_module`
360 */
361 static void ebpf_obsolete_cachestat_services(ebpf_module_t *em, char *id)
362 {
363 ebpf_write_chart_obsolete(
364 id,
365 NETDATA_CACHESTAT_HIT_RATIO_CHART,
366 "",
367 "Hit ratio",
368 EBPF_COMMON_UNITS_PERCENTAGE,
369 NETDATA_CACHESTAT_SUBMENU,
370 NETDATA_EBPF_CHART_TYPE_LINE,
371 NETDATA_SYSTEMD_CACHESTAT_HIT_RATIO_CONTEXT,
372 21100,
373 em->update_every);
374
375 ebpf_write_chart_obsolete(
376 id,
377 NETDATA_CACHESTAT_DIRTY_CHART,
378 "",
379 "Number of dirty pages",
380 EBPF_CACHESTAT_UNITS_PAGE,
381 NETDATA_CACHESTAT_SUBMENU,
382 NETDATA_EBPF_CHART_TYPE_LINE,
383 NETDATA_SYSTEMD_CACHESTAT_MODIFIED_CACHE_CONTEXT,
384 21101,
385 em->update_every);
386
387 ebpf_write_chart_obsolete(
388 id,
389 NETDATA_CACHESTAT_HIT_CHART,
390 "",
391 "Number of accessed files",
392 EBPF_CACHESTAT_UNITS_HITS,
393 NETDATA_CACHESTAT_SUBMENU,
394 NETDATA_EBPF_CHART_TYPE_LINE,
395 NETDATA_SYSTEMD_CACHESTAT_HIT_FILES_CONTEXT,
396 21102,
397 em->update_every);
398
399 ebpf_write_chart_obsolete(
400 id,
401 NETDATA_CACHESTAT_MISSES_CHART,
402 "",
403 "Files out of page cache",
404 EBPF_CACHESTAT_UNITS_MISSES,
405 NETDATA_CACHESTAT_SUBMENU,
406 NETDATA_EBPF_CHART_TYPE_LINE,
407 NETDATA_SYSTEMD_CACHESTAT_MISS_FILES_CONTEXT,
408 21103,
409 em->update_every);
410 }
411
412 /**
413 * Obsolete cgroup chart
414 *
415 * Send obsolete for all charts created before to close.
416 *
417 * @param em a pointer to `struct ebpf_module`
418 */
419 static inline void ebpf_obsolete_cachestat_cgroup_charts(ebpf_module_t *em)
420 {
421 netdata_mutex_lock(&mutex_cgroup_shm);
422
423 ebpf_cgroup_target_t *ect;
424 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
425 if (ect->systemd) {
426 ebpf_obsolete_cachestat_services(em, ect->name);
427
428 continue;
429 }
430
431 ebpf_obsolete_specific_cachestat_charts(ect->name, em->update_every);
432 }
433 netdata_mutex_unlock(&mutex_cgroup_shm);
434 }
435
436 /**
437 * Obsolete global
438 *
439 * Obsolete global charts created by thread.
440 *
441 * @param em a pointer to `struct ebpf_module`
442 */
443 static void ebpf_obsolete_cachestat_global(ebpf_module_t *em)
444 {
445 ebpf_write_chart_obsolete(
446 NETDATA_EBPF_MEMORY_GROUP,
447 NETDATA_CACHESTAT_HIT_RATIO_CHART,
448 "",
449 "Hit ratio",
450 EBPF_COMMON_UNITS_PERCENTAGE,
451 NETDATA_CACHESTAT_SUBMENU,
452 NETDATA_EBPF_CHART_TYPE_LINE,
453 NETDATA_MEM_CACHESTAT_HIT_RATIO_CONTEXT,
454 21100,
455 em->update_every);
456
457 ebpf_write_chart_obsolete(
458 NETDATA_EBPF_MEMORY_GROUP,
459 NETDATA_CACHESTAT_DIRTY_CHART,
460 "",
461 "Number of dirty pages",
462 EBPF_CACHESTAT_UNITS_PAGE,
463 NETDATA_CACHESTAT_SUBMENU,
464 NETDATA_EBPF_CHART_TYPE_LINE,
465 NETDATA_MEM_CACHESTAT_MODIFIED_CACHE_CONTEXT,
466 21101,
467 em->update_every);
468
469 ebpf_write_chart_obsolete(
470 NETDATA_EBPF_MEMORY_GROUP,
471 NETDATA_CACHESTAT_HIT_CHART,
472 "",
473 "Number of accessed files",
474 EBPF_CACHESTAT_UNITS_HITS,
475 NETDATA_CACHESTAT_SUBMENU,
476 NETDATA_EBPF_CHART_TYPE_LINE,
477 NETDATA_MEM_CACHESTAT_HIT_FILES_CONTEXT,
478 21102,
479 em->update_every);
480
481 ebpf_write_chart_obsolete(
482 NETDATA_EBPF_MEMORY_GROUP,
483 NETDATA_CACHESTAT_MISSES_CHART,
484 "",
485 "Files out of page cache",
486 EBPF_CACHESTAT_UNITS_MISSES,
487 NETDATA_CACHESTAT_SUBMENU,
488 NETDATA_EBPF_CHART_TYPE_LINE,
489 NETDATA_MEM_CACHESTAT_MISS_FILES_CONTEXT,
490 21103,
491 em->update_every);
492 }
493
494 /**
495 * Obsolette apps charts
496 *
497 * Obsolete apps charts.
498 *
499 * @param em a pointer to the structure with the default values.
500 */
501 void ebpf_obsolete_cachestat_apps_charts(struct ebpf_module *em)
502 {
503 struct ebpf_target *w;
504 int update_every = em->update_every;
505 netdata_mutex_lock(&collect_data_mutex);
506 for (w = apps_groups_root_target; w; w = w->next) {
507 if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_CACHESTAT_IDX))))
508 continue;
509
510 ebpf_write_chart_obsolete(
511 NETDATA_APP_FAMILY,
512 w->clean_name,
513 "_ebpf_cachestat_hit_ratio",
514 "Hit ratio",
515 EBPF_COMMON_UNITS_PERCENTAGE,
516 NETDATA_CACHESTAT_SUBMENU,
517 NETDATA_EBPF_CHART_TYPE_LINE,
518 "app.ebpf_cachestat_hit_ratio",
519 20260,
520 update_every);
521
522 ebpf_write_chart_obsolete(
523 NETDATA_APP_FAMILY,
524 w->clean_name,
525 "_ebpf_cachestat_dirty_pages",
526 "Number of dirty pages",
527 EBPF_CACHESTAT_UNITS_PAGE,
528 NETDATA_CACHESTAT_SUBMENU,
529 NETDATA_EBPF_CHART_TYPE_STACKED,
530 "app.ebpf_cachestat_dirty_pages",
531 20261,
532 update_every);
533
534 ebpf_write_chart_obsolete(
535 NETDATA_APP_FAMILY,
536 w->clean_name,
537 "_ebpf_cachestat_access",
538 "Number of accessed files",
539 EBPF_CACHESTAT_UNITS_HITS,
540 NETDATA_CACHESTAT_SUBMENU,
541 NETDATA_EBPF_CHART_TYPE_STACKED,
542 "app.ebpf_cachestat_access",
543 20262,
544 update_every);
545
546 ebpf_write_chart_obsolete(
547 NETDATA_APP_FAMILY,
548 w->clean_name,
549 "_ebpf_cachestat_misses",
550 "Files out of page cache",
551 EBPF_CACHESTAT_UNITS_MISSES,
552 NETDATA_CACHESTAT_SUBMENU,
553 NETDATA_EBPF_CHART_TYPE_STACKED,
554 "app.ebpf_cachestat_misses",
555 20263,
556 update_every);
557 w->charts_created &= ~(1 << EBPF_MODULE_CACHESTAT_IDX);
558 }
559 netdata_mutex_unlock(&collect_data_mutex);
560 }
561
562 /**
563 * Cachestat exit.
564 *
565 * Cancel child and exit.
566 *
567 * @param ptr thread data.
568 */
569 void ebpf_cachestat_unload_bpf(ebpf_module_t *em)
570 {
571 #ifdef LIBBPF_MAJOR_VERSION
572 if (cachestat_bpf_obj) {
573 cachestat_bpf__destroy(cachestat_bpf_obj);
574 cachestat_bpf_obj = NULL;
575 }
576 #endif
577 if ((em->load & EBPF_LOAD_LEGACY) && em->probe_links) {
578 ebpf_unload_legacy_code(em->objects, em->probe_links);
579 em->objects = NULL;
580 em->probe_links = NULL;
581 }
582 }
583
584 static void ebpf_cachestat_exit(void *pptr)
585 {
586 ebpf_set_pid_map_fd(NETDATA_EBPF_PIDS_CACHESTAT_IDX, -1);
587 ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr);
588 if (!em)
589 return;
590
591 if (!cachestat_safe_clean) {
592 netdata_mutex_lock(&ebpf_exit_cleanup);
593 ebpf_module_enabled_set(em, NETDATA_THREAD_EBPF_STOPPED);
594 netdata_mutex_unlock(&ebpf_exit_cleanup);
595 return;
596 }
597
598 netdata_mutex_lock(&lock);
599 collect_pids &= ~(1 << EBPF_MODULE_CACHESTAT_IDX);
600 netdata_mutex_unlock(&lock);
601
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 (ebpf_module_enabled_get(em) == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
613 netdata_mutex_lock(&lock);
614 if (em->cgroup_charts) {
615 ebpf_obsolete_cachestat_cgroup_charts(em);
616 fflush(stdout);
617 }
618
619 if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) {
620 ebpf_obsolete_cachestat_apps_charts(em);
621 }
622
623 ebpf_obsolete_cachestat_global(em);
624
625 fflush(stdout);
626 netdata_mutex_unlock(&lock);
627 }
628
629 if (!ebpf_plugin_stop() && em->functions.bpf_unload)
630 em->functions.bpf_unload(em);
631
632 netdata_mutex_lock(&ebpf_exit_cleanup);
633 ebpf_module_enabled_set(em, NETDATA_THREAD_EBPF_STOPPED);
634 netdata_mutex_unlock(&ebpf_exit_cleanup);
635
636 freez(cachestat_vector);
637 cachestat_vector = NULL;
638 freez(cachestat_values);
639 cachestat_values = NULL;
640 }
641
642 /*****************************************************************
643 *
644 * COMMON FUNCTIONS
645 *
646 *****************************************************************/
647
648 /**
649 * Update publish
650 *
651 * Update publish values before to write dimension.
652 *
653 * @param out structure that will receive data.
654 * @param mpa calls for mark_page_accessed during the last second.
655 * @param mbd calls for mark_buffer_dirty during the last second.
656 * @param apcl calls for add_to_page_cache_lru during the last second.
657 * @param apd calls for account_page_dirtied during the last second.
658 */
659 static void
660 cachestat_update_publish(netdata_publish_cachestat_t *out, uint64_t mpa, uint64_t mbd, uint64_t apcl, uint64_t apd)
661 {
662 // Adapted algorithm from https://github.com/iovisor/bcc/blob/master/tools/cachestat.py#L126-L138
663 NETDATA_DOUBLE total = (NETDATA_DOUBLE)mpa - (NETDATA_DOUBLE)mbd;
664 if (total < 0)
665 total = 0;
666
667 NETDATA_DOUBLE misses = (NETDATA_DOUBLE)apcl - (NETDATA_DOUBLE)apd;
668 if (misses < 0)
669 misses = 0;
670
671 // If hits are < 0, then its possible misses are overestimate due to possibly page cache read ahead adding
672 // more pages than needed. In this case just assume misses as total and reset hits.
673 NETDATA_DOUBLE hits = total - misses;
674 if (hits < 0) {
675 misses = total;
676 hits = 0;
677 }
678
679 NETDATA_DOUBLE ratio = (total > 0) ? hits / total : 1;
680
681 out->ratio = (long long)(ratio * 100);
682 out->hit = (long long)hits;
683 out->miss = (long long)misses;
684 }
685
686 /**
687 * Calculate cachestat from current and previous values
688 *
689 * Calculate delta values and update publish structure.
690 *
691 * @param out structure that will receive data.
692 * @param current pointer to current cache statistics.
693 * @param prev pointer to previous cache statistics.
694 */
695 static void cachestat_calculate_from_values(
696 netdata_publish_cachestat_t *out,
697 const netdata_cachestat_t *current,
698 const netdata_cachestat_t *prev)
699 {
700 int64_t mpa = (int64_t)current->mark_page_accessed - (int64_t)prev->mark_page_accessed;
701 if (mpa < 0)
702 mpa = 0;
703
704 int64_t mbd = (int64_t)current->mark_buffer_dirty - (int64_t)prev->mark_buffer_dirty;
705 if (mbd < 0)
706 mbd = 0;
707
708 int64_t apcl = (int64_t)current->add_to_page_cache_lru - (int64_t)prev->add_to_page_cache_lru;
709 if (apcl < 0)
710 apcl = 0;
711
712 int64_t apd = (int64_t)current->account_page_dirtied - (int64_t)prev->account_page_dirtied;
713 if (apd < 0)
714 apd = 0;
715
716 out->dirty = (long long)mbd;
717 cachestat_update_publish(out, mpa, mbd, apcl, apd);
718 }
719
720 /**
721 * Initialize cachestat
722 *
723 * Initialize prev values on first call.
724 *
725 * @param publish the structure where we will store the data.
726 */
727 static void cachestat_initialize(netdata_publish_cachestat_t *publish)
728 {
729 publish->prev.mark_page_accessed = cachestat_hash_values[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED];
730 publish->prev.account_page_dirtied = cachestat_hash_values[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED];
731 publish->prev.add_to_page_cache_lru = cachestat_hash_values[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU];
732 publish->prev.mark_buffer_dirty = cachestat_hash_values[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY];
733 }
734
735 /**
736 * Calculate statistics
737 *
738 * @param publish the structure where we will store the data.
739 */
740 static void calculate_stats(netdata_publish_cachestat_t *publish)
741 {
742 if (!publish->prev.mark_page_accessed && !publish->prev.add_to_page_cache_lru && !publish->prev.mark_buffer_dirty &&
743 !publish->prev.account_page_dirtied) {
744 cachestat_initialize(publish);
745 return;
746 }
747
748 netdata_cachestat_t current = {
749 .mark_page_accessed = cachestat_hash_values[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED],
750 .mark_buffer_dirty = cachestat_hash_values[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY],
751 .add_to_page_cache_lru = cachestat_hash_values[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU],
752 .account_page_dirtied = cachestat_hash_values[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED]};
753
754 cachestat_calculate_from_values(publish, &current, &publish->prev);
755
756 publish->prev.mark_page_accessed = current.mark_page_accessed;
757 publish->prev.account_page_dirtied = current.account_page_dirtied;
758 publish->prev.add_to_page_cache_lru = current.add_to_page_cache_lru;
759 publish->prev.mark_buffer_dirty = current.mark_buffer_dirty;
760 }
761
762 /*****************************************************************
763 *
764 * APPS
765 *
766 *****************************************************************/
767
768 /**
769 * Apps Accumulator
770 *
771 * Sum all values read from kernel and store in the first address.
772 *
773 * @param out the vector with read values.
774 * @param maps_per_core do I need to read all cores?
775 */
776 static void cachestat_apps_accumulator(netdata_cachestat_pid_t *out, int maps_per_core)
777 {
778 int i, end = (maps_per_core) ? ebpf_nprocs : 1;
779 netdata_cachestat_pid_t *total = &out[0];
780 for (i = 1; i < end; i++) {
781 if (ebpf_plugin_stop())
782 break;
783
784 netdata_cachestat_pid_t *w = &out[i];
785 total->account_page_dirtied += w->account_page_dirtied;
786 total->add_to_page_cache_lru += w->add_to_page_cache_lru;
787 total->mark_buffer_dirty += w->mark_buffer_dirty;
788 total->mark_page_accessed += w->mark_page_accessed;
789 if (w->ct > total->ct)
790 total->ct = w->ct;
791
792 if (!total->name[0] && w->name[0])
793 strncpyz(total->name, w->name, sizeof(total->name));
794 }
795 }
796
797 /**
798 * Save Pid values
799 *
800 * Save the current values inside the structure
801 *
802 * @param out vector used to plot charts
803 * @param in vector with values read from hash tables.
804 */
805 static inline void cachestat_save_pid_values(netdata_publish_cachestat_t *out, netdata_cachestat_pid_t *in)
806 {
807 out->ct = in->ct;
808 memcpy(&out->prev, &out->current, sizeof(netdata_cachestat_t));
809
810 out->current.account_page_dirtied = in[0].account_page_dirtied;
811 out->current.add_to_page_cache_lru = in[0].add_to_page_cache_lru;
812 out->current.mark_buffer_dirty = in[0].mark_buffer_dirty;
813 out->current.mark_page_accessed = in[0].mark_page_accessed;
814 }
815
816 /**
817 * Read APPS table
818 *
819 * Read the apps table and store data inside the structure.
820 *
821 * @param maps_per_core do I need to read all cores?
822 */
823 static void ebpf_read_cachestat_apps_table(int maps_per_core)
824 {
825 netdata_cachestat_pid_t *cv = cachestat_vector;
826 int fd = cachestat_maps[NETDATA_CACHESTAT_PID_STATS].map_fd;
827 size_t length = sizeof(netdata_cachestat_pid_t);
828 if (maps_per_core)
829 length *= ebpf_nprocs;
830
831 uint32_t key = 0, next_key = 0;
832 while (bpf_map_get_next_key(fd, &key, &next_key) == 0) {
833 if (ebpf_plugin_stop())
834 break;
835
836 if (bpf_map_lookup_elem(fd, &key, cv)) {
837 goto end_cachestat_loop;
838 }
839
840 cachestat_apps_accumulator(cv, 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)
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 {
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 }
854 }
855
856 end_cachestat_loop:
857 // We are cleaning to avoid passing data read from one process to other.
858 memset(cv, 0, length);
859 key = next_key;
860 }
861 }
862
863 /**
864 * Update cgroup
865 *
866 * Update cgroup data based in
867 *
868 * @param maps_per_core do I need to read all cores?
869 */
870 static void ebpf_update_cachestat_cgroup()
871 {
872 ebpf_cgroup_target_t *ect;
873 netdata_mutex_lock(&mutex_cgroup_shm);
874 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
875 if (ebpf_plugin_stop())
876 break;
877
878 struct pid_on_target2 *pids;
879 for (pids = ect->pids; pids; pids = pids->next) {
880 if (ebpf_plugin_stop())
881 break;
882
883 uint32_t pid = pids->pid;
884 netdata_publish_cachestat_t *out = &pids->cachestat;
885
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;
891 memcpy(&out->current, &in->current, sizeof(netdata_cachestat_t));
892 }
893 }
894 netdata_mutex_unlock(&mutex_cgroup_shm);
895 }
896
897 static inline void sum_single_pid_cachestat(netdata_cachestat_t *dst, const netdata_cachestat_t *src)
898 {
899 dst->account_page_dirtied += src->account_page_dirtied;
900 dst->add_to_page_cache_lru += src->add_to_page_cache_lru;
901 dst->mark_buffer_dirty += src->mark_buffer_dirty;
902 dst->mark_page_accessed += src->mark_page_accessed;
903 }
904
905 static void cachestat_sum_pids_internal(netdata_publish_cachestat_t *publish, void *root, bool is_cgroup)
906 {
907 netdata_cachestat_t new_prev = publish->current;
908 memset(&publish->current, 0, sizeof(publish->current));
909 netdata_cachestat_t *dst = &publish->current;
910
911 if (is_cgroup) {
912 struct pid_on_target2 *r = (struct pid_on_target2 *)root;
913 for (; r; r = r->next) {
914 if (ebpf_plugin_stop())
915 break;
916 sum_single_pid_cachestat(dst, &r->cachestat.current);
917 }
918 } else {
919 struct ebpf_pid_on_target *r = (struct ebpf_pid_on_target *)root;
920 for (; r; r = r->next) {
921 if (ebpf_plugin_stop())
922 break;
923
924 uint32_t pid = r->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);
930 }
931 }
932 publish->prev = new_prev;
933 }
934
935 static void write_cachestat_charts(
936 const char *family,
937 const char *name,
938 const netdata_publish_cachestat_t *npc,
939 const char *ratio_name,
940 const char *dirty_name,
941 const char *hit_name,
942 const char *miss_name)
943 {
944 if (!ratio_name) {
945 /* app charts use the new-style naming from ebpf_cachestat_create_apps_charts() */
946 ebpf_write_begin_chart(family, name, "_ebpf_cachestat_hit_ratio");
947 write_chart_dimension("ratio", (long long)npc->ratio);
948 ebpf_write_end_chart();
949
950 ebpf_write_begin_chart(family, name, "_ebpf_cachestat_dirty_pages");
951 write_chart_dimension("pages", (long long)npc->dirty);
952 ebpf_write_end_chart();
953
954 ebpf_write_begin_chart(family, name, "_ebpf_cachestat_access");
955 write_chart_dimension("hits", (long long)npc->hit);
956 ebpf_write_end_chart();
957
958 ebpf_write_begin_chart(family, name, "_ebpf_cachestat_misses");
959 write_chart_dimension("misses", (long long)npc->miss);
960 ebpf_write_end_chart();
961 return;
962 }
963
964 ebpf_write_begin_chart(family, name, NETDATA_CACHESTAT_HIT_RATIO_CHART);
965 write_chart_dimension(ratio_name, (long long)npc->ratio);
966 ebpf_write_end_chart();
967
968 ebpf_write_begin_chart(family, name, NETDATA_CACHESTAT_DIRTY_CHART);
969 write_chart_dimension(dirty_name, (long long)npc->dirty);
970 ebpf_write_end_chart();
971
972 ebpf_write_begin_chart(family, name, NETDATA_CACHESTAT_HIT_CHART);
973 write_chart_dimension(hit_name, (long long)npc->hit);
974 ebpf_write_end_chart();
975
976 ebpf_write_begin_chart(family, name, NETDATA_CACHESTAT_MISSES_CHART);
977 write_chart_dimension(miss_name, (long long)npc->miss);
978 ebpf_write_end_chart();
979 }
980
981 void ebpf_cachestat_sum_pids(netdata_publish_cachestat_t *publish, struct ebpf_pid_on_target *root)
982 {
983 cachestat_sum_pids_internal(publish, root, false);
984 }
985
986 /**
987 * Resume apps data
988 */
989 void ebpf_cachestat_resume_apps_data()
990 {
991 struct ebpf_target *w;
992
993 netdata_mutex_lock(&collect_data_mutex);
994 for (w = apps_groups_root_target; w; w = w->next) {
995 if (ebpf_plugin_stop())
996 break;
997
998 if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_CACHESTAT_IDX))))
999 continue;
1000
1001 ebpf_cachestat_sum_pids(&w->cachestat, w->root_pid);
1002 }
1003 netdata_mutex_unlock(&collect_data_mutex);
1004 }
1005
1006 /**
1007 * Cachestat thread
1008 *
1009 * Thread used to generate cachestat charts.
1010 *
1011 * @param ptr a pointer to `struct ebpf_module`
1012 *
1013 * @return It always return NULL
1014 */
1015 void ebpf_read_cachestat_thread(void *ptr)
1016 {
1017 ebpf_module_t *em = (ebpf_module_t *)ptr;
1018 int collect_pid = (em->apps_charts || em->cgroup_charts);
1019 if (!collect_pid)
1020 return;
1021
1022 int maps_per_core = em->maps_per_core;
1023 int update_every = em->update_every;
1024 int cgroups = em->cgroup_charts;
1025
1026 int counter = update_every - 1;
1027
1028 uint32_t lifetime = em->lifetime;
1029 uint32_t running_time = 0;
1030 ebpf_set_pid_map_fd(NETDATA_EBPF_PIDS_CACHESTAT_IDX, cachestat_maps[NETDATA_CACHESTAT_PID_STATS].map_fd);
1031 heartbeat_t hb;
1032 heartbeat_init(&hb, USEC_PER_SEC);
1033 while (!ebpf_plugin_stop() && running_time < lifetime) {
1034 if (ebpf_plugin_stop())
1035 break;
1036
1037 (void)heartbeat_next(&hb);
1038 if (ebpf_plugin_stop())
1039 break;
1040
1041 if (++counter != update_every)
1042 continue;
1043
1044 if (!ebpf_shm_sem_wait_or_stop(shm_mutex_ebpf_integration)) {
1045 if (errno != ECANCELED)
1046 netdata_log_error("CACHESTAT: Failed to wait on semaphore.");
1047 break;
1048 }
1049
1050 ebpf_read_cachestat_apps_table(maps_per_core);
1051 ebpf_cachestat_resume_apps_data();
1052 if (ebpf_plugin_stop()) {
1053 if (sem_post(shm_mutex_ebpf_integration))
1054 netdata_log_error("CACHESTAT: Failed to post semaphore.");
1055 break;
1056 }
1057
1058 if (cgroups && ebpf_cgroup_integration_active_get())
1059 ebpf_update_cachestat_cgroup();
1060 if (sem_post(shm_mutex_ebpf_integration)) {
1061 netdata_log_error("CACHESTAT: Failed to post semaphore.");
1062 break;
1063 }
1064
1065 counter = 0;
1066
1067 if (ebpf_plugin_stop())
1068 break;
1069
1070 netdata_mutex_lock(&ebpf_exit_cleanup);
1071 if (running_time)
1072 running_time += update_every;
1073 else
1074 running_time = update_every;
1075 em->running_time = running_time;
1076 netdata_mutex_unlock(&ebpf_exit_cleanup);
1077 }
1078 }
1079
1080 /**
1081 * Create apps charts
1082 *
1083 * Call ebpf_create_chart to create the charts on apps submenu.
1084 *
1085 * @param em a pointer to the structure with the default values.
1086 */
1087 void ebpf_cachestat_create_apps_charts(struct ebpf_module *em, void *ptr)
1088 {
1089 struct ebpf_target *root = ptr;
1090 struct ebpf_target *w;
1091 int update_every = em->update_every;
1092 for (w = root; w; w = w->next) {
1093 if (ebpf_plugin_stop())
1094 break;
1095
1096 if (unlikely(!w->exposed))
1097 continue;
1098
1099 ebpf_write_chart_cmd(
1100 NETDATA_APP_FAMILY,
1101 w->clean_name,
1102 "_ebpf_cachestat_hit_ratio",
1103 "Hit ratio",
1104 EBPF_COMMON_UNITS_PERCENTAGE,
1105 NETDATA_CACHESTAT_SUBMENU,
1106 NETDATA_EBPF_CHART_TYPE_LINE,
1107 "app.ebpf_cachestat_hit_ratio",
1108 20260,
1109 update_every,
1110 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1111 ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO);
1112 ebpf_commit_label();
1113 fprintf(stdout, "DIMENSION ratio '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]);
1114
1115 ebpf_write_chart_cmd(
1116 NETDATA_APP_FAMILY,
1117 w->clean_name,
1118 "_ebpf_cachestat_dirty_pages",
1119 "Number of dirty pages",
1120 EBPF_CACHESTAT_UNITS_PAGE,
1121 NETDATA_CACHESTAT_SUBMENU,
1122 NETDATA_EBPF_CHART_TYPE_LINE,
1123 "app.ebpf_cachestat_dirty_pages",
1124 20261,
1125 update_every,
1126 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1127 ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO);
1128 ebpf_commit_label();
1129 fprintf(stdout, "DIMENSION pages '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]);
1130
1131 ebpf_write_chart_cmd(
1132 NETDATA_APP_FAMILY,
1133 w->clean_name,
1134 "_ebpf_cachestat_access",
1135 "Number of accessed files",
1136 EBPF_CACHESTAT_UNITS_HITS,
1137 NETDATA_CACHESTAT_SUBMENU,
1138 NETDATA_EBPF_CHART_TYPE_STACKED,
1139 "app.ebpf_cachestat_access",
1140 20262,
1141 update_every,
1142 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1143 ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO);
1144 ebpf_commit_label();
1145 fprintf(stdout, "DIMENSION hits '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]);
1146
1147 ebpf_write_chart_cmd(
1148 NETDATA_APP_FAMILY,
1149 w->clean_name,
1150 "_ebpf_cachestat_misses",
1151 "Files out of page cache",
1152 EBPF_CACHESTAT_UNITS_MISSES,
1153 NETDATA_CACHESTAT_SUBMENU,
1154 NETDATA_EBPF_CHART_TYPE_STACKED,
1155 "app.ebpf_cachestat_misses",
1156 20263,
1157 update_every,
1158 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1159 ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO);
1160 ebpf_commit_label();
1161 fprintf(stdout, "DIMENSION misses '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]);
1162 w->charts_created |= 1 << EBPF_MODULE_CACHESTAT_IDX;
1163 }
1164
1165 em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED;
1166 }
1167
1168 /*****************************************************************
1169 *
1170 * MAIN LOOP
1171 *
1172 *****************************************************************/
1173
1174 /**
1175 * Read global counter
1176 *
1177 * Read the table with number of calls for all functions
1178 *
1179 * @param stats vector used to read data from control table.
1180 * @param maps_per_core do I need to read all cores?
1181 */
1182 static void ebpf_cachestat_read_global_tables(netdata_idx_t *stats, int maps_per_core)
1183 {
1184 ebpf_read_global_table_stats(
1185 cachestat_hash_values,
1186 cachestat_values,
1187 cachestat_maps[NETDATA_CACHESTAT_GLOBAL_STATS].map_fd,
1188 maps_per_core,
1189 NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU,
1190 NETDATA_CACHESTAT_END);
1191
1192 ebpf_read_global_table_stats(
1193 stats,
1194 cachestat_values,
1195 cachestat_maps[NETDATA_CACHESTAT_CTRL].map_fd,
1196 maps_per_core,
1197 NETDATA_CONTROLLER_PID_TABLE_ADD,
1198 NETDATA_CONTROLLER_END);
1199 }
1200
1201 /**
1202 * Send global
1203 *
1204 * Send global charts to Netdata
1205 */
1206 static void cachestat_send_global(netdata_publish_cachestat_t *publish)
1207 {
1208 calculate_stats(publish);
1209
1210 netdata_publish_syscall_t *ptr = cachestat_counter_publish_aggregated;
1211 ebpf_one_dimension_write_charts(
1212 NETDATA_EBPF_MEMORY_GROUP,
1213 NETDATA_CACHESTAT_HIT_RATIO_CHART,
1214 ptr[NETDATA_CACHESTAT_IDX_RATIO].dimension,
1215 publish->ratio);
1216
1217 ebpf_one_dimension_write_charts(
1218 NETDATA_EBPF_MEMORY_GROUP,
1219 NETDATA_CACHESTAT_DIRTY_CHART,
1220 ptr[NETDATA_CACHESTAT_IDX_DIRTY].dimension,
1221 (long long)cachestat_hash_values[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY]);
1222
1223 ebpf_one_dimension_write_charts(
1224 NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_HIT_CHART, ptr[NETDATA_CACHESTAT_IDX_HIT].dimension, publish->hit);
1225
1226 ebpf_one_dimension_write_charts(
1227 NETDATA_EBPF_MEMORY_GROUP,
1228 NETDATA_CACHESTAT_MISSES_CHART,
1229 ptr[NETDATA_CACHESTAT_IDX_MISS].dimension,
1230 publish->miss);
1231 }
1232
1233 /**
1234 * Send data to Netdata calling auxiliary functions.
1235 *
1236 * @param root the target list.
1237 */
1238 void ebpf_cache_send_apps_data(struct ebpf_target *root)
1239 {
1240 struct ebpf_target *w;
1241
1242 netdata_mutex_lock(&collect_data_mutex);
1243 for (w = root; w; w = w->next) {
1244 if (ebpf_plugin_stop())
1245 break;
1246
1247 if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_CACHESTAT_IDX))))
1248 continue;
1249
1250 cachestat_calculate_from_values(&w->cachestat, &w->cachestat.current, &w->cachestat.prev);
1251 write_cachestat_charts(NETDATA_APP_FAMILY, w->clean_name, &w->cachestat, NULL, NULL, NULL, NULL);
1252 }
1253 netdata_mutex_unlock(&collect_data_mutex);
1254 }
1255
1256 void ebpf_cachestat_sum_cgroup_pids(netdata_publish_cachestat_t *publish, struct pid_on_target2 *root)
1257 {
1258 cachestat_sum_pids_internal(publish, root, true);
1259 }
1260
1261 /**
1262 * Calc chart values
1263 *
1264 * Do necessary math to plot charts.
1265 */
1266 void ebpf_cachestat_calc_chart_values()
1267 {
1268 ebpf_cgroup_target_t *ect;
1269 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
1270 if (ebpf_plugin_stop())
1271 break;
1272
1273 ebpf_cachestat_sum_cgroup_pids(&ect->publish_cachestat, ect->pids);
1274 cachestat_calculate_from_values(
1275 &ect->publish_cachestat, &ect->publish_cachestat.current, &ect->publish_cachestat.prev);
1276 }
1277 }
1278
1279 /**
1280 * Create Systemd cachestat Charts
1281 *
1282 * Create charts when systemd is enabled
1283 *
1284 * @param update_every value to overwrite the update frequency set by the server.
1285 **/
1286 static void ebpf_create_systemd_cachestat_charts(int update_every)
1287 {
1288 static ebpf_systemd_args_t data_hit_ratio = {
1289 .title = "Hit ratio",
1290 .units = EBPF_COMMON_UNITS_PERCENTAGE,
1291 .family = NETDATA_CACHESTAT_SUBMENU,
1292 .charttype = NETDATA_EBPF_CHART_TYPE_LINE,
1293 .order = 21100,
1294 .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE,
1295 .context = NETDATA_SYSTEMD_CACHESTAT_HIT_RATIO_CONTEXT,
1296 .module = NETDATA_EBPF_MODULE_NAME_CACHESTAT,
1297 .update_every = 0,
1298 .suffix = NETDATA_CACHESTAT_HIT_RATIO_CHART,
1299 .dimension = "percentage"};
1300
1301 static ebpf_systemd_args_t data_dirty = {
1302 .title = "Number of dirty pages",
1303 .units = EBPF_CACHESTAT_UNITS_PAGE,
1304 .family = NETDATA_CACHESTAT_SUBMENU,
1305 .charttype = NETDATA_EBPF_CHART_TYPE_LINE,
1306 .order = 21101,
1307 .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE,
1308 .context = NETDATA_SYSTEMD_CACHESTAT_MODIFIED_CACHE_CONTEXT,
1309 .module = NETDATA_EBPF_MODULE_NAME_CACHESTAT,
1310 .update_every = 0,
1311 .suffix = NETDATA_CACHESTAT_DIRTY_CHART,
1312 .dimension = "pages"};
1313
1314 static ebpf_systemd_args_t data_hit = {
1315 .title = "Number of accessed files",
1316 .units = EBPF_CACHESTAT_UNITS_HITS,
1317 .family = NETDATA_CACHESTAT_SUBMENU,
1318 .charttype = NETDATA_EBPF_CHART_TYPE_LINE,
1319 .order = 21102,
1320 .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE,
1321 .context = NETDATA_SYSTEMD_CACHESTAT_HIT_FILES_CONTEXT,
1322 .module = NETDATA_EBPF_MODULE_NAME_CACHESTAT,
1323 .update_every = 0,
1324 .suffix = NETDATA_CACHESTAT_HIT_CHART,
1325 .dimension = "hits"};
1326
1327 static ebpf_systemd_args_t data_miss = {
1328 .title = "Files out of page cache",
1329 .units = EBPF_CACHESTAT_UNITS_MISSES,
1330 .family = NETDATA_CACHESTAT_SUBMENU,
1331 .charttype = NETDATA_EBPF_CHART_TYPE_LINE,
1332 .order = 21103,
1333 .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE,
1334 .context = NETDATA_SYSTEMD_CACHESTAT_MISS_FILES_CONTEXT,
1335 .module = NETDATA_EBPF_MODULE_NAME_CACHESTAT,
1336 .update_every = 0,
1337 .suffix = NETDATA_CACHESTAT_MISSES_CHART,
1338 .dimension = "misses"};
1339
1340 if (!data_miss.update_every)
1341 data_hit_ratio.update_every = data_dirty.update_every = data_hit.update_every = data_miss.update_every =
1342 update_every;
1343
1344 ebpf_cgroup_target_t *w;
1345 for (w = ebpf_cgroup_pids; w; w = w->next) {
1346 if (ebpf_plugin_stop())
1347 break;
1348
1349 if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_CACHESTAT_CHART))
1350 continue;
1351
1352 data_hit_ratio.id = data_dirty.id = data_hit.id = data_miss.id = w->name;
1353 ebpf_create_charts_on_systemd(&data_hit_ratio);
1354
1355 ebpf_create_charts_on_systemd(&data_dirty);
1356
1357 ebpf_create_charts_on_systemd(&data_hit);
1358
1359 ebpf_create_charts_on_systemd(&data_miss);
1360
1361 w->flags |= NETDATA_EBPF_SERVICES_HAS_CACHESTAT_CHART;
1362 }
1363 }
1364
1365 /**
1366 * Send Cache Stat charts
1367 *
1368 * Send collected data to Netdata.
1369 */
1370 static void ebpf_send_systemd_cachestat_charts()
1371 {
1372 ebpf_cgroup_target_t *ect;
1373
1374 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
1375 if (ebpf_plugin_stop())
1376 break;
1377
1378 if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_CACHESTAT_CHART))) {
1379 continue;
1380 }
1381
1382 write_cachestat_charts(ect->name, "", &ect->publish_cachestat, "percentage", "pages", "hits", "misses");
1383 }
1384 }
1385
1386 /**
1387 * Send Directory Cache charts
1388 *
1389 * Send collected data to Netdata.
1390 */
1391 static void ebpf_send_specific_cachestat_data(char *type, netdata_publish_cachestat_t *npc)
1392 {
1393 write_cachestat_charts(
1394 type,
1395 "",
1396 npc,
1397 cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_RATIO].name,
1398 cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_DIRTY].name,
1399 cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_HIT].name,
1400 cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_MISS].name);
1401 }
1402
1403 /**
1404 * Create specific cache Stat charts
1405 *
1406 * Create charts for cgroup/application.
1407 *
1408 * @param type the chart type.
1409 * @param update_every value to overwrite the update frequency set by the server.
1410 */
1411 static void ebpf_create_specific_cachestat_charts(char *type, int update_every)
1412 {
1413 char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type;
1414 ebpf_create_chart(
1415 type,
1416 NETDATA_CACHESTAT_HIT_RATIO_CHART,
1417 "Hit ratio",
1418 EBPF_COMMON_UNITS_PERCENTAGE,
1419 NETDATA_CACHESTAT_SUBMENU,
1420 NETDATA_CGROUP_CACHESTAT_HIT_RATIO_CONTEXT,
1421 NETDATA_EBPF_CHART_TYPE_LINE,
1422 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5200,
1423 ebpf_create_global_dimension,
1424 cachestat_counter_publish_aggregated,
1425 1,
1426 update_every,
1427 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1428 ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO);
1429 ebpf_commit_label();
1430
1431 ebpf_create_chart(
1432 type,
1433 NETDATA_CACHESTAT_DIRTY_CHART,
1434 "Number of dirty pages",
1435 EBPF_CACHESTAT_UNITS_PAGE,
1436 NETDATA_CACHESTAT_SUBMENU,
1437 NETDATA_CGROUP_CACHESTAT_MODIFIED_CACHE_CONTEXT,
1438 NETDATA_EBPF_CHART_TYPE_LINE,
1439 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5201,
1440 ebpf_create_global_dimension,
1441 &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_DIRTY],
1442 1,
1443 update_every,
1444 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1445 ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO);
1446 ebpf_commit_label();
1447
1448 ebpf_create_chart(
1449 type,
1450 NETDATA_CACHESTAT_HIT_CHART,
1451 "Number of accessed files",
1452 EBPF_CACHESTAT_UNITS_HITS,
1453 NETDATA_CACHESTAT_SUBMENU,
1454 NETDATA_CGROUP_CACHESTAT_HIT_FILES_CONTEXT,
1455 NETDATA_EBPF_CHART_TYPE_LINE,
1456 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5202,
1457 ebpf_create_global_dimension,
1458 &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_HIT],
1459 1,
1460 update_every,
1461 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1462 ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO);
1463 ebpf_commit_label();
1464
1465 ebpf_create_chart(
1466 type,
1467 NETDATA_CACHESTAT_MISSES_CHART,
1468 "Files out of page cache",
1469 EBPF_CACHESTAT_UNITS_MISSES,
1470 NETDATA_CACHESTAT_SUBMENU,
1471 NETDATA_CGROUP_CACHESTAT_MISS_FILES_CONTEXT,
1472 NETDATA_EBPF_CHART_TYPE_LINE,
1473 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5203,
1474 ebpf_create_global_dimension,
1475 &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_MISS],
1476 1,
1477 update_every,
1478 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1479 ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO);
1480 ebpf_commit_label();
1481 }
1482
1483 /**
1484 * Obsolete specific cache stat charts
1485 *
1486 * Obsolete charts for cgroup/application.
1487 *
1488 * @param type the chart type.
1489 * @param update_every value to overwrite the update frequency set by the server.
1490 */
1491 static void ebpf_obsolete_specific_cachestat_charts(char *type, int update_every)
1492 {
1493 ebpf_write_chart_obsolete(
1494 type,
1495 NETDATA_CACHESTAT_HIT_RATIO_CHART,
1496 "",
1497 "Hit ratio",
1498 EBPF_COMMON_UNITS_PERCENTAGE,
1499 NETDATA_CACHESTAT_SUBMENU,
1500 NETDATA_EBPF_CHART_TYPE_LINE,
1501 NETDATA_CGROUP_CACHESTAT_HIT_RATIO_CONTEXT,
1502 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5200,
1503 update_every);
1504
1505 ebpf_write_chart_obsolete(
1506 type,
1507 NETDATA_CACHESTAT_DIRTY_CHART,
1508 "",
1509 "Number of dirty pages",
1510 EBPF_CACHESTAT_UNITS_PAGE,
1511 NETDATA_CACHESTAT_SUBMENU,
1512 NETDATA_EBPF_CHART_TYPE_LINE,
1513 NETDATA_CGROUP_CACHESTAT_MODIFIED_CACHE_CONTEXT,
1514 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5201,
1515 update_every);
1516
1517 ebpf_write_chart_obsolete(
1518 type,
1519 NETDATA_CACHESTAT_HIT_CHART,
1520 "",
1521 "Number of accessed files",
1522 EBPF_CACHESTAT_UNITS_HITS,
1523 NETDATA_CACHESTAT_SUBMENU,
1524 NETDATA_EBPF_CHART_TYPE_LINE,
1525 NETDATA_CGROUP_CACHESTAT_HIT_FILES_CONTEXT,
1526 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5202,
1527 update_every);
1528
1529 ebpf_write_chart_obsolete(
1530 type,
1531 NETDATA_CACHESTAT_MISSES_CHART,
1532 "",
1533 "Files out of page cache",
1534 EBPF_CACHESTAT_UNITS_MISSES,
1535 NETDATA_CACHESTAT_SUBMENU,
1536 NETDATA_EBPF_CHART_TYPE_LINE,
1537 NETDATA_CGROUP_CACHESTAT_MISS_FILES_CONTEXT,
1538 NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5203,
1539 update_every);
1540 }
1541
1542 /**
1543 * Send data to Netdata calling auxiliary functions.
1544 *
1545 * @param update_every value to overwrite the update frequency set by the server.
1546 */
1547 void ebpf_cachestat_send_cgroup_data(int update_every)
1548 {
1549 netdata_mutex_lock(&mutex_cgroup_shm);
1550 ebpf_cgroup_target_t *ect;
1551 ebpf_cachestat_calc_chart_values();
1552
1553 if (ebpf_cgroup_systemd_enabled_get()) {
1554 if (ebpf_send_cgroup_chart_get()) {
1555 ebpf_create_systemd_cachestat_charts(update_every);
1556 }
1557
1558 ebpf_send_systemd_cachestat_charts();
1559 }
1560
1561 for (ect = ebpf_cgroup_pids; ect; ect = ect->next) {
1562 if (ebpf_plugin_stop())
1563 break;
1564
1565 if (ect->systemd)
1566 continue;
1567
1568 if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART) && ect->updated) {
1569 ebpf_create_specific_cachestat_charts(ect->name, update_every);
1570 ect->flags |= NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART;
1571 }
1572
1573 if (ect->flags & NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART) {
1574 if (ect->updated) {
1575 ebpf_send_specific_cachestat_data(ect->name, &ect->publish_cachestat);
1576 } else {
1577 ebpf_obsolete_specific_cachestat_charts(ect->name, update_every);
1578 ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART;
1579 }
1580 }
1581 }
1582
1583 netdata_mutex_unlock(&mutex_cgroup_shm);
1584 }
1585
1586 /**
1587 * Main loop for this collector.
1588 */
1589 static void cachestat_collector(ebpf_module_t *em)
1590 {
1591 netdata_publish_cachestat_t publish;
1592 memset(&publish, 0, sizeof(publish));
1593 int cgroups = em->cgroup_charts;
1594 int update_every = em->update_every;
1595 int maps_per_core = em->maps_per_core;
1596 heartbeat_t hb;
1597 heartbeat_init(&hb, USEC_PER_SEC);
1598 int counter = update_every - 1;
1599 //This will be cancelled by its parent
1600 uint32_t running_time = 0;
1601 uint32_t lifetime = em->lifetime;
1602 netdata_idx_t *stats = em->hash_table_stats;
1603 memset(stats, 0, sizeof(em->hash_table_stats));
1604 while (!ebpf_plugin_stop() && running_time < lifetime) {
1605 (void)heartbeat_next(&hb);
1606
1607 if (ebpf_plugin_stop())
1608 break;
1609
1610 if (++counter != update_every)
1611 continue;
1612
1613 counter = 0;
1614 netdata_apps_integration_flags_t apps = em->apps_charts;
1615 ebpf_cachestat_read_global_tables(stats, maps_per_core);
1616
1617 if (ebpf_plugin_stop())
1618 break;
1619
1620 netdata_mutex_lock(&lock);
1621
1622 cachestat_send_global(&publish);
1623
1624 if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED)
1625 ebpf_cache_send_apps_data(apps_groups_root_target);
1626
1627 if (ebpf_plugin_stop()) {
1628 netdata_mutex_unlock(&lock);
1629 break;
1630 }
1631
1632 if (cgroups && ebpf_cgroup_integration_active_get())
1633 ebpf_cachestat_send_cgroup_data(update_every);
1634
1635 netdata_mutex_unlock(&lock);
1636
1637 if (ebpf_plugin_stop())
1638 break;
1639
1640 netdata_mutex_lock(&ebpf_exit_cleanup);
1641 if (running_time)
1642 running_time += update_every;
1643 else
1644 running_time = update_every;
1645 em->running_time = running_time;
1646 netdata_mutex_unlock(&ebpf_exit_cleanup);
1647 }
1648 }
1649
1650 /*****************************************************************
1651 *
1652 * INITIALIZE THREAD
1653 *
1654 *****************************************************************/
1655
1656 /**
1657 * Create global charts
1658 *
1659 * Call ebpf_create_chart to create the charts for the collector.
1660 *
1661 * @param em a pointer to `struct ebpf_module`
1662 */
1663 static void ebpf_create_memory_charts(ebpf_module_t *em)
1664 {
1665 ebpf_create_chart(
1666 NETDATA_EBPF_MEMORY_GROUP,
1667 NETDATA_CACHESTAT_HIT_RATIO_CHART,
1668 "Hit ratio",
1669 EBPF_COMMON_UNITS_PERCENTAGE,
1670 NETDATA_CACHESTAT_SUBMENU,
1671 NETDATA_MEM_CACHESTAT_HIT_RATIO_CONTEXT,
1672 NETDATA_EBPF_CHART_TYPE_LINE,
1673 21100,
1674 ebpf_create_global_dimension,
1675 cachestat_counter_publish_aggregated,
1676 1,
1677 em->update_every,
1678 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1679
1680 ebpf_create_chart(
1681 NETDATA_EBPF_MEMORY_GROUP,
1682 NETDATA_CACHESTAT_DIRTY_CHART,
1683 "Number of dirty pages",
1684 EBPF_CACHESTAT_UNITS_PAGE,
1685 NETDATA_CACHESTAT_SUBMENU,
1686 NETDATA_MEM_CACHESTAT_MODIFIED_CACHE_CONTEXT,
1687 NETDATA_EBPF_CHART_TYPE_LINE,
1688 21101,
1689 ebpf_create_global_dimension,
1690 &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_DIRTY],
1691 1,
1692 em->update_every,
1693 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1694
1695 ebpf_create_chart(
1696 NETDATA_EBPF_MEMORY_GROUP,
1697 NETDATA_CACHESTAT_HIT_CHART,
1698 "Number of accessed files",
1699 EBPF_CACHESTAT_UNITS_HITS,
1700 NETDATA_CACHESTAT_SUBMENU,
1701 NETDATA_MEM_CACHESTAT_HIT_FILES_CONTEXT,
1702 NETDATA_EBPF_CHART_TYPE_LINE,
1703 21102,
1704 ebpf_create_global_dimension,
1705 &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_HIT],
1706 1,
1707 em->update_every,
1708 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1709
1710 ebpf_create_chart(
1711 NETDATA_EBPF_MEMORY_GROUP,
1712 NETDATA_CACHESTAT_MISSES_CHART,
1713 "Files out of page cache",
1714 EBPF_CACHESTAT_UNITS_MISSES,
1715 NETDATA_CACHESTAT_SUBMENU,
1716 NETDATA_MEM_CACHESTAT_MISS_FILES_CONTEXT,
1717 NETDATA_EBPF_CHART_TYPE_LINE,
1718 21103,
1719 ebpf_create_global_dimension,
1720 &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_MISS],
1721 1,
1722 em->update_every,
1723 NETDATA_EBPF_MODULE_NAME_CACHESTAT);
1724
1725 fflush(stdout);
1726 }
1727
1728 /**
1729 * Allocate vectors used with this thread.
1730 *
1731 * We are not testing the return, because callocz does this and shutdown the software
1732 * case it was not possible to allocate.
1733 */
1734 static void ebpf_cachestat_allocate_global_vectors()
1735 {
1736 cachestat_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_cachestat_pid_t));
1737 cachestat_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t));
1738
1739 memset(cachestat_hash_values, 0, NETDATA_CACHESTAT_END * sizeof(netdata_idx_t));
1740 memset(cachestat_counter_aggregated_data, 0, NETDATA_CACHESTAT_END * sizeof(netdata_syscall_stat_t));
1741 memset(cachestat_counter_publish_aggregated, 0, NETDATA_CACHESTAT_END * sizeof(netdata_publish_syscall_t));
1742 }
1743
1744 /*****************************************************************
1745 *
1746 * MAIN THREAD
1747 *
1748 *****************************************************************/
1749
1750 /**
1751 * Update Internal value
1752 *
1753 * Update values used during runtime.
1754 *
1755 * @return It returns 0 when one of the functions is present and -1 otherwise.
1756 */
1757 static int ebpf_cachestat_set_internal_value()
1758 {
1759 ebpf_addresses_t address = {.function = NULL, .hash = 0, .addr = 0};
1760 int i;
1761 for (i = 0; i < NETDATA_CACHESTAT_ACCOUNT_DIRTY_END; i++) {
1762 address.function = account_page[i];
1763 ebpf_load_addresses(&address, -1);
1764 if (address.addr)
1765 break;
1766 }
1767
1768 if (!address.addr) {
1769 netdata_log_error("%s cachestat.", NETDATA_EBPF_DEFAULT_FNT_NOT_FOUND);
1770 return -1;
1771 }
1772
1773 cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name = address.function;
1774
1775 return 0;
1776 }
1777
1778 /*
1779 * Load BPF
1780 *
1781 * Load BPF files.
1782 *
1783 * @param em the structure with configuration
1784 */
1785 static int ebpf_cachestat_load_bpf(ebpf_module_t *em)
1786 {
1787 #ifdef LIBBPF_MAJOR_VERSION
1788 ebpf_define_map_type(cachestat_maps, em->maps_per_core, running_on_kernel);
1789 #endif
1790
1791 int ret = 0;
1792 ebpf_adjust_apps_cgroup(em, em->targets[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU].mode);
1793 if (em->load & EBPF_LOAD_LEGACY) {
1794 em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects);
1795 if (!em->probe_links) {
1796 ret = -1;
1797 }
1798 }
1799 #ifdef LIBBPF_MAJOR_VERSION
1800 else {
1801 cachestat_bpf_obj = cachestat_bpf__open();
1802 if (!cachestat_bpf_obj)
1803 ret = -1;
1804 else {
1805 ret = ebpf_cachestat_load_and_attach(cachestat_bpf_obj, em);
1806 if (ret) {
1807 cachestat_bpf__destroy(cachestat_bpf_obj);
1808 cachestat_bpf_obj = NULL;
1809 }
1810 }
1811 }
1812 #endif
1813
1814 if (ret)
1815 netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name);
1816
1817 return ret;
1818 }
1819
1820 /**
1821 * Cachestat thread
1822 *
1823 * Thread used to make cachestat thread
1824 *
1825 * @param ptr a pointer to `struct ebpf_module`
1826 *
1827 * @return It always return NULL
1828 */
1829 void ebpf_cachestat_thread(void *ptr)
1830 {
1831 ebpf_module_t *em = (ebpf_module_t *)ptr;
1832
1833 CLEANUP_FUNCTION_REGISTER(ebpf_cachestat_exit) cleanup_ptr = em;
1834
1835 if (!ebpf_module_thread_has_valid_state(em)) {
1836 goto endcachestat;
1837 }
1838
1839 em->maps = cachestat_maps;
1840
1841 ebpf_update_pid_table(&cachestat_maps[NETDATA_CACHESTAT_PID_STATS], em);
1842
1843 if (ebpf_cachestat_set_internal_value()) {
1844 goto endcachestat;
1845 }
1846
1847 #ifdef LIBBPF_MAJOR_VERSION
1848 ebpf_adjust_thread_load(em, default_btf);
1849 #endif
1850 if (ebpf_cachestat_load_bpf(em)) {
1851 goto endcachestat;
1852 }
1853 ebpf_mark_program_loaded();
1854
1855 ebpf_cachestat_allocate_global_vectors();
1856
1857 int algorithms[NETDATA_CACHESTAT_END] = {
1858 NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX};
1859
1860 ebpf_global_labels(
1861 cachestat_counter_aggregated_data,
1862 cachestat_counter_publish_aggregated,
1863 cachestat_counter_dimension_name,
1864 cachestat_counter_dimension_name,
1865 algorithms,
1866 NETDATA_CACHESTAT_END);
1867
1868 netdata_mutex_lock(&lock);
1869 ebpf_update_stats(&plugin_statistics, em);
1870 ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD);
1871 ebpf_create_memory_charts(em);
1872
1873 netdata_mutex_unlock(&lock);
1874
1875 ebpf_read_cachestat.thread =
1876 nd_thread_create(ebpf_read_cachestat.name, NETDATA_THREAD_OPTION_DEFAULT, ebpf_read_cachestat_thread, em);
1877
1878 cachestat_safe_clean = true;
1879 cachestat_collector(em);
1880
1881 endcachestat:
1882 ebpf_update_disabled_plugin_stats(em);
1883 }