@cryptotaxi247 / netdata-1 / commits / ca43d15d1

added support for patterns (prefix, suffix, substrings) in apps_groups.conf; in the case of substrings the whole command line is evaluated; #146 #142

Costa Tsaousis (ktsaou) committed Apr 16, 2016 at 18:47 UTC ca43d15d12c3495c33c001c76736a3fb17045ef8
2 files changed +208 -113
conf.d/apps_groups.conf
+54 -29
@@ -16,20 +16,45 @@
16 # group_name: process1 process2 process3 ...
17 #
18 # The process names are the same to the ones returned by: ps -e
19 +# or /proc/PID/stat
20 #
20 -# If a group_name starts with a -, the dimension will be hidden (cpu chart only)
21 +# To add process names with spaces, enclose them in quotes (single or double)
22 +# example: 'Plex Media Serv' "my other process"
23 #
24 +# Wildcard support:
25 +# You can add an asterisk (*) at the beginning and/or the end of a process name:
26 +# *name suffix mode: will search for processes ending with 'name' (/proc/PID/stat)
27 +# name* prefix mode: will search for processes beginning with 'name' (/proc/PID/stat)
28 +# *name* substring mode: will search for 'name' in the whole command line (/proc/PID/cmdline)
29 +#
30 +# If you enter even just one *name* (substring), apps.plugin will process
31 +# /proc/PID/cmdline for all processes, on every iteration.
32 +#
33 +# To add process names with single quotes, enclose them in double quotes
34 +# example: "process with this ' single quote"
35 +#
36 +# To add process names with double quotes, enclose them in single quotes:
37 +# example: 'process with this " double quote'
38 +#
39 +# If a group name starts with a -, the dimension will be hidden (cpu chart only)
40 +#
41 +# If any process name starts with a +, debugging will be enabled for it
42 +# (debugging produces a lot of output - do not enable it in production systems)
43 +#
44 +# You can add any number of groups you like. Only the ones found running will
45 +# affect the charts generated. However, producing charts with hundreds of
46 +# dimensions may slow down your web browser.
47
23 -compile: cc1 cc1plus as gcc ld make automake autoconf git
48 +compile: cc1 cc1plus as gcc* ld make automake autoconf git
49 rsync: rsync
25 -media: mplayer vlc xine mediatomb omxplayer omxplayer.bin kodi kodi.bin xbmc xbmc.bin mediacenter eventlircd
26 -squid: squid squid2 squid3 c-icap
27 -apache: apache apache2
28 -mysql: mysqld mysql
50 +media: mplayer vlc xine mediatomb omxplayer* kodi* xbmc* mediacenter eventlircd
51 +squid: squid* c-icap
52 +apache: apache*
53 +mysql: mysql*
54 asterisk: asterisk
30 -opensips: opensips opensips-mi-pro stund
31 -radius: radiusd radiusclient
32 -fail2ban: fail2ban-server
55 +opensips: opensips* stund
56 +radius: radius*
57 +fail2ban: fail2ban*
58 mail: dovecot imapd pop3d
59 postfix: master
60 nginx: nginx
@@ -38,34 +63,34 @@ mongo: mongod
63 lighttpd: lighttpd
64 ftpd: proftpd in.tftpd
65 samba: smbd nmbd winbindd
41 -nfs: rpcbind rpc.statd rpc.idmapd rpc.mountd nfsd4 nfsd4_callbacks nfsd nfsiod
42 -ssh: ssh sshd scp
66 +nfs: rpcbind rpc.* nfs*
67 +ssh: ssh* scp
68 X: X lightdm xdm pulseaudio gkrellm
44 -xfce: xfwm4 xfdesktop xfce4-appfinder Thunar xfsettingsd xfce4-panel
45 -gnome: gnome-session gdm gconfd-2 gnome-terminal gnome-screensaver gnome-settings-daemon
69 +xfce: xfwm4 xfdesktop xfce* Thunar xfsettingsd
70 +gnome: gnome-* gdm gconfd-2
71 named: named rncd
47 -clam: clamd freshclam
48 -cups: cupsd cups-browsed
49 -ntp: ntpq ntpd
50 -deluge: deluge deluged
51 -vbox: vboxwebsrv VBoxXPCOMIPCD VBoxSVC
52 -log: ulogd syslogd syslog-ng rsyslogd logrotate
53 -nms: snmpd vnstatd smokeping zabbix_agentd monit munin-node mon openhpid
54 -ppp: ppp pppd pptpd pptpctrl
72 +clam: clam* *clam
73 +cups: cups*
74 +ntp: ntp*
75 +deluge: deluge*
76 +vbox: vbox* VBox*
77 +log: ulogd syslog* rsyslog* logrotate
78 +nms: snmpd vnstatd smokeping zabbix* monit munin* mon openhpid
79 +ppp: ppp* pptp*
80 inetd: inetd xinetd
81 openvpn: openvpn
82 cjdns: cjdroute
83 cron: cron atd
84 ha: corosync hs_logd ha_logd stonithd
60 -ipvs: ipvs_syncmaster ipvs_syncbackup
85 +ipvs: ipvs_*
86 kernel: kthreadd kauditd lockd khelper kdevtmpfs khungtaskd rpciod fsnotify_mark kthrotld iscsi_eh deferwq
62 -netdata: netdata apps.plugin charts.d.plugin
87 +netdata: netdata
88 crsproxy: crsproxy
89 wifi: hostapd wpa_supplicant
65 -system: systemd-journal systemd-udevd systemd-logind udisks-glue udisks-daemon udevd udevd connmand ipv6_addrconf dbus-daemon
90 +system: systemd* udisks* udevd connmand ipv6_addrconf dbus-*
91 ksmd: ksmd
67 -lxc: lxc-start
68 -zfs-spl: spl_kmem_cache spl_system_task spl_dynamic_tas
69 -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
70 -zfs-txg: txg_quiesce txg_sync zil_clean
71 -zfs-arc: arc_prune arc_reclaim arc_user_evicts l2arc_feed
92 +lxc: lxc*
93 +zfs-spl: spl_*
94 +zfs-posix: z_*
95 +zfs-txg: txg_* zil_*
96 +zfs-arc: arc_* l2arc*
src/apps_plugin.c
+154 -84
@@ -39,8 +39,9 @@
39 #include "procfile.h"
40 #include "../config.h"
41
42 -#define MAX_COMPARE_NAME 15
42 +#define MAX_COMPARE_NAME 100
43 #define MAX_NAME 100
44 +#define MAX_CMDLINE 1024
45
46 unsigned long long Hertz = 1;
47
@@ -50,6 +51,7 @@ int debug = 0;
51
52 int update_every = 1;
53 unsigned long long file_counter = 0;
54 +int proc_pid_cmdline_is_needed = 0;
55
56 char *host_prefix = "";
57 char *config_dir = CONFIG_DIR;
@@ -294,9 +296,12 @@ unsigned long long get_system_hertz(void)
296
297 struct target {
298 char compare[MAX_COMPARE_NAME + 1];
297 - uint32_t hash;
299 + uint32_t comparehash;
300 + size_t comparelen;
301
302 char id[MAX_NAME + 1];
303 + uint32_t idhash;
304 +
305 char name[MAX_NAME + 1];
306
307 uid_t uid;
@@ -361,6 +366,9 @@ struct target {
366 int exposed; // if set, we have sent this to netdata
367 int hidden; // if set, we set the hidden flag on the dimension
368 int debug;
369 + int ends_with;
370 + int starts_with; // if set, the compare string matches only the
371 + // beginning of the command
372
373 struct target *target; // the one that will be reported to netdata
374 struct target *next;
@@ -391,9 +399,11 @@ struct target *get_users_target(uid_t uid)
399 }
400
401 snprintf(w->compare, MAX_COMPARE_NAME, "%d", uid);
394 - w->hash = simple_hash(w->compare);
402 + w->comparehash = simple_hash(w->compare);
403 + w->comparelen = strlen(w->compare);
404
405 snprintf(w->id, MAX_NAME, "%d", uid);
406 + w->idhash = simple_hash(w->id);
407
408 struct passwd *pw = getpwuid(uid);
409 if(!pw)
@@ -425,9 +435,11 @@ struct target *get_groups_target(gid_t gid)
435 }
436
437 snprintf(w->compare, MAX_COMPARE_NAME, "%d", gid);
428 - w->hash = simple_hash(w->compare);
438 + w->comparehash = simple_hash(w->compare);
439 + w->comparelen = strlen(w->compare);
440
441 snprintf(w->id, MAX_NAME, "%d", gid);
442 + w->idhash = simple_hash(w->id);
443
444 struct group *gr = getgrgid(gid);
445 if(!gr)
@@ -450,12 +462,22 @@ struct target *get_groups_target(gid_t gid)
462 // there are targets that are just aggregated to other target (the second argument)
463 struct target *get_apps_groups_target(const char *id, struct target *target)
464 {
465 + int tdebug = 0, thidden = 0, ends_with = 0;
466 const char *nid = id;
454 - if(nid[0] == '-') nid++;
467 +
468 + while(nid[0] == '-' || nid[0] == '+' || nid[0] == '*') {
469 + if(nid[0] == '-') thidden = 1;
470 + if(nid[0] == '+') tdebug = 1;
471 + if(nid[0] == '*') ends_with = 1;
472 + nid++;
473 + }
474 + uint32_t hash = simple_hash(id);
475
476 struct target *w;
457 - for(w = apps_groups_root_target ; w ; w = w->next)
458 - if(strncmp(nid, w->id, MAX_NAME) == 0) return w;
477 + for(w = apps_groups_root_target ; w ; w = w->next) {
478 + if(w->idhash == hash && strncmp(nid, w->id, MAX_NAME) == 0)
479 + return w;
480 + }
481
482 w = calloc(sizeof(struct target), 1);
483 if(unlikely(!w)) {
@@ -464,19 +486,39 @@ struct target *get_apps_groups_target(const char *id, struct target *target)
486 }
487
488 strncpy(w->id, nid, MAX_NAME);
489 + w->idhash = simple_hash(w->id);
490 +
491 strncpy(w->name, nid, MAX_NAME);
492 +
493 strncpy(w->compare, nid, MAX_COMPARE_NAME);
469 - w->hash = simple_hash(w->compare);
494 + int len = strlen(w->compare);
495 + if(w->compare[len - 1] == '*') {
496 + w->compare[len - 1] = '\0';
497 + w->starts_with = 1;
498 + }
499 + w->ends_with = ends_with;
500 +
501 + if(w->starts_with && w->ends_with)
502 + proc_pid_cmdline_is_needed = 1;
503
471 - if(id[0] == '-') w->hidden = 1;
504 + w->comparehash = simple_hash(w->compare);
505 + w->comparelen = strlen(w->compare);
506
507 + w->hidden = thidden;
508 + w->debug = tdebug;
509 w->target = target;
510
511 w->next = apps_groups_root_target;
512 apps_groups_root_target = w;
513
514 if(unlikely(debug))
479 - fprintf(stderr, "apps.plugin: adding hook for process '%s', compare '%s' on target '%s'\n", w->id, w->compare, w->target?w->target->id:"");
515 + fprintf(stderr, "apps.plugin: ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s\n"
516 + , w->id
517 + , w->compare, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact"))
518 + , w->target?w->target->id:w->id
519 + , (w->hidden)?"hidden":"-"
520 + , (w->debug)?"debug":"-"
521 + );
522
523 return w;
524 }
@@ -484,7 +526,6 @@ struct target *get_apps_groups_target(const char *id, struct target *target)
526 // read the apps_groups.conf file
527 int read_apps_groups_conf(const char *name)
528 {
487 - char buffer[4096+1];
529 char filename[FILENAME_MAX + 1];
530
531 snprintf(filename, FILENAME_MAX, "%s/apps_%s.conf", config_dir, name);
@@ -492,95 +533,77 @@ int read_apps_groups_conf(const char *name)
533 if(unlikely(debug))
534 fprintf(stderr, "apps.plugin: process groups file: '%s'\n", filename);
535
495 - FILE *fp = fopen(filename, "r");
496 - if(unlikely(!fp)) {
497 - error("Cannot open file '%s'", filename);
498 - return 1;
499 - }
536 + // ----------------------------------------
537
501 - long line = 0;
502 - while(fgets(buffer, 4096, fp) != NULL) {
503 - int whidden = 0, wdebug = 0;
504 - line++;
538 + procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT);
539 + if(!ff) return 1;
540
506 - // if(debug) fprintf(stderr, "apps.plugin: \tread %s\n", buffer);
541 + procfile_set_quotes(ff, "'\"");
542
508 - char *s = buffer, *t, *p;
509 - s = trim(s);
510 - if(!s || !*s || *s == '#') continue;
543 + ff = procfile_readall(ff);
544 + if(!ff) {
545 + procfile_close(ff);
546 + return 1;
547 + }
548
512 - if(debug) fprintf(stderr, "apps.plugin: \tread %s\n", s);
549 + unsigned long line, lines = procfile_lines(ff);
550
514 - // the target name
515 - t = strsep(&s, ":");
516 - if(t) t = trim(t);
551 + for(line = 0; line < lines ;line++) {
552 + unsigned long word, words = procfile_linewords(ff, line);
553 + struct target *w = NULL;
554 +
555 + char *t = procfile_lineword(ff, line, 0);
556 if(!t || !*t) continue;
557
519 - while(t[0]) {
520 - int stop = 1;
558 + for(word = 0; word < words ;word++) {
559 + char *s = procfile_lineword(ff, line, word);
560 + if(!s || !*s) continue;
561 + if(*s == '#') break;
562
522 - switch(t[0]) {
523 - case '-':
524 - stop = 0;
525 - whidden = 1;
526 - t++;
527 - break;
563 + if(t == s) continue;
564
529 - case '+':
530 - stop = 0;
531 - wdebug = 1;
532 - t++;
533 - break;
565 + struct target *n = get_apps_groups_target(s, w);
566 + if(!n) {
567 + error("Cannot create target '%s' (line %d, word %d)", s, line, word);
568 + continue;
569 }
570
536 - if(stop) break;
571 + if(!w) w = n;
572 }
573
539 - if(debug) fprintf(stderr, "apps.plugin: \t\ttarget %s\n", t);
540 -
541 - struct target *w = NULL;
542 - long count = 0;
543 - int blen = 0;
544 - char buffer[4097] = "";
545 - buffer[4096] = '\0';
546 -
547 - // the process names
548 - while((p = strsep(&s, " "))) {
549 - p = trim(p);
550 - if(!p || !*p) continue;
574 + if(w) {
575 + int tdebug = 0, thidden = 0;
576
552 - strncpy(&buffer[blen], p, 4096 - blen);
553 - blen = strlen(buffer);
554 -
555 - while(buffer[blen - 1] == '\\') {
556 - buffer[blen - 1] = ' ';
557 -
558 - if((p = strsep(&s, " ")))
559 - p = trim(p);
560 -
561 - if(!p || !*p) p = " ";
562 - strncpy(&buffer[blen], p, 4096 - blen);
563 - blen = strlen(buffer);
577 + while(t[0] == '-' || t[0] == '+') {
578 + if(t[0] == '-') thidden = 1;
579 + if(t[0] == '+') tdebug = 1;
580 + t++;
581 }
582
566 - struct target *n = get_apps_groups_target(buffer, w);
567 - n->hidden = whidden;
568 - n->debug = wdebug;
569 - if(!w) w = n;
570 -
571 - buffer[0] = '\0';
572 - blen = 0;
573 -
574 - count++;
583 + strncpy(w->name, t, MAX_NAME);
584 + w->name[MAX_NAME] = '\0';
585 + w->hidden = thidden;
586 + w->debug = tdebug;
587 +
588 + if(unlikely(debug))
589 + fprintf(stderr, "apps.plugin: AGGREGATION TARGET NAME '%s' on ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s\n"
590 + , w->name
591 + , w->id
592 + , w->compare, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact"))
593 + , w->target?w->target->id:w->id
594 + , (w->hidden)?"hidden":"-"
595 + , (w->debug)?"debug":"-"
596 + );
597 }
576 -
577 - if(w) strncpy(w->name, t, MAX_NAME);
578 - if(!count) error("The line %ld on file '%s', for group '%s' does not state any process names.", line, filename, t);
598 }
580 - fclose(fp);
599
582 - apps_groups_default_target = get_apps_groups_target("+p!o@w#e$i^r&7*5(-i)l-o_", NULL); // match nothing
583 - strncpy(apps_groups_default_target->name, "other", MAX_NAME);
600 + procfile_close(ff);
601 +
602 + apps_groups_default_target = get_apps_groups_target("p+!o@w#e$i^r&7*5(-i)l-o_", NULL); // match nothing
603 + if(!apps_groups_default_target)
604 + error("Cannot create default target");
605 + else
606 + strncpy(apps_groups_default_target->name, "other", MAX_NAME);
607
608 return 0;
609 }
@@ -593,6 +616,7 @@ int read_apps_groups_conf(const char *name)
616 struct pid_stat {
617 int32_t pid;
618 char comm[MAX_COMPARE_NAME + 1];
619 + char cmdline[MAX_CMDLINE + 1];
620
621 // char state;
622 int32_t ppid;
@@ -767,6 +791,33 @@ void del_pid_entry(pid_t pid)
791 // ----------------------------------------------------------------------------
792 // update pids from proc
793
794 +int read_proc_pid_cmdline(struct pid_stat *p) {
795 + char filename[FILENAME_MAX + 1];
796 + snprintf(filename, FILENAME_MAX, "%s/proc/%d/cmdline", host_prefix, p->pid);
797 +
798 + int fd = open(filename, O_RDONLY, 0666);
799 + if(unlikely(fd == -1)) return 1;
800 +
801 + int i, bytes = read(fd, p->cmdline, MAX_CMDLINE);
802 + close(fd);
803 +
804 + if(bytes <= 0) {
805 + // copy the command to the command line
806 + strncpy(p->cmdline, p->comm, MAX_CMDLINE);
807 + p->cmdline[MAX_CMDLINE] = '\0';
808 + return 0;
809 + }
810 +
811 + p->cmdline[bytes] = '\0';
812 + for(i = 0; i < bytes ; i++)
813 + if(!p->cmdline[i]) p->cmdline[i] = ' ';
814 +
815 + if(unlikely(debug))
816 + fprintf(stderr, "Read file '%s' contents: %s\n", filename, p->cmdline);
817 +
818 + return 0;
819 +}
820 +
821 int read_proc_pid_ownership(struct pid_stat *p) {
822 char filename[FILENAME_MAX + 1];
823
@@ -811,8 +862,8 @@ int read_proc_pid_stat(struct pid_stat *p) {
862 file_counter++;
863
864 // parse the process name
814 - unsigned int i = 1;
815 - strncpy(p->comm, procfile_lineword(ff, 0, i), MAX_COMPARE_NAME);
865 + unsigned int i = 0;
866 + strncpy(p->comm, procfile_lineword(ff, 0, 1), MAX_COMPARE_NAME);
867 p->comm[MAX_COMPARE_NAME] = '\0';
868
869 // p->pid = atol(procfile_lineword(ff, 0, 0+i));
@@ -1318,6 +1369,15 @@ int collect_data_for_all_processes_from_proc(void)
1369 p->ppid = 0;
1370 }
1371
1372 + // --------------------------------------------------------------------
1373 + // /proc/<pid>/cmdline
1374 +
1375 + if(proc_pid_cmdline_is_needed) {
1376 + if(unlikely(read_proc_pid_cmdline(p))) {
1377 + if(!count_errors++ || debug || (p->target && p->target->debug))
1378 + error("Cannot process %s/proc/%d/cmdline", host_prefix, pid);
1379 + }
1380 + }
1381
1382 // --------------------------------------------------------------------
1383 // /proc/<pid>/statm
@@ -1359,12 +1419,22 @@ int collect_data_for_all_processes_from_proc(void)
1419 if(unlikely(p->new_entry)) {
1420 if(debug) fprintf(stderr, "apps.plugin: \tJust added %s\n", p->comm);
1421 uint32_t hash = simple_hash(p->comm);
1422 + size_t pclen = strlen(p->comm);
1423
1424 struct target *w;
1425 for(w = apps_groups_root_target; w ; w = w->next) {
1426 // if(debug || (p->target && p->target->debug)) fprintf(stderr, "apps.plugin: \t\tcomparing '%s' with '%s'\n", w->compare, p->comm);
1427
1367 - if(w->hash == hash && strcmp(w->compare, p->comm) == 0) {
1428 + // find it - 4 cases:
1429 + // 1. the target is not a pattern
1430 + // 2. the target has the prefix
1431 + // 3. the target has the suffix
1432 + // 4. the target is something inside cmdline
1433 + if( (!w->starts_with && !w->ends_with && w->comparehash == hash && !strcmp(w->compare, p->comm))
1434 + || (w->starts_with && !w->ends_with && !strncmp(w->compare, p->comm, w->comparelen))
1435 + || (!w->starts_with && w->ends_with && pclen >= w->comparelen && !strcmp(w->compare, &p->comm[pclen - w->comparelen]))
1436 + || (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && strstr(p->cmdline, w->compare))
1437 + ) {
1438 if(w->target) p->target = w->target;
1439 else p->target = w;
1440