]> arthur.barton.de Git - netdata.git/commitdiff
added support for patterns (prefix, suffix, substrings) in apps_groups.conf; in the...
authorCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Sat, 16 Apr 2016 15:47:06 +0000 (18:47 +0300)
committerCosta Tsaousis (ktsaou) <costa@tsaousis.gr>
Sat, 16 Apr 2016 15:47:06 +0000 (18:47 +0300)
conf.d/apps_groups.conf
src/apps_plugin.c [changed mode: 0644->0755]

index cf6397519d0e4c7fbd83890b91cf1a518f4b0ed3..995ee5d748cbfb9747f379b66ee8029d1d71f232 100644 (file)
 # group_name: process1 process2 process3 ...
 #
 # The process names are the same to the ones returned by: ps -e
+# or /proc/PID/stat
 #
-# If a group_name starts with a -, the dimension will be hidden (cpu chart only)
+# To add process names with spaces, enclose them in quotes (single or double)
+# example: 'Plex Media Serv' "my other process"
 #
+# Wildcard support:
+# You can add an asterisk (*) at the beginning and/or the end of a process name:
+#  *name    suffix mode: will search for processes ending with 'name' (/proc/PID/stat)
+#   name*   prefix mode: will search for processes beginning with 'name' (/proc/PID/stat)
+#  *name*   substring mode: will search for 'name' in the whole command line (/proc/PID/cmdline)
+#
+# If you enter even just one *name* (substring), apps.plugin will process
+# /proc/PID/cmdline for all processes, on every iteration.
+#
+# To add process names with single quotes, enclose them in double quotes
+# example: "process with this ' single quote"
+#
+# To add process names with double quotes, enclose them in single quotes:
+# example: 'process with this " double quote'
+#
+# If a group name starts with a -, the dimension will be hidden (cpu chart only)
+#
+# If any process name starts with a +, debugging will be enabled for it
+# (debugging produces a lot of output - do not enable it in production systems)
+#
+# You can add any number of groups you like. Only the ones found running will
+# affect the charts generated. However, producing charts with hundreds of
+# dimensions may slow down your web browser.
 
-compile: cc1 cc1plus as gcc ld make automake autoconf git
+compile: cc1 cc1plus as gcc* ld make automake autoconf git
 rsync: rsync
-media: mplayer vlc xine mediatomb omxplayer omxplayer.bin kodi kodi.bin xbmc xbmc.bin mediacenter eventlircd
-squid: squid squid2 squid3 c-icap
-apache: apache apache2
-mysql: mysqld mysql
+media: mplayer vlc xine mediatomb omxplayer* kodi* xbmc* mediacenter eventlircd
+squid: squid* c-icap
+apache: apache*
+mysql: mysql*
 asterisk: asterisk
-opensips: opensips opensips-mi-pro stund
-radius: radiusd radiusclient
-fail2ban: fail2ban-server
+opensips: opensips* stund
+radius: radius*
+fail2ban: fail2ban*
 mail: dovecot imapd pop3d
 postfix: master
 nginx: nginx
@@ -38,34 +63,34 @@ mongo: mongod
 lighttpd: lighttpd
 ftpd: proftpd in.tftpd
 samba: smbd nmbd winbindd
-nfs: rpcbind rpc.statd rpc.idmapd rpc.mountd nfsd4 nfsd4_callbacks nfsd nfsiod
-ssh: ssh sshd scp
+nfs: rpcbind rpc.* nfs*
+ssh: ssh* scp
 X: X lightdm xdm pulseaudio gkrellm
-xfce: xfwm4 xfdesktop xfce4-appfinder Thunar xfsettingsd xfce4-panel
-gnome: gnome-session gdm gconfd-2 gnome-terminal gnome-screensaver gnome-settings-daemon
+xfce: xfwm4 xfdesktop xfce* Thunar xfsettingsd
+gnome: gnome-* gdm gconfd-2
 named: named rncd
