From eea34da7f3be18f8da52b5bb772436602dd65c84 Mon Sep 17 00:00:00 2001 From: Andrew Bays Date: Wed, 13 Dec 2017 12:21:57 -0500 Subject: [PATCH] connectivity plugin initial commit --- AUTHORS | 6 + Makefile.am | 8 + README | 3 + configure.ac | 2 + src/collectd.conf.in | 5 + src/collectd.conf.pod | 34 ++++ src/connectivity.c | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/types.db | 1 + 8 files changed, 586 insertions(+) create mode 100644 src/connectivity.c diff --git a/AUTHORS b/AUTHORS index 4df743c5..9fcb386d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -56,9 +56,15 @@ Amit Gupta Andreas Henriksson - libmnl support in the netlink plugin. +Andrew Bays + - connectivity plugin. + Andy Parkins - battery plugin: sysfs code. +Aneesh Puttur + - connectivity plugin. + Anthony Dewhurst - zfs_arc plugin. diff --git a/Makefile.am b/Makefile.am index e37716f6..e9a38135 100644 --- a/Makefile.am +++ b/Makefile.am @@ -637,6 +637,14 @@ chrony_la_LDFLAGS = $(PLUGIN_LDFLAGS) chrony_la_LIBADD = -lm endif +if BUILD_PLUGIN_CONNECTIVITY +pkglib_LTLIBRARIES += connectivity.la +connectivity_la_SOURCES = src/connectivity.c +connectivity_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBMNL_CFLAGS) +connectivity_la_LDFLAGS = $(PLUGIN_LDFLAGS) +connectivity_la_LIBADD = $(BUILD_WITH_LIBMNL_LIBS) +endif + if BUILD_PLUGIN_CONNTRACK pkglib_LTLIBRARIES += conntrack.la conntrack_la_SOURCES = src/conntrack.c diff --git a/README b/README index ca86c84d..2fb1ad48 100644 --- a/README +++ b/README @@ -54,6 +54,9 @@ Features - chrony Chrony daemon statistics: Local clock drift, offset to peers, etc. + - connectivity + Event-based interface status. + - conntrack Number of nf_conntrack entries. diff --git a/configure.ac b/configure.ac index 352a7b66..e5b7ebab 100644 --- a/configure.ac +++ b/configure.ac @@ -6522,6 +6522,7 @@ AC_PLUGIN([bind], [$plugin_bind], [ISC Bind nameserver AC_PLUGIN([ceph], [$plugin_ceph], [Ceph daemon statistics]) AC_PLUGIN([cgroups], [$plugin_cgroups], [CGroups CPU usage accounting]) AC_PLUGIN([chrony], [yes], [Chrony statistics]) +AC_PLUGIN([connectivity], [$with_libmnl], [Network interface up/down statistics]) AC_PLUGIN([conntrack], [$plugin_conntrack], [nf_conntrack statistics]) AC_PLUGIN([contextswitch], [$plugin_contextswitch], [context switch statistics]) AC_PLUGIN([cpu], [$plugin_cpu], [CPU usage statistics]) @@ -6941,6 +6942,7 @@ AC_MSG_RESULT([ bind . . . . . . . . $enable_bind]) AC_MSG_RESULT([ ceph . . . . . . . . $enable_ceph]) AC_MSG_RESULT([ cgroups . . . . . . . $enable_cgroups]) AC_MSG_RESULT([ chrony. . . . . . . . $enable_chrony]) +AC_MSG_RESULT([ connectivity. . . . . $enable_connectivity]) AC_MSG_RESULT([ conntrack . . . . . . $enable_conntrack]) AC_MSG_RESULT([ contextswitch . . . . $enable_contextswitch]) AC_MSG_RESULT([ cpu . . . . . . . . . $enable_cpu]) diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 6ec61f32..6b97115e 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -101,6 +101,7 @@ #@BUILD_PLUGIN_CEPH_TRUE@LoadPlugin ceph #@BUILD_PLUGIN_CGROUPS_TRUE@LoadPlugin cgroups #@BUILD_PLUGIN_CHRONY_TRUE@LoadPlugin chrony +#@BUILD_PLUGIN_CONNECTIVITY_TRUE@LoadPlugin connectivity #@BUILD_PLUGIN_CONNTRACK_TRUE@LoadPlugin conntrack #@BUILD_PLUGIN_CONTEXTSWITCH_TRUE@LoadPlugin contextswitch @BUILD_PLUGIN_CPU_TRUE@@BUILD_PLUGIN_CPU_TRUE@LoadPlugin cpu @@ -355,6 +356,10 @@ # Timeout "2" # +# +# Interface eth0 +# + # # CGroup "libvirt" # IgnoreSelected false diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index dfd785a2..a9bdb6d6 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -1409,6 +1409,40 @@ Connection timeout in seconds. Defaults to B<2>. =back +=head2 Plugin Connectivity + +connectivity - Documentation of collectd's C + + + LoadPlugin connectivity + # ... + + Interface eth0 + + +The C queries interface status using netlink (man 7 netlink) which provides information about network interfaces via the NETLINK_ROUTE family (man 7 rtnetlink). The plugin translates the value it receives to collectd's internal format and, depending on the write plugins you have loaded, it may be written to disk or submitted to another instance. +The plugin listens to interfaces configured in LoadPlugin (see configuration below). + +Here this example shows C monitoring 2 interfaces "eth0" and "eth1" +LoadPlugin connectivity + + Interface eth0 + Interface eth1 + + +=over 4 + +=item B I + +interface(s) to monitor connect to. + +=item I + +If I is greater than or equal to zero the message indicates interface is up, +if I is less than zero the message indicates interface is down. + +=back + =head2 Plugin C This plugin collects IP conntrack statistics. diff --git a/src/connectivity.c b/src/connectivity.c new file mode 100644 index 00000000..5a27fe1f --- /dev/null +++ b/src/connectivity.c @@ -0,0 +1,527 @@ +/** + * collectd - src/connectivity.c + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Red Hat NFVPE + * Andrew Bays + * Aneesh Puttur + **/ + +#include "collectd.h" + +#include "common.h" +#include "plugin.h" +#include "utils_complain.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MYPROTO NETLINK_ROUTE + +/* + * Private data types + */ +struct interfacelist_s { + char *interface; + + uint32_t status; + uint32_t prev_status; + uint32_t sent; + uint32_t sec; + uint32_t usec; + + struct interfacelist_s *next; +}; +typedef struct interfacelist_s interfacelist_t; + +/* + * Private variables + */ +static interfacelist_t *interfacelist_head = NULL; + +static int connectivity_thread_loop = 0; +static int connectivity_thread_error = 0; +static pthread_t connectivity_thread_id; +static pthread_mutex_t connectivity_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t connectivity_cond = PTHREAD_COND_INITIALIZER; +static struct mnl_socket *sock; + +static const char *config_keys[] = {"Interface"}; +static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); + +/* + * Private functions + */ + +static int connectivity_link_state(struct nlmsghdr *msg) { + int retval = 0; + struct ifinfomsg *ifi = mnl_nlmsg_get_payload(msg); + struct nlattr *attr; + const char *dev = NULL; + + pthread_mutex_lock(&connectivity_lock); + + interfacelist_t *il; + + /* Scan attribute list for device name. */ + mnl_attr_for_each(attr, msg, sizeof(*ifi)) { + if (mnl_attr_get_type(attr) != IFLA_IFNAME) + continue; + + if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) { + ERROR("connectivity plugin: connectivity_link_state: IFLA_IFNAME " + "mnl_attr_validate " + "failed."); + pthread_mutex_unlock(&connectivity_lock); + return MNL_CB_ERROR; + } + + dev = mnl_attr_get_str(attr); + + for (il = interfacelist_head; il != NULL; il = il->next) + if (strcmp(dev, il->interface) == 0) + break; + + if (il == NULL) { + INFO("connectivity plugin: Ignoring link state change for unmonitored " + "interface: %s", + dev); + } else { + uint32_t prev_status; + struct timeval tv; + + gettimeofday(&tv, NULL); + + unsigned long long millisecondsSinceEpoch = + (unsigned long long)(tv.tv_sec) * 1000 + + (unsigned long long)(tv.tv_usec) / 1000; + + INFO("connectivity plugin (%llu): Interface %s status is now %s", + millisecondsSinceEpoch, dev, + ((ifi->ifi_flags & IFF_RUNNING) ? "UP" : "DOWN")); + prev_status = il->status; + il->status = ((ifi->ifi_flags & IFF_RUNNING) ? 1 : 0); + il->sec = tv.tv_sec; + il->usec = tv.tv_usec; + // If the new status is different than the previous status, + // store the previous status and set sent to zero + if (il->status != prev_status) { + il->prev_status = prev_status; + il->sent = 0; + } + } + + // no need to loop again, we found the interface name + // (otherwise the first if-statement in the loop would + // have moved us on with 'continue') + break; + } + + pthread_mutex_unlock(&connectivity_lock); + + return retval; +} + +static int msg_handler(struct nlmsghdr *msg) { + switch (msg->nlmsg_type) { + case RTM_NEWADDR: + break; + case RTM_DELADDR: + break; + case RTM_NEWROUTE: + break; + case RTM_DELROUTE: + break; + case RTM_NEWLINK: + connectivity_link_state(msg); + break; + case RTM_DELLINK: + break; + default: + ERROR("connectivity plugin: msg_handler: Unknown netlink nlmsg_type %d\n", + msg->nlmsg_type); + break; + } + return 0; +} + +static int read_event(struct mnl_socket *nl, + int (*msg_handler)(struct nlmsghdr *)) { + int status; + int ret = 0; + char buf[4096]; + struct nlmsghdr *h; + + if (nl == NULL) + return ret; + + status = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + + if (status < 0) { + /* Socket non-blocking so bail out once we have read everything */ + if (errno == EWOULDBLOCK || errno == EAGAIN) + return ret; + + /* Anything else is an error */ + ERROR("connectivity plugin: read_event: Error mnl_socket_recvfrom: %d\n", + status); + return status; + } + + if (status == 0) { + DEBUG("connectivity plugin: read_event: EOF\n"); + } + + /* We need to handle more than one message per 'recvmsg' */ + for (h = (struct nlmsghdr *)buf; NLMSG_OK(h, (unsigned int)status); + h = NLMSG_NEXT(h, status)) { + /* Finish reading */ + if (h->nlmsg_type == NLMSG_DONE) + return ret; + + /* Message is some kind of error */ + if (h->nlmsg_type == NLMSG_ERROR) { + ERROR("connectivity plugin: read_event: Message is an error - decode " + "TBD\n"); + return -1; // Error + } + + /* Call message handler */ + if (msg_handler) { + ret = (*msg_handler)(h); + if (ret < 0) { + ERROR("connectivity plugin: read_event: Message handler error %d\n", + ret); + return ret; + } + } else { + ERROR("connectivity plugin: read_event: Error NULL message handler\n"); + return -1; + } + } + + return ret; +} + +static void *connectivity_thread(void *arg) /* {{{ */ +{ + pthread_mutex_lock(&connectivity_lock); + + while (connectivity_thread_loop > 0) { + int status; + + pthread_mutex_unlock(&connectivity_lock); + + status = read_event(sock, msg_handler); + + pthread_mutex_lock(&connectivity_lock); + + if (status < 0) { + connectivity_thread_error = 1; + break; + } + + if (connectivity_thread_loop <= 0) + break; + } /* while (connectivity_thread_loop > 0) */ + + pthread_mutex_unlock(&connectivity_lock); + + return ((void *)0); +} /* }}} void *connectivity_thread */ + +static int start_thread(void) /* {{{ */ +{ + int status; + + pthread_mutex_lock(&connectivity_lock); + + if (connectivity_thread_loop != 0) { + pthread_mutex_unlock(&connectivity_lock); + return (0); + } + + connectivity_thread_loop = 1; + connectivity_thread_error = 0; + + if (sock == NULL) { + sock = mnl_socket_open(NETLINK_ROUTE); + if (sock == NULL) { + ERROR( + "connectivity plugin: connectivity_thread: mnl_socket_open failed."); + pthread_mutex_unlock(&connectivity_lock); + return (-1); + } + + if (mnl_socket_bind(sock, RTMGRP_LINK, MNL_SOCKET_AUTOPID) < 0) { + ERROR( + "connectivity plugin: connectivity_thread: mnl_socket_bind failed."); + pthread_mutex_unlock(&connectivity_lock); + return (1); + } + } + + status = plugin_thread_create(&connectivity_thread_id, /* attr = */ NULL, + connectivity_thread, + /* arg = */ (void *)0, "connectivity"); + if (status != 0) { + connectivity_thread_loop = 0; + ERROR("connectivity plugin: Starting thread failed."); + pthread_mutex_unlock(&connectivity_lock); + mnl_socket_close(sock); + return (-1); + } + + pthread_mutex_unlock(&connectivity_lock); + return (0); +} /* }}} int start_thread */ + +static int stop_thread(int shutdown) /* {{{ */ +{ + int status; + + if (sock != NULL) + mnl_socket_close(sock); + + pthread_mutex_lock(&connectivity_lock); + + if (connectivity_thread_loop == 0) { + pthread_mutex_unlock(&connectivity_lock); + return (-1); + } + + connectivity_thread_loop = 0; + pthread_cond_broadcast(&connectivity_cond); + pthread_mutex_unlock(&connectivity_lock); + + if (shutdown == 1) { + // Since the thread is blocking, calling pthread_join + // doesn't actually succeed in stopping it. It will stick around + // until a NETLINK message is received on the socket (at which + // it will realize that "connectivity_thread_loop" is 0 and will + // break out of the read loop and be allowed to die). This is + // fine when the process isn't supposed to be exiting, but in + // the case of a process shutdown, we don't want to have an + // idle thread hanging around. Calling pthread_cancel here in + // the case of a shutdown is just assures that the thread is + // gone and that the process has been fully terminated. + + INFO("connectivity plugin: Canceling thread for process shutdown"); + + status = pthread_cancel(connectivity_thread_id); + + if (status != 0) { + ERROR("connectivity plugin: Unable to cancel thread: %d", status); + status = -1; + } + } else { + status = pthread_join(connectivity_thread_id, /* return = */ NULL); + if (status != 0) { + ERROR("connectivity plugin: Stopping thread failed."); + status = -1; + } + } + + pthread_mutex_lock(&connectivity_lock); + memset(&connectivity_thread_id, 0, sizeof(connectivity_thread_id)); + connectivity_thread_error = 0; + pthread_mutex_unlock(&connectivity_lock); + + INFO("connectivity plugin: Finished requesting stop of thread"); + + return (status); +} /* }}} int stop_thread */ + +static int connectivity_init(void) /* {{{ */ +{ + if (interfacelist_head == NULL) { + NOTICE("connectivity plugin: No interfaces have been configured."); + return (-1); + } + + return (start_thread()); +} /* }}} int connectivity_init */ + +static int connectivity_config(const char *key, const char *value) /* {{{ */ +{ + if (strcasecmp(key, "Interface") == 0) { + interfacelist_t *il; + char *interface; + + il = malloc(sizeof(*il)); + if (il == NULL) { + char errbuf[1024]; + ERROR("connectivity plugin: malloc failed during connectivity_config: %s", + sstrerror(errno, errbuf, sizeof(errbuf))); + return (1); + } + + interface = strdup(value); + if (interface == NULL) { + char errbuf[1024]; + sfree(il); + ERROR("connectivity plugin: strdup failed connectivity_config: %s", + sstrerror(errno, errbuf, sizeof(errbuf))); + return (1); + } + + il->interface = interface; + il->status = 2; // "unknown" + il->prev_status = 2; + il->sent = 0; + il->next = interfacelist_head; + interfacelist_head = il; + + } else { + return (-1); + } + + return (0); +} /* }}} int connectivity_config */ + +static void submit(const char *interface, const char *type, /* {{{ */ + gauge_t value, uint32_t sec, uint32_t usec) { + value_list_t vl = VALUE_LIST_INIT; + char hostname[1024]; + vl.values = &(value_t){.gauge = value}; + vl.values_len = 1; + sstrncpy(vl.plugin, "connectivity", sizeof(vl.plugin)); + sstrncpy(vl.type_instance, interface, sizeof(vl.type_instance)); + sstrncpy(vl.type, type, sizeof(vl.type)); + + // Create metadata to store JSON key-values + meta_data_t *meta = meta_data_create(); + + vl.meta = meta; + // For latency measurement + struct timeval tv; + gettimeofday(&tv, NULL); + gethostname(hostname, sizeof(hostname)); + char strSec[11]; + char struSec[11]; + snprintf(strSec, sizeof strSec, "%" PRIu32, sec); + snprintf(struSec, sizeof struSec, "%" PRIu32, usec); + if (value == 1) { + meta_data_add_string(meta, "condition", "interface_up"); + meta_data_add_string(meta, "entity", interface); + meta_data_add_string(meta, "source", hostname); + meta_data_add_string(meta, "sec", strSec); + meta_data_add_string(meta, "usec", struSec); + meta_data_add_string(meta, "dest", "interface_down"); + } else { + meta_data_add_string(meta, "condition", "interface_down"); + meta_data_add_string(meta, "entity", interface); + meta_data_add_string(meta, "source", hostname); + meta_data_add_string(meta, "sec", strSec); + meta_data_add_string(meta, "usec", struSec); + meta_data_add_string(meta, "dest", "interface_up"); + } + + plugin_dispatch_values(&vl); +} /* }}} void interface_submit */ + +static int connectivity_read(void) /* {{{ */ +{ + if (connectivity_thread_error != 0) { + ERROR("connectivity plugin: The interface thread had a problem. Restarting " + "it."); + + stop_thread(0); + + for (interfacelist_t *il = interfacelist_head; il != NULL; il = il->next) { + il->status = 2; // signifies "unknown" + il->prev_status = 2; + il->sent = 0; + } + + start_thread(); + + return (-1); + } /* if (connectivity_thread_error != 0) */ + + for (interfacelist_t *il = interfacelist_head; il != NULL; + il = il->next) /* {{{ */ + { + uint32_t status; + uint32_t prev_status; + uint32_t sent; + + /* Locking here works, because the structure of the linked list is only + * changed during configure and shutdown. */ + pthread_mutex_lock(&connectivity_lock); + + status = il->status; + prev_status = il->prev_status; + sent = il->sent; + + if (status != prev_status && sent == 0) { + submit(il->interface, "gauge", status, il->sec, il->usec); + + il->sent = 1; + } + + pthread_mutex_unlock(&connectivity_lock); + } /* }}} for (il = interfacelist_head; il != NULL; il = il->next) */ + + return (0); +} /* }}} int connectivity_read */ + +static int connectivity_shutdown(void) /* {{{ */ +{ + interfacelist_t *il; + + INFO("connectivity plugin: Shutting down thread."); + if (stop_thread(1) < 0) + return (-1); + + il = interfacelist_head; + while (il != NULL) { + interfacelist_t *il_next; + + il_next = il->next; + + sfree(il->interface); + sfree(il); + + il = il_next; + } + + return (0); +} /* }}} int connectivity_shutdown */ + +void module_register(void) { + plugin_register_config("connectivity", connectivity_config, config_keys, + config_keys_num); + plugin_register_init("connectivity", connectivity_init); + plugin_register_read("connectivity", connectivity_read); + plugin_register_shutdown("connectivity", connectivity_shutdown); +} /* void module_register */ diff --git a/src/types.db b/src/types.db index f27ba465..01bb3539 100644 --- a/src/types.db +++ b/src/types.db @@ -32,6 +32,7 @@ clock_stratum value:GAUGE:0:U compression uncompressed:DERIVE:0:U, compressed:DERIVE:0:U compression_ratio value:GAUGE:0:2 connections value:DERIVE:0:U +connectivity value:GAUGE:0:2 conntrack value:GAUGE:0:4294967295 contextswitch value:DERIVE:0:U cookies value:DERIVE:0:U -- 2.11.0