]> arthur.barton.de Git - netdata.git/blobdiff - src/apps_plugin.c
Merge pull request #299 from fredericopissarra/master
[netdata.git] / src / apps_plugin.c
old mode 100755 (executable)
new mode 100644 (file)
index 33ffcd9..e8a6f43
@@ -39,8 +39,9 @@
 #include "procfile.h"
 #include "../config.h"
 
-#define MAX_COMPARE_NAME 15
+#define MAX_COMPARE_NAME 100
 #define MAX_NAME 100
+#define MAX_CMDLINE 1024
 
 unsigned long long Hertz = 1;
 
@@ -50,6 +51,7 @@ int debug = 0;
 
 int update_every = 1;
 unsigned long long file_counter = 0;
+int proc_pid_cmdline_is_needed = 0;
 
 char *host_prefix = "";
 char *config_dir = CONFIG_DIR;
@@ -209,7 +211,6 @@ char *strdup_debug(const char *file, int line, const char *function, const char
 
 #endif /* NETDATA_INTERNAL_CHECKS */
 
-
 // ----------------------------------------------------------------------------
 // system functions
 // to retrieve settings of the system
@@ -294,9 +295,12 @@ unsigned long long get_system_hertz(void)
 
 struct target {
        char compare[MAX_COMPARE_NAME + 1];
-       uint32_t hash;
+       uint32_t comparehash;
+       size_t comparelen;
 
        char id[MAX_NAME + 1];
+       uint32_t idhash;
+
        char name[MAX_NAME + 1];
 
        uid_t uid;
@@ -361,6 +365,9 @@ struct target {
        int exposed;                            // if set, we have sent this to netdata
        int hidden;                                     // if set, we set the hidden flag on the dimension
        int debug;
+       int ends_with;
+       int starts_with;            // if set, the compare string matches only the
+                                                               // beginning of the command
 
        struct target *target;          // the one that will be reported to netdata
        struct target *next;
@@ -391,9 +398,11 @@ struct target *get_users_target(uid_t uid)
        }
 
        snprintf(w->compare, MAX_COMPARE_NAME, "%d", uid);
-       w->hash = simple_hash(w->compare);
+       w->comparehash = simple_hash(w->compare);
+       w->comparelen = strlen(w->compare);
 
        snprintf(w->id, MAX_NAME, "%d", uid);
+       w->idhash = simple_hash(w->id);
 
        struct passwd *pw = getpwuid(uid);
        if(!pw)
@@ -401,6 +410,8 @@ struct target *get_users_target(uid_t uid)
        else
                snprintf(w->name, MAX_NAME, "%s", pw->pw_name);
 
+       netdata_fix_id(w->name);
+
        w->uid = uid;
 
        w->next = users_root_target;
@@ -425,9 +436,11 @@ struct target *get_groups_target(gid_t gid)
        }
 
        snprintf(w->compare, MAX_COMPARE_NAME, "%d", gid);
-       w->hash = simple_hash(w->compare);
+       w->comparehash = simple_hash(w->compare);
+       w->comparelen = strlen(w->compare);
 
        snprintf(w->id, MAX_NAME, "%d", gid);
+       w->idhash = simple_hash(w->id);
 
        struct group *gr = getgrgid(gid);
        if(!gr)
@@ -435,6 +448,8 @@ struct target *get_groups_target(gid_t gid)
        else
                snprintf(w->name, MAX_NAME, "%s", gr->gr_name);
 
+       netdata_fix_id(w->name);
+
        w->gid = gid;
 
        w->next = groups_root_target;
@@ -450,12 +465,22 @@ struct target *get_groups_target(gid_t gid)
 // there are targets that are just aggregated to other target (the second argument)
 struct target *get_apps_groups_target(const char *id, struct target *target)
 {
+       int tdebug = 0, thidden = 0, ends_with = 0;
        const char *nid = id;
-       if(nid[0] == '-') nid++;
+
+       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);
 
        struct target *w;
-       for(w = apps_groups_root_target ; w ; w = w->next)
-               if(strncmp(nid, w->id, MAX_NAME) == 0) return w;
+       for(w = apps_groups_root_target ; w ; w = w->next) {
+               if(w->idhash == hash && strncmp(nid, w->id, MAX_NAME) == 0)
+                       return w;
+       }
 
        w = calloc(sizeof(struct target), 1);
        if(unlikely(!w)) {
@@ -464,19 +489,39 @@ struct target *get_apps_groups_target(const char *id, struct target *target)
        }
 
        strncpy(w->id, nid, MAX_NAME);
+       w->idhash = simple_hash(w->id);
+
        strncpy(w->name, nid, MAX_NAME);
+
        strncpy(w->compare, nid, MAX_COMPARE_NAME);
-       w->hash = simple_hash(w->compare);
+       int 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;
 
-       if(id[0] == '-') w->hidden = 1;
+       w->comparehash = simple_hash(w->compare);
+       w->comparelen = strlen(w->compare);
 
+       w->hidden = thidden;
+       w->debug = tdebug;
        w->target = target;
 
        w->next = apps_groups_root_target;
        apps_groups_root_target = w;
 
        if(unlikely(debug))
-               fprintf(stderr, "apps.plugin: adding hook for process '%s', compare '%s' on target '%s'\n", w->id, w->compare, w->target?w->target->id:"");
+               fprintf(stderr, "apps.plugin: ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s\n"
+                       , w->id
+                               , w->compare, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact"))
+                               , w->target?w->target->id:w->id
+                               , (w->hidden)?"hidden":"-"
+                               , (w->debug)?"debug":"-"
+               );
 
        return w;
 }
