Viewing file: ebpf_apps.c (32.05 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
// 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 success 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 specified 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 success 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--; }
/** * Get command string associated with a PID. * This can only safely be used when holding the `collect_data_mutex` lock. * * @param pid the pid to search the data. * @param n the maximum amount of bytes to copy into dest. * if this is greater than the size of the command, it is clipped. * @param dest the target memory buffer to write the command into. * @return -1 if the PID hasn't been scraped yet, 0 otherwise. */ int get_pid_comm(pid_t pid, size_t n, char *dest) { struct pid_stat *stat;
stat = all_pids[pid]; if (unlikely(stat == NULL)) { return -1; }
if (unlikely(n > sizeof(stat->comm))) { n = sizeof(stat->comm); }
strncpyz(dest, stat->comm, n); return 0; }
/** * Cleanup variable from other threads * * @param pid current pid. */ void cleanup_variables_from_other_threads(uint32_t pid) { // Clean socket structures if (socket_bandwidth_curr) { freez(socket_bandwidth_curr[pid]); socket_bandwidth_curr[pid] = NULL; }
// Clean cachestat strcture if (cachestat_pid) { freez(cachestat_pid[pid]); cachestat_pid[pid] = NULL; }
// Clean directory cache structure if (dcstat_pid) { freez(dcstat_pid[pid]); dcstat_pid[pid] = NULL; }
// Clean swap structure if (swap_pid) { freez(swap_pid[pid]); swap_pid[pid] = NULL; }
// Clean vfs structure if (vfs_pid) { freez(vfs_pid[pid]); vfs_pid[pid] = NULL; }
// Clean fd structure if (fd_pid) { freez(fd_pid[pid]); fd_pid[pid] = NULL; }
// Clean shm structure if (shm_pid) { freez(shm_pid[pid]); shm_pid[pid] = NULL; } }
/** * 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;
// Clean process structure freez(global_process_stats[r]); global_process_stats[r] = NULL;
freez(current_apps_data[r]); current_apps_data[r] = NULL;
cleanup_variables_from_other_threads(r);
del_pid_entry(r); } 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;
cleanup_variables_from_other_threads(key);
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); }
|