procevent plugin initial commit
[collectd.git] / src / procevent.c
1 /**
2  * collectd - src/procevent.c
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  *
22  * Authors:
23  *   Red Hat NFVPE
24  *     Andrew Bays <abays at redhat.com>
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h"
30 #include "plugin.h"
31 #include "utils_complain.h"
32
33 #include <errno.h>
34 #include <pthread.h>
35 #include <regex.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <sys/socket.h>
39 #include <unistd.h>
40
41 #include <dirent.h>
42 #include <linux/cn_proc.h>
43 #include <linux/connector.h>
44 #include <linux/netlink.h>
45 #include <linux/rtnetlink.h>
46 #include <signal.h>
47 #include <stdbool.h>
48 #include <stdlib.h>
49 #include <string.h>
50
51 #define PROCEVENT_EXITED 0
52 #define PROCEVENT_STARTED 1
53 #define PROCEVENT_FIELDS 3 // pid, status, extra
54 #define BUFSIZE 512
55 #define PROCDIR "/proc"
56 #define PROCEVENT_REGEX_MATCHES 1
57
58 /*
59  * Private data types
60  */
61
62 typedef struct {
63   int head;
64   int tail;
65   int maxLen;
66   int **buffer;
67 } circbuf_t;
68
69 struct processlist_s {
70   char *process;
71   char *process_regex;
72
73   regex_t process_regex_obj;
74
75   uint32_t is_regex;
76   uint32_t pid;
77
78   struct processlist_s *next;
79 };
80 typedef struct processlist_s processlist_t;
81
82 /*
83  * Private variables
84  */
85
86 static int procevent_thread_loop = 0;
87 static int procevent_thread_error = 0;
88 static pthread_t procevent_thread_id;
89 static pthread_mutex_t procevent_lock = PTHREAD_MUTEX_INITIALIZER;
90 static pthread_cond_t procevent_cond = PTHREAD_COND_INITIALIZER;
91 static pthread_mutex_t procevent_list_lock = PTHREAD_MUTEX_INITIALIZER;
92 static int nl_sock = -1;
93 static int buffer_length;
94 static circbuf_t ring;
95 static processlist_t *processlist_head = NULL;
96
97 static const char *config_keys[] = {"BufferLength", "Process", "RegexProcess"};
98 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
99
100 /*
101  * Private functions
102  */
103
104 // Does /proc/<pid>/comm contain a process name we are interested in?
105 static processlist_t *process_check(int pid) {
106   int len, is_match, status;
107   char file[BUFSIZE];
108   FILE *fh;
109   char buffer[BUFSIZE];
110   regmatch_t matches[PROCEVENT_REGEX_MATCHES];
111
112   len = snprintf(file, sizeof(file), PROCDIR "/%d/comm", pid);
113
114   if ((len < 0) || (len >= BUFSIZE)) {
115     WARNING("procevent process_check: process name too large");
116     return NULL;
117   }
118
119   if (NULL == (fh = fopen(file, "r"))) {
120     // No /proc/<pid>/comm for this pid, just ignore
121     DEBUG("procevent plugin: no comm file available for pid %d", pid);
122     return NULL;
123   }
124
125   fscanf(fh, "%[^\n]", buffer);
126
127   //
128   // Go through the processlist linked list and look for the process name
129   // in /proc/<pid>/comm.  If found:
130   // 1. If pl->pid is -1, then set pl->pid to <pid>
131   // 2. If pl->pid is not -1, then another <process name> process was already
132   //    found.  If <pid> == pl->pid, this is an old match, so do nothing.
133   //    If the <pid> is different, however,  make a new processlist_t and
134   //    associate <pid> with it (with the same process name as the existing).
135   //
136
137   pthread_mutex_lock(&procevent_list_lock);
138
139   processlist_t *pl;
140   processlist_t *match = NULL;
141
142   for (pl = processlist_head; pl != NULL; pl = pl->next) {
143     if (pl->is_regex != 0) {
144       is_match = (regexec(&pl->process_regex_obj, buffer,
145                           PROCEVENT_REGEX_MATCHES, matches, 0) == 0
146                       ? 1
147                       : 0);
148     } else {
149       is_match = (strcmp(buffer, pl->process) == 0 ? 1 : 0);
150     }
151
152     if (is_match == 1) {
153       DEBUG("procevent plugin: process %d name match (pattern: %s) for %s", pid,
154             (pl->is_regex == 0 ? pl->process : pl->process_regex), buffer);
155
156       if (pl->is_regex == 1) {
157         // If this is a regex name, copy the actual process name into the object
158         // for cleaner log reporting
159
160         if (pl->process != NULL)
161           sfree(pl->process);
162         pl->process = strdup(buffer);
163         if (pl->process == NULL) {
164           char errbuf[1024];
165           ERROR("procevent plugin: strdup failed during process_check: %s",
166                 sstrerror(errno, errbuf, sizeof(errbuf)));
167           pthread_mutex_unlock(&procevent_list_lock);
168           return NULL;
169         }
170       }
171
172       if (pl->pid == pid) {
173         // this is a match, and we've already stored the exact pid/name combo
174         match = pl;
175         break;
176       } else if (pl->pid == -1) {
177         // this is a match, and we've found a candidate processlist_t to store
178         // this new pid/name combo
179         pl->pid = pid;
180         match = pl;
181         break;
182       } else if (pl->pid != -1) {
183         // this is a match, but another instance of this process has already
184         // claimed this pid/name combo,
185         // so keep looking
186         match = pl;
187         continue;
188       }
189     }
190   }
191
192   if (match != NULL && match->pid != -1 && match->pid != pid) {
193     // if there was a match but the associated processlist_t object already
194     // contained a pid/name combo,
195     // then make a new one and add it to the linked list
196
197     DEBUG(
198         "procevent plugin: allocating new processlist_t object for PID %d (%s)",
199         pid, match->process);
200
201     processlist_t *pl2;
202     char *process;
203     char *process_regex;
204
205     pl2 = malloc(sizeof(*pl2));
206     if (pl2 == NULL) {
207       char errbuf[1024];
208       ERROR("procevent plugin: malloc failed during process_check: %s",
209             sstrerror(errno, errbuf, sizeof(errbuf)));
210       pthread_mutex_unlock(&procevent_list_lock);
211       return NULL;
212     }
213
214     process = strdup(match->process);
215     if (process == NULL) {
216       char errbuf[1024];
217       sfree(pl2);
218       ERROR("procevent plugin: strdup failed during process_check: %s",
219             sstrerror(errno, errbuf, sizeof(errbuf)));
220       pthread_mutex_unlock(&procevent_list_lock);
221       return NULL;
222     }
223
224     if (match->is_regex == 1) {
225       pl2->is_regex = 1;
226       status =
227           regcomp(&pl2->process_regex_obj, match->process_regex, REG_EXTENDED);
228
229       if (status != 0) {
230         ERROR("procevent plugin: invalid regular expression: %s",
231               match->process_regex);
232         return NULL;
233       }
234
235       process_regex = strdup(match->process_regex);
236       if (process_regex == NULL) {
237         char errbuf[1024];
238         sfree(pl);
239         ERROR("procevent plugin: strdup failed during process_check: %s",
240               sstrerror(errno, errbuf, sizeof(errbuf)));
241         return NULL;
242       }
243
244       pl2->process_regex = process_regex;
245     }
246
247     pl2->process = process;
248     pl2->pid = pid;
249     pl2->next = processlist_head;
250     processlist_head = pl2;
251
252     match = pl2;
253   }
254
255   pthread_mutex_unlock(&procevent_list_lock);
256
257   if (fh != NULL) {
258     fclose(fh);
259     fh = NULL;
260   }
261
262   return match;
263 }
264
265 // Does our map have this PID or name?
266 static processlist_t *process_map_check(int pid, char *process) {
267   processlist_t *pl;
268
269   pthread_mutex_lock(&procevent_list_lock);
270
271   for (pl = processlist_head; pl != NULL; pl = pl->next) {
272     int match_pid = 0;
273     int match_process = 0;
274     int match = 0;
275
276     if (pid > 0) {
277       if (pl->pid == pid)
278         match_pid = 1;
279     }
280
281     if (process != NULL) {
282       if (strcmp(pl->process, process) == 0)
283         match_process = 1;
284     }
285
286     if (pid > 0 && process == NULL && match_pid == 1)
287       match = 1;
288     else if (pid < 0 && process != NULL && match_process == 1)
289       match = 1;
290     else if (pid > 0 && process != NULL && match_pid == 1 && match_process == 1)
291       match = 1;
292
293     if (match == 1) {
294       pthread_mutex_unlock(&procevent_list_lock);
295       return pl;
296     }
297   }
298
299   pthread_mutex_unlock(&procevent_list_lock);
300
301   return NULL;
302 }
303
304 static int process_map_refresh(void) {
305   DIR *proc;
306
307   errno = 0;
308   proc = opendir(PROCDIR);
309   if (proc == NULL) {
310     char errbuf[1024];
311     ERROR("procevent plugin: fopen (%s): %s", PROCDIR,
312           sstrerror(errno, errbuf, sizeof(errbuf)));
313     return -1;
314   }
315
316   while (42) {
317     struct dirent *dent;
318     int len;
319     char file[BUFSIZE];
320
321     struct stat statbuf;
322
323     int status;
324
325     errno = 0;
326     dent = readdir(proc);
327     if (dent == NULL) {
328       char errbuf[4096];
329
330       if (errno == 0) /* end of directory */
331         break;
332
333       ERROR("procevent plugin: failed to read directory %s: %s", PROCDIR,
334             sstrerror(errno, errbuf, sizeof(errbuf)));
335       closedir(proc);
336       return -1;
337     }
338
339     if (dent->d_name[0] == '.')
340       continue;
341
342     len = snprintf(file, sizeof(file), PROCDIR "/%s", dent->d_name);
343     if ((len < 0) || (len >= BUFSIZE))
344       continue;
345
346     status = stat(file, &statbuf);
347     if (status != 0) {
348       char errbuf[4096];
349       WARNING("procevent plugin: stat (%s) failed: %s", file,
350               sstrerror(errno, errbuf, sizeof(errbuf)));
351       continue;
352     }
353
354     if (!S_ISDIR(statbuf.st_mode))
355       continue;
356
357     len = snprintf(file, sizeof(file), PROCDIR "/%s/comm", dent->d_name);
358     if ((len < 0) || (len >= BUFSIZE))
359       continue;
360
361     int not_number = 0;
362
363     for (int i = 0; i < strlen(dent->d_name); i++) {
364       if (!isdigit(dent->d_name[i])) {
365         not_number = 1;
366         break;
367       }
368     }
369
370     if (not_number != 0)
371       continue;
372
373     // Check if we need to store this pid/name combo in our processlist_t linked
374     // list
375     int this_pid = atoi(dent->d_name);
376     processlist_t *pl = process_check(this_pid);
377
378     if (pl != NULL)
379       DEBUG("procevent plugin: process map refreshed for PID %d and name %s",
380             this_pid, pl->process);
381   }
382
383   closedir(proc);
384
385   return 0;
386 }
387
388 static int nl_connect() {
389   int rc;
390   struct sockaddr_nl sa_nl;
391
392   nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
393   if (nl_sock == -1) {
394     ERROR("procevent plugin: socket open failed.");
395     return -1;
396   }
397
398   sa_nl.nl_family = AF_NETLINK;
399   sa_nl.nl_groups = CN_IDX_PROC;
400   sa_nl.nl_pid = getpid();
401
402   rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
403   if (rc == -1) {
404     ERROR("procevent plugin: socket bind failed.");
405     close(nl_sock);
406     return -1;
407   }
408
409   return 0;
410 }
411
412 static int set_proc_ev_listen(bool enable) {
413   int rc;
414   struct __attribute__((aligned(NLMSG_ALIGNTO))) {
415     struct nlmsghdr nl_hdr;
416     struct __attribute__((__packed__)) {
417       struct cn_msg cn_msg;
418       enum proc_cn_mcast_op cn_mcast;
419     };
420   } nlcn_msg;
421
422   memset(&nlcn_msg, 0, sizeof(nlcn_msg));
423   nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg);
424   nlcn_msg.nl_hdr.nlmsg_pid = getpid();
425   nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE;
426
427   nlcn_msg.cn_msg.id.idx = CN_IDX_PROC;
428   nlcn_msg.cn_msg.id.val = CN_VAL_PROC;
429   nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op);
430
431   nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;
432
433   rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
434   if (rc == -1) {
435     ERROR("procevent plugin: subscribing to netlink process events failed.");
436     return -1;
437   }
438
439   return 0;
440 }
441
442 static int read_event() {
443   int status;
444   int ret = 0;
445   int proc_id = -1;
446   int proc_status = -1;
447   int proc_extra = -1;
448   struct __attribute__((aligned(NLMSG_ALIGNTO))) {
449     struct nlmsghdr nl_hdr;
450     struct __attribute__((__packed__)) {
451       struct cn_msg cn_msg;
452       struct proc_event proc_ev;
453     };
454   } nlcn_msg;
455
456   if (nl_sock == -1)
457     return ret;
458
459   status = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
460
461   if (status == 0) {
462     return 0;
463   } else if (status == -1) {
464     if (errno != EINTR) {
465       ERROR("procevent plugin: socket receive error: %d", errno);
466       return -1;
467     }
468   }
469
470   switch (nlcn_msg.proc_ev.what) {
471   case PROC_EVENT_NONE:
472     // printf("set mcast listen ok\n");
473     break;
474   case PROC_EVENT_FORK:
475     // printf("fork: parent tid=%d pid=%d -> child tid=%d pid=%d\n",
476     //         nlcn_msg.proc_ev.event_data.fork.parent_pid,
477     //         nlcn_msg.proc_ev.event_data.fork.parent_tgid,
478     //         nlcn_msg.proc_ev.event_data.fork.child_pid,
479     //         nlcn_msg.proc_ev.event_data.fork.child_tgid);
480     // proc_status = PROCEVENT_STARTED;
481     // proc_id = nlcn_msg.proc_ev.event_data.fork.child_pid;
482     break;
483   case PROC_EVENT_EXEC:
484     // printf("exec: tid=%d pid=%d\n",
485     //         nlcn_msg.proc_ev.event_data.exec.process_pid,
486     //         nlcn_msg.proc_ev.event_data.exec.process_tgid);
487     proc_status = PROCEVENT_STARTED;
488     proc_id = nlcn_msg.proc_ev.event_data.exec.process_pid;
489     break;
490   case PROC_EVENT_UID:
491     // printf("uid change: tid=%d pid=%d from %d to %d\n",
492     //         nlcn_msg.proc_ev.event_data.id.process_pid,
493     //         nlcn_msg.proc_ev.event_data.id.process_tgid,
494     //         nlcn_msg.proc_ev.event_data.id.r.ruid,
495     //         nlcn_msg.proc_ev.event_data.id.e.euid);
496     break;
497   case PROC_EVENT_GID:
498     // printf("gid change: tid=%d pid=%d from %d to %d\n",
499     //         nlcn_msg.proc_ev.event_data.id.process_pid,
500     //         nlcn_msg.proc_ev.event_data.id.process_tgid,
501     //         nlcn_msg.proc_ev.event_data.id.r.rgid,
502     //         nlcn_msg.proc_ev.event_data.id.e.egid);
503     break;
504   case PROC_EVENT_EXIT:
505     proc_id = nlcn_msg.proc_ev.event_data.exit.process_pid;
506     proc_status = PROCEVENT_EXITED;
507     proc_extra = nlcn_msg.proc_ev.event_data.exit.exit_code;
508     break;
509   default:
510     break;
511   }
512
513   // If we're interested in this process status event, place the event
514   // in the ring buffer for consumption by the main polling thread.
515
516   if (proc_status != -1) {
517     pthread_mutex_unlock(&procevent_lock);
518
519     int next = ring.head + 1;
520     if (next >= ring.maxLen)
521       next = 0;
522
523     if (next == ring.tail) {
524       WARNING("procevent plugin: ring buffer full");
525     } else {
526       DEBUG("procevent plugin: Process %d status is now %s", proc_id,
527             (proc_status == PROCEVENT_EXITED ? "EXITED" : "STARTED"));
528
529       if (proc_status == PROCEVENT_EXITED) {
530         ring.buffer[ring.head][0] = proc_id;
531         ring.buffer[ring.head][1] = proc_status;
532         ring.buffer[ring.head][2] = proc_extra;
533       } else {
534         ring.buffer[ring.head][0] = proc_id;
535         ring.buffer[ring.head][1] = proc_status;
536         ring.buffer[ring.head][2] = 0;
537       }
538
539       ring.head = next;
540     }
541
542     pthread_mutex_unlock(&procevent_lock);
543   }
544
545   return ret;
546 }
547
548 static void *procevent_thread(void *arg) /* {{{ */
549 {
550   pthread_mutex_lock(&procevent_lock);
551
552   while (procevent_thread_loop > 0) {
553     int status;
554
555     pthread_mutex_unlock(&procevent_lock);
556
557     usleep(1000);
558
559     status = read_event();
560
561     pthread_mutex_lock(&procevent_lock);
562
563     if (status < 0) {
564       procevent_thread_error = 1;
565       break;
566     }
567
568     if (procevent_thread_loop <= 0)
569       break;
570   } /* while (procevent_thread_loop > 0) */
571
572   pthread_mutex_unlock(&procevent_lock);
573
574   return ((void *)0);
575 } /* }}} void *procevent_thread */
576
577 static int start_thread(void) /* {{{ */
578 {
579   int status;
580
581   pthread_mutex_lock(&procevent_lock);
582
583   if (procevent_thread_loop != 0) {
584     pthread_mutex_unlock(&procevent_lock);
585     return (0);
586   }
587
588   if (nl_sock == -1) {
589     status = nl_connect();
590
591     if (status != 0)
592       return status;
593
594     status = set_proc_ev_listen(true);
595     if (status == -1)
596       return status;
597   }
598
599   DEBUG("procevent plugin: socket created and bound");
600
601   procevent_thread_loop = 1;
602   procevent_thread_error = 0;
603
604   status = plugin_thread_create(&procevent_thread_id, /* attr = */ NULL,
605                                 procevent_thread,
606                                 /* arg = */ (void *)0, "procevent");
607   if (status != 0) {
608     procevent_thread_loop = 0;
609     ERROR("procevent plugin: Starting thread failed.");
610     pthread_mutex_unlock(&procevent_lock);
611     return (-1);
612   }
613
614   pthread_mutex_unlock(&procevent_lock);
615   return (0);
616 } /* }}} int start_thread */
617
618 static int stop_thread(int shutdown) /* {{{ */
619 {
620   int status;
621
622   if (nl_sock != -1) {
623     status = close(nl_sock);
624     if (status != 0) {
625       ERROR("procevent plugin: failed to close socket %d: %d (%s)", nl_sock,
626             status, strerror(errno));
627       return (-1);
628     } else
629       nl_sock = -1;
630   }
631
632   pthread_mutex_lock(&procevent_lock);
633
634   if (procevent_thread_loop == 0) {
635     pthread_mutex_unlock(&procevent_lock);
636     return (-1);
637   }
638
639   procevent_thread_loop = 0;
640   pthread_cond_broadcast(&procevent_cond);
641   pthread_mutex_unlock(&procevent_lock);
642
643   if (shutdown == 1) {
644     // Calling pthread_cancel here in
645     // the case of a shutdown just assures that the thread is
646     // gone and that the process has been fully terminated.
647
648     DEBUG("procevent plugin: Canceling thread for process shutdown");
649
650     status = pthread_cancel(procevent_thread_id);
651
652     if (status != 0) {
653       ERROR("procevent plugin: Unable to cancel thread: %d", status);
654       status = -1;
655     }
656   } else {
657     status = pthread_join(procevent_thread_id, /* return = */ NULL);
658     if (status != 0) {
659       ERROR("procevent plugin: Stopping thread failed.");
660       status = -1;
661     }
662   }
663
664   pthread_mutex_lock(&procevent_lock);
665   memset(&procevent_thread_id, 0, sizeof(procevent_thread_id));
666   procevent_thread_error = 0;
667   pthread_mutex_unlock(&procevent_lock);
668
669   DEBUG("procevent plugin: Finished requesting stop of thread");
670
671   return (status);
672 } /* }}} int stop_thread */
673
674 static int procevent_init(void) /* {{{ */
675 {
676   int status;
677
678   if (processlist_head == NULL) {
679     NOTICE("procevent plugin: No processes have been configured.");
680     return (-1);
681   }
682
683   ring.head = 0;
684   ring.tail = 0;
685   ring.maxLen = buffer_length;
686   ring.buffer = (int **)malloc(buffer_length * sizeof(int *));
687
688   for (int i = 0; i < buffer_length; i++) {
689     ring.buffer[i] = (int *)malloc(PROCEVENT_FIELDS * sizeof(int));
690   }
691
692   status = process_map_refresh();
693
694   if (status == -1) {
695     ERROR("procevent plugin: Initial process mapping failed.");
696     return (-1);
697   }
698
699   return (start_thread());
700 } /* }}} int procevent_init */
701
702 static int procevent_config(const char *key, const char *value) /* {{{ */
703 {
704   int status;
705
706   if (strcasecmp(key, "BufferLength") == 0) {
707     buffer_length = atoi(value);
708   } else if (strcasecmp(key, "Process") == 0 ||
709              strcasecmp(key, "RegexProcess") == 0) {
710
711     processlist_t *pl;
712     char *process;
713     char *process_regex;
714
715     pl = malloc(sizeof(*pl));
716     if (pl == NULL) {
717       char errbuf[1024];
718       ERROR("procevent plugin: malloc failed during procevent_config: %s",
719             sstrerror(errno, errbuf, sizeof(errbuf)));
720       return (1);
721     }
722
723     process = strdup(value);
724     if (process == NULL) {
725       char errbuf[1024];
726       sfree(pl);
727       ERROR("procevent plugin: strdup failed during procevent_config: %s",
728             sstrerror(errno, errbuf, sizeof(errbuf)));
729       return (1);
730     }
731
732     if (strcasecmp(key, "RegexProcess") == 0) {
733       pl->is_regex = 1;
734       status = regcomp(&pl->process_regex_obj, value, REG_EXTENDED);
735
736       if (status != 0) {
737         ERROR("procevent plugin: invalid regular expression: %s", value);
738         return (1);
739       }
740
741       process_regex = strdup(value);
742       if (process_regex == NULL) {
743         char errbuf[1024];
744         sfree(pl);
745         ERROR("procevent plugin: strdup failed during procevent_config: %s",
746               sstrerror(errno, errbuf, sizeof(errbuf)));
747         return (1);
748       }
749
750       pl->process_regex = process_regex;
751     } else {
752       pl->is_regex = 0;
753     }
754
755     pl->process = process;
756     pl->pid = -1;
757     pl->next = processlist_head;
758     processlist_head = pl;
759   } else {
760     return (-1);
761   }
762
763   return (0);
764 } /* }}} int procevent_config */
765
766 static void submit(int pid, const char *type, /* {{{ */
767                    gauge_t value, const char *process) {
768   value_list_t vl = VALUE_LIST_INIT;
769   char hostname[1024];
770
771   vl.values = &(value_t){.gauge = value};
772   vl.values_len = 1;
773   sstrncpy(vl.plugin, "procevent", sizeof(vl.plugin));
774   sstrncpy(vl.plugin_instance, process, sizeof(vl.plugin_instance));
775   sstrncpy(vl.type, type, sizeof(vl.type));
776
777   DEBUG("procevent plugin: dispatching state %d for PID %d (%s)", (int)value,
778         pid, process);
779
780   // Create metadata to store JSON key-values
781   meta_data_t *meta = meta_data_create();
782
783   vl.meta = meta;
784
785   gethostname(hostname, sizeof(hostname));
786
787   if (value == 1) {
788     meta_data_add_string(meta, "condition", "process_up");
789     meta_data_add_string(meta, "entity", process);
790     meta_data_add_string(meta, "source", hostname);
791     meta_data_add_string(meta, "dest", "process_down");
792   } else {
793     meta_data_add_string(meta, "condition", "process_down");
794     meta_data_add_string(meta, "entity", process);
795     meta_data_add_string(meta, "source", hostname);
796     meta_data_add_string(meta, "dest", "process_up");
797   }
798
799   plugin_dispatch_values(&vl);
800 } /* }}} void interface_submit */
801
802 static int procevent_read(void) /* {{{ */
803 {
804   if (procevent_thread_error != 0) {
805     ERROR(
806         "procevent plugin: The interface thread had a problem. Restarting it.");
807
808     stop_thread(0);
809
810     start_thread();
811
812     return (-1);
813   } /* if (procevent_thread_error != 0) */
814
815   pthread_mutex_lock(&procevent_lock);
816
817   while (ring.head != ring.tail) {
818     int next = ring.tail + 1;
819
820     if (next >= ring.maxLen)
821       next = 0;
822
823     if (ring.buffer[ring.tail][1] == PROCEVENT_EXITED) {
824       processlist_t *pl = process_map_check(ring.buffer[ring.tail][0], NULL);
825
826       if (pl != NULL) {
827         // This process is of interest to us, so publish its EXITED status
828         submit(ring.buffer[ring.tail][0], "gauge", ring.buffer[ring.tail][1],
829                pl->process);
830         DEBUG("procevent plugin: PID %d (%s) EXITED, removing PID from process "
831               "list",
832               pl->pid, pl->process);
833         pl->pid = -1;
834       }
835     } else if (ring.buffer[ring.tail][1] == PROCEVENT_STARTED) {
836       // a new process has started, so check if we should monitor it
837       processlist_t *pl = process_check(ring.buffer[ring.tail][0]);
838
839       if (pl != NULL) {
840         // This process is of interest to us, so publish its STARTED status
841         submit(ring.buffer[ring.tail][0], "gauge", ring.buffer[ring.tail][1],
842                pl->process);
843         DEBUG(
844             "procevent plugin: PID %d (%s) STARTED, adding PID to process list",
845             pl->pid, pl->process);
846       }
847     }
848
849     ring.tail = next;
850   }
851
852   pthread_mutex_unlock(&procevent_lock);
853
854   return (0);
855 } /* }}} int procevent_read */
856
857 static int procevent_shutdown(void) /* {{{ */
858 {
859   // int status = 0;
860   processlist_t *pl;
861
862   DEBUG("procevent plugin: Shutting down thread.");
863
864   if (stop_thread(1) < 0)
865     return (-1);
866
867   for (int i = 0; i < buffer_length; i++) {
868     free(ring.buffer[i]);
869   }
870
871   free(ring.buffer);
872
873   pl = processlist_head;
874   while (pl != NULL) {
875     processlist_t *pl_next;
876
877     pl_next = pl->next;
878
879     if (pl->is_regex == 1) {
880       sfree(pl->process_regex);
881       regfree(&pl->process_regex_obj);
882     }
883
884     sfree(pl->process);
885     sfree(pl);
886
887     pl = pl_next;
888   }
889
890   return (0);
891 } /* }}} int procevent_shutdown */
892
893 void module_register(void) {
894   plugin_register_config("procevent", procevent_config, config_keys,
895                          config_keys_num);
896   plugin_register_init("procevent", procevent_init);
897   plugin_register_read("procevent", procevent_read);
898   plugin_register_shutdown("procevent", procevent_shutdown);
899 } /* void module_register */