Merge branch 'collectd-5.9' into collectd-5.10
authorFlorian Forster <octo@collectd.org>
Fri, 15 Nov 2019 09:47:11 +0000 (10:47 +0100)
committerFlorian Forster <octo@collectd.org>
Fri, 15 Nov 2019 09:47:11 +0000 (10:47 +0100)
41 files changed:
.travis.yml
AUTHORS
CODEOWNERS
ChangeLog
Makefile.am
README
bindings/perl/lib/Collectd/Plugins/OpenVZ.pm
configure.ac
contrib/collectd_network.py
contrib/collection3/share/navigate.js
contrib/php-collection/browser.js
contrib/redhat/collectd.spec
src/check_uptime.c [new file with mode: 0644]
src/collectd.conf.in
src/collectd.conf.pod
src/connectivity.c [new file with mode: 0644]
src/contextswitch.c
src/daemon/configfile.c
src/daemon/plugin.c
src/daemon/plugin.h
src/daemon/plugin_mock.c
src/daemon/utils_cache.c
src/daemon/utils_cache.h
src/daemon/utils_random.h
src/grpc.cc
src/intel_rdt.c
src/java.c
src/memcached.c
src/netlink.c
src/network_test.c [new file with mode: 0644]
src/powerdns.c
src/procevent.c [new file with mode: 0644]
src/sysevent.c [new file with mode: 0644]
src/turbostat.c
src/types.db
src/utils/common/common.c
src/utils/latency/latency.h
src/utils/proc_pids/proc_pids.h
src/utils_tail_match.h
src/zfs_arc.c
version-gen.sh

index 271a40a..f0e35a7 100644 (file)
@@ -12,17 +12,21 @@ env:
 matrix:
   include:
     - os: osx
-      osx_image: xcode10.1
+      osx_image: xcode11.2
       compiler: clang
+      jdk: openjdk10
       env:
         - CXX=clang++
         - PATH="/usr/local/opt/mysql-client/bin:$PATH"
+        - JAVA_HOME="/Library/Java/JavaVirtualMachines/openjdk-13.jdk/Contents/Home"
     - os: linux
       dist: bionic
       compiler: clang
+      jdk: openjdk10
     - os: linux
       dist: bionic
       compiler: gcc
+      jdk: openjdk10
 
 before_install:
   # When building the coverity_scan branch, allow only the first job to continue to avoid travis-ci/travis-ci#1975.
@@ -32,7 +36,10 @@ before_script: autoreconf -vif
 
 script:
   - if [[ "${TRAVIS_BRANCH}" == "coverity_scan" ]]; then exit 0; fi
+  - type pkg-config
+  - pkg-config --list-all | sort -u
   - ./configure --disable-lvm
+  - cat config.log
   - make distcheck DISTCHECK_CONFIGURE_FLAGS="--disable-dependency-tracking --enable-debug --disable-lvm"
 
 addons:
@@ -78,7 +85,6 @@ addons:
     - libsensors4-dev
     - libsigrok-dev
     - libsnmp-dev
-    - libstatgrab-dev
     - libtokyocabinet-dev
     - libtokyotyrant-dev
     - libudev-dev
@@ -106,6 +112,7 @@ addons:
     packages:
     - curl
     - glib
+    - grpc
     - hiredis
     - libdbi
     - libmemcached
@@ -115,9 +122,10 @@ addons:
     - liboping
     - libpcap
     - librdkafka
-    - libstatgrab
     - libvirt
+    - libxml2
     - lua
+    - mongo-c-driver
     - mosquitto
     - mysql-client
     - net-snmp
diff --git a/AUTHORS b/AUTHORS
index d05b86f..d8548d1 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -56,11 +56,23 @@ Amit Gupta <amit.gupta221 at gmail.com>
 Andreas Henriksson <andreas at fatal.se>
  - libmnl support in the netlink plugin.
 
+Andrew Bays <abays at redhat.com>
+ - connectivity plugin.
+ - procevent plugin.
+ - sysevent plugin.
+ - connectivity plugin.
+
 Andy Parkins <andyp at fussylogic.co.uk>
  - battery plugin: sysfs code.
 
+Aneesh Puttur <aputtur at redhat.com>
+ - connectivity plugin.
+
 Andy Smith <ansmith at redhat.com>
  - AMQP 1.0 plugin.
+Aneesh Puttur <aputtur at redhat.com>
+ - connectivity plugin.
 
 Anthony Dewhurst <dewhurst at gmail.com>
  - zfs_arc plugin.
index 55fbd04..5a2fe30 100644 (file)
@@ -4,7 +4,6 @@
 # These owners will be the default owners for everything in the repo. Unless a
 # later match takes precedence, # @trusted-contributors will be requested for
 # review when someone opens a pull request.
-*       @trusted-contributors
 
 /src/intel_pmu.c       @kwiatrox @sunkuranganath
 /src/intel_rdt.c       @kwiatrox @sunkuranganath
@@ -13,3 +12,7 @@
 /src/virt.c            @anaudx @rjablonx
 # TODO(#2926): Add the following owners:
 #/src/redfish.c                @kkepka @mkobyli
+
+# Order is important; the last matching pattern takes the most
+# precedence.
+*       @trusted-contributors
index e558a26..fda2816 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,53 @@
+2019-10-17, Version 5.10.0
+       * turbostat plugin: Power metrics update for recent server CPUs. Thanks
+         to Chris MacNamara. #3276
+       * ZFS ARC plugin: New cache values are now read on Linux. Thanks to
+         Jan-Philipp Litza. #3247, #2843
+       * connectivity plugin: monitor the network interface up/down status via
+         the netlink library. Thanks to Andrew Bays. #2622
+       * sysevent plugin: A new plugin that monitors rsyslog for system events.
+         Thanks to Andrew Bays. #2624
+       * procevent plugin: A new plugin that monitors process starts/stops via
+         netlink library. Thanks to Andrew Bays. #2623
+       * daemon: Check if plugin actually loaded before reporting configuration
+         issues. Thanks to Pavel Rochnyak. #3217
+       * daemon: Recover setlocale() call in src/daemon/collectd.c do_init().
+         Thanks to Pavel Rochnyak.#3181, #3214
+       * Build System: Only include <sys/sysctl.h> when needed. Thanks to Ruben
+         Kerkhof. #3298
+       * Build System: Link to libnsl.so if needed for inet_ntop(). Thanks to
+         Dagobert Michelsen. #3291
+       * Build System: Remove double "without" added by commit b781871. Thanks
+         to Fabrice Fontaine. #3261
+       * Build System: fix compile time issues. Thanks to Matthias Runge.
+         #3179, #3242, #3245
+       * Build System: Fix activation of snmp_agent. Thanks to Fabrice
+         Fontaine. #3241
+       * Build System: Fix bug that leads to CPPFLAGS gets overridden with
+         CFLAGS when libxmms is enabled. Thanks to Dagobert Michelsen. #3207
+       * perl module: Collectd::Plugins::Openvz: Fix indentation of some
+         closing curlies. Thanks to Christian Bartolomäus. #3239
+       * tree-wide: Fix a few issues found with LGTM. Thanks to Ruben Kerkhof.
+         #3252
+       * tree-wide: fix ssnprintf wrapper. Thanks to Fabien Wernli.
+         #3237,#3232,#3235,#3236
+       * tree-wide: Fix make check. Thanks to Ruben Kerkhof. #3306
+       * CI System: Travis: switch to Bionic. Thanks to Ruben Kerkhof. #3307
+       * CI System: Travis improvements for MacOS. Thanks to Ruben Kerkhof.
+         #3308
+       * MySQL plugin: Minor documentation improvements. Thanks to Christian
+         Bartolomäus. #3288
+       * Java plugin: Fix typo in an error message. Thanks to Matthias Runge.
+         #3285,#3286
+       * sysevent plugin: Add a few missing calloc result checks in the
+         sysevent_init function. Thanks to Andrew Bays. #3282
+       * ZFS ARC plugin: A bug that caused the first to values to be skipped
+         was fixed. Thanks to Jan-Philipp Litza. #3246
+       * SysLog plugin: restore previous behaviour: fallback to info for
+         unsupported level. Thanks to Fabien Wernli. #3236, #3238
+       * virt plugin: Fix memory leak with libvirt MetadataXPath enabled.
+         Thanks to Pavel Rochnyak. #3225,#3228
+
 2019-10-01, Version 5.9.2
        * syslog plugin: Don't fail if syslog loglevel doesn't match. Thanks to
          Fabien Wernli. #3236 #3238
index f00871b..9e6c8fa 100644 (file)
@@ -768,6 +768,21 @@ chrony_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 chrony_la_LIBADD = -lm
 endif
 
+if BUILD_PLUGIN_CHECK_UPTIME
+pkglib_LTLIBRARIES += check_uptime.la
+check_uptime_la_SOURCES = src/check_uptime.c
+check_uptime_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+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_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBYAJL_CPPFLAGS)
+connectivity_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBYAJL_LDFLAGS)
+connectivity_la_LIBADD = $(BUILD_WITH_LIBYAJL_LIBS) $(BUILD_WITH_LIBMNL_LIBS) libignorelist.la
+endif
+
 if BUILD_PLUGIN_CONNTRACK
 pkglib_LTLIBRARIES += conntrack.la
 conntrack_la_SOURCES = src/conntrack.c
@@ -1391,6 +1406,27 @@ network_la_CPPFLAGS += $(GCRYPT_CPPFLAGS)
 network_la_LDFLAGS += $(GCRYPT_LDFLAGS)
 network_la_LIBADD += $(GCRYPT_LIBS)
 endif
+
+test_plugin_network_SOURCES = \
+       src/network_test.c \
+       src/utils_fbhash.c \
+       src/daemon/configfile.c \
+       src/daemon/types_list.c
+test_plugin_network_CPPFLAGS = $(AM_CPPFLAGS) $(GCRYPT_CPPFLAGS)
+test_plugin_network_LDFLAGS = $(PLUGIN_LDFLAGS) $(GCRYPT_LDFLAGS)
+test_plugin_network_LDADD = \
+       libavltree.la \
+       liboconfig.la \
+       libplugin_mock.la \
+       libmetadata.la \
+       $(GCRYPT_LIBS)
+if BUILD_WITH_LIBSOCKET
+test_plugin_network_LDADD += -lsocket
+endif
+if BUILD_WITH_LIBNSL
+test_plugin_network_LDADD += -lnsl
+endif
+check_PROGRAMS += test_plugin_network
 endif
 
 if BUILD_PLUGIN_NFS
@@ -1631,6 +1667,14 @@ processes_la_LIBADD += libtaskstats.la
 endif
 endif
 
+if BUILD_PLUGIN_PROCEVENT
+pkglib_LTLIBRARIES += procevent.la
+procevent_la_SOURCES = src/procevent.c
+procevent_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBYAJL_CPPFLAGS)
+procevent_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBYAJL_LDFLAGS)
+procevent_la_LIBADD = $(BUILD_WITH_LIBYAJL_LIBS) libignorelist.la
+endif
+
 if BUILD_PLUGIN_PROTOCOLS
 pkglib_LTLIBRARIES += protocols.la
 protocols_la_SOURCES = src/protocols.c
@@ -1779,6 +1823,14 @@ synproxy_la_SOURCES = src/synproxy.c
 synproxy_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 endif
 
+if BUILD_PLUGIN_SYSEVENT
+pkglib_LTLIBRARIES += sysevent.la
+sysevent_la_SOURCES = src/sysevent.c
+sysevent_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBYAJL_CPPFLAGS)
+sysevent_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBYAJL_LDFLAGS)
+sysevent_la_LIBADD = $(BUILD_WITH_LIBYAJL_LIBS) libignorelist.la
+endif
+
 if BUILD_PLUGIN_SYSLOG
 pkglib_LTLIBRARIES += syslog.la
 syslog_la_SOURCES = src/syslog.c
diff --git a/README b/README
index f28d499..f77efd2 100644 (file)
--- 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.
 
@@ -347,6 +350,9 @@ Features
     - processes
       Process counts: Number of running, sleeping, zombie, ... processes.
 
+    - procevent
+      Listens for process starts and exits via netlink.
+
     - protocols
       Counts various aspects of network protocols such as IP, TCP, UDP, etc.
 
@@ -391,6 +397,9 @@ Features
       Acts as a StatsD server, reading values sent over the network from StatsD
       clients and calculating rates and other aggregates out of these values.
 