-clam: clamd freshclam
-cups: cupsd cups-browsed
-ntp: ntpq ntpd
-deluge: deluge deluged
-vbox: vboxwebsrv VBoxXPCOMIPCD VBoxSVC
-log: ulogd syslogd syslog-ng rsyslogd logrotate
-nms: snmpd vnstatd smokeping zabbix_agentd monit munin-node mon openhpid
-ppp: ppp pppd pptpd pptpctrl
+clam: clam* *clam
+cups: cups*
+ntp: ntp*
+deluge: deluge*
+vbox: vbox* VBox*
+log: ulogd syslog* rsyslog* logrotate
+nms: snmpd vnstatd smokeping zabbix* monit munin* mon openhpid
+ppp: ppp* pptp*
 inetd: inetd xinetd
 openvpn: openvpn
 cjdns: cjdroute
 cron: cron atd
 ha: corosync hs_logd ha_logd stonithd
-ipvs: ipvs_syncmaster ipvs_syncbackup
+ipvs: ipvs_*
 kernel: kthreadd kauditd lockd khelper kdevtmpfs khungtaskd rpciod fsnotify_mark kthrotld iscsi_eh deferwq
-netdata: netdata apps.plugin charts.d.plugin
+netdata: netdata
 crsproxy: crsproxy
 wifi: hostapd wpa_supplicant
-system: systemd-journal systemd-udevd systemd-logind udisks-glue udisks-daemon udevd udevd connmand ipv6_addrconf dbus-daemon
+system: systemd* udisks* udevd connmand ipv6_addrconf dbus-*
 ksmd: ksmd
-lxc: lxc-start
-zfs-spl: spl_kmem_cache spl_system_task spl_dynamic_tas 
-zfs-posix: z_import z_unmount z_rd_iss z_rd_int_ z_wr_iss z_wr_int z_fr_iss z_fr_int z_cl_iss z_ioctl_iss z_zvol z_iput
-zfs-txg: txg_quiesce txg_sync zil_clean
-zfs-arc: arc_prune arc_reclaim arc_user_evicts l2arc_feed 
+lxc: lxc*
+zfs-spl: spl_* 
+zfs-posix: z_*
+zfs-txg: txg_* zil_*
+zfs-arc: arc_* l2arc* 
old mode 100644 (file)
new mode 100755 (executable)
index 33ffcd9..6be562f
@@ -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;
@@ -294,9 +296,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 +366,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 +399,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)
@@ -425,9 +435,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)
@@ -450,12 +462,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 +486,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 +526,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 +533,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;
 
-               // if(debug) fprintf(stderr, "apps.plugin: \tread %s\n", buffer);
+       procfile_set_quotes(ff, "'\"");
 
-               char *s = buffer, *t, *p;
-               s = trim(s);
-               if(!s || !*s || *s == '#') continue;
+       ff = procfile_readall(ff);
+       if(!ff) {
+               procfile_close(ff);
+               return 1;
+       }
 
-               if(debug) fprintf(stderr, "apps.plugin: \tread %s\n", s);
+       unsigned long line, lines = procfile_lines(ff);
 
-               // the target name
-               t = strsep(&s, ":");
-               if(t) t = trim(t);
+       for(line = 0; line < lines ;line++) {
+               unsigned long word, words = procfile_linewords(ff, line);
+               struct target *w = NULL;
+
+               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 +616,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 +791,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 +862,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));
@@ -1318,6 +1369,15 @@ int collect_data_for_all_processes_from_proc(void)
                        p->ppid = 0;
                }
 
+               // --------------------------------------------------------------------
+               // /proc/<pid>/cmdline
+
+               if(proc_pid_cmdline_is_needed) {
+                       if(unlikely(read_proc_pid_cmdline(p))) {
+                               if(!count_errors++ || debug || (p->target && p->target->debug))
+                                       error("Cannot process %s/proc/%d/cmdline", host_prefix, pid);
+                       }
+               }
 
                // --------------------------------------------------------------------
                // /proc/<pid>/statm
@@ -1359,12 +1419,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;