| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "ebpf.h" |
| 4 | #include "ebpf_socket.h" |
| 5 | #include "ebpf_apps.h" |
| 6 | |
| 7 | // ---------------------------------------------------------------------------- |
| 8 | // ARAL vectors used to speed up processing |
| 9 | ARAL *ebpf_aral_apps_pid_stat = NULL; |
| 10 | |
| 11 | /** |
| 12 | * eBPF ARAL Init |
| 13 | * |
| 14 | * Initiallize array allocator that will be used when integration with apps and ebpf is created. |
| 15 | */ |
| 16 | void ebpf_aral_init(void) |
| 17 | { |
| 18 | size_t max_elements = NETDATA_EBPF_ALLOC_MAX_PID; |
| 19 | if (max_elements < NETDATA_EBPF_ALLOC_MIN_ELEMENTS) { |
| 20 | netdata_log_error( |
| 21 | "Number of elements given is too small, adjusting it for %d", NETDATA_EBPF_ALLOC_MIN_ELEMENTS); |
| 22 | max_elements = NETDATA_EBPF_ALLOC_MIN_ELEMENTS; |
| 23 | } |
| 24 | |
| 25 | #ifdef NETDATA_DEV_MODE |
| 26 | netdata_log_info("Plugin is using ARAL with values %d", NETDATA_EBPF_ALLOC_MAX_PID); |
| 27 | #endif |
| 28 | } |
| 29 | |
| 30 | // ---------------------------------------------------------------------------- |
| 31 | // internal flags |
| 32 | // handled in code (automatically set) |
| 33 | |
| 34 | static int proc_pid_cmdline_is_needed = 0; // 1 when we need to read /proc/cmdline |
| 35 | |
| 36 | /***************************************************************** |
| 37 | * |
| 38 | * FUNCTIONS USED TO READ HASH TABLES |
| 39 | * |
| 40 | *****************************************************************/ |
| 41 | |
| 42 | /** |
| 43 | * Read statistic hash table. |
| 44 | * |
| 45 | * @param ep the output structure. |
| 46 | * @param fd the file descriptor mapped from kernel ring. |
| 47 | * @param pid the index used to select the data. |
| 48 | * @param bpf_map_lookup_elem a pointer for the function used to read data. |
| 49 | * |
| 50 | * @return It returns 0 when the data was copied and -1 otherwise |
| 51 | */ |
| 52 | int ebpf_read_hash_table(void *ep, int fd, uint32_t pid) |
| 53 | { |
| 54 | if (!ep) |
| 55 | return -1; |
| 56 | |
| 57 | if (!bpf_map_lookup_elem(fd, &pid, ep)) |
| 58 | return 0; |
| 59 | |
| 60 | return -1; |
| 61 | } |
| 62 | |
| 63 | /***************************************************************** |
| 64 | * |
| 65 | * FUNCTIONS CALLED FROM COLLECTORS |
| 66 | * |
| 67 | *****************************************************************/ |
| 68 | |
| 69 | /** |
| 70 | * Reset the target values |
| 71 | * |
| 72 | * @param root the pointer to the chain that will be reset. |
| 73 | * |
| 74 | * @return it returns the number of structures that was reset. |
| 75 | */ |
| 76 | size_t zero_all_targets(struct ebpf_target *root) |
| 77 | { |
| 78 | struct ebpf_target *w; |
| 79 | size_t count = 0; |
| 80 | |
| 81 | for (w = root; w; w = w->next) { |
| 82 | if (ebpf_plugin_stop()) |
| 83 | break; |
| 84 | |
| 85 | count++; |
| 86 | |
| 87 | if (unlikely(w->root_pid)) { |
| 88 | struct ebpf_pid_on_target *pid_on_target = w->root_pid; |
| 89 | |
| 90 | while (pid_on_target) { |
| 91 | struct ebpf_pid_on_target *pid_on_target_to_free = pid_on_target; |
| 92 | pid_on_target = pid_on_target->next; |
| 93 | freez(pid_on_target_to_free); |
| 94 | } |
| 95 | |
| 96 | w->root_pid = NULL; |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | return count; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Clean the allocated structures |
| 105 | * |
| 106 | * @param agrt the pointer to be cleaned. |
| 107 | */ |
| 108 | void clean_apps_groups_target(struct ebpf_target *agrt) |
| 109 | { |
| 110 | struct ebpf_target *current_target; |
| 111 | while (agrt) { |
| 112 | if (ebpf_plugin_stop()) |
| 113 | break; |
| 114 | |
| 115 | current_target = agrt; |
| 116 | agrt = current_target->target; |
| 117 | |
| 118 | freez(current_target); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Find or create a new target |
| 124 | * there are targets that are just aggregated to other target (the second argument) |
| 125 | * |
| 126 | * @param id |
| 127 | * @param target |
| 128 | * @param name |
| 129 | * |
| 130 | * @return It returns the target on success and NULL otherwise |
| 131 | */ |
| 132 | struct ebpf_target * |
| 133 | get_apps_groups_target(struct ebpf_target **agrt, const char *id, struct ebpf_target *target, const char *name) |
| 134 | { |
| 135 | int tdebug = 0, thidden = target ? target->hidden : 0, ends_with = 0; |
| 136 | const char *nid = id; |
| 137 | |
| 138 | // extract the options |
| 139 | while (nid[0] == '-' || nid[0] == '+' || nid[0] == '*') { |
| 140 | if (nid[0] == '-') |
| 141 | thidden = 1; |
| 142 | if (nid[0] == '+') |
| 143 | tdebug = 1; |
| 144 | if (nid[0] == '*') |
| 145 | ends_with = 1; |
| 146 | nid++; |
| 147 | } |
| 148 | uint32_t hash = simple_hash(id); |
| 149 | |
| 150 | // find if it already exists |
| 151 | struct ebpf_target *w, *last = *agrt; |
| 152 | for (w = *agrt; w; w = w->next) { |
| 153 | if (ebpf_plugin_stop()) |
| 154 | break; |
| 155 | |
| 156 | if (w->idhash == hash && strncmp(nid, w->id, EBPF_MAX_NAME) == 0) |
| 157 | return w; |
| 158 | |
| 159 | last = w; |
| 160 | } |
| 161 | |
| 162 | // find an existing target |
| 163 | if (unlikely(!target)) { |
| 164 | while (*name == '-') { |
| 165 | if (*name == '-') |
| 166 | thidden = 1; |
| 167 | name++; |
| 168 | } |
| 169 | |
| 170 | for (target = *agrt; target != NULL; target = target->next) { |
| 171 | if (!target->target && strcmp(name, target->name) == 0) |
| 172 | break; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | if (target && target->target) |
| 177 | fatal( |
| 178 | "Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'", |
| 179 | id, |
| 180 | target->id, |
| 181 | target->target->id); |
| 182 | |
| 183 | w = callocz(1, sizeof(struct ebpf_target)); |
| 184 | strncpyz(w->id, nid, EBPF_MAX_NAME); |
| 185 | w->idhash = simple_hash(w->id); |
| 186 | |
| 187 | if (unlikely(!target)) |
| 188 | // copy the name |
| 189 | strncpyz(w->name, name, EBPF_MAX_NAME); |
| 190 | else |
| 191 | // copy the id |
| 192 | strncpyz(w->name, nid, EBPF_MAX_NAME); |
| 193 | |
| 194 | strncpyz(w->clean_name, w->name, EBPF_MAX_NAME); |
| 195 | netdata_fix_chart_name(w->clean_name); |
| 196 | for (char *d = w->clean_name; *d; d++) { |
| 197 | if (*d == '.') |
| 198 | *d = '_'; |
| 199 | } |
| 200 | |
| 201 | strncpyz(w->compare, nid, EBPF_MAX_COMPARE_NAME); |
| 202 | size_t len = strlen(w->compare); |
| 203 | if (w->compare[len - 1] == '*') { |
| 204 | w->compare[len - 1] = '\0'; |
| 205 | w->starts_with = 1; |
| 206 | } |
| 207 | w->ends_with = ends_with; |
| 208 | |
| 209 | if (w->starts_with && w->ends_with) |
| 210 | proc_pid_cmdline_is_needed = 1; |
| 211 | |
| 212 | w->comparehash = simple_hash(w->compare); |
| 213 | w->comparelen = strlen(w->compare); |
| 214 | |
| 215 | w->hidden = thidden; |
| 216 | #ifdef NETDATA_INTERNAL_CHECKS |
| 217 | w->debug_enabled = tdebug; |
| 218 | #else |
| 219 | if (tdebug) |
| 220 | fprintf(stderr, "apps.plugin has been compiled without debugging\n"); |
| 221 | #endif |
| 222 | w->target = target; |
| 223 | |
| 224 | // append it, to maintain the order in apps_groups.conf |
| 225 | if (last) |
| 226 | last->next = w; |
| 227 | else |
| 228 | *agrt = w; |
| 229 | |
| 230 | return w; |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * Read the apps_groups.conf file |
| 235 | * |
| 236 | * @param agrt a pointer to apps_group_root_target |
| 237 | * @param path the directory to search apps_%s.conf |
| 238 | * @param file the word to complement the file name. |
| 239 | * |
| 240 | * @return It returns 0 on success and -1 otherwise |
| 241 | */ |
| 242 | int ebpf_read_apps_groups_conf(struct ebpf_target **agdt, struct ebpf_target **agrt, const char *path, const char *file) |
| 243 | { |
| 244 | char filename[FILENAME_MAX + 1]; |
| 245 | |
| 246 | snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", path, file); |
| 247 | |
| 248 | // ---------------------------------------- |
| 249 | |
| 250 | procfile *ff = procfile_open_no_log(filename, " :\t", PROCFILE_FLAG_DEFAULT); |
| 251 | if (!ff) |
| 252 | return -1; |
| 253 | |
| 254 | procfile_set_quotes(ff, "'\""); |
| 255 | |
| 256 | ff = procfile_readall(ff); |
| 257 | if (!ff) |
| 258 | return -1; |
| 259 | |
| 260 | size_t line, lines = procfile_lines(ff); |
| 261 | |
| 262 | for (line = 0; line < lines; line++) { |
| 263 | size_t word, words = procfile_linewords(ff, line); |
| 264 | if (!words) |
| 265 | continue; |
| 266 | |
| 267 | char *name = procfile_lineword(ff, line, 0); |
| 268 | if (!name || !*name) |
| 269 | continue; |
| 270 | |
| 271 | // find a possibly existing target |
| 272 | struct ebpf_target *w = NULL; |
| 273 | |
| 274 | // loop through all words, skipping the first one (the name) |
| 275 | for (word = 0; word < words; word++) { |
| 276 | char *s = procfile_lineword(ff, line, word); |
| 277 | if (!s || !*s) |
| 278 | continue; |
| 279 | if (*s == '#') |
| 280 | break; |
| 281 | |
| 282 | // is this the first word? skip it |
| 283 | if (s == name) |
| 284 | continue; |
| 285 | |
| 286 | // add this target |
| 287 | struct ebpf_target *n = get_apps_groups_target(agrt, s, w, name); |
| 288 | if (!n) { |
| 289 | netdata_log_error("Cannot create target '%s' (line %zu, word %zu)", s, line, word); |
| 290 | continue; |
| 291 | } |
| 292 | |
| 293 | // just some optimization |
| 294 | // to avoid searching for a target for each process |
| 295 | if (!w) |
| 296 | w = n->target ? n->target : n; |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | procfile_close(ff); |
| 301 | |
| 302 | *agdt = get_apps_groups_target(agrt, "p+!o@w#e$i^r&7*5(-i)l-o_", NULL, "other"); // match nothing |
| 303 | if (!*agdt) |
| 304 | fatal("Cannot create default target"); |
| 305 | |
| 306 | struct ebpf_target *ptr = *agdt; |
| 307 | if (ptr->target) |
| 308 | *agdt = ptr->target; |
| 309 | |
| 310 | return 0; |
| 311 | } |
| 312 | |
| 313 | // the minimum PID of the system |
| 314 | // this is also the pid of the init process |
| 315 | #define INIT_PID 1 |
| 316 | |
| 317 | // ---------------------------------------------------------------------------- |
| 318 | // string lengths |
| 319 | |
| 320 | #define MAX_CMDLINE 16384 |
| 321 | |
| 322 | Pvoid_t ebpf_pid_judyL = NULL; |
| 323 | SPINLOCK ebpf_pid_spinlock = SPINLOCK_INITIALIZER; |
| 324 | |
| 325 | void ebpf_pid_del(pid_t pid) |
| 326 | { |
| 327 | spinlock_lock(&ebpf_pid_spinlock); |
| 328 | (void)JudyLDel(&ebpf_pid_judyL, (Word_t)pid, PJE0); |
| 329 | spinlock_unlock(&ebpf_pid_spinlock); |
| 330 | } |
| 331 | |
| 332 | static ebpf_pid_data_t *ebpf_find_pid_data_unsafe(pid_t pid) |
| 333 | { |
| 334 | ebpf_pid_data_t *pid_data = NULL; |
| 335 | Pvoid_t *Pvalue = JudyLGet(ebpf_pid_judyL, (Word_t)pid, PJE0); |
| 336 | if (Pvalue) |
| 337 | pid_data = *Pvalue; |
| 338 | return pid_data; |
| 339 | } |
| 340 | |
| 341 | ebpf_pid_data_t *ebpf_find_pid_data(pid_t pid) |
| 342 | { |
| 343 | spinlock_lock(&ebpf_pid_spinlock); |
| 344 | ebpf_pid_data_t *pid_data = ebpf_find_pid_data_unsafe(pid); |
| 345 | spinlock_unlock(&ebpf_pid_spinlock); |
| 346 | return pid_data; |
| 347 | } |
| 348 | |
| 349 | ebpf_pid_data_t *ebpf_find_or_create_pid_data(pid_t pid) |
| 350 | { |
| 351 | spinlock_lock(&ebpf_pid_spinlock); |
| 352 | ebpf_pid_data_t *pid_data = ebpf_find_pid_data_unsafe(pid); |
| 353 | if (!pid_data) { |
| 354 | Pvoid_t *Pvalue = JudyLIns(&ebpf_pid_judyL, (Word_t)pid, PJE0); |
| 355 | internal_fatal(!Pvalue || Pvalue == PJERR, "EBPF: pid judy array"); |
| 356 | if (likely(!*Pvalue)) |
| 357 | *Pvalue = pid_data = callocz(1, sizeof(*pid_data)); |
| 358 | else |
| 359 | pid_data = *Pvalue; |
| 360 | } |
| 361 | spinlock_unlock(&ebpf_pid_spinlock); |
| 362 | |
| 363 | return pid_data; |
| 364 | } |
| 365 | |
| 366 | ebpf_pid_data_t *ebpf_pids_link_list = NULL; // global list of all processes running |
| 367 | |
| 368 | size_t ebpf_all_pids_count = 0; // the number of processes running read from /proc |
| 369 | size_t ebpf_hash_table_pids_count = 0; // the number of tasks in our hash tables |
| 370 | |
| 371 | struct ebpf_target *apps_groups_default_target = NULL, // the default target |
| 372 | *apps_groups_root_target = NULL, // apps_groups.conf defined |
| 373 | *users_root_target = NULL, // users |
| 374 | *groups_root_target = NULL; // user groups |
| 375 | |
| 376 | size_t apps_groups_targets_count = 0; // # of apps_groups.conf targets |
| 377 | |
| 378 | static int ebpf_pid_map_fds[NETDATA_EBPF_PIDS_END_IDX]; |
| 379 | |
| 380 | void ebpf_reset_pid_map_fds(void) |
| 381 | { |
| 382 | memset(ebpf_pid_map_fds, -1, sizeof(ebpf_pid_map_fds)); |
| 383 | } |
| 384 | |
| 385 | void ebpf_set_pid_map_fd(int idx, int fd) |
| 386 | { |
| 387 | if (unlikely(idx < 0 || idx >= NETDATA_EBPF_PIDS_END_IDX)) |
| 388 | return; |
| 389 | |
| 390 | ebpf_pid_map_fds[idx] = fd; |
| 391 | } |
| 392 | |
| 393 | int ebpf_get_pid_map_fd(int idx) |
| 394 | { |
| 395 | if (unlikely(idx < 0 || idx >= NETDATA_EBPF_PIDS_END_IDX)) |
| 396 | return -1; |
| 397 | |
| 398 | return ebpf_pid_map_fds[idx]; |
| 399 | } |
| 400 | |
| 401 | // ---------------------------------------------------------------------------- |
| 402 | // internal counters |
| 403 | |
| 404 | static size_t targets_assignment_counter = 0; |
| 405 | |
| 406 | // ---------------------------------------------------------------------------- |
| 407 | // debugging |
| 408 | |
| 409 | // log each problem once per process |
| 410 | // log flood protection flags (log_thrown) |
| 411 | #define PID_LOG_IO 0x00000001 |
| 412 | #define PID_LOG_STATUS 0x00000002 |
| 413 | #define PID_LOG_CMDLINE 0x00000004 |
| 414 | #define PID_LOG_FDS 0x00000008 |
| 415 | #define PID_LOG_STAT 0x00000010 |
| 416 | |
| 417 | int debug_enabled = 0; |
| 418 | |
| 419 | #ifdef NETDATA_INTERNAL_CHECKS |
| 420 | |
| 421 | #define debug_log(fmt, args...) \ |
| 422 | do { \ |
| 423 | if (unlikely(debug_enabled)) \ |
| 424 | debug_log_int(fmt, ##args); \ |
| 425 | } while (0) |
| 426 | |
| 427 | #else |
| 428 | |
| 429 | static inline void debug_log_dummy(void) |
| 430 | { |
| 431 | } |
| 432 | #define debug_log(fmt, args...) debug_log_dummy() |
| 433 | |
| 434 | #endif |
| 435 | |
| 436 | /** |
| 437 | * Assign the PID to a target. |
| 438 | * |
| 439 | * @param p the pid_stat structure to assign for a target. |
| 440 | */ |
| 441 | static inline void assign_target_to_pid(ebpf_pid_data_t *p) |
| 442 | { |
| 443 | targets_assignment_counter++; |
| 444 | |
| 445 | uint32_t hash = simple_hash(p->comm); |
| 446 | size_t pclen = strlen(p->comm); |
| 447 | |
| 448 | struct ebpf_target *w; |
| 449 | bool assigned = false; |
| 450 | for (w = apps_groups_root_target; w; w = w->next) { |
| 451 | if (ebpf_plugin_stop()) |
| 452 | break; |
| 453 | |
| 454 | // if(debug_enabled || (p->target && p->target->debug_enabled)) debug_log_int("\t\tcomparing '%s' with '%s'", w->compare, p->comm); |
| 455 | |
| 456 | // find it - 4 cases: |
| 457 | // 1. the target is not a pattern |
| 458 | // 2. the target has the prefix |
| 459 | // 3. the target has the suffix |
| 460 | // 4. the target is something inside cmdline |
| 461 | |
| 462 | if (unlikely( |
| 463 | ((!w->starts_with && !w->ends_with && w->comparehash == hash && !strcmp(w->compare, p->comm)) || |
| 464 | (w->starts_with && !w->ends_with && !strncmp(w->compare, p->comm, w->comparelen)) || |
| 465 | (!w->starts_with && w->ends_with && pclen >= w->comparelen && |
| 466 | !strcmp(w->compare, &p->comm[pclen - w->comparelen])) || |
| 467 | (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && p->cmdline && |
| 468 | strstr(p->cmdline, w->compare))))) { |
| 469 | if (w->target) |
| 470 | p->target = w->target; |
| 471 | else |
| 472 | p->target = w; |
| 473 | |
| 474 | if (debug_enabled || (p->target && p->target->debug_enabled)) |
| 475 | debug_log_int("%s linked to target %s", p->comm, p->target->name); |
| 476 | |
| 477 | w->processes++; |
| 478 | assigned = true; |
| 479 | |
| 480 | break; |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | if (!assigned) { |
| 485 | apps_groups_default_target->processes++; |
| 486 | p->target = apps_groups_default_target; |
| 487 | } |
| 488 | } |
| 489 | |
| 490 | // ---------------------------------------------------------------------------- |
| 491 | // update pids from proc |
| 492 | |
| 493 | /** |
| 494 | * Read cmd line from /proc/PID/cmdline |
| 495 | * |
| 496 | * @param p the ebpf_pid_data structure. |
| 497 | * |
| 498 | * @return It returns 1 on success and 0 otherwise. |
| 499 | */ |
| 500 | static inline int read_proc_pid_cmdline(ebpf_pid_data_t *p, char *cmdline) |
| 501 | { |
| 502 | char filename[FILENAME_MAX + 1]; |
| 503 | snprintfz(filename, FILENAME_MAX, "%s/proc/%u/cmdline", netdata_configured_host_prefix, p->pid); |
| 504 | |
| 505 | int ret = 0; |
| 506 | |
| 507 | int fd = open(filename, procfile_open_flags, 0666); |
| 508 | if (unlikely(fd == -1)) |
| 509 | goto cleanup; |
| 510 | |
| 511 | ssize_t i, bytes = read(fd, cmdline, MAX_CMDLINE); |
| 512 | close(fd); |
| 513 | |
| 514 | if (unlikely(bytes < 0)) |
| 515 | goto cleanup; |
| 516 | |
| 517 | cmdline[bytes] = '\0'; |
| 518 | for (i = 0; i < bytes; i++) { |
| 519 | if (unlikely(!cmdline[i])) |
| 520 | cmdline[i] = ' '; |
| 521 | } |
| 522 | |
| 523 | ret = 1; |
| 524 | |
| 525 | cleanup: |
| 526 | if (p->cmdline) |
| 527 | p->cmdline[0] = '\0'; |
| 528 | |
| 529 | return ret; |
| 530 | } |
| 531 | |
| 532 | /** |
| 533 | * Read information from /proc/PID/stat and /proc/PID/cmdline |
| 534 | * Assign target to pid |
| 535 | * |
| 536 | * @param p the pid stat structure to store the data. |
| 537 | */ |
| 538 | static inline int read_proc_pid_stat(ebpf_pid_data_t *p) |
| 539 | { |
| 540 | procfile *ff; |
| 541 | |
| 542 | char filename[FILENAME_MAX + 1]; |
| 543 | int ret = 0; |
| 544 | snprintfz(filename, FILENAME_MAX, "%s/proc/%u/stat", netdata_configured_host_prefix, p->pid); |
| 545 | |
| 546 | struct stat statbuf; |
| 547 | if (stat(filename, &statbuf)) { |
| 548 | // PID ended before we stat the file |
| 549 | p->has_proc_file = 0; |
| 550 | return 0; |
| 551 | } |
| 552 | |
| 553 | ff = procfile_open(filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); |
| 554 | if (unlikely(!ff)) |
| 555 | goto cleanup_pid_stat; |
| 556 | |
| 557 | procfile_set_open_close(ff, "(", ")"); |
| 558 | |
| 559 | ff = procfile_readall(ff); |
| 560 | if (unlikely(!ff)) |
| 561 | goto cleanup_pid_stat; |
| 562 | |
| 563 | char *comm = procfile_lineword(ff, 0, 1); |
| 564 | int32_t ppid = (int32_t)str2pid_t(procfile_lineword(ff, 0, 3)); |
| 565 | |
| 566 | if (p->ppid == (uint32_t)ppid && p->target) |
| 567 | goto without_cmdline_target; |
| 568 | |
| 569 | p->ppid = ppid; |
| 570 | |
| 571 | char cmdline[MAX_CMDLINE + 1]; |
| 572 | if (read_proc_pid_cmdline(p, cmdline)) |
| 573 | p->cmdline = cmdline; /* point at filled buffer so assign_target_to_pid can match *pattern* rules */ |
| 574 | if (strcmp(p->comm, comm) != 0) { |
| 575 | if (unlikely(debug_enabled)) { |
| 576 | if (p->comm[0]) |
| 577 | debug_log("\tpid %d (%s) changed name to '%s'", p->pid, p->comm, comm); |
| 578 | else |
| 579 | debug_log("\tJust added %d (%s)", p->pid, comm); |
| 580 | } |
| 581 | |
| 582 | strncpyz(p->comm, comm, EBPF_MAX_COMPARE_NAME); |
| 583 | } |
| 584 | |
| 585 | if (!p->target) |
| 586 | assign_target_to_pid(p); |
| 587 | |
| 588 | p->cmdline = NULL; /* cmdline is stack-local; do not let it escape this frame */ |
| 589 | |
| 590 | if (unlikely(debug_enabled || (p->target && p->target->debug_enabled))) |
| 591 | debug_log_int( |
| 592 | "READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s'", |
| 593 | netdata_configured_host_prefix, |
| 594 | p->pid, |
| 595 | p->comm, |
| 596 | (p->target) ? p->target->name : "UNSET"); |
| 597 | |
| 598 | without_cmdline_target: |
| 599 | p->has_proc_file = 1; |
| 600 | p->not_updated = 0; |
| 601 | ret = 1; |
| 602 | cleanup_pid_stat: |
| 603 | procfile_close(ff); |
| 604 | |
| 605 | return ret; |
| 606 | } |
| 607 | |
| 608 | /** |
| 609 | * Collect data for PID |
| 610 | * |
| 611 | * @param pid the current pid that we are working |
| 612 | * |
| 613 | * @return It returns 1 on success and 0 otherwise |
| 614 | */ |
| 615 | static inline int ebpf_collect_data_for_pid(pid_t pid) |
| 616 | { |
| 617 | if (unlikely(pid < 0 || pid > pid_max)) { |
| 618 | netdata_log_error("Invalid pid %d read (expected %d to %d). Ignoring process.", pid, 0, pid_max); |
| 619 | return 0; |
| 620 | } |
| 621 | |
| 622 | ebpf_pid_data_t *p = ebpf_get_pid_data((uint32_t)pid, 0, NULL, NETDATA_EBPF_PIDS_PROC_FILE); |
| 623 | read_proc_pid_stat(p); |
| 624 | |
| 625 | // check its parent pid |
| 626 | if (unlikely(p->ppid > (uint32_t)pid_max)) { |
| 627 | netdata_log_error("Pid %d (command '%s') states invalid parent pid %u. Using 0.", pid, p->comm, p->ppid); |
| 628 | p->ppid = 0; |
| 629 | } |
| 630 | |
| 631 | return 1; |
| 632 | } |
| 633 | |
| 634 | /** |
| 635 | * Fill link list of parents with children PIDs |
| 636 | */ |
| 637 | static inline void link_all_processes_to_their_parents(void) |
| 638 | { |
| 639 | ebpf_pid_data_t *p, *pp; |
| 640 | |
| 641 | // link all children to their parents |
| 642 | // and update children count on parents |
| 643 | for (p = ebpf_pids_link_list; p; p = p->next) { |
| 644 | // for each process found |
| 645 | |
| 646 | // Reset before the stop check: breaking early must not leave stale parent |
| 647 | // pointers that apply_apps_groups_targets_inheritance() would dereference. |
| 648 | p->parent = NULL; |
| 649 | |
| 650 | if (ebpf_plugin_stop()) |
| 651 | break; |
| 652 | |
| 653 | if (unlikely(!p->ppid)) { |
| 654 | continue; |
| 655 | } |
| 656 | |
| 657 | pp = ebpf_find_pid_data(p->ppid); |
| 658 | if (likely(pp && pp->pid)) { |
| 659 | p->parent = pp; |
| 660 | pp->children_count++; |
| 661 | |
| 662 | if (unlikely(debug_enabled || (p->target && p->target->debug_enabled))) |
| 663 | debug_log_int( |
| 664 | "child %d (%s) on target '%s' has parent %d (%s).", |
| 665 | p->pid, |
| 666 | p->comm, |
| 667 | (p->target) ? p->target->name : "UNSET", |
| 668 | pp->pid, |
| 669 | pp->comm); |
| 670 | } else { |
| 671 | p->parent = NULL; |
| 672 | debug_log("pid %d %s states parent %d, but the later does not exist.", p->pid, p->comm, p->ppid); |
| 673 | } |
| 674 | } |
| 675 | } |
| 676 | |
| 677 | /** |
| 678 | * Aggregate PIDs to targets. |
| 679 | * |
| 680 | * This function performs target inheritance iteratively to ensure |
| 681 | * proper propagation even when children appear before parents in the list. |
| 682 | * Algorithm: |
| 683 | * 1. Propagate targets from parent to children without targets (iterative) |
| 684 | * 2. Merge leaf processes upward to their parents (iterative) |
| 685 | * 3. Assign default target to unmerged top-level processes |
| 686 | * 4. Propagate targets to merged children via their parents (iterative) |
| 687 | */ |
| 688 | static void apply_apps_groups_targets_inheritance(void) |
| 689 | { |
| 690 | int sortlist = 1; |
| 691 | struct ebpf_pid_data *p = NULL; |
| 692 | |
| 693 | ebpf_pid_data_t *pid_entry = ebpf_find_or_create_pid_data(INIT_PID); |
| 694 | pid_entry->target = apps_groups_default_target; |
| 695 | |
| 696 | pid_entry = ebpf_find_or_create_pid_data(0); |
| 697 | pid_entry->target = apps_groups_default_target; |
| 698 | |
| 699 | int found = 1; |
| 700 | while (found) { |
| 701 | found = 0; |
| 702 | for (p = ebpf_pids_link_list; p; p = p->next) { |
| 703 | if (unlikely(!p->target && p->parent && p->parent->target)) { |
| 704 | p->target = p->parent->target; |
| 705 | found++; |
| 706 | } |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | for (p = ebpf_pids_link_list; p; p = p->next) { |
| 711 | if (unlikely(!p->sortlist && !p->children_count)) |
| 712 | p->sortlist = sortlist++; |
| 713 | } |
| 714 | |
| 715 | found = 1; |
| 716 | while (found) { |
| 717 | found = 0; |
| 718 | for (p = ebpf_pids_link_list; p; p = p->next) { |
| 719 | if (ebpf_plugin_stop()) |
| 720 | break; |
| 721 | |
| 722 | if (unlikely( |
| 723 | !p->children_count && !p->merged && p->parent && p->parent->children_count && |
| 724 | (p->target == p->parent->target || !p->parent->target) && p->ppid != INIT_PID)) { |
| 725 | p->parent->children_count--; |
| 726 | p->merged = 1; |
| 727 | |
| 728 | if (unlikely(p->target && !p->parent->target)) |
| 729 | p->parent->target = p->target; |
| 730 | |
| 731 | found++; |
| 732 | } |
| 733 | } |
| 734 | } |
| 735 | |
| 736 | for (p = ebpf_pids_link_list; p; p = p->next) { |
| 737 | if (unlikely(!p->merged && !p->target)) |
| 738 | p->target = apps_groups_default_target; |
| 739 | |
| 740 | if (unlikely(!p->sortlist)) |
| 741 | p->sortlist = sortlist++; |
| 742 | } |
| 743 | |
| 744 | pid_entry = ebpf_find_or_create_pid_data(1); |
| 745 | pid_entry->sortlist = sortlist++; |
| 746 | |
| 747 | found = 1; |
| 748 | while (found) { |
| 749 | found = 0; |
| 750 | for (p = ebpf_pids_link_list; p; p = p->next) { |
| 751 | if (unlikely(!p->target && p->merged && p->parent && p->parent->target)) { |
| 752 | p->target = p->parent->target; |
| 753 | found++; |
| 754 | } |
| 755 | } |
| 756 | } |
| 757 | } |
| 758 | |
| 759 | /** |
| 760 | * Update target timestamp. |
| 761 | * |
| 762 | * @param root the targets that will be updated. |
| 763 | */ |
| 764 | static inline void post_aggregate_targets(struct ebpf_target *root) |
| 765 | { |
| 766 | struct ebpf_target *w; |
| 767 | for (w = root; w; w = w->next) { |
| 768 | if (ebpf_plugin_stop()) |
| 769 | break; |
| 770 | |
| 771 | if (w->collected_starttime) { |
| 772 | if (!w->starttime || w->collected_starttime < w->starttime) { |
| 773 | w->starttime = w->collected_starttime; |
| 774 | } |
| 775 | } else { |
| 776 | w->starttime = 0; |
| 777 | } |
| 778 | } |
| 779 | } |
| 780 | |
| 781 | /** |
| 782 | * Remove PID from the link list. |
| 783 | * |
| 784 | * @param pid the PID that will be removed. |
| 785 | */ |
| 786 | void ebpf_del_pid_entry(pid_t pid) |
| 787 | { |
| 788 | ebpf_pid_data_t *p = ebpf_find_pid_data(pid); |
| 789 | |
| 790 | debug_log("process %d %s exited, deleting it.", pid, p->comm); |
| 791 | |
| 792 | if (ebpf_pids_link_list == p) |
| 793 | ebpf_pids_link_list = p->next; |
| 794 | |
| 795 | if (p->next) |
| 796 | p->next->prev = p->prev; |
| 797 | if (p->prev) |
| 798 | p->prev->next = p->next; |
| 799 | |
| 800 | if ((p->thread_collecting & NETDATA_EBPF_PIDS_PROC_FILE) || p->has_proc_file) |
| 801 | ebpf_all_pids_count--; |
| 802 | |
| 803 | rw_spinlock_write_lock(&ebpf_judy_pid.index.rw_spinlock); |
| 804 | netdata_ebpf_judy_pid_stats_t *pid_ptr = ebpf_get_pid_from_judy_unsafe(&ebpf_judy_pid.index.JudyLArray, p->pid); |
| 805 | if (pid_ptr) { |
| 806 | if (pid_ptr->socket_stats.JudyLArray) { |
| 807 | Word_t local_socket = 0; |
| 808 | Pvoid_t *socket_value; |
| 809 | bool first_socket = true; |
| 810 | while ( |
| 811 | (socket_value = JudyLFirstThenNext(pid_ptr->socket_stats.JudyLArray, &local_socket, &first_socket))) { |
| 812 | netdata_socket_plus_t *socket_clean = *socket_value; |
| 813 | aral_freez(aral_socket_table, socket_clean); |
| 814 | } |
| 815 | JudyLFreeArray(&pid_ptr->socket_stats.JudyLArray, PJE0); |
| 816 | } |
| 817 | aral_freez(ebpf_judy_pid.pid_table, pid_ptr); |
| 818 | JudyLDel(&ebpf_judy_pid.index.JudyLArray, p->pid, PJE0); |
| 819 | } |
| 820 | rw_spinlock_write_unlock(&ebpf_judy_pid.index.rw_spinlock); |
| 821 | |
| 822 | freez(p); |
| 823 | ebpf_pid_del(pid); |
| 824 | } |
| 825 | |
| 826 | /** |
| 827 | * Remove PIDs when they are not running more. |
| 828 | */ |
| 829 | static void ebpf_cleanup_exited_pids() |
| 830 | { |
| 831 | ebpf_pid_data_t *p = NULL; |
| 832 | for (p = ebpf_pids_link_list; p;) { |
| 833 | ebpf_pid_data_t *next = p->next; |
| 834 | if (!p->has_proc_file) { |
| 835 | ebpf_reset_specific_pid_data(p); |
| 836 | } |
| 837 | p = next; |
| 838 | } |
| 839 | } |
| 840 | |
| 841 | /** |
| 842 | * Read proc filesystem for the first time. |
| 843 | * |
| 844 | * @return It returns 0 on success and -1 otherwise. |
| 845 | */ |
| 846 | static int ebpf_read_proc_filesystem() |
| 847 | { |
| 848 | char dirname[FILENAME_MAX + 1]; |
| 849 | |
| 850 | snprintfz(dirname, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix); |
| 851 | DIR *dir = opendir(dirname); |
| 852 | if (!dir) |
| 853 | return -1; |
| 854 | |
| 855 | struct dirent *de = NULL; |
| 856 | |
| 857 | while ((de = readdir(dir))) { |
| 858 | if (ebpf_plugin_stop()) |
| 859 | break; |
| 860 | |
| 861 | char *endptr = de->d_name; |
| 862 | |
| 863 | if (unlikely(de->d_type != DT_DIR || de->d_name[0] < '0' || de->d_name[0] > '9')) |
| 864 | continue; |
| 865 | |
| 866 | pid_t pid = (pid_t)strtoul(de->d_name, &endptr, 10); |
| 867 | |
| 868 | // make sure we read a valid number |
| 869 | if (unlikely(endptr == de->d_name || *endptr != '\0')) |
| 870 | continue; |
| 871 | |
| 872 | ebpf_collect_data_for_pid(pid); |
| 873 | } |
| 874 | closedir(dir); |
| 875 | |
| 876 | return 0; |
| 877 | } |
| 878 | |
| 879 | /** |
| 880 | * Aggregated PID on target |
| 881 | * |
| 882 | * @param w the target output |
| 883 | * @param p the pid with information to update |
| 884 | * @param o never used |
| 885 | */ |
| 886 | static inline void aggregate_pid_on_target(struct ebpf_target *w, ebpf_pid_data_t *p, struct ebpf_target *o) |
| 887 | { |
| 888 | UNUSED(o); |
| 889 | |
| 890 | if (unlikely(!p->has_proc_file)) { |
| 891 | // the process is not running |
| 892 | return; |
| 893 | } |
| 894 | |
| 895 | if (unlikely(!w)) { |
| 896 | netdata_log_error("pid %u %s was left without a target!", p->pid, p->comm); |
| 897 | return; |
| 898 | } |
| 899 | |
| 900 | w->processes++; |
| 901 | struct ebpf_pid_on_target *pid_on_target = mallocz(sizeof(struct ebpf_pid_on_target)); |
| 902 | pid_on_target->pid = p->pid; |
| 903 | pid_on_target->next = w->root_pid; |
| 904 | w->root_pid = pid_on_target; |
| 905 | } |
| 906 | |
| 907 | /** |
| 908 | * |
| 909 | */ |
| 910 | void ebpf_parse_proc_files() |
| 911 | { |
| 912 | ebpf_pid_data_t *pids; |
| 913 | for (pids = ebpf_pids_link_list; pids;) { |
| 914 | if (ebpf_plugin_stop()) |
| 915 | break; |
| 916 | |
| 917 | if (kill(pids->pid, 0) == -1 && errno == ESRCH) { |
| 918 | ebpf_pid_data_t *next = pids->next; |
| 919 | ebpf_reset_specific_pid_data(pids); |
| 920 | pids = next; |
| 921 | continue; |
| 922 | } |
| 923 | |
| 924 | pids->not_updated = EBPF_CLEANUP_FACTOR; |
| 925 | pids->merged = 0; |
| 926 | pids->children_count = 0; |
| 927 | pids = pids->next; |
| 928 | } |
| 929 | |
| 930 | if (ebpf_read_proc_filesystem()) |
| 931 | return; |
| 932 | |
| 933 | link_all_processes_to_their_parents(); |
| 934 | |
| 935 | apply_apps_groups_targets_inheritance(); |
| 936 | |
| 937 | apps_groups_targets_count = zero_all_targets(apps_groups_root_target); |
| 938 | |
| 939 | for (pids = ebpf_pids_link_list; pids; pids = pids->next) { |
| 940 | if (ebpf_plugin_stop()) |
| 941 | break; |
| 942 | |
| 943 | aggregate_pid_on_target(pids->target, pids, NULL); |
| 944 | } |
| 945 | |
| 946 | ebpf_cleanup_exited_pids(); |
| 947 | } |