@@ -484,7 +529,6 @@ struct target *get_apps_groups_target(const char *id, struct target *target)
 // read the apps_groups.conf file
 int read_apps_groups_conf(const char *name)
 {
-       char buffer[4096+1];
        char filename[FILENAME_MAX + 1];
 
        snprintf(filename, FILENAME_MAX, "%s/apps_%s.conf", config_dir, name);
@@ -492,95 +536,77 @@ int read_apps_groups_conf(const char *name)
        if(unlikely(debug))
                fprintf(stderr, "apps.plugin: process groups file: '%s'\n", filename);
 
-       FILE *fp = fopen(filename, "r");
-       if(unlikely(!fp)) {
-               error("Cannot open file '%s'", filename);
-               return 1;
-       }
+       // ----------------------------------------
 
-       long line = 0;
-       while(fgets(buffer, 4096, fp) != NULL) {
-               int whidden = 0, wdebug = 0;
-               line++;
+       procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT);
+       if(!ff) return 1;
+
+       procfile_set_quotes(ff, "'\"");
 
-               // if(debug) fprintf(stderr, "apps.plugin: \tread %s\n", buffer);
+       ff = procfile_readall(ff);
+       if(!ff) {
+               procfile_close(ff);
+               return 1;
+       }
 
-               char *s = buffer, *t, *p;
-               s = trim(s);
-               if(!s || !*s || *s == '#') continue;
+       unsigned long line, lines = procfile_lines(ff);
 
-               if(debug) fprintf(stderr, "apps.plugin: \tread %s\n", s);
+       for(line = 0; line < lines ;line++) {
+               unsigned long word, words = procfile_linewords(ff, line);
+               struct target *w = NULL;
 
-               // the target name
-               t = strsep(&s, ":");
-               if(t) t = trim(t);
+               char *t = procfile_lineword(ff, line, 0);
                if(!t || !*t) continue;
 
-               while(t[0]) {
-                       int stop = 1;
+               for(word = 0; word < words ;word++) {
+                       char *s = procfile_lineword(ff, line, word);
+                       if(!s || !*s) continue;
+                       if(*s == '#') break;
 
-                       switch(t[0]) {
-                               case '-':
-                                       stop = 0;
-                                       whidden = 1;
-                                       t++;
-                                       break;
+                       if(t == s) continue;
 
-                               case '+':
-                                       stop = 0;
-                                       wdebug = 1;
-                                       t++;
-                                       break;
+                       struct target *n = get_apps_groups_target(s, w);
+                       if(!n) {
+                               error("Cannot create target '%s' (line %d, word %d)", s, line, word);
+                               continue;
                        }
 
-                       if(stop) break;
+                       if(!w) w = n;
                }
 
-               if(debug) fprintf(stderr, "apps.plugin: \t\ttarget %s\n", t);
-
-               struct target *w = NULL;
-               long count = 0;
-               int blen = 0;
-               char buffer[4097] = "";
-               buffer[4096] = '\0';
-
-               // the process names
-               while((p = strsep(&s, " "))) {
-                       p = trim(p);
-                       if(!p || !*p) continue;
+               if(w) {
+                       int tdebug = 0, thidden = 0;
 
-                       strncpy(&buffer[blen], p, 4096 - blen);
-                       blen = strlen(buffer);
-
-                       while(buffer[blen - 1] == '\\') {
-                               buffer[blen - 1] = ' ';
-
-                               if((p = strsep(&s, " ")))
-                                       p = trim(p);
-
-                               if(!p || !*p) p = " ";
-                               strncpy(&buffer[blen], p, 4096 - blen);
-                               blen = strlen(buffer);
+                       while(t[0] == '-' || t[0] == '+') {
+                               if(t[0] == '-') thidden = 1;
+                               if(t[0] == '+') tdebug = 1;
+                               t++;
                        }
 
-                       struct target *n = get_apps_groups_target(buffer, w);
-                       n->hidden = whidden;
-                       n->debug = wdebug;
-                       if(!w) w = n;
-
-                       buffer[0] = '\0';
-                       blen = 0;
-
-                       count++;
+                       strncpy(w->name, t, MAX_NAME);
+                       w->name[MAX_NAME] = '\0';
+                       w->hidden = thidden;
+                       w->debug = tdebug;
+
+                       if(unlikely(debug))
+                               fprintf(stderr, "apps.plugin: AGGREGATION TARGET NAME '%s' on ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s\n"
+                                               , w->name
+                                               , w->id
+                                               , w->compare, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact"))
+                                               , w->target?w->target->id:w->id
+                                               , (w->hidden)?"hidden":"-"
+                                               , (w->debug)?"debug":"-"
+                               );
                }
-
-               if(w) strncpy(w->name, t, MAX_NAME);
-               if(!count) error("The line %ld on file '%s', for group '%s' does not state any process names.", line, filename, t);
        }
-       fclose(fp);
 
-       apps_groups_default_target = get_apps_groups_target("+p!o@w#e$i^r&7*5(-i)l-o_", NULL); // match nothing
-       strncpy(apps_groups_default_target->name, "other", MAX_NAME);
+       procfile_close(ff);
+
+       apps_groups_default_target = get_apps_groups_target("p+!o@w#e$i^r&7*5(-i)l-o_", NULL); // match nothing
+       if(!apps_groups_default_target)
+               error("Cannot create default target");
+       else
+               strncpy(apps_groups_default_target->name, "other", MAX_NAME);
 
        return 0;
 }
@@ -593,6 +619,7 @@ int read_apps_groups_conf(const char *name)
 struct pid_stat {
        int32_t pid;
        char comm[MAX_COMPARE_NAME + 1];
+       char cmdline[MAX_CMDLINE + 1];
 
        // char state;
        int32_t ppid;
@@ -767,6 +794,33 @@ void del_pid_entry(pid_t pid)
 // ----------------------------------------------------------------------------
 // update pids from proc
 
+int read_proc_pid_cmdline(struct pid_stat *p) {
+       char filename[FILENAME_MAX + 1];
+       snprintf(filename, FILENAME_MAX, "%s/proc/%d/cmdline", host_prefix, p->pid);
+
+       int fd = open(filename, O_RDONLY, 0666);
+       if(unlikely(fd == -1)) return 1;
+
+       int i, bytes = read(fd, p->cmdline, MAX_CMDLINE);
+       close(fd);
+
+       if(bytes <= 0) {
+               // copy the command to the command line
+               strncpy(p->cmdline, p->comm, MAX_CMDLINE);
+               p->cmdline[MAX_CMDLINE] = '\0';
+               return 0;
+       }
+
+       p->cmdline[bytes] = '\0';
+       for(i = 0; i < bytes ; i++)
+               if(!p->cmdline[i]) p->cmdline[i] = ' ';
+
+       if(unlikely(debug))
+               fprintf(stderr, "Read file '%s' contents: %s\n", filename, p->cmdline);
+
+       return 0;
+}
+
 int read_proc_pid_ownership(struct pid_stat *p) {
        char filename[FILENAME_MAX + 1];
 
@@ -811,8 +865,8 @@ int read_proc_pid_stat(struct pid_stat *p) {
        file_counter++;
 
        // parse the process name
-       unsigned int i = 1;
-       strncpy(p->comm, procfile_lineword(ff, 0, i), MAX_COMPARE_NAME);
+       unsigned int i = 0;
+       strncpy(p->comm, procfile_lineword(ff, 0, 1), MAX_COMPARE_NAME);
        p->comm[MAX_COMPARE_NAME] = '\0';
 
        // p->pid                       = atol(procfile_lineword(ff, 0, 0+i));
@@ -1248,8 +1302,6 @@ int read_pid_file_descriptors(struct pid_stat *p) {
 
 int collect_data_for_all_processes_from_proc(void)
 {
-       static long count_errors = 0;
-
        char dirname[FILENAME_MAX + 1];
 
        snprintf(dirname, FILENAME_MAX, "%s/proc", host_prefix);
@@ -1303,7 +1355,6 @@ int collect_data_for_all_processes_from_proc(void)
                // /proc/<pid>/stat
 
                if(unlikely(read_proc_pid_stat(p))) {
-                       if(!count_errors++ || debug || (p->target && p->target->debug))
                                error("Cannot process %s/proc/%d/stat", host_prefix, pid);
 
                        // there is no reason to proceed if we cannot get its status
@@ -1312,18 +1363,24 @@ int collect_data_for_all_processes_from_proc(void)
 
                // check its parent pid
                if(unlikely(p->ppid < 0 || p->ppid > pid_max)) {
-                       if(unlikely(!count_errors++ || debug || (p->target && p->target->debug)))
                                error("Pid %d states invalid parent pid %d. Using 0.", pid, p->ppid);
 
                        p->ppid = 0;
                }
 
+               // --------------------------------------------------------------------
+               // /proc/<pid>/cmdline
+
+               if(proc_pid_cmdline_is_needed) {
+                       if(unlikely(read_proc_pid_cmdline(p))) {
+                                       error("Cannot process %s/proc/%d/cmdline", host_prefix, pid);
+                       }
+               }
 
                // --------------------------------------------------------------------
                // /proc/<pid>/statm
 
                if(unlikely(read_proc_pid_statm(p))) {
-                       if(unlikely(!count_errors++ || debug || (p->target && p->target->debug)))
                                error("Cannot process %s/proc/%d/statm", host_prefix, pid);
 
                        // there is no reason to proceed if we cannot get its memory status
@@ -1335,7 +1392,6 @@ int collect_data_for_all_processes_from_proc(void)
                // /proc/<pid>/io
 
                if(unlikely(read_proc_pid_io(p))) {
-                       if(unlikely(!count_errors++ || debug || (p->target && p->target->debug)))
                                error("Cannot process %s/proc/%d/io", host_prefix, pid);
 
                        // on systems without /proc/X/io
@@ -1347,7 +1403,6 @@ int collect_data_for_all_processes_from_proc(void)
                // <pid> ownership
 
                if(unlikely(read_proc_pid_ownership(p))) {
-                       if(unlikely(!count_errors++ || debug || (p->target && p->target->debug)))
                                error("Cannot stat %s/proc/%d", host_prefix, pid);
                }
 
@@ -1359,12 +1414,22 @@ int collect_data_for_all_processes_from_proc(void)
                if(unlikely(p->new_entry)) {
                        if(debug) fprintf(stderr, "apps.plugin: \tJust added %s\n", p->comm);
                        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 || (p->target && p->target->debug)) fprintf(stderr, "apps.plugin: \t\tcomparing '%s' with '%s'\n", w->compare, p->comm);
 
-                               if(w->hash == hash && strcmp(w->compare, p->comm) == 0) {
+                               // 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(     (!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 && strstr(p->cmdline, w->compare))
+                                               ) {
                                        if(w->target) p->target = w->target;
                                        else p->target = w;
 
@@ -1378,7 +1443,6 @@ int collect_data_for_all_processes_from_proc(void)
                // /proc/<pid>/fd
 
                if(unlikely(read_pid_file_descriptors(p))) {
-                       if(unlikely(!count_errors++ || debug || (p->target && p->target->debug)))
                                error("Cannot process entries in %s/proc/%d/fd", host_prefix, pid);
                }
 
@@ -1389,11 +1453,6 @@ int collect_data_for_all_processes_from_proc(void)
                p->updated = 1;
        }
 
-       if(unlikely(count_errors > 1000)) {
-               error("%ld more errors encountered\n", count_errors - 1);
-               count_errors = 0;
-       }
-
        closedir(dir);
 
        return 1;