2 * collectd - src/procevent.c
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:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
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.
24 * Andrew Bays <abays at redhat.com>
31 #include "utils_complain.h"
32 #include "utils_ignorelist.h"
38 #include <sys/socket.h>
42 #include <linux/cn_proc.h>
43 #include <linux/connector.h>
44 #include <linux/netlink.h>
45 #include <linux/rtnetlink.h>
51 #include <yajl/yajl_common.h>
52 #include <yajl/yajl_gen.h>
53 #if HAVE_YAJL_YAJL_VERSION_H
54 #include <yajl/yajl_version.h>
56 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
57 #define HAVE_YAJL_V2 1
60 #define PROCEVENT_EXITED 0
61 #define PROCEVENT_STARTED 1
62 #define PROCEVENT_FIELDS 4 // pid, status, extra, timestamp
64 #define PROCDIR "/proc"
66 #define PROCEVENT_DOMAIN_FIELD "domain"
67 #define PROCEVENT_DOMAIN_VALUE "fault"
68 #define PROCEVENT_EVENT_ID_FIELD "eventId"
69 #define PROCEVENT_EVENT_NAME_FIELD "eventName"
70 #define PROCEVENT_EVENT_NAME_DOWN_VALUE "down"
71 #define PROCEVENT_EVENT_NAME_UP_VALUE "up"
72 #define PROCEVENT_LAST_EPOCH_MICROSEC_FIELD "lastEpochMicrosec"
73 #define PROCEVENT_PRIORITY_FIELD "priority"
74 #define PROCEVENT_PRIORITY_VALUE "high"
75 #define PROCEVENT_REPORTING_ENTITY_NAME_FIELD "reportingEntityName"
76 #define PROCEVENT_REPORTING_ENTITY_NAME_VALUE "collectd procevent plugin"
77 #define PROCEVENT_SEQUENCE_FIELD "sequence"
78 #define PROCEVENT_SEQUENCE_VALUE "0"
79 #define PROCEVENT_SOURCE_NAME_FIELD "sourceName"
80 #define PROCEVENT_START_EPOCH_MICROSEC_FIELD "startEpochMicrosec"
81 #define PROCEVENT_VERSION_FIELD "version"
82 #define PROCEVENT_VERSION_VALUE "1.0"
84 #define PROCEVENT_ALARM_CONDITION_FIELD "alarmCondition"
85 #define PROCEVENT_ALARM_INTERFACE_A_FIELD "alarmInterfaceA"
86 #define PROCEVENT_EVENT_SEVERITY_FIELD "eventSeverity"
87 #define PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE "CRITICAL"
88 #define PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE "NORMAL"
89 #define PROCEVENT_EVENT_SOURCE_TYPE_FIELD "eventSourceType"
90 #define PROCEVENT_EVENT_SOURCE_TYPE_VALUE "process"
91 #define PROCEVENT_FAULT_FIELDS_FIELD "faultFields"
92 #define PROCEVENT_FAULT_FIELDS_VERSION_FIELD "faultFieldsVersion"
93 #define PROCEVENT_FAULT_FIELDS_VERSION_VALUE "1.0"
94 #define PROCEVENT_SPECIFIC_PROBLEM_FIELD "specificProblem"
95 #define PROCEVENT_SPECIFIC_PROBLEM_DOWN_VALUE "down"
96 #define PROCEVENT_SPECIFIC_PROBLEM_UP_VALUE "up"
97 #define PROCEVENT_VF_STATUS_FIELD "vfStatus"
98 #define PROCEVENT_VF_STATUS_CRITICAL_VALUE "Ready to terminate"
99 #define PROCEVENT_VF_STATUS_NORMAL_VALUE "Active"
109 long long unsigned int **buffer;
112 struct processlist_s {
117 struct processlist_s *next;
119 typedef struct processlist_s processlist_t;
124 static ignorelist_t *ignorelist = NULL;
126 static int procevent_thread_loop = 0;
127 static int procevent_thread_error = 0;
128 static pthread_t procevent_thread_id;
129 static pthread_mutex_t procevent_lock = PTHREAD_MUTEX_INITIALIZER;
130 static pthread_cond_t procevent_cond = PTHREAD_COND_INITIALIZER;
131 static pthread_mutex_t procevent_list_lock = PTHREAD_MUTEX_INITIALIZER;
132 static int nl_sock = -1;
133 static int buffer_length;
134 static circbuf_t ring;
135 static processlist_t *processlist_head = NULL;
136 static int event_id = 0;
138 static const char *config_keys[] = {"BufferLength", "Process", "RegexProcess"};
139 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
145 static int gen_message_payload(int state, int pid, char *process,
146 long long unsigned int timestamp, char **buf) {
147 const unsigned char *buf2;
149 char json_str[DATA_MAX_NAME_LEN];
151 #if !defined(HAVE_YAJL_V2)
152 yajl_gen_config conf = {};
159 g = yajl_gen_alloc(NULL);
160 yajl_gen_config(g, yajl_gen_beautify, 0);
163 g = yajl_gen_alloc(&conf, NULL);
168 // *** BEGIN common event header ***
170 if (yajl_gen_map_open(g) != yajl_gen_status_ok)
174 if (yajl_gen_string(g, (u_char *)PROCEVENT_DOMAIN_FIELD,
175 strlen(PROCEVENT_DOMAIN_FIELD)) != yajl_gen_status_ok)
178 if (yajl_gen_string(g, (u_char *)PROCEVENT_DOMAIN_VALUE,
179 strlen(PROCEVENT_DOMAIN_VALUE)) != yajl_gen_status_ok)
183 if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_ID_FIELD,
184 strlen(PROCEVENT_EVENT_ID_FIELD)) != yajl_gen_status_ok)
187 event_id = event_id + 1;
188 int event_id_len = sizeof(char) * sizeof(int) * 4 + 1;
189 memset(json_str, '\0', DATA_MAX_NAME_LEN);
190 snprintf(json_str, event_id_len, "%d", event_id);
192 if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
197 if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_NAME_FIELD,
198 strlen(PROCEVENT_EVENT_NAME_FIELD)) != yajl_gen_status_ok)
201 int event_name_len = 0;
202 event_name_len = event_name_len + (sizeof(char) * sizeof(int) * 4); // pid
203 event_name_len = event_name_len + strlen(process); // process name
204 event_name_len = event_name_len + (state == 0 ? 4 : 2); // "down" or "up"
205 event_name_len = event_name_len +
206 13; // "process", 3 spaces, 2 parentheses and null-terminator
207 memset(json_str, '\0', DATA_MAX_NAME_LEN);
208 snprintf(json_str, event_name_len, "process %s (%d) %s", process, pid,
209 (state == 0 ? PROCEVENT_EVENT_NAME_DOWN_VALUE
210 : PROCEVENT_EVENT_NAME_UP_VALUE));
212 if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
213 yajl_gen_status_ok) {
218 if (yajl_gen_string(g, (u_char *)PROCEVENT_LAST_EPOCH_MICROSEC_FIELD,
219 strlen(PROCEVENT_LAST_EPOCH_MICROSEC_FIELD)) !=
223 int last_epoch_microsec_len =
224 sizeof(char) * sizeof(long long unsigned int) * 4 + 1;
225 memset(json_str, '\0', DATA_MAX_NAME_LEN);
226 snprintf(json_str, last_epoch_microsec_len, "%llu",
227 (long long unsigned int)CDTIME_T_TO_US(cdtime()));
229 if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
234 if (yajl_gen_string(g, (u_char *)PROCEVENT_PRIORITY_FIELD,
235 strlen(PROCEVENT_PRIORITY_FIELD)) != yajl_gen_status_ok)
238 if (yajl_gen_string(g, (u_char *)PROCEVENT_PRIORITY_VALUE,
239 strlen(PROCEVENT_PRIORITY_VALUE)) != yajl_gen_status_ok)
242 // reportingEntityName
243 if (yajl_gen_string(g, (u_char *)PROCEVENT_REPORTING_ENTITY_NAME_FIELD,
244 strlen(PROCEVENT_REPORTING_ENTITY_NAME_FIELD)) !=
248 if (yajl_gen_string(g, (u_char *)PROCEVENT_REPORTING_ENTITY_NAME_VALUE,
249 strlen(PROCEVENT_REPORTING_ENTITY_NAME_VALUE)) !=
254 if (yajl_gen_string(g, (u_char *)PROCEVENT_SEQUENCE_FIELD,
255 strlen(PROCEVENT_SEQUENCE_FIELD)) != yajl_gen_status_ok)
258 if (yajl_gen_number(g, PROCEVENT_SEQUENCE_VALUE,
259 strlen(PROCEVENT_SEQUENCE_VALUE)) != yajl_gen_status_ok)
263 if (yajl_gen_string(g, (u_char *)PROCEVENT_SOURCE_NAME_FIELD,
264 strlen(PROCEVENT_SOURCE_NAME_FIELD)) !=
268 if (yajl_gen_string(g, (u_char *)process, strlen(process)) !=
272 // startEpochMicrosec
273 if (yajl_gen_string(g, (u_char *)PROCEVENT_START_EPOCH_MICROSEC_FIELD,
274 strlen(PROCEVENT_START_EPOCH_MICROSEC_FIELD)) !=
278 int start_epoch_microsec_len =
279 sizeof(char) * sizeof(long long unsigned int) * 4 + 1;
280 memset(json_str, '\0', DATA_MAX_NAME_LEN);
281 snprintf(json_str, start_epoch_microsec_len, "%llu",
282 (long long unsigned int)timestamp);
284 if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
289 if (yajl_gen_string(g, (u_char *)PROCEVENT_VERSION_FIELD,
290 strlen(PROCEVENT_VERSION_FIELD)) != yajl_gen_status_ok)
293 if (yajl_gen_number(g, PROCEVENT_VERSION_VALUE,
294 strlen(PROCEVENT_VERSION_VALUE)) != yajl_gen_status_ok)
297 // *** END common event header ***
299 // *** BEGIN fault fields ***
301 if (yajl_gen_string(g, (u_char *)PROCEVENT_FAULT_FIELDS_FIELD,
302 strlen(PROCEVENT_FAULT_FIELDS_FIELD)) !=
306 if (yajl_gen_map_open(g) != yajl_gen_status_ok)
310 if (yajl_gen_string(g, (u_char *)PROCEVENT_ALARM_CONDITION_FIELD,
311 strlen(PROCEVENT_ALARM_CONDITION_FIELD)) !=
315 int alarm_condition_len = 0;
316 alarm_condition_len =
317 alarm_condition_len + (sizeof(char) * sizeof(int) * 4); // pid
318 alarm_condition_len = alarm_condition_len + strlen(process); // process name
319 alarm_condition_len =
320 alarm_condition_len + 25; // "process", "state", "change", 4 spaces, 2
321 // parentheses and null-terminator
322 memset(json_str, '\0', DATA_MAX_NAME_LEN);
323 snprintf(json_str, alarm_condition_len, "process %s (%d) state change",
326 if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
327 yajl_gen_status_ok) {
332 if (yajl_gen_string(g, (u_char *)PROCEVENT_ALARM_INTERFACE_A_FIELD,
333 strlen(PROCEVENT_ALARM_INTERFACE_A_FIELD)) !=
337 if (yajl_gen_string(g, (u_char *)process, strlen(process)) !=
342 if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SEVERITY_FIELD,
343 strlen(PROCEVENT_EVENT_SEVERITY_FIELD)) !=
348 g, (u_char *)(state == 0 ? PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE
349 : PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE),
350 strlen((state == 0 ? PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE
351 : PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE))) !=
356 if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SOURCE_TYPE_FIELD,
357 strlen(PROCEVENT_EVENT_SOURCE_TYPE_FIELD)) !=
361 if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SOURCE_TYPE_VALUE,
362 strlen(PROCEVENT_EVENT_SOURCE_TYPE_VALUE)) !=
366 // faultFieldsVersion
367 if (yajl_gen_string(g, (u_char *)PROCEVENT_FAULT_FIELDS_VERSION_FIELD,
368 strlen(PROCEVENT_FAULT_FIELDS_VERSION_FIELD)) !=
372 if (yajl_gen_number(g, PROCEVENT_FAULT_FIELDS_VERSION_VALUE,
373 strlen(PROCEVENT_FAULT_FIELDS_VERSION_VALUE)) !=
378 if (yajl_gen_string(g, (u_char *)PROCEVENT_SPECIFIC_PROBLEM_FIELD,
379 strlen(PROCEVENT_SPECIFIC_PROBLEM_FIELD)) !=
383 int specific_problem_len = 0;
384 specific_problem_len =
385 specific_problem_len + (sizeof(char) * sizeof(int) * 4); // pid
386 specific_problem_len = specific_problem_len + strlen(process); // process name
387 specific_problem_len =
388 specific_problem_len + (state == 0 ? 4 : 2); // "down" or "up"
389 specific_problem_len =
390 specific_problem_len +
391 13; // "process", 3 spaces, 2 parentheses and null-terminator
392 memset(json_str, '\0', DATA_MAX_NAME_LEN);
393 snprintf(json_str, specific_problem_len, "process %s (%d) %s", process, pid,
394 (state == 0 ? PROCEVENT_SPECIFIC_PROBLEM_DOWN_VALUE
395 : PROCEVENT_SPECIFIC_PROBLEM_UP_VALUE));
397 if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
398 yajl_gen_status_ok) {
403 if (yajl_gen_string(g, (u_char *)PROCEVENT_VF_STATUS_FIELD,
404 strlen(PROCEVENT_VF_STATUS_FIELD)) != yajl_gen_status_ok)
408 g, (u_char *)(state == 0 ? PROCEVENT_VF_STATUS_CRITICAL_VALUE
409 : PROCEVENT_VF_STATUS_NORMAL_VALUE),
410 strlen((state == 0 ? PROCEVENT_VF_STATUS_CRITICAL_VALUE
411 : PROCEVENT_VF_STATUS_NORMAL_VALUE))) !=
415 if (yajl_gen_map_close(g) != yajl_gen_status_ok)
418 // *** END fault fields ***
420 if (yajl_gen_map_close(g) != yajl_gen_status_ok)
423 if (yajl_gen_get_buf(g, &buf2, &len) != yajl_gen_status_ok)
426 *buf = malloc(strlen((char *)buf2) + 1);
430 ERROR("procevent plugin: malloc failed during gen_message_payload: %s",
431 sstrerror(errno, errbuf, sizeof(errbuf)));
435 sstrncpy(*buf, (char *)buf2, strlen((char *)buf2) + 1);
443 ERROR("procevent plugin: gen_message_payload failed to generate JSON");
447 // Does /proc/<pid>/comm contain a process name we are interested in?
448 static processlist_t *process_check(int pid) {
449 int len, is_match, retval;
452 char buffer[BUFSIZE];
454 len = snprintf(file, sizeof(file), PROCDIR "/%d/comm", pid);
456 if ((len < 0) || (len >= BUFSIZE)) {
457 WARNING("procevent process_check: process name too large");
461 if (NULL == (fh = fopen(file, "r"))) {
462 // No /proc/<pid>/comm for this pid, just ignore
463 DEBUG("procevent plugin: no comm file available for pid %d", pid);
467 retval = fscanf(fh, "%[^\n]", buffer);
470 WARNING("procevent process_check: unable to read comm file for pid %d",
476 // Now that we have the process name in the buffer, check if we are
477 // even interested in it
478 if (ignorelist_match(ignorelist, buffer) != 0) {
479 DEBUG("procevent process_check: ignoring process %s (%d)", buffer, pid);
490 // Go through the processlist linked list and look for the process name
491 // in /proc/<pid>/comm. If found:
492 // 1. If pl->pid is -1, then set pl->pid to <pid> (and return that object)
493 // 2. If pl->pid is not -1, then another <process name> process was already
494 // found. If <pid> == pl->pid, this is an old match, so do nothing.
495 // If the <pid> is different, however, make a new processlist_t and
496 // associate <pid> with it (with the same process name as the existing).
499 pthread_mutex_lock(&procevent_list_lock);
502 processlist_t *match = NULL;
504 for (pl = processlist_head; pl != NULL; pl = pl->next) {
506 is_match = (strcmp(buffer, pl->process) == 0 ? 1 : 0);
509 DEBUG("procevent plugin: process %d name match for %s", pid, buffer);
511 if (pl->pid == pid) {
512 // this is a match, and we've already stored the exact pid/name combo
515 } else if (pl->pid == -1) {
516 // this is a match, and we've found a candidate processlist_t to store
517 // this new pid/name combo
521 } else if (pl->pid != -1) {
522 // this is a match, but another instance of this process has already
523 // claimed this pid/name combo,
532 (match != NULL && match->pid != -1 && match->pid != pid)) {
533 // if there wasn't an existing match, OR
534 // if there was a match but the associated processlist_t object already
535 // contained a pid/name combo,
536 // then make a new one and add it to the linked list
539 "procevent plugin: allocating new processlist_t object for PID %d (%s)",
545 pl2 = malloc(sizeof(*pl2));
548 ERROR("procevent plugin: malloc failed during process_check: %s",
549 sstrerror(errno, errbuf, sizeof(errbuf)));
550 pthread_mutex_unlock(&procevent_list_lock);
554 process = strdup(buffer);
555 if (process == NULL) {
558 ERROR("procevent plugin: strdup failed during process_check: %s",
559 sstrerror(errno, errbuf, sizeof(errbuf)));
560 pthread_mutex_unlock(&procevent_list_lock);
564 pl2->process = process;
566 pl2->next = processlist_head;
567 processlist_head = pl2;
572 pthread_mutex_unlock(&procevent_list_lock);
577 // Does our map have this PID or name?
578 static processlist_t *process_map_check(int pid, char *process) {
581 pthread_mutex_lock(&procevent_list_lock);
583 for (pl = processlist_head; pl != NULL; pl = pl->next) {
585 int match_process = 0;
593 if (process != NULL) {
594 if (strcmp(pl->process, process) == 0)
598 if (pid > 0 && process == NULL && match_pid == 1)
600 else if (pid < 0 && process != NULL && match_process == 1)
602 else if (pid > 0 && process != NULL && match_pid == 1 && match_process == 1)
606 pthread_mutex_unlock(&procevent_list_lock);
611 pthread_mutex_unlock(&procevent_list_lock);
616 static int process_map_refresh(void) {
620 proc = opendir(PROCDIR);
623 ERROR("procevent plugin: fopen (%s): %s", PROCDIR,
624 sstrerror(errno, errbuf, sizeof(errbuf)));
638 dent = readdir(proc);
642 if (errno == 0) /* end of directory */
645 ERROR("procevent plugin: failed to read directory %s: %s", PROCDIR,
646 sstrerror(errno, errbuf, sizeof(errbuf)));
651 if (dent->d_name[0] == '.')
654 len = snprintf(file, sizeof(file), PROCDIR "/%s", dent->d_name);
655 if ((len < 0) || (len >= BUFSIZE))
658 status = stat(file, &statbuf);
661 WARNING("procevent plugin: stat (%s) failed: %s", file,
662 sstrerror(errno, errbuf, sizeof(errbuf)));
666 if (!S_ISDIR(statbuf.st_mode))
669 len = snprintf(file, sizeof(file), PROCDIR "/%s/comm", dent->d_name);
670 if ((len < 0) || (len >= BUFSIZE))
675 for (int i = 0; i < strlen(dent->d_name); i++) {
676 if (!isdigit(dent->d_name[i])) {
685 // Check if we need to store this pid/name combo in our processlist_t linked
687 int this_pid = atoi(dent->d_name);
688 processlist_t *pl = process_check(this_pid);
691 DEBUG("procevent plugin: process map refreshed for PID %d and name %s",
692 this_pid, pl->process);
700 static int nl_connect() {
702 struct sockaddr_nl sa_nl;
704 nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
706 ERROR("procevent plugin: socket open failed.");
710 sa_nl.nl_family = AF_NETLINK;
711 sa_nl.nl_groups = CN_IDX_PROC;
712 sa_nl.nl_pid = getpid();
714 rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
716 ERROR("procevent plugin: socket bind failed.");
724 static int set_proc_ev_listen(bool enable) {
726 struct __attribute__((aligned(NLMSG_ALIGNTO))) {
727 struct nlmsghdr nl_hdr;
728 struct __attribute__((__packed__)) {
729 struct cn_msg cn_msg;
730 enum proc_cn_mcast_op cn_mcast;
734 memset(&nlcn_msg, 0, sizeof(nlcn_msg));
735 nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg);
736 nlcn_msg.nl_hdr.nlmsg_pid = getpid();
737 nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE;
739 nlcn_msg.cn_msg.id.idx = CN_IDX_PROC;
740 nlcn_msg.cn_msg.id.val = CN_VAL_PROC;
741 nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op);
743 nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;
745 rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
747 ERROR("procevent plugin: subscribing to netlink process events failed.");
754 static int read_event() {
758 int proc_status = -1;
760 struct __attribute__((aligned(NLMSG_ALIGNTO))) {
761 struct nlmsghdr nl_hdr;
762 struct __attribute__((__packed__)) {
763 struct cn_msg cn_msg;
764 struct proc_event proc_ev;
771 status = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
775 } else if (status == -1) {
776 if (errno != EINTR) {
777 ERROR("procevent plugin: socket receive error: %d", errno);
782 switch (nlcn_msg.proc_ev.what) {
783 case PROC_EVENT_NONE:
784 case PROC_EVENT_FORK:
787 // Not of interest in current version
789 case PROC_EVENT_EXEC:
790 proc_status = PROCEVENT_STARTED;
791 proc_id = nlcn_msg.proc_ev.event_data.exec.process_pid;
793 case PROC_EVENT_EXIT:
794 proc_id = nlcn_msg.proc_ev.event_data.exit.process_pid;
795 proc_status = PROCEVENT_EXITED;
796 proc_extra = nlcn_msg.proc_ev.event_data.exit.exit_code;
802 // If we're interested in this process status event, place the event
803 // in the ring buffer for consumption by the main polling thread.
805 if (proc_status != -1) {
806 pthread_mutex_lock(&procevent_lock);
808 int next = ring.head + 1;
809 if (next >= ring.maxLen)
812 if (next == ring.tail) {
813 WARNING("procevent plugin: ring buffer full");
815 DEBUG("procevent plugin: Process %d status is now %s at %llu", proc_id,
816 (proc_status == PROCEVENT_EXITED ? "EXITED" : "STARTED"),
817 (long long unsigned int)CDTIME_T_TO_US(cdtime()));
819 if (proc_status == PROCEVENT_EXITED) {
820 ring.buffer[ring.head][0] = proc_id;
821 ring.buffer[ring.head][1] = proc_status;
822 ring.buffer[ring.head][2] = proc_extra;
823 ring.buffer[ring.head][3] =
824 (long long unsigned int)CDTIME_T_TO_US(cdtime());
826 ring.buffer[ring.head][0] = proc_id;
827 ring.buffer[ring.head][1] = proc_status;
828 ring.buffer[ring.head][2] = 0;
829 ring.buffer[ring.head][3] =
830 (long long unsigned int)CDTIME_T_TO_US(cdtime());
836 pthread_mutex_unlock(&procevent_lock);
842 static void *procevent_thread(void *arg) /* {{{ */
844 pthread_mutex_lock(&procevent_lock);
846 while (procevent_thread_loop > 0) {
849 pthread_mutex_unlock(&procevent_lock);
853 status = read_event();
855 pthread_mutex_lock(&procevent_lock);
858 procevent_thread_error = 1;
862 if (procevent_thread_loop <= 0)
864 } /* while (procevent_thread_loop > 0) */
866 pthread_mutex_unlock(&procevent_lock);
869 } /* }}} void *procevent_thread */
871 static int start_thread(void) /* {{{ */
875 pthread_mutex_lock(&procevent_lock);
877 if (procevent_thread_loop != 0) {
878 pthread_mutex_unlock(&procevent_lock);
883 status = nl_connect();
888 status = set_proc_ev_listen(true);
893 DEBUG("procevent plugin: socket created and bound");
895 procevent_thread_loop = 1;
896 procevent_thread_error = 0;
898 status = plugin_thread_create(&procevent_thread_id, /* attr = */ NULL,
900 /* arg = */ (void *)0, "procevent");
902 procevent_thread_loop = 0;
903 ERROR("procevent plugin: Starting thread failed.");
904 pthread_mutex_unlock(&procevent_lock);
908 pthread_mutex_unlock(&procevent_lock);
910 } /* }}} int start_thread */
912 static int stop_thread(int shutdown) /* {{{ */
917 status = close(nl_sock);
919 ERROR("procevent plugin: failed to close socket %d: %d (%s)", nl_sock,
920 status, strerror(errno));
926 pthread_mutex_lock(&procevent_lock);
928 if (procevent_thread_loop == 0) {
929 pthread_mutex_unlock(&procevent_lock);
933 procevent_thread_loop = 0;
934 pthread_cond_broadcast(&procevent_cond);
935 pthread_mutex_unlock(&procevent_lock);
938 // Calling pthread_cancel here in
939 // the case of a shutdown just assures that the thread is
940 // gone and that the process has been fully terminated.
942 DEBUG("procevent plugin: Canceling thread for process shutdown");
944 status = pthread_cancel(procevent_thread_id);
947 ERROR("procevent plugin: Unable to cancel thread: %d", status);
951 status = pthread_join(procevent_thread_id, /* return = */ NULL);
953 ERROR("procevent plugin: Stopping thread failed.");
958 pthread_mutex_lock(&procevent_lock);
959 memset(&procevent_thread_id, 0, sizeof(procevent_thread_id));
960 procevent_thread_error = 0;
961 pthread_mutex_unlock(&procevent_lock);
963 DEBUG("procevent plugin: Finished requesting stop of thread");
966 } /* }}} int stop_thread */
968 static int procevent_init(void) /* {{{ */
974 ring.maxLen = buffer_length;
975 ring.buffer = (long long unsigned int **)malloc(
976 buffer_length * sizeof(long long unsigned int *));
978 for (int i = 0; i < buffer_length; i++) {
979 ring.buffer[i] = (long long unsigned int *)malloc(
980 PROCEVENT_FIELDS * sizeof(long long unsigned int));
983 status = process_map_refresh();
986 ERROR("procevent plugin: Initial process mapping failed.");
990 if (processlist_head == NULL) {
991 NOTICE("procevent plugin: No processes have been configured.");
995 return (start_thread());
996 } /* }}} int procevent_init */
998 static int procevent_config(const char *key, const char *value) /* {{{ */
1002 if (ignorelist == NULL)
1003 ignorelist = ignorelist_create(/* invert = */ 1);
1005 if (strcasecmp(key, "BufferLength") == 0) {
1006 buffer_length = atoi(value);
1007 } else if (strcasecmp(key, "Process") == 0) {
1008 ignorelist_add(ignorelist, value);
1009 } else if (strcasecmp(key, "RegexProcess") == 0) {
1011 status = ignorelist_add(ignorelist, value);
1014 ERROR("procevent plugin: invalid regular expression: %s", value);
1018 WARNING("procevent plugin: The plugin has been compiled without support "
1019 "for the \"RegexProcess\" option.");
1026 } /* }}} int procevent_config */
1028 static void procevent_dispatch_notification(int pid, const char *type, /* {{{ */
1029 gauge_t value, char *process,
1030 long long unsigned int timestamp) {
1032 notification_t n = {NOTIF_FAILURE, cdtime(), "", "", "procevent", "", "", "",
1036 n.severity = NOTIF_OKAY;
1038 sstrncpy(n.host, hostname_g, sizeof(n.host));
1039 sstrncpy(n.plugin_instance, process, sizeof(n.plugin_instance));
1040 sstrncpy(n.type, "gauge", sizeof(n.type));
1041 sstrncpy(n.type_instance, "process_status", sizeof(n.type_instance));
1043 gen_message_payload(value, pid, process, timestamp, &buf);
1045 notification_meta_t *m = calloc(1, sizeof(*m));
1050 ERROR("procevent plugin: unable to allocate metadata: %s",
1051 sstrerror(errno, errbuf, sizeof(errbuf)));
1055 sstrncpy(m->name, "ves", sizeof(m->name));
1056 m->nm_value.nm_string = sstrdup(buf);
1057 m->type = NM_TYPE_STRING;
1060 DEBUG("procevent plugin: notification message: %s",
1061 n.meta->nm_value.nm_string);
1063 DEBUG("procevent plugin: dispatching state %d for PID %d (%s)", (int)value,
1066 plugin_dispatch_notification(&n);
1067 plugin_notification_meta_free(n.meta);
1069 // malloc'd in gen_message_payload
1074 static int procevent_read(void) /* {{{ */
1076 if (procevent_thread_error != 0) {
1078 "procevent plugin: The interface thread had a problem. Restarting it.");
1085 } /* if (procevent_thread_error != 0) */
1087 pthread_mutex_lock(&procevent_lock);
1089 while (ring.head != ring.tail) {
1090 int next = ring.tail + 1;
1092 if (next >= ring.maxLen)
1095 if (ring.buffer[ring.tail][1] == PROCEVENT_EXITED) {
1096 processlist_t *pl = process_map_check(ring.buffer[ring.tail][0], NULL);
1099 // This process is of interest to us, so publish its EXITED status
1100 procevent_dispatch_notification(ring.buffer[ring.tail][0], "gauge",
1101 ring.buffer[ring.tail][1], pl->process,
1102 ring.buffer[ring.tail][3]);
1103 DEBUG("procevent plugin: PID %d (%s) EXITED, removing PID from process "
1105 pl->pid, pl->process);
1108 } else if (ring.buffer[ring.tail][1] == PROCEVENT_STARTED) {
1109 // a new process has started, so check if we should monitor it
1110 processlist_t *pl = process_check(ring.buffer[ring.tail][0]);
1113 // This process is of interest to us, so publish its STARTED status
1114 procevent_dispatch_notification(ring.buffer[ring.tail][0], "gauge",
1115 ring.buffer[ring.tail][1], pl->process,
1116 ring.buffer[ring.tail][3]);
1118 "procevent plugin: PID %d (%s) STARTED, adding PID to process list",
1119 pl->pid, pl->process);
1126 pthread_mutex_unlock(&procevent_lock);
1129 } /* }}} int procevent_read */
1131 static int procevent_shutdown(void) /* {{{ */
1135 DEBUG("procevent plugin: Shutting down thread.");
1137 if (stop_thread(1) < 0)
1140 for (int i = 0; i < buffer_length; i++) {
1141 free(ring.buffer[i]);
1146 pl = processlist_head;
1147 while (pl != NULL) {
1148 processlist_t *pl_next;
1159 } /* }}} int procevent_shutdown */
1161 void module_register(void) {
1162 plugin_register_config("procevent", procevent_config, config_keys,
1164 plugin_register_init("procevent", procevent_init);
1165 plugin_register_read("procevent", procevent_read);
1166 plugin_register_shutdown("procevent", procevent_shutdown);
1167 } /* void module_register */