#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;
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;
#endif /* NETDATA_INTERNAL_CHECKS */
-
// ----------------------------------------------------------------------------
// system functions
// to retrieve settings of the system
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;
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;
}
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)
else
snprintf(w->name, MAX_NAME, "%s", pw->pw_name);
+ netdata_fix_id(w->name);
+
w->uid = uid;
w->next = users_root_target;
}
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)
else
snprintf(w->name, MAX_NAME, "%s", gr->gr_name);
+ netdata_fix_id(w->name);
+
w->gid = gid;
w->next = groups_root_target;
// 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)) {
}
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;
}
// 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);
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;
}
struct pid_stat {
int32_t pid;
char comm[MAX_COMPARE_NAME + 1];
+ char cmdline[MAX_CMDLINE + 1];
// char state;
int32_t ppid;
// ----------------------------------------------------------------------------
// 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];
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));
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);
// /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
// 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
// /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
// <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);
}
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;
// /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);
}
p->updated = 1;
}
- if(unlikely(count_errors > 1000)) {
- error("%ld more errors encountered\n", count_errors - 1);
- count_errors = 0;
- }
-
closedir(dir);
return 1;