Remove types.db entry and change RegexProcess conf option name
[collectd.git] / src / procevent.c
index a04a9c9..a7a0107 100644 (file)
 #include "common.h"
 #include "plugin.h"
 #include "utils_complain.h"
+#include "utils_ignorelist.h"
 
 #include <errno.h>
 #include <pthread.h>
-#include <regex.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/socket.h>
@@ -62,7 +62,6 @@
 #define PROCEVENT_FIELDS 4 // pid, status, extra, timestamp
 #define BUFSIZE 512
 #define PROCDIR "/proc"
-#define PROCEVENT_REGEX_MATCHES 1
 
 #define PROCEVENT_DOMAIN_FIELD "domain"
 #define PROCEVENT_DOMAIN_VALUE "fault"
@@ -112,12 +111,9 @@ typedef struct {
 
 struct processlist_s {
   char *process;
-  char *process_regex;
 
-  regex_t process_regex_obj;
-
-  uint32_t is_regex;
-  uint32_t pid;
+  long pid;
+  int32_t last_status;
 
   struct processlist_s *next;
 };
@@ -126,6 +122,7 @@ typedef struct processlist_s processlist_t;
 /*
  * Private variables
  */
+static ignorelist_t *ignorelist = NULL;
 
 static int procevent_thread_loop = 0;
 static int procevent_thread_error = 0;
@@ -139,14 +136,14 @@ static circbuf_t ring;
 static processlist_t *processlist_head = NULL;
 static int event_id = 0;
 
-static const char *config_keys[] = {"BufferLength", "Process", "RegexProcess"};
+static const char *config_keys[] = {"BufferLength", "Process", "ProcessRegex"};
 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
 
 /*
  * Private functions
  */
 
-static int gen_message_payload(int state, int pid, char *process,
+static int gen_message_payload(int state, long pid, char *process,
                                long long unsigned int timestamp, char **buf) {
   const unsigned char *buf2;
   yajl_gen g;
@@ -209,7 +206,7 @@ static int gen_message_payload(int state, int pid, char *process,
   event_name_len = event_name_len +
                    13; // "process", 3 spaces, 2 parentheses and null-terminator
   memset(json_str, '\0', DATA_MAX_NAME_LEN);
-  snprintf(json_str, event_name_len, "process %s (%d) %s", process, pid,
+  snprintf(json_str, event_name_len, "process %s (%ld) %s", process, pid,
            (state == 0 ? PROCEVENT_EVENT_NAME_DOWN_VALUE
                        : PROCEVENT_EVENT_NAME_UP_VALUE));
 
@@ -324,7 +321,7 @@ static int gen_message_payload(int state, int pid, char *process,
       alarm_condition_len + 25; // "process", "state", "change", 4 spaces, 2
                                 // parentheses and null-terminator
   memset(json_str, '\0', DATA_MAX_NAME_LEN);
-  snprintf(json_str, alarm_condition_len, "process %s (%d) state change",
+  snprintf(json_str, alarm_condition_len, "process %s (%ld) state change",
            process, pid);
 
   if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
@@ -394,7 +391,7 @@ static int gen_message_payload(int state, int pid, char *process,
       specific_problem_len +
       13; // "process", 3 spaces, 2 parentheses and null-terminator
   memset(json_str, '\0', DATA_MAX_NAME_LEN);
-  snprintf(json_str, specific_problem_len, "process %s (%d) %s", process, pid,
+  snprintf(json_str, specific_problem_len, "process %s (%ld) %s", process, pid,
            (state == 0 ? PROCEVENT_SPECIFIC_PROBLEM_DOWN_VALUE
                        : PROCEVENT_SPECIFIC_PROBLEM_UP_VALUE));
 
@@ -429,6 +426,13 @@ static int gen_message_payload(int state, int pid, char *process,
 
   *buf = malloc(strlen((char *)buf2) + 1);
 
+  if (*buf == NULL) {
+    char errbuf[1024];
+    ERROR("procevent plugin: malloc failed during gen_message_payload: %s",
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    goto err;
+  }
+
   sstrncpy(*buf, (char *)buf2, strlen((char *)buf2) + 1);
 
   yajl_gen_free(g);
@@ -442,14 +446,13 @@ err:
 }
 
 // Does /proc/<pid>/comm contain a process name we are interested in?
-static processlist_t *process_check(int pid) {
-  int len, is_match, status, retval;
+static processlist_t *process_check(long pid) {
+  int len, is_match, retval;
   char file[BUFSIZE];
   FILE *fh;
   char buffer[BUFSIZE];
-  regmatch_t matches[PROCEVENT_REGEX_MATCHES];
 
-  len = snprintf(file, sizeof(file), PROCDIR "/%d/comm", pid);
+  len = snprintf(file, sizeof(file), PROCDIR "/%ld/comm", pid);
 
   if ((len < 0) || (len >= BUFSIZE)) {
     WARNING("procevent process_check: process name too large");
@@ -458,25 +461,39 @@ static processlist_t *process_check(int pid) {
 
   if (NULL == (fh = fopen(file, "r"))) {
     // No /proc/<pid>/comm for this pid, just ignore
-    DEBUG("procevent plugin: no comm file available for pid %d", pid);
+    DEBUG("procevent plugin: no comm file available for pid %ld", pid);
     return NULL;
   }
 
   retval = fscanf(fh, "%[^\n]", buffer);
 
   if (retval < 0) {
-    WARNING("procevent process_check: unable to read comm file for pid %d",
+    WARNING("procevent process_check: unable to read comm file for pid %ld",
             pid);
+    fclose(fh);
     return NULL;
   }
 
+  // Now that we have the process name in the buffer, check if we are
+  // even interested in it
+  if (ignorelist_match(ignorelist, buffer) != 0) {
+    DEBUG("procevent process_check: ignoring process %s (%ld)", buffer, pid);
+    fclose(fh);
+    return NULL;
+  }
+
+  if (fh != NULL) {
+    fclose(fh);
+    fh = NULL;
+  }
+
   //
   // Go through the processlist linked list and look for the process name
   // in /proc/<pid>/comm.  If found:
-  // 1. If pl->pid is -1, then set pl->pid to <pid>
+  // 1. If pl->pid is -1, then set pl->pid to <pid> (and return that object)
   // 2. If pl->pid is not -1, then another <process name> process was already
   //    found.  If <pid> == pl->pid, this is an old match, so do nothing.
-  //    If the <pid> is different, however,  make a new processlist_t and
+  //    If the <pid> is different, however, make a new processlist_t and
   //    associate <pid> with it (with the same process name as the existing).
   //
 
@@ -486,42 +503,25 @@ static processlist_t *process_check(int pid) {
   processlist_t *match = NULL;
 
   for (pl = processlist_head; pl != NULL; pl = pl->next) {
-    if (pl->is_regex != 0) {
-      is_match = (regexec(&pl->process_regex_obj, buffer,
-                          PROCEVENT_REGEX_MATCHES, matches, 0) == 0
-                      ? 1
-                      : 0);
-    } else {
-      is_match = (strcmp(buffer, pl->process) == 0 ? 1 : 0);
-    }
+
+    is_match = (strcmp(buffer, pl->process) == 0 ? 1 : 0);
 
     if (is_match == 1) {
-      DEBUG("procevent plugin: process %d name match (pattern: %s) for %s", pid,
-            (pl->is_regex == 0 ? pl->process : pl->process_regex), buffer);
-
-      if (pl->is_regex == 1) {
-        // If this is a regex name, copy the actual process name into the object
-        // for cleaner log reporting
-
-        if (pl->process != NULL)
-          sfree(pl->process);
-        pl->process = strdup(buffer);
-        if (pl->process == NULL) {
-          char errbuf[1024];
-          ERROR("procevent plugin: strdup failed during process_check: %s",
-                sstrerror(errno, errbuf, sizeof(errbuf)));
-          pthread_mutex_unlock(&procevent_list_lock);
-          return NULL;
-        }
-      }
+      DEBUG("procevent plugin: process %ld name match for %s", pid, buffer);
 
       if (pl->pid == pid) {
         // this is a match, and we've already stored the exact pid/name combo
+        DEBUG("procevent plugin: found exact match with name %s, PID %ld for "
+              "incoming PID %ld",
+              pl->process, pl->pid, pid);
         match = pl;
         break;
       } else if (pl->pid == -1) {
         // this is a match, and we've found a candidate processlist_t to store
         // this new pid/name combo
+        DEBUG("procevent plugin: reusing pl object with PID %ld for incoming "
+              "PID %ld",
+              pl->pid, pid);
         pl->pid = pid;
         match = pl;
         break;
@@ -529,24 +529,28 @@ static processlist_t *process_check(int pid) {
         // this is a match, but another instance of this process has already
         // claimed this pid/name combo,
         // so keep looking
+        DEBUG("procevent plugin: found pl object with matching name for "
+              "incoming PID %ld, but object is in use by PID %ld",
+              pid, pl->pid);
         match = pl;
         continue;
       }
     }
   }
 
-  if (match != NULL && match->pid != -1 && match->pid != pid) {
+  if (match == NULL ||
+      (match != NULL && match->pid != -1 && match->pid != pid)) {
+    // if there wasn't an existing match, OR
     // if there was a match but the associated processlist_t object already
     // contained a pid/name combo,
     // then make a new one and add it to the linked list
 
-    DEBUG(
-        "procevent plugin: allocating new processlist_t object for PID %d (%s)",
-        pid, match->process);
+    DEBUG("procevent plugin: allocating new processlist_t object for PID %ld "
+          "(%s)",
+          pid, buffer);
 
     processlist_t *pl2;
     char *process;
-    char *process_regex;
 
     pl2 = malloc(sizeof(*pl2));
     if (pl2 == NULL) {
@@ -557,7 +561,7 @@ static processlist_t *process_check(int pid) {
       return NULL;
     }
 
-    process = strdup(match->process);
+    process = strdup(buffer);
     if (process == NULL) {
       char errbuf[1024];
       sfree(pl2);
@@ -567,32 +571,6 @@ static processlist_t *process_check(int pid) {
       return NULL;
     }
 
-    if (match->is_regex == 1) {
-      pl2->is_regex = 1;
-      status =
-          regcomp(&pl2->process_regex_obj, match->process_regex, REG_EXTENDED);
-
-      if (status != 0) {
-        sfree(pl2);
-        sfree(process);
-        ERROR("procevent plugin: invalid regular expression: %s",
-              match->process_regex);
-        return NULL;
-      }
-
-      process_regex = strdup(match->process_regex);
-      if (process_regex == NULL) {
-        char errbuf[1024];
-        sfree(pl2);
-        sfree(process);
-        ERROR("procevent plugin: strdup failed during process_check: %s",
-              sstrerror(errno, errbuf, sizeof(errbuf)));
-        return NULL;
-      }
-
-      pl2->process_regex = process_regex;
-    }
-
     pl2->process = process;
     pl2->pid = pid;
     pl2->next = processlist_head;
@@ -603,16 +581,11 @@ static processlist_t *process_check(int pid) {
 
   pthread_mutex_unlock(&procevent_list_lock);
 
-  if (fh != NULL) {
-    fclose(fh);
-    fh = NULL;
-  }
-
   return match;
 }
 
 // Does our map have this PID or name?
-static processlist_t *process_map_check(int pid, char *process) {
+static processlist_t *process_map_check(long pid, char *process) {
   processlist_t *pl;
 
   pthread_mutex_lock(&procevent_list_lock);
@@ -740,7 +713,7 @@ static int nl_connect() {
 
   nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
   if (nl_sock == -1) {
-    ERROR("procevent plugin: socket open failed.");
+    ERROR("procevent plugin: socket open failed: %d", errno);
     return -1;
   }
 
@@ -750,7 +723,7 @@ static int nl_connect() {
 
   rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
   if (rc == -1) {
-    ERROR("procevent plugin: socket bind failed.");
+    ERROR("procevent plugin: socket bind failed: %d", errno);
     close(nl_sock);
     return -1;
   }
@@ -781,7 +754,8 @@ static int set_proc_ev_listen(bool enable) {
 
   rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
   if (rc == -1) {
-    ERROR("procevent plugin: subscribing to netlink process events failed.");
+    ERROR("procevent plugin: subscribing to netlink process events failed: %d",
+          errno);
     return -1;
   }
 
@@ -805,72 +779,82 @@ static int read_event() {
   if (nl_sock == -1)
     return ret;
 
-  status = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
+  while (42) {
 
-  if (status == 0) {
-    return 0;
-  } else if (status == -1) {
-    if (errno != EINTR) {
-      ERROR("procevent plugin: socket receive error: %d", errno);
-      return -1;
+    pthread_mutex_lock(&procevent_lock);
+
+    if (procevent_thread_loop <= 0)
+      return ret;
+
+    pthread_mutex_unlock(&procevent_lock);
+
+    status = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
+
+    if (status == 0) {
+      return 0;
+    } else if (status == -1) {
+      if (errno != EINTR) {
+        ERROR("procevent plugin: socket receive error: %d", errno);
+        return -1;
+      }
     }
-  }
 
-  switch (nlcn_msg.proc_ev.what) {
-  case PROC_EVENT_NONE:
-  case PROC_EVENT_FORK:
-  case PROC_EVENT_UID:
-  case PROC_EVENT_GID:
-    // Not of interest in current version
-    break;
-  case PROC_EVENT_EXEC:
-    proc_status = PROCEVENT_STARTED;
-    proc_id = nlcn_msg.proc_ev.event_data.exec.process_pid;
-    break;
-  case PROC_EVENT_EXIT:
-    proc_id = nlcn_msg.proc_ev.event_data.exit.process_pid;
-    proc_status = PROCEVENT_EXITED;
-    proc_extra = nlcn_msg.proc_ev.event_data.exit.exit_code;
-    break;
-  default:
-    break;
-  }
+    switch (nlcn_msg.proc_ev.what) {
+    case PROC_EVENT_NONE:
+    case PROC_EVENT_FORK:
+    case PROC_EVENT_UID:
+    case PROC_EVENT_GID:
+      // Not of interest in current version
+      break;
+    case PROC_EVENT_EXEC:
+      proc_status = PROCEVENT_STARTED;
+      proc_id = nlcn_msg.proc_ev.event_data.exec.process_pid;
+      break;
+    case PROC_EVENT_EXIT:
+      proc_id = nlcn_msg.proc_ev.event_data.exit.process_pid;
+      proc_status = PROCEVENT_EXITED;
+      proc_extra = nlcn_msg.proc_ev.event_data.exit.exit_code;
+      break;
+    default:
+      break;
+    }
 
-  // If we're interested in this process status event, place the event
-  // in the ring buffer for consumption by the main polling thread.
+    // If we're interested in this process status event, place the event
+    // in the ring buffer for consumption by the main polling thread.
 
-  if (proc_status != -1) {
-    pthread_mutex_unlock(&procevent_lock);
+    if (proc_status != -1) {
+      pthread_mutex_lock(&procevent_lock);
 
-    int next = ring.head + 1;
-    if (next >= ring.maxLen)
-      next = 0;
+      int next = ring.head + 1;
+      if (next >= ring.maxLen)
+        next = 0;
 
-    if (next == ring.tail) {
-      WARNING("procevent plugin: ring buffer full");
-    } else {
-      DEBUG("procevent plugin: Process %d status is now %s at %llu", proc_id,
-            (proc_status == PROCEVENT_EXITED ? "EXITED" : "STARTED"),
-            (long long unsigned int)CDTIME_T_TO_US(cdtime()));
-
-      if (proc_status == PROCEVENT_EXITED) {
-        ring.buffer[ring.head][0] = proc_id;
-        ring.buffer[ring.head][1] = proc_status;
-        ring.buffer[ring.head][2] = proc_extra;
-        ring.buffer[ring.head][3] =
-            (long long unsigned int)CDTIME_T_TO_US(cdtime());
+      if (next == ring.tail) {
+        WARNING("procevent plugin: ring buffer full");
       } else {
-        ring.buffer[ring.head][0] = proc_id;
-        ring.buffer[ring.head][1] = proc_status;
-        ring.buffer[ring.head][2] = 0;
-        ring.buffer[ring.head][3] =
-            (long long unsigned int)CDTIME_T_TO_US(cdtime());
+        DEBUG("procevent plugin: Process %d status is now %s at %llu", proc_id,
+              (proc_status == PROCEVENT_EXITED ? "EXITED" : "STARTED"),
+              (long long unsigned int)CDTIME_T_TO_US(cdtime()));
+
+        if (proc_status == PROCEVENT_EXITED) {
+          ring.buffer[ring.head][0] = proc_id;
+          ring.buffer[ring.head][1] = proc_status;
+          ring.buffer[ring.head][2] = proc_extra;
+          ring.buffer[ring.head][3] =
+              (long long unsigned int)CDTIME_T_TO_US(cdtime());
+        } else {
+          ring.buffer[ring.head][0] = proc_id;
+          ring.buffer[ring.head][1] = proc_status;
+          ring.buffer[ring.head][2] = 0;
+          ring.buffer[ring.head][3] =
+              (long long unsigned int)CDTIME_T_TO_US(cdtime());
+        }
+
+        ring.head = next;
       }
 
-      ring.head = next;
+      pthread_mutex_unlock(&procevent_lock);
     }
-
-    pthread_mutex_unlock(&procevent_lock);
   }
 
   return ret;
@@ -1006,11 +990,6 @@ static int procevent_init(void) /* {{{ */
 {
   int status;
 
-  if (processlist_head == NULL) {
-    NOTICE("procevent plugin: No processes have been configured.");
-    return (-1);
-  }
-
   ring.head = 0;
   ring.tail = 0;
   ring.maxLen = buffer_length;
@@ -1029,6 +1008,11 @@ static int procevent_init(void) /* {{{ */
     return (-1);
   }
 
+  if (ignorelist == NULL) {
+    NOTICE("procevent plugin: No processes have been configured.");
+    return (-1);
+  }
+
   return (start_thread());
 } /* }}} int procevent_init */
 
@@ -1036,62 +1020,25 @@ static int procevent_config(const char *key, const char *value) /* {{{ */
 {
   int status;
 
+  if (ignorelist == NULL)
+    ignorelist = ignorelist_create(/* invert = */ 1);
+
   if (strcasecmp(key, "BufferLength") == 0) {
     buffer_length = atoi(value);
-  } else if (strcasecmp(key, "Process") == 0 ||
-             strcasecmp(key, "RegexProcess") == 0) {
-
-    processlist_t *pl;
-    char *process;
-    char *process_regex;
+  } else if (strcasecmp(key, "Process") == 0) {
+    ignorelist_add(ignorelist, value);
+  } else if (strcasecmp(key, "ProcessRegex") == 0) {
+#if HAVE_REGEX_H
+    status = ignorelist_add(ignorelist, value);
 
-    pl = malloc(sizeof(*pl));
-    if (pl == NULL) {
-      char errbuf[1024];
-      ERROR("procevent plugin: malloc failed during procevent_config: %s",
-            sstrerror(errno, errbuf, sizeof(errbuf)));
-      return (1);
-    }
-
-    process = strdup(value);
-    if (process == NULL) {
-      char errbuf[1024];
-      sfree(pl);
-      ERROR("procevent plugin: strdup failed during procevent_config: %s",
-            sstrerror(errno, errbuf, sizeof(errbuf)));
+    if (status != 0) {
+      ERROR("procevent plugin: invalid regular expression: %s", value);
       return (1);
     }
-
-    if (strcasecmp(key, "RegexProcess") == 0) {
-      pl->is_regex = 1;
-      status = regcomp(&pl->process_regex_obj, value, REG_EXTENDED);
-
-      if (status != 0) {
-        sfree(pl);
-        sfree(process);
-        ERROR("procevent plugin: invalid regular expression: %s", value);
-        return (1);
-      }
-
-      process_regex = strdup(value);
-      if (process_regex == NULL) {
-        char errbuf[1024];
-        sfree(pl);
-        sfree(process);
-        ERROR("procevent plugin: strdup failed during procevent_config: %s",
-              sstrerror(errno, errbuf, sizeof(errbuf)));
-        return (1);
-      }
-
-      pl->process_regex = process_regex;
-    } else {
-      pl->is_regex = 0;
-    }
-
-    pl->process = process;
-    pl->pid = -1;
-    pl->next = processlist_head;
-    processlist_head = pl;
+#else
+    WARNING("procevent plugin: The plugin has been compiled without support "
+            "for the \"ProcessRegex\" option.");
+#endif
   } else {
     return (-1);
   }
@@ -1099,7 +1046,8 @@ static int procevent_config(const char *key, const char *value) /* {{{ */
   return (0);
 } /* }}} int procevent_config */
 
-static void procevent_dispatch_notification(int pid, const char *type, /* {{{ */
+static void procevent_dispatch_notification(long pid,
+                                            const char *type, /* {{{ */
                                             gauge_t value, char *process,
                                             long long unsigned int timestamp) {
   char *buf = NULL;
@@ -1109,10 +1057,7 @@ static void procevent_dispatch_notification(int pid, const char *type, /* {{{ */
   if (value == 1)
     n.severity = NOTIF_OKAY;
 
-  char hostname[1024];
-  gethostname(hostname, sizeof(hostname));
-
-  sstrncpy(n.host, hostname, sizeof(n.host));
+  sstrncpy(n.host, hostname_g, sizeof(n.host));
   sstrncpy(n.plugin_instance, process, sizeof(n.plugin_instance));
   sstrncpy(n.type, "gauge", sizeof(n.type));
   sstrncpy(n.type_instance, "process_status", sizeof(n.type_instance));
@@ -1137,7 +1082,7 @@ static void procevent_dispatch_notification(int pid, const char *type, /* {{{ */
   DEBUG("procevent plugin: notification message: %s",
         n.meta->nm_value.nm_string);
 
-  DEBUG("procevent plugin: dispatching state %d for PID %d (%s)", (int)value,
+  DEBUG("procevent plugin: dispatching state %d for PID %ld (%s)", (int)value,
         pid, process);
 
   plugin_dispatch_notification(&n);
@@ -1177,23 +1122,32 @@ static int procevent_read(void) /* {{{ */
         procevent_dispatch_notification(ring.buffer[ring.tail][0], "gauge",
                                         ring.buffer[ring.tail][1], pl->process,
                                         ring.buffer[ring.tail][3]);
-        DEBUG("procevent plugin: PID %d (%s) EXITED, removing PID from process "
-              "list",
-              pl->pid, pl->process);
+        DEBUG(
+            "procevent plugin: PID %ld (%s) EXITED, removing PID from process "
+            "list",
+            pl->pid, pl->process);
         pl->pid = -1;
+        pl->last_status = -1;
       }
     } else if (ring.buffer[ring.tail][1] == PROCEVENT_STARTED) {
       // a new process has started, so check if we should monitor it
       processlist_t *pl = process_check(ring.buffer[ring.tail][0]);
 
-      if (pl != NULL) {
+      // If we had already seen this process name and pid combo before,
+      // and the last message was a "process started" message, don't send
+      // the notfication again
+
+      if (pl != NULL && pl->last_status != PROCEVENT_STARTED) {
         // This process is of interest to us, so publish its STARTED status
         procevent_dispatch_notification(ring.buffer[ring.tail][0], "gauge",
                                         ring.buffer[ring.tail][1], pl->process,
                                         ring.buffer[ring.tail][3]);
-        DEBUG(
-            "procevent plugin: PID %d (%s) STARTED, adding PID to process list",
-            pl->pid, pl->process);
+
+        pl->last_status = PROCEVENT_STARTED;
+
+        DEBUG("procevent plugin: PID %ld (%s) STARTED, adding PID to process "
+              "list",
+              pl->pid, pl->process);
       }
     }
 
@@ -1226,11 +1180,6 @@ static int procevent_shutdown(void) /* {{{ */
 
     pl_next = pl->next;
 
-    if (pl->is_regex == 1) {
-      sfree(pl->process_regex);
-      regfree(&pl->process_regex_obj);
-    }
-
     sfree(pl->process);
     sfree(pl);