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