+    - sysevent
+      Listens to rsyslog events and submits matched values.
+
     - swap
       Pages swapped out onto hard disk or whatever is called `swap' by the OS..
 
index 4c7c3fe..66359da 100644 (file)
@@ -110,9 +110,9 @@ sub cpu_read {
             $v{'type_instance'} = $cpu_instances[$key];
             $v{'values'} = [ $counters[$key] ];
             plugin_dispatch_values(\%v);
+        }
     }
 }
-}
 
 sub df_read {
     my $veid = shift;
@@ -137,7 +137,7 @@ sub df_read {
         $v{'type_instance'} = $val;
         $v{'values'} = [ $parts[5] * ($parts[6] - $parts[7]), $parts[5] * $parts[7] ];
         plugin_dispatch_values(\%v);
-}
+    }
 }
 
 sub load_read {
@@ -180,7 +180,7 @@ sub processes_read {
         $v{'type_instance'} = $key;
         $v{'values'} = [ $ps_states->{$key} ];
         plugin_dispatch_values(\%v);
-}
+    }
 }
 
 sub users_read {
index 82016b3..e8eee8d 100644 (file)
@@ -743,27 +743,12 @@ test_cxx_flags() {
 #
 AC_CHECK_FUNCS_ONCE([ \
     asprintf \
-    closelog \
-    getaddrinfo \
-    getgrnam_r \
-    getnameinfo \
     getpwnam \
     getpwnam_r \
-    gettimeofday \
     if_indextoname \
-    openlog \
-    regcomp \
-    regerror \
-    regexec \
-    regfree \
-    select \
     setenv \
     setgroups \
-    setlocale \
-    strcasecmp \
-    strdup \
-    strncasecmp \
-    sysconf
+    setlocale
   ]
 )
 
@@ -873,6 +858,17 @@ AC_CHECK_FUNCS([socket],
 AM_CONDITIONAL([BUILD_WITH_LIBSOCKET], [test "x$socket_needs_socket" = "xyes"])
 AM_CONDITIONAL([BUILD_WITH_GNULIB], [test "x$socket_needs_gnulib" = "xyes"])
 
+AC_CHECK_FUNCS([inet_ntop],
+  [],
+  [
+    AC_CHECK_LIB([nsl], [inet_ntop],
+      [inet_ntop_needs_nsl="yes"],
+      [AC_MSG_ERROR([cannot find inet_ntop() in libnsl])]
+    )
+  ]
+)
+AM_CONDITIONAL([BUILD_WITH_LIBNSL], [test "x$inet_ntop_needs_nsl" = "xyes"])
+
 clock_gettime_needs_posix4="no"
 AC_CHECK_FUNCS([clock_gettime],
   [have_clock_gettime="yes"],
@@ -3965,7 +3961,13 @@ if test "x$with_libnetsnmpagent" = "xyes"; then
   )
 
   AC_CHECK_LIB([netsnmpagent], [init_agent],
-    [with_libnetsnmpagent="yes"],
+    [
+      # libnetsnmp can be built without mib loading support
+      AC_CHECK_LIB([netsnmp], [get_tree],
+        [with_libnetsnmpagent="yes"],
+        [with_libnetsnmpagent="no (libnetsnmp doesn't support mib loading)"]
+      )
+    ],
     [with_libnetsnmpagent="no (libnetsnmpagent not found)"],
     [$libnetsnmphelpers]
   )
@@ -6371,6 +6373,7 @@ plugin_battery="no"
 plugin_bind="no"
 plugin_ceph="no"
 plugin_cgroups="no"
+plugin_connectivity="no"
 plugin_conntrack="no"
 plugin_contextswitch="no"
 plugin_cpu="no"
@@ -6411,12 +6414,14 @@ plugin_pcie_errors="no"
 plugin_perl="no"
 plugin_pinba="no"
 plugin_processes="no"
+plugin_procevent="no"
 plugin_protocols="no"
 plugin_python="no"
 plugin_serial="no"
 plugin_smart="no"
 plugin_swap="no"
 plugin_synproxy="no"
+plugin_sysevent="no"
 plugin_tape="no"
 plugin_tcpconns="no"
 plugin_ted="no"
@@ -6486,6 +6491,11 @@ if test "x$ac_system" = "xLinux"; then
   if test "x$with_libyajl" = "xyes" && test "x$with_libyajl2" = "xyes"; then
     plugin_ovs_events="yes"
     plugin_ovs_stats="yes"
+    plugin_procevent="yes"
+
+    if test "x$with_libmnl" = "xyes"; then
+      plugin_connectivity="yes"
+    fi
   fi
 
   if test "x$have_pci_regs_h" = "xyes"; then
@@ -6599,6 +6609,7 @@ fi
 
 if test "x$with_libyajl" = "xyes"; then
   plugin_ceph="yes"
+  plugin_sysevent="yes"
 fi
 
 if test "x$have_processor_info" = "xyes"; then
@@ -6795,6 +6806,8 @@ AC_PLUGIN([bind],                [$plugin_bind],              [ISC Bind nameserv
 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([check_uptime],        [yes],                       [Notify about uptime reset])
+AC_PLUGIN([connectivity],        [$plugin_connectivity],      [Network interface up/down events])
 AC_PLUGIN([conntrack],           [$plugin_conntrack],         [nf_conntrack statistics])
 AC_PLUGIN([contextswitch],       [$plugin_contextswitch],     [context switch statistics])
 AC_PLUGIN([cpu],                 [$plugin_cpu],               [CPU usage statistics])
@@ -6883,6 +6896,7 @@ AC_PLUGIN([ping],                [$with_liboping],            [Network latency s
 AC_PLUGIN([postgresql],          [$with_libpq],               [PostgreSQL database statistics])
 AC_PLUGIN([powerdns],            [yes],                       [PowerDNS statistics])
 AC_PLUGIN([processes],           [$plugin_processes],         [Process statistics])
+AC_PLUGIN([procevent],           [$plugin_procevent],         [Process event (start, stop) statistics])
 AC_PLUGIN([protocols],           [$plugin_protocols],         [Protocol (IP, TCP, ...) statistics])
 AC_PLUGIN([python],              [$plugin_python],            [Embed a Python interpreter])
 AC_PLUGIN([redis],               [$with_libhiredis],          [Redis plugin])
@@ -6898,6 +6912,7 @@ AC_PLUGIN([snmp_agent],          [$with_libnetsnmpagent],     [SNMP agent plugin
 AC_PLUGIN([statsd],              [yes],                       [StatsD plugin])
 AC_PLUGIN([swap],                [$plugin_swap],              [Swap usage statistics])
 AC_PLUGIN([synproxy],            [$plugin_synproxy],          [Synproxy stats plugin])
+AC_PLUGIN([sysevent],            [$plugin_sysevent],          [rsyslog events])
 AC_PLUGIN([syslog],              [$have_syslog],              [Syslog logging plugin])
 AC_PLUGIN([table],               [yes],                       [Parsing of tabular data])
 AC_PLUGIN([tail],                [yes],                       [Parsing of logfiles])
@@ -7224,6 +7239,8 @@ 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([    check_uptime. . . . . $enable_check_uptime])
+AC_MSG_RESULT([    connectivity. . . . . $enable_connectivity])
 AC_MSG_RESULT([    conntrack . . . . . . $enable_conntrack])
 AC_MSG_RESULT([    contextswitch . . . . $enable_contextswitch])
 AC_MSG_RESULT([    cpu . . . . . . . . . $enable_cpu])
@@ -7311,6 +7328,7 @@ AC_MSG_RESULT([    ping  . . . . . . . . $enable_ping])
 AC_MSG_RESULT([    postgresql  . . . . . $enable_postgresql])
 AC_MSG_RESULT([    powerdns  . . . . . . $enable_powerdns])
 AC_MSG_RESULT([    processes . . . . . . $enable_processes])
+AC_MSG_RESULT([    procevent . . . . . . $enable_procevent])
 AC_MSG_RESULT([    protocols . . . . . . $enable_protocols])
 AC_MSG_RESULT([    python  . . . . . . . $enable_python])
 AC_MSG_RESULT([    redis . . . . . . . . $enable_redis])
@@ -7326,6 +7344,7 @@ AC_MSG_RESULT([    snmp_agent  . . . . . $enable_snmp_agent])
 AC_MSG_RESULT([    statsd  . . . . . . . $enable_statsd])
 AC_MSG_RESULT([    swap  . . . . . . . . $enable_swap])
 AC_MSG_RESULT([    synproxy  . . . . . . $enable_synproxy])
+AC_MSG_RESULT([    sysevent. . . . . . . $enable_sysevent])
 AC_MSG_RESULT([    syslog  . . . . . . . $enable_syslog])
 AC_MSG_RESULT([    table . . . . . . . . $enable_table])
 AC_MSG_RESULT([    tail_csv  . . . . . . $enable_tail_csv])
index 809f19d..cb328f2 100644 (file)
@@ -16,7 +16,7 @@
 Collectd network protocol implementation.
 """
 
-import socket,struct,sys
+import socket,struct
 import platform
 if platform.python_version() < '2.8.0':
     # Python 2.7 and below io.StringIO does not like unicode
index 3bfe56e..0cf1513 100644 (file)
@@ -183,12 +183,11 @@ function nav_calculate_offset_x (obj)
 function nav_calculate_event_x (e)
 {
   var pos = 0;
-  var off = 0;
 
   if (!e || !e.target)
     return;
   
-  off = nav_calculate_offset_x (e.target);
+  nav_calculate_offset_x (e.target);
 
   if (e.pageX || e.pageY)
   {
index 4ddc424..09673da 100644 (file)
@@ -106,8 +106,8 @@ function refillSelect(options, select) {
                newOption.setAttribute('style', 'font-style: italic');
                newOption.appendChild(document.createTextNode('- please select -'));
                select.appendChild(newOption);
-               for (i = 0; i < optCnt; i++) {
-                       newOption = document.createElement("option");
+               for (var i = 0; i < optCnt; i++) {
+                       var newOption = document.createElement("option");
                        newOption.value = options[i].firstChild ? options[i].firstChild.data : '';
                        if (keepSelection && newOption.value == oldValue)
                                newOption.setAttribute('selected', 'selected');
@@ -311,7 +311,7 @@ function GraphAppend() {
 function ListOfGraph(response) {
        var graphs = response ? response.getElementsByTagName('graph') : null;
        if (graphs && graphs.length > 0) {
-               for (i = 0; i < graphs.length; i++)
+               for (var i = 0; i < graphs.length; i++)
                        GraphDoAppend(graphs[i].getAttribute('host'), graphs[i].getAttribute('plugin'), graphs[i].getAttribute('plugin_instance'),
                                      graphs[i].getAttribute('type'), graphs[i].getAttribute('type_instance'), graphs[i].getAttribute('timespan'),
                                      graphs[i].getAttribute('tinyLegend') == '1', graphs[i].getAttribute('logarithmic') == '1');
@@ -326,7 +326,7 @@ function GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, l
                var graph_id   = 'graph_'+nextGraphId++;
                var graph_src  = graph_url+'?host='+encodeURIComponent(host)+'&plugin='+encodeURIComponent(plugin)+'&plugin_instance='+encodeURIComponent(pinst)+'&type='+encodeURIComponent(type);
                var graph_alt  = '';
-               var grap_title = '';
+               var graph_title = '';
                if (tinst == '@') {
                        graph_alt   = host+'/'+plugin+(pinst.length > 0 ? '-'+pinst : '')+'/'+type;
                        graph_title = type+' of '+plugin+(pinst.length > 0 ? '-'+pinst : '')+' plugin for '+host;
@@ -346,11 +346,11 @@ function GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, l
                graphList.push(graph_id+' '+encodeURIComponent(graph_alt)+(logarithmic ? '&logarithmic=1' : '')+(tinyLegend ? '&tinylegend=1' : '')+'&timespan='+encodeURIComponent(timespan));
 
                // Graph container
-               newGraph = document.createElement('div');
+               var newGraph = document.createElement('div');
                newGraph.setAttribute('class', 'graph');
                newGraph.setAttribute('id', graph_id);
                // Graph cell + graph
-               newImg = document.createElement('img');
+               var newImg = document.createElement('img');
                newImg.setAttribute('src', graph_src);
                newImg.setAttribute('alt', graph_alt);
                newImg.setAttribute('title', graph_title);
@@ -397,7 +397,7 @@ function GraphToggleTools(graph) {
                document.getElementById('ge_graphid').value = graph;
                document.getElementById('ge_tinylegend').checked = src_url.match(/&tinylegend=1/);
                document.getElementById('ge_logarithmic').checked = src_url.match(/&logarithmic=1/);
-               for (i = 0; i < ts_sel.options.length; i++)
+               for (var i = 0; i < ts_sel.options.length; i++)
                        if (ts_sel.options[i].value == ts) {
                                ts_sel.selectedIndex = i;
                                break;
@@ -513,7 +513,7 @@ function GraphAdjust(graph) {
                                imgs[imgCnt].setAttribute('src', newSrc);
 
                                var myList = Array();
-                               for (i = 0; i < graphList.length; i++)
+                               for (var i = 0; i < graphList.length; i++)
                                        if (graphList[i].substring(0, graphId.length) == graphId && graphList[i].charAt(graphId.length) == ' ') {
                                                newSrc = graphList[i];
                                                newSrc = newSrc.replace(/&logarithmic=[^&]*/, '');
@@ -544,7 +544,7 @@ function GraphRemove(graph) {
                        document.getElementById('nograph').style.display = 'block';
 
                var myList = Array();
-               for (i = 0; i < graphList.length; i++)
+               for (var i = 0; i < graphList.length; i++)
                        if (graphList[i].substring(0, graphId.length) == graphId && graphList[i].charAt(graphId.length) == ' ')
                                continue;
                        else
@@ -558,7 +558,7 @@ function GraphMoveUp(graph) {
        var graphId   = graph == null ? document.getElementById('ge_graphid').value : graph;
        var childCnt  = graphs.childNodes.length;
        var prevGraph = null;
-       for (i = 0; i < childCnt; i++)
+       for (var i = 0; i < childCnt; i++)
                if (graphs.childNodes[i].nodeName == 'div' || graphs.childNodes[i].nodeName == 'DIV') {
                        if (graphs.childNodes[i].id == 'nograph') {
                                // Skip
@@ -590,7 +590,7 @@ function GraphMoveDown(graph) {
        var childCnt  = graphs.childNodes.length;
        var nextGraph = null;
        var myGraph   = null;
-       for (i = 0; i < childCnt; i++)
+       for (var i = 0; i < childCnt; i++)
                if (graphs.childNodes[i].nodeName == 'div' || graphs.childNodes[i].nodeName == 'DIV') {
                        if (graphs.childNodes[i].id == 'nograph') {
                                // Skip
@@ -603,12 +603,12 @@ function GraphMoveDown(graph) {
                                break;
                        }
                }
-       for (i = 0; i < graphList.length; i++)
-               if (graphList[i].substring(0, graphId.length) == graphId && graphList[i].charAt(graphId.length) == ' ') {
-                       if (i+1 < graphList.length) {
-                               var tmp = graphList[i+1];
-                               graphList[i+1] = graphList[i];
-                               graphList[i]   = tmp;
+       for (var j = 0; j < graphList.length; j++)
+               if (graphList[j].substring(0, graphId.length) == graphId && graphList[j].charAt(graphId.length) == ' ') {
+                       if (j+1 < graphList.length) {
+                               var tmp = graphList[j+1];
+                               graphList[j+1] = graphList[i];
+                               graphList[j]   = tmp;
                        }
                        break;
                }
@@ -619,7 +619,7 @@ function GraphListFromCookie(lname) {
        if (document.cookie.length > 0) {
                var cname= 'graphLst'+lname+'=';
                var cookies = document.cookie.split('; ');
-               for (i = 0; i < cookies.length; i++)
+               for (var i = 0; i < cookies.length; i++)
                        if (cookies[i].substring(0, cname.length) == cname)
                                return cookies[i].substring(cname.length).split('/');
        }
@@ -663,7 +663,7 @@ function GraphListRefresh() {
        } else {
                select.removeAttribute('disabled');
                for (i = 0; i < optCnt; i++) {
-                       newOption = document.createElement("option");
+                       var newOption = document.createElement("option");
                        newOption.value = options[i][0];
                        if (newOption.value == oldValue)
                                newOption.setAttribute('selected', 'selected');
@@ -705,7 +705,7 @@ function GraphSave() {
        if (graphList.length > 0) {
                // Save graph list to cookie
                var str = '';
-               for (i = 0; i < graphList.length; i++) {
+               for (var i = 0; i < graphList.length; i++) {
                        var g = graphList[i].indexOf(' ');
                        if (i > 0)
                                str += '/';
@@ -743,7 +743,7 @@ function GraphLoad() {
        // Load graph list from cookie
        var grLst = GraphListFromCookie(cname);
        var oldLength = graphList.length;
-       for (i = 0; i < grLst.length; i++) {
+       for (var i = 0; i < grLst.length; i++) {
                var host        = '';
                var plugin      = '';
                var pinst       = '';
@@ -753,7 +753,7 @@ function GraphLoad() {
                var logarithmic = false;
                var tinyLegend  = false;
                var graph = grLst[i].split('&');
-               for (j = 0; j < graph.length; j++)
+               for (var j = 0; j < graph.length; j++)
                        if (graph[j] == 'logarithmic=1')
                                logarithmic = true;
                        else if (graph[j] == 'tinylegend=1')
index 246fcb5..885ba5c 100644 (file)
@@ -53,6 +53,7 @@
 %define with_ceph 0%{!?_without_ceph:1}
 %define with_cgroups 0%{!?_without_cgroups:1}
 %define with_chrony 0%{!?_without_chrony:1}
+%define with_connectivity 0%{!?_without_connectivity:1}
 %define with_conntrack 0%{!?_without_conntrack:1}
 %define with_contextswitch 0%{!?_without_contextswitch:1}
 %define with_cpu 0%{!?_without_cpu:1}
 %define with_postgresql 0%{!?_without_postgresql:1}
 %define with_powerdns 0%{!?_without_powerdns:1}
 %define with_processes 0%{!?_without_processes:1}
+%define with_procevent 0%{!?_without_procevent:1}
 %define with_protocols 0%{!?_without_protocols:1}
 %define with_python 0%{!?_without_python:1}
 %define with_redis 0%{!?_without_redis:1}
 %define with_statsd 0%{!?_without_statsd:1}
 %define with_swap 0%{!?_without_swap:1}
 %define with_synproxy 0%{!?_without_synproxy:0}
+%define with_sysevent 0%{!?_without_sysevent:1}
 %define with_syslog 0%{!?_without_syslog:1}
 %define with_table 0%{!?_without_table:1}
 %define with_tail 0%{!?_without_tail:1}
 
 # Plugins not buildable on RHEL < 7
 %if 0%{?rhel} && 0%{?rhel} < 7
+%define with_connectivity 0
 %define with_cpusleep 0
 %define with_gps 0
 %define with_mqtt 0
 %define with_ovs_events 0
 %define with_ovs_stats 0
+%define with_procevent 0
 %define with_redis 0
 %define with_rrdcached 0
+%define with_sysevent 0
 %define with_write_redis 0
 %define with_write_riemann 0
 %define with_xmms 0
 
 Summary:       Statistics collection and monitoring daemon
 Name:          collectd
-Version:       5.9.0
+Version:       5.10.0
 Release:       1%{?dist}
 URL:           https://collectd.org
 Source:                https://collectd.org/files/%{name}-%{version}.tar.bz2
@@ -380,6 +386,16 @@ Requires:      %{name}%{?_isa} = %{version}-%{release}
 Chrony plugin for collectd
 %endif
 
+%if %{with_connectivity}
+%package connectivity
+Summary:       Connectivity plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: libmnl-devel, yajl-devel
+%description connectivity
+Monitors network interface up/down status via netlink library.
+%endif
+
 %if %{with_curl}
 %package curl
 Summary:       Curl plugin for collectd
@@ -792,6 +808,16 @@ The PostgreSQL plugin connects to and executes SQL statements on a PostgreSQL
 database.
 %endif
 
+%if %{with_procevent}
+%package procevent
+Summary:       Processes event plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: yajl-devel
+%description procevent
+Monitors process starts/stops via netlink library.
+%endif
+
 %if %{with_python}
 %package python
 Summary:       Python plugin for collectd
@@ -891,6 +917,16 @@ BuildRequires:     net-snmp-devel
 This plugin for collectd to support AgentX integration.
 %endif
 
+%if %{with_sysevent}
+%package sysevent
+Summary:       Rsyslog event plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: yajl-devel
+%description sysevent
+Monitors rsyslog for system events.
+%endif
+
 %if %{with_varnish}
 %package varnish
 Summary:       Varnish plugin for collectd
@@ -974,6 +1010,16 @@ The write_stackdriver collectd plugin writes metrics to the
 Google Stackdriver Monitoring service.
 %endif
 
+%if %{with_write_syslog}
+%package write_syslog
+Summary:       write_syslog plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+%description write_syslog
+The write_syslog collectd plugin writes metrics to syslog
+using JSON or RFC5424 formatting
+%endif
+
 %if %{with_gpu_nvidia}
 %package gpu_nvidia
 Summary:       stackdriver plugin for collectd
@@ -1136,6 +1182,12 @@ Collectd utilities
 %define _with_chrony --disable-chrony
 %endif
 
+%if %{with_connectivity}
+%define _with_connectivity --enable-connectivity
+%else
+%define _with_connectivity --disable-connectivity
+%endif
+
 %if %{with_conntrack}
 %define _with_conntrack --enable-conntrack
 %else
@@ -1634,6 +1686,12 @@ Collectd utilities
 %define _with_processes --disable-processes
 %endif
 
+%if %{with_procevent}
+%define _with_procevent --enable-procevent
+%else
+%define _with_procevent --disable-procevent
+%endif
+
 %if %{with_protocols}
 %define _with_protocols --enable-protocols
 %else
@@ -1729,6 +1787,12 @@ Collectd utilities
 %define _with_synproxy --disable-synproxy
 %endif
 
+%if %{with_sysevent}
+%define _with_sysevent --enable-sysevent
+%else
+%define _with_sysevent --disable-sysevent
+%endif
+
 %if %{with_syslog}
 %define _with_syslog --enable-syslog
 %else
@@ -1992,6 +2056,7 @@ Collectd utilities
        %{?_with_ceph} \
        %{?_with_cgroups} \
        %{?_with_chrony} \
+       %{?_with_connectivity} \
        %{?_with_conntrack} \
        %{?_with_contextswitch} \
        %{?_with_cpufreq} \
@@ -2073,6 +2138,7 @@ Collectd utilities
        %{?_with_postgresql} \
        %{?_with_powerdns} \
        %{?_with_processes} \
+       %{?_with_procevent} \
        %{?_with_protocols} \
        %{?_with_python} \
        %{?_with_redis} \
@@ -2088,6 +2154,7 @@ Collectd utilities
        %{?_with_statsd} \
        %{?_with_swap} \
        %{?_with_synproxy} \
+       %{?_with_sysevent} \
        %{?_with_syslog} \
        %{?_with_table} \
        %{?_with_tail_csv} \
@@ -2539,6 +2606,11 @@ fi
 %{_libdir}/%{name}/chrony.so
 %endif
 
+%if %{with_connectivity}
+%files connectivity
+%{_libdir}/%{name}/connectivity.so
+%endif
+
 %if %{with_curl}
 %files curl
 %{_libdir}/%{name}/curl.so
@@ -2748,6 +2820,11 @@ fi
 %{_libdir}/%{name}/postgresql.so
 %endif
 
+%if %{with_procevent}
+%files procevent
+%{_libdir}/%{name}/procevent.so
+%endif
+
 %if %{with_python}
 %files python
 %{_mandir}/man5/collectd-python*
@@ -2795,6 +2872,11 @@ fi
 %{_libdir}/%{name}/snmp_agent.so
 %endif
 
+%if %{with_sysevent}
+%files sysevent
+%{_libdir}/%{name}/sysevent.so
+%endif
+
 %if %{with_varnish}
 %files varnish
 %{_libdir}/%{name}/varnish.so
@@ -2830,6 +2912,11 @@ fi
 %{_libdir}/%{name}/write_stackdriver.so
 %endif
 
+%if %{with_write_syslog}
+%files write_syslog
+%{_libdir}/%{name}/write_syslog.so
+%endif
+
 %if %{with_gpu_nvidia}
 %files write_gpu_nvidia
 %{_libdir}/%{name}/write_gpu_nvidia.so
@@ -2857,6 +2944,9 @@ fi
 %doc contrib/
 
 %changelog
+* Fri Oct 18 2019 Matthias Runge <mrunge@redhat.com> - 5.10.0-1
+- update to 5.10.0
+
 * Fri Jun 14 2019 Fabien Wernli <rpmbuild@faxmodem.org> - 5.9.0-1
 - add code for write_stackdriver (disabled for now)
 - add code for gpu_nvidia (disabled for now)
diff --git a/src/check_uptime.c b/src/check_uptime.c
new file mode 100644 (file)
index 0000000..33363b5
--- /dev/null
@@ -0,0 +1,273 @@
+/**
+ * collectd - src/check_uptime.c
+ * Copyright (C) 2007-2019  Florian Forster
+ * Copyright (C) 2019  Pavel V. Rochnyack
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Pavel Rochnyak <pavel2000 ngs.ru>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "utils/avltree/avltree.h"
+#include "utils/common/common.h"
+#include "utils_cache.h"
+
+/* Types are registered only in `config` phase, so access is not protected by
+ * locks */
+c_avl_tree_t *types_tree = NULL;
+
+static int format_uptime(unsigned long uptime_sec, char *buf, size_t bufsize) {
+
+  unsigned int uptime_days = uptime_sec / 24 / 3600;
+  uptime_sec -= uptime_days * 24 * 3600;
+  unsigned int uptime_hours = uptime_sec / 3600;
+  uptime_sec -= uptime_hours * 3600;
+  unsigned int uptime_mins = uptime_sec / 60;
+  uptime_sec -= uptime_mins * 60;
+
+  int ret = 0;
+  if (uptime_days) {
+    ret += snprintf(buf + ret, bufsize - ret, " %u day(s)", uptime_days);
+  }
+  if (uptime_days || uptime_hours) {
+    ret += snprintf(buf + ret, bufsize - ret, " %u hour(s)", uptime_hours);
+  }
+  if (uptime_days || uptime_hours || uptime_mins) {
+    ret += snprintf(buf + ret, bufsize - ret, " %u min", uptime_mins);
+  }
+  ret += snprintf(buf + ret, bufsize - ret, " %lu sec.", uptime_sec);
+  return ret;
+}
+
+static int cu_notify(enum cache_event_type_e event_type, const value_list_t *vl,
+                     gauge_t old_uptime, gauge_t new_uptime) {
+  notification_t n;
+  NOTIFICATION_INIT_VL(&n, vl);
+
+  int status;
+  char *buf = n.message;
+  size_t bufsize = sizeof(n.message);
+
+  n.time = vl->time;
+
+  const char *service = "Service";
+  if (strcmp(vl->plugin, "uptime") == 0)
+    service = "Host";
+
+  switch (event_type) {
+  case CE_VALUE_NEW:
+    n.severity = NOTIF_OKAY;
+    status = snprintf(buf, bufsize, "%s is running.", service);
+    buf += status;
+    bufsize -= status;
+    break;
+  case CE_VALUE_UPDATE:
+    n.severity = NOTIF_WARNING;
+    status = snprintf(buf, bufsize, "%s just restarted.", service);
+    buf += status;
+    bufsize -= status;
+    break;
+  case CE_VALUE_EXPIRED:
+    n.severity = NOTIF_FAILURE;
+    status = snprintf(buf, bufsize, "%s is unreachable.", service);
+    buf += status;
+    bufsize -= status;
+    break;
+  }
+
+  if (!isnan(old_uptime)) {
+    status = snprintf(buf, bufsize, " Uptime was:");
+    buf += status;
+    bufsize -= status;
+
+    status = format_uptime(old_uptime, buf, bufsize);
+    buf += status;
+    bufsize -= status;
+
+    plugin_notification_meta_add_double(&n, "LastValue", old_uptime);
+  }
+
+  if (!isnan(new_uptime)) {
+    status = snprintf(buf, bufsize, " Uptime now:");
+    buf += status;
+    bufsize -= status;
+
+    status = format_uptime(new_uptime, buf, bufsize);
+    buf += status;
+    bufsize -= status;
+
+    plugin_notification_meta_add_double(&n, "CurrentValue", new_uptime);
+  }
+
+  plugin_dispatch_notification(&n);
+
+  plugin_notification_meta_free(n.meta);
+  return 0;
+}
+
+static int cu_cache_event(cache_event_t *event,
+                          __attribute__((unused)) user_data_t *ud) {
+  gauge_t values_history[2];
+
+  /* For CE_VALUE_EXPIRED */
+  int ret;
+  value_t *values;
+  size_t values_num;
+  gauge_t old_uptime = NAN;
+
+  switch (event->type) {
+  case CE_VALUE_NEW:
+    DEBUG("check_uptime: CE_VALUE_NEW, %s", event->value_list_name);
+    if (c_avl_get(types_tree, event->value_list->type, NULL) == 0) {
+      event->ret = 1;
+      assert(event->value_list->values_len > 0);
+      cu_notify(CE_VALUE_NEW, event->value_list, NAN /* old */,
+                event->value_list->values[0].gauge /* new */);
+    }
+    break;
+  case CE_VALUE_UPDATE:
+    DEBUG("check_uptime: CE_VALUE_UPDATE, %s", event->value_list_name);
+    if (uc_get_history_by_name(event->value_list_name, values_history, 2, 1)) {
+      ERROR("check_uptime plugin: Failed to get value history for %s.",
+            event->value_list_name);
+    } else {
+      if (!isnan(values_history[0]) && !isnan(values_history[1]) &&
+          values_history[0] < values_history[1]) {
+        cu_notify(CE_VALUE_UPDATE, event->value_list,
+                  values_history[1] /* old */, values_history[0] /* new */);
+      }
+    }
+    break;
+  case CE_VALUE_EXPIRED:
+    DEBUG("check_uptime: CE_VALUE_EXPIRED, %s", event->value_list_name);
+    ret = uc_get_value_by_name(event->value_list_name, &values, &values_num);
+    if (ret == 0) {
+      old_uptime = values[0].gauge;
+      sfree(values);
+    }
+
+    cu_notify(CE_VALUE_EXPIRED, event->value_list, old_uptime, NAN /* new */);
+    break;
+  }
+  return 0;
+}
+
+static int cu_config(oconfig_item_t *ci) {
+  if (types_tree == NULL) {
+    types_tree = c_avl_create((int (*)(const void *, const void *))strcmp);
+    if (types_tree == NULL) {
+      ERROR("check_uptime plugin: c_avl_create failed.");
+      return -1;
+    }
+  }
+
+  for (int i = 0; i < ci->children_num; ++i) {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp("Type", child->key) == 0) {
+      if ((child->values_num != 1) ||
+          (child->values[0].type != OCONFIG_TYPE_STRING)) {
+        WARNING("check_uptime plugin: The `Type' option needs exactly one "
+                "string argument.");
+        return -1;
+      }
+      char *type = child->values[0].value.string;
+
+      if (c_avl_get(types_tree, type, NULL) == 0) {
+        ERROR("check_uptime plugin: Type `%s' already added.", type);
+        return -1;
+      }
+
+      char *type_copy = strdup(type);
+      if (type_copy == NULL) {
+        ERROR("check_uptime plugin: strdup failed.");
+        return -1;
+      }
+
+      int status = c_avl_insert(types_tree, type_copy, NULL);
+      if (status != 0) {
+        ERROR("check_uptime plugin: c_avl_insert failed.");
+        sfree(type_copy);
+        return -1;
+      }
+    } else
+      WARNING("check_uptime plugin: Ignore unknown config option `%s'.",
+              child->key);
+  }
+
+  return 0;
+}
+
+static int cu_init(void) {
+  if (types_tree == NULL) {
+    types_tree = c_avl_create((int (*)(const void *, const void *))strcmp);
+    if (types_tree == NULL) {
+      ERROR("check_uptime plugin: c_avl_create failed.");
+      return -1;
+    }
+    /* Default configuration */
+    char *type = strdup("uptime");
+    if (type == NULL) {
+      ERROR("check_uptime plugin: strdup failed.");
+      return -1;
+    }
+    int status = c_avl_insert(types_tree, type, NULL);
+    if (status != 0) {
+      ERROR("check_uptime plugin: c_avl_insert failed.");
+      sfree(type);
+      return -1;
+    }
+  }
+
+  int ret = 0;
+  char *type;
+  void *val;
+  c_avl_iterator_t *iter = c_avl_get_iterator(types_tree);
+  while (c_avl_iterator_next(iter, (void *)&type, (void *)&val) == 0) {
+    data_set_t const *ds = plugin_get_ds(type);
+    if (ds == NULL) {
+      ERROR("check_uptime plugin: Failed to look up type \"%s\".", type);
+      ret = -1;
+      continue;
+    }
+    if (ds->ds_num != 1) {
+      ERROR("check_uptime plugin: The type \"%s\" has %" PRIsz " data sources. "
+            "Only types with a single GAUGE data source are supported.",
+            ds->type, ds->ds_num);
+      ret = -1;
+      continue;
+    }
+    if (ds->ds[0].type != DS_TYPE_GAUGE) {
+      ERROR("check_uptime plugin: The type \"%s\" has wrong data source type. "
+            "Only types with a single GAUGE data source are supported.",
+            ds->type);
+      ret = -1;
+      continue;
+    }
+  }
+  c_avl_iterator_destroy(iter);
+
+  if (ret == 0)
+    plugin_register_cache_event("check_uptime", cu_cache_event, NULL);
+
+  return ret;
+}
+
+void module_register(void) {
+  plugin_register_complex_config("check_uptime", cu_config);
+  plugin_register_init("check_uptime", cu_init);
+}
index f09f373..a194b47 100644 (file)
 #@BUILD_PLUGIN_CEPH_TRUE@LoadPlugin ceph
 #@BUILD_PLUGIN_CGROUPS_TRUE@LoadPlugin cgroups
 #@BUILD_PLUGIN_CHRONY_TRUE@LoadPlugin chrony
+#@BUILD_PLUGIN_CHECK_UPTIME_TRUE@LoadPlugin check_uptime
+#@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
 #@BUILD_PLUGIN_POSTGRESQL_TRUE@LoadPlugin postgresql
 #@BUILD_PLUGIN_POWERDNS_TRUE@LoadPlugin powerdns
 #@BUILD_PLUGIN_PROCESSES_TRUE@LoadPlugin processes
+#@BUILD_PLUGIN_PROCEVENT_TRUE@LoadPlugin procevent
 #@BUILD_PLUGIN_PROTOCOLS_TRUE@LoadPlugin protocols
 #@BUILD_PLUGIN_PYTHON_TRUE@LoadPlugin python
 #@BUILD_PLUGIN_REDIS_TRUE@LoadPlugin redis
 #@BUILD_PLUGIN_SNMP_AGENT_TRUE@LoadPlugin snmp_agent
 #@BUILD_PLUGIN_STATSD_TRUE@LoadPlugin statsd
 #@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
+#@BUILD_PLUGIN_SYSEVENT_TRUE@LoadPlugin sysevent
 #@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table
 #@BUILD_PLUGIN_TAIL_TRUE@LoadPlugin tail
 #@BUILD_PLUGIN_TAIL_CSV_TRUE@LoadPlugin tail_csv
 #      Timeout "2"
 #</Plugin>
 
+#<Plugin connectivity>
+#  Interface eth0
+#</Plugin>
+
 #<Plugin cgroups>
 #  CGroup "libvirt"
 #  IgnoreSelected false
 #      </Process>
 #</Plugin>
 
+#<Plugin "procevent">
+#  BufferLength 10
+#  ProcessRegex "/^ovs.*$/" 
+#  Process tuned
+#</Plugin>
+
 #<Plugin protocols>
 #      Value "/^Tcp:/"
 #      IgnoreSelected false
 #      ReportIO true
 #</Plugin>
 
+#<Plugin sysevent>
+#       Listen "127.0.0.1" "6666"
+#       BufferSize 1024
+#       BufferLength 10
+#       RegexFilter "regex"
+#</Plugin>
+
 #<Plugin table>
 #      <Table "/proc/slabinfo">
 #              #Plugin "table"
index ed49195..cda1002 100644 (file)
@@ -1548,6 +1548,35 @@ at all, B<all> cgroups are selected.
 
 =back
 
+=head2 Plugin C<check_uptime>
+
+The I<check_uptime plugin> designed to check and notify about host or service
+status based on I<uptime> metric.
+
+When new metric of I<uptime> type appears in cache, OK notification is sent.
+When new value for metric is less than previous value, WARNING notification is
+sent about host/service restart.
+When no new updates comes for metric and cache entry expires, then FAILURE
+notification is sent about unreachable host or service.
+
+By default (when no explicit configuration), plugin checks for I<uptime> metric.
+
+B<Synopsis:>
+
+ <Plugin "check_uptime">
+   Type "uptime"
+   Type "my_uptime_type"
+ </Plugin>
+
+=over 4
+
+=item B<Type> I<Type>
+
+Metric type to check for status/values. The type should consist single GAUGE
+data source.
+
+=back
+
 =head2 Plugin C<chrony>
 
 The C<chrony> plugin collects ntp data from a B<chronyd> server, such as clock
@@ -1574,6 +1603,47 @@ Connection timeout in seconds. Defaults to B<2>.
 
 =back
 
+=head2 Plugin Connectivity
+
+connectivity - Documentation of collectd's C<connectivity plugin>
+
+
+  LoadPlugin connectivity
+  # ...
+  <Plugin connectivity>
+    Interface eth0
+  </Plugin>
+
+The C<connectivity plugin> 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 enumerated within the plugin configuration (see below).  If no interfaces are listed, then the default is for all interfaces to be monitored.
+
+This example shows C<connectivity plugin> monitoring all interfaces.
+LoadPlugin connectivity
+<Plugin connectivity>
+</Plugin>
+
+This example shows C<connectivity plugin> monitoring 2 interfaces, "eth0" and "eth1".
+LoadPlugin connectivity
+<Plugin connectivity>
+  Interface eth0
+  Interface eth1
+</Plugin>
+
+This example shows C<connectivity plugin> monitoring all interfaces except "eth1".
+LoadPlugin connectivity
+<Plugin connectivity>
+  Interface eth1
+  IgnoreSelected true
+</Plugin>
+=over 4
+
+=item B<Interface> I<interface_name>
+
+interface(s) to monitor connect to. 
+
+=back
+
 =head2 Plugin C<conntrack>
 
 This plugin collects IP conntrack statistics.
@@ -4758,7 +4828,7 @@ Hostname of the database server. Defaults to B<localhost>.
 
 Username to use when connecting to the database. The user does not have to be
 granted any privileges (which is synonym to granting the C<USAGE> privilege),
-unless you want to collectd replication statistics (see B<MasterStats> and
+unless you want to collect replication statistics (see B<MasterStats> and
 B<SlaveStats> below). In this case, the user needs the C<REPLICATION CLIENT>
 (or C<SUPER>) privileges. Else, any existing MySQL user will do.
 
@@ -4808,9 +4878,10 @@ or SQL threads are not running. Defaults to B<false>.
 
 =item B<WsrepStats> I<true|false>
 
- Enable the collection of wsrep plugin statistics, used in Master-Master
- replication setups like in MySQL Galera/Percona XtraDB Cluster.
- User needs only privileges to execute 'SHOW GLOBAL STATUS'
+Enable the collection of wsrep plugin statistics, used in Master-Master
+replication setups like in MySQL Galera/Percona XtraDB Cluster.
+User needs only privileges to execute 'SHOW GLOBAL STATUS'.
+Defaults to B<false>.
 
 =item B<ConnectTimeout> I<Seconds>
 
@@ -7314,6 +7385,40 @@ reporting the corresponding processes only. Outside of B<Process> and
 B<ProcessMatch> blocks these options set the default value for subsequent
 matches.
 
+=head2 Plugin C<procevent>
+The I<procevent> plugin monitors when processes start (EXEC) and stop (EXIT).
+B<Synopsis:>
+  <Plugin procevent>
+    BufferLength 10
+    Process "name"
+    ProcessRegex "regex"
+  </Plugin>
+B<Options:>
+=over 4
+=item B<BufferLength> I<length>
+Maximum number of process events that can be stored in plugin's ring buffer.
+By default, this is set to 10.  Once an event has been read, its location
+becomes available for storing a new event.
+=item B<Process> I<name>
+Enumerate a process name to monitor.  All processes that match this exact
+name will be monitored for EXECs and EXITs.
+
+=item B<ProcessRegex> I<regex>
+Enumerate a process pattern to monitor.  All processes that match this 
+regular expression will be monitored for EXECs and EXITs.
+=back
+
 =head2 Plugin C<protocols>
 
 Collects a lot of information about various network protocols, such as I<IP>,
@@ -8247,6 +8352,70 @@ or is not reliable.
 
 =back
 
+=head2 Plugin C<sysevent>
+The I<sysevent> plugin monitors rsyslog messages.
+B<Synopsis:>
+  <Plugin sysevent>
+    Listen "192.168.0.2" "6666"
+    BufferSize 1024
+    BufferLength 10
+    RegexFilter "regex"
+  </Plugin>
+
+  rsyslog should be configured such that it sends data to the IP and port you
+  include in the plugin configuration.  For example, given the configuration
+  above, something like this would be set in /etc/rsyslog.conf:
+
+    if $programname != 'collectd' then
+    *.* @192.168.0.2:6666
+
+  This plugin is designed to consume JSON rsyslog data, so a more complete
+  rsyslog configuration would look like so (where we define a JSON template
+  and use it when sending data to our IP and port):
+
+    $template ls_json,"{%timestamp:::date-rfc3339,jsonf:@timestamp%, \
+    %source:::jsonf:@source_host%,\"@source\":\"syslog://%fromhost-ip:::json%\", \
+    \"@message\":\"%timestamp% %app-name%:%msg:::json%\",\"@fields\": \
+    {%syslogfacility-text:::jsonf:facility%,%syslogseverity:::jsonf:severity-num%, \
+    %syslogseverity-text:::jsonf:severity%,%programname:::jsonf:program%, \
+    %procid:::jsonf:processid%}}"
+
+    if $programname != 'collectd' then
+    *.* @192.168.0.2:6666;ls_json
+
+  Please note that these rsyslog.conf examples are *not* complete, as rsyslog
+  requires more than these options in the configuration file.  These examples 
+  are meant to demonstration the proper remote logging and JSON format syntax.
+
+B<Options:>
+=over 4
+=item B<Listen> I<host> I<port>
+Listen on this IP on this port for incoming rsyslog messages.
+
+=item B<BufferSize> I<length>
+Maximum allowed size for incoming rsyslog messages.  Messages that exceed 
+this number will be truncated to this size.  Default is 4096 bytes.
+
+=item B<BufferLength> I<length>
+Maximum number of rsyslog events that can be stored in plugin's ring buffer.
+By default, this is set to 10.  Once an event has been read, its location
+becomes available for storing a new event.
+
+=item B<RegexFilter> I<regex>
+Enumerate a regex filter to apply to all incoming rsyslog messages.  If a
+message matches this filter, it will be published.
+=back
+
 =head2 Plugin C<syslog>
 
 =over 4
diff --git a/src/connectivity.c b/src/connectivity.c
new file mode 100644 (file)
index 0000000..45b65aa
--- /dev/null
@@ -0,0 +1,1032 @@
+/**
+ * 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 <abays at redhat.com>
+ *     Aneesh Puttur <aputtur at redhat.com>
+ **/
+
+#include "collectd.h"
+
+#include "plugin.h"
+#include "utils/common/common.h"
+#include "utils/ignorelist/ignorelist.h"
+#include "utils_complain.h"
+
+#include <asm/types.h>
+#include <errno.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_gen.h>
+#if HAVE_YAJL_YAJL_VERSION_H
+#include <yajl/yajl_version.h>
+#endif
+#if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
+#define HAVE_YAJL_V2 1
+#endif
+
+#define MYPROTO NETLINK_ROUTE
+
+#define LINK_STATE_DOWN 0
+#define LINK_STATE_UP 1
+#define LINK_STATE_UNKNOWN 2
+
+#define CONNECTIVITY_DOMAIN_FIELD "domain"
+#define CONNECTIVITY_DOMAIN_VALUE "stateChange"
+#define CONNECTIVITY_EVENT_ID_FIELD "eventId"
+#define CONNECTIVITY_EVENT_NAME_FIELD "eventName"
+#define CONNECTIVITY_EVENT_NAME_DOWN_VALUE "down"
+#define CONNECTIVITY_EVENT_NAME_UP_VALUE "up"
+#define CONNECTIVITY_LAST_EPOCH_MICROSEC_FIELD "lastEpochMicrosec"
+#define CONNECTIVITY_PRIORITY_FIELD "priority"
+#define CONNECTIVITY_PRIORITY_VALUE "high"
+#define CONNECTIVITY_REPORTING_ENTITY_NAME_FIELD "reportingEntityName"
+#define CONNECTIVITY_REPORTING_ENTITY_NAME_VALUE "collectd connectivity plugin"
+#define CONNECTIVITY_SEQUENCE_FIELD "sequence"
+#define CONNECTIVITY_SEQUENCE_VALUE "0"
+#define CONNECTIVITY_SOURCE_NAME_FIELD "sourceName"
+#define CONNECTIVITY_START_EPOCH_MICROSEC_FIELD "startEpochMicrosec"
+#define CONNECTIVITY_VERSION_FIELD "version"
+#define CONNECTIVITY_VERSION_VALUE "1.0"
+
+#define CONNECTIVITY_NEW_STATE_FIELD "newState"
+#define CONNECTIVITY_NEW_STATE_FIELD_DOWN_VALUE "outOfService"
+#define CONNECTIVITY_NEW_STATE_FIELD_UP_VALUE "inService"
+#define CONNECTIVITY_OLD_STATE_FIELD "oldState"
+#define CONNECTIVITY_OLD_STATE_FIELD_DOWN_VALUE "outOfService"
+#define CONNECTIVITY_OLD_STATE_FIELD_UP_VALUE "inService"
+#define CONNECTIVITY_STATE_CHANGE_FIELDS_FIELD "stateChangeFields"
+#define CONNECTIVITY_STATE_CHANGE_FIELDS_VERSION_FIELD                         \
+  "stateChangeFieldsVersion"
+#define CONNECTIVITY_STATE_CHANGE_FIELDS_VERSION_VALUE "1.0"
+#define CONNECTIVITY_STATE_INTERFACE_FIELD "stateInterface"
+
+/*
+ * Private data types
+ */
+
+struct interface_list_s {
+  char *interface;
+
+  uint32_t status;
+  uint32_t prev_status;
+  uint32_t sent;
+  cdtime_t timestamp;
+
+  struct interface_list_s *next;
+};
+typedef struct interface_list_s interface_list_t;
+
+/*
+ * Private variables
+ */
+
+static ignorelist_t *ignorelist = NULL;
+
+static interface_list_t *interface_list_head = NULL;
+static int monitor_all_interfaces = 1;
+
+static int connectivity_netlink_thread_loop = 0;
+static int connectivity_netlink_thread_error = 0;
+static pthread_t connectivity_netlink_thread_id;
+static int connectivity_dequeue_thread_loop = 0;
+static pthread_t connectivity_dequeue_thread_id;
+static pthread_mutex_t connectivity_threads_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t connectivity_data_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t connectivity_cond = PTHREAD_COND_INITIALIZER;
+static int nl_sock = -1;
+static int event_id = 0;
+static int statuses_to_send = 0;
+
+static const char *config_keys[] = {"Interface", "IgnoreSelected"};
+static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
+
+/*
+ * Private functions
+ */
+
+static int gen_message_payload(int state, int old_state, const char *interface,
+                               cdtime_t timestamp, char **buf) {
+  const unsigned char *buf2;
+  yajl_gen g;
+  char json_str[DATA_MAX_NAME_LEN];
+
+#if !defined(HAVE_YAJL_V2)
+  yajl_gen_config conf = {0};
+#endif
+
+#if HAVE_YAJL_V2
+  size_t len;
+  g = yajl_gen_alloc(NULL);
+  yajl_gen_config(g, yajl_gen_beautify, 0);
+#else
+  unsigned int len;
+  g = yajl_gen_alloc(&conf, NULL);
+#endif
+
+  yajl_gen_clear(g);
+
+  // *** BEGIN common event header ***
+
+  if (yajl_gen_map_open(g) != yajl_gen_status_ok)
+    goto err;
+
+  // domain
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_DOMAIN_FIELD,
+                      strlen(CONNECTIVITY_DOMAIN_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_DOMAIN_VALUE,
+                      strlen(CONNECTIVITY_DOMAIN_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // eventId
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_EVENT_ID_FIELD,
+                      strlen(CONNECTIVITY_EVENT_ID_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  event_id = event_id + 1;
+  if (snprintf(json_str, sizeof(json_str), "%d", event_id) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // eventName
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_EVENT_NAME_FIELD,
+                      strlen(CONNECTIVITY_EVENT_NAME_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (snprintf(json_str, sizeof(json_str), "interface %s %s", interface,
+               (state == 0 ? CONNECTIVITY_EVENT_NAME_DOWN_VALUE
+                           : CONNECTIVITY_EVENT_NAME_UP_VALUE)) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
+      yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // lastEpochMicrosec
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_LAST_EPOCH_MICROSEC_FIELD,
+                      strlen(CONNECTIVITY_LAST_EPOCH_MICROSEC_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (snprintf(json_str, sizeof(json_str), "%" PRIu64,
+               CDTIME_T_TO_US(cdtime())) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // priority
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_PRIORITY_FIELD,
+                      strlen(CONNECTIVITY_PRIORITY_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_PRIORITY_VALUE,
+                      strlen(CONNECTIVITY_PRIORITY_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // reportingEntityName
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_REPORTING_ENTITY_NAME_FIELD,
+                      strlen(CONNECTIVITY_REPORTING_ENTITY_NAME_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_REPORTING_ENTITY_NAME_VALUE,
+                      strlen(CONNECTIVITY_REPORTING_ENTITY_NAME_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // sequence
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_SEQUENCE_FIELD,
+                      strlen(CONNECTIVITY_SEQUENCE_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, CONNECTIVITY_SEQUENCE_VALUE,
+                      strlen(CONNECTIVITY_SEQUENCE_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // sourceName
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_SOURCE_NAME_FIELD,
+                      strlen(CONNECTIVITY_SOURCE_NAME_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)interface, strlen(interface)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // startEpochMicrosec
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_START_EPOCH_MICROSEC_FIELD,
+                      strlen(CONNECTIVITY_START_EPOCH_MICROSEC_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (snprintf(json_str, sizeof(json_str), "%" PRIu64,
+               CDTIME_T_TO_US(timestamp)) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // version
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_VERSION_FIELD,
+                      strlen(CONNECTIVITY_VERSION_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, CONNECTIVITY_VERSION_VALUE,
+                      strlen(CONNECTIVITY_VERSION_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // *** END common event header ***
+
+  // *** BEGIN state change fields ***
+
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_STATE_CHANGE_FIELDS_FIELD,
+                      strlen(CONNECTIVITY_STATE_CHANGE_FIELDS_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_map_open(g) != yajl_gen_status_ok)
+    goto err;
+
+  // newState
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_NEW_STATE_FIELD,
+                      strlen(CONNECTIVITY_NEW_STATE_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  int new_state_len =
+      (state == 0 ? strlen(CONNECTIVITY_NEW_STATE_FIELD_DOWN_VALUE)
+                  : strlen(CONNECTIVITY_NEW_STATE_FIELD_UP_VALUE));
+
+  if (yajl_gen_string(g,
+                      (u_char *)(state == 0
+                                     ? CONNECTIVITY_NEW_STATE_FIELD_DOWN_VALUE
+                                     : CONNECTIVITY_NEW_STATE_FIELD_UP_VALUE),
+                      new_state_len) != yajl_gen_status_ok)
+    goto err;
+
+  // oldState
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_OLD_STATE_FIELD,
+                      strlen(CONNECTIVITY_OLD_STATE_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  int old_state_len =
+      (old_state == 0 ? strlen(CONNECTIVITY_OLD_STATE_FIELD_DOWN_VALUE)
+                      : strlen(CONNECTIVITY_OLD_STATE_FIELD_UP_VALUE));
+
+  if (yajl_gen_string(g,
+                      (u_char *)(old_state == 0
+                                     ? CONNECTIVITY_OLD_STATE_FIELD_DOWN_VALUE
+                                     : CONNECTIVITY_OLD_STATE_FIELD_UP_VALUE),
+                      old_state_len) != yajl_gen_status_ok)
+    goto err;
+
+  // stateChangeFieldsVersion
+  if (yajl_gen_string(g,
+                      (u_char *)CONNECTIVITY_STATE_CHANGE_FIELDS_VERSION_FIELD,
+                      strlen(CONNECTIVITY_STATE_CHANGE_FIELDS_VERSION_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, CONNECTIVITY_STATE_CHANGE_FIELDS_VERSION_VALUE,
+                      strlen(CONNECTIVITY_STATE_CHANGE_FIELDS_VERSION_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // stateInterface
+  if (yajl_gen_string(g, (u_char *)CONNECTIVITY_STATE_INTERFACE_FIELD,
+                      strlen(CONNECTIVITY_STATE_INTERFACE_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)interface, strlen(interface)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // close state change and header fields
+  if (yajl_gen_map_close(g) != yajl_gen_status_ok ||
+      yajl_gen_map_close(g) != yajl_gen_status_ok)
+    goto err;
+
+  // *** END state change fields ***
+
+  if (yajl_gen_get_buf(g, &buf2, &len) != yajl_gen_status_ok)
+    goto err;
+
+  *buf = strdup((char *)buf2);
+
+  if (*buf == NULL) {
+    ERROR("connectivity plugin: strdup failed during gen_message_payload: %s",
+          STRERRNO);
+    goto err;
+  }
+
+  yajl_gen_free(g);
+
+  return 0;
+
+err:
+  yajl_gen_free(g);
+  ERROR("connectivity plugin: gen_message_payload failed to generate JSON");
+  return -1;
+}
+
+static interface_list_t *add_interface(const char *interface, int status,
+                                       int prev_status) {
+  interface_list_t *il = calloc(1, sizeof(*il));
+
+  if (il == NULL) {
+    ERROR("connectivity plugin: calloc failed during add_interface: %s",
+          STRERRNO);
+    return NULL;
+  }
+
+  char *interface2 = strdup(interface);
+  if (interface2 == NULL) {
+    sfree(il);
+    ERROR("connectivity plugin: strdup failed during add_interface: %s",
+          STRERRNO);
+    return NULL;
+  }
+
+  il->interface = interface2;
+  il->status = status;
+  il->prev_status = prev_status;
+  il->timestamp = cdtime();
+  il->sent = 0;
+  il->next = interface_list_head;
+  interface_list_head = il;
+
+  DEBUG("connectivity plugin: added interface %s", interface2);
+
+  return il;
+}
+
+static int connectivity_link_state(struct nlmsghdr *msg) {
+  pthread_mutex_lock(&connectivity_data_lock);
+
+  struct nlattr *attr;
+  struct ifinfomsg *ifi = mnl_nlmsg_get_payload(msg);
+
+  /* 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_data_lock);
+      return MNL_CB_ERROR;
+    }
+
+    const char *dev = mnl_attr_get_str(attr);
+
+    // Check the list of interfaces we should monitor, if we've chosen
+    // a subset.  If we don't care about this one, abort.
+    if (ignorelist_match(ignorelist, dev) != 0) {
+      DEBUG("connectivity plugin: Ignoring link state change for unmonitored "
+            "interface: %s",
+            dev);
+      break;
+    }
+
+    interface_list_t *il = NULL;
+
+    for (il = interface_list_head; il != NULL; il = il->next)
+      if (strcmp(dev, il->interface) == 0)
+        break;
+
+    if (il == NULL) {
+      // We haven't encountered this interface yet, so add it to the linked list
+      il = add_interface(dev, LINK_STATE_UNKNOWN, LINK_STATE_UNKNOWN);
+
+      if (il == NULL) {
+        ERROR("connectivity plugin: unable to add interface %s during "
+              "connectivity_link_state",
+              dev);
+        return MNL_CB_ERROR;
+      }
+    }
+
+    uint32_t prev_status = il->status;
+    il->status =
+        ((ifi->ifi_flags & IFF_RUNNING) ? LINK_STATE_UP : LINK_STATE_DOWN);
+    il->timestamp = cdtime();
+
+    // If the new status is different than the previous status,
+    // store the previous status and set sent to zero, and set the
+    // global flag to indicate there are statuses to dispatch
+    if (il->status != prev_status) {
+      il->prev_status = prev_status;
+      il->sent = 0;
+      statuses_to_send = 1;
+    }
+
+    DEBUG("connectivity plugin (%llu): Interface %s status is now %s",
+          (unsigned long long)il->timestamp, dev,
+          ((ifi->ifi_flags & IFF_RUNNING) ? "UP" : "DOWN"));
+
+    // no need to loop again, we found the interface name attr
+    // (otherwise the first if-statement in the loop would
+    // have moved us on with 'continue')
+    break;
+  }
+
+  pthread_mutex_unlock(&connectivity_data_lock);
+
+  return 0;
+}
+
+static int msg_handler(struct nlmsghdr *msg) {
+  // We are only interested in RTM_NEWLINK messages
+  if (msg->nlmsg_type != RTM_NEWLINK) {
+    return 0;
+  }
+  return connectivity_link_state(msg);
+}
+
+static int read_event(int (*msg_handler)(struct nlmsghdr *)) {
+  int ret = 0;
+  int recv_flags = MSG_DONTWAIT;
+
+  if (nl_sock == -1 || msg_handler == NULL)
+    return EINVAL;
+
+  while (42) {
+    pthread_mutex_lock(&connectivity_threads_lock);
+
+    if (connectivity_netlink_thread_loop <= 0) {
+      pthread_mutex_unlock(&connectivity_threads_lock);
+      return ret;
+    }
+
+    pthread_mutex_unlock(&connectivity_threads_lock);
+
+    char buf[4096];
+    int status = recv(nl_sock, buf, sizeof(buf), recv_flags);
+
+    if (status < 0) {
+
+      // If there were no more messages to drain from the socket,
+      // then signal the dequeue thread and allow it to dispatch
+      // any saved interface status changes.  Then continue, but
+      // block and wait for new messages
+      if (errno == EWOULDBLOCK || errno == EAGAIN) {
+        pthread_cond_signal(&connectivity_cond);
+
+        recv_flags = 0;
+        continue;
+      }
+
+      if (errno == EINTR) {
+        // Interrupt, so just continue and try again
+        continue;
+      }
+
+      /* Anything else is an error */
+      ERROR("connectivity plugin: read_event: Error recv: %d", status);
+      return status;
+    }
+
+    // Message received successfully, so we'll stop blocking on the
+    // receive call for now (until we get a "would block" error, which
+    // will be handled above)
+    recv_flags = MSG_DONTWAIT;
+
+    if (status == 0) {
+      DEBUG("connectivity plugin: read_event: EOF");
+    }
+
+    /* We need to handle more than one message per 'recvmsg' */
+    for (struct nlmsghdr *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) {
+        struct nlmsgerr *l_err = (struct nlmsgerr *)NLMSG_DATA(h);
+        ERROR("connectivity plugin: read_event: Message is an error: %d",
+              l_err->error);
+        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",
+                ret);
+          return ret;
+        }
+      } else {
+        ERROR("connectivity plugin: read_event: Error NULL message handler");
+        return -1;
+      }
+    }
+  }
+
+  return ret;
+}
+
+static void connectivity_dispatch_notification(const char *interface,
+                                               gauge_t value, gauge_t old_value,
+                                               cdtime_t timestamp) {
+
+  notification_t n = {
+      .severity = (value == LINK_STATE_UP ? NOTIF_OKAY : NOTIF_FAILURE),
+      .time = cdtime(),
+      .plugin = "connectivity",
+      .type = "gauge",
+      .type_instance = "interface_status",
+  };
+
+  sstrncpy(n.host, hostname_g, sizeof(n.host));
+  sstrncpy(n.plugin_instance, interface, sizeof(n.plugin_instance));
+
+  char *buf = NULL;
+
+  gen_message_payload(value, old_value, interface, timestamp, &buf);
+
+  int status = plugin_notification_meta_add_string(&n, "ves", buf);
+
+  if (status < 0) {
+    sfree(buf);
+    ERROR("connectivity plugin: unable to set notification VES metadata: %s",
+          STRERRNO);
+    return;
+  }
+
+  DEBUG("connectivity plugin: notification VES metadata: %s",
+        n.meta->nm_value.nm_string);
+
+  DEBUG("connectivity plugin: dispatching state %d for interface %s",
+        (int)value, interface);
+
+  plugin_dispatch_notification(&n);
+  plugin_notification_meta_free(n.meta);
+
+  // strdup'd in gen_message_payload
+  if (buf != NULL)
+    sfree(buf);
+}
+
+// NOTE: Caller MUST hold connectivity_data_lock when calling this function
+static void send_interface_status() {
+  for (interface_list_t *il = interface_list_head; il != NULL;
+       il = il->next) /* {{{ */
+  {
+    uint32_t status = il->status;
+    uint32_t prev_status = il->prev_status;
+    uint32_t sent = il->sent;
+
+    if (status != prev_status && sent == 0) {
+      connectivity_dispatch_notification(il->interface, status, prev_status,
+                                         il->timestamp);
+      il->sent = 1;
+    }
+  } /* }}} for (il = interface_list_head; il != NULL; il = il->next) */
+
+  statuses_to_send = 0;
+}
+
+static void read_interface_status() /* {{{ */
+{
+  pthread_mutex_lock(&connectivity_data_lock);
+
+  // If we don't have any interface statuses to dispatch,
+  // then we wait until signalled
+  if (!statuses_to_send)
+    pthread_cond_wait(&connectivity_cond, &connectivity_data_lock);
+
+  send_interface_status();
+
+  pthread_mutex_unlock(&connectivity_data_lock);
+} /* }}} int *read_interface_status */
+
+static void *connectivity_netlink_thread(void *arg) /* {{{ */
+{
+  pthread_mutex_lock(&connectivity_threads_lock);
+
+  while (connectivity_netlink_thread_loop > 0) {
+    pthread_mutex_unlock(&connectivity_threads_lock);
+
+    int status = read_event(msg_handler);
+
+    pthread_mutex_lock(&connectivity_threads_lock);
+
+    if (status < 0) {
+      connectivity_netlink_thread_error = 1;
+      break;
+    }
+  } /* while (connectivity_netlink_thread_loop > 0) */
+
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  return (void *)0;
+} /* }}} void *connectivity_netlink_thread */
+
+static void *connectivity_dequeue_thread(void *arg) /* {{{ */
+{
+  pthread_mutex_lock(&connectivity_threads_lock);
+
+  while (connectivity_dequeue_thread_loop > 0) {
+    pthread_mutex_unlock(&connectivity_threads_lock);
+
+    read_interface_status();
+
+    pthread_mutex_lock(&connectivity_threads_lock);
+  } /* while (connectivity_dequeue_thread_loop > 0) */
+
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  return ((void *)0);
+} /* }}} void *connectivity_dequeue_thread */
+
+static int nl_connect() {
+  struct sockaddr_nl sa_nl = {
+      .nl_family = AF_NETLINK,
+      .nl_groups = RTMGRP_LINK,
+      .nl_pid = getpid(),
+  };
+
+  nl_sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+  if (nl_sock == -1) {
+    ERROR("connectivity plugin: socket open failed: %s", STRERRNO);
+    return -1;
+  }
+
+  int rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
+  if (rc == -1) {
+    ERROR("connectivity plugin: socket bind failed: %s", STRERRNO);
+    close(nl_sock);
+    nl_sock = -1;
+    return -1;
+  }
+
+  return 0;
+}
+
+static int start_netlink_thread(void) /* {{{ */
+{
+  pthread_mutex_lock(&connectivity_threads_lock);
+
+  if (connectivity_netlink_thread_loop != 0) {
+    pthread_mutex_unlock(&connectivity_threads_lock);
+    return 0;
+  }
+
+  connectivity_netlink_thread_loop = 1;
+  connectivity_netlink_thread_error = 0;
+
+  int status;
+
+  if (nl_sock == -1) {
+    status = nl_connect();
+
+    if (status != 0) {
+      pthread_mutex_unlock(&connectivity_threads_lock);
+      return status;
+    }
+  }
+
+  status = plugin_thread_create(&connectivity_netlink_thread_id,
+                                /* attr = */ NULL, connectivity_netlink_thread,
+                                /* arg = */ (void *)0, "connectivity");
+  if (status != 0) {
+    connectivity_netlink_thread_loop = 0;
+    ERROR("connectivity plugin: Starting thread failed.");
+    pthread_mutex_unlock(&connectivity_threads_lock);
+
+    int status2 = close(nl_sock);
+
+    if (status2 != 0) {
+      ERROR("connectivity plugin: failed to close socket %d: %d (%s)", nl_sock,
+            status2, STRERRNO);
+    }
+
+    nl_sock = -1;
+
+    return -1;
+  }
+
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  return status;
+}
+
+static int start_dequeue_thread(void) /* {{{ */
+{
+  pthread_mutex_lock(&connectivity_threads_lock);
+
+  if (connectivity_dequeue_thread_loop != 0) {
+    pthread_mutex_unlock(&connectivity_threads_lock);
+    return 0;
+  }
+
+  connectivity_dequeue_thread_loop = 1;
+
+  int status =
+      plugin_thread_create(&connectivity_dequeue_thread_id,
+                           /* attr = */ NULL, connectivity_dequeue_thread,
+                           /* arg = */ (void *)0, "connectivity");
+  if (status != 0) {
+    connectivity_dequeue_thread_loop = 0;
+    ERROR("connectivity plugin: Starting dequeue thread failed.");
+    pthread_mutex_unlock(&connectivity_threads_lock);
+    return -1;
+  }
+
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  return status;
+} /* }}} int start_dequeue_thread */
+
+static int start_threads(void) /* {{{ */
+{
+  int status = start_netlink_thread();
+  int status2 = start_dequeue_thread();
+
+  if (status != 0)
+    return status;
+  else
+    return status2;
+} /* }}} int start_threads */
+
+static int stop_netlink_thread(int shutdown) /* {{{ */
+{
+  int socket_status;
+
+  if (nl_sock != -1) {
+    socket_status = close(nl_sock);
+    if (socket_status != 0) {
+      ERROR("connectivity plugin: failed to close socket %d: %d (%s)", nl_sock,
+            socket_status, STRERRNO);
+    }
+
+    nl_sock = -1;
+  } else
+    socket_status = 0;
+
+  pthread_mutex_lock(&connectivity_threads_lock);
+
+  if (connectivity_netlink_thread_loop == 0) {
+    pthread_mutex_unlock(&connectivity_threads_lock);
+    // Thread has already been terminated, nothing more to attempt
+    return socket_status;
+  }
+
+  // Set thread termination status
+  connectivity_netlink_thread_loop = 0;
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  // Let threads waiting on access to the interface list know to move
+  // on such that they'll see the thread's termination status
+  pthread_cond_broadcast(&connectivity_cond);
+
+  int thread_status;
+
+  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_netlink_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.
+
+    DEBUG("connectivity plugin: Canceling netlink thread for process shutdown");
+
+    thread_status = pthread_cancel(connectivity_netlink_thread_id);
+
+    if (thread_status != 0 && thread_status != ESRCH) {
+      ERROR("connectivity plugin: Unable to cancel netlink thread: %d",
+            thread_status);
+      thread_status = -1;
+    } else
+      thread_status = 0;
+  } else {
+    thread_status =
+        pthread_join(connectivity_netlink_thread_id, /* return = */ NULL);
+    if (thread_status != 0 && thread_status != ESRCH) {
+      ERROR("connectivity plugin: Stopping netlink thread failed: %d",
+            thread_status);
+      thread_status = -1;
+    } else
+      thread_status = 0;
+  }
+
+  pthread_mutex_lock(&connectivity_threads_lock);
+  memset(&connectivity_netlink_thread_id, 0,
+         sizeof(connectivity_netlink_thread_id));
+  connectivity_netlink_thread_error = 0;
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  DEBUG("connectivity plugin: Finished requesting stop of netlink thread");
+
+  if (socket_status != 0)
+    return socket_status;
+  else
+    return thread_status;
+}
+
+static int stop_dequeue_thread() /* {{{ */
+{
+  pthread_mutex_lock(&connectivity_threads_lock);
+
+  if (connectivity_dequeue_thread_loop == 0) {
+    pthread_mutex_unlock(&connectivity_threads_lock);
+    return -1;
+  }
+
+  // Set thread termination status
+  connectivity_dequeue_thread_loop = 0;
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  // Let threads waiting on access to the interface list know to move
+  // on such that they'll see the threads termination status
+  pthread_cond_broadcast(&connectivity_cond);
+
+  // Calling pthread_cancel here just assures that the thread is
+  // gone and that the process has been fully terminated.
+
+  DEBUG("connectivity plugin: Canceling dequeue thread for process shutdown");
+
+  int status = pthread_cancel(connectivity_dequeue_thread_id);
+
+  if (status != 0 && status != ESRCH) {
+    ERROR("connectivity plugin: Unable to cancel dequeue thread: %d", status);
+    status = -1;
+  } else
+    status = 0;
+
+  pthread_mutex_lock(&connectivity_threads_lock);
+  memset(&connectivity_dequeue_thread_id, 0,
+         sizeof(connectivity_dequeue_thread_id));
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  DEBUG("connectivity plugin: Finished requesting stop of dequeue thread");
+
+  return status;
+} /* }}} int stop_dequeue_thread */
+
+static int stop_threads() /* {{{ */
+{
+  int status = stop_netlink_thread(1);
+  int status2 = stop_dequeue_thread();
+
+  if (status != 0)
+    return status;
+  else
+    return status2;
+} /* }}} int stop_threads */
+
+static int connectivity_init(void) /* {{{ */
+{
+  if (monitor_all_interfaces) {
+    NOTICE("connectivity plugin: No interfaces have been selected, so all will "
+           "be monitored");
+  }
+
+  return start_threads();
+} /* }}} int connectivity_init */
+
+static int connectivity_config(const char *key, const char *value) /* {{{ */
+{
+  if (ignorelist == NULL) {
+    ignorelist = ignorelist_create(/* invert = */ 1);
+
+    if (ignorelist == NULL)
+      return -1;
+  }
+
+  if (strcasecmp(key, "Interface") == 0) {
+    ignorelist_add(ignorelist, value);
+    monitor_all_interfaces = 0;
+  } else if (strcasecmp(key, "IgnoreSelected") == 0) {
+    int invert = 1;
+    if (IS_TRUE(value))
+      invert = 0;
+    ignorelist_set_invert(ignorelist, invert);
+  } else {
+    return -1;
+  }
+
+  return 0;
+} /* }}} int connectivity_config */
+
+static int connectivity_read(void) /* {{{ */
+{
+  pthread_mutex_lock(&connectivity_threads_lock);
+
+  if (connectivity_netlink_thread_error != 0) {
+
+    pthread_mutex_unlock(&connectivity_threads_lock);
+
+    ERROR("connectivity plugin: The netlink thread had a problem. Restarting "
+          "it.");
+
+    stop_netlink_thread(0);
+
+    for (interface_list_t *il = interface_list_head; il != NULL;
+         il = il->next) {
+      il->status = LINK_STATE_UNKNOWN;
+      il->prev_status = LINK_STATE_UNKNOWN;
+      il->sent = 0;
+    }
+
+    start_netlink_thread();
+
+    return -1;
+  } /* if (connectivity_netlink_thread_error != 0) */
+
+  pthread_mutex_unlock(&connectivity_threads_lock);
+
+  return 0;
+} /* }}} int connectivity_read */
+
+static int connectivity_shutdown(void) /* {{{ */
+{
+  DEBUG("connectivity plugin: Shutting down thread.");
+
+  int status = stop_threads();
+
+  interface_list_t *il = interface_list_head;
+  while (il != NULL) {
+    interface_list_t *il_next;
+
+    il_next = il->next;
+
+    sfree(il->interface);
+    sfree(il);
+
+    il = il_next;
+  }
+
+  ignorelist_free(ignorelist);
+
+  return status;
+} /* }}} 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 */
index cf3d3da..d23d071 100644 (file)
 #include "plugin.h"
 #include "utils/common/common.h"
 
-#ifdef HAVE_SYS_SYSCTL_H
+#if defined(HAVE_SYSCTLBYNAME) && defined(HAVE_SYS_SYSCTL_H)
 #include <sys/sysctl.h>
-#endif
-
-#if HAVE_SYSCTLBYNAME
 /* no global variables */
 /* #endif HAVE_SYSCTLBYNAME */
 
index 1a3c4f4..3a7364e 100644 (file)
@@ -137,40 +137,58 @@ static cf_callback_t *cf_search(const char *type) {
   return cf_cb;
 }
 
-static int cf_dispatch(const char *type, const char *orig_key,
-                       const char *orig_value) {
-  cf_callback_t *cf_cb;
-  plugin_ctx_t old_ctx;
-  char *key;
-  char *value;
-  int ret;
-  int i = 0;
+static int cf_dispatch_option(const cf_callback_t *cf_cb, oconfig_item_t *ci) {
 
+  const char *plugin = cf_cb->type;
+  const char *orig_key = ci->key;
   if (orig_key == NULL)
     return EINVAL;
 
-  DEBUG("type = %s, key = %s, value = %s", ESCAPE_NULL(type), orig_key,
-        ESCAPE_NULL(orig_value));
+  /* (Re)construct string value for option */
+  char buffer[4096];
+  int buffer_free = sizeof(buffer);
+  char *buffer_ptr = buffer;
 
-  if ((cf_cb = cf_search(type)) == NULL) {
-    WARNING("Found a configuration for the `%s' plugin, but "
-            "the plugin isn't loaded or didn't register "
-            "a configuration callback.",
-            type);
-    return -1;
+  for (int i = 0; i < ci->values_num; i++) {
+    int status = -1;
+
+    if (ci->values[i].type == OCONFIG_TYPE_STRING)
+      status =
+          ssnprintf(buffer_ptr, buffer_free, " %s", ci->values[i].value.string);
+    else if (ci->values[i].type == OCONFIG_TYPE_NUMBER)
+      status = ssnprintf(buffer_ptr, buffer_free, " %lf",
+                         ci->values[i].value.number);
+    else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN)
+      status = ssnprintf(buffer_ptr, buffer_free, " %s",
+                         ci->values[i].value.boolean ? "true" : "false");
+
+    if ((status < 0) || (status >= buffer_free))
+      return -1;
+    buffer_free -= status;
+    buffer_ptr += status;
   }
 
-  if ((key = strdup(orig_key)) == NULL)
+  /* skip the initial space */
+  const char *orig_value = buffer + 1;
+
+  DEBUG("plugin = %s, key = %s, value = %s", ESCAPE_NULL(plugin), orig_key,
+        ESCAPE_NULL(orig_value));
+
+  char *key = strdup(orig_key);
+  if (key == NULL)
     return 1;
-  if ((value = strdup(orig_value)) == NULL) {
+
+  char *value = strdup(orig_value);
+  if (value == NULL) {
     free(key);
     return 2;
   }
 
-  ret = -1;
+  int ret = -1;
 
-  old_ctx = plugin_set_ctx(cf_cb->ctx);
+  plugin_ctx_t old_ctx = plugin_set_ctx(cf_cb->ctx);
 
+  int i;
   for (i = 0; i < cf_cb->keys_num; i++) {
     if ((cf_cb->keys[i] != NULL) && (strcasecmp(cf_cb->keys[i], key) == 0)) {
       ret = (*cf_cb->callback)(key, value);
@@ -181,13 +199,13 @@ static int cf_dispatch(const char *type, const char *orig_key,
   plugin_set_ctx(old_ctx);
 
   if (i >= cf_cb->keys_num)
-    WARNING("Plugin `%s' did not register for value `%s'.", type, key);
+    WARNING("Plugin `%s' did not register for value `%s'.", plugin, key);
 
   free(key);
   free(value);
 
   return ret;
-} /* int cf_dispatch */
+} /* int cf_dispatch_option */
 
 static int dispatch_global_option(const oconfig_item_t *ci) {
   if (ci->values_num != 1) {
@@ -299,38 +317,6 @@ static int dispatch_loadplugin(oconfig_item_t *ci) {
   return ret_val;
 } /* int dispatch_value_loadplugin */
 
-static int dispatch_value_plugin(const char *plugin, oconfig_item_t *ci) {
-  char buffer[4096];
-  char *buffer_ptr;
-  int buffer_free;
-
-  buffer_ptr = buffer;
-  buffer_free = sizeof(buffer);
-
-  for (int i = 0; i < ci->values_num; i++) {
-    int status = -1;
-
-    if (ci->values[i].type == OCONFIG_TYPE_STRING)
-      status =
-          ssnprintf(buffer_ptr, buffer_free, " %s", ci->values[i].value.string);
-    else if (ci->values[i].type == OCONFIG_TYPE_NUMBER)
-      status = ssnprintf(buffer_ptr, buffer_free, " %lf",
-                         ci->values[i].value.number);
-    else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN)
-      status = ssnprintf(buffer_ptr, buffer_free, " %s",
-                         ci->values[i].value.boolean ? "true" : "false");
-
-    if ((status < 0) || (status >= buffer_free))
-      return -1;
-    buffer_free -= status;
-    buffer_ptr += status;
-  }
-  /* skip the initial space */
-  buffer_ptr = buffer + 1;
-
-  return cf_dispatch(plugin, ci->key, buffer_ptr);
-} /* int dispatch_value_plugin */
-
 static int dispatch_value(oconfig_item_t *ci) {
   int ret = 0;
 
@@ -364,71 +350,84 @@ static int dispatch_block_plugin(oconfig_item_t *ci) {
     return -1;
   }
 
-  const char *name = ci->values[0].value.string;
-  if (strcmp("libvirt", name) == 0) {
+  const char *plugin_name = ci->values[0].value.string;
+  if (strcmp("libvirt", plugin_name) == 0) {
     /* TODO(octo): Remove this legacy. */
     WARNING("The \"libvirt\" plugin has been renamed to \"virt\" to avoid "
             "problems with the build system. "
             "Your configuration is still using the old name. "
             "Please change it to use \"virt\" as soon as possible. "
             "This compatibility code will go away eventually.");
-    name = "virt";
+    plugin_name = "virt";
   }
 
-  if (IS_TRUE(global_option_get("AutoLoadPlugin"))) {
+  bool plugin_loaded = plugin_is_loaded(plugin_name);
+
+  if (!plugin_loaded && IS_TRUE(global_option_get("AutoLoadPlugin"))) {
     plugin_ctx_t ctx = {0};
-    plugin_ctx_t old_ctx;
-    int status;
 
     /* default to the global interval set before loading this plugin */
     ctx.interval = cf_get_default_interval();
-    ctx.name = strdup(name);
+    ctx.name = strdup(plugin_name);
 
-    old_ctx = plugin_set_ctx(ctx);
-    status = plugin_load(name, /* flags = */ false);
+    plugin_ctx_t old_ctx = plugin_set_ctx(ctx);
+    int status = plugin_load(plugin_name, /* flags = */ false);
     /* reset to the "global" context */
     plugin_set_ctx(old_ctx);
 
     if (status != 0) {
-      ERROR("Automatically loading plugin \"%s\" failed "
+      ERROR("Automatically loading plugin `%s' failed "
             "with status %i.",
-            name, status);
+            plugin_name, status);
       return status;
     }
+    plugin_loaded = true;
+  }
+
+  if (!plugin_loaded) {
+    WARNING("There is configuration for the `%s' plugin, but the plugin isn't "
+            "loaded. Please check your configuration.",
+            plugin_name);
+
+    /* Try to be backward-compatible with previous versions */
+    return 0;
   }
 
   /* Check for a complex callback first */
   for (cf_complex_callback_t *cb = complex_callback_head; cb != NULL;
        cb = cb->next) {
-    if (strcasecmp(name, cb->type) == 0) {
-      plugin_ctx_t old_ctx;
-      int ret_val;
-
-      old_ctx = plugin_set_ctx(cb->ctx);
-      ret_val = (cb->callback(ci));
+    if (strcasecmp(plugin_name, cb->type) == 0) {
+      plugin_ctx_t old_ctx = plugin_set_ctx(cb->ctx);
+      int ret_val = (cb->callback(ci));
       plugin_set_ctx(old_ctx);
       return ret_val;
     }
   }
 
   /* Hm, no complex plugin found. Dispatch the values one by one */
-  for (int i = 0, ret = 0; i < ci->children_num; i++) {
+  cf_callback_t *cf_cb = cf_search(plugin_name);
+  if (cf_cb == NULL) {
+    WARNING("Found a configuration for the `%s' plugin, but "
+            "the plugin didn't register a configuration callback.",
+            plugin_name);
+    return -1;
+  }
+
+  for (int i = 0; i < ci->children_num; i++) {
     if (ci->children[i].children == NULL) {
       oconfig_item_t *child = ci->children + i;
-      ret = dispatch_value_plugin(name, child);
+      int ret = cf_dispatch_option(cf_cb, child);
       if (ret != 0) {
-        ERROR("Plugin %s failed to handle option %s, return code: %i", name,
-              child->key, ret);
+        ERROR("Plugin `%s' failed to handle option `%s', return code: %i",
+              plugin_name, child->key, ret);
         return ret;
       }
     } else {
       WARNING("There is a `%s' block within the "
-              "configuration for the %s plugin. "
-              "The plugin either only expects "
-              "\"simple\" configuration statements "
-              "or wasn't loaded using `LoadPlugin'."
-              " Please check your configuration.",
-              ci->children[i].key, name);
+              "configuration for the `%s' plugin. "
+              "The plugin only expects \"simple\" configuration options. "
+              "Blocks are not supported. Please check your configuration.",
+              ci->children[i].key, plugin_name);
     }
   }
 
index 2c784e4..52cb0a4 100644 (file)
@@ -85,6 +85,14 @@ struct read_func_s {
 };
 typedef struct read_func_s read_func_t;
 
+struct cache_event_func_s {
+  plugin_cache_event_cb callback;
+  char *name;
+  user_data_t user_data;
+  plugin_ctx_t plugin_ctx;
+};
+typedef struct cache_event_func_s cache_event_func_t;
+
 struct write_queue_s;
 typedef struct write_queue_s write_queue_t;
 struct write_queue_s {
@@ -112,6 +120,9 @@ static llist_t *list_shutdown;
 static llist_t *list_log;
 static llist_t *list_notification;
 
+static size_t list_cache_event_num;
+static cache_event_func_t list_cache_event[32];
+
 static fc_chain_t *pre_cache_chain;
 static fc_chain_t *post_cache_chain;
 
@@ -263,8 +274,6 @@ static void destroy_read_heap(void) /* {{{ */
 
 static int register_callback(llist_t **list, /* {{{ */
                              const char *name, callback_func_t *cf) {
-  llentry_t *le;
-  char *key;
 
   if (*list == NULL) {
     *list = llist_create();
@@ -276,14 +285,14 @@ static int register_callback(llist_t **list, /* {{{ */
     }
   }
 
-  key = strdup(name);
+  char *key = strdup(name);
   if (key == NULL) {
     ERROR("plugin: register_callback: strdup failed.");
     destroy_callback(cf);
     return -1;
   }
 
-  le = llist_search(*list, name);
+  llentry_t *le = llist_search(*list, name);
   if (le == NULL) {
     le = llentry_create(key, cf);
     if (le == NULL) {
@@ -296,9 +305,7 @@ static int register_callback(llist_t **list, /* {{{ */
 
     llist_append(*list, le);
   } else {
-    callback_func_t *old_cf;
-
-    old_cf = le->value;
+    callback_func_t *old_cf = le->value;
     le->value = cf;
 
     P_WARNING("register_callback: "
@@ -907,15 +914,13 @@ void plugin_set_dir(const char *dir) {
     ERROR("plugin_set_dir: strdup(\"%s\") failed", dir);
 }
 
-static bool plugin_is_loaded(char const *name) {
-  int status;
-
+bool plugin_is_loaded(char const *name) {
   if (plugins_loaded == NULL)
     plugins_loaded =
         c_avl_create((int (*)(const void *, const void *))strcasecmp);
   assert(plugins_loaded != NULL);
 
-  status = c_avl_get(plugins_loaded, name, /* ret_value = */ NULL);
+  int status = c_avl_get(plugins_loaded, name, /* ret_value = */ NULL);
   return status == 0;
 }
 
@@ -1314,6 +1319,60 @@ EXPORT int plugin_register_missing(const char *name, plugin_missing_cb callback,
   return create_register_callback(&list_missing, name, (void *)callback, ud);
 } /* int plugin_register_missing */
 
+EXPORT int plugin_register_cache_event(const char *name,
+                                       plugin_cache_event_cb callback,
+                                       user_data_t const *ud) {
+
+  if (name == NULL || callback == NULL)
+    return EINVAL;
+
+  char *name_copy = strdup(name);
+  if (name_copy == NULL) {
+    P_ERROR("plugin_register_cache_event: strdup failed.");
+    free_userdata(ud);
+    return ENOMEM;
+  }
+
+  if (list_cache_event_num >= 32) {
+    P_ERROR("plugin_register_cache_event: Too much cache event callbacks tried "
+            "to be registered.");
+    free_userdata(ud);
+    return ENOMEM;
+  }
+
+  for (size_t i = 0; i < list_cache_event_num; i++) {
+    cache_event_func_t *cef = &list_cache_event[i];
+    if (!cef->callback)
+      continue;
+
+    if (strcmp(name, cef->name) == 0) {
+      P_ERROR("plugin_register_cache_event: a callback named `%s' already "
+              "registered!",
+              name);
+      free_userdata(ud);
+      return -1;
+    }
+  }
+
+  user_data_t user_data;
+  if (ud == NULL) {
+    user_data = (user_data_t){
+        .data = NULL, .free_func = NULL,
+    };
+  } else {
+    user_data = *ud;
+  }
+
+  list_cache_event[list_cache_event_num] =
+      (cache_event_func_t){.callback = callback,
+                           .name = name_copy,
+                           .user_data = user_data,
+                           .plugin_ctx = plugin_get_ctx()};
+  list_cache_event_num++;
+
+  return 0;
+} /* int plugin_register_cache_event */
+
 EXPORT int plugin_register_shutdown(const char *name, int (*callback)(void)) {
   return create_register_callback(&list_shutdown, name, (void *)callback, NULL);
 } /* int plugin_register_shutdown */
@@ -1515,6 +1574,32 @@ EXPORT int plugin_unregister_missing(const char *name) {
   return plugin_unregister(list_missing, name);
 }
 
+EXPORT int plugin_unregister_cache_event(const char *name) {
+  for (size_t i = 0; i < list_cache_event_num; i++) {
+    cache_event_func_t *cef = &list_cache_event[i];
+    if (!cef->callback)
+      continue;
+    if (strcmp(name, cef->name) == 0) {
+      /* Mark callback as inactive, so mask in cache entries remains actual */
+      cef->callback = NULL;
+      sfree(cef->name);
+      free_userdata(&cef->user_data);
+    }
+  }
+  return 0;
+}
+
+static void destroy_cache_event_callbacks() {
+  for (size_t i = 0; i < list_cache_event_num; i++) {
+    cache_event_func_t *cef = &list_cache_event[i];
+    if (!cef->callback)
+      continue;
+    cef->callback = NULL;
+    sfree(cef->name);
+    free_userdata(&cef->user_data);
+  }
+}
+
 EXPORT int plugin_unregister_shutdown(const char *name) {
   return plugin_unregister(list_shutdown, name);
 }
@@ -1859,6 +1944,7 @@ EXPORT int plugin_shutdown_all(void) {
    * the data isn't freed twice. */
   destroy_all_callbacks(&list_flush);
   destroy_all_callbacks(&list_missing);
+  destroy_cache_event_callbacks();
   destroy_all_callbacks(&list_write);
 
   destroy_all_callbacks(&list_notification);
@@ -1899,6 +1985,82 @@ EXPORT int plugin_dispatch_missing(const value_list_t *vl) /* {{{ */
   return 0;
 } /* int }}} plugin_dispatch_missing */
 
+void plugin_dispatch_cache_event(enum cache_event_type_e event_type,
+                                 unsigned long callbacks_mask, const char *name,
+                                 const value_list_t *vl) {
+  switch (event_type) {
+  case CE_VALUE_NEW:
+    callbacks_mask = 0;
+    for (size_t i = 0; i < list_cache_event_num; i++) {
+      cache_event_func_t *cef = &list_cache_event[i];
+      plugin_cache_event_cb callback = cef->callback;
+
+      if (!callback)
+        continue;
+
+      cache_event_t event = (cache_event_t){.type = event_type,
+                                            .value_list = vl,
+                                            .value_list_name = name,
+                                            .ret = 0};
+
+      plugin_ctx_t old_ctx = plugin_set_ctx(cef->plugin_ctx);
+      int status = (*callback)(&event, &cef->user_data);
+      plugin_set_ctx(old_ctx);
+
+      if (status != 0) {
+        ERROR("plugin_dispatch_cache_event: Callback \"%s\" failed with status "
+              "%i for event NEW.",
+              cef->name, status);
+      } else {
+        if (event.ret) {
+          DEBUG(
+              "plugin_dispatch_cache_event: Callback \"%s\" subscribed to %s.",
+              cef->name, name);
+          callbacks_mask |= (1 << (i));
+        } else {
+          DEBUG("plugin_dispatch_cache_event: Callback \"%s\" ignores %s.",
+                cef->name, name);
+        }
+      }
+    }
+
+    if (callbacks_mask)
+      uc_set_callbacks_mask(name, callbacks_mask);
+
+    break;
+  case CE_VALUE_UPDATE:
+  case CE_VALUE_EXPIRED:
+    for (size_t i = 0; i < list_cache_event_num; i++) {
+      cache_event_func_t *cef = &list_cache_event[i];
+      plugin_cache_event_cb callback = cef->callback;
+
+      if (!callback)
+        continue;
+
+      if (callbacks_mask && (1 << (i)) == 0)
+        continue;
+
+      cache_event_t event = (cache_event_t){.type = event_type,
+                                            .value_list = vl,
+                                            .value_list_name = name,
+                                            .ret = 0};
+
+      plugin_ctx_t old_ctx = plugin_set_ctx(cef->plugin_ctx);
+      int status = (*callback)(&event, &cef->user_data);
+      plugin_set_ctx(old_ctx);
+
+      if (status != 0) {
+        ERROR("plugin_dispatch_cache_event: Callback \"%s\" failed with status "
+              "%i for event %s.",
+              cef->name, status,
+              ((event_type == CE_VALUE_UPDATE) ? "UPDATE" : "EXPIRED"));
+      }
+    }
+    break;
+  }
+  return;
+}
+
 static int plugin_dispatch_values_internal(value_list_t *vl) {
   int status;
   static c_complain_t no_write_complaint = C_COMPLAIN_INIT_STATIC;
index 6b3a030..af3693d 100644 (file)
@@ -171,6 +171,15 @@ struct user_data_s {
 };
 typedef struct user_data_s user_data_t;
 
+enum cache_event_type_e { CE_VALUE_NEW, CE_VALUE_UPDATE, CE_VALUE_EXPIRED };
+
+typedef struct cache_event_s {
+  enum cache_event_type_e type;
+  const value_list_t *value_list;
+  const char *value_list_name;
+  int ret;
+} cache_event_t;
+
 struct plugin_ctx_s {
   char *name;
   cdtime_t interval;
@@ -192,6 +201,11 @@ typedef int (*plugin_flush_cb)(cdtime_t timeout, const char *identifier,
  * callbacks should be called, greater than zero if no more callbacks should be
  * called. */
 typedef int (*plugin_missing_cb)(const value_list_t *, user_data_t *);
+/* "cache event" callback. CE_VALUE_NEW events are sent to all registered
+ * callbacks. Callback should check if it interested in further CE_VALUE_UPDATE
+ * and CE_VALUE_EXPIRED events for metric and set event->ret = 1 if so.
+ */
+typedef int (*plugin_cache_event_cb)(cache_event_t *, user_data_t *);
 typedef void (*plugin_log_cb)(int severity, const char *message, user_data_t *);
 typedef int (*plugin_shutdown_cb)(void);
 typedef int (*plugin_notification_cb)(const notification_t *, user_data_t *);
@@ -233,6 +247,7 @@ void plugin_set_dir(const char *dir);
  *  this case.
  */
 int plugin_load(const char *name, bool global);
+bool plugin_is_loaded(char const *name);
 
 int plugin_init_all(void);
 void plugin_read_all(void);
@@ -294,6 +309,9 @@ int plugin_register_flush(const char *name, plugin_flush_cb callback,
                           user_data_t const *user_data);
 int plugin_register_missing(const char *name, plugin_missing_cb callback,
                             user_data_t const *user_data);
+int plugin_register_cache_event(const char *name,
+                                plugin_cache_event_cb callback,
+                                user_data_t const *ud);
 int plugin_register_shutdown(const char *name, plugin_shutdown_cb callback);
 int plugin_register_data_set(const data_set_t *ds);
 int plugin_register_log(const char *name, plugin_log_cb callback,
@@ -310,6 +328,7 @@ int plugin_unregister_read_group(const char *group);
 int plugin_unregister_write(const char *name);
 int plugin_unregister_flush(const char *name);
 int plugin_unregister_missing(const char *name);
+int plugin_unregister_cache_event(const char *name);
 int plugin_unregister_shutdown(const char *name);
 int plugin_unregister_data_set(const char *name);
 int plugin_unregister_log(const char *name);
@@ -380,6 +399,9 @@ __attribute__((sentinel)) int plugin_dispatch_multivalue(value_list_t const *vl,
                                                          int store_type, ...);
 
 int plugin_dispatch_missing(const value_list_t *vl);
+void plugin_dispatch_cache_event(enum cache_event_type_e event_type,
+                                 unsigned long callbacks_mask, const char *name,
+                                 const value_list_t *vl);
 
 int plugin_dispatch_notification(const notification_t *notif);
 
index 1624f0e..69d5e28 100644 (file)
@@ -41,6 +41,8 @@ void plugin_set_dir(const char *dir) { /* nop */
 
 int plugin_load(const char *name, bool global) { return ENOTSUP; }
 
+bool plugin_is_loaded(const char *name) { return false; }
+
 int plugin_register_config(const char *name,
                            int (*callback)(const char *key, const char *val),
                            const char **keys, int keys_num) {
@@ -67,6 +69,13 @@ int plugin_register_write(__attribute__((unused)) const char *name,
   return ENOTSUP;
 }
 
+int plugin_register_flush(__attribute__((unused)) const char *name,
+                          __attribute__((unused)) plugin_flush_cb callback,
+                          __attribute__((unused))
+                          user_data_t const *user_data) {
+  return ENOTSUP;
+}
+
 int plugin_register_missing(const char *name, plugin_missing_cb callback,
                             user_data_t const *ud) {
   return ENOTSUP;
@@ -85,6 +94,31 @@ int plugin_register_shutdown(const char *name, int (*callback)(void)) {
 
 int plugin_register_data_set(const data_set_t *ds) { return ENOTSUP; }
 
+int plugin_register_notification(__attribute__((unused)) const char *name,
+                                 __attribute__((unused))
+                                 plugin_notification_cb callback,
+                                 __attribute__((unused))
+                                 user_data_t const *user_data) {
+  return ENOTSUP;
+}
+
+#define DECLARE_UNREGISTER(t)                                                  \
+  int plugin_unregister_##t(__attribute__((unused)) char const *name) {        \
+    return ENOTSUP;                                                            \
+  }
+DECLARE_UNREGISTER(config)
+DECLARE_UNREGISTER(complex_config)
+DECLARE_UNREGISTER(init)
+DECLARE_UNREGISTER(read)
+DECLARE_UNREGISTER(read_group)
+DECLARE_UNREGISTER(write)
+DECLARE_UNREGISTER(flush)
+DECLARE_UNREGISTER(missing)
+DECLARE_UNREGISTER(shutdown)
+DECLARE_UNREGISTER(data_set)
+DECLARE_UNREGISTER(log)
+DECLARE_UNREGISTER(notification)
+
 int plugin_dispatch_values(value_list_t const *vl) { return ENOTSUP; }
 
 int plugin_dispatch_notification(__attribute__((unused))
@@ -198,6 +232,14 @@ plugin_ctx_t plugin_set_ctx(plugin_ctx_t ctx) {
 
 cdtime_t plugin_get_interval(void) { return mock_context.interval; }
 
+int plugin_thread_create(__attribute__((unused)) pthread_t *thread,
+                         __attribute__((unused)) const pthread_attr_t *attr,
+                         __attribute__((unused)) void *(*start_routine)(void *),
+                         __attribute__((unused)) void *arg,
+                         __attribute__((unused)) char const *name) {
+  return ENOTSUP;
+}
+
 /* TODO(octo): this function is actually from filter_chain.h, but in order not
  * to tumble down that rabbit hole, we're declaring it here. A better solution
  * would be to hard-code the top-level config keys in daemon/collectd.c to avoid
index df358d2..672b01f 100644 (file)
@@ -67,6 +67,7 @@ typedef struct cache_entry_s {
   size_t history_length;
 
   meta_data_t *meta;
+  unsigned long callbacks_mask;
 } cache_entry_t;
 
 struct uc_iter_s {
@@ -140,18 +141,15 @@ static void uc_check_range(const data_set_t *ds, cache_entry_t *ce) {
 
 static int uc_insert(const data_set_t *ds, const value_list_t *vl,
                      const char *key) {
-  char *key_copy;
-  cache_entry_t *ce;
-
   /* `cache_lock' has been locked by `uc_update' */
 
-  key_copy = strdup(key);
+  char *key_copy = strdup(key);
   if (key_copy == NULL) {
     ERROR("uc_insert: strdup failed.");
     return -1;
   }
 
-  ce = cache_alloc(ds->ds_num);
+  cache_entry_t *ce = cache_alloc(ds->ds_num);
   if (ce == NULL) {
     sfree(key_copy);
     ERROR("uc_insert: cache_alloc (%" PRIsz ") failed.", ds->ds_num);
@@ -203,6 +201,10 @@ static int uc_insert(const data_set_t *ds, const value_list_t *vl,
   ce->interval = vl->interval;
   ce->state = STATE_UNKNOWN;
 
+  if (vl->meta != NULL) {
+    ce->meta = meta_data_clone(vl->meta);
+  }
+
   if (c_avl_insert(cache_tree, key_copy, ce) != 0) {
     sfree(key_copy);
     ERROR("uc_insert: c_avl_insert failed.");
@@ -226,6 +228,7 @@ int uc_check_timeout(void) {
     char *key;
     cdtime_t time;
     cdtime_t interval;
+    unsigned long callbacks_mask;
   } *expired = NULL;
   size_t expired_num = 0;
 
@@ -251,6 +254,7 @@ int uc_check_timeout(void) {
     expired[expired_num].key = strdup(key);
     expired[expired_num].time = ce->last_time;
     expired[expired_num].interval = ce->interval;
+    expired[expired_num].callbacks_mask = ce->callbacks_mask;
 
     if (expired[expired_num].key == NULL) {
       ERROR("uc_check_timeout: strdup failed.");
@@ -286,6 +290,10 @@ int uc_check_timeout(void) {
     }
 
     plugin_dispatch_missing(&vl);
+
+    if (expired[i].callbacks_mask)
+      plugin_dispatch_cache_event(CE_VALUE_EXPIRED, expired[i].callbacks_mask,
+                                  expired[i].key, &vl);
   } /* for (i = 0; i < expired_num; i++) */
 
   /* Now actually remove all the values from the cache. We don't re-evaluate
@@ -315,8 +323,6 @@ int uc_check_timeout(void) {
 
 int uc_update(const data_set_t *ds, const value_list_t *vl) {
   char name[6 * DATA_MAX_NAME_LEN];
-  cache_entry_t *ce = NULL;
-  int status;
 
   if (FORMAT_VL(name, sizeof(name), vl) != 0) {
     ERROR("uc_update: FORMAT_VL failed.");
@@ -325,11 +331,16 @@ int uc_update(const data_set_t *ds, const value_list_t *vl) {
 
   pthread_mutex_lock(&cache_lock);
 
-  status = c_avl_get(cache_tree, name, (void *)&ce);
+  cache_entry_t *ce = NULL;
+  int status = c_avl_get(cache_tree, name, (void *)&ce);
   if (status != 0) /* entry does not yet exist */
   {
     status = uc_insert(ds, vl, name);
     pthread_mutex_unlock(&cache_lock);
+
+    if (status == 0)
+      plugin_dispatch_cache_event(CE_VALUE_NEW, 0 /* mask */, name, vl);
+
     return status;
   }
 
@@ -404,11 +415,32 @@ int uc_update(const data_set_t *ds, const value_list_t *vl) {
   ce->last_update = cdtime();
   ce->interval = vl->interval;
 
+  /* Check if cache entry has registered callbacks */
+  unsigned long callbacks_mask = ce->callbacks_mask;
+
   pthread_mutex_unlock(&cache_lock);
 
+  if (callbacks_mask)
+    plugin_dispatch_cache_event(CE_VALUE_UPDATE, callbacks_mask, name, vl);
+
   return 0;
 } /* int uc_update */
 
+int uc_set_callbacks_mask(const char *name, unsigned long mask) {
+  pthread_mutex_lock(&cache_lock);
+  cache_entry_t *ce = NULL;
+  int status = c_avl_get(cache_tree, name, (void *)&ce);
+  if (status != 0) { /* Ouch, just created entry disappeared ?! */
+    ERROR("uc_set_callbacks_mask: Couldn't find %s entry!", name);
+    pthread_mutex_unlock(&cache_lock);
+    return -1;
+  }
+  DEBUG("uc_set_callbacks_mask: set mask for \"%s\" to %lu.", name, mask);
+  ce->callbacks_mask = mask;
+  pthread_mutex_unlock(&cache_lock);
+  return 0;
+}
+
 int uc_get_rate_by_name(const char *name, gauge_t **ret_values,
                         size_t *ret_values_num) {
   gauge_t *ret = NULL;
@@ -890,13 +922,12 @@ int uc_iterator_get_values(uc_iter_t *iter, value_t **ret_values,
   if ((iter == NULL) || (iter->entry == NULL) || (ret_values == NULL) ||
       (ret_num == NULL))
     return -1;
-
   *ret_values =
       calloc(iter->entry->values_num, sizeof(*iter->entry->values_raw));
   if (*ret_values == NULL)
     return -1;
   for (size_t i = 0; i < iter->entry->values_num; ++i)
-    *ret_values[i] = iter->entry->values_raw[i];
+    (*ret_values)[i] = iter->entry->values_raw[i];
 
   *ret_num = iter->entry->values_num;
 
index d3ea936..a069221 100644 (file)
@@ -56,6 +56,8 @@ int uc_get_hits(const data_set_t *ds, const value_list_t *vl);
 int uc_set_hits(const data_set_t *ds, const value_list_t *vl, int hits);
 int uc_inc_hits(const data_set_t *ds, const value_list_t *vl, int step);
 
+int uc_set_callbacks_mask(const char *name, unsigned long callbacks_mask);
+
 int uc_get_history(const data_set_t *ds, const value_list_t *vl,
                    gauge_t *ret_history, size_t num_steps, size_t num_ds);
 int uc_get_history_by_name(const char *name, gauge_t *ret_history,
index 75bdc12..ff0022e 100644 (file)
@@ -24,6 +24,9 @@
  *   Florian Forster <octo at collectd.org>
  **/
 
+#ifndef UTILS_RANDOM_H
+#define UTILS_RANDOM_H 1
+
 /**
  * Returns a random double value in the range [0..1), i.e. excluding 1.
  *
@@ -46,3 +49,5 @@ uint32_t cdrand_u(void);
  * outside the intended range. This function is thread- and reentrant-safe.
  */
 long cdrand_range(long min, long max);
+
+#endif /* !UTILS_RANDOM_H */
index 1e9cb20..d9577a4 100644 (file)
@@ -747,7 +747,8 @@ static int c_grpc_config_server(oconfig_item_t *ci) {
 
   auto callback_name = grpc::string("grpc/") + addr;
   user_data_t ud = {
-      .data = client, .free_func = c_grpc_destroy_write_callback,
+      .data = client,
+      .free_func = c_grpc_destroy_write_callback,
   };
 
   plugin_register_write(callback_name.c_str(), c_grpc_write, &ud);
index 62848db..99c3ed0 100644 (file)
@@ -325,11 +325,7 @@ static int strlisttoarray(char *str_list, char ***names, size_t *names_num) {
       continue;
 
     if ((isdupstr((const char **)*names, *names_num, token))) {
-      if (str_list != NULL)
-        ERROR(RDT_PLUGIN ": Duplicated process name \'%s\' in group \'%s\'",
-              token, str_list);
-      else
-        ERROR(RDT_PLUGIN ": Duplicated process name \'%s\'", token);
+      ERROR(RDT_PLUGIN ": Duplicated process name \'%s\'", token);
 
       return -EINVAL;
     } else {
index 41f4909..4da73bb 100644 (file)
@@ -2076,7 +2076,7 @@ static int cjni_config_load_plugin(oconfig_item_t *ci) /* {{{ */
     class->object = NULL;
   if (class->object == NULL) {
     ERROR("java plugin: cjni_config_load_plugin: "
-          "Could create a new `%s' object.",
+          "Could not create a new `%s' object.",
           class->name);
     cjni_thread_detach();
     free(class->name);
index 89dc75f..c00f53e 100644 (file)
@@ -513,6 +513,13 @@ static int memcached_read(user_data_t *user_data) {
     }
 
     /*
+     * Number of secs since the server started
+     */
+    else if (FIELD_IS("uptime")) {
+      submit_gauge("uptime", NULL, atof(fields[2]), st);
+    }
+
+    /*
      * Number of bytes used and available (total - used)
      */
     else if (FIELD_IS("bytes")) {
index 806265a..b998cf3 100644 (file)
@@ -638,7 +638,7 @@ static int ir_config(const char *key, const char *value) {
   } else if ((strcasecmp(key, "QDisc") == 0) ||
              (strcasecmp(key, "Class") == 0) ||
              (strcasecmp(key, "Filter") == 0)) {
-    if ((fields_num < 1) || (fields_num > 2)) {
+    if (fields_num > 2) {
       ERROR("netlink plugin: Invalid number of fields for option "
             "`%s'. Got %i, expected 1 or 2.",
             key, fields_num);
diff --git a/src/network_test.c b/src/network_test.c
new file mode 100644 (file)
index 0000000..ae4875c
--- /dev/null
@@ -0,0 +1,255 @@
+/**
+ * Copyright (C) 2019       Florian octo Forster
+ *
+ * ISC License (ISC)
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#define TEST_PLUGIN_NETWORK 1
+
+#include "network.c" /* (sic) */
+
+#include "testing.h"
+
+char *raw_packet_data[] = {
+    "0000000e6c6f63616c686f7374000008000c1513676ac3a6e0970009000c00000002800000"
+    "000002000973776170000004000973776170000005000966726565000006000f0001010000"
+    "0080ff610f420008000c1513676ac3a8fc120004000c737761705f696f0000050007696e00"
+    "0006000f00010200000000000000000008000c1513676ac3a9077d000500086f7574000006"
+    "000f00010200000000000000000008000c1513676ac3bd2a8c0002000e696e746572666163"
+    "65000003000965746830000004000e69665f6f637465747300000500050000060018000202"
+    "02000000000000000000000000000000000008000c1513676ac3bd5a970004000e69665f65"
+    "72726f7273000006001800020202000000000000000000000000000000000008000c151367"
+    "6ac3bd7fea000300076c6f000004000e69665f6f6374657473000006001800020202000000"
+    "000009e79c000000000009e79c0008000c1513676ac3bdaae60003000a776c616e30000006"
+    "001800020202000000001009fa5400000000011cf6670008000c1513676ac3bdb0e0000400"
+    "0e69665f6572726f7273000006001800020202000000000000000000000000000000000008"
+    "000c1513676ac3bd3d6d0003000965746830000004000f69665f7061636b65747300000600"
+    "1800020202000000000000000000000000000000000008000c1513676ac3bdae290003000a"
+    "776c616e300000060018000202020000000000032f8f00000000000205e50008000c151367"
+    "6ac3bdbb7b0003000c646f636b657230000006001800020202000000000000000000000000"
+    "000000000008000c1513676ac3bda0db000300076c6f000004000e69665f6572726f727300"
+    "0006001800020202000000000000000000000000000000000008000c1513676ac3bdbde800"
+    "03000c646f636b657230000006001800020202000000000000000000000000000000000008"
+    "000c1513676ac3bd8d8e000300076c6f000004000f69665f7061636b657473000006001800"
+    "0202020000000000000c9c0000000000000c9c0008000c1513676ac3bdb90b0003000c646f"
+    "636b657230000004000e69665f6f6374657473000006001800020202000000000000000000"
+    "000000000000000008000c1513676ac469b10f0002000e70726f6365737365730000030005"
+    "000004000d70735f7374617465000005000c7a6f6d62696573000006000f00010100000000"
+    "000000000008000c1513676ac469a4a30005000d736c656570696e67000006000f00010100"
+    "00000000006e400008000c1513676ac469c6320005000b706167696e67000006000f000101"
+    "00000000000000000008000c1513676ac469f06e0005000c626c6f636b6564000006000f00"
+    "010100000000000000000008000c1513676ac4698af40005000c72756e6e696e6700000600"
+    "0f00010100000000000000000008000c1513676ac469bbe10005000c73746f707065640000"
+    "06000f00010100000000000000000008000c1513676ac46b8e710004000e666f726b5f7261"
+    "74650000050005000006000f0001020000000000001bcf0008000c1513676d437f12960002"
+    "00086370750000030006300000040008637075000005000b73797374656d000006000f0001"
+    "0200000000000021870008000c1513676d437f36020005000969646c65000006000f000102"
+    "000000000005847a0008000c1513676d437f979b0005000977616974000006000f00010200"
+    "000000000005210008000c1513676d43802ff60005000c736f6674697271000006000f0001"
+    "02000000000000001f0008000c1513676d43803b3a0005000a737465616c000006000f0001"
+    "020000000000000000",
+    "0000000e6c6f63616c686f7374000008000c1513676d4380551f0009000c00000002800000"
+    "00000200086370750000030006310000040008637075000005000975736572000006000f00"
+    "01020000000000007cad0008000c1513676d43805dbe000500096e696365000006000f0001"
+    "0200000000000001de0008000c1513676d4380697d0005000b73797374656d000006000f00"
+    "01020000000000001ce80008000c1513676d438072bd0005000969646c65000006000f0001"
+    "02000000000005931c0008000c1513676d43807c430005000977616974000006000f000102"
+    "000000000000094b0008000c1513676d43808cee0005000c736f6674697271000006000f00"
+    "010200000000000000120008000c1513676d4380843a0005000e696e746572727570740000"
+    "06000f00010200000000000000000008000c1513676d438096230005000a737465616c0000"
+    "06000f00010200000000000000000008000c1513676d4380aa9c0003000632000005000975"
+    "736572000006000f00010200000000000089580008000c1513676d4380b29f000500096e69"
+    "6365000006000f00010200000000000003610008000c1513676d4380c44c0005000969646c"
+    "65000006000f000102000000000005873d0008000c1513676d4380bc0f0005000b73797374"
+    "656d000006000f000102000000000000201d0008000c1513676d4380cea400050009776169"
+    "74000006000f00010200000000000005810008000c1513676d4380d7370005000e696e7465"
+    "7272757074000006000f00010200000000000000000008000c1513676d4380ea830005000a"
+    "737465616c000006000f00010200000000000000000008000c1513676d437eef6200030006"
+    "3000000500096e696365000006000f00010200000000000003920008000c1513676d4380e0"
+    "260003000632000005000c736f6674697271000006000f0001020000000000000016000800"
+    "0c1513676d438101410003000633000005000975736572000006000f000102000000000000"
+    "7d8a0008000c1513676d438109f5000500096e696365000006000f00010200000000000004"
+    "350008000c1513676d4380244b0003000630000005000e696e74657272757074000006000f"
+    "00010200000000000000000008000c1513676d438122070003000633000005000969646c65"
+    "000006000f0001020000000000058eb60008000c1513676d43812e83000500097761697400"
+    "0006000f0001020000000000000ca80008000c1513676d438141480005000c736f66746972"
+    "71000006000f000102000000000000001e0008000c1513676d43814a5d0005000a73746561"
+    "6c000006000f00010200000000000000000008000c1513676d4381149e0005000b73797374"
+    "656d000006000f0001020000000000001b9a0008000c1513676d437ea86000030006300000"
+    "05000975736572000006000f00010200000000000089a80008000c1513676d438138190003"
+    "000633000005000e696e74657272757074000006000f00010200000000000000000008000c"
+    "1513676d438a9ca00002000e696e74657266616365000003000965746830000004000e6966"
+    "5f6f6374657473000005000500000600180002020200000000000000000000000000000000"
+    "0008000c1513676d438aea760004000f69665f7061636b6574730000060018000202020000"
+    "00000000000000000000000000000008000c1513676d438b214d0004000e69665f6572726f"
+    "727300000600180002020200000000000000000000000000000000",
+    "0000000e6c6f63616c686f7374000008000c1513676d438aac590009000c00000002800000"
+    "000002000764660000030009726f6f74000004000f64665f636f6d706c6578000005000966"
+    "726565000006000f0001010000004c077e57420008000c1513676d438b6ada0005000d7265"
+    "736572766564000006000f00010100000000338116420008000c1513676d438b7a17000200"
+    "0e696e7465726661636500000300076c6f000004000e69665f6f6374657473000005000500"
+    "0006001800020202000000000009ecf5000000000009ecf50008000c1513676d438b757800"
+    "02000764660000030009726f6f74000004000f64665f636f6d706c65780000050009757365"
+    "64000006000f000101000000e0a41b26420008000c1513676d438b8ed20002000e696e7465"
+    "726661636500000300076c6f000004000e69665f6572726f72730000050005000006001800"
+    "020202000000000000000000000000000000000008000c1513676d438b86bf0004000f6966"
+    "5f7061636b6574730000060018000202020000000000000c9d0000000000000c9d0008000c"
+    "1513676d438bb3e60003000a776c616e300000060018000202020000000000032fab000000"
+    "00000205ed0008000c1513676d438bd62e0003000c646f636b657230000004000e69665f6f"
+    "6374657473000006001800020202000000000000000000000000000000000008000c151367"
+    "6d438bbc8f0003000a776c616e30000004000e69665f6572726f7273000006001800020202"
+    "000000000000000000000000000000000008000c1513676d438bdf030003000c646f636b65"
+    "7230000004000f69665f7061636b6574730000060018000202020000000000000000000000"
+    "00000000000008000c1513676d438baaf10003000a776c616e30000004000e69665f6f6374"
+    "65747300000600180002020200000000100a042300000000011cfa460008000c1513676d43"
+    "8c5f100002000764660000030009626f6f74000004000f64665f636f6d706c657800000500"
+    "0966726565000006000f0001010000000010e198410008000c1513676d438c689c0005000d"
+    "7265736572766564000006000f00010100000000804c68410008000c1513676d438c70ce00"
+    "05000975736564000006000f0001010000000020ea9e410008000c1513676d438be7bc0002"
+    "000e696e74657266616365000003000c646f636b657230000004000e69665f6572726f7273"
+    "0000050005000006001800020202000000000000000000000000000000000008000c151367"
+    "6d43beca8c0002000c656e74726f70790000030005000004000c656e74726f707900000600"
+    "0f0001010000000000088f400008000c1513676d43bf1d13000200096c6f61640000040009"
+    "6c6f6164000006002100030101019a9999999999a93f666666666666d63f5c8fc2f5285cdf"
+    "3f0008000c1513676d43c02b85000200096469736b00000300087364610000040010646973"
+    "6b5f6f63746574730000060018000202020000000075887800000000005b6f3c000008000c"
+    "1513676d43c06d1f0004000d6469736b5f6f7073000006001800020202000000000003cbbd"
+    "000000000001c0510008000c1513676d43c08b6a0004000e6469736b5f74696d6500000600"
+    "1800020202000000000000003f00000000000001720008000c1513676d43c0a5fb00040010"
+    "6469736b5f6d65726765640000060018000202020000000000001285000000000000f80100"
+    "08000c1513676d43c0c8b4000300097364613100000400106469736b5f6f63746574730000"
+    "060018000202020000000001107c000000000000003c00",
+    "0000000e6c6f63616c686f7374000008000c1513676d43c0d00a0009000c00000002800000"
+    "00000200096469736b000003000973646131000004000d6469736b5f6f7073000006001800"
+    "020202000000000000029b00000000000000080008000c1513676d43c0d7b20004000e6469"
+    "736b5f74696d650000060018000202020000000000000004000000000000000f0008000c15"
+    "13676d43c0df73000400106469736b5f6d6572676564000006001800020202000000000000"
+    "0fb400000000000000010008000c1513676d43c0f87c000300097364613200000400106469"
+    "736b5f6f637465747300000600180002020200000000000008000000000000000000000800"
+    "0c1513676d43c1003e0004000d6469736b5f6f707300000600180002020200000000000000"
+    "0200000000000000000008000c1513676d43c107bf000400106469736b5f6d657267656400"
+    "0006001800020202000000000000000000000000000000000008000c1513676d43c12fa400"
+    "03000973646135000004000d6469736b5f6f7073000006001800020202000000000003c867"
+    "000000000001aef20008000c1513676d43c13d5e000400106469736b5f6d65726765640000"
+    "0600180002020200000000000002d1000000000000f8000008000c1513676d43c136a90004"
+    "000e6469736b5f74696d65000006001800020202000000000000003f000000000000011c00"
+    "08000c1513676d43c1740500030009646d2d3000000400106469736b5f6f63746574730000"
+    "060018000202020000000074596400000000005b6f00000008000c1513676d43c179c70004"
+    "000d6469736b5f6f7073000006001800020202000000000003cae4000000000002b0f30008"
+    "000c1513676d43c18abe000400106469736b5f6d6572676564000006001800020202000000"
+    "000000000000000000000000000008000c1513676d43c181b90004000e6469736b5f74696d"
+    "650000060018000202020000000000000040000000000000013e0008000c1513676d43c1a9"
+    "5e00030009646d2d3100000400106469736b5f6f6374657473000006001800020202000000"
+    "00000e000000000000000000000008000c1513676d43c1b7ea0004000e6469736b5f74696d"
+    "65000006001800020202000000000000000200000000000000000008000c1513676d43c1b0"
+    "3e0004000d6469736b5f6f707300000600180002020200000000000000e000000000000000"
+    "000008000c1513676d43c1c00d000400106469736b5f6d6572676564000006001800020202"
+    "000000000000000000000000000000000008000c1513676d43c12818000300097364613500"
+    "000400106469736b5f6f637465747300000600180002020200000000746c6400000000005b"
+    "6f00000008000c1513676d43d320a80002000c62617474657279000003000630000004000b"
+    "636861726765000006000f0001018fc2f5285c2f58400008000c1513676d43d36fd6000400"
+    "0c63757272656e74000006000f00010100000000000000800008000c1513676d43d3cdb600"
+    "04000c766f6c74616765000006000f000101736891ed7cbf28400008000c1513676d43d59d"
+    "d60002000869727100000300050000040008697271000005000630000006000f0001020000"
+    "0000000000110008000c1513676d43d5d2cf0005000631000006000f000102000000000000"
+    "00100008000c1513676d43d5fe820005000638000006000f00010200000000000000010008"
+    "000c1513676d43d635440005000639000006000f00010200000000000035210008000c1513"
+    "676d43d66265000500073132000006000f0001020000000000000790",
+    "0000000e6c6f63616c686f7374000008000c1513676d43d68e940009000c00000002800000"
+    "0000020008697271000004000869727100000500073136000006000f000102000000000000"
+    "00210008000c1513676d43d69be20002000a7573657273000004000a757365727300000500"
+    "05000006000f00010100000000000010400008000c1513676d43d6aa5d0002000869727100"
+    "0004000869727100000500073233000006000f00010200000000000000250008000c151367"
+    "6d43d6c7dc000500073431000006000f000102000000000000ff7d0008000c1513676d43d6"
+    "e23d000500073432000006000f00010200000000000008070008000c1513676d43d9aa3a00"
+    "0500073437000006000f0001020000000000079a260008000c1513676d43d9cca900050007"
+    "3438000006000f00010200000000000000c70008000c1513676d43d9ea5d00050007343900"
+    "0006000f00010200000000000004c20008000c1513676d43da050e00050007353000000600"
+    "0f000102000000000000001c0008000c1513676d43da1efa000500084e4d49000006000f00"
+    "010200000000000000000008000c1513676d43da3c82000500084c4f43000006000f000102"
+    "000000000018d3080008000c1513676d43da544e00050008535055000006000f0001020000"
+    "0000000000000008000c1513676d43da6cca00050008504d49000006000f00010200000000"
+    "000000000008000c1513676d43da885400050008495749000006000f000102000000000000"
+    "a9da0008000c1513676d43daa23a00050008525452000006000f0001020000000000000003"
+    "0008000c1513676d43dabaed00050008524553000006000f00010200000000000ac8360008"
+    "000c1513676d43dad4150005000843414c000006000f000102000000000000191f0008000c"
+    "1513676d43daeef300050008544c42000006000f000102000000000003dbdc0008000c1513"
+    "676d43db11410005000854524d000006000f00010200000000000000000008000c1513676d"
+    "43db292c00050008544852000006000f00010200000000000000000008000c1513676d43db"
+    "411d000500084d4345000006000f00010200000000000000000008000c1513676d43db5b59"
+    "000500084d4350000006000f000102000000000000003c0008000c1513676d43db68010005"
+    "0008455252000006000f00010200000000000000000008000c1513676d43db758a00050008"
+    "4d4953000006000f00010200000000000000000008000c1513676d43dd2e800002000b6d65"
+    "6d6f7279000004000b6d656d6f7279000005000975736564000006000f00010100000000fe"
+    "bbe0410008000c1513676d43dd3f4b0005000d6275666665726564000006000f0001010000"
+    "000070fbc8410008000c1513676d43dd48700005000b636163686564000006000f00010100"
+    "000000c008df410008000c1513676d43dd51c60005000966726565000006000f0001010000"
+    "0080481d05420008000c1513676d43dec7e300020009737761700000040009737761700000"
+    "05000975736564000006000f00010100000000000000000008000c1513676d43ded4490005"
+    "000966726565000006000f00010100000080ff610f420008000c1513676d43dedcfd000500"
+    "0b636163686564000006000f00010100000000000000000008000c1513676d43d715e30002"
+    "0008697271000004000869727100000500073434000006000f0001020000000000031b6100"
+    "08000c1513676d43d73116000500073435000006000f00010200000000000000180008000c"
+    "1513676d43ee00150002000973776170000004000c737761705f696f0000050007696e0000"
+    "06000f0001020000000000000000",
+};
+
+static int decode_string(char const *in, uint8_t *out, size_t *out_size) {
+  size_t in_size = strlen(in);
+  if (*out_size < (in_size / 2))
+    return -1;
+  *out_size = in_size / 2;
+
+  for (size_t i = 0; i < *out_size; i++) {
+    char tmp[] = {in[2 * i], in[2 * i + 1], 0};
+    out[i] = (uint8_t)strtoul(tmp, NULL, 16);
+  }
+
+  return 0;
+}
+
+DEF_TEST(parse_packet) {
+  sockent_t se = {
+      .data.server =
+          (struct sockent_server){
+#if HAVE_GCRYPT_H
+              .cypher = NULL,
+              .userdb = NULL,
+              .security_level = SECURITY_LEVEL_NONE,
+#endif
+          },
+  };
+
+  for (size_t i = 0; i < sizeof(raw_packet_data) / sizeof(raw_packet_data[0]);
+       i++) {
+    uint8_t buffer[network_config_packet_size];
+    size_t buffer_size = sizeof(buffer);
+
+    EXPECT_EQ_INT(0, decode_string(raw_packet_data[i], buffer, &buffer_size));
+    EXPECT_EQ_INT(0, parse_packet(&se, buffer, buffer_size, 0, NULL));
+  }
+  EXPECT_EQ_INT(139, (int)stats_values_dispatched);
+
+  return 0;
+}
+
+int main() {
+  RUN_TEST(parse_packet);
+
+  END_TEST;
+}
index a5b45a1..9079719 100644 (file)
@@ -89,48 +89,48 @@ all-outqueries        counts the number of outgoing UDP queries since starting
 answers-slow          counts the number of queries answered after 1 second
 answers0-1            counts the number of queries answered within 1 millisecond
 answers1-10           counts the number of queries answered within 10
-milliseconds
+                      milliseconds
 answers10-100         counts the number of queries answered within 100
-milliseconds
+                      milliseconds
 answers100-1000       counts the number of queries answered within 1 second
 cache-bytes           size of the cache in bytes (since 3.3.1)
 cache-entries         shows the number of entries in the cache
 cache-hits            counts the number of cache hits since starting, this does
-not include hits that got answered from the packet-cache
+                      not include hits that got answered from the packet-cache
 cache-misses          counts the number of cache misses since starting
 case-mismatches       counts the number of mismatches in character case since
-starting
+                      starting
 chain-resends         number of queries chained to existing outstanding query
 client-parse-errors   counts number of client packets that could not be parsed
 concurrent-queries    shows the number of MThreads currently running
 dlg-only-drops        number of records dropped because of delegation only
-setting
+                      setting
 dont-outqueries       number of outgoing queries dropped because of 'dont-query'
-setting (since 3.3)
+                      setting (since 3.3)
 edns-ping-matches     number of servers that sent a valid EDNS PING respons
 edns-ping-mismatches  number of servers that sent an invalid EDNS PING response
 failed-host-entries   number of servers that failed to resolve
 ipv6-outqueries       number of outgoing queries over IPv6
 ipv6-questions        counts all End-user initiated queries with the RD bit set,
-received over IPv6 UDP
+                      received over IPv6 UDP
 malloc-bytes          returns the number of bytes allocated by the process
-(broken, always returns 0)
+                      (broken, always returns 0)
 max-mthread-stack     maximum amount of thread stack ever used
 negcache-entries      shows the number of entries in the Negative answer cache
 no-packet-error       number of errorneous received packets
 noedns-outqueries     number of queries sent out without EDNS
 noerror-answers       counts the number of times it answered NOERROR since
-starting
+                      starting
 noping-outqueries     number of queries sent out without ENDS PING
 nsset-invalidations   number of times an nsset was dropped because it no longer
-worked
+                      worked
 nsspeeds-entries      shows the number of entries in the NS speeds map
 nxdomain-answers      counts the number of times it answered NXDOMAIN since
-starting
+                      starting
 outgoing-timeouts     counts the number of timeouts on outgoing UDP queries
-since starting
+                      since starting
 over-capacity-drops   questions dropped because over maximum concurrent query
-limit (since 3.2)
+                      limit (since 3.2)
 packetcache-bytes     size of the packet cache in bytes (since 3.3.1)
 packetcache-entries   size of packet cache (since 3.2)
 packetcache-hits      packet cache hits (since 3.2)
@@ -139,32 +139,32 @@ policy-drops          packets dropped because of (Lua) policy decision
 qa-latency            shows the current latency average
 questions             counts all end-user initiated queries with the RD bit set
 resource-limits       counts number of queries that could not be performed
-because of resource limits
+                      because of resource limits
 security-status       security status based on security polling
 server-parse-errors   counts number of server replied packets that could not be
-parsed
+                      parsed
 servfail-answers      counts the number of times it answered SERVFAIL since
-starting
+                      starting
 spoof-prevents        number of times PowerDNS considered itself spoofed, and
-dropped the data
+                      dropped the data
 sys-msec              number of CPU milliseconds spent in 'system' mode
 tcp-client-overflow   number of times an IP address was denied TCP access
-because it already had too many connections
+                      because it already had too many connections
 tcp-clients           counts the number of currently active TCP/IP clients
 tcp-outqueries        counts the number of outgoing TCP queries since starting
 tcp-questions         counts all incoming TCP queries (since starting)
 throttle-entries      shows the number of entries in the throttle map
 throttled-out         counts the number of throttled outgoing UDP queries since
-starting
+                      starting
 throttled-outqueries  idem to throttled-out
 unauthorized-tcp      number of TCP questions denied because of allow-from
-restrictions
+                      restrictions
 unauthorized-udp      number of UDP questions denied because of allow-from
-restrictions
+                      restrictions
 unexpected-packets    number of answers from remote servers that were unexpected
-(might point to spoofing)
+                      (might point to spoofing)
 unreachables          number of times nameservers were unreachable since
-starting
+                      starting
 uptime                number of seconds process has been running (since 3.1.5)
 user-msec             number of CPU milliseconds spent in 'user' mode
 }}} */
@@ -301,6 +301,7 @@ static statname_lookup_t lookup_table[] = /* {{{ */
         {"unauthorized-tcp", "counter", "denied-unauthorized_tcp"},
         {"unauthorized-udp", "counter", "denied-unauthorized_udp"},
         {"unexpected-packets", "dns_answer", "unexpected"},
+        {"unreachables", "counter", "unreachables"},
         {"uptime", "uptime", NULL}}; /* }}} */
 static int lookup_table_length = STATIC_ARRAY_SIZE(lookup_table);
 
diff --git a/src/procevent.c b/src/procevent.c
new file mode 100644 (file)
index 0000000..ab000db
--- /dev/null
@@ -0,0 +1,1303 @@
+/**
+ * collectd - src/procevent.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 <abays at redhat.com>
+ **/
+
+#include "collectd.h"
+
+#include "plugin.h"
+#include "utils/common/common.h"
+#include "utils/ignorelist/ignorelist.h"
+#include "utils_complain.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <dirent.h>
+#include <linux/cn_proc.h>
+#include <linux/connector.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_gen.h>
+#if HAVE_YAJL_YAJL_VERSION_H
+#include <yajl/yajl_version.h>
+#endif
+#if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
+#define HAVE_YAJL_V2 1
+#endif
+
+#define PROCEVENT_EXITED 0
+#define PROCEVENT_STARTED 1
+#define PROCEVENT_FIELDS 3 // pid, status, timestamp
+#define BUFSIZE 512
+#define PROCDIR "/proc"
+#define RBUF_PROC_ID_INDEX 0
+#define RBUF_PROC_STATUS_INDEX 1
+#define RBUF_TIME_INDEX 2
+
+#define PROCEVENT_DOMAIN_FIELD "domain"
+#define PROCEVENT_DOMAIN_VALUE "fault"
+#define PROCEVENT_EVENT_ID_FIELD "eventId"
+#define PROCEVENT_EVENT_NAME_FIELD "eventName"
+#define PROCEVENT_EVENT_NAME_DOWN_VALUE "down"
+#define PROCEVENT_EVENT_NAME_UP_VALUE "up"
+#define PROCEVENT_LAST_EPOCH_MICROSEC_FIELD "lastEpochMicrosec"
+#define PROCEVENT_PRIORITY_FIELD "priority"
+#define PROCEVENT_PRIORITY_VALUE "high"
+#define PROCEVENT_REPORTING_ENTITY_NAME_FIELD "reportingEntityName"
+#define PROCEVENT_REPORTING_ENTITY_NAME_VALUE "collectd procevent plugin"
+#define PROCEVENT_SEQUENCE_FIELD "sequence"
+#define PROCEVENT_SEQUENCE_VALUE "0"
+#define PROCEVENT_SOURCE_NAME_FIELD "sourceName"
+#define PROCEVENT_START_EPOCH_MICROSEC_FIELD "startEpochMicrosec"
+#define PROCEVENT_VERSION_FIELD "version"
+#define PROCEVENT_VERSION_VALUE "1.0"
+
+#define PROCEVENT_ALARM_CONDITION_FIELD "alarmCondition"
+#define PROCEVENT_ALARM_INTERFACE_A_FIELD "alarmInterfaceA"
+#define PROCEVENT_EVENT_SEVERITY_FIELD "eventSeverity"
+#define PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE "CRITICAL"
+#define PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE "NORMAL"
+#define PROCEVENT_EVENT_SOURCE_TYPE_FIELD "eventSourceType"
+#define PROCEVENT_EVENT_SOURCE_TYPE_VALUE "process"
+#define PROCEVENT_FAULT_FIELDS_FIELD "faultFields"
+#define PROCEVENT_FAULT_FIELDS_VERSION_FIELD "faultFieldsVersion"
+#define PROCEVENT_FAULT_FIELDS_VERSION_VALUE "1.0"
+#define PROCEVENT_SPECIFIC_PROBLEM_FIELD "specificProblem"
+#define PROCEVENT_SPECIFIC_PROBLEM_DOWN_VALUE "down"
+#define PROCEVENT_SPECIFIC_PROBLEM_UP_VALUE "up"
+#define PROCEVENT_VF_STATUS_FIELD "vfStatus"
+#define PROCEVENT_VF_STATUS_CRITICAL_VALUE "Ready to terminate"
+#define PROCEVENT_VF_STATUS_NORMAL_VALUE "Active"
+
+/*
+ * Private data types
+ */
+
+typedef struct {
+  int head;
+  int tail;
+  int maxLen;
+  cdtime_t **buffer;
+} circbuf_t;
+
+struct processlist_s {
+  char *process;
+
+  long pid;
+  int32_t last_status;
+
+  struct processlist_s *next;
+};
+typedef struct processlist_s processlist_t;
+
+/*
+ * Private variables
+ */
+static ignorelist_t *ignorelist = NULL;
+
+static int procevent_netlink_thread_loop = 0;
+static int procevent_netlink_thread_error = 0;
+static pthread_t procevent_netlink_thread_id;
+static int procevent_dequeue_thread_loop = 0;
+static pthread_t procevent_dequeue_thread_id;
+static pthread_mutex_t procevent_thread_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t procevent_data_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t procevent_cond = PTHREAD_COND_INITIALIZER;
+static int nl_sock = -1;
+static int buffer_length;
+static circbuf_t ring;
+static processlist_t *processlist_head = NULL;
+static int event_id = 0;
+
+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, long pid, char *process,
+                               cdtime_t timestamp, char **buf) {
+  const unsigned char *buf2;
+  yajl_gen g;
+  char json_str[DATA_MAX_NAME_LEN];
+
+#if !defined(HAVE_YAJL_V2)
+  yajl_gen_config conf = {0};
+#endif
+
+#if HAVE_YAJL_V2
+  size_t len;
+  g = yajl_gen_alloc(NULL);
+  yajl_gen_config(g, yajl_gen_beautify, 0);
+#else
+  unsigned int len;
+  g = yajl_gen_alloc(&conf, NULL);
+#endif
+
+  yajl_gen_clear(g);
+
+  // *** BEGIN common event header ***
+
+  if (yajl_gen_map_open(g) != yajl_gen_status_ok)
+    goto err;
+
+  // domain
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_DOMAIN_FIELD,
+                      strlen(PROCEVENT_DOMAIN_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_DOMAIN_VALUE,
+                      strlen(PROCEVENT_DOMAIN_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // eventId
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_ID_FIELD,
+                      strlen(PROCEVENT_EVENT_ID_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  event_id = event_id + 1;
+  if (snprintf(json_str, sizeof(json_str), "%d", event_id) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // eventName
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_NAME_FIELD,
+                      strlen(PROCEVENT_EVENT_NAME_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (snprintf(json_str, sizeof(json_str), "process %s (%ld) %s", process, pid,
+               (state == 0 ? PROCEVENT_EVENT_NAME_DOWN_VALUE
+                           : PROCEVENT_EVENT_NAME_UP_VALUE)) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
+      yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // lastEpochMicrosec
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_LAST_EPOCH_MICROSEC_FIELD,
+                      strlen(PROCEVENT_LAST_EPOCH_MICROSEC_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (snprintf(json_str, sizeof(json_str), "%" PRIu64,
+               CDTIME_T_TO_US(cdtime())) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // priority
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_PRIORITY_FIELD,
+                      strlen(PROCEVENT_PRIORITY_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_PRIORITY_VALUE,
+                      strlen(PROCEVENT_PRIORITY_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // reportingEntityName
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_REPORTING_ENTITY_NAME_FIELD,
+                      strlen(PROCEVENT_REPORTING_ENTITY_NAME_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_REPORTING_ENTITY_NAME_VALUE,
+                      strlen(PROCEVENT_REPORTING_ENTITY_NAME_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // sequence
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_SEQUENCE_FIELD,
+                      strlen(PROCEVENT_SEQUENCE_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, PROCEVENT_SEQUENCE_VALUE,
+                      strlen(PROCEVENT_SEQUENCE_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // sourceName
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_SOURCE_NAME_FIELD,
+                      strlen(PROCEVENT_SOURCE_NAME_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)process, strlen(process)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // startEpochMicrosec
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_START_EPOCH_MICROSEC_FIELD,
+                      strlen(PROCEVENT_START_EPOCH_MICROSEC_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (snprintf(json_str, sizeof(json_str), "%" PRIu64,
+               CDTIME_T_TO_US(timestamp)) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // version
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_VERSION_FIELD,
+                      strlen(PROCEVENT_VERSION_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, PROCEVENT_VERSION_VALUE,
+                      strlen(PROCEVENT_VERSION_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // *** END common event header ***
+
+  // *** BEGIN fault fields ***
+
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_FAULT_FIELDS_FIELD,
+                      strlen(PROCEVENT_FAULT_FIELDS_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_map_open(g) != yajl_gen_status_ok)
+    goto err;
+
+  // alarmCondition
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_ALARM_CONDITION_FIELD,
+                      strlen(PROCEVENT_ALARM_CONDITION_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (snprintf(json_str, sizeof(json_str), "process %s (%ld) state change",
+               process, pid) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
+      yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // alarmInterfaceA
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_ALARM_INTERFACE_A_FIELD,
+                      strlen(PROCEVENT_ALARM_INTERFACE_A_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)process, strlen(process)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // eventSeverity
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SEVERITY_FIELD,
+                      strlen(PROCEVENT_EVENT_SEVERITY_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(
+          g,
+          (u_char *)(state == 0 ? PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE
+                                : PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE),
+          strlen((state == 0 ? PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE
+                             : PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE))) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // eventSourceType
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SOURCE_TYPE_FIELD,
+                      strlen(PROCEVENT_EVENT_SOURCE_TYPE_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SOURCE_TYPE_VALUE,
+                      strlen(PROCEVENT_EVENT_SOURCE_TYPE_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // faultFieldsVersion
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_FAULT_FIELDS_VERSION_FIELD,
+                      strlen(PROCEVENT_FAULT_FIELDS_VERSION_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, PROCEVENT_FAULT_FIELDS_VERSION_VALUE,
+                      strlen(PROCEVENT_FAULT_FIELDS_VERSION_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // specificProblem
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_SPECIFIC_PROBLEM_FIELD,
+                      strlen(PROCEVENT_SPECIFIC_PROBLEM_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (snprintf(json_str, sizeof(json_str), "process %s (%ld) %s", process, pid,
+               (state == 0 ? PROCEVENT_SPECIFIC_PROBLEM_DOWN_VALUE
+                           : PROCEVENT_SPECIFIC_PROBLEM_UP_VALUE)) < 0) {
+    goto err;
+  }
+
+  if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
+      yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // vfStatus
+  if (yajl_gen_string(g, (u_char *)PROCEVENT_VF_STATUS_FIELD,
+                      strlen(PROCEVENT_VF_STATUS_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(
+          g,
+          (u_char *)(state == 0 ? PROCEVENT_VF_STATUS_CRITICAL_VALUE
+                                : PROCEVENT_VF_STATUS_NORMAL_VALUE),
+          strlen((state == 0 ? PROCEVENT_VF_STATUS_CRITICAL_VALUE
+                             : PROCEVENT_VF_STATUS_NORMAL_VALUE))) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // *** END fault fields ***
+
+  // close fault and header fields
+  if (yajl_gen_map_close(g) != yajl_gen_status_ok ||
+      yajl_gen_map_close(g) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_get_buf(g, &buf2, &len) != yajl_gen_status_ok)
+    goto err;
+
+  *buf = strdup((char *)buf2);
+
+  if (*buf == NULL) {
+    ERROR("procevent plugin: strdup failed during gen_message_payload: %s",
+          STRERRNO);
+    goto err;
+  }
+
+  yajl_gen_free(g);
+
+  return 0;
+
+err:
+  yajl_gen_free(g);
+  ERROR("procevent plugin: gen_message_payload failed to generate JSON");
+  return -1;
+}
+
+// Does /proc/<pid>/comm contain a process name we are interested in?
+// NOTE: Caller MUST hold procevent_data_lock when calling this function
+static processlist_t *process_check(long pid) {
+  char file[BUFSIZE];
+
+  int len = snprintf(file, sizeof(file), PROCDIR "/%ld/comm", pid);
+
+  if ((len < 0) || (len >= BUFSIZE)) {
+    WARNING("procevent process_check: process name too large");
+    return NULL;
+  }
+
+  FILE *fh;
+
+  if (NULL == (fh = fopen(file, "r"))) {
+    // No /proc/<pid>/comm for this pid, just ignore
+    DEBUG("procevent plugin: no comm file available for pid %ld", pid);
+    return NULL;
+  }
+
+  char buffer[BUFSIZE];
+  int retval = fscanf(fh, "%[^\n]", buffer);
+
+  if (retval < 0) {
+    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> (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
+  //    associate <pid> with it (with the same process name as the existing).
+  //
+
+  processlist_t *match = NULL;
+
+  for (processlist_t *pl = processlist_head; pl != NULL; pl = pl->next) {
+
+    int is_match = (strcmp(buffer, pl->process) == 0 ? 1 : 0);
+
+    if (is_match == 1) {
+      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;
+      } else if (pl->pid != -1) {
+        // 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 != 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 %ld "
+          "(%s)",
+          pid, buffer);
+
+    processlist_t *pl2 = calloc(1, sizeof(*pl2));
+    if (pl2 == NULL) {
+      ERROR("procevent plugin: calloc failed during process_check: %s",
+            STRERRNO);
+      return NULL;
+    }
+
+    char *process = strdup(buffer);
+    if (process == NULL) {
+      sfree(pl2);
+      ERROR("procevent plugin: strdup failed during process_check: %s",
+            STRERRNO);
+      return NULL;
+    }
+
+    pl2->process = process;
+    pl2->pid = pid;
+    pl2->next = processlist_head;
+    processlist_head = pl2;
+
+    match = pl2;
+  }
+
+  return match;
+}
+
+// Does our map have this PID or name?
+// NOTE: Caller MUST hold procevent_data_lock when calling this function
+static processlist_t *process_map_check(long pid, char *process) {
+  for (processlist_t *pl = processlist_head; pl != NULL; pl = pl->next) {
+    int match_pid = 0;
+
+    if (pid > 0) {
+      if (pl->pid == pid)
+        match_pid = 1;
+    }
+
+    int match_process = 0;
+
+    if (process != NULL) {
+      if (strcmp(pl->process, process) == 0)
+        match_process = 1;
+    }
+
+    int match = 0;
+
+    if ((pid > 0 && process == NULL && match_pid == 1) ||
+        (pid < 0 && process != NULL && match_process == 1) ||
+        (pid > 0 && process != NULL && match_pid == 1 && match_process == 1)) {
+      match = 1;
+    }
+
+    if (match == 1) {
+      return pl;
+    }
+  }
+
+  return NULL;
+}
+
+static int process_map_refresh(void) {
+  errno = 0;
+  DIR *proc = opendir(PROCDIR);
+
+  if (proc == NULL) {
+    ERROR("procevent plugin: fopen (%s): %s", PROCDIR, STRERRNO);
+    return -1;
+  }
+
+  while (42) {
+    errno = 0;
+    struct dirent *dent = readdir(proc);
+    if (dent == NULL) {
+      if (errno == 0) /* end of directory */
+        break;
+
+      ERROR("procevent plugin: failed to read directory %s: %s", PROCDIR,
+            STRERRNO);
+      closedir(proc);
+      return -1;
+    }
+
+    if (dent->d_name[0] == '.')
+      continue;
+
+    char file[BUFSIZE];
+
+    int len = snprintf(file, sizeof(file), PROCDIR "/%s", dent->d_name);
+    if ((len < 0) || (len >= BUFSIZE))
+      continue;
+
+    struct stat statbuf;
+
+    int status = stat(file, &statbuf);
+    if (status != 0) {
+      WARNING("procevent plugin: stat (%s) failed: %s", file, STRERRNO);
+      continue;
+    }
+
+    if (!S_ISDIR(statbuf.st_mode))
+      continue;
+
+    len = snprintf(file, sizeof(file), PROCDIR "/%s/comm", dent->d_name);
+    if ((len < 0) || (len >= BUFSIZE))
+      continue;
+
+    int not_number = 0;
+
+    for (int i = 0; i < strlen(dent->d_name); i++) {
+      if (!isdigit(dent->d_name[i])) {
+        not_number = 1;
+        break;
+      }
+    }
+
+    if (not_number != 0)
+      continue;
+
+    // Check if we need to store this pid/name combo in our processlist_t linked
+    // list
+    int this_pid = atoi(dent->d_name);
+    pthread_mutex_lock(&procevent_data_lock);
+    processlist_t *pl = process_check(this_pid);
+    pthread_mutex_unlock(&procevent_data_lock);
+
+    if (pl != NULL)
+      DEBUG("procevent plugin: process map refreshed for PID %d and name %s",
+            this_pid, pl->process);
+  }
+
+  closedir(proc);
+
+  return 0;
+}
+
+static int nl_connect() {
+  struct sockaddr_nl sa_nl = {
+      .nl_family = AF_NETLINK,
+      .nl_groups = CN_IDX_PROC,
+      .nl_pid = getpid(),
+  };
+
+  nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
+  if (nl_sock == -1) {
+    ERROR("procevent plugin: socket open failed: %d", errno);
+    return -1;
+  }
+
+  int rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
+  if (rc == -1) {
+    ERROR("procevent plugin: socket bind failed: %d", errno);
+    close(nl_sock);
+    nl_sock = -1;
+    return -1;
+  }
+
+  return 0;
+}
+
+static int set_proc_ev_listen(bool enable) {
+  struct __attribute__((aligned(NLMSG_ALIGNTO))) {
+    struct nlmsghdr nl_hdr;
+    struct __attribute__((__packed__)) {
+      struct cn_msg cn_msg;
+      enum proc_cn_mcast_op cn_mcast;
+    };
+  } nlcn_msg;
+
+  memset(&nlcn_msg, 0, sizeof(nlcn_msg));
+  nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg);
+  nlcn_msg.nl_hdr.nlmsg_pid = getpid();
+  nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE;
+
+  nlcn_msg.cn_msg.id.idx = CN_IDX_PROC;
+  nlcn_msg.cn_msg.id.val = CN_VAL_PROC;
+  nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op);
+
+  nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;
+
+  int rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
+  if (rc == -1) {
+    ERROR("procevent plugin: subscribing to netlink process events failed: %d",
+          errno);
+    return -1;
+  }
+
+  return 0;
+}
+
+// Read from netlink socket and write to ring buffer
+static int read_event() {
+  int recv_flags = MSG_DONTWAIT;
+  struct __attribute__((aligned(NLMSG_ALIGNTO))) {
+    struct nlmsghdr nl_hdr;
+    struct __attribute__((__packed__)) {
+      struct cn_msg cn_msg;
+      struct proc_event proc_ev;
+    };
+  } nlcn_msg;
+
+  if (nl_sock == -1)
+    return 0;
+
+  while (42) {
+    pthread_mutex_lock(&procevent_thread_lock);
+
+    if (procevent_netlink_thread_loop <= 0) {
+      pthread_mutex_unlock(&procevent_thread_lock);
+      return 0;
+    }
+
+    pthread_mutex_unlock(&procevent_thread_lock);
+
+    int status = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), recv_flags);
+
+    if (status == 0) {
+      return 0;
+    } else if (status < 0) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        pthread_mutex_lock(&procevent_data_lock);
+
+        // There was nothing more to receive for now, so...
+        // If ring head does not equal ring tail, then there is data
+        // in the ring buffer for the dequeue thread to read, so
+        // signal it
+        if (ring.head != ring.tail)
+          pthread_cond_signal(&procevent_cond);
+
+        pthread_mutex_unlock(&procevent_data_lock);
+
+        // Since there was nothing to receive, set recv to block and
+        // try again
+        recv_flags = 0;
+        continue;
+      } else if (errno != EINTR) {
+        ERROR("procevent plugin: socket receive error: %d", errno);
+        return -1;
+      } else {
+        // Interrupt, so just continue and try again
+        continue;
+      }
+    }
+
+    // We successfully received a message, so don't block on the next
+    // read in case there are more (and if there aren't, it will be
+    // handled above in the EWOULDBLOCK error-checking)
+    recv_flags = MSG_DONTWAIT;
+
+    int proc_id = -1;
+    int proc_status = -1;
+
+    switch (nlcn_msg.proc_ev.what) {
+    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;
+      break;
+    default:
+      // Otherwise not of interest
+      break;
+    }
+
+    // If we're interested in this process status event, place the event
+    // in the ring buffer for consumption by the dequeue (dispatch) thread.
+
+    if (proc_status != -1) {
+      pthread_mutex_lock(&procevent_data_lock);
+
+      int next = ring.head + 1;
+      if (next >= ring.maxLen)
+        next = 0;
+
+      if (next == ring.tail) {
+        // Buffer is full, signal the dequeue thread to process the buffer
+        // and clean it out, and then sleep
+        WARNING("procevent plugin: ring buffer full");
+
+        pthread_cond_signal(&procevent_cond);
+        pthread_mutex_unlock(&procevent_data_lock);
+
+        usleep(1000);
+        continue;
+      } else {
+        DEBUG("procevent plugin: Process %d status is now %s at %llu", proc_id,
+              (proc_status == PROCEVENT_EXITED ? "EXITED" : "STARTED"),
+              (unsigned long long)cdtime());
+
+        ring.buffer[ring.head][RBUF_PROC_ID_INDEX] = proc_id;
+        ring.buffer[ring.head][RBUF_PROC_STATUS_INDEX] = proc_status;
+        ring.buffer[ring.head][RBUF_TIME_INDEX] = cdtime();
+
+        ring.head = next;
+      }
+
+      pthread_mutex_unlock(&procevent_data_lock);
+    }
+  }
+
+  return 0;
+}
+
+static void procevent_dispatch_notification(long pid, gauge_t value,
+                                            char *process, cdtime_t timestamp) {
+
+  notification_t n = {
+      .severity = (value == 1 ? NOTIF_OKAY : NOTIF_FAILURE),
+      .time = cdtime(),
+      .plugin = "procevent",
+      .type = "gauge",
+      .type_instance = "process_status",
+  };
+
+  sstrncpy(n.host, hostname_g, sizeof(n.host));
+  sstrncpy(n.plugin_instance, process, sizeof(n.plugin_instance));
+
+  char *buf = NULL;
+  gen_message_payload(value, pid, process, timestamp, &buf);
+
+  int status = plugin_notification_meta_add_string(&n, "ves", buf);
+
+  if (status < 0) {
+    sfree(buf);
+    ERROR("procevent plugin: unable to set notification VES metadata: %s",
+          STRERRNO);
+    return;
+  }
+
+  DEBUG("procevent plugin: notification VES metadata: %s",
+        n.meta->nm_value.nm_string);
+
+  DEBUG("procevent plugin: dispatching state %d for PID %ld (%s)", (int)value,
+        pid, process);
+
+  plugin_dispatch_notification(&n);
+  plugin_notification_meta_free(n.meta);
+
+  // strdup'd in gen_message_payload
+  if (buf != NULL)
+    sfree(buf);
+}
+
+// Read from ring buffer and dispatch to write plugins
+static void read_ring_buffer() {
+  pthread_mutex_lock(&procevent_data_lock);
+
+  // If there's currently nothing to read from the buffer,
+  // then wait
+  if (ring.head == ring.tail)
+    pthread_cond_wait(&procevent_cond, &procevent_data_lock);
+
+  while (ring.head != ring.tail) {
+    int next = ring.tail + 1;
+
+    if (next >= ring.maxLen)
+      next = 0;
+
+    if (ring.buffer[ring.tail][RBUF_PROC_STATUS_INDEX] == PROCEVENT_EXITED) {
+      processlist_t *pl = process_map_check(ring.buffer[ring.tail][0], NULL);
+
+      if (pl != NULL) {
+        // This process is of interest to us, so publish its EXITED status
+        procevent_dispatch_notification(
+            ring.buffer[ring.tail][RBUF_PROC_ID_INDEX],
+            ring.buffer[ring.tail][RBUF_PROC_STATUS_INDEX], pl->process,
+            ring.buffer[ring.tail][RBUF_TIME_INDEX]);
+        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][RBUF_PROC_STATUS_INDEX] ==
+               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 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][RBUF_PROC_ID_INDEX],
+            ring.buffer[ring.tail][RBUF_PROC_STATUS_INDEX], pl->process,
+            ring.buffer[ring.tail][RBUF_TIME_INDEX]);
+
+        pl->last_status = PROCEVENT_STARTED;
+
+        DEBUG("procevent plugin: PID %ld (%s) STARTED, adding PID to process "
+              "list",
+              pl->pid, pl->process);
+      }
+    }
+
+    ring.tail = next;
+  }
+
+  pthread_mutex_unlock(&procevent_data_lock);
+}
+
+// Entry point for thread responsible for listening
+// to netlink socket and writing data to ring buffer
+static void *procevent_netlink_thread(void *arg) /* {{{ */
+{
+  pthread_mutex_lock(&procevent_thread_lock);
+
+  while (procevent_netlink_thread_loop > 0) {
+    pthread_mutex_unlock(&procevent_thread_lock);
+
+    int status = read_event();
+
+    pthread_mutex_lock(&procevent_thread_lock);
+
+    if (status < 0) {
+      procevent_netlink_thread_error = 1;
+      break;
+    }
+  } /* while (procevent_netlink_thread_loop > 0) */
+
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  return (void *)0;
+} /* }}} void *procevent_netlink_thread */
+
+// Entry point for thread responsible for reading from
+// ring buffer and dispatching notifications
+static void *procevent_dequeue_thread(void *arg) /* {{{ */
+{
+  pthread_mutex_lock(&procevent_thread_lock);
+
+  while (procevent_dequeue_thread_loop > 0) {
+    pthread_mutex_unlock(&procevent_thread_lock);
+
+    read_ring_buffer();
+
+    pthread_mutex_lock(&procevent_thread_lock);
+  } /* while (procevent_dequeue_thread_loop > 0) */
+
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  return (void *)0;
+} /* }}} void *procevent_dequeue_thread */
+
+static int start_netlink_thread(void) /* {{{ */
+{
+  pthread_mutex_lock(&procevent_thread_lock);
+
+  if (procevent_netlink_thread_loop != 0) {
+    pthread_mutex_unlock(&procevent_thread_lock);
+    return 0;
+  }
+
+  int status;
+
+  if (nl_sock == -1) {
+    status = nl_connect();
+
+    if (status != 0) {
+      pthread_mutex_unlock(&procevent_thread_lock);
+      return status;
+    }
+
+    status = set_proc_ev_listen(true);
+    if (status == -1) {
+      pthread_mutex_unlock(&procevent_thread_lock);
+      return status;
+    }
+  }
+
+  DEBUG("procevent plugin: socket created and bound");
+
+  procevent_netlink_thread_loop = 1;
+  procevent_netlink_thread_error = 0;
+
+  status = plugin_thread_create(&procevent_netlink_thread_id, /* attr = */ NULL,
+                                procevent_netlink_thread,
+                                /* arg = */ (void *)0, "procevent");
+  if (status != 0) {
+    procevent_netlink_thread_loop = 0;
+    ERROR("procevent plugin: Starting netlink thread failed.");
+    pthread_mutex_unlock(&procevent_thread_lock);
+
+    int status2 = close(nl_sock);
+
+    if (status2 != 0) {
+      ERROR("procevent plugin: failed to close socket %d: %d (%s)", nl_sock,
+            status2, STRERRNO);
+    }
+
+    nl_sock = -1;
+
+    return -1;
+  }
+
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  return status;
+} /* }}} int start_netlink_thread */
+
+static int start_dequeue_thread(void) /* {{{ */
+{
+  pthread_mutex_lock(&procevent_thread_lock);
+
+  if (procevent_dequeue_thread_loop != 0) {
+    pthread_mutex_unlock(&procevent_thread_lock);
+    return 0;
+  }
+
+  procevent_dequeue_thread_loop = 1;
+
+  int status = plugin_thread_create(&procevent_dequeue_thread_id,
+                                    /* attr = */ NULL, procevent_dequeue_thread,
+                                    /* arg = */ (void *)0, "procevent");
+  if (status != 0) {
+    procevent_dequeue_thread_loop = 0;
+    ERROR("procevent plugin: Starting dequeue thread failed.");
+    pthread_mutex_unlock(&procevent_thread_lock);
+    return -1;
+  }
+
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  return status;
+} /* }}} int start_dequeue_thread */
+
+static int start_threads(void) /* {{{ */
+{
+  int status = start_netlink_thread();
+  int status2 = start_dequeue_thread();
+
+  if (status != 0)
+    return status;
+  else
+    return status2;
+} /* }}} int start_threads */
+
+static int stop_netlink_thread(int shutdown) /* {{{ */
+{
+  int socket_status;
+
+  if (nl_sock != -1) {
+    socket_status = close(nl_sock);
+    if (socket_status != 0) {
+      ERROR("procevent plugin: failed to close socket %d: %d (%s)", nl_sock,
+            socket_status, strerror(errno));
+    }
+
+    nl_sock = -1;
+  } else
+    socket_status = 0;
+
+  pthread_mutex_lock(&procevent_thread_lock);
+
+  if (procevent_netlink_thread_loop == 0) {
+    pthread_mutex_unlock(&procevent_thread_lock);
+    return -1;
+  }
+
+  // Set thread termination status
+  procevent_netlink_thread_loop = 0;
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  // Let threads waiting on access to the data know to move
+  // on such that they'll see the thread's termination status
+  pthread_cond_broadcast(&procevent_cond);
+
+  int thread_status;
+
+  if (shutdown == 1) {
+    // Calling pthread_cancel here in
+    // the case of a shutdown just assures that the thread is
+    // gone and that the process has been fully terminated.
+
+    DEBUG("procevent plugin: Canceling netlink thread for process shutdown");
+
+    thread_status = pthread_cancel(procevent_netlink_thread_id);
+
+    if (thread_status != 0 && thread_status != ESRCH) {
+      ERROR("procevent plugin: Unable to cancel netlink thread: %d",
+            thread_status);
+      thread_status = -1;
+    } else
+      thread_status = 0;
+  } else {
+    thread_status =
+        pthread_join(procevent_netlink_thread_id, /* return = */ NULL);
+    if (thread_status != 0 && thread_status != ESRCH) {
+      ERROR("procevent plugin: Stopping netlink thread failed.");
+      thread_status = -1;
+    } else
+      thread_status = 0;
+  }
+
+  pthread_mutex_lock(&procevent_thread_lock);
+  memset(&procevent_netlink_thread_id, 0, sizeof(procevent_netlink_thread_id));
+  procevent_netlink_thread_error = 0;
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  DEBUG("procevent plugin: Finished requesting stop of netlink thread");
+
+  if (socket_status != 0)
+    return socket_status;
+  else
+    return thread_status;
+} /* }}} int stop_netlink_thread */
+
+static int stop_dequeue_thread() /* {{{ */
+{
+  pthread_mutex_lock(&procevent_thread_lock);
+
+  if (procevent_dequeue_thread_loop == 0) {
+    pthread_mutex_unlock(&procevent_thread_lock);
+    return -1;
+  }
+
+  procevent_dequeue_thread_loop = 0;
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  pthread_cond_broadcast(&procevent_cond);
+
+  // Calling pthread_cancel here just assures that the thread is
+  // gone and that the process has been fully terminated.
+
+  DEBUG("procevent plugin: Canceling dequeue thread for process shutdown");
+
+  int status = pthread_cancel(procevent_dequeue_thread_id);
+
+  if (status != 0 && status != ESRCH) {
+    ERROR("procevent plugin: Unable to cancel dequeue thread: %d", status);
+    status = -1;
+  } else
+    status = 0;
+
+  pthread_mutex_lock(&procevent_thread_lock);
+  memset(&procevent_dequeue_thread_id, 0, sizeof(procevent_dequeue_thread_id));
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  DEBUG("procevent plugin: Finished requesting stop of dequeue thread");
+
+  return status;
+} /* }}} int stop_dequeue_thread */
+
+static int stop_threads() /* {{{ */
+{
+  int status = stop_netlink_thread(1);
+  int status2 = stop_dequeue_thread();
+
+  if (status != 0)
+    return status;
+  else
+    return status2;
+} /* }}} int stop_threads */
+
+static int procevent_init(void) /* {{{ */
+{
+  ring.head = 0;
+  ring.tail = 0;
+  ring.maxLen = buffer_length;
+  ring.buffer = (cdtime_t **)calloc(buffer_length, sizeof(cdtime_t *));
+
+  for (int i = 0; i < buffer_length; i++) {
+    ring.buffer[i] = (cdtime_t *)calloc(PROCEVENT_FIELDS, sizeof(cdtime_t));
+  }
+
+  int status = process_map_refresh();
+
+  if (status == -1) {
+    ERROR("procevent plugin: Initial process mapping failed.");
+    return -1;
+  }
+
+  if (ignorelist == NULL) {
+    NOTICE("procevent plugin: No processes have been configured.");
+    return -1;
+  }
+
+  return start_threads();
+} /* }}} int procevent_init */
+
+static int procevent_config(const char *key, const char *value) /* {{{ */
+{
+  if (ignorelist == NULL)
+    ignorelist = ignorelist_create(/* invert = */ 1);
+
+  if (ignorelist == NULL) {
+    return -1;
+  }
+
+  if (strcasecmp(key, "BufferLength") == 0) {
+    buffer_length = atoi(value);
+  } else if (strcasecmp(key, "Process") == 0) {
+    ignorelist_add(ignorelist, value);
+  } else if (strcasecmp(key, "ProcessRegex") == 0) {
+#if HAVE_REGEX_H
+    int status = ignorelist_add(ignorelist, value);
+
+    if (status != 0) {
+      ERROR("procevent plugin: invalid regular expression: %s", value);
+      return 1;
+    }
+#else
+    WARNING("procevent plugin: The plugin has been compiled without support "
+            "for the \"ProcessRegex\" option.");
+#endif
+  } else {
+    return -1;
+  }
+
+  return 0;
+} /* }}} int procevent_config */
+
+static int procevent_read(void) /* {{{ */
+{
+  pthread_mutex_lock(&procevent_thread_lock);
+
+  if (procevent_netlink_thread_error != 0) {
+
+    pthread_mutex_unlock(&procevent_thread_lock);
+
+    ERROR("procevent plugin: The netlink thread had a problem. Restarting it.");
+
+    stop_netlink_thread(0);
+
+    start_netlink_thread();
+
+    return -1;
+  } /* if (procevent_netlink_thread_error != 0) */
+
+  pthread_mutex_unlock(&procevent_thread_lock);
+
+  return 0;
+} /* }}} int procevent_read */
+
+static int procevent_shutdown(void) /* {{{ */
+{
+  DEBUG("procevent plugin: Shutting down threads.");
+
+  int status = stop_threads();
+
+  for (int i = 0; i < buffer_length; i++) {
+    free(ring.buffer[i]);
+  }
+
+  free(ring.buffer);
+
+  processlist_t *pl = processlist_head;
+  while (pl != NULL) {
+    processlist_t *pl_next;
+
+    pl_next = pl->next;
+
+    sfree(pl->process);
+    sfree(pl);
+
+    pl = pl_next;
+  }
+
+  ignorelist_free(ignorelist);
+
+  return status;
+} /* }}} int procevent_shutdown */
+
+void module_register(void) {
+  plugin_register_config("procevent", procevent_config, config_keys,
+                         config_keys_num);
+  plugin_register_init("procevent", procevent_init);
+  plugin_register_read("procevent", procevent_read);
+  plugin_register_shutdown("procevent", procevent_shutdown);
+} /* void module_register */
diff --git a/src/sysevent.c b/src/sysevent.c
new file mode 100644 (file)
index 0000000..7f9aa9f
--- /dev/null
@@ -0,0 +1,1148 @@
+/**
+ * collectd - src/sysevent.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 <abays at redhat.com>
+ **/
+
+#include "collectd.h"
+
+#include "plugin.h"
+#include "utils/common/common.h"
+#include "utils/ignorelist/ignorelist.h"
+#include "utils_complain.h"
+
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <regex.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_gen.h>
+
+#if HAVE_YAJL_YAJL_VERSION_H
+#include <yajl/yajl_version.h>
+#endif
+#if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
+#include <yajl/yajl_tree.h>
+#define HAVE_YAJL_V2 1
+#endif
+
+#define SYSEVENT_DOMAIN_FIELD "domain"
+#define SYSEVENT_DOMAIN_VALUE "syslog"
+#define SYSEVENT_EVENT_ID_FIELD "eventId"
+#define SYSEVENT_EVENT_NAME_FIELD "eventName"
+#define SYSEVENT_EVENT_NAME_VALUE "syslog message"
+#define SYSEVENT_LAST_EPOCH_MICROSEC_FIELD "lastEpochMicrosec"
+#define SYSEVENT_PRIORITY_FIELD "priority"
+#define SYSEVENT_PRIORITY_VALUE_HIGH "high"
+#define SYSEVENT_PRIORITY_VALUE_LOW "low"
+#define SYSEVENT_PRIORITY_VALUE_MEDIUM "medium"
+#define SYSEVENT_PRIORITY_VALUE_NORMAL "normal"
+#define SYSEVENT_PRIORITY_VALUE_UNKNOWN "unknown"
+#define SYSEVENT_REPORTING_ENTITY_NAME_FIELD "reportingEntityName"
+#define SYSEVENT_REPORTING_ENTITY_NAME_VALUE "collectd sysevent plugin"
+#define SYSEVENT_SEQUENCE_FIELD "sequence"
+#define SYSEVENT_SEQUENCE_VALUE "0"
+#define SYSEVENT_SOURCE_NAME_FIELD "sourceName"
+#define SYSEVENT_SOURCE_NAME_VALUE "syslog"
+#define SYSEVENT_START_EPOCH_MICROSEC_FIELD "startEpochMicrosec"
+#define SYSEVENT_VERSION_FIELD "version"
+#define SYSEVENT_VERSION_VALUE "1.0"
+
+#define SYSEVENT_EVENT_SOURCE_HOST_FIELD "eventSourceHost"
+#define SYSEVENT_EVENT_SOURCE_TYPE_FIELD "eventSourceType"
+#define SYSEVENT_EVENT_SOURCE_TYPE_VALUE "host"
+#define SYSEVENT_SYSLOG_FIELDS_FIELD "syslogFields"
+#define SYSEVENT_SYSLOG_FIELDS_VERSION_FIELD "syslogFieldsVersion"
+#define SYSEVENT_SYSLOG_FIELDS_VERSION_VALUE "1.0"
+#define SYSEVENT_SYSLOG_MSG_FIELD "syslogMsg"
+#define SYSEVENT_SYSLOG_PROC_FIELD "syslogProc"
+#define SYSEVENT_SYSLOG_SEV_FIELD "syslogSev"
+#define SYSEVENT_SYSLOG_TAG_FIELD "syslogTag"
+#define SYSEVENT_SYSLOG_TAG_VALUE "NILVALUE"
+
+/*
+ * Private data types
+ */
+
+typedef struct {
+  int head;
+  int tail;
+  int maxLen;
+  char **buffer;
+  cdtime_t *timestamp;
+} circbuf_t;
+
+/*
+ * Private variables
+ */
+
+static ignorelist_t *ignorelist = NULL;
+
+static int sysevent_socket_thread_loop = 0;
+static int sysevent_socket_thread_error = 0;
+static pthread_t sysevent_socket_thread_id;
+static int sysevent_dequeue_thread_loop = 0;
+static pthread_t sysevent_dequeue_thread_id;
+static pthread_mutex_t sysevent_thread_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t sysevent_data_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t sysevent_cond = PTHREAD_COND_INITIALIZER;
+static int sock = -1;
+static int event_id = 0;
+static circbuf_t ring;
+
+static char *listen_ip;
+static char *listen_port;
+static int listen_buffer_size = 4096;
+static int buffer_length = 10;
+
+static int monitor_all_messages = 1;
+
+#if HAVE_YAJL_V2
+static const char *rsyslog_keys[3] = {"@timestamp", "@source_host", "@message"};
+static const char *rsyslog_field_keys[5] = {
+    "facility", "severity", "severity-num", "program", "processid"};
+#endif
+
+/*
+ * Private functions
+ */
+
+static int gen_message_payload(const char *msg, char *sev, int sev_num,
+                               char *process, char *host, cdtime_t timestamp,
+                               char **buf) {
+  const unsigned char *buf2;
+  yajl_gen g;
+  char json_str[DATA_MAX_NAME_LEN];
+
+#if !defined(HAVE_YAJL_V2)
+  yajl_gen_config conf = {0};
+#endif
+
+#if HAVE_YAJL_V2
+  size_t len;
+  g = yajl_gen_alloc(NULL);
+  yajl_gen_config(g, yajl_gen_beautify, 0);
+#else
+  unsigned int len;
+  g = yajl_gen_alloc(&conf, NULL);
+#endif
+
+  yajl_gen_clear(g);
+
+  // *** BEGIN common event header ***
+
+  if (yajl_gen_map_open(g) != yajl_gen_status_ok)
+    goto err;
+
+  // domain
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_DOMAIN_FIELD,
+                      strlen(SYSEVENT_DOMAIN_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_DOMAIN_VALUE,
+                      strlen(SYSEVENT_DOMAIN_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // eventId
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_EVENT_ID_FIELD,
+                      strlen(SYSEVENT_EVENT_ID_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  event_id = event_id + 1;
+  snprintf(json_str, sizeof(json_str), "%d", event_id);
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // eventName
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_EVENT_NAME_FIELD,
+                      strlen(SYSEVENT_EVENT_NAME_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  snprintf(json_str, sizeof(json_str), "host %s rsyslog message", host);
+
+  if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
+      yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // lastEpochMicrosec
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_LAST_EPOCH_MICROSEC_FIELD,
+                      strlen(SYSEVENT_LAST_EPOCH_MICROSEC_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  snprintf(json_str, sizeof(json_str), "%" PRIu64, CDTIME_T_TO_US(cdtime()));
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // priority
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_PRIORITY_FIELD,
+                      strlen(SYSEVENT_PRIORITY_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  switch (sev_num) {
+  case 4:
+    if (yajl_gen_string(g, (u_char *)SYSEVENT_PRIORITY_VALUE_MEDIUM,
+                        strlen(SYSEVENT_PRIORITY_VALUE_MEDIUM)) !=
+        yajl_gen_status_ok)
+      goto err;
+    break;
+  case 5:
+    if (yajl_gen_string(g, (u_char *)SYSEVENT_PRIORITY_VALUE_NORMAL,
+                        strlen(SYSEVENT_PRIORITY_VALUE_NORMAL)) !=
+        yajl_gen_status_ok)
+      goto err;
+    break;
+  case 6:
+  case 7:
+    if (yajl_gen_string(g, (u_char *)SYSEVENT_PRIORITY_VALUE_LOW,
+                        strlen(SYSEVENT_PRIORITY_VALUE_LOW)) !=
+        yajl_gen_status_ok)
+      goto err;
+    break;
+  default:
+    if (yajl_gen_string(g, (u_char *)SYSEVENT_PRIORITY_VALUE_UNKNOWN,
+                        strlen(SYSEVENT_PRIORITY_VALUE_UNKNOWN)) !=
+        yajl_gen_status_ok)
+      goto err;
+    break;
+  }
+
+  // reportingEntityName
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_REPORTING_ENTITY_NAME_FIELD,
+                      strlen(SYSEVENT_REPORTING_ENTITY_NAME_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_REPORTING_ENTITY_NAME_VALUE,
+                      strlen(SYSEVENT_REPORTING_ENTITY_NAME_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // sequence
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_SEQUENCE_FIELD,
+                      strlen(SYSEVENT_SEQUENCE_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, SYSEVENT_SEQUENCE_VALUE,
+                      strlen(SYSEVENT_SEQUENCE_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // sourceName
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_SOURCE_NAME_FIELD,
+                      strlen(SYSEVENT_SOURCE_NAME_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_SOURCE_NAME_VALUE,
+                      strlen(SYSEVENT_SOURCE_NAME_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // startEpochMicrosec
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_START_EPOCH_MICROSEC_FIELD,
+                      strlen(SYSEVENT_START_EPOCH_MICROSEC_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  snprintf(json_str, sizeof(json_str), "%" PRIu64, CDTIME_T_TO_US(timestamp));
+
+  if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
+    goto err;
+  }
+
+  // version
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_VERSION_FIELD,
+                      strlen(SYSEVENT_VERSION_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, SYSEVENT_VERSION_VALUE,
+                      strlen(SYSEVENT_VERSION_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // *** END common event header ***
+
+  // *** BEGIN syslog fields ***
+
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_SYSLOG_FIELDS_FIELD,
+                      strlen(SYSEVENT_SYSLOG_FIELDS_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_map_open(g) != yajl_gen_status_ok)
+    goto err;
+
+  // eventSourceHost
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_EVENT_SOURCE_HOST_FIELD,
+                      strlen(SYSEVENT_EVENT_SOURCE_HOST_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)host, strlen(host)) != yajl_gen_status_ok)
+    goto err;
+
+  // eventSourceType
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_EVENT_SOURCE_TYPE_FIELD,
+                      strlen(SYSEVENT_EVENT_SOURCE_TYPE_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_EVENT_SOURCE_TYPE_VALUE,
+                      strlen(SYSEVENT_EVENT_SOURCE_TYPE_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // syslogFieldsVersion
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_SYSLOG_FIELDS_VERSION_FIELD,
+                      strlen(SYSEVENT_SYSLOG_FIELDS_VERSION_FIELD)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_number(g, SYSEVENT_SYSLOG_FIELDS_VERSION_VALUE,
+                      strlen(SYSEVENT_SYSLOG_FIELDS_VERSION_VALUE)) !=
+      yajl_gen_status_ok)
+    goto err;
+
+  // syslogMsg
+  if (msg != NULL) {
+    if (yajl_gen_string(g, (u_char *)SYSEVENT_SYSLOG_MSG_FIELD,
+                        strlen(SYSEVENT_SYSLOG_MSG_FIELD)) !=
+        yajl_gen_status_ok)
+      goto err;
+
+    if (yajl_gen_string(g, (u_char *)msg, strlen(msg)) != yajl_gen_status_ok)
+      goto err;
+  }
+
+  // syslogProc
+  if (process != NULL) {
+    if (yajl_gen_string(g, (u_char *)SYSEVENT_SYSLOG_PROC_FIELD,
+                        strlen(SYSEVENT_SYSLOG_PROC_FIELD)) !=
+        yajl_gen_status_ok)
+      goto err;
+
+    if (yajl_gen_string(g, (u_char *)process, strlen(process)) !=
+        yajl_gen_status_ok)
+      goto err;
+  }
+
+  // syslogSev
+  if (sev != NULL) {
+    if (yajl_gen_string(g, (u_char *)SYSEVENT_SYSLOG_SEV_FIELD,
+                        strlen(SYSEVENT_SYSLOG_SEV_FIELD)) !=
+        yajl_gen_status_ok)
+      goto err;
+
+    if (yajl_gen_string(g, (u_char *)sev, strlen(sev)) != yajl_gen_status_ok)
+      goto err;
+  }
+
+  // syslogTag
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_SYSLOG_TAG_FIELD,
+                      strlen(SYSEVENT_SYSLOG_TAG_FIELD)) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_string(g, (u_char *)SYSEVENT_SYSLOG_TAG_VALUE,
+                      strlen(SYSEVENT_SYSLOG_TAG_VALUE)) != yajl_gen_status_ok)
+    goto err;
+
+  // *** END syslog fields ***
+
+  // close syslog and header fields
+  if (yajl_gen_map_close(g) != yajl_gen_status_ok ||
+      yajl_gen_map_close(g) != yajl_gen_status_ok)
+    goto err;
+
+  if (yajl_gen_get_buf(g, &buf2, &len) != yajl_gen_status_ok)
+    goto err;
+
+  *buf = strdup((char *)buf2);
+
+  if (*buf == NULL) {
+    ERROR("sysevent plugin: gen_message_payload strdup failed");
+    goto err;
+  }
+
+  yajl_gen_free(g);
+
+  return 0;
+
+err:
+  yajl_gen_free(g);
+  ERROR("sysevent plugin: gen_message_payload failed to generate JSON");
+  return -1;
+}
+
+static int read_socket() {
+  int recv_flags = MSG_DONTWAIT;
+
+  while (42) {
+    struct sockaddr_storage src_addr;
+    socklen_t src_addr_len = sizeof(src_addr);
+
+    char buffer[listen_buffer_size];
+    memset(buffer, '\0', listen_buffer_size);
+
+    ssize_t count = recvfrom(sock, buffer, sizeof(buffer), recv_flags,
+                             (struct sockaddr *)&src_addr, &src_addr_len);
+
+    if (count < 0) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        pthread_mutex_lock(&sysevent_data_lock);
+
+        // There was nothing more to receive for now, so...
+        // If ring head does not equal ring tail, there is data
+        // in the ring buffer for the dequeue thread to read, so
+        // signal it
+        if (ring.head != ring.tail)
+          pthread_cond_signal(&sysevent_cond);
+
+        pthread_mutex_unlock(&sysevent_data_lock);
+
+        // Since there was nothing to receive, set recv to block and
+        // try again
+        recv_flags = 0;
+        continue;
+      } else if (errno != EINTR) {
+        ERROR("sysevent plugin: failed to receive data: %s", STRERRNO);
+        return -1;
+      } else {
+        // Interrupt, so continue and try again
+        continue;
+      }
+    }
+
+    if (count >= sizeof(buffer)) {
+      WARNING("sysevent plugin: datagram too large for buffer: truncated");
+    }
+
+    // We successfully received a message, so don't block on the next
+    // read in case there are more (and if there aren't, it will be
+    // handled above in the EWOULDBLOCK error-checking)
+    recv_flags = MSG_DONTWAIT;
+
+    // 1. Acquire data lock
+    // 2. Push to buffer if there is room, otherwise raise warning
+    //    and allow dequeue thread to take over
+
+    pthread_mutex_lock(&sysevent_data_lock);
+
+    int next = ring.head + 1;
+    if (next >= ring.maxLen)
+      next = 0;
+
+    if (next == ring.tail) {
+      // Buffer is full, signal the dequeue thread to process the buffer
+      // and clean it out, and then sleep
+      WARNING("sysevent plugin: ring buffer full");
+
+      pthread_cond_signal(&sysevent_cond);
+      pthread_mutex_unlock(&sysevent_data_lock);
+
+      usleep(1000);
+      continue;
+    } else {
+      DEBUG("sysevent plugin: writing %s", buffer);
+
+      sstrncpy(ring.buffer[ring.head], buffer, sizeof(buffer));
+      ring.timestamp[ring.head] = cdtime();
+      ring.head = next;
+    }
+
+    pthread_mutex_unlock(&sysevent_data_lock);
+  }
+}
+
+static void sysevent_dispatch_notification(const char *message,
+#if HAVE_YAJL_V2
+                                           yajl_val *node,
+#endif
+                                           cdtime_t timestamp) {
+  char *buf = NULL;
+
+  notification_t n = {
+      .severity = NOTIF_OKAY,
+      .time = cdtime(),
+      .plugin = "sysevent",
+      .type = "gauge",
+  };
+
+#if HAVE_YAJL_V2
+  if (node != NULL) {
+    // If we have a parsed-JSON node to work with, use that
+    // msg
+    const char *msg_path[] = {rsyslog_keys[2], (const char *)0};
+    yajl_val msg_v = yajl_tree_get(*node, msg_path, yajl_t_string);
+
+    char msg[listen_buffer_size];
+
+    if (msg_v != NULL) {
+      memset(msg, '\0', listen_buffer_size);
+      snprintf(msg, listen_buffer_size, "%s%c", YAJL_GET_STRING(msg_v), '\0');
+    }
+
+    // severity
+    const char *severity_path[] = {"@fields", rsyslog_field_keys[1],
+                                   (const char *)0};
+    yajl_val severity_v = yajl_tree_get(*node, severity_path, yajl_t_string);
+
+    char severity[listen_buffer_size];
+
+    if (severity_v != NULL) {
+      memset(severity, '\0', listen_buffer_size);
+      snprintf(severity, listen_buffer_size, "%s%c",
+               YAJL_GET_STRING(severity_v), '\0');
+    }
+
+    // sev_num
+    const char *sev_num_str_path[] = {"@fields", rsyslog_field_keys[2],
+                                      (const char *)0};
+    yajl_val sev_num_str_v =
+        yajl_tree_get(*node, sev_num_str_path, yajl_t_string);
+
+    char sev_num_str[listen_buffer_size];
+    int sev_num = -1;
+
+    if (sev_num_str_v != NULL) {
+      memset(sev_num_str, '\0', listen_buffer_size);
+      snprintf(sev_num_str, listen_buffer_size, "%s%c",
+               YAJL_GET_STRING(sev_num_str_v), '\0');
+
+      sev_num = atoi(sev_num_str);
+
+      if (sev_num < 4)
+        n.severity = NOTIF_FAILURE;
+    }
+
+    // process
+    const char *process_path[] = {"@fields", rsyslog_field_keys[3],
+                                  (const char *)0};
+    yajl_val process_v = yajl_tree_get(*node, process_path, yajl_t_string);
+
+    char process[listen_buffer_size];
+
+    if (process_v != NULL) {
+      memset(process, '\0', listen_buffer_size);
+      snprintf(process, listen_buffer_size, "%s%c", YAJL_GET_STRING(process_v),
+               '\0');
+    }
+
+    // hostname
+    const char *hostname_path[] = {rsyslog_keys[1], (const char *)0};
+    yajl_val hostname_v = yajl_tree_get(*node, hostname_path, yajl_t_string);
+
+    char hostname_str[listen_buffer_size];
+
+    if (hostname_v != NULL) {
+      memset(hostname_str, '\0', listen_buffer_size);
+      snprintf(hostname_str, listen_buffer_size, "%s%c",
+               YAJL_GET_STRING(hostname_v), '\0');
+    }
+
+    gen_message_payload(
+        (msg_v != NULL ? msg : NULL), (severity_v != NULL ? severity : NULL),
+        (sev_num_str_v != NULL ? sev_num : -1),
+        (process_v != NULL ? process : NULL),
+        (hostname_v != NULL ? hostname_str : hostname_g), timestamp, &buf);
+  } else {
+    // Data was not sent in JSON format, so just treat the whole log entry
+    // as the message (and we'll be unable to acquire certain data, so the
+    // payload
+    // generated below will be less informative)
+
+    gen_message_payload(message, NULL, -1, NULL, hostname_g, timestamp, &buf);
+  }
+#else
+  gen_message_payload(message, NULL, -1, NULL, hostname_g, timestamp, &buf);
+#endif
+
+  sstrncpy(n.host, hostname_g, sizeof(n.host));
+
+  int status = plugin_notification_meta_add_string(&n, "ves", buf);
+
+  if (status < 0) {
+    sfree(buf);
+    ERROR("sysevent plugin: unable to set notification VES metadata: %s",
+          STRERRNO);
+    return;
+  }
+
+  DEBUG("sysevent plugin: notification VES metadata: %s",
+        n.meta->nm_value.nm_string);
+
+  DEBUG("sysevent plugin: dispatching message");
+
+  plugin_dispatch_notification(&n);
+  plugin_notification_meta_free(n.meta);
+
+  // strdup'd in gen_message_payload
+  if (buf != NULL)
+    sfree(buf);
+}
+
+static void read_ring_buffer() {
+  pthread_mutex_lock(&sysevent_data_lock);
+
+  // If there's currently nothing to read from the buffer,
+  // then wait
+  if (ring.head == ring.tail)
+    pthread_cond_wait(&sysevent_cond, &sysevent_data_lock);
+
+  while (ring.head != ring.tail) {
+    int next = ring.tail + 1;
+
+    if (next >= ring.maxLen)
+      next = 0;
+
+    DEBUG("sysevent plugin: reading from ring buffer: %s",
+          ring.buffer[ring.tail]);
+
+    cdtime_t timestamp = ring.timestamp[ring.tail];
+    char *match_str = NULL;
+
+#if HAVE_YAJL_V2
+    // Try to parse JSON, and if it fails, fall back to plain string
+    char errbuf[1024];
+    errbuf[0] = 0;
+    yajl_val node = yajl_tree_parse((const char *)ring.buffer[ring.tail],
+                                    errbuf, sizeof(errbuf));
+
+    if (node != NULL) {
+      // JSON rsyslog data
+
+      // If we have any regex filters, we need to see if the message portion of
+      // the data matches any of them (otherwise we're not interested)
+      if (monitor_all_messages == 0) {
+        const char *path[] = {"@message", (const char *)0};
+        yajl_val v = yajl_tree_get(node, path, yajl_t_string);
+
+        char json_val[listen_buffer_size];
+        memset(json_val, '\0', listen_buffer_size);
+
+        snprintf(json_val, listen_buffer_size, "%s%c", YAJL_GET_STRING(v),
+                 '\0');
+
+        match_str = (char *)&json_val;
+      }
+    } else {
+      // non-JSON rsyslog data
+
+      // If we have any regex filters, we need to see if the message data
+      // matches any of them (otherwise we're not interested)
+      if (monitor_all_messages == 0)
+        match_str = ring.buffer[ring.tail];
+    }
+#else
+    // If we have any regex filters, we need to see if the message data
+    // matches any of them (otherwise we're not interested)
+    if (monitor_all_messages == 0)
+      match_str = ring.buffer[ring.tail];
+#endif
+
+    int is_match = 1;
+
+    // If we care about matching, do that comparison here
+    if (match_str != NULL) {
+      if (ignorelist_match(ignorelist, match_str) != 0)
+        is_match = 0;
+      else
+        DEBUG("sysevent plugin: regex filter match");
+    }
+
+#if HAVE_YAJL_V2
+    if (is_match == 1 && node != NULL) {
+      sysevent_dispatch_notification(NULL, &node, timestamp);
+      yajl_tree_free(node);
+    } else if (is_match == 1)
+      sysevent_dispatch_notification(ring.buffer[ring.tail], NULL, timestamp);
+#else
+    if (is_match == 1)
+      sysevent_dispatch_notification(ring.buffer[ring.tail], timestamp);
+#endif
+
+    ring.tail = next;
+  }
+
+  pthread_mutex_unlock(&sysevent_data_lock);
+}
+
+static void *sysevent_socket_thread(void *arg) /* {{{ */
+{
+  pthread_mutex_lock(&sysevent_thread_lock);
+
+  while (sysevent_socket_thread_loop > 0) {
+    pthread_mutex_unlock(&sysevent_thread_lock);
+
+    if (sock == -1)
+      return (void *)0;
+
+    int status = read_socket();
+
+    pthread_mutex_lock(&sysevent_thread_lock);
+
+    if (status < 0) {
+      WARNING("sysevent plugin: problem with socket thread (status: %d)",
+              status);
+      sysevent_socket_thread_error = 1;
+      break;
+    }
+  } /* while (sysevent_socket_thread_loop > 0) */
+
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  return (void *)0;
+} /* }}} void *sysevent_socket_thread */
+
+// Entry point for thread responsible for reading from
+// ring buffer and dispatching notifications
+static void *sysevent_dequeue_thread(void *arg) /* {{{ */
+{
+  pthread_mutex_lock(&sysevent_thread_lock);
+
+  while (sysevent_dequeue_thread_loop > 0) {
+    pthread_mutex_unlock(&sysevent_thread_lock);
+
+    read_ring_buffer();
+
+    pthread_mutex_lock(&sysevent_thread_lock);
+  } /* while (sysevent_dequeue_thread_loop > 0) */
+
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  return (void *)0;
+} /* }}} void *sysevent_dequeue_thread */
+
+static int start_socket_thread(void) /* {{{ */
+{
+  pthread_mutex_lock(&sysevent_thread_lock);
+
+  if (sysevent_socket_thread_loop != 0) {
+    pthread_mutex_unlock(&sysevent_thread_lock);
+    return 0;
+  }
+
+  sysevent_socket_thread_loop = 1;
+  sysevent_socket_thread_error = 0;
+
+  DEBUG("sysevent plugin: starting socket thread");
+
+  int status = plugin_thread_create(&sysevent_socket_thread_id,
+                                    /* attr = */ NULL, sysevent_socket_thread,
+                                    /* arg = */ (void *)0, "sysevent");
+  if (status != 0) {
+    sysevent_socket_thread_loop = 0;
+    ERROR("sysevent plugin: starting socket thread failed.");
+    pthread_mutex_unlock(&sysevent_thread_lock);
+    return -1;
+  }
+
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  return 0;
+} /* }}} int start_socket_thread */
+
+static int start_dequeue_thread(void) /* {{{ */
+{
+  pthread_mutex_lock(&sysevent_thread_lock);
+
+  if (sysevent_dequeue_thread_loop != 0) {
+    pthread_mutex_unlock(&sysevent_thread_lock);
+    return 0;
+  }
+
+  sysevent_dequeue_thread_loop = 1;
+
+  int status = plugin_thread_create(&sysevent_dequeue_thread_id,
+                                    /* attr = */ NULL, sysevent_dequeue_thread,
+                                    /* arg = */ (void *)0, "ssyevent");
+  if (status != 0) {
+    sysevent_dequeue_thread_loop = 0;
+    ERROR("sysevent plugin: Starting dequeue thread failed.");
+    pthread_mutex_unlock(&sysevent_thread_lock);
+    return -1;
+  }
+
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  return status;
+} /* }}} int start_dequeue_thread */
+
+static int start_threads(void) /* {{{ */
+{
+  int status = start_socket_thread();
+  int status2 = start_dequeue_thread();
+
+  if (status != 0)
+    return status;
+  else
+    return status2;
+} /* }}} int start_threads */
+
+static int stop_socket_thread(int shutdown) /* {{{ */
+{
+  pthread_mutex_lock(&sysevent_thread_lock);
+
+  if (sysevent_socket_thread_loop == 0) {
+    pthread_mutex_unlock(&sysevent_thread_lock);
+    return -1;
+  }
+
+  sysevent_socket_thread_loop = 0;
+  pthread_cond_broadcast(&sysevent_cond);
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  int status;
+
+  if (shutdown == 1) {
+    // Since the thread is blocking, calling pthread_join
+    // doesn't actually succeed in stopping it.  It will stick around
+    // until a message is received on the socket (at which
+    // it will realize that "sysevent_socket_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.
+
+    DEBUG("sysevent plugin: Canceling socket thread for process shutdown");
+
+    status = pthread_cancel(sysevent_socket_thread_id);
+
+    if (status != 0 && status != ESRCH) {
+      ERROR("sysevent plugin: Unable to cancel socket thread: %d (%s)", status,
+            STRERRNO);
+      status = -1;
+    } else
+      status = 0;
+  } else {
+    status = pthread_join(sysevent_socket_thread_id, /* return = */ NULL);
+    if (status != 0 && status != ESRCH) {
+      ERROR("sysevent plugin: Stopping socket thread failed.");
+      status = -1;
+    } else
+      status = 0;
+  }
+
+  pthread_mutex_lock(&sysevent_thread_lock);
+  memset(&sysevent_socket_thread_id, 0, sizeof(sysevent_socket_thread_id));
+  sysevent_socket_thread_error = 0;
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  DEBUG("sysevent plugin: Finished requesting stop of socket thread");
+
+  return status;
+} /* }}} int stop_socket_thread */
+
+static int stop_dequeue_thread() /* {{{ */
+{
+  pthread_mutex_lock(&sysevent_thread_lock);
+
+  if (sysevent_dequeue_thread_loop == 0) {
+    pthread_mutex_unlock(&sysevent_thread_lock);
+    return -1;
+  }
+
+  sysevent_dequeue_thread_loop = 0;
+  pthread_cond_broadcast(&sysevent_cond);
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  // Since the thread is blocking, calling pthread_join
+  // doesn't actually succeed in stopping it.  It will stick around
+  // until a message is received on the socket (at which
+  // it will realize that "sysevent_dequeue_thread_loop" is 0 and will
+  // break out of the read loop and be allowed to die).  Since this
+  // function is called when the processing is exiting, we don't want to
+  // have an idle thread hanging around.  Calling pthread_cancel here
+  // just assures that the thread is gone and that the process has been
+  // fully terminated.
+
+  DEBUG("sysevent plugin: Canceling dequeue thread for process shutdown");
+
+  int status = pthread_cancel(sysevent_dequeue_thread_id);
+
+  if (status != 0 && status != ESRCH) {
+    ERROR("sysevent plugin: Unable to cancel dequeue thread: %d (%s)", status,
+          STRERRNO);
+    status = -1;
+  } else
+    status = 0;
+
+  pthread_mutex_lock(&sysevent_thread_lock);
+  memset(&sysevent_dequeue_thread_id, 0, sizeof(sysevent_dequeue_thread_id));
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  DEBUG("sysevent plugin: Finished requesting stop of dequeue thread");
+
+  return status;
+} /* }}} int stop_dequeue_thread */
+
+static int stop_threads() /* {{{ */
+{
+  int status = stop_socket_thread(1);
+  int status2 = stop_dequeue_thread();
+
+  if (status != 0)
+    return status;
+  else
+    return status2;
+} /* }}} int stop_threads */
+
+static int sysevent_init(void) /* {{{ */
+{
+  ring.head = 0;
+  ring.tail = 0;
+  ring.maxLen = buffer_length;
+  ring.buffer = (char **)calloc(buffer_length, sizeof(char *));
+
+  if (ring.buffer == NULL) {
+    ERROR("sysevent plugin: sysevent_init ring buffer calloc failed");
+    return -1;
+  }
+
+  for (int i = 0; i < buffer_length; i++) {
+    ring.buffer[i] = calloc(1, listen_buffer_size);
+
+    if (ring.buffer[i] == NULL) {
+      ERROR("sysevent plugin: sysevent_init ring buffer entry calloc failed");
+      return -1;
+    }
+  }
+
+  ring.timestamp = (cdtime_t *)calloc(buffer_length, sizeof(cdtime_t));
+
+  if (ring.timestamp == NULL) {
+    ERROR("sysevent plugin: sysevent_init ring buffer timestamp calloc failed");
+    return -1;
+  }
+
+  if (sock == -1) {
+    struct addrinfo hints = {
+        .ai_family = AF_UNSPEC,
+        .ai_socktype = SOCK_DGRAM,
+        .ai_protocol = 0,
+        .ai_flags = AI_PASSIVE | AI_ADDRCONFIG,
+    };
+    struct addrinfo *res = 0;
+
+    int err = getaddrinfo(listen_ip, listen_port, &hints, &res);
+
+    if (err != 0) {
+      ERROR("sysevent plugin: failed to resolve local socket address (err=%d)",
+            err);
+      freeaddrinfo(res);
+      return -1;
+    }
+
+    sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+    if (sock == -1) {
+      ERROR("sysevent plugin: failed to open socket: %s", STRERRNO);
+      freeaddrinfo(res);
+      return -1;
+    }
+
+    if (bind(sock, res->ai_addr, res->ai_addrlen) == -1) {
+      ERROR("sysevent plugin: failed to bind socket: %s", STRERRNO);
+      freeaddrinfo(res);
+      sock = -1;
+      return -1;
+    }
+
+    freeaddrinfo(res);
+  }
+
+  DEBUG("sysevent plugin: socket created and bound");
+
+  return start_threads();
+} /* }}} int sysevent_init */
+
+static int sysevent_config_add_listen(const oconfig_item_t *ci) /* {{{ */
+{
+  if (ci->values_num != 2 || ci->values[0].type != OCONFIG_TYPE_STRING ||
+      ci->values[1].type != OCONFIG_TYPE_STRING) {
+    ERROR("sysevent plugin: The `%s' config option needs "
+          "two string arguments (ip and port).",
+          ci->key);
+    return -1;
+  }
+
+  listen_ip = strdup(ci->values[0].value.string);
+  listen_port = strdup(ci->values[1].value.string);
+
+  return 0;
+}
+
+static int sysevent_config_add_buffer_size(const oconfig_item_t *ci) /* {{{ */
+{
+  int tmp = 0;
+
+  if (cf_util_get_int(ci, &tmp) != 0)
+    return -1;
+  else if ((tmp >= 1024) && (tmp <= 65535))
+    listen_buffer_size = tmp;
+  else {
+    WARNING(
+        "sysevent plugin: The `BufferSize' must be between 1024 and 65535.");
+    return -1;
+  }
+
+  return 0;
+}
+
+static int sysevent_config_add_buffer_length(const oconfig_item_t *ci) /* {{{ */
+{
+  int tmp = 0;
+
+  if (cf_util_get_int(ci, &tmp) != 0)
+    return -1;
+  else if ((tmp >= 3) && (tmp <= 4096))
+    buffer_length = tmp;
+  else {
+    WARNING("sysevent plugin: The `Bufferlength' must be between 3 and 4096.");
+    return -1;
+  }
+
+  return 0;
+}
+
+static int sysevent_config_add_regex_filter(const oconfig_item_t *ci) /* {{{ */
+{
+  if (ci->values_num != 1 || ci->values[0].type != OCONFIG_TYPE_STRING) {
+    ERROR("sysevent plugin: The `%s' config option needs "
+          "one string argument, a regular expression.",
+          ci->key);
+    return -1;
+  }
+
+#if HAVE_REGEX_H
+  if (ignorelist == NULL)
+    ignorelist = ignorelist_create(/* invert = */ 1);
+
+  int status = ignorelist_add(ignorelist, ci->values[0].value.string);
+
+  if (status != 0) {
+    ERROR("sysevent plugin: invalid regular expression: %s",
+          ci->values[0].value.string);
+    return 1;
+  }
+
+  monitor_all_messages = 0;
+#else
+  WARNING("sysevent plugin: The plugin has been compiled without support "
+          "for the \"RegexFilter\" option.");
+#endif
+
+  return 0;
+}
+
+static int sysevent_config(oconfig_item_t *ci) /* {{{ */
+{
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Listen", child->key) == 0)
+      sysevent_config_add_listen(child);
+    else if (strcasecmp("BufferSize", child->key) == 0)
+      sysevent_config_add_buffer_size(child);
+    else if (strcasecmp("BufferLength", child->key) == 0)
+      sysevent_config_add_buffer_length(child);
+    else if (strcasecmp("RegexFilter", child->key) == 0)
+      sysevent_config_add_regex_filter(child);
+    else {
+      WARNING("sysevent plugin: Option `%s' is not allowed here.", child->key);
+    }
+  }
+
+  return 0;
+} /* }}} int sysevent_config */
+
+static int sysevent_read(void) /* {{{ */
+{
+  pthread_mutex_lock(&sysevent_thread_lock);
+
+  if (sysevent_socket_thread_error != 0) {
+    pthread_mutex_unlock(&sysevent_thread_lock);
+
+    ERROR("sysevent plugin: The sysevent socket thread had a problem (%d). "
+          "Restarting it.",
+          sysevent_socket_thread_error);
+
+    stop_threads();
+
+    start_threads();
+
+    return -1;
+  } /* if (sysevent_socket_thread_error != 0) */
+
+  pthread_mutex_unlock(&sysevent_thread_lock);
+
+  return 0;
+} /* }}} int sysevent_read */
+
+static int sysevent_shutdown(void) /* {{{ */
+{
+  DEBUG("sysevent plugin: Shutting down thread.");
+
+  int status = stop_threads();
+  int status2 = 0;
+
+  if (sock != -1) {
+    status2 = close(sock);
+    if (status2 != 0) {
+      ERROR("sysevent plugin: failed to close socket %d: %d (%s)", sock, status,
+            STRERRNO);
+    }
+
+    sock = -1;
+  }
+
+  free(listen_ip);
+  free(listen_port);
+
+  for (int i = 0; i < buffer_length; i++) {
+    free(ring.buffer[i]);
+  }
+
+  free(ring.buffer);
+  free(ring.timestamp);
+
+  if (status != 0)
+    return status;
+  else
+    return status2;
+} /* }}} int sysevent_shutdown */
+
+void module_register(void) {
+  plugin_register_complex_config("sysevent", sysevent_config);
+  plugin_register_init("sysevent", sysevent_init);
+  plugin_register_read("sysevent", sysevent_read);
+  plugin_register_shutdown("sysevent", sysevent_shutdown);
+} /* void module_register */
index 249447e..8bbb92b 100644 (file)
@@ -987,6 +987,8 @@ static int __attribute__((warn_unused_result)) probe_cpu(void) {
     /* Ivy Bridge */
     case 0x3A: /* IVB */
     case 0x3E: /* IVB Xeon */
+    case 0x55: /* SKX,CLX Xeon */
+    case 0x6A: /* ICX Xeon */
       do_smi = true;
       do_core_cstate = (1 << 3) | (1 << 6) | (1 << 7);
       do_pkg_cstate = (1 << 2) | (1 << 3) | (1 << 6) | (1 << 7);
@@ -1042,6 +1044,8 @@ static int __attribute__((warn_unused_result)) probe_cpu(void) {
       break;
     case 0x2D: /* SNB Xeon */
     case 0x3E: /* IVB Xeon */
+    case 0x55: /* SKX,CLX Xeon */
+    case 0x6A: /* ICX Xeon */
       do_rapl = RAPL_PKG | RAPL_CORES | RAPL_DRAM;
       do_power_fields = TURBO_PLATFORM | PSTATES_PLATFORM;
       break;
index 69f59b0..6c08936 100644 (file)
@@ -242,6 +242,7 @@ spam_score              value:GAUGE:U:U
 spl                     value:GAUGE:U:U
 swap                    value:GAUGE:0:1099511627776
 swap_io                 value:DERIVE:0:U
+sysevent                value:GAUGE:0:1
 tcp_connections         value:GAUGE:0:4294967295
 tdp                     value:GAUGE:U:U
 temperature             value:GAUGE:U:U
index 8b2521d..6a0ac28 100644 (file)
@@ -1453,13 +1453,13 @@ int service_name_to_port_number(const char *service_name) {
       service_number = (int)ntohs(sa->sin6_port);
     }
 
-    if ((service_number > 0) && (service_number <= 65535))
+    if (service_number > 0)
       break;
   }
 
   freeaddrinfo(ai_list);
 
-  if ((service_number > 0) && (service_number <= 65535))
+  if (service_number > 0)
     return service_number;
   return -1;
 } /* int service_name_to_port_number */
index 9d878da..6c2c8ae 100644 (file)
@@ -24,6 +24,9 @@
  *   Florian Forster <ff at octo.it>
  **/
 
+#ifndef UTILS_LATENCY_LATENCY_H
+#define UTILS_LATENCY_LATENCY_H 1
+
 #include "collectd.h"
 
 #include "utils_time.h"
@@ -61,3 +64,5 @@ cdtime_t latency_counter_get_percentile(latency_counter_t *lc, double percent);
  */
 double latency_counter_get_rate(const latency_counter_t *lc, cdtime_t lower,
                                 cdtime_t upper, const cdtime_t now);
+
+#endif /* UTILS_LATENCY_LATENCY_H */
index 8b19497..9170398 100644 (file)
@@ -27,6 +27,9 @@
  *   Michał Aleksiński <michalx.aleksinski@intel.com>
  **/
 
+#ifndef UTILS_PROC_PIDS_PROC_PIDS_H
+#define UTILS_PROC_PIDS_PROC_PIDS_H 1
+
 #include <dirent.h>
 #include <sys/types.h>
 
@@ -224,3 +227,5 @@ int proc_pids_update(const char *procfs_path, proc_pids_t *proc_pids[],
  *   0 on success. -1 on error.
  */
 int proc_pids_free(proc_pids_t *proc_pids[], size_t proc_pids_num);
+
+#endif /* UTILS_PROC_PIDS_PROC_PIDS_H */
index 43d3fcf..771c241 100644 (file)
@@ -33,6 +33,9 @@
  *   regular expressions.
  */
 
+#ifndef UTILS_TAIL_MATCH_H
+#define UTILS_TAIL_MATCH_H 1
+
 #include "utils/latency/latency_config.h"
 #include "utils/match/match.h"
 
@@ -133,3 +136,5 @@ int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
  *   Zero on success, nonzero on failure.
  */
 int tail_match_read(cu_tail_match_t *obj);
+
+#endif /* UTILS_TAIL_MATCH_H */
index a3c2451..3364697 100644 (file)
@@ -231,23 +231,6 @@ static int za_read(void) {
     return -1;
   }
 
-  // Ignore the first two lines because they contain information about
-  // the rest of the file.
-  // See kstat_seq_show_headers module/spl/spl-kstat.c of the spl kernel
-  // module.
-  if (fgets(buffer, sizeof(buffer), fh) == NULL) {
-    ERROR("zfs_arc plugin: \"%s\" does not contain a single line.",
-          ZOL_ARCSTATS_FILE);
-    fclose(fh);
-    return (-1);
-  }
-  if (fgets(buffer, sizeof(buffer), fh) == NULL) {
-    ERROR("zfs_arc plugin: \"%s\" does not contain at least two lines.",
-          ZOL_ARCSTATS_FILE);
-    fclose(fh);
-    return (-1);
-  }
-
   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
     char *fields[3];
     value_t v;
@@ -285,10 +268,17 @@ static int za_read(void) {
   za_read_gauge(ksp, "mfu_size", "cache_size", "mfu_size");
   za_read_gauge(ksp, "mru_ghost_size", "cache_size", "mru_ghost_size");
   za_read_gauge(ksp, "mru_size", "cache_size", "mru_size");
-  za_read_gauge(ksp, "other_size", "cache_size", "other_size");
   za_read_gauge(ksp, "p", "cache_size", "p");
   za_read_gauge(ksp, "size", "cache_size", "arc");
 
+  /* The "other_size" value was replaced by more specific values in ZFS on Linux
+   * version 0.7.0 (commit 25458cb)
+   */
+  if (za_read_gauge(ksp, "dbuf_size", "cache_size", "dbuf_size") != 0 ||
+      za_read_gauge(ksp, "dnode_size", "cache_size", "dnode_size") != 0 ||
+      za_read_gauge(ksp, "bonus_size", "cache_size", "bonus_size") != 0)
+    za_read_gauge(ksp, "other_size", "cache_size", "other_size");
+
   /* The "l2_size" value has disappeared from Solaris some time in
    * early 2013, and has only reappeared recently in Solaris 11.2.
    * Stop trying if we ever fail to read it, so we don't spam the log.
index 0298a44..24cf667 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEFAULT_VERSION="5.9.0.git"
+DEFAULT_VERSION="5.10.0.git"
 
 if [ -d .git ]; then
        VERSION="`git describe --dirty=+ --abbrev=7 2> /dev/null | grep collectd | sed -e 's/^collectd-//' -e 's/-/./g'`"