]> arthur.barton.de Git - netdata.git/commitdiff
Merge pull request #251 from ktsaou/master
authorCosta Tsaousis <costa@tsaousis.gr>
Sat, 16 Apr 2016 16:08:13 +0000 (19:08 +0300)
committerCosta Tsaousis <costa@tsaousis.gr>
Sat, 16 Apr 2016 16:08:13 +0000 (19:08 +0300)
apps.plugin pattern matching on processes for grouping them

.gitignore
CMakeLists.txt [new file with mode: 0755]
conf.d/apps_groups.conf
src/apps_plugin.c
src/procfile.c
src/procfile.h

index d3d8aaf802627cab5b58d9eb7994ae47eeb19d5f..f4b9fe85e3ba6cab7bbe1b561feb812b530f9c80 100644 (file)
@@ -32,7 +32,6 @@ netdata.spec
 .cproject
 .idea/
 .project
-CMakeLists.txt
 README
 TODO.md
 asan_symbolize.py
@@ -67,5 +66,8 @@ netdata-uninstaller.sh
 
 gmon.out
 gmon.txt
-
 apps.plugin-profiler.sh
+
+CMakeCache.txt
+CMakeFiles/
+cmake_install.cmake
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100755 (executable)
index 0000000..2c7abd2
--- /dev/null
@@ -0,0 +1,94 @@
+
+# This file is just a hack to make netdata
+# open in Clion
+# It cannot build netdata
+
+cmake_minimum_required(VERSION 3.3)
+project(netdata)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+
+set(NETDATA_SOURCE_FILES
+       src/appconfig.c
+       src/appconfig.h
+       src/avl.c
+       src/avl.h
+       src/common.c
+       src/common.h
+       src/daemon.c
+       src/daemon.h
+       src/dictionary.c
+       src/dictionary.h
+       src/global_statistics.c
+       src/global_statistics.h
+       src/log.c
+       src/log.h
+       src/main.c
+       src/main.h
+       src/plugin_checks.c
+       src/plugin_checks.h
+       src/plugin_idlejitter.c
+       src/plugin_idlejitter.h
+       src/plugin_nfacct.c
+       src/plugin_nfacct.h
+       src/plugin_proc.c
+       src/plugin_proc.h
+       src/plugins_d.c
+       src/plugins_d.h
+       src/plugin_tc.c
+       src/plugin_tc.h
+       src/popen.c
+       src/popen.h
+       src/proc_diskstats.c
+       src/procfile.c
+       src/procfile.h
+       src/proc_interrupts.c
+       src/proc_loadavg.c
+       src/proc_meminfo.c
+       src/proc_net_dev.c
+       src/proc_net_ip_vs_stats.c
+       src/proc_net_netstat.c
+       src/proc_net_rpc_nfsd.c
+       src/proc_net_snmp6.c
+       src/proc_net_snmp.c
+       src/proc_net_stat_conntrack.c
+       src/proc_net_stat_synproxy.c
+       src/proc_softirqs.c
+       src/proc_stat.c
+       src/proc_sys_kernel_random_entropy_avail.c
+       src/proc_vmstat.c
+       src/rrd2json.c
+       src/rrd2json.h
+       src/rrd.c
+       src/rrd.h
+       src/storage_number.c
+       src/storage_number.h
+       src/sys_kernel_mm_ksm.c
+       src/unit_test.c
+       src/unit_test.h
+       src/url.c
+       src/url.h
+       src/web_buffer.c
+       src/web_buffer.h
+       src/web_client.c
+       src/web_client.h
+       src/web_server.c
+       src/web_server.h
+        config.h)
+
+set(APPS_PLUGIN_SOURCE_FILES
+       src/appconfig.c
+       src/appconfig.h
+       src/apps_plugin.c
+       src/avl.c
+       src/avl.h
+       src/common.c
+       src/common.h
+       src/log.c
+       src/log.h
+        config.h)
+
+add_definitions(-DHAVE_CONFIG_H -DNETDATA_INTERNAL_CHECKS=1 -DCACHE_DIR="/tmp" -DCONFIG_DIR="/tmp" -DLOG_DIR="/tmp" -DPLUGINS_DIR="/tmp" -DWEB_DIR="/tmp")
+
+add_executable(netdata ${NETDATA_SOURCE_FILES})
+add_executable(apps.plugin ${APPS_PLUGIN_SOURCE_FILES})
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* 
index e0232361fc9407a2ae2a85d633a07c532a3a24e0..6be562f7951cdcca79f75424a8030ec848bf9398 100644 (file)
 #include "common.h"
 #include "log.h"
 #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;
 
@@ -49,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;
@@ -213,20 +216,20 @@ char *strdup_debug(const char *file, int line, const char *function, const char
 // system functions
 // to retrieve settings of the system
 
-procfile *ff = NULL;
-
 long get_system_cpus(void) {
+       procfile *ff = NULL;
+
        int processors = 0;
 
        char filename[FILENAME_MAX + 1];
        snprintf(filename, FILENAME_MAX, "%s/proc/stat", host_prefix);
 
-       ff = procfile_reopen(ff, filename, "", PROCFILE_FLAG_DEFAULT);
+       ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
        if(!ff) return 1;
 
        ff = procfile_readall(ff);
        if(!ff) {
-               // procfile_close(ff);
+               procfile_close(ff);
                return 1;
        }
 
@@ -239,28 +242,29 @@ long get_system_cpus(void) {
        processors--;
        if(processors < 1) processors = 1;
 
-       // procfile_close(ff);
+       procfile_close(ff);
        return processors;
 }
 
 long get_system_pid_max(void) {
+       procfile *ff = NULL;
        long mpid = 32768;
 
        char filename[FILENAME_MAX + 1];
        snprintf(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", host_prefix);
-       ff = procfile_reopen(ff, filename, "", PROCFILE_FLAG_DEFAULT);
+       ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
        if(!ff) return mpid;
 
        ff = procfile_readall(ff);
        if(!ff) {
-               // procfile_close(ff);
+               procfile_close(ff);
                return mpid;
        }
 
        mpid = atol(procfile_lineword(ff, 0, 0));
        if(!mpid) mpid = 32768;
 
-       // procfile_close(ff);
+       procfile_close(ff);
        return mpid;
 }
 
@@ -292,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;
@@ -359,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;
@@ -389,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)
@@ -423,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)
@@ -448,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)) {
@@ -462,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(id[0] == '-') w->hidden = 1;
+       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;
+       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;
 }
@@ -482,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);
@@ -490,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;
-
-                       strncpy(&buffer[blen], p, 4096 - blen);
-                       blen = strlen(buffer);
-
-                       while(buffer[blen - 1] == '\\') {
-                               buffer[blen - 1] = ' ';
-
-                               if((p = strsep(&s, " ")))
-                                       p = trim(p);
+               if(w) {
+                       int tdebug = 0, thidden = 0;
 
-                               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;
 }
@@ -591,6 +616,8 @@ 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;
        // int32_t pgrp;
@@ -764,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];
 
@@ -783,15 +837,22 @@ int read_proc_pid_ownership(struct pid_stat *p) {
 }
 
 int read_proc_pid_stat(struct pid_stat *p) {
+       static procfile *ff = NULL;
+
        char filename[FILENAME_MAX + 1];
 
        snprintf(filename, FILENAME_MAX, "%s/proc/%d/stat", host_prefix, p->pid);
 
        // ----------------------------------------
 
+       int set_quotes = (!ff)?1:0;
+
        ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
        if(!ff) return 1;
 
+       // if(set_quotes) procfile_set_quotes(ff, "()");
+       if(set_quotes) procfile_set_open_close(ff, "(", ")");
+
        ff = procfile_readall(ff);
        if(!ff) {
                // procfile_close(ff);
@@ -800,27 +861,10 @@ int read_proc_pid_stat(struct pid_stat *p) {
 
        file_counter++;
 
-       p->comm[0] = '\0';
-       p->comm[MAX_COMPARE_NAME] = '\0';
-       size_t blen = 0;
-
-       char *s = procfile_lineword(ff, 0, 1);
-       if(*s == '(') s++;
-       size_t len = strlen(s);
+       // parse the process name
        unsigned int i = 0;
-       while(len && s[len - 1] != ')') {
-               if(blen < MAX_COMPARE_NAME) {
-                       strncpy(&p->comm[blen], s, MAX_COMPARE_NAME - blen);
-                       blen = strlen(p->comm);
-               }
-
-               i++;
-               s = procfile_lineword(ff, 0, 1+i);
-               len = strlen(s);
-       }
-       if(len && s[len - 1] == ')') s[len - 1] = '\0';
-       if(blen < MAX_COMPARE_NAME)
-               strncpy(&p->comm[blen], s, MAX_COMPARE_NAME - blen);
+       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));
        // comm is at 1
@@ -867,13 +911,16 @@ int read_proc_pid_stat(struct pid_stat *p) {
        // p->guest_time        = strtoull(procfile_lineword(ff, 0, 42+i), NULL, 10);
        // p->cguest_time       = strtoull(procfile_lineword(ff, 0, 43), NULL, 10);
 
-       if(debug || (p->target && p->target->debug)) fprintf(stderr, "apps.plugin: VALUES: %s utime=%llu, stime=%llu, cutime=%llu, cstime=%llu, minflt=%llu, majflt=%llu, cminflt=%llu, cmajflt=%llu, threads=%d\n", p->comm, p->utime, p->stime, p->cutime, p->cstime, p->minflt, p->majflt, p->cminflt, p->cmajflt, p->num_threads);
+       if(debug || (p->target && p->target->debug))
+               fprintf(stderr, "apps.plugin: READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' VALUES: utime=%llu, stime=%llu, cutime=%llu, cstime=%llu, minflt=%llu, majflt=%llu, cminflt=%llu, cmajflt=%llu, threads=%d\n", host_prefix, p->pid, p->comm, p->utime, p->stime, p->cutime, p->cstime, p->minflt, p->majflt, p->cminflt, p->cmajflt, p->num_threads);
 
        // procfile_close(ff);
        return 0;
 }
 
 int read_proc_pid_statm(struct pid_stat *p) {
+       static procfile *ff = NULL;
+
        char filename[FILENAME_MAX + 1];
 
        snprintf(filename, FILENAME_MAX, "%s/proc/%d/statm", host_prefix, p->pid);
@@ -902,6 +949,8 @@ int read_proc_pid_statm(struct pid_stat *p) {
 }
 
 int read_proc_pid_io(struct pid_stat *p) {
+       static procfile *ff = NULL;
+
        char filename[FILENAME_MAX + 1];
 
        snprintf(filename, FILENAME_MAX, "%s/proc/%d/io", host_prefix, p->pid);
@@ -1320,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
@@ -1361,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;
 
@@ -2367,6 +2435,9 @@ int main(int argc, char **argv)
        // set the name for logging
        program_name = "apps.plugin";
 
+       // disable syslog for apps.plugin
+       error_log_syslog = 0;
+
        host_prefix = getenv("NETDATA_HOST_PREFIX");
        if(host_prefix == NULL) {
                info("NETDATA_HOST_PREFIX is not passed from netdata");
index 7a48579593caceb6fdfccbab710f359acc5d1142..1fc33ef5f26625afe3acc4cb6db2e55791859011 100644 (file)
@@ -20,6 +20,7 @@
 #include "common.h"
 #include "log.h"
 #include "procfile.h"
+#include "../config.h"
 
 #define PF_PREFIX "PROCFILE"
 
@@ -142,6 +143,9 @@ void pflines_free(pflines *fl) {
 #define PF_CHAR_IS_SEPARATOR   ' '
 #define PF_CHAR_IS_NEWLINE             'N'
 #define PF_CHAR_IS_WORD                        'W'
+#define PF_CHAR_IS_QUOTE        'Q'
+#define PF_CHAR_IS_OPEN         'O'
+#define PF_CHAR_IS_CLOSE        'C'
 
 void procfile_close(procfile *ff) {
        debug(D_PROCFILE, PF_PREFIX ": Closing file '%s'", ff->filename);
@@ -156,17 +160,82 @@ void procfile_close(procfile *ff) {
 procfile *procfile_parser(procfile *ff) {
        debug(D_PROCFILE, PF_PREFIX ": Parsing file '%s'", ff->filename);
 
-       char *s = ff->data, *e = ff->data, *t = ff->data;
+       char *s = ff->data, *e = &ff->data[ff->len], *t = ff->data, quote = 0;
        uint32_t l = 0, w = 0;
-       e += ff->len;
+       int opened = 0;
 
        ff->lines = pflines_add(ff->lines, w);
        if(unlikely(!ff->lines)) goto cleanup;
 
        while(likely(s < e)) {
+               // we are not at the end
+
                switch(ff->separators[(int)(*s)]) {
+                       case PF_CHAR_IS_OPEN:
+                               if(s == t) {
+                                       opened++;
+                                       t = ++s;
+                               }
+                               else if(opened) {
+                                       opened++;
+                                       s++;
+                               }
+                               else
+                                       s++;
+                               continue;
+
+                       case PF_CHAR_IS_CLOSE:
+                               if(opened) {
+                                       opened--;
+
+                                       if(!opened) {
+                                               *s = '\0';
+                                               ff->words = pfwords_add(ff->words, t);
+                                               if(unlikely(!ff->words)) goto cleanup;
+
+                                               ff->lines->lines[l].words++;
+                                               w++;
+
+                                               t = ++s;
+                                       }
+                                       else
+                                               s++;
+                               }
+                               else
+                                       s++;
+                               continue;
+
+                       case PF_CHAR_IS_QUOTE:
+                               if(unlikely(!quote && s == t)) {
+                                       // quote opened at the beginning
+                                       quote = *s;
+                                       t = ++s;
+                               }
+                               else if(unlikely(quote && quote == *s)) {
+                                       // quote closed
+                                       quote = 0;
+
+                                       *s = '\0';
+                                       ff->words = pfwords_add(ff->words, t);
+                                       if(unlikely(!ff->words)) goto cleanup;
+
+                                       ff->lines->lines[l].words++;
+                                       w++;
+
+                                       t = ++s;
+                               }
+                               else
+                                       s++;
+                               continue;
+
                        case PF_CHAR_IS_SEPARATOR:
-                               if(likely(s == t)) {
+                               if(unlikely(quote || opened)) {
+                                       // we are inside a quote
+                                       s++;
+                                       continue;
+                               }
+
+                               if(unlikely(s == t)) {
                                        // skip all leading white spaces
                                        t = ++s;
                                        continue;
@@ -209,9 +278,10 @@ procfile *procfile_parser(procfile *ff) {
                }
        }
 
-       if(likely(s != t)) {
+       if(likely(s > t && t < e)) {
                // the last word
-               if(likely(ff->len < ff->size)) *s = '\0';
+               if(likely(ff->len < ff->size))
+                       *s = '\0';
                else {
                        // we are going to loose the last byte
                        ff->data[ff->size - 1] = '\0';
@@ -309,10 +379,53 @@ static void procfile_set_separators(procfile *ff, const char *separators) {
        while(likely(ffd != ffe)) *ffs++ = *ffd++;
 
        // set the separators
-       if(unlikely(!separators)) separators = " \t=|";
+       if(unlikely(!separators))
+               separators = " \t=|";
+
        ffs = ff->separators;
        const char *s = separators;
-       while(likely(*s)) ffs[(int)*s++] = PF_CHAR_IS_SEPARATOR;
+       while(likely(*s))
+               ffs[(int)*s++] = PF_CHAR_IS_SEPARATOR;
+}
+
+void procfile_set_quotes(procfile *ff, const char *quotes) {
+       // remove all quotes
+       int i;
+       for(i = 0; i < 256 ; i++)
+               if(ff->separators[i] == PF_CHAR_IS_QUOTE)
+                       ff->separators[i] = PF_CHAR_IS_WORD;
+
+       // if nothing given, return
+       if(unlikely(!quotes || !*quotes))
+               return;
+
+       // set the quotes
+       char *ffs = ff->separators;
+       const char *s = quotes;
+       while(likely(*s))
+               ffs[(int)*s++] = PF_CHAR_IS_QUOTE;
+}
+
+void procfile_set_open_close(procfile *ff, const char *open, const char *close) {
+       // remove all open/close
+       int i;
+       for(i = 0; i < 256 ; i++)
+               if(ff->separators[i] == PF_CHAR_IS_OPEN || ff->separators[i] == PF_CHAR_IS_CLOSE)
+                       ff->separators[i] = PF_CHAR_IS_WORD;
+
+       // if nothing given, return
+       if(unlikely(!open || !*open || !close || !*close))
+               return;
+
+       // set the openings
+       char *ffs = ff->separators;
+       const char *s = open;
+       while(likely(*s))
+               ffs[(int)*s++] = PF_CHAR_IS_OPEN;
+
+       s = close;
+       while(likely(*s))
+               ffs[(int)*s++] = PF_CHAR_IS_CLOSE;
 }
 
 procfile *procfile_open(const char *filename, const char *separators, uint32_t flags) {
index ce2f9bc927276fb09a141e6b32e5daa0df121404..122e153f1eb7c76bc320fa98ca90b38afaf8d34e 100644 (file)
@@ -86,6 +86,9 @@ extern procfile *procfile_reopen(procfile *ff, const char *filename, const char
 // example walk-through a procfile parsed file
 extern void procfile_print(procfile *ff);
 
+extern void procfile_set_quotes(procfile *ff, const char *quotes);
+extern void procfile_set_open_close(procfile *ff, const char *open, const char *close);
+
 // ----------------------------------------------------------------------------
 
 // set this to 1, to have procfile adapt its initial buffer allocation to the max allocation used so far