master
c 429 lines 10.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "ebpf.h"
4 #include "ebpf_mdflush.h"
5 #include "libbpf_api/ebpf_library.h"
6
7 struct config mdflush_config = APPCONFIG_INITIALIZER;
8
9 #define MDFLUSH_MAP_COUNT 0
10 static ebpf_local_maps_t mdflush_maps[] = {
11 {.name = "tbl_mdflush",
12 .internal_input = 1024,
13 .user_input = 0,
14 .type = NETDATA_EBPF_MAP_STATIC,
15 .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED,
16 #ifdef LIBBPF_MAJOR_VERSION
17 .map_type = BPF_MAP_TYPE_PERCPU_HASH
18 #endif
19 },
20 /* end */
21 {.name = NULL,
22 .internal_input = 0,
23 .user_input = 0,
24 .type = NETDATA_EBPF_MAP_CONTROLLER,
25 .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED}};
26
27 netdata_ebpf_targets_t mdflush_targets[] = {
28 {.name = "md_flush_request", .mode = EBPF_LOAD_TRAMPOLINE},
29 {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}};
30
31 static bool mdflush_safe_clean = false;
32
33 // store for "published" data from the reader thread, which the collector
34 // thread will write to netdata agent.
35 static avl_tree_lock mdflush_pub;
36
37 // tmp store for mdflush values we get from a per-CPU eBPF map.
38 static mdflush_ebpf_val_t *mdflush_ebpf_vals = NULL;
39
40 #ifdef LIBBPF_MAJOR_VERSION
41 /**
42 * Disable probes
43 *
44 * Disable probes to use trampolines.
45 *
46 * @param obj the loaded object structure.
47 */
48 static inline void ebpf_disable_probes(struct mdflush_bpf *obj)
49 {
50 bpf_program__set_autoload(obj->progs.netdata_md_flush_request_kprobe, false);
51 }
52
53 /**
54 * Disable trampolines
55 *
56 * Disable trampoliness to use probes.
57 *
58 * @param obj the loaded object structure.
59 */
60 static inline void ebpf_disable_trampoline(struct mdflush_bpf *obj)
61 {
62 bpf_program__set_autoload(obj->progs.netdata_md_flush_request_fentry, false);
63 }
64
65 /**
66 * Set Trampoline
67 *
68 * Define target to attach trampoline
69 *
70 * @param obj the loaded object structure.
71 */
72 static void ebpf_set_trampoline_target(struct mdflush_bpf *obj)
73 {
74 bpf_program__set_attach_target(
75 obj->progs.netdata_md_flush_request_fentry, 0, mdflush_targets[NETDATA_MD_FLUSH_REQUEST].name);
76 }
77
78 /**
79 * Load probe
80 *
81 * Load probe to monitor internal function.
82 *
83 * @param obj the loaded object structure.
84 */
85 static inline int ebpf_load_probes(struct mdflush_bpf *obj)
86 {
87 obj->links.netdata_md_flush_request_kprobe = bpf_program__attach_kprobe(
88 obj->progs.netdata_md_flush_request_kprobe, false, mdflush_targets[NETDATA_MD_FLUSH_REQUEST].name);
89 return libbpf_get_error(obj->links.netdata_md_flush_request_kprobe);
90 }
91
92 /**
93 * Load and Attach
94 *
95 * Load and attach bpf codes according user selection.
96 *
97 * @param obj the loaded object structure.
98 * @param em the structure with configuration
99 */
100 static inline int ebpf_mdflush_load_and_attach(struct mdflush_bpf *obj, ebpf_module_t *em)
101 {
102 int mode = em->targets[NETDATA_MD_FLUSH_REQUEST].mode;
103 if (mode == EBPF_LOAD_TRAMPOLINE) { // trampoline
104 ebpf_disable_probes(obj);
105
106 ebpf_set_trampoline_target(obj);
107 } else // kprobe
108 ebpf_disable_trampoline(obj);
109
110 int ret = mdflush_bpf__load(obj);
111 if (ret) {
112 fprintf(stderr, "failed to load BPF object: %d\n", ret);
113 return -1;
114 }
115
116 if (mode == EBPF_LOAD_TRAMPOLINE)
117 ret = mdflush_bpf__attach(obj);
118 else
119 ret = ebpf_load_probes(obj);
120
121 return ret;
122 }
123
124 #endif
125
126 /**
127 * Obsolete global
128 *
129 * Obsolete global charts created by thread.
130 *
131 * @param em a pointer to `struct ebpf_module`
132 */
133 static void ebpf_obsolete_mdflush_global(ebpf_module_t *em)
134 {
135 ebpf_write_chart_obsolete(
136 "mdstat",
137 "mdstat_flush",
138 "",
139 "MD flushes",
140 "flushes",
141 "flush (eBPF)",
142 NETDATA_EBPF_CHART_TYPE_STACKED,
143 "mdstat.mdstat_flush",
144 NETDATA_CHART_PRIO_MDSTAT_FLUSH,
145 em->update_every);
146 }
147
148 /**
149 * MDflush exit
150 *
151 * Cancel thread and exit.
152 *
153 * @param ptr thread data.
154 */
155
156 static void mdflush_exit(void *pptr)
157 {
158 ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr);
159 if (!em)
160 return;
161
162 if (!mdflush_safe_clean) {
163 netdata_mutex_lock(&ebpf_exit_cleanup);
164 ebpf_module_enabled_set(em, NETDATA_THREAD_EBPF_STOPPED);
165 netdata_mutex_unlock(&ebpf_exit_cleanup);
166 return;
167 }
168
169 if (ebpf_module_enabled_get(em) == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) {
170 netdata_mutex_lock(&lock);
171
172 ebpf_obsolete_mdflush_global(em);
173
174 netdata_mutex_unlock(&lock);
175 fflush(stdout);
176 }
177
178 if (!ebpf_plugin_stop() && em->functions.bpf_unload)
179 em->functions.bpf_unload(em);
180
181 netdata_mutex_lock(&ebpf_exit_cleanup);
182 ebpf_module_enabled_set(em, NETDATA_THREAD_EBPF_STOPPED);
183 netdata_mutex_unlock(&ebpf_exit_cleanup);
184 }
185
186 /**
187 * Compare mdflush values.
188 *
189 * @param a `netdata_mdflush_t *`.
190 * @param b `netdata_mdflush_t *`.
191 *
192 * @return 0 if a==b, 1 if a>b, -1 if a<b.
193 */
194 static int mdflush_val_cmp(void *a, void *b)
195 {
196 netdata_mdflush_t *ptr1 = a;
197 netdata_mdflush_t *ptr2 = b;
198
199 if (ptr1->unit > ptr2->unit) {
200 return 1;
201 } else if (ptr1->unit < ptr2->unit) {
202 return -1;
203 }
204
205 return 0;
206 }
207
208 /**
209 * Read count map
210 *
211 * Read the hash table and store data to allocated vectors.
212 *
213 * @param maps_per_core do I need to read all cores?
214 */
215 static void mdflush_read_count_map(int maps_per_core)
216 {
217 int mapfd = mdflush_maps[MDFLUSH_MAP_COUNT].map_fd;
218 mdflush_ebpf_key_t curr_key = (uint32_t)-1;
219 mdflush_ebpf_key_t key;
220
221 int end = maps_per_core ? ebpf_nprocs : 1;
222
223 while (bpf_map_get_next_key(mapfd, &curr_key, &key) == 0) {
224 if (ebpf_plugin_stop())
225 break;
226
227 curr_key = key;
228
229 int ret = bpf_map_lookup_elem(mapfd, &key, mdflush_ebpf_vals);
230 if (unlikely(ret < 0)) {
231 continue;
232 }
233
234 netdata_mdflush_t search_v = {.unit = key};
235 netdata_mdflush_t *v = (netdata_mdflush_t *)avl_search_lock(&mdflush_pub, (avl_t *)&search_v);
236 if (unlikely(v == NULL)) {
237 v = callocz(1, sizeof(netdata_mdflush_t));
238 v->unit = key;
239 snprintf(v->disk_name, sizeof(v->disk_name), "md%u", key);
240 v->dim_exists = false;
241
242 avl_t *check = avl_insert_lock(&mdflush_pub, (avl_t *)v);
243 if (check != (avl_t *)v) {
244 netdata_log_error("Internal error, cannot insert the AVL tree.");
245 }
246 }
247
248 uint64_t total_cnt = 0;
249 int i;
250 for (i = 0; i < end; i++) {
251 total_cnt += mdflush_ebpf_vals[i];
252 }
253 v->cnt = total_cnt;
254 }
255 }
256
257 static void mdflush_create_charts(int update_every)
258 {
259 ebpf_create_chart(
260 "mdstat",
261 "mdstat_flush",
262 "MD flushes",
263 "flushes",
264 "flush (eBPF)",
265 "mdstat.mdstat_flush",
266 NETDATA_EBPF_CHART_TYPE_STACKED,
267 NETDATA_CHART_PRIO_MDSTAT_FLUSH,
268 NULL,
269 NULL,
270 0,
271 update_every,
272 NETDATA_EBPF_MODULE_NAME_MDFLUSH);
273
274 fflush(stdout);
275 }
276
277 // callback for avl tree traversal on `mdflush_pub`.
278 static int mdflush_write_dims(void *entry, void *data __maybe_unused)
279 {
280 netdata_mdflush_t *v = entry;
281
282 if (!v->dim_exists) {
283 ebpf_write_global_dimension(v->disk_name, v->disk_name, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]);
284 v->dim_exists = true;
285 }
286
287 write_chart_dimension(v->disk_name, v->cnt);
288
289 return 1;
290 }
291
292 /**
293 * Main loop for this collector.
294 */
295 static void mdflush_collector(ebpf_module_t *em)
296 {
297 mdflush_ebpf_vals = callocz(ebpf_nprocs, sizeof(mdflush_ebpf_val_t));
298
299 int update_every = em->update_every;
300 avl_init_lock(&mdflush_pub, mdflush_val_cmp);
301
302 // create chart and static dims.
303 netdata_mutex_lock(&lock);
304 mdflush_create_charts(update_every);
305 ebpf_update_stats(&plugin_statistics, em);
306 ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD);
307 netdata_mutex_unlock(&lock);
308
309 int counter = update_every - 1;
310 uint32_t running_time = 0;
311 uint32_t lifetime = em->lifetime;
312 heartbeat_t hb;
313 heartbeat_init(&hb, USEC_PER_SEC);
314 while (!ebpf_plugin_stop() && running_time < lifetime) {
315 if (ebpf_plugin_stop())
316 break;
317
318 heartbeat_next(&hb);
319
320 if (ebpf_plugin_stop())
321 break;
322
323 if (++counter != update_every)
324 continue;
325
326 counter = 0;
327 mdflush_read_count_map(em->maps_per_core);
328 netdata_mutex_lock(&lock);
329 // write dims now for all hitherto discovered devices.
330 ebpf_write_begin_chart("mdstat", "mdstat_flush", "");
331 avl_traverse_lock(&mdflush_pub, mdflush_write_dims, NULL);
332 ebpf_write_end_chart();
333
334 netdata_mutex_unlock(&lock);
335
336 if (ebpf_plugin_stop())
337 break;
338
339 netdata_mutex_lock(&ebpf_exit_cleanup);
340 running_time += update_every;
341 em->running_time = running_time;
342 netdata_mutex_unlock(&ebpf_exit_cleanup);
343 }
344 }
345
346 /*
347 * Load BPF
348 *
349 * Load BPF files.
350 *
351 * @param em the structure with configuration
352 *
353 * @return It returns 0 on success and -1 otherwise.
354 */
355 static int ebpf_mdflush_load_bpf(ebpf_module_t *em)
356 {
357 int ret = 0;
358 if (em->load & EBPF_LOAD_LEGACY) {
359 em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects);
360 if (!em->probe_links) {
361 ret = -1;
362 }
363 }
364 #ifdef LIBBPF_MAJOR_VERSION
365 else {
366 mdflush_bpf_obj = mdflush_bpf__open();
367 if (!mdflush_bpf_obj)
368 ret = -1;
369 else {
370 ret = ebpf_mdflush_load_and_attach(mdflush_bpf_obj, em);
371 if (ret && em->targets[NETDATA_MD_FLUSH_REQUEST].mode == EBPF_LOAD_TRAMPOLINE) {
372 mdflush_bpf__destroy(mdflush_bpf_obj);
373 mdflush_bpf_obj = mdflush_bpf__open();
374 if (!mdflush_bpf_obj)
375 ret = -1;
376 else {
377 em->targets[NETDATA_MD_FLUSH_REQUEST].mode = EBPF_LOAD_PROBE;
378 ret = ebpf_mdflush_load_and_attach(mdflush_bpf_obj, em);
379 }
380 }
381 }
382 }
383 #endif
384
385 return ret;
386 }
387
388 /**
389 * mdflush thread.
390 *
391 * @param ptr a `ebpf_module_t *`.
392 * @return always NULL.
393 */
394 void ebpf_mdflush_thread(void *ptr)
395 {
396 ebpf_module_t *em = (ebpf_module_t *)ptr;
397 CLEANUP_FUNCTION_REGISTER(mdflush_exit) cleanup_ptr = em;
398
399 char *md_flush_request = NULL;
400
401 if (!ebpf_module_thread_has_valid_state(em)) {
402 goto endmdflush;
403 }
404
405 em->maps = mdflush_maps;
406
407 md_flush_request = ebpf_find_symbol("md_flush_request");
408 if (!md_flush_request) {
409 netdata_log_error("Cannot monitor MD devices, because md is not loaded.");
410 goto endmdflush;
411 }
412
413 #ifdef LIBBPF_MAJOR_VERSION
414 ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel);
415 ebpf_adjust_thread_load(em, default_btf);
416 #endif
417 if (ebpf_mdflush_load_bpf(em)) {
418 netdata_log_error("Cannot load eBPF software.");
419 goto endmdflush;
420 }
421 ebpf_mark_program_loaded();
422
423 mdflush_safe_clean = true;
424 mdflush_collector(em);
425
426 endmdflush:
427 freez(md_flush_request);
428 ebpf_update_disabled_plugin_stats(em);
429 }