| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include <sys/types.h> |
| 4 | #include <sys/stat.h> |
| 5 | #include <fcntl.h> |
| 6 | #include <dlfcn.h> |
| 7 | #include <sys/utsname.h> |
| 8 | |
| 9 | #include "../ebpf.h" |
| 10 | #include "libnetdata/libnetdata.h" |
| 11 | |
| 12 | char *ebpf_user_config_dir = CONFIG_DIR; |
| 13 | char *ebpf_stock_config_dir = LIBCONFIG_DIR; |
| 14 | |
| 15 | /* |
| 16 | static int clean_kprobe_event(FILE *out, char *filename, char *father_pid, netdata_ebpf_events_t *ptr) |
| 17 | { |
| 18 | int fd = open(filename, O_WRONLY | O_APPEND, 0); |
| 19 | if (fd < 0) { |
| 20 | if (out) { |
| 21 | fprintf(out, "Cannot open %s : %s\n", filename, strerror(errno)); |
| 22 | } |
| 23 | return 1; |
| 24 | } |
| 25 | |
| 26 | char cmd[1024]; |
| 27 | int length = snprintf(cmd, 1023, "-:kprobes/%c_netdata_%s_%s", ptr->type, ptr->name, father_pid); |
| 28 | int ret = 0; |
| 29 | if (length > 0) { |
| 30 | ssize_t written = write(fd, cmd, strlen(cmd)); |
| 31 | if (written < 0) { |
| 32 | if (out) { |
| 33 | fprintf( |
| 34 | out, "Cannot remove the event (%d, %d) '%s' from %s : %s\n", getppid(), getpid(), cmd, filename, |
| 35 | strerror((int)errno)); |
| 36 | } |
| 37 | ret = 1; |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | close(fd); |
| 42 | |
| 43 | return ret; |
| 44 | } |
| 45 | |
| 46 | int clean_kprobe_events(FILE *out, int pid, netdata_ebpf_events_t *ptr) |
| 47 | { |
| 48 | char filename[FILENAME_MAX + 1]; |
| 49 | snprintf(filename, FILENAME_MAX, "%s%s", NETDATA_DEBUGFS, "kprobe_events"); |
| 50 | |
| 51 | char removeme[16]; |
| 52 | snprintf(removeme, 15, "%d", pid); |
| 53 | |
| 54 | int i; |
| 55 | for (i = 0; ptr[i].name; i++) { |
| 56 | if (clean_kprobe_event(out, filename, removeme, &ptr[i])) { |
| 57 | break; |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | return 0; |
| 62 | } |
| 63 | */ |
| 64 | |
| 65 | //---------------------------------------------------------------------------------------------------------------------- |
| 66 | |
| 67 | /** |
| 68 | * Get Kernel version |
| 69 | * |
| 70 | * Get the current kernel from /proc and returns an integer value representing it |
| 71 | * |
| 72 | * @return it returns a value representing the kernel version. |
| 73 | */ |
| 74 | int ebpf_get_kernel_version() |
| 75 | { |
| 76 | char major[16], minor[16], patch[16]; |
| 77 | char ver[VERSION_STRING_LEN]; |
| 78 | char *version = ver; |
| 79 | |
| 80 | int fd = open("/proc/sys/kernel/osrelease", O_RDONLY | O_CLOEXEC); |
| 81 | if (fd < 0) |
| 82 | return -1; |
| 83 | |
| 84 | ssize_t len = read(fd, ver, sizeof(ver) - 1); |
| 85 | if (len < 0) { |
| 86 | close(fd); |
| 87 | return -1; |
| 88 | } |
| 89 | |
| 90 | ver[len] = '\0'; |
| 91 | close(fd); |
| 92 | |
| 93 | char *move = major; |
| 94 | while (*version && *version != '.') |
| 95 | *move++ = *version++; |
| 96 | *move = '\0'; |
| 97 | |
| 98 | version++; |
| 99 | move = minor; |
| 100 | while (*version && *version != '.') |
| 101 | *move++ = *version++; |
| 102 | *move = '\0'; |
| 103 | |
| 104 | if (*version) |
| 105 | version++; |
| 106 | else |
| 107 | return -1; |
| 108 | |
| 109 | move = patch; |
| 110 | while (*version && *version != '\n' && *version != '-') |
| 111 | *move++ = *version++; |
| 112 | *move = '\0'; |
| 113 | |
| 114 | // This new rule is fixing kernel version according the formula: |
| 115 | // KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c))) |
| 116 | // that was extracted from /usr/include/linux/version.h |
| 117 | long major_val = str2l(major); |
| 118 | long minor_val = str2l(minor); |
| 119 | if (major_val < 0 || minor_val < 0) |
| 120 | return -1; |
| 121 | |
| 122 | int ipatch = (int)str2l(patch); |
| 123 | if (ipatch < 0) |
| 124 | return -1; |
| 125 | |
| 126 | if (ipatch > 255) |
| 127 | ipatch = 255; |
| 128 | |
| 129 | return ((int)(major_val * 65536) + (int)(minor_val * 256) + ipatch); |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Get RH release |
| 134 | * |
| 135 | * Read Red Hat release from /etc/redhat-release |
| 136 | * |
| 137 | * @return It returns RH release on success and -1 otherwise |
| 138 | */ |
| 139 | int get_redhat_release() |
| 140 | { |
| 141 | char buffer[VERSION_STRING_LEN + 1]; |
| 142 | int major, minor; |
| 143 | FILE *fp = fopen("/etc/redhat-release", "r"); |
| 144 | |
| 145 | if (fp) { |
| 146 | major = 0; |
| 147 | minor = -1; |
| 148 | size_t length = fread(buffer, sizeof(char), VERSION_STRING_LEN, fp); |
| 149 | if (length > 4) { |
| 150 | buffer[length] = '\0'; |
| 151 | char *end = strchr(buffer, '.'); |
| 152 | char *start; |
| 153 | if (end) { |
| 154 | *end = '\0'; |
| 155 | |
| 156 | if (end > buffer) { |
| 157 | start = end - 1; |
| 158 | |
| 159 | major = strtol(start, NULL, 10); |
| 160 | start = ++end; |
| 161 | |
| 162 | char *minor_end = strchr(start, ' '); |
| 163 | if (minor_end) { |
| 164 | *minor_end = '\0'; |
| 165 | minor = strtol(start, NULL, 10); |
| 166 | } else { |
| 167 | minor = -1; |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | fclose(fp); |
| 174 | return ((major * 256) + minor); |
| 175 | } else { |
| 176 | return -1; |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Check if the kernel is in a list of rejected ones |
| 182 | * |
| 183 | * @return Returns 1 if the kernel is rejected, 0 otherwise. |
| 184 | */ |
| 185 | static int kernel_is_rejected() |
| 186 | { |
| 187 | // Get kernel version from system |
| 188 | char version_string[VERSION_STRING_LEN + 1]; |
| 189 | int version_string_len = 0; |
| 190 | |
| 191 | if (read_txt_file("/proc/version_signature", version_string, sizeof(version_string))) { |
| 192 | if (read_txt_file("/proc/version", version_string, sizeof(version_string))) { |
| 193 | struct utsname uname_buf; |
| 194 | if (uname(&uname_buf)) { |
| 195 | collector_info("Cannot check kernel version"); |
| 196 | return 0; |
| 197 | } |
| 198 | version_string_len = |
| 199 | snprintfz(version_string, VERSION_STRING_LEN, "%s %s", uname_buf.release, uname_buf.version); |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | if (!version_string_len) |
| 204 | version_string_len = strlen(version_string); |
| 205 | |
| 206 | // Open a file with a list of rejected kernels |
| 207 | char *config_dir = getenv("NETDATA_USER_CONFIG_DIR"); |
| 208 | if (config_dir == NULL) { |
| 209 | config_dir = CONFIG_DIR; |
| 210 | } |
| 211 | |
| 212 | char filename[FILENAME_MAX + 1]; |
| 213 | snprintfz(filename, FILENAME_MAX, "%s/ebpf.d/%s", config_dir, EBPF_KERNEL_REJECT_LIST_FILE); |
| 214 | FILE *kernel_reject_list = fopen(filename, "r"); |
| 215 | |
| 216 | if (!kernel_reject_list) { |
| 217 | // Keep this to have compatibility with old versions |
| 218 | snprintfz(filename, FILENAME_MAX, "%s/%s", config_dir, EBPF_KERNEL_REJECT_LIST_FILE); |
| 219 | kernel_reject_list = fopen(filename, "r"); |
| 220 | |
| 221 | if (!kernel_reject_list) { |
| 222 | config_dir = getenv("NETDATA_STOCK_CONFIG_DIR"); |
| 223 | if (config_dir == NULL) { |
| 224 | config_dir = LIBCONFIG_DIR; |
| 225 | } |
| 226 | |
| 227 | snprintfz(filename, FILENAME_MAX, "%s/ebpf.d/%s", config_dir, EBPF_KERNEL_REJECT_LIST_FILE); |
| 228 | kernel_reject_list = fopen(filename, "r"); |
| 229 | |
| 230 | if (!kernel_reject_list) |
| 231 | return 0; |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | // Find if the kernel is in the reject list |
| 236 | char *reject_string = NULL; |
| 237 | size_t buf_len = 0; |
| 238 | ssize_t reject_string_len; |
| 239 | while ((reject_string_len = getline(&reject_string, &buf_len, kernel_reject_list)) > 0) { |
| 240 | if (reject_string_len > 1) { |
| 241 | reject_string_len--; |
| 242 | if (version_string_len >= reject_string_len) { |
| 243 | if (!strncmp(version_string, reject_string, reject_string_len)) { |
| 244 | collector_info("A buggy kernel is detected"); |
| 245 | fclose(kernel_reject_list); |
| 246 | freez(reject_string); |
| 247 | return 1; |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | fclose(kernel_reject_list); |
| 254 | freez(reject_string); |
| 255 | |
| 256 | return 0; |
| 257 | } |
| 258 | |
| 259 | /** |
| 260 | * Check Kernel Version |
| 261 | * |
| 262 | * Test kernel version |
| 263 | * |
| 264 | * @param version current kernel version |
| 265 | * |
| 266 | * @return It returns 1 when kernel is supported and 0 otherwise |
| 267 | */ |
| 268 | int ebpf_check_kernel_version(int version) |
| 269 | { |
| 270 | if (kernel_is_rejected()) |
| 271 | return 0; |
| 272 | |
| 273 | // Kernel 4.11.0 or RH > 7.5 |
| 274 | return (version >= NETDATA_MINIMUM_EBPF_KERNEL || get_redhat_release() >= NETDATA_MINIMUM_RH_VERSION); |
| 275 | } |
| 276 | |
| 277 | /** |
| 278 | * Am I running as Root |
| 279 | * |
| 280 | * Verify the user that is running the collector. |
| 281 | * |
| 282 | * @return It returns 1 for root and 0 otherwise. |
| 283 | */ |
| 284 | int is_ebpf_plugin_running_as_root() |
| 285 | { |
| 286 | uid_t uid = getuid(), euid = geteuid(); |
| 287 | |
| 288 | if (uid == 0 || euid == 0) { |
| 289 | return 1; |
| 290 | } |
| 291 | |
| 292 | return 0; |
| 293 | } |
| 294 | |
| 295 | /** |
| 296 | * Can the plugin run eBPF code |
| 297 | * |
| 298 | * This function checks kernel version and permissions. |
| 299 | * |
| 300 | * @param kver the kernel version |
| 301 | * @param name the plugin name. |
| 302 | * |
| 303 | * @return It returns 0 on success and -1 otherwise |
| 304 | */ |
| 305 | int ebpf_can_plugin_load_code(int kver, char *plugin_name) |
| 306 | { |
| 307 | if (!ebpf_check_kernel_version(kver)) { |
| 308 | netdata_log_error("The current collector cannot run on this kernel."); |
| 309 | return -1; |
| 310 | } |
| 311 | |
| 312 | if (!is_ebpf_plugin_running_as_root()) { |
| 313 | netdata_log_error( |
| 314 | "%s should either run as root (now running with uid %u, euid %u) or have special capabilities.", |
| 315 | plugin_name, |
| 316 | (unsigned int)getuid(), |
| 317 | (unsigned int)geteuid()); |
| 318 | return -1; |
| 319 | } |
| 320 | |
| 321 | return 0; |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * Adjust memory |
| 326 | * |
| 327 | * Adjust memory values to load eBPF programs. |
| 328 | * |
| 329 | * @return It returns 0 on success and -1 otherwise |
| 330 | */ |
| 331 | int ebpf_adjust_memory_limit() |
| 332 | { |
| 333 | struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; |
| 334 | if (setrlimit(RLIMIT_MEMLOCK, &r)) { |
| 335 | netdata_log_error("Setrlimit(RLIMIT_MEMLOCK)"); |
| 336 | return -1; |
| 337 | } |
| 338 | |
| 339 | return 0; |
| 340 | } |
| 341 | |
| 342 | //---------------------------------------------------------------------------------------------------------------------- |
| 343 | |
| 344 | /** |
| 345 | * Kernel Name |
| 346 | * |
| 347 | * Select kernel name used by eBPF programs |
| 348 | * |
| 349 | * Netdata delivers for users eBPF programs with specific suffixes that represent the kernels they were |
| 350 | * compiled, when we load the eBPF program, the suffix must be the nereast possible of the kernel running. |
| 351 | * |
| 352 | * @param selector select the kernel version. |
| 353 | * |
| 354 | * @return It returns the string to load kernel. |
| 355 | */ |
| 356 | static char *ebpf_select_kernel_name(uint32_t selector) |
| 357 | { |
| 358 | static char *kernel_names[] = { |
| 359 | NETDATA_IDX_STR_V3_10, |
| 360 | NETDATA_IDX_STR_V4_14, |
| 361 | NETDATA_IDX_STR_V4_16, |
| 362 | NETDATA_IDX_STR_V4_18, |
| 363 | NETDATA_IDX_STR_V5_4, |
| 364 | NETDATA_IDX_STR_V5_10, |
| 365 | NETDATA_IDX_STR_V5_11, |
| 366 | NETDATA_IDX_STR_V5_14, |
| 367 | NETDATA_IDX_STR_V5_15, |
| 368 | NETDATA_IDX_STR_V5_16, |
| 369 | NETDATA_IDX_STR_V6_8}; |
| 370 | |
| 371 | return kernel_names[selector]; |
| 372 | } |
| 373 | |
| 374 | /** |
| 375 | * Select Max Index |
| 376 | * |
| 377 | * Select last index that will be tested on host. |
| 378 | * |
| 379 | * @param is_rhf is Red Hat fammily? |
| 380 | * @param kver the kernel version |
| 381 | * |
| 382 | * @return it returns the index to access kernel string. |
| 383 | */ |
| 384 | static int ebpf_select_max_index(int is_rhf, uint32_t kver) |
| 385 | { |
| 386 | if (is_rhf > 0) { // Is Red Hat family |
| 387 | if (kver >= NETDATA_EBPF_KERNEL_5_14) |
| 388 | return NETDATA_IDX_V5_14; |
| 389 | else if (kver >= NETDATA_EBPF_KERNEL_5_4 && kver < NETDATA_EBPF_KERNEL_5_5) // For Oracle Linux |
| 390 | return NETDATA_IDX_V5_4; |
| 391 | else if (kver >= NETDATA_EBPF_KERNEL_4_11) |
| 392 | return NETDATA_IDX_V4_18; |
| 393 | } else { // Kernels from kernel.org |
| 394 | if (kver >= NETDATA_EBPF_KERNEL_6_8) |
| 395 | return NETDATA_IDX_V6_8; |
| 396 | else if (kver >= NETDATA_EBPF_KERNEL_5_16) |
| 397 | return NETDATA_IDX_V5_16; |
| 398 | else if (kver >= NETDATA_EBPF_KERNEL_5_15) |
| 399 | return NETDATA_IDX_V5_15; |
| 400 | else if (kver >= NETDATA_EBPF_KERNEL_5_11) |
| 401 | return NETDATA_IDX_V5_11; |
| 402 | else if (kver >= NETDATA_EBPF_KERNEL_5_10) |
| 403 | return NETDATA_IDX_V5_10; |
| 404 | else if (kver >= NETDATA_EBPF_KERNEL_4_17) |
| 405 | return NETDATA_IDX_V5_4; |
| 406 | else if (kver >= NETDATA_EBPF_KERNEL_4_15) |
| 407 | return NETDATA_IDX_V4_16; |
| 408 | else if (kver >= NETDATA_EBPF_KERNEL_4_11) |
| 409 | return NETDATA_IDX_V4_14; |
| 410 | } |
| 411 | |
| 412 | return NETDATA_IDX_V3_10; |
| 413 | } |
| 414 | |
| 415 | /** |
| 416 | * Select Index |
| 417 | * |
| 418 | * Select index to load data. |
| 419 | * |
| 420 | * @param kernels is the variable with kernel versions. |
| 421 | * @param is_rhf is Red Hat fammily? |
| 422 | * param kver the kernel version |
| 423 | */ |
| 424 | static uint32_t ebpf_select_index(uint32_t kernels, int is_rhf, uint32_t kver) |
| 425 | { |
| 426 | uint32_t start = ebpf_select_max_index(is_rhf, kver); |
| 427 | uint32_t idx; |
| 428 | |
| 429 | if (is_rhf == -1) |
| 430 | kernels &= ~NETDATA_V5_14; |
| 431 | |
| 432 | for (idx = start; idx; idx--) { |
| 433 | if (kernels & 1 << idx) |
| 434 | break; |
| 435 | } |
| 436 | |
| 437 | return idx; |
| 438 | } |
| 439 | |
| 440 | /** |
| 441 | * Mount Name |
| 442 | * |
| 443 | * Mount name of eBPF program to be loaded. |
| 444 | * |
| 445 | * Netdata eBPF programs has the following format: |
| 446 | * |
| 447 | * Tnetdata_ebpf_N.V.o |
| 448 | * |
| 449 | * where: |
| 450 | * T - Is the eBPF type. When starts with 'p', this means we are only adding probes, |
| 451 | * and when they start with 'r' we are using retprobes. |
| 452 | * N - The eBPF program name. |
| 453 | * V - The kernel version in string format. |
| 454 | * |
| 455 | * @param out the vector where the name will be stored |
| 456 | * @param len the size of the out vector. |
| 457 | * @param path where the binaries are stored |
| 458 | * @param kver the kernel version |
| 459 | * @param name the eBPF program name. |
| 460 | * @param is_return is return or entry ? |
| 461 | */ |
| 462 | static void |
| 463 | ebpf_mount_name(char *out, size_t len, char *path, uint32_t kver, const char *name, int is_return, int is_rhf) |
| 464 | { |
| 465 | char *version = ebpf_select_kernel_name(kver); |
| 466 | snprintfz( |
| 467 | out, |
| 468 | len, |
| 469 | "%s/ebpf.d/%cnetdata_ebpf_%s.%s%s.o", |
| 470 | path, |
| 471 | (is_return) ? 'r' : 'p', |
| 472 | name, |
| 473 | version, |
| 474 | (is_rhf != -1) ? ".rhf" : ""); |
| 475 | } |
| 476 | |
| 477 | //---------------------------------------------------------------------------------------------------------------------- |
| 478 | |
| 479 | /** |
| 480 | * Statistics from targets |
| 481 | * |
| 482 | * Count the information from targets. |
| 483 | * |
| 484 | * @param report the output structure |
| 485 | * @param targets vector with information about the eBPF plugin. |
| 486 | * @param value factor used to update calculation |
| 487 | */ |
| 488 | static void ebpf_stats_targets(ebpf_plugin_stats_t *report, netdata_ebpf_targets_t *targets, int value) |
| 489 | { |
| 490 | if (!targets) { |
| 491 | report->probes = report->tracepoints = report->trampolines = 0; |
| 492 | return; |
| 493 | } |
| 494 | |
| 495 | int i = 0; |
| 496 | while (targets[i].name) { |
| 497 | switch (targets[i].mode) { |
| 498 | case EBPF_LOAD_PROBE: { |
| 499 | report->probes += value; |
| 500 | break; |
| 501 | } |
| 502 | case EBPF_LOAD_RETPROBE: { |
| 503 | report->retprobes += value; |
| 504 | break; |
| 505 | } |
| 506 | case EBPF_LOAD_TRACEPOINT: { |
| 507 | report->tracepoints += value; |
| 508 | break; |
| 509 | } |
| 510 | case EBPF_LOAD_TRAMPOLINE: { |
| 511 | report->trampolines += value; |
| 512 | break; |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | i++; |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | /** |
| 521 | * Update General stats |
| 522 | * |
| 523 | * Update eBPF plugin statistics that has relationship with the thread. |
| 524 | * |
| 525 | * This function must be called with mutex associated to charts is locked. |
| 526 | * |
| 527 | * @param report the output structure |
| 528 | * @param em the structure with information about how the module/thread is working. |
| 529 | */ |
| 530 | void ebpf_update_stats(ebpf_plugin_stats_t *report, ebpf_module_t *em) |
| 531 | { |
| 532 | int value; |
| 533 | |
| 534 | // It is not necessary to report more information. |
| 535 | if (ebpf_module_enabled_get(em) > NETDATA_THREAD_EBPF_FUNCTION_RUNNING) |
| 536 | value = -1; |
| 537 | else |
| 538 | value = 1; |
| 539 | |
| 540 | report->threads += value; |
| 541 | report->running += value; |
| 542 | |
| 543 | // In theory the `else if` is useless, because when this function is called, the module should not stay in |
| 544 | // EBPF_LOAD_PLAY_DICE. We have this additional condition to detect errors from developers. |
| 545 | if (em->load & EBPF_LOAD_LEGACY) |
| 546 | report->legacy += value; |
| 547 | else if (em->load & EBPF_LOAD_CORE) |
| 548 | report->core += value; |
| 549 | |
| 550 | if (em->maps_per_core) |
| 551 | report->hash_percpu += value; |
| 552 | else |
| 553 | report->hash_unique += value; |
| 554 | |
| 555 | ebpf_stats_targets(report, em->targets, value); |
| 556 | } |
| 557 | |
| 558 | /** |
| 559 | * Update Kernel memory with memory |
| 560 | * |
| 561 | * This algorithm is an adaptation of https://elixir.bootlin.com/linux/v6.1.14/source/tools/bpf/bpftool/common.c#L402 |
| 562 | * to get 'memlock' data and update report. |
| 563 | * |
| 564 | * @param report the output structure |
| 565 | * @param map pointer to a map. |
| 566 | * @param action What action will be done with this map. |
| 567 | */ |
| 568 | void ebpf_update_kernel_memory(ebpf_plugin_stats_t *report, ebpf_local_maps_t *map, ebpf_stats_action_t action) |
| 569 | { |
| 570 | char filename[FILENAME_MAX + 1]; |
| 571 | snprintfz(filename, FILENAME_MAX, "/proc/self/fdinfo/%d", map->map_fd); |
| 572 | procfile *ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT); |
| 573 | if (unlikely(!ff)) { |
| 574 | netdata_log_error("Cannot open %s", filename); |
| 575 | return; |
| 576 | } |
| 577 | |
| 578 | ff = procfile_readall(ff); |
| 579 | if (unlikely(!ff)) |
| 580 | return; |
| 581 | |
| 582 | unsigned long j, lines = procfile_lines(ff); |
| 583 | char *memlock = "memlock"; |
| 584 | for (j = 0; j < lines; j++) { |
| 585 | char *cmp = procfile_lineword(ff, j, 0); |
| 586 | if (!strncmp(memlock, cmp, 7)) { |
| 587 | uint64_t memsize = (uint64_t)str2l(procfile_lineword(ff, j, 1)); |
| 588 | switch (action) { |
| 589 | case EBPF_ACTION_STAT_ADD: { |
| 590 | report->memlock_kern += memsize; |
| 591 | report->hash_tables += 1; |
| 592 | #ifdef NETDATA_DEV_MODE |
| 593 | collector_info( |
| 594 | "Hash table %u: %s (FD = %d) is consuming %lu bytes totalizing %lu bytes", |
| 595 | report->hash_tables, |
| 596 | map->name, |
| 597 | map->map_fd, |
| 598 | memsize, |
| 599 | report->memlock_kern); |
| 600 | #endif |
| 601 | break; |
| 602 | } |
| 603 | case EBPF_ACTION_STAT_REMOVE: { |
| 604 | report->memlock_kern -= memsize; |
| 605 | report->hash_tables -= 1; |
| 606 | #ifdef NETDATA_DEV_MODE |
| 607 | collector_info( |
| 608 | "Hash table %s (FD = %d) was removed releasing %lu bytes, now we have %u tables loaded totalizing %lu bytes.", |
| 609 | map->name, |
| 610 | map->map_fd, |
| 611 | memsize, |
| 612 | report->hash_tables, |
| 613 | report->memlock_kern); |
| 614 | #endif |
| 615 | break; |
| 616 | } |
| 617 | default: { |
| 618 | break; |
| 619 | } |
| 620 | } |
| 621 | break; |
| 622 | } |
| 623 | } |
| 624 | |
| 625 | procfile_close(ff); |
| 626 | } |
| 627 | |
| 628 | /** |
| 629 | * Update Kernel memory with memory |
| 630 | * |
| 631 | * This algorithm is an adaptation of https://elixir.bootlin.com/linux/v6.1.14/source/tools/bpf/bpftool/common.c#L402 |
| 632 | * to get 'memlock' data and update report. |
| 633 | * |
| 634 | * @param report the output structure |
| 635 | * @param map pointer to a map. Last map must fish with name = NULL |
| 636 | * @param action should plugin add or remove values from amount. |
| 637 | */ |
| 638 | void ebpf_update_kernel_memory_with_vector( |
| 639 | ebpf_plugin_stats_t *report, |
| 640 | ebpf_local_maps_t *maps, |
| 641 | ebpf_stats_action_t action) |
| 642 | { |
| 643 | if (!maps) |
| 644 | return; |
| 645 | |
| 646 | ebpf_local_maps_t *map; |
| 647 | int i = 0; |
| 648 | for (map = &maps[i]; maps[i].name; i++, map = &maps[i]) { |
| 649 | int fd = map->map_fd; |
| 650 | if (fd == ND_EBPF_MAP_FD_NOT_INITIALIZED) |
| 651 | continue; |
| 652 | |
| 653 | ebpf_update_kernel_memory(report, map, action); |
| 654 | } |
| 655 | } |
| 656 | |
| 657 | //---------------------------------------------------------------------------------------------------------------------- |
| 658 | |
| 659 | void ebpf_update_pid_table(ebpf_local_maps_t *pid, ebpf_module_t *em) |
| 660 | { |
| 661 | pid->user_input = em->pid_map_size; |
| 662 | } |
| 663 | |
| 664 | /** |
| 665 | * Update map size |
| 666 | * |
| 667 | * Update map size with information read from configuration files. |
| 668 | * |
| 669 | * @param map the structure with file descriptor to update. |
| 670 | * @param lmap the structure with information from configuration files. |
| 671 | * @param em the structure with information about how the module/thread is working. |
| 672 | * @param map_name the name of the file used to log. |
| 673 | */ |
| 674 | void ebpf_update_map_size( |
| 675 | struct bpf_map *map, |
| 676 | ebpf_local_maps_t *lmap, |
| 677 | ebpf_module_t *em, |
| 678 | const char *map_name __maybe_unused) |
| 679 | { |
| 680 | uint32_t define_size = 0; |
| 681 | uint32_t apps_type = NETDATA_EBPF_MAP_PID | NETDATA_EBPF_MAP_RESIZABLE; |
| 682 | if (lmap->user_input && lmap->user_input != lmap->internal_input) { |
| 683 | define_size = lmap->internal_input; |
| 684 | #ifdef NETDATA_INTERNAL_CHECKS |
| 685 | collector_info("Changing map %s from size %u to %u ", map_name, lmap->internal_input, lmap->user_input); |
| 686 | #endif |
| 687 | } else if (((lmap->type & apps_type) == apps_type) && (!em->apps_charts) && (!em->cgroup_charts)) { |
| 688 | lmap->user_input = ND_EBPF_DEFAULT_MIN_PID; |
| 689 | } else if (((em->apps_charts) || (em->cgroup_charts)) && (em->apps_level != NETDATA_APPS_NOT_SET)) { |
| 690 | switch (em->apps_level) { |
| 691 | case NETDATA_APPS_LEVEL_ALL: { |
| 692 | define_size = lmap->user_input; |
| 693 | break; |
| 694 | } |
| 695 | case NETDATA_APPS_LEVEL_PARENT: { |
| 696 | define_size = ND_EBPF_DEFAULT_PID_SIZE / 2; |
| 697 | break; |
| 698 | } |
| 699 | case NETDATA_APPS_LEVEL_REAL_PARENT: |
| 700 | default: { |
| 701 | define_size = ND_EBPF_DEFAULT_PID_SIZE / 3; |
| 702 | } |
| 703 | } |
| 704 | } |
| 705 | |
| 706 | if (!define_size) |
| 707 | return; |
| 708 | |
| 709 | #ifdef LIBBPF_MAJOR_VERSION |
| 710 | bpf_map__set_max_entries(map, define_size); |
| 711 | #else |
| 712 | bpf_map__resize(map, define_size); |
| 713 | #endif |
| 714 | } |
| 715 | |
| 716 | #ifdef LIBBPF_MAJOR_VERSION |
| 717 | /** |
| 718 | * Update map type |
| 719 | * |
| 720 | * Update map type with information given. |
| 721 | * |
| 722 | * @param map the map we want to modify |
| 723 | * @param w a structure with user input |
| 724 | */ |
| 725 | void ebpf_update_map_type(struct bpf_map *map, ebpf_local_maps_t *w) |
| 726 | { |
| 727 | if (bpf_map__set_type(map, w->map_type)) { |
| 728 | netdata_log_error("Cannot modify map type for %s", w->name); |
| 729 | } |
| 730 | } |
| 731 | |
| 732 | /** |
| 733 | * Define map type |
| 734 | * |
| 735 | * This PR defines the type used by hash tables according user input. |
| 736 | * |
| 737 | * @param maps the list of maps used with a hash table. |
| 738 | * @param maps_per_core define if map type according user specification. |
| 739 | * @param kver kernel version host is running. |
| 740 | */ |
| 741 | void ebpf_define_map_type(ebpf_local_maps_t *maps, int maps_per_core, int kver) |
| 742 | { |
| 743 | if (!maps) |
| 744 | return; |
| 745 | |
| 746 | // Before kernel 4.06 there was not percpu hash tables |
| 747 | if (kver < NETDATA_EBPF_KERNEL_4_06) |
| 748 | maps_per_core = CONFIG_BOOLEAN_NO; |
| 749 | |
| 750 | int i = 0; |
| 751 | while (maps[i].name) { |
| 752 | ebpf_local_maps_t *map = &maps[i]; |
| 753 | // maps_per_core is a boolean value in configuration files. |
| 754 | if (maps_per_core) { |
| 755 | if (map->map_type == BPF_MAP_TYPE_HASH) |
| 756 | map->map_type = BPF_MAP_TYPE_PERCPU_HASH; |
| 757 | else if (map->map_type == BPF_MAP_TYPE_ARRAY) |
| 758 | map->map_type = BPF_MAP_TYPE_PERCPU_ARRAY; |
| 759 | } else { |
| 760 | if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH) |
| 761 | map->map_type = BPF_MAP_TYPE_HASH; |
| 762 | else if (map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) |
| 763 | map->map_type = BPF_MAP_TYPE_ARRAY; |
| 764 | } |
| 765 | |
| 766 | i++; |
| 767 | } |
| 768 | } |
| 769 | #endif |
| 770 | |
| 771 | /** |
| 772 | * Update Legacy map |
| 773 | * |
| 774 | * Update map for eBPF legacy code. |
| 775 | * |
| 776 | * @param program the structure with values read from binary. |
| 777 | * @param em the structure with information about how the module/thread is working. |
| 778 | */ |
| 779 | static void ebpf_update_legacy_map(struct bpf_object *program, ebpf_module_t *em) |
| 780 | { |
| 781 | struct bpf_map *map; |
| 782 | ebpf_local_maps_t *maps = em->maps; |
| 783 | if (!maps) |
| 784 | return; |
| 785 | |
| 786 | bpf_map__for_each(map, program) |
| 787 | { |
| 788 | const char *map_name = bpf_map__name(map); |
| 789 | int i = 0; |
| 790 | while (maps[i].name) { |
| 791 | ebpf_local_maps_t *w = &maps[i]; |
| 792 | |
| 793 | if (!strcmp(w->name, map_name)) { |
| 794 | // Modify size |
| 795 | if (w->type & NETDATA_EBPF_MAP_RESIZABLE) { |
| 796 | ebpf_update_map_size(map, w, em, map_name); |
| 797 | } |
| 798 | |
| 799 | #ifdef LIBBPF_MAJOR_VERSION |
| 800 | ebpf_update_map_type(map, w); |
| 801 | #endif |
| 802 | } |
| 803 | |
| 804 | i++; |
| 805 | } |
| 806 | } |
| 807 | } |
| 808 | |
| 809 | size_t ebpf_count_programs(struct bpf_object *obj) |
| 810 | { |
| 811 | size_t tot = 0; |
| 812 | struct bpf_program *prog; |
| 813 | bpf_object__for_each_program(prog, obj) |
| 814 | { |
| 815 | tot++; |
| 816 | } |
| 817 | |
| 818 | return tot; |
| 819 | } |
| 820 | |
| 821 | static ebpf_specify_name_t *ebpf_find_names(ebpf_specify_name_t *names, const char *prog_name) |
| 822 | { |
| 823 | size_t i = 0; |
| 824 | while (names[i].program_name) { |
| 825 | if (!strcmp(prog_name, names[i].program_name)) |
| 826 | return &names[i]; |
| 827 | |
| 828 | i++; |
| 829 | } |
| 830 | |
| 831 | return NULL; |
| 832 | } |
| 833 | |
| 834 | static struct bpf_link **ebpf_attach_programs(struct bpf_object *obj, size_t length, ebpf_specify_name_t *names) |
| 835 | { |
| 836 | struct bpf_link **links = callocz(length, sizeof(struct bpf_link *)); |
| 837 | size_t i = 0; |
| 838 | struct bpf_program *prog; |
| 839 | ebpf_specify_name_t *w; |
| 840 | bpf_object__for_each_program(prog, obj) |
| 841 | { |
| 842 | if (names) { |
| 843 | const char *name = bpf_program__name(prog); |
| 844 | w = ebpf_find_names(names, name); |
| 845 | } else |
| 846 | w = NULL; |
| 847 | |
| 848 | if (w) { |
| 849 | enum bpf_prog_type type = bpf_program__get_type(prog); |
| 850 | if (type == BPF_PROG_TYPE_KPROBE) |
| 851 | links[i] = bpf_program__attach_kprobe(prog, w->retprobe, w->optional); |
| 852 | } else |
| 853 | links[i] = bpf_program__attach(prog); |
| 854 | |
| 855 | if (libbpf_get_error(links[i])) { |
| 856 | links[i] = NULL; |
| 857 | } |
| 858 | |
| 859 | i++; |
| 860 | } |
| 861 | |
| 862 | return links; |
| 863 | } |
| 864 | |
| 865 | static void ebpf_update_maps(ebpf_module_t *em, struct bpf_object *obj) |
| 866 | { |
| 867 | if (!em->maps) |
| 868 | return; |
| 869 | |
| 870 | ebpf_local_maps_t *maps = em->maps; |
| 871 | struct bpf_map *map; |
| 872 | bpf_map__for_each(map, obj) |
| 873 | { |
| 874 | int fd = bpf_map__fd(map); |
| 875 | if (maps) { |
| 876 | const char *map_name = bpf_map__name(map); |
| 877 | int j = 0; |
| 878 | while (maps[j].name) { |
| 879 | ebpf_local_maps_t *w = &maps[j]; |
| 880 | if (w->map_fd == ND_EBPF_MAP_FD_NOT_INITIALIZED && !strcmp(map_name, w->name)) |
| 881 | w->map_fd = fd; |
| 882 | |
| 883 | j++; |
| 884 | } |
| 885 | } |
| 886 | } |
| 887 | } |
| 888 | |
| 889 | /** |
| 890 | * Update Controller |
| 891 | * |
| 892 | * Update controller value with user input. |
| 893 | * |
| 894 | * @param fd the table file descriptor |
| 895 | * @param em structure with information about eBPF program we will load. |
| 896 | */ |
| 897 | void ebpf_update_controller(int fd, ebpf_module_t *em) |
| 898 | { |
| 899 | uint32_t values[NETDATA_CONTROLLER_END] = { |
| 900 | (em->apps_charts & NETDATA_EBPF_APPS_FLAG_YES) | em->cgroup_charts, em->apps_level, 0, 0, 0, 0}; |
| 901 | uint32_t key; |
| 902 | uint32_t end = NETDATA_CONTROLLER_PID_TABLE_ADD; |
| 903 | |
| 904 | for (key = NETDATA_CONTROLLER_APPS_ENABLED; key < end; key++) { |
| 905 | int ret = bpf_map_update_elem(fd, &key, &values[key], BPF_ANY); |
| 906 | if (ret) |
| 907 | netdata_log_error("Add key(%u) for controller table failed.", key); |
| 908 | } |
| 909 | } |
| 910 | |
| 911 | /** |
| 912 | * Update Legacy controller |
| 913 | * |
| 914 | * Update legacy controller table when eBPF program has it. |
| 915 | * |
| 916 | * @param em structure with information about eBPF program we will load. |
| 917 | * @param obj bpf object with tables. |
| 918 | */ |
| 919 | static void ebpf_update_legacy_controller(ebpf_module_t *em, struct bpf_object *obj) |
| 920 | { |
| 921 | ebpf_local_maps_t *maps = em->maps; |
| 922 | if (!maps) |
| 923 | return; |
| 924 | |
| 925 | struct bpf_map *map; |
| 926 | bpf_map__for_each(map, obj) |
| 927 | { |
| 928 | size_t i = 0; |
| 929 | while (maps[i].name) { |
| 930 | ebpf_local_maps_t *w = &maps[i]; |
| 931 | if (w->map_fd != ND_EBPF_MAP_FD_NOT_INITIALIZED && (w->type & NETDATA_EBPF_MAP_CONTROLLER)) { |
| 932 | w->type &= ~NETDATA_EBPF_MAP_CONTROLLER; |
| 933 | w->type |= NETDATA_EBPF_MAP_CONTROLLER_UPDATED; |
| 934 | |
| 935 | ebpf_update_controller(w->map_fd, em); |
| 936 | } |
| 937 | i++; |
| 938 | } |
| 939 | } |
| 940 | } |
| 941 | |
| 942 | /** |
| 943 | * Load Program |
| 944 | * |
| 945 | * Load eBPF program into kernel |
| 946 | * |
| 947 | * @param plugins_dir directory where binary are stored |
| 948 | * @param em structure with information about eBPF program we will load. |
| 949 | * @param kver the kernel version according /usr/include/linux/version.h |
| 950 | * @param is_rhf is a kernel from Red Hat Family? |
| 951 | * @param obj structure where we will store object loaded. |
| 952 | * |
| 953 | * @return it returns a link for each target we associated an eBPF program. |
| 954 | */ |
| 955 | struct bpf_link **ebpf_load_program(char *plugins_dir, ebpf_module_t *em, int kver, int is_rhf, struct bpf_object **obj) |
| 956 | { |
| 957 | char lpath[4096]; |
| 958 | |
| 959 | uint32_t idx = ebpf_select_index(em->kernels, is_rhf, kver); |
| 960 | |
| 961 | ebpf_mount_name(lpath, 4095, plugins_dir, idx, em->info.thread_name, em->mode, is_rhf); |
| 962 | |
| 963 | // When this function is called ebpf.plugin is using legacy code, so we should reset the variable |
| 964 | em->load &= ~NETDATA_EBPF_LOAD_METHODS; |
| 965 | em->load |= EBPF_LOAD_LEGACY; |
| 966 | |
| 967 | *obj = bpf_object__open_file(lpath, NULL); |
| 968 | if (!*obj) { |
| 969 | *obj = NULL; |
| 970 | return NULL; |
| 971 | } |
| 972 | |
| 973 | if (libbpf_get_error(*obj)) { |
| 974 | bpf_object__close(*obj); |
| 975 | *obj = NULL; |
| 976 | return NULL; |
| 977 | } |
| 978 | |
| 979 | ebpf_update_legacy_map(*obj, em); |
| 980 | |
| 981 | if (bpf_object__load(*obj)) { |
| 982 | netdata_log_error("ERROR: loading BPF object file failed %s\n", lpath); |
| 983 | bpf_object__close(*obj); |
| 984 | *obj = NULL; |
| 985 | return NULL; |
| 986 | } |
| 987 | |
| 988 | ebpf_update_maps(em, *obj); |
| 989 | ebpf_update_legacy_controller(em, *obj); |
| 990 | |
| 991 | size_t count_programs = ebpf_count_programs(*obj); |
| 992 | |
| 993 | #ifdef NETDATA_INTERNAL_CHECKS |
| 994 | collector_info("eBPF program %s loaded with success!", lpath); |
| 995 | #endif |
| 996 | |
| 997 | return ebpf_attach_programs(*obj, count_programs, em->names); |
| 998 | } |
| 999 | |
| 1000 | char *ebpf_find_symbol(char *search) |
| 1001 | { |
| 1002 | char filename[FILENAME_MAX + 1]; |
| 1003 | char *ret = NULL; |
| 1004 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, NETDATA_KALLSYMS); |
| 1005 | procfile *ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT); |
| 1006 | if (unlikely(!ff)) { |
| 1007 | netdata_log_error("Cannot open %s%s", netdata_configured_host_prefix, NETDATA_KALLSYMS); |
| 1008 | return ret; |
| 1009 | } |
| 1010 | |
| 1011 | ff = procfile_readall(ff); |
| 1012 | if (unlikely(!ff)) |
| 1013 | return ret; |
| 1014 | |
| 1015 | unsigned long i, lines = procfile_lines(ff); |
| 1016 | size_t length = strlen(search); |
| 1017 | for (i = 0; i < lines; i++) { |
| 1018 | char *cmp = procfile_lineword(ff, i, 2); |
| 1019 | if (!strncmp(search, cmp, length)) { |
| 1020 | ret = strdupz(cmp); |
| 1021 | break; |
| 1022 | } |
| 1023 | } |
| 1024 | |
| 1025 | procfile_close(ff); |
| 1026 | |
| 1027 | return ret; |
| 1028 | } |
| 1029 | |
| 1030 | void ebpf_update_names(ebpf_specify_name_t *opt, ebpf_module_t *em) |
| 1031 | { |
| 1032 | int mode = em->mode; |
| 1033 | em->names = opt; |
| 1034 | |
| 1035 | size_t i = 0; |
| 1036 | while (opt[i].program_name) { |
| 1037 | opt[i].retprobe = (mode == MODE_RETURN); |
| 1038 | opt[i].optional = ebpf_find_symbol(opt[i].function_to_attach); |
| 1039 | |
| 1040 | i++; |
| 1041 | } |
| 1042 | } |
| 1043 | |
| 1044 | //---------------------------------------------------------------------------------------------------------------------- |
| 1045 | |
| 1046 | void ebpf_mount_config_name(char *filename, size_t length, char *path, const char *config) |
| 1047 | { |
| 1048 | snprintf(filename, length, "%s/ebpf.d/%s", path, config); |
| 1049 | } |
| 1050 | |
| 1051 | int ebpf_load_config(struct config *config, char *filename) |
| 1052 | { |
| 1053 | return inicfg_load(config, filename, 0, NULL); |
| 1054 | } |
| 1055 | |
| 1056 | static netdata_run_mode_t ebpf_select_mode(const char *mode) |
| 1057 | { |
| 1058 | if (!strcasecmp(mode, EBPF_CFG_LOAD_MODE_RETURN)) |
| 1059 | return MODE_RETURN; |
| 1060 | else if (!strcasecmp(mode, "dev")) |
| 1061 | return MODE_DEVMODE; |
| 1062 | |
| 1063 | return MODE_ENTRY; |
| 1064 | } |
| 1065 | |
| 1066 | static void ebpf_select_mode_string(char *output, size_t len, netdata_run_mode_t sel) |
| 1067 | { |
| 1068 | if (sel == MODE_RETURN) |
| 1069 | strncpyz(output, EBPF_CFG_LOAD_MODE_RETURN, len); |
| 1070 | else |
| 1071 | strncpyz(output, EBPF_CFG_LOAD_MODE_DEFAULT, len); |
| 1072 | } |
| 1073 | |
| 1074 | /** |
| 1075 | * Convert string to load mode |
| 1076 | * |
| 1077 | * Convert the string given as argument to value present in enum. |
| 1078 | * |
| 1079 | * @param str value read from configuration file. |
| 1080 | * |
| 1081 | * @return It returns the value to be used. |
| 1082 | */ |
| 1083 | netdata_ebpf_load_mode_t epbf_convert_string_to_load_mode(const char *str) |
| 1084 | { |
| 1085 | if (!strcasecmp(str, EBPF_CFG_CORE_PROGRAM)) |
| 1086 | return EBPF_LOAD_CORE; |
| 1087 | else if (!strcasecmp(str, EBPF_CFG_LEGACY_PROGRAM)) |
| 1088 | return EBPF_LOAD_LEGACY; |
| 1089 | |
| 1090 | return EBPF_LOAD_PLAY_DICE; |
| 1091 | } |
| 1092 | |
| 1093 | /** |
| 1094 | * Convert load mode to string |
| 1095 | * |
| 1096 | * @param mode value that will select the string |
| 1097 | * |
| 1098 | * @return It returns the string associated to mode. |
| 1099 | */ |
| 1100 | static char *ebpf_convert_load_mode_to_string(netdata_ebpf_load_mode_t mode) |
| 1101 | { |
| 1102 | if (mode & EBPF_LOAD_CORE) |
| 1103 | return EBPF_CFG_CORE_PROGRAM; |
| 1104 | else if (mode & EBPF_LOAD_LEGACY) |
| 1105 | return EBPF_CFG_LEGACY_PROGRAM; |
| 1106 | |
| 1107 | return EBPF_CFG_DEFAULT_PROGRAM; |
| 1108 | } |
| 1109 | |
| 1110 | /** |
| 1111 | * Convert collect pid to string |
| 1112 | * |
| 1113 | * @param level value that will select the string |
| 1114 | * |
| 1115 | * @return It returns the string associated to level. |
| 1116 | */ |
| 1117 | static char *ebpf_convert_collect_pid_to_string(netdata_apps_level_t level) |
| 1118 | { |
| 1119 | if (level == NETDATA_APPS_LEVEL_REAL_PARENT) |
| 1120 | return EBPF_CFG_PID_REAL_PARENT; |
| 1121 | else if (level == NETDATA_APPS_LEVEL_PARENT) |
| 1122 | return EBPF_CFG_PID_PARENT; |
| 1123 | else if (level == NETDATA_APPS_LEVEL_ALL) |
| 1124 | return EBPF_CFG_PID_ALL; |
| 1125 | |
| 1126 | return EBPF_CFG_PID_INTERNAL_USAGE; |
| 1127 | } |
| 1128 | |
| 1129 | /** |
| 1130 | * Convert string to apps level |
| 1131 | * |
| 1132 | * @param str the argument read from config files |
| 1133 | * |
| 1134 | * @return it returns the level associated to the string or default when it is a wrong value |
| 1135 | */ |
| 1136 | netdata_apps_level_t ebpf_convert_string_to_apps_level(const char *str) |
| 1137 | { |
| 1138 | if (!strcasecmp(str, EBPF_CFG_PID_REAL_PARENT)) |
| 1139 | return NETDATA_APPS_LEVEL_REAL_PARENT; |
| 1140 | else if (!strcasecmp(str, EBPF_CFG_PID_PARENT)) |
| 1141 | return NETDATA_APPS_LEVEL_PARENT; |
| 1142 | else if (!strcasecmp(str, EBPF_CFG_PID_ALL)) |
| 1143 | return NETDATA_APPS_LEVEL_ALL; |
| 1144 | |
| 1145 | return NETDATA_APPS_NOT_SET; |
| 1146 | } |
| 1147 | |
| 1148 | /** |
| 1149 | * CO-RE type |
| 1150 | * |
| 1151 | * Select the preferential type of CO-RE |
| 1152 | * |
| 1153 | * @param str value read from configuration file. |
| 1154 | * @param lmode load mode used by collector. |
| 1155 | */ |
| 1156 | netdata_ebpf_program_loaded_t ebpf_convert_core_type(const char *str, netdata_run_mode_t lmode) |
| 1157 | { |
| 1158 | if (!strcasecmp(str, EBPF_CFG_ATTACH_TRACEPOINT)) |
| 1159 | return EBPF_LOAD_TRACEPOINT; |
| 1160 | else if (!strcasecmp(str, EBPF_CFG_ATTACH_PROBE)) { |
| 1161 | return (lmode == MODE_ENTRY) ? EBPF_LOAD_PROBE : EBPF_LOAD_RETPROBE; |
| 1162 | } |
| 1163 | |
| 1164 | return EBPF_LOAD_TRAMPOLINE; |
| 1165 | } |
| 1166 | |
| 1167 | #ifdef LIBBPF_MAJOR_VERSION |
| 1168 | /** |
| 1169 | * Adjust Thread Load |
| 1170 | * |
| 1171 | * Adjust thread configuration according specified load. |
| 1172 | * |
| 1173 | * @param mod the main structure that will be adjusted. |
| 1174 | * @param file the btf file used with thread. |
| 1175 | */ |
| 1176 | void ebpf_adjust_thread_load(ebpf_module_t *mod, struct btf *file) |
| 1177 | { |
| 1178 | if (!file) { |
| 1179 | mod->load &= ~EBPF_LOAD_CORE; |
| 1180 | mod->load |= EBPF_LOAD_LEGACY; |
| 1181 | } else if (mod->load == EBPF_LOAD_PLAY_DICE && file) { |
| 1182 | mod->load &= ~EBPF_LOAD_LEGACY; |
| 1183 | mod->load |= EBPF_LOAD_CORE; |
| 1184 | } |
| 1185 | } |
| 1186 | |
| 1187 | /** |
| 1188 | * Parse BTF file |
| 1189 | * |
| 1190 | * Parse a specific BTF file present on filesystem |
| 1191 | * |
| 1192 | * @param filename the file that will be parsed. |
| 1193 | * |
| 1194 | * @return It returns a pointer for the file on success and NULL otherwise. |
| 1195 | */ |
| 1196 | struct btf *ebpf_parse_btf_file(const char *filename) |
| 1197 | { |
| 1198 | struct btf *bf = btf__parse(filename, NULL); |
| 1199 | if (libbpf_get_error(bf)) { |
| 1200 | fprintf(stderr, "Cannot parse btf file"); |
| 1201 | btf__free(bf); |
| 1202 | return NULL; |
| 1203 | } |
| 1204 | |
| 1205 | return bf; |
| 1206 | } |
| 1207 | |
| 1208 | /** |
| 1209 | * Load default btf file |
| 1210 | * |
| 1211 | * Load the default BTF file on environment. |
| 1212 | * |
| 1213 | * @param path is the fullpath |
| 1214 | * @param filename is the file inside BTF path. |
| 1215 | */ |
| 1216 | struct btf *ebpf_load_btf_file(const char *path, const char *filename) |
| 1217 | { |
| 1218 | char fullpath[PATH_MAX + 1]; |
| 1219 | snprintfz(fullpath, PATH_MAX, "%s/%s", path, filename); |
| 1220 | struct btf *ret = ebpf_parse_btf_file(fullpath); |
| 1221 | if (!ret) |
| 1222 | collector_info( |
| 1223 | "Your environment does not have BTF file %s/%s. The plugin will work with 'legacy' code.", path, filename); |
| 1224 | |
| 1225 | return ret; |
| 1226 | } |
| 1227 | |
| 1228 | /** |
| 1229 | * Find BTF attach type |
| 1230 | * |
| 1231 | * Search type fr current btf file. |
| 1232 | * |
| 1233 | * @param file is the structure for the btf file already parsed. |
| 1234 | */ |
| 1235 | static inline const struct btf_type *ebpf_find_btf_attach_type(struct btf *file) |
| 1236 | { |
| 1237 | int id = btf__find_by_name_kind(file, "bpf_attach_type", BTF_KIND_ENUM); |
| 1238 | if (id < 0) { |
| 1239 | fprintf(stderr, "Cannot find 'bpf_attach_type'"); |
| 1240 | |
| 1241 | return NULL; |
| 1242 | } |
| 1243 | |
| 1244 | return btf__type_by_id(file, id); |
| 1245 | } |
| 1246 | |
| 1247 | /** |
| 1248 | * Is function inside BTF |
| 1249 | * |
| 1250 | * Look for a specific function inside the given BTF file. |
| 1251 | * |
| 1252 | * @param file is the structure for the btf file already parsed. |
| 1253 | * @param function is the function that we want to find. |
| 1254 | */ |
| 1255 | int ebpf_is_function_inside_btf(struct btf *file, char *function) |
| 1256 | { |
| 1257 | const struct btf_type *type = ebpf_find_btf_attach_type(file); |
| 1258 | if (!type) |
| 1259 | return -1; |
| 1260 | |
| 1261 | const struct btf_enum *e = btf_enum(type); |
| 1262 | int i, id; |
| 1263 | for (id = -1, i = 0; i < btf_vlen(type); i++, e++) { |
| 1264 | if (!strcmp(btf__name_by_offset(file, e->name_off), "BPF_TRACE_FENTRY")) { |
| 1265 | id = btf__find_by_name_kind(file, function, BTF_KIND_FUNC); |
| 1266 | break; |
| 1267 | } |
| 1268 | } |
| 1269 | |
| 1270 | return (id > 0) ? 1 : 0; |
| 1271 | } |
| 1272 | #endif |
| 1273 | |
| 1274 | /** |
| 1275 | * Update target with configuration |
| 1276 | * |
| 1277 | * Update target load mode with value. |
| 1278 | * |
| 1279 | * @param em the module structure |
| 1280 | * @param value value used to update. |
| 1281 | */ |
| 1282 | static void ebpf_update_target_with_conf(ebpf_module_t *em, netdata_ebpf_program_loaded_t value) |
| 1283 | { |
| 1284 | netdata_ebpf_targets_t *targets = em->targets; |
| 1285 | if (!targets) { |
| 1286 | return; |
| 1287 | } |
| 1288 | |
| 1289 | int i = 0; |
| 1290 | while (targets[i].name) { |
| 1291 | targets[i].mode = value; |
| 1292 | i++; |
| 1293 | } |
| 1294 | } |
| 1295 | |
| 1296 | /** |
| 1297 | * Select Load Mode |
| 1298 | * |
| 1299 | * Select the load mode according the given inputs. |
| 1300 | * |
| 1301 | * @param btf_file a pointer to the loaded btf file. |
| 1302 | * @parma load current value. |
| 1303 | * @param btf_file a pointer to the loaded btf file. |
| 1304 | * @param is_rhf is Red Hat family? |
| 1305 | * |
| 1306 | * @return it returns the new load mode. |
| 1307 | */ |
| 1308 | static netdata_ebpf_load_mode_t ebpf_select_load_mode( |
| 1309 | struct btf *btf_file __maybe_unused, |
| 1310 | netdata_ebpf_load_mode_t load, |
| 1311 | int kver __maybe_unused, |
| 1312 | int is_rh __maybe_unused) |
| 1313 | { |
| 1314 | #ifdef LIBBPF_MAJOR_VERSION |
| 1315 | if ((load & EBPF_LOAD_CORE) || (load & EBPF_LOAD_PLAY_DICE)) { |
| 1316 | // Quick fix for Oracle linux 8.x |
| 1317 | load = (!btf_file || (is_rh && (kver >= NETDATA_EBPF_KERNEL_5_4 && kver < NETDATA_EBPF_KERNEL_5_5))) ? |
| 1318 | EBPF_LOAD_LEGACY : |
| 1319 | EBPF_LOAD_CORE; |
| 1320 | } |
| 1321 | #else |
| 1322 | load = EBPF_LOAD_LEGACY; |
| 1323 | #endif |
| 1324 | |
| 1325 | return load; |
| 1326 | } |
| 1327 | |
| 1328 | /** |
| 1329 | * Update Module using config |
| 1330 | * |
| 1331 | * Update configuration for a specific thread. |
| 1332 | * |
| 1333 | * @param modules structure that will be updated |
| 1334 | * @param origin specify the configuration file loaded |
| 1335 | * @param btf_file a pointer to the loaded btf file. |
| 1336 | * @param is_rhf is Red Hat family? |
| 1337 | */ |
| 1338 | void ebpf_update_module_using_config( |
| 1339 | ebpf_module_t *modules, |
| 1340 | netdata_ebpf_load_mode_t origin, |
| 1341 | struct btf *btf_file, |
| 1342 | int kver, |
| 1343 | int is_rh) |
| 1344 | { |
| 1345 | char default_value[EBPF_MAX_MODE_LENGTH + 1]; |
| 1346 | ebpf_select_mode_string(default_value, EBPF_MAX_MODE_LENGTH, modules->mode); |
| 1347 | const char *load_mode = inicfg_get(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_LOAD_MODE, default_value); |
| 1348 | modules->mode = ebpf_select_mode(load_mode); |
| 1349 | |
| 1350 | modules->update_every = |
| 1351 | (int)inicfg_get_number(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_UPDATE_EVERY, modules->update_every); |
| 1352 | |
| 1353 | modules->apps_charts = inicfg_get_boolean( |
| 1354 | modules->cfg, |
| 1355 | EBPF_GLOBAL_SECTION, |
| 1356 | EBPF_CFG_APPLICATION, |
| 1357 | (int)(modules->apps_charts & NETDATA_EBPF_APPS_FLAG_YES)); |
| 1358 | |
| 1359 | modules->cgroup_charts = |
| 1360 | inicfg_get_boolean(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_CGROUP, modules->cgroup_charts); |
| 1361 | |
| 1362 | modules->pid_map_size = |
| 1363 | (uint32_t)inicfg_get_number(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_PID_SIZE, modules->pid_map_size); |
| 1364 | |
| 1365 | modules->lifetime = |
| 1366 | (uint32_t)inicfg_get_number(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_LIFETIME, EBPF_DEFAULT_LIFETIME); |
| 1367 | |
| 1368 | char *value = ebpf_convert_load_mode_to_string(modules->load & NETDATA_EBPF_LOAD_METHODS); |
| 1369 | const char *type_format = inicfg_get(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_TYPE_FORMAT, value); |
| 1370 | netdata_ebpf_load_mode_t load = epbf_convert_string_to_load_mode(type_format); |
| 1371 | load = ebpf_select_load_mode(btf_file, load, kver, is_rh); |
| 1372 | modules->load = origin | load; |
| 1373 | |
| 1374 | const char *core_attach = |
| 1375 | inicfg_get(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_CORE_ATTACH, EBPF_CFG_ATTACH_TRAMPOLINE); |
| 1376 | netdata_ebpf_program_loaded_t fill_lm = ebpf_convert_core_type(core_attach, modules->mode); |
| 1377 | ebpf_update_target_with_conf(modules, fill_lm); |
| 1378 | |
| 1379 | value = ebpf_convert_collect_pid_to_string(modules->apps_level); |
| 1380 | const char *collect_pid = inicfg_get(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_COLLECT_PID, value); |
| 1381 | modules->apps_level = ebpf_convert_string_to_apps_level(collect_pid); |
| 1382 | |
| 1383 | modules->maps_per_core = |
| 1384 | inicfg_get_boolean(modules->cfg, EBPF_GLOBAL_SECTION, EBPF_CFG_MAPS_PER_CORE, modules->maps_per_core); |
| 1385 | if (kver < NETDATA_EBPF_KERNEL_4_06) |
| 1386 | modules->maps_per_core = CONFIG_BOOLEAN_NO; |
| 1387 | |
| 1388 | #ifdef NETDATA_DEV_MODE |
| 1389 | collector_info( |
| 1390 | "The thread %s was configured with: mode = %s; update every = %d; apps = %s; cgroup = %s; ebpf type format = %s; ebpf co-re tracing = %s; collect pid = %s; maps per core = %s, lifetime=%u", |
| 1391 | modules->info.thread_name, |
| 1392 | load_mode, |
| 1393 | modules->update_every, |
| 1394 | (modules->apps_charts) ? "enabled" : "disabled", |
| 1395 | (modules->cgroup_charts) ? "enabled" : "disabled", |
| 1396 | type_format, |
| 1397 | core_attach, |
| 1398 | collect_pid, |
| 1399 | (modules->maps_per_core) ? "enabled" : "disabled", |
| 1400 | modules->lifetime); |
| 1401 | #endif |
| 1402 | } |
| 1403 | |
| 1404 | /** |
| 1405 | * Update module |
| 1406 | * |
| 1407 | * When this function is called, it will load the configuration file and after this |
| 1408 | * it updates the global information of ebpf_module. |
| 1409 | * If the module has specific configuration, this function will load it, but it will not |
| 1410 | * update the variables. |
| 1411 | * |
| 1412 | * @param em the module structure |
| 1413 | * @param btf_file a pointer to the loaded btf file. |
| 1414 | * @param is_rhf is Red Hat family? |
| 1415 | * @param kver the kernel version |
| 1416 | */ |
| 1417 | void ebpf_update_module(ebpf_module_t *em, struct btf *btf_file, int kver, int is_rh) |
| 1418 | { |
| 1419 | char filename[FILENAME_MAX + 1]; |
| 1420 | netdata_ebpf_load_mode_t origin; |
| 1421 | |
| 1422 | ebpf_mount_config_name(filename, FILENAME_MAX, ebpf_user_config_dir, em->config_file); |
| 1423 | if (!ebpf_load_config(em->cfg, filename)) { |
| 1424 | ebpf_mount_config_name(filename, FILENAME_MAX, ebpf_stock_config_dir, em->config_file); |
| 1425 | if (!ebpf_load_config(em->cfg, filename)) { |
| 1426 | netdata_log_error("Cannot load the ebpf configuration file %s", em->config_file); |
| 1427 | return; |
| 1428 | } |
| 1429 | // If user defined data globally, we will have here EBPF_LOADED_FROM_USER, we need to consider this, to avoid |
| 1430 | // forcing users to configure thread by thread. |
| 1431 | origin = |
| 1432 | (!(em->load & NETDATA_EBPF_LOAD_SOURCE)) ? EBPF_LOADED_FROM_STOCK : em->load & NETDATA_EBPF_LOAD_SOURCE; |
| 1433 | } else |
| 1434 | origin = EBPF_LOADED_FROM_USER; |
| 1435 | |
| 1436 | ebpf_update_module_using_config(em, origin, btf_file, kver, is_rh); |
| 1437 | } |
| 1438 | |
| 1439 | /** |
| 1440 | * Adjust Apps Cgroup |
| 1441 | * |
| 1442 | * Apps and cgroup has internal cleanup that needs attaching tracers to release_task, to avoid overload the function |
| 1443 | * we will enable this integration by default, if and only if, we are running with trampolines. |
| 1444 | * |
| 1445 | * @param em a pointer to the main thread structure. |
| 1446 | * @param mode is the mode used with different |
| 1447 | */ |
| 1448 | void ebpf_adjust_apps_cgroup(ebpf_module_t *em, netdata_ebpf_program_loaded_t mode) |
| 1449 | { |
| 1450 | if ((em->load & EBPF_LOADED_FROM_STOCK) && (em->apps_charts || em->cgroup_charts) && mode != EBPF_LOAD_TRAMPOLINE) { |
| 1451 | em->apps_charts = NETDATA_EBPF_APPS_FLAG_NO; |
| 1452 | em->cgroup_charts = 0; |
| 1453 | } |
| 1454 | } |
| 1455 | |
| 1456 | //---------------------------------------------------------------------------------------------------------------------- |
| 1457 | |
| 1458 | /** |
| 1459 | * Load Address |
| 1460 | * |
| 1461 | * Helper used to get address from /proc/kallsym |
| 1462 | * |
| 1463 | * @param fa address structure |
| 1464 | * @param fd file descriptor loaded inside kernel. If a negative value is given |
| 1465 | * the function will load address and it won't update hash table. |
| 1466 | */ |
| 1467 | void ebpf_load_addresses(ebpf_addresses_t *fa, int fd) |
| 1468 | { |
| 1469 | if (fa->addr) |
| 1470 | return; |
| 1471 | |
| 1472 | procfile *ff = procfile_open("/proc/kallsyms", " \t:", PROCFILE_FLAG_DEFAULT); |
| 1473 | if (!ff) |
| 1474 | return; |
| 1475 | |
| 1476 | ff = procfile_readall(ff); |
| 1477 | if (!ff) |
| 1478 | return; |
| 1479 | |
| 1480 | fa->hash = simple_hash(fa->function); |
| 1481 | |
| 1482 | size_t lines = procfile_lines(ff), l; |
| 1483 | for (l = 0; l < lines; l++) { |
| 1484 | char *fcnt = procfile_lineword(ff, l, 2); |
| 1485 | uint32_t hash = simple_hash(fcnt); |
| 1486 | if (fa->hash == hash && !strcmp(fcnt, fa->function)) { |
| 1487 | char *type = procfile_lineword(ff, l, 1); |
| 1488 | fa->type = type[0]; |
| 1489 | // Only text symbols (T=global, t=static, W=weak global, w=weak local) are probeable |
| 1490 | if (fa->type != 'T' && fa->type != 't' && fa->type != 'W' && fa->type != 'w') |
| 1491 | continue; |
| 1492 | if (fd > 0) { |
| 1493 | char addr[128]; |
| 1494 | snprintf(addr, 127, "0x%s", procfile_lineword(ff, l, 0)); |
| 1495 | fa->addr = (unsigned long)strtoul(addr, NULL, 16); |
| 1496 | uint32_t key = 0; |
| 1497 | bpf_map_update_elem(fd, &key, &fa->addr, BPF_ANY); |
| 1498 | } else |
| 1499 | fa->addr = 1; |
| 1500 | break; |
| 1501 | } |
| 1502 | } |
| 1503 | |
| 1504 | procfile_close(ff); |
| 1505 | } |
| 1506 | |
| 1507 | //---------------------------------------------------------------------------------------------------------------------- |
| 1508 | |
| 1509 | /** |
| 1510 | * Fill Algorithms |
| 1511 | * |
| 1512 | * Set one unique dimension for all vector position. |
| 1513 | * |
| 1514 | * @param algorithms the output vector |
| 1515 | * @param length number of elements of algorithms vector |
| 1516 | * @param algorithm algorithm used on charts. |
| 1517 | */ |
| 1518 | void ebpf_fill_algorithms(int *algorithms, size_t length, int algorithm) |
| 1519 | { |
| 1520 | size_t i; |
| 1521 | for (i = 0; i < length; i++) { |
| 1522 | algorithms[i] = algorithm; |
| 1523 | } |
| 1524 | } |
| 1525 | |
| 1526 | /** |
| 1527 | * Fill Histogram dimension |
| 1528 | * |
| 1529 | * Fill the histogram dimension with the specified ranges |
| 1530 | */ |
| 1531 | char **ebpf_fill_histogram_dimension(size_t maximum) |
| 1532 | { |
| 1533 | char *dimensions[] = {"us", "ms", "s"}; |
| 1534 | int previous_dim = 0, current_dim = 0; |
| 1535 | uint32_t previous_level = 1000, current_level = 1000; |
| 1536 | uint32_t previous_divisor = 1, current_divisor = 1; |
| 1537 | uint32_t current = 1, previous = 0; |
| 1538 | uint32_t selector; |
| 1539 | char **out = callocz(maximum, sizeof(char *)); |
| 1540 | char range[128]; |
| 1541 | size_t end = maximum - 1; |
| 1542 | for (selector = 0; selector < end; selector++) { |
| 1543 | snprintf( |
| 1544 | range, |
| 1545 | 127, |
| 1546 | "%u%s->%u%s", |
| 1547 | previous / previous_divisor, |
| 1548 | dimensions[previous_dim], |
| 1549 | current / current_divisor, |
| 1550 | dimensions[current_dim]); |
| 1551 | out[selector] = strdupz(range); |
| 1552 | previous = current; |
| 1553 | current <<= 1; |
| 1554 | |
| 1555 | if (previous_dim != 2 && previous > previous_level) { |
| 1556 | previous_dim++; |
| 1557 | |
| 1558 | previous_divisor *= 1000; |
| 1559 | previous_level *= 1000; |
| 1560 | } |
| 1561 | |
| 1562 | if (current_dim != 2 && current > current_level) { |
| 1563 | current_dim++; |
| 1564 | |
| 1565 | current_divisor *= 1000; |
| 1566 | current_level *= 1000; |
| 1567 | } |
| 1568 | } |
| 1569 | snprintf(range, 127, "%u%s->+Inf", previous / previous_divisor, dimensions[previous_dim]); |
| 1570 | out[selector] = strdupz(range); |
| 1571 | |
| 1572 | return out; |
| 1573 | } |
| 1574 | |
| 1575 | /** |
| 1576 | * Histogram dimension cleanup |
| 1577 | * |
| 1578 | * Cleanup dimensions allocated with function ebpf_fill_histogram_dimension |
| 1579 | * |
| 1580 | * @param ptr |
| 1581 | * @param length |
| 1582 | */ |
| 1583 | void ebpf_histogram_dimension_cleanup(char **ptr, size_t length) |
| 1584 | { |
| 1585 | size_t i; |
| 1586 | for (i = 0; i < length; i++) { |
| 1587 | freez(ptr[i]); |
| 1588 | } |
| 1589 | freez(ptr); |
| 1590 | } |
| 1591 | |
| 1592 | //---------------------------------------------------------------------------------------------------------------------- |
| 1593 | |
| 1594 | /** |
| 1595 | * Open tracepoint path |
| 1596 | * |
| 1597 | * @param filename pointer to store the path |
| 1598 | * @param length file length |
| 1599 | * @param subsys is the name of your subsystem. |
| 1600 | * @param eventname is the name of the event to trace. |
| 1601 | * @param flags flags used with syscall open |
| 1602 | * |
| 1603 | * @return it returns a positive value on success and a negative otherwise. |
| 1604 | */ |
| 1605 | static inline int |
| 1606 | ebpf_open_tracepoint_path(char *filename, size_t length, const char *subsys, const char *eventname, int flags) |
| 1607 | { |
| 1608 | snprintfz(filename, length, "%s/events/%s/%s/enable", NETDATA_DEBUGFS, subsys, eventname); |
| 1609 | return open(filename, flags | O_CLOEXEC, 0); |
| 1610 | } |
| 1611 | |
| 1612 | /** |
| 1613 | * Is tracepoint enabled |
| 1614 | * |
| 1615 | * Check whether the tracepoint is enabled. |
| 1616 | * |
| 1617 | * @param subsys is the name of your subsystem. |
| 1618 | * @param eventname is the name of the event to trace. |
| 1619 | * |
| 1620 | * @return it returns 1 when it is enabled, 0 when it is disabled and -1 on error. |
| 1621 | */ |
| 1622 | int ebpf_is_tracepoint_enabled(const char *subsys, const char *eventname) |
| 1623 | { |
| 1624 | char text[FILENAME_MAX + 1]; |
| 1625 | int fd = ebpf_open_tracepoint_path(text, FILENAME_MAX, subsys, eventname, O_RDONLY); |
| 1626 | if (fd < 0) { |
| 1627 | return -1; |
| 1628 | } |
| 1629 | |
| 1630 | ssize_t length = read(fd, text, 1); |
| 1631 | if (length != 1) { |
| 1632 | close(fd); |
| 1633 | return -1; |
| 1634 | } |
| 1635 | close(fd); |
| 1636 | |
| 1637 | return (text[0] == '1') ? CONFIG_BOOLEAN_YES : CONFIG_BOOLEAN_NO; |
| 1638 | } |
| 1639 | |
| 1640 | /** |
| 1641 | * Change Tracing values |
| 1642 | * |
| 1643 | * Change value for specific tracepoint enabling or disabling it according value given. |
| 1644 | * |
| 1645 | * @param subsys is the name of your subsystem. |
| 1646 | * @param eventname is the name of the event to trace. |
| 1647 | * @param value a value to enable (1) or disable (0) a tracepoint. |
| 1648 | * |
| 1649 | * @return It returns 0 on success and -1 otherwise |
| 1650 | */ |
| 1651 | static int ebpf_change_tracing_values(const char *subsys, const char *eventname, const char *value) |
| 1652 | { |
| 1653 | if (strcmp("0", value) && strcmp("1", value)) { |
| 1654 | netdata_log_error("Invalid value given to either enable or disable a tracepoint."); |
| 1655 | return -1; |
| 1656 | } |
| 1657 | |
| 1658 | char filename[1024]; |
| 1659 | int fd = ebpf_open_tracepoint_path(filename, 1023, subsys, eventname, O_WRONLY); |
| 1660 | if (fd < 0) { |
| 1661 | return -1; |
| 1662 | } |
| 1663 | |
| 1664 | ssize_t written = write(fd, value, strlen(value)); |
| 1665 | if (written < 0) { |
| 1666 | close(fd); |
| 1667 | return -1; |
| 1668 | } |
| 1669 | |
| 1670 | close(fd); |
| 1671 | return 0; |
| 1672 | } |
| 1673 | |
| 1674 | /** |
| 1675 | * Enable tracing values |
| 1676 | * |
| 1677 | * Enable a tracepoint on a system |
| 1678 | * |
| 1679 | * @param subsys is the name of your subsystem. |
| 1680 | * @param eventname is the name of the event to trace. |
| 1681 | * |
| 1682 | * @return It returns 0 on success and -1 otherwise |
| 1683 | */ |
| 1684 | int ebpf_enable_tracing_values(const char *subsys, const char *eventname) |
| 1685 | { |
| 1686 | return ebpf_change_tracing_values(subsys, eventname, "1"); |
| 1687 | } |
| 1688 | |
| 1689 | /** |
| 1690 | * Disable tracing values |
| 1691 | * |
| 1692 | * Disable tracing points enabled by collector |
| 1693 | * |
| 1694 | * @param subsys is the name of your subsystem. |
| 1695 | * @param eventname is the name of the event to trace. |
| 1696 | * |
| 1697 | * @return It returns 0 on success and -1 otherwise |
| 1698 | */ |
| 1699 | int ebpf_disable_tracing_values(const char *subsys, const char *eventname) |
| 1700 | { |
| 1701 | return ebpf_change_tracing_values(subsys, eventname, "0"); |
| 1702 | } |
| 1703 | |
| 1704 | /** |
| 1705 | * Select PC prefix |
| 1706 | * |
| 1707 | * Identify the prefix to run on PC architecture. |
| 1708 | * |
| 1709 | * @return It returns 32 or 64 according to host arch. |
| 1710 | */ |
| 1711 | static uint32_t ebpf_select_pc_prefix() |
| 1712 | { |
| 1713 | #if SIZE_OF_VOID_P == 4 |
| 1714 | return 32; |
| 1715 | #else |
| 1716 | return 64; |
| 1717 | #endif |
| 1718 | } |
| 1719 | |
| 1720 | /** |
| 1721 | * Select Host Prefix |
| 1722 | * |
| 1723 | * Select prefix to syscall when host is running a kernel newer than 4.17.0 |
| 1724 | * |
| 1725 | * @param output the vector to store data. |
| 1726 | * @param length length of output vector. |
| 1727 | * @param syscall the syscall that prefix will be attached; |
| 1728 | * @param kver the current kernel version in format MAJOR*65536 + MINOR*256 + PATCH |
| 1729 | */ |
| 1730 | void ebpf_select_host_prefix(char *output, size_t length, char *syscall, int kver) |
| 1731 | { |
| 1732 | if (kver < NETDATA_EBPF_KERNEL_4_17) |
| 1733 | snprintfz(output, length, "sys_%s", syscall); |
| 1734 | else { |
| 1735 | uint32_t arch = ebpf_select_pc_prefix(); |
| 1736 | // Prefix selected according https://www.kernel.org/doc/html/latest/process/adding-syscalls.html |
| 1737 | char *prefix = (arch == 32) ? "__ia32" : "__x64"; |
| 1738 | snprintfz(output, length, "%s_sys_%s", prefix, syscall); |
| 1739 | } |
| 1740 | } |