diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 11:08:07 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 11:08:07 +0000 |
commit | c69cb8cc094cc916adbc516b09e944cd3d137c01 (patch) | |
tree | f2878ec41fb6d0e3613906c6722fc02b934eeb80 /collectors/ebpf.plugin/ebpf_apps.c | |
parent | Initial commit. (diff) | |
download | netdata-c69cb8cc094cc916adbc516b09e944cd3d137c01.tar.xz netdata-c69cb8cc094cc916adbc516b09e944cd3d137c01.zip |
Adding upstream version 1.29.3.upstream/1.29.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | collectors/ebpf.plugin/ebpf_apps.c | 1082 |
1 files changed, 1082 insertions, 0 deletions
diff --git a/collectors/ebpf.plugin/ebpf_apps.c b/collectors/ebpf.plugin/ebpf_apps.c new file mode 100644 index 0000000..844ce23 --- /dev/null +++ b/collectors/ebpf.plugin/ebpf_apps.c @@ -0,0 +1,1082 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_socket.h" +#include "ebpf_apps.h" + +// ---------------------------------------------------------------------------- +// internal flags +// handled in code (automatically set) + +static int proc_pid_cmdline_is_needed = 0; // 1 when we need to read /proc/cmdline + +/***************************************************************** + * + * FUNCTIONS USED TO READ HASH TABLES + * + *****************************************************************/ + +/** + * Read statistic hash table. + * + * @param ep the output structure. + * @param fd the file descriptor mapped from kernel ring. + * @param pid the index used to select the data. + * @param bpf_map_lookup_elem a pointer for the function used to read data. + * + * @return It returns 0 when the data was copied and -1 otherwise + */ +int ebpf_read_hash_table(void *ep, int fd, uint32_t pid) +{ + if (!ep) + return -1; + + if (!bpf_map_lookup_elem(fd, &pid, ep)) + return 0; + + return -1; +} + +/** + * Read socket statistic + * + * Read information from kernel ring to user ring. + * + * @param ep the table with all process stats values. + * @param fd the file descriptor mapped from kernel + * @param ef a pointer for the functions mapped from dynamic library + * @param pids the list of pids associated to a target. + * + * @return + */ +size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd, struct pid_on_target *pids) +{ + size_t count = 0; + while (pids) { + uint32_t current_pid = pids->pid; + if (!ebpf_read_hash_table(ep[current_pid], fd, current_pid)) + count++; + + pids = pids->next; + } + + return count; +} + +/** + * Read bandwidth statistic using hash table + * + * @param out the output tensor that will receive the information. + * @param fd the file descriptor that has the data + * @param bpf_map_lookup_elem a pointer for the function to read the data + * @param bpf_map_get_next_key a pointer fo the function to read the index. + */ +size_t read_bandwidth_statistic_using_hash_table(ebpf_bandwidth_t **out, int fd) +{ + size_t count = 0; + uint32_t key = 0; + uint32_t next_key = 0; + + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + ebpf_bandwidth_t *eps = out[next_key]; + if (!eps) { + eps = callocz(1, sizeof(ebpf_process_stat_t)); + out[next_key] = eps; + } + ebpf_read_hash_table(eps, fd, next_key); + } + + return count; +} + +/***************************************************************** + * + * FUNCTIONS CALLED FROM COLLECTORS + * + *****************************************************************/ + +/** + * Am I running as Root + * + * Verify the user that is running the collector. + * + * @return It returns 1 for root and 0 otherwise. + */ +int am_i_running_as_root() +{ + uid_t uid = getuid(), euid = geteuid(); + + if (uid == 0 || euid == 0) { + return 1; + } + + return 0; +} + +/** + * Reset the target values + * + * @param root the pointer to the chain that will be reseted. + * + * @return it returns the number of structures that was reseted. + */ +size_t zero_all_targets(struct target *root) +{ + struct target *w; + size_t count = 0; + + for (w = root; w; w = w->next) { + count++; + + if (unlikely(w->root_pid)) { + struct pid_on_target *pid_on_target = w->root_pid; + + while (pid_on_target) { + struct pid_on_target *pid_on_target_to_free = pid_on_target; + pid_on_target = pid_on_target->next; + free(pid_on_target_to_free); + } + + w->root_pid = NULL; + } + } + + return count; +} + +/** + * Clean the allocated structures + * + * @param agrt the pointer to be cleaned. + */ +void clean_apps_groups_target(struct target *agrt) +{ + struct target *current_target; + while (agrt) { + current_target = agrt; + agrt = current_target->target; + + freez(current_target); + } +} + +/** + * Find or create a new target + * there are targets that are just aggregated to other target (the second argument) + * + * @param id + * @param target + * @param name + * + * @return It returns the target on success and NULL otherwise + */ +struct target *get_apps_groups_target(struct target **agrt, const char *id, struct target *target, const char *name) +{ + int tdebug = 0, thidden = target ? target->hidden : 0, ends_with = 0; + const char *nid = id; + + // extract the options + while (nid[0] == '-' || nid[0] == '+' || nid[0] == '*') { + if (nid[0] == '-') + thidden = 1; + if (nid[0] == '+') + tdebug = 1; + if (nid[0] == '*') + ends_with = 1; + nid++; + } + uint32_t hash = simple_hash(id); + + // find if it already exists + struct target *w, *last = *agrt; + for (w = *agrt; w; w = w->next) { + if (w->idhash == hash && strncmp(nid, w->id, MAX_NAME) == 0) + return w; + + last = w; + } + + // find an existing target + if (unlikely(!target)) { + while (*name == '-') { + if (*name == '-') + thidden = 1; + name++; + } + + for (target = *agrt; target != NULL; target = target->next) { + if (!target->target && strcmp(name, target->name) == 0) + break; + } + } + + if (target && target->target) + fatal( + "Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'", id, + target->id, target->target->id); + + w = callocz(1, sizeof(struct target)); + strncpyz(w->id, nid, MAX_NAME); + w->idhash = simple_hash(w->id); + + if (unlikely(!target)) + // copy the name + strncpyz(w->name, name, MAX_NAME); + else + // copy the id + strncpyz(w->name, nid, MAX_NAME); + + strncpyz(w->compare, nid, MAX_COMPARE_NAME); + size_t len = strlen(w->compare); + if (w->compare[len - 1] == '*') { + w->compare[len - 1] = '\0'; + w->starts_with = 1; + } + w->ends_with = ends_with; + + if (w->starts_with && w->ends_with) + proc_pid_cmdline_is_needed = 1; + + w->comparehash = simple_hash(w->compare); + w->comparelen = strlen(w->compare); + + w->hidden = thidden; +#ifdef NETDATA_INTERNAL_CHECKS + w->debug_enabled = tdebug; +#else + if (tdebug) + fprintf(stderr, "apps.plugin has been compiled without debugging\n"); +#endif + w->target = target; + + // append it, to maintain the order in apps_groups.conf + if (last) + last->next = w; + else + *agrt = w; + + return w; +} + +/** + * Read the apps_groups.conf file + * + * @param agrt a pointer to apps_group_root_target + * @param path the directory to search apps_%s.conf + * @param file the word to complement the file name. + * + * @return It returns 0 on succcess and -1 otherwise + */ +int ebpf_read_apps_groups_conf(struct target **agdt, struct target **agrt, const char *path, const char *file) +{ + char filename[FILENAME_MAX + 1]; + + snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", path, file); + + // ---------------------------------------- + + procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT); + if (!ff) + return -1; + + procfile_set_quotes(ff, "'\""); + + ff = procfile_readall(ff); + if (!ff) + return -1; + + size_t line, lines = procfile_lines(ff); + + for (line = 0; line < lines; line++) { + size_t word, words = procfile_linewords(ff, line); + if (!words) + continue; + + char *name = procfile_lineword(ff, line, 0); + if (!name || !*name) + continue; + + // find a possibly existing target + struct target *w = NULL; + + // loop through all words, skipping the first one (the name) + for (word = 0; word < words; word++) { + char *s = procfile_lineword(ff, line, word); + if (!s || !*s) + continue; + if (*s == '#') + break; + + // is this the first word? skip it + if (s == name) + continue; + + // add this target + struct target *n = get_apps_groups_target(agrt, s, w, name); + if (!n) { + error("Cannot create target '%s' (line %zu, word %zu)", s, line, word); + continue; + } + + // just some optimization + // to avoid searching for a target for each process + if (!w) + w = n->target ? n->target : n; + } + } + + procfile_close(ff); + + *agdt = get_apps_groups_target(agrt, "p+!o@w#e$i^r&7*5(-i)l-o_", NULL, "other"); // match nothing + if (!*agdt) + fatal("Cannot create default target"); + + struct target *ptr = *agdt; + if (ptr->target) + *agdt = ptr->target; + + return 0; +} + +// the minimum PID of the system +// this is also the pid of the init process +#define INIT_PID 1 + +// ---------------------------------------------------------------------------- +// string lengths + +#define MAX_COMPARE_NAME 100 +#define MAX_NAME 100 +#define MAX_CMDLINE 16384 + +struct pid_stat **all_pids = NULL; // to avoid allocations, we pre-allocate the + // the entire pid space. +struct pid_stat *root_of_pids = NULL; // global list of all processes running + +size_t all_pids_count = 0; // the number of processes running + +struct target + *apps_groups_default_target = NULL, // the default target + *apps_groups_root_target = NULL, // apps_groups.conf defined + *users_root_target = NULL, // users + *groups_root_target = NULL; // user groups + +size_t apps_groups_targets_count = 0; // # of apps_groups.conf targets + +// ---------------------------------------------------------------------------- +// internal counters + +static size_t + // global_iterations_counter = 1, + calls_counter = 0, + // file_counter = 0, + // filenames_allocated_counter = 0, + // inodes_changed_counter = 0, + // links_changed_counter = 0, + targets_assignment_counter = 0; + +// ---------------------------------------------------------------------------- +// debugging + +// log each problem once per process +// log flood protection flags (log_thrown) +#define PID_LOG_IO 0x00000001 +#define PID_LOG_STATUS 0x00000002 +#define PID_LOG_CMDLINE 0x00000004 +#define PID_LOG_FDS 0x00000008 +#define PID_LOG_STAT 0x00000010 + +int debug_enabled = 0; + +#ifdef NETDATA_INTERNAL_CHECKS + +#define debug_log(fmt, args...) \ + do { \ + if (unlikely(debug_enabled)) \ + debug_log_int(fmt, ##args); \ + } while (0) + +#else + +static inline void debug_log_dummy(void) +{ +} +#define debug_log(fmt, args...) debug_log_dummy() + +#endif + +/** + * Managed log + * + * Store log information if it is necessary. + * + * @param p the pid stat structure + * @param log the log id + * @param status the return from a function. + * + * @return It returns the status value. + */ +static inline int managed_log(struct pid_stat *p, uint32_t log, int status) +{ + if (unlikely(!status)) { + // error("command failed log %u, errno %d", log, errno); + + if (unlikely(debug_enabled || errno != ENOENT)) { + if (unlikely(debug_enabled || !(p->log_thrown & log))) { + p->log_thrown |= log; + switch (log) { + case PID_LOG_IO: + error( + "Cannot process %s/proc/%d/io (command '%s')", netdata_configured_host_prefix, p->pid, + p->comm); + break; + + case PID_LOG_STATUS: + error( + "Cannot process %s/proc/%d/status (command '%s')", netdata_configured_host_prefix, p->pid, + p->comm); + break; + + case PID_LOG_CMDLINE: + error( + "Cannot process %s/proc/%d/cmdline (command '%s')", netdata_configured_host_prefix, p->pid, + p->comm); + break; + + case PID_LOG_FDS: + error( + "Cannot process entries in %s/proc/%d/fd (command '%s')", netdata_configured_host_prefix, + p->pid, p->comm); + break; + + case PID_LOG_STAT: + break; + + default: + error("unhandled error for pid %d, command '%s'", p->pid, p->comm); + break; + } + } + } + errno = 0; + } else if (unlikely(p->log_thrown & log)) { + // error("unsetting log %u on pid %d", log, p->pid); + p->log_thrown &= ~log; + } + + return status; +} + +/** + * Get PID entry + * + * Get or allocate the PID entry for the specifid pid. + * + * @param pid the pid to search the data. + * + * @return It returns the pid entry structure + */ +static inline struct pid_stat *get_pid_entry(pid_t pid) +{ + if (unlikely(all_pids[pid])) + return all_pids[pid]; + + struct pid_stat *p = callocz(1, sizeof(struct pid_stat)); + + if (likely(root_of_pids)) + root_of_pids->prev = p; + + p->next = root_of_pids; + root_of_pids = p; + + p->pid = pid; + + all_pids[pid] = p; + all_pids_count++; + + return p; +} + +/** + * Assign the PID to a target. + * + * @param p the pid_stat structure to assign for a target. + */ +static inline void assign_target_to_pid(struct pid_stat *p) +{ + targets_assignment_counter++; + + uint32_t hash = simple_hash(p->comm); + size_t pclen = strlen(p->comm); + + struct target *w; + for (w = apps_groups_root_target; w; w = w->next) { + // if(debug_enabled || (p->target && p->target->debug_enabled)) debug_log_int("\t\tcomparing '%s' with '%s'", w->compare, p->comm); + + // find it - 4 cases: + // 1. the target is not a pattern + // 2. the target has the prefix + // 3. the target has the suffix + // 4. the target is something inside cmdline + + if (unlikely( + ((!w->starts_with && !w->ends_with && w->comparehash == hash && !strcmp(w->compare, p->comm)) || + (w->starts_with && !w->ends_with && !strncmp(w->compare, p->comm, w->comparelen)) || + (!w->starts_with && w->ends_with && pclen >= w->comparelen && !strcmp(w->compare, &p->comm[pclen - w->comparelen])) || + (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && p->cmdline && strstr(p->cmdline, w->compare))))) { + if (w->target) + p->target = w->target; + else + p->target = w; + + if (debug_enabled || (p->target && p->target->debug_enabled)) + debug_log_int("%s linked to target %s", p->comm, p->target->name); + + break; + } + } +} + +// ---------------------------------------------------------------------------- +// update pids from proc + +/** + * Read cmd line from /proc/PID/cmdline + * + * @param p the pid_stat_structure. + * + * @return It returns 1 on success and 0 otherwise. + */ +static inline int read_proc_pid_cmdline(struct pid_stat *p) +{ + static char cmdline[MAX_CMDLINE + 1]; + + if (unlikely(!p->cmdline_filename)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/cmdline", netdata_configured_host_prefix, p->pid); + p->cmdline_filename = strdupz(filename); + } + + int fd = open(p->cmdline_filename, procfile_open_flags, 0666); + if (unlikely(fd == -1)) + goto cleanup; + + ssize_t i, bytes = read(fd, cmdline, MAX_CMDLINE); + close(fd); + + if (unlikely(bytes < 0)) + goto cleanup; + + cmdline[bytes] = '\0'; + for (i = 0; i < bytes; i++) { + if (unlikely(!cmdline[i])) + cmdline[i] = ' '; + } + + if (p->cmdline) + freez(p->cmdline); + p->cmdline = strdupz(cmdline); + + debug_log("Read file '%s' contents: %s", p->cmdline_filename, p->cmdline); + + return 1; + +cleanup: + // copy the command to the command line + if (p->cmdline) + freez(p->cmdline); + p->cmdline = strdupz(p->comm); + return 0; +} + +/** + * Read information from /proc/PID/stat and /proc/PID/cmdline + * Assign target to pid + * + * @param p the pid stat structure to store the data. + * @param ptr an useless argument. + */ +static inline int read_proc_pid_stat(struct pid_stat *p, void *ptr) +{ + UNUSED(ptr); + + static procfile *ff = NULL; + + if (unlikely(!p->stat_filename)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/stat", netdata_configured_host_prefix, p->pid); + p->stat_filename = strdupz(filename); + } + + int set_quotes = (!ff) ? 1 : 0; + + struct stat statbuf; + if (stat(p->stat_filename, &statbuf)) + return 0; + + ff = procfile_reopen(ff, p->stat_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); + if (unlikely(!ff)) + return 0; + + if (unlikely(set_quotes)) + procfile_set_open_close(ff, "(", ")"); + + ff = procfile_readall(ff); + if (unlikely(!ff)) + return 0; + + p->last_stat_collected_usec = p->stat_collected_usec; + p->stat_collected_usec = now_monotonic_usec(); + calls_counter++; + + char *comm = procfile_lineword(ff, 0, 1); + p->ppid = (int32_t)str2pid_t(procfile_lineword(ff, 0, 3)); + + if (strcmp(p->comm, comm) != 0) { + if (unlikely(debug_enabled)) { + if (p->comm[0]) + debug_log("\tpid %d (%s) changed name to '%s'", p->pid, p->comm, comm); + else + debug_log("\tJust added %d (%s)", p->pid, comm); + } + + strncpyz(p->comm, comm, MAX_COMPARE_NAME); + + // /proc/<pid>/cmdline + if (likely(proc_pid_cmdline_is_needed)) + managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p)); + + assign_target_to_pid(p); + } + + if (unlikely(debug_enabled || (p->target && p->target->debug_enabled))) + debug_log_int( + "READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu)", + netdata_configured_host_prefix, p->pid, p->comm, (p->target) ? p->target->name : "UNSET", + p->stat_collected_usec - p->last_stat_collected_usec); + + return 1; +} + +/** + * Collect data for PID + * + * @param pid the current pid that we are working + * @param ptr a NULL value + * + * @return It returns 1 on succcess and 0 otherwise + */ +static inline int collect_data_for_pid(pid_t pid, void *ptr) +{ + if (unlikely(pid < 0 || pid > pid_max)) { + error("Invalid pid %d read (expected %d to %d). Ignoring process.", pid, 0, pid_max); + return 0; + } + + struct pid_stat *p = get_pid_entry(pid); + if (unlikely(!p || p->read)) + return 0; + p->read = 1; + + if (unlikely(!managed_log(p, PID_LOG_STAT, read_proc_pid_stat(p, ptr)))) + // there is no reason to proceed if we cannot get its status + return 0; + + // check its parent pid + if (unlikely(p->ppid < 0 || p->ppid > pid_max)) { + error("Pid %d (command '%s') states invalid parent pid %d. Using 0.", pid, p->comm, p->ppid); + p->ppid = 0; + } + + // mark it as updated + p->updated = 1; + p->keep = 0; + p->keeploops = 0; + + return 1; +} + +/** + * Fill link list of parents with children PIDs + */ +static inline void link_all_processes_to_their_parents(void) +{ + struct pid_stat *p, *pp; + + // link all children to their parents + // and update children count on parents + for (p = root_of_pids; p; p = p->next) { + // for each process found + + p->sortlist = 0; + p->parent = NULL; + + if (unlikely(!p->ppid)) { + p->parent = NULL; + continue; + } + + pp = all_pids[p->ppid]; + if (likely(pp)) { + p->parent = pp; + pp->children_count++; + + if (unlikely(debug_enabled || (p->target && p->target->debug_enabled))) + debug_log_int( + "child %d (%s, %s) on target '%s' has parent %d (%s, %s).", p->pid, p->comm, + p->updated ? "running" : "exited", (p->target) ? p->target->name : "UNSET", pp->pid, pp->comm, + pp->updated ? "running" : "exited"); + } else { + p->parent = NULL; + debug_log("pid %d %s states parent %d, but the later does not exist.", p->pid, p->comm, p->ppid); + } + } +} + +/** + * Aggregate PIDs to targets. + */ +static void apply_apps_groups_targets_inheritance(void) +{ + struct pid_stat *p = NULL; + + // children that do not have a target + // inherit their target from their parent + int found = 1, loops = 0; + while (found) { + if (unlikely(debug_enabled)) + loops++; + found = 0; + for (p = root_of_pids; p; p = p->next) { + // if this process does not have a target + // and it has a parent + // and its parent has a target + // then, set the parent's target to this process + if (unlikely(!p->target && p->parent && p->parent->target)) { + p->target = p->parent->target; + found++; + + if (debug_enabled || (p->target && p->target->debug_enabled)) + debug_log_int( + "TARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s).", p->target->name, + p->pid, p->comm, p->parent->pid, p->parent->comm); + } + } + } + + // find all the procs with 0 childs and merge them to their parents + // repeat, until nothing more can be done. + int sortlist = 1; + found = 1; + while (found) { + if (unlikely(debug_enabled)) + loops++; + found = 0; + + for (p = root_of_pids; p; p = p->next) { + if (unlikely(!p->sortlist && !p->children_count)) + p->sortlist = sortlist++; + + if (unlikely( + !p->children_count // if this process does not have any children + && !p->merged // and is not already merged + && p->parent // and has a parent + && p->parent->children_count // and its parent has children + // and the target of this process and its parent is the same, + // or the parent does not have a target + && (p->target == p->parent->target || !p->parent->target) && + p->ppid != INIT_PID // and its parent is not init + )) { + // mark it as merged + p->parent->children_count--; + p->merged = 1; + + // the parent inherits the child's target, if it does not have a target itself + if (unlikely(p->target && !p->parent->target)) { + p->parent->target = p->target; + + if (debug_enabled || (p->target && p->target->debug_enabled)) + debug_log_int( + "TARGET INHERITANCE: %s is inherited by %d (%s) from its child %d (%s).", p->target->name, + p->parent->pid, p->parent->comm, p->pid, p->comm); + } + + found++; + } + } + + debug_log("TARGET INHERITANCE: merged %d processes", found); + } + + // init goes always to default target + if (all_pids[INIT_PID]) + all_pids[INIT_PID]->target = apps_groups_default_target; + + // pid 0 goes always to default target + if (all_pids[0]) + all_pids[0]->target = apps_groups_default_target; + + // give a default target on all top level processes + if (unlikely(debug_enabled)) + loops++; + for (p = root_of_pids; p; p = p->next) { + // if the process is not merged itself + // then is is a top level process + if (unlikely(!p->merged && !p->target)) + p->target = apps_groups_default_target; + + // make sure all processes have a sortlist + if (unlikely(!p->sortlist)) + p->sortlist = sortlist++; + } + + if (all_pids[1]) + all_pids[1]->sortlist = sortlist++; + + // give a target to all merged child processes + found = 1; + while (found) { + if (unlikely(debug_enabled)) + loops++; + found = 0; + for (p = root_of_pids; p; p = p->next) { + if (unlikely(!p->target && p->merged && p->parent && p->parent->target)) { + p->target = p->parent->target; + found++; + + if (debug_enabled || (p->target && p->target->debug_enabled)) + debug_log_int( + "TARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s) at phase 2.", + p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm); + } + } + } + + debug_log("apply_apps_groups_targets_inheritance() made %d loops on the process tree", loops); +} + +/** + * Update target timestamp. + * + * @param root the targets that will be updated. + */ +static inline void post_aggregate_targets(struct target *root) +{ + struct target *w; + for (w = root; w; w = w->next) { + if (w->collected_starttime) { + if (!w->starttime || w->collected_starttime < w->starttime) { + w->starttime = w->collected_starttime; + } + } else { + w->starttime = 0; + } + } +} + +/** + * Remove PID from the link list. + * + * @param pid the PID that will be removed. + */ +static inline void del_pid_entry(pid_t pid) +{ + struct pid_stat *p = all_pids[pid]; + + if (unlikely(!p)) { + error("attempted to free pid %d that is not allocated.", pid); + return; + } + + debug_log("process %d %s exited, deleting it.", pid, p->comm); + + if (root_of_pids == p) + root_of_pids = p->next; + + if (p->next) + p->next->prev = p->prev; + if (p->prev) + p->prev->next = p->next; + + freez(p->stat_filename); + freez(p->status_filename); + freez(p->io_filename); + freez(p->cmdline_filename); + freez(p->cmdline); + freez(p); + + all_pids[pid] = NULL; + all_pids_count--; +} + +/** + * Remove PIDs when they are not running more. + */ +void cleanup_exited_pids() +{ + struct pid_stat *p = NULL; + + for (p = root_of_pids; p;) { + if (!p->updated && (!p->keep || p->keeploops > 0)) { + if (unlikely(debug_enabled && (p->keep || p->keeploops))) + debug_log(" > CLEANUP cannot keep exited process %d (%s) anymore - removing it.", p->pid, p->comm); + + pid_t r = p->pid; + p = p->next; + del_pid_entry(r); + + // Clean process structure + freez(global_process_stats[r]); + global_process_stats[r] = NULL; + + freez(current_apps_data[r]); + current_apps_data[r] = NULL; + + // Clean socket structures + if (socket_bandwidth_curr) { + freez(socket_bandwidth_curr[r]); + socket_bandwidth_curr[r] = NULL; + } + } else { + if (unlikely(p->keep)) + p->keeploops++; + p->keep = 0; + p = p->next; + } + } +} + +/** + * Read proc filesystem for the first time. + * + * @return It returns 0 on success and -1 otherwise. + */ +static inline void read_proc_filesystem() +{ + char dirname[FILENAME_MAX + 1]; + + snprintfz(dirname, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix); + DIR *dir = opendir(dirname); + if (!dir) + return; + + struct dirent *de = NULL; + + while ((de = readdir(dir))) { + char *endptr = de->d_name; + + if (unlikely(de->d_type != DT_DIR || de->d_name[0] < '0' || de->d_name[0] > '9')) + continue; + + pid_t pid = (pid_t)strtoul(de->d_name, &endptr, 10); + + // make sure we read a valid number + if (unlikely(endptr == de->d_name || *endptr != '\0')) + continue; + + collect_data_for_pid(pid, NULL); + } + closedir(dir); +} + +/** + * Aggregated PID on target + * + * @param w the target output + * @param p the pid with information to update + * @param o never used + */ +static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, struct target *o) +{ + UNUSED(o); + + if (unlikely(!p->updated)) { + // the process is not running + return; + } + + if (unlikely(!w)) { + error("pid %d %s was left without a target!", p->pid, p->comm); + return; + } + + w->processes++; + struct pid_on_target *pid_on_target = mallocz(sizeof(struct pid_on_target)); + pid_on_target->pid = p->pid; + pid_on_target->next = w->root_pid; + w->root_pid = pid_on_target; +} + +/** + * Collect data for all process + * + * Read data from hash table and store it in appropriate vectors. + * It also creates the link between targets and PIDs. + * + * @param tbl_pid_stats_fd The mapped file descriptor for the hash table. + */ +void collect_data_for_all_processes(int tbl_pid_stats_fd) +{ + struct pid_stat *pids = root_of_pids; // global list of all processes running + while (pids) { + if (pids->updated_twice) { + pids->read = 0; // mark it as not read, so that collect_data_for_pid() will read it + pids->updated = 0; + pids->merged = 0; + pids->children_count = 0; + pids->parent = NULL; + } else { + if (pids->updated) + pids->updated_twice = 1; + } + + pids = pids->next; + } + + read_proc_filesystem(); + + uint32_t key; + pids = root_of_pids; // global list of all processes running + // while (bpf_map_get_next_key(tbl_pid_stats_fd, &key, &next_key) == 0) { + while (pids) { + key = pids->pid; + ebpf_process_stat_t *w = global_process_stats[key]; + if (!w) { + w = mallocz(sizeof(ebpf_process_stat_t)); + global_process_stats[key] = w; + } + + if (bpf_map_lookup_elem(tbl_pid_stats_fd, &key, w)) { + // Clean Process structures + freez(w); + global_process_stats[key] = NULL; + + freez(current_apps_data[key]); + current_apps_data[key] = NULL; + + // Clean socket structures + if (socket_bandwidth_curr) { + freez(socket_bandwidth_curr[key]); + socket_bandwidth_curr[key] = NULL; + } + + pids = pids->next; + continue; + } + + pids = pids->next; + } + + link_all_processes_to_their_parents(); + + apply_apps_groups_targets_inheritance(); + + apps_groups_targets_count = zero_all_targets(apps_groups_root_target); + + // this has to be done, before the cleanup + // // concentrate everything on the targets + for (pids = root_of_pids; pids; pids = pids->next) + aggregate_pid_on_target(pids->target, pids, NULL); + + post_aggregate_targets(apps_groups_root_target); +} |