Code

Merge pull request #2346 from trenkel/master
authorPavel Rochnyak <pavel2000@ngs.ru>
Fri, 20 Oct 2017 13:15:57 +0000 (20:15 +0700)
committerGitHub <noreply@github.com>
Fri, 20 Oct 2017 13:15:57 +0000 (20:15 +0700)
Add CollectdError exception which can be thrown without causing a stacktrace to be logged.

100 files changed:
AUTHORS
ChangeLog
Makefile.am
README
configure.ac
contrib/curl_json/php-fpm.conf [new file with mode: 0644]
contrib/redhat/collectd.spec
contrib/systemd.collectd.service
proto/types.proto
src/apache.c
src/ceph.c
src/collectd-nagios.c
src/collectd-python.pod
src/collectd-snmp.pod
src/collectd-tg.c
src/collectd.conf.in
src/collectd.conf.pod
src/collectd.pod
src/collectdctl.c
src/cpu.c
src/cpython.h
src/curl.c
src/curl_json.c
src/curl_xml.c
src/daemon/collectd.c
src/daemon/collectd.h
src/daemon/common.c
src/daemon/common.h
src/daemon/configfile.c
src/daemon/globals.c [new file with mode: 0644]
src/daemon/globals.h [new file with mode: 0644]
src/daemon/plugin.c
src/daemon/plugin.h
src/daemon/plugin_mock.c
src/daemon/utils_cache.c
src/daemon/utils_cache.h
src/dbi.c
src/dpdkevents.c
src/dpdkstat.c
src/email.c
src/exec.c
src/filecount.c
src/grpc.cc
src/intel_pmu.c [new file with mode: 0644]
src/ipmi.c
src/libcollectdclient/client.c
src/libcollectdclient/collectd/client.h
src/libcollectdclient/collectd/network.h
src/libcollectdclient/collectd/network_buffer.h
src/libcollectdclient/collectd/network_parse.h [new file with mode: 0644]
src/libcollectdclient/collectd/server.h [new file with mode: 0644]
src/libcollectdclient/collectd/types.h [new file with mode: 0644]
src/libcollectdclient/network_parse.c [new file with mode: 0644]
src/libcollectdclient/network_parse_test.c [new file with mode: 0644]
src/libcollectdclient/server.c [new file with mode: 0644]
src/mcelog.c
src/memcachec.c
src/memcached.c
src/mic.c
src/mqtt.c
src/msr-index.h [new file with mode: 0644]
src/multimeter.c
src/network.c
src/nfs.c
src/ntpd.c
src/nut.c
src/openldap.c
src/openvpn.c
src/oracle.c
src/perl.c
src/postgresql.c
src/processes.c
src/python.c
src/pyvalues.c
src/routeros.c
src/rrdtool.c
src/snmp.c
src/swap.c
src/synproxy.c [new file with mode: 0644]
src/table.c
src/tail.c
src/tail_csv.c
src/ted.c
src/turbostat.c
src/types.db
src/unixsock.c
src/uptime.c
src/utils_curl_stats.c
src/utils_format_kairosdb.c
src/utils_format_kairosdb.h
src/utils_latency_config.c
src/utils_latency_config.h
src/utils_match.c
src/utils_tail_match.c
src/varnish.c
src/write_http.c
src/write_prometheus.c
src/write_redis.c
src/write_tsdb.c
src/zfs_arc.c

diff --git a/AUTHORS b/AUTHORS
index d866c7004f70ec72719c4b056d6387c92d0283be..4df743c51376d3290df9bbcaa51f3d0c61b4ecd9 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -116,6 +116,9 @@ Dan Ryder <daryder at cisco.com>
 David Bacher <drbacher at gmail.com>
  - serial plugin.
 
+Denis Pompilio <denis.pompilio at gmail.com>
+ - Improvements to the write_http plugin.
+
 Doug MacEachern <dougm at hyperic.com>
  - The `-T' option (config testing mode).
  - OpenVPN plugin.
@@ -285,6 +288,11 @@ Scott Sanders <scott at jssjr.com>
 Sebastien Pahl <sebastien.pahl at dotcloud.com>
  - AMQP plugin.
 
+Serhiy Pshyk <serhiyx.pshyk at intel.com>
+ - intel_pmu plugin
+ - intel_rdt plugin
+ - snmp_agent plugin
+
 Simon Kuhnle <simon at blarzwurst.de>
  - OpenBSD code for the cpu and memory plugins.
 
index b8755d9938021e32a9f6a33a802c6c992fe98f61..f990dd46c994b2912ed84bdcada46f9b3cf43879 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
          embedded HTTP server, in a format compatible with Prometheus'
          collectd_exporter. Thanks to Florian Forster. #1967
 
+2017-10-06, Version 5.6.3
+       * collectd: support for boolean string config values has been
+         reintroduced. Thanks to Sebastian Harl. #2083, #2098
+       * collectd: The capability checking has been changed to use
+         "cap_get_proc()". Thanks to Marc Fournier. #2151
+       * Documentation: A section documenting ignore lists has been added to
+         collectd.conf(5). Thanks to Florian Forster.
+       * AMQP plugin: The "ExchangeType" option is now also valid for
+         publishers. Thanks to Florian Forster. #2286
+       * Apache, Ascent, BIND, cURL, cURL-JSON, cURL-XML, nginx, Write HTTP
+         plugins: Handling of URLs that redirect elsewhere has been fixed.
+         Thanks to Pavel Rochnyack. #2328
+       * BIND plugin: Fix parsing of the sample time provided by BIND.
+         Previously, the time was assumed to be in the local time zone when in
+         fact it was in UTC. Thanks to Ed Ravin. #1268
+       * BIND plugin: Memory leaks have been fixed. Thanks to Ruben Kerkhof.
+         #2303
+       * Chrony plugin: Build flags have been fixed. Thanks to Thomas Jost and
+         Marc Fournier. #2133
+       * cURL-JSON plugin: The timeout value has been changed to default to the
+         collection interval. This fixes a regression. Thanks to Marc Fournier.
+       * cURL-JSON plugin: Handling of arrays has been fixed. Thanks to Florian
+         Forster. #2266
+       * DBI plugin: Memory leaks at shutdown have been fixes. Thanks to Pavel
+         Rochnyack and Florian Forster.
+       * E-Mail, Exec, UnixSock plugins: Group ID lookup on systems with many
+         groups has been fixed. Thanks to Ruben Kerkhof and Florian Forster.
+         #2208
+       * IPC plugin: A compilation error on AIX has been fixed. Thanks to Pavel
+         Rochnyack. #2305
+       * LogFile plugin: If writing to the file fails, print log messages on
+         "STDERR" instead. Thanks to Marc Fournier.
+       * Log Logstash plugin: If writing the log entry fails, print it to
+         "STDERR" instead. Thanks to Marc Fournier.
+       * memcachec, Tail plugins: A resource leak in the matching
+         infrastructure has been fixed. Thanks to Krzysztof Matczak. #2192
+       * MQTT plugin: Invalid symbols in topic names are now replaced and a
+         resource leak has been fixed. Thanks to Denys Fedoryshchenko. #2123
+       * Network plugin: A potential endless-loop has been fixed. This can be
+         triggered remotely by sending a signed network packet to a server
+         which is not set up to check signatures. Thanks to Marcin Kozlowski
+         and Pavel Rochnyack. #2174, #2233, CVE-2017-7401
+       * Network plugin: A use-after-free has been fixed. Thanks to Pavel
+         Rochnyack. #2375
+       * Notify Email plugin: The plugin is no longer explicitly linked against
+         libssl and libcrypto, relies on libesmtp being linked correctly.
+         Thanks to Marc Fournier. Debian#852924
+       * NTPd plugin: Calculation of loop offset and error has been fixed.
+         Thanks to Neil Wilson. #2188
+       * OpenLDAP plugin: An incorrect use of the ldap library, leading to a
+         crash, has been fixed. Thanks to Marc Fournier. #2331
+       * Perl plugin: A potential double-free has been fixed. Thanks to Florian
+         Forster. #2278
+       * Perl plugin: Print an error when an incorrect configuration is
+         encountered. Thanks to Pavel Rochnyack. #927
+       * RRDtool plugin: Incorrect handling of the flushes timeout option has
+         been fixed. Handling of the "RandomTimeout" has been fixed. Thanks to
+         Pavel Rochnyack. #2363
+       * SMART plugin: Some warning messages have been removed and the code has
+         been cleaned up. Thanks to Florian Forster. #2062
+       * SMART plugin: A check for the "CAP_SYS_RAWIO" capability has been
+         added. Thanks to Marc Fournier.
+       * SNMP plugin: A double free has been fixed. Thanks to Pavel Rochnyack.
+         #2291
+       * Write Graphite plugin: Error handling in the case that calculating a
+         metric's rate fails has been improved. Previously, the raw counter
+         values were sent to Graphite. Thanks to Iain Buclaw. #2209
+       * Write Kafka plugin: A 32 bit random number is now used when formatting
+         a random key. Thanks to Florian Forster. #2074
+
+
 2016-11-30, Version 5.6.2
        * collectd: A compile error on AIX has been fixed: "MSG_DONTWAIT" is not
          available on AIX. Thanks to Chao Yang.
index f927399002f17aa90ecdc92656ee001cfbd7d776..ae027a36881d75e2145919a545bfd426ab31b9fb 100644 (file)
@@ -89,9 +89,12 @@ nodist_pkgconfig_DATA = \
 
 pkginclude_HEADERS = \
        src/libcollectdclient/collectd/client.h \
-       src/libcollectdclient/collectd/network.h \
+       src/libcollectdclient/collectd/lcc_features.h \
        src/libcollectdclient/collectd/network_buffer.h \
-       src/libcollectdclient/collectd/lcc_features.h
+       src/libcollectdclient/collectd/network.h \
+       src/libcollectdclient/collectd/network_parse.h \
+       src/libcollectdclient/collectd/server.h \
+       src/libcollectdclient/collectd/types.h
 
 lib_LTLIBRARIES = libcollectdclient.la
 
@@ -137,7 +140,8 @@ check_PROGRAMS = \
        test_utils_mount \
        test_utils_subst \
        test_utils_time \
-       test_utils_vl_lookup
+       test_utils_vl_lookup \
+       test_libcollectd_network_parse
 
 
 TESTS = $(check_PROGRAMS)
@@ -195,6 +199,8 @@ collectd_SOURCES = \
        src/daemon/configfile.h \
        src/daemon/filter_chain.c \
        src/daemon/filter_chain.h \
+       src/daemon/globals.c \
+       src/daemon/globals.h \
        src/daemon/meta_data.c \
        src/daemon/meta_data.h \
        src/daemon/plugin.c \
@@ -246,8 +252,8 @@ collectdmon_SOURCES = src/collectdmon.c
 
 collectd_nagios_SOURCES = src/collectd-nagios.c
 collectd_nagios_CPPFLAGS = $(AM_CPPFLAGS) \
-       -I$(srcdir)/src/libcollectdclient/collectd \
-       -I$(top_builddir)/src/libcollectdclient/collectd
+       -I$(srcdir)/src/libcollectdclient \
+       -I$(top_builddir)/src/libcollectdclient
 collectd_nagios_LDADD = libcollectdclient.la
 if BUILD_WITH_LIBSOCKET
 collectd_nagios_LDADD += -lsocket
@@ -259,8 +265,8 @@ endif
 
 collectdctl_SOURCES = src/collectdctl.c
 collectdctl_CPPFLAGS = $(AM_CPPFLAGS) \
-       -I$(srcdir)/src/libcollectdclient/collectd \
-       -I$(top_builddir)/src/libcollectdclient/collectd
+       -I$(srcdir)/src/libcollectdclient \
+       -I$(top_builddir)/src/libcollectdclient
 collectdctl_LDADD = libcollectdclient.la
 if BUILD_WITH_LIBSOCKET
 collectdctl_LDADD += -lsocket
@@ -272,8 +278,8 @@ endif
 
 collectd_tg_SOURCES = src/collectd-tg.c
 collectd_tg_CPPFLAGS = $(AM_CPPFLAGS) \
-       -I$(srcdir)/src/libcollectdclient/collectd \
-       -I$(top_builddir)/src/libcollectdclient/collectd
+       -I$(srcdir)/src/libcollectdclient \
+       -I$(top_builddir)/src/libcollectdclient
 collectd_tg_LDADD = \
        $(PTHREAD_LIBS) \
        libheap.la \
@@ -475,20 +481,34 @@ endif
 libcollectdclient_la_SOURCES = \
        src/libcollectdclient/client.c \
        src/libcollectdclient/network.c \
-       src/libcollectdclient/network_buffer.c
+       src/libcollectdclient/network_buffer.c \
+       src/libcollectdclient/network_parse.c \
+       src/libcollectdclient/server.c
 libcollectdclient_la_CPPFLAGS = \
        $(AM_CPPFLAGS) \
-       -I$(srcdir)/src/libcollectdclient/collectd \
-       -I$(top_builddir)/src/libcollectdclient/collectd \
+       -I$(srcdir)/src/libcollectdclient \
+       -I$(top_builddir)/src/libcollectdclient \
        -I$(srcdir)/src/daemon
-libcollectdclient_la_LDFLAGS = -version-info 1:0:0
-libcollectdclient_la_LIBADD = 
+libcollectdclient_la_LDFLAGS = -version-info 2:0:1
+libcollectdclient_la_LIBADD = -lm
 if BUILD_WITH_LIBGCRYPT
 libcollectdclient_la_CPPFLAGS += $(GCRYPT_CPPFLAGS)
 libcollectdclient_la_LDFLAGS += $(GCRYPT_LDFLAGS)
 libcollectdclient_la_LIBADD += $(GCRYPT_LIBS)
 endif
 
+# network_parse_test.c includes network_parse.c, so no need to link with
+# libcollectdclient.so.
+test_libcollectd_network_parse_SOURCES = src/libcollectdclient/network_parse_test.c
+test_libcollectd_network_parse_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       -I$(srcdir)/src/libcollectdclient \
+       -I$(top_builddir)/src/libcollectdclient
+if BUILD_WITH_LIBGCRYPT
+test_libcollectd_network_parse_CPPFLAGS += $(GCRYPT_CPPFLAGS)
+test_libcollectd_network_parse_LDFLAGS = $(GCRYPT_LDFLAGS)
+test_libcollectd_network_parse_LDADD = $(GCRYPT_LIBS)
+endif
 
 liboconfig_la_SOURCES = \
        src/liboconfig/oconfig.c \
@@ -783,16 +803,18 @@ if BUILD_PLUGIN_DPDKEVENTS
 pkglib_LTLIBRARIES += dpdkevents.la
 dpdkevents_la_SOURCES = src/dpdkevents.c src/utils_dpdk.c src/utils_dpdk.h
 dpdkevents_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDPDK_CPPFLAGS)
+dpdkevents_la_CFLAGS = $(AM_CFLAGS) $(LIBDPDK_CFLAGS)
 dpdkevents_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(LIBDPDK_LDFLAGS)
-dpdkevents_la_LIBADD = -ldpdk
+dpdkevents_la_LIBADD = $(LIBDPDK_LIBS)
 endif
 
 if BUILD_PLUGIN_DPDKSTAT
 pkglib_LTLIBRARIES += dpdkstat.la
 dpdkstat_la_SOURCES = src/dpdkstat.c src/utils_dpdk.c src/utils_dpdk.h
 dpdkstat_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDPDK_CPPFLAGS)
+dpdkstat_la_CFLAGS = $(AM_CFLAGS) $(LIBDPDK_CFLAGS)
 dpdkstat_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(LIBDPDK_LDFLAGS)
-dpdkstat_la_LIBADD = -ldpdk
+dpdkstat_la_LIBADD = $(LIBDPDK_LIBS)
 endif
 
 if BUILD_PLUGIN_DRBD
@@ -888,6 +910,14 @@ hugepages_la_SOURCES = src/hugepages.c
 hugepages_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 endif
 
+if BUILD_PLUGIN_INTEL_PMU
+pkglib_LTLIBRARIES += intel_pmu.la
+intel_pmu_la_SOURCES = src/intel_pmu.c
+intel_pmu_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBJEVENTS_CPPFLAGS)
+intel_pmu_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBJEVENTS_LDFLAGS)
+intel_pmu_la_LIBADD = $(BUILD_WITH_LIBJEVENTS_LIBS)
+endif
+
 if BUILD_PLUGIN_INTEL_RDT
 pkglib_LTLIBRARIES += intel_rdt.la
 intel_rdt_la_SOURCES = src/intel_rdt.c
@@ -1526,6 +1556,12 @@ endif
 
 endif
 
+if BUILD_PLUGIN_SYNPROXY
+pkglib_LTLIBRARIES += synproxy.la
+synproxy_la_SOURCES = src/synproxy.c
+synproxy_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+endif
+
 if BUILD_PLUGIN_SYSLOG
 pkglib_LTLIBRARIES += syslog.la
 syslog_la_SOURCES = src/syslog.c
@@ -1646,7 +1682,9 @@ endif
 
 if BUILD_PLUGIN_TURBOSTAT
 pkglib_LTLIBRARIES += turbostat.la
-turbostat_la_SOURCES = src/turbostat.c
+turbostat_la_SOURCES = \
+       src/turbostat.c \
+       src/msr-index.h
 turbostat_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 endif
 
diff --git a/README b/README
index b0b59214d6550b1071dcbcc25bd2d4a95c95c1fa..ca86c84da4692b4d5307ae5ed284b7f0bd64eed5 100644 (file)
--- a/README
+++ b/README
@@ -140,6 +140,11 @@ Features
       hugepages can be found here:
       https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt.
 
+    - intel_pmu
+      The intel_pmu plugin reads performance counters provided by the Linux
+      kernel perf interface. The plugin uses jevents library to resolve named
+      events to perf events and access perf interface.
+
     - intel_rdt
       The intel_rdt plugin collects information provided by monitoring features
       of Intel Resource Director Technology (Intel(R) RDT) like Cache Monitoring
@@ -240,7 +245,7 @@ Features
 
     - netapp
       Plugin to query performance values from a NetApp storage system using the
-      “Manage ONTAP” SDK provided by NetApp.
+      “Manage ONTAP” SDK provided by NetApp.
 
     - netlink
       Very detailed Linux network interface and routing statistics. You can get
@@ -253,8 +258,7 @@ Features
       plugin of choice for that.
 
     - nfs
-      NFS Procedures: Which NFS command were called how often. Only NFSv2 and
-      NFSv3 right now.
+      NFS Procedures: Which NFS command were called how often.
 
     - nginx
       Collects statistics from `nginx' (speak: engine X), a HTTP and mail
@@ -786,6 +790,13 @@ Prerequisites
     For querying iptables counters.
     <http://netfilter.org/>
 
+  * libjevents (optional)
+    The jevents library is used by the `intel_pmu' plugin to access the Linux
+    kernel perf interface.
+    Note: the library should be build with -fPIC flag to be linked with
+    intel_pmu shared object correctly.
+    <https://github.com/andikleen/pmu-tools>
+
   * libjvm (optional)
     Library that encapsulates the `Java Virtual Machine' (JVM). This library is
     used by the `java' plugin to execute Java bytecode.
index 35b139c45ba21326cd0b2c9473d683f710a55f2e..55f78d8a73d3d9950d2dcce7b5f16f4f6cd77372 100644 (file)
@@ -168,6 +168,7 @@ AC_CHECK_HEADERS_ONCE([ \
   pthread_np.h \
   pwd.h \
   regex.h \
+  sys/endian.h \
   sys/fs_types.h \
   sys/fstyp.h \
   sys/ioctl.h \
@@ -520,32 +521,6 @@ if test "x$ac_system" = "xLinux"; then
     ]]
   )
   # For the turbostat plugin
-  AC_CHECK_HEADERS([asm/msr-index.h],
-    [have_asm_msrindex_h="yes"],
-    [have_asm_msrindex_h="no"]
-  )
-
-  if test "x$have_asm_msrindex_h" = "xyes"; then
-    AC_CACHE_CHECK([whether asm/msr-index.h has MSR_PKG_C10_RESIDENCY],
-      [c_cv_have_usable_asm_msrindex_h],
-      [
-        AC_COMPILE_IFELSE(
-          [
-            AC_LANG_PROGRAM(
-              [[#include<asm/msr-index.h>]],
-              [[
-                int y = MSR_PKG_C10_RESIDENCY;
-                return(y);
-              ]]
-            )
-          ],
-          [c_cv_have_usable_asm_msrindex_h="yes"],
-          [c_cv_have_usable_asm_msrindex_h="no"],
-        )
-      ]
-    )
-  fi
-
   AC_CHECK_HEADERS([cpuid.h],
     [have_cpuid_h="yes"],
     [have_cpuid_h="no (cpuid.h not found)"]
@@ -2349,7 +2324,9 @@ AC_SUBST(BUILD_WITH_LIBDBI_LIBS)
 
 # --with-libdpdk {{{
 AC_ARG_VAR([LIBDPDK_CPPFLAGS], [Preprocessor flags for libdpdk])
+AC_ARG_VAR([LIBDPDK_CFLAGS], [Compiler flags for libdpdk])
 AC_ARG_VAR([LIBDPDK_LDFLAGS], [Linker flags for libdpdk])
+AC_ARG_VAR([LIBDPDK_LIBS], [Libraries to link for libdpdk])
 
 AC_ARG_WITH([libdpdk],
   [AS_HELP_STRING([--without-libdpdk], [Disable libdpdk.])],
@@ -2358,11 +2335,26 @@ AC_ARG_WITH([libdpdk],
 )
 
 if test "x$with_libdpdk" != "xno"; then
+  PKG_CHECK_MODULES([DPDK], [libdpdk], [],
+    [AC_MSG_NOTICE([no DPDK pkg-config, using defaults])])
   if test "x$LIBDPDK_CPPFLAGS" = "x"; then
     LIBDPDK_CPPFLAGS="-I/usr/include/dpdk"
   fi
+  if test "x$LIBDPDK_CFLAGS" = "x"; then
+    LIBDPDK_CFLAGS="$DPDK_CFLAGS"
+    LIBDPDK_CPPFLAGS="$LIBDPDK_CPPFLAGS $DPDK_CFLAGS"
+  fi
+  if test "x$LIBDPDK_LIBS" = "x"; then
+    if test "x$DPDK_LIBS" != "x"; then
+      LIBDPDK_LIBS="$DPDK_LIBS"
+    else
+      LIBDPDK_LIBS="-ldpdk"
+    fi
+  fi
   SAVE_CPPFLAGS="$CPPFLAGS"
   CPPFLAGS="$LIBDPDK_CPPFLAGS $CPPFLAGS"
+  SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$LIBDPDK_CFLAGS $CFLAGS"
   AC_CHECK_HEADERS([rte_config.h],
     [
       with_libdpdk="yes"
@@ -2384,6 +2376,7 @@ if test "x$with_libdpdk" != "xno"; then
     [with_libdpdk="no (rte_config.h not found)"]
   )
   CPPFLAGS="$SAVE_CPPFLAGS"
+  CFLAGS="$SAVE_CFLAGS"
 fi
 
 if test "x$with_libdpdk" = "xyes"; then
@@ -4429,6 +4422,49 @@ AC_SUBST([BUILD_WITH_LIBPQOS_LDFLAGS])
 AC_SUBST([BUILD_WITH_LIBPQOS_LIBS])
 # }}}
 
+# --with-libjevents {{{
+with_libjevents_cppflags=""
+with_libjevents_ldflags=""
+AC_ARG_WITH([libjevents],
+  [AS_HELP_STRING([--with-libjevents@<:@=PREFIX@:>@], [Path to libjevents.])],
+  [
+    if test "x$withval" != "xno" && test "x$withval" != "xyes"; then
+      with_libjevents_cppflags="-I$withval/include"
+      with_libjevents_ldflags="-L$withval/lib"
+      with_libjevents="yes"
+    else
+      with_libjevents="$withval"
+    fi
+  ],
+  [with_libjevents="yes"]
+)
+
+if test "x$with_libjevents" = "xyes"; then
+  SAVE_CPPFLAGS="$CPPFLAGS"
+  CPPFLAGS="$CPPFLAGS $with_libjevents_cppflags"
+
+  AC_CHECK_HEADERS([jevents.h], [with_libjevents="yes"], [with_libjevents="no (jevents.h not found)"])
+
+  CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libjevents" = "xyes"; then
+  SAVE_LDFLAGS="$LDFLAGS"
+  LDFLAGS="$LDFLAGS $with_libjevents_ldflags"
+
+  AC_CHECK_LIB([jevents], [json_events], [with_libjevents="yes"], [with_libjevents="no (Can't find libjevents)"])
+
+  LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libjevents" = "xyes"; then
+  BUILD_WITH_LIBJEVENTS_CPPFLAGS="$with_libjevents_cppflags"
+  BUILD_WITH_LIBJEVENTS_LDFLAGS="$with_libjevents_ldflags"
+  BUILD_WITH_LIBJEVENTS_LIBS="-ljevents"
+fi
+AC_SUBST([BUILD_WITH_LIBJEVENTS_CPPFLAGS])
+AC_SUBST([BUILD_WITH_LIBJEVENTS_LDFLAGS])
+AC_SUBST([BUILD_WITH_LIBJEVENTS_LIBS])
+# }}}
+
 # --with-libprotobuf {{{
 with_libprotobuf_cppflags=""
 with_libprotobuf_ldflags=""
@@ -6083,6 +6119,7 @@ plugin_fscache="no"
 plugin_gps="no"
 plugin_grpc="no"
 plugin_hugepages="no"
+plugin_intel_pmu="no"
 plugin_intel_rdt="no"
 plugin_interface="no"
 plugin_ipc="no"
@@ -6106,6 +6143,7 @@ plugin_python="no"
 plugin_serial="no"
 plugin_smart="no"
 plugin_swap="no"
+plugin_synproxy="no"
 plugin_tape="no"
 plugin_tcpconns="no"
 plugin_ted="no"
@@ -6150,6 +6188,7 @@ if test "x$ac_system" = "xLinux"; then
   plugin_protocols="yes"
   plugin_serial="yes"
   plugin_swap="yes"
+  plugin_synproxy="yes"
   plugin_tcpconns="yes"
   plugin_thermal="yes"
   plugin_uptime="yes"
@@ -6162,7 +6201,7 @@ if test "x$ac_system" = "xLinux"; then
     plugin_ipvs="yes"
   fi
 
-  if test "x$c_cv_have_usable_asm_msrindex_h" = "xyes" && test "x$have_cpuid_h" = "xyes"; then
+  if test "x$have_cpuid_h" = "xyes"; then
     plugin_turbostat="yes"
   fi
 
@@ -6425,8 +6464,7 @@ if test "x$with_libxenctrl" = "xyes"; then
   plugin_xencpu="yes"
 fi
 
-if test "x$with_libdpdk" = "xyes"
-then
+if test "x$with_libdpdk" = "xyes"; then
   plugin_dpdkevents="$dpdk_keepalive"
   plugin_dpdkstat="yes"
 fi
@@ -6491,6 +6529,7 @@ AC_PLUGIN([gps],                 [$plugin_gps],             [GPS plugin])
 AC_PLUGIN([grpc],                [$plugin_grpc],            [gRPC plugin])
 AC_PLUGIN([hddtemp],             [yes],                     [Query hddtempd])
 AC_PLUGIN([hugepages],           [$plugin_hugepages],       [Hugepages statistics])
+AC_PLUGIN([intel_pmu],           [$with_libjevents],        [Intel performance monitor plugin])
 AC_PLUGIN([intel_rdt],           [$with_libpqos],           [Intel RDT monitor plugin])
 AC_PLUGIN([interface],           [$plugin_interface],       [Interface traffic statistics])
 AC_PLUGIN([ipc],                 [$plugin_ipc],             [IPC statistics])
@@ -6562,6 +6601,7 @@ AC_PLUGIN([snmp],                [$with_libnetsnmp],        [SNMP querying plugi
 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([syslog],              [$have_syslog],            [Syslog logging plugin])
 AC_PLUGIN([table],               [yes],                     [Parsing of tabular data])
 AC_PLUGIN([tail],                [yes],                     [Parsing of logfiles])
@@ -6809,6 +6849,7 @@ AC_MSG_RESULT([    libhiredis  . . . . . $with_libhiredis])
 AC_MSG_RESULT([    libi2c-dev  . . . . . $with_libi2c])
 AC_MSG_RESULT([    libiokit  . . . . . . $with_libiokit])
 AC_MSG_RESULT([    libiptc . . . . . . . $with_libiptc])
+AC_MSG_RESULT([    libjevents  . . . . . $with_libjevents])
 AC_MSG_RESULT([    libjvm  . . . . . . . $with_java])
 AC_MSG_RESULT([    libkstat  . . . . . . $with_kstat])
 AC_MSG_RESULT([    libkvm  . . . . . . . $with_libkvm])
@@ -6907,6 +6948,7 @@ AC_MSG_RESULT([    gps . . . . . . . . . $enable_gps])
 AC_MSG_RESULT([    grpc  . . . . . . . . $enable_grpc])
 AC_MSG_RESULT([    hddtemp . . . . . . . $enable_hddtemp])
 AC_MSG_RESULT([    hugepages . . . . . . $enable_hugepages])
+AC_MSG_RESULT([    intel_pmu . . . . . . $enable_intel_pmu])
 AC_MSG_RESULT([    intel_rdt . . . . . . $enable_intel_rdt])
 AC_MSG_RESULT([    interface . . . . . . $enable_interface])
 AC_MSG_RESULT([    ipc . . . . . . . . . $enable_ipc])
@@ -6977,6 +7019,7 @@ AC_MSG_RESULT([    snmp  . . . . . . . . $enable_snmp])
 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([    syslog  . . . . . . . $enable_syslog])
 AC_MSG_RESULT([    table . . . . . . . . $enable_table])
 AC_MSG_RESULT([    tail_csv  . . . . . . $enable_tail_csv])
diff --git a/contrib/curl_json/php-fpm.conf b/contrib/curl_json/php-fpm.conf
new file mode 100644 (file)
index 0000000..34b8b67
--- /dev/null
@@ -0,0 +1,27 @@
+# Example configuration for PHP-FPM
+<Plugin "curl_json">
+  <URL "http://nginx-status/php-fpm-status?json">
+    Plugin "phpfpm"
+    Instance "main"
+    <Key "accepted conn">
+      Type "total_requests"
+      Instance "accepted"
+    </Key>
+    <Key "slow requests">
+      Type "total_requests"
+      Instance "slow"
+    </Key>
+    <Key "listen queue">
+      Type "queue_length"
+      Instance "listen"
+    </Key>
+    <Key "active processes">
+      Type "vs_processes"
+      Instance "active"
+    </Key>
+    <Key "total processes">
+      Type "vs_processes"
+      Instance "total"
+    </Key>
+  </URL>
+</Plugin>
index b36e2a272504dc9a079daed26ca611be10b38afd..7862663e3550dfd7ee52825848fa47b8d04c5b2e 100644 (file)
@@ -16,7 +16,7 @@
 #
 # - fetch the desired collectd release file from https://collectd.org/files/
 #   and save it in your ~/rpmbuild/SOURCES/ directory (or build your own out of
-#   the git repository: ./build.sh && ./configure && make-dist-bz2)
+#   the git repository: ./build.sh && ./configure && make dist)
 #
 # - copy this file in your ~/rpmbuild/SPECS/ directory. Make sure the
 #   "Version:" tag matches the version from the tarball.
 %define with_grpc 0%{!?_without_grpc:0}
 # plugin lpar disabled, requires AIX
 %define with_lpar 0%{!?_without_lpar:0}
+# plugin intel_pmu disabled, requires libjevents
+%define with_intel_pmu 0%{!?_without_intel_pmu:0}
 # plugin intel_rdt disabled, requires intel-cmt-cat
 %define with_intel_rdt 0%{!?_without_intel_rdt:0}
 # plugin mic disabled, requires Mic
 Summary:       Statistics collection and monitoring daemon
 Name:          collectd
 Version:       5.7.1
-Release:       6%{?dist}
+Release:       7%{?dist}
 URL:           https://collectd.org
 Source:                https://collectd.org/files/%{name}-%{version}.tar.bz2
 License:       GPLv2
@@ -470,6 +472,16 @@ The HDDTemp plugin collects the temperature of hard disks. The temperatures are
 provided via SMART and queried by the external hddtemp daemon.
 %endif
 
+%if %{with_intel_pmu}
+%package intel_pmu
+Summary:       Intel PMU plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+%description intel_pmu
+The intel_pmu plugin reads performance counters provided by the Linux
+kernel perf interface.
+%endif
+
 %if %{with_intel_rdt}
 %package intel_rdt
 Summary:       Intel RDT plugin for collectd
@@ -509,8 +521,8 @@ the byte- and packet-counters of selected rules and submit them to collectd.
 Summary:       Java plugin for collectd
 Group:         System Environment/Daemons
 Requires:      %{name}%{?_isa} = %{version}-%{release}
-BuildRequires: java-devel, jpackage-utils
-Requires:      java, jpackage-utils
+BuildRequires: java-devel >= 1.6, jpackage-utils >= 1.6
+Requires:      java >= 1.6, jpackage-utils >= 1.6
 %description java
 This plugin for collectd allows plugins to be written in Java and executed
 in an embedded JVM.
@@ -1236,6 +1248,12 @@ Collectd utilities
 %define _with_hugepages --disable-hugepages
 %endif
 
+%if %{with_intel_pmu}
+%define _with_intel_pmu --enable-intel_pmu
+%else
+%define _with_intel_pmu --disable-intel_pmu
+%endif
+
 %if %{with_intel_rdt}
 %define _with_intel_rdt --enable-intel_rdt
 %else
@@ -1902,6 +1920,7 @@ Collectd utilities
        %{?_with_grpc} \
        %{?_with_hddtemp} \
        %{?_with_hugepages} \
+       %{?_with_intel_pmu} \
        %{?_with_intel_rdt} \
        %{?_with_interface} \
        %{?_with_ipc} \
@@ -2466,6 +2485,11 @@ fi
 %{_libdir}/%{name}/hddtemp.so
 %endif
 
+%if %{with_intel_pmu}
+%files intel_pmu
+%{_libdir}/%{name}/intel_pmu.so
+%endif
+
 %if %{with_intel_rdt}
 %files intel_rdt
 %{_libdir}/%{name}/intel_rdt.so
@@ -2699,6 +2723,9 @@ fi
 %doc contrib/
 
 %changelog
+* Fri Aug 18 2017 Ruben Kerkhof <ruben@rubenkerkhof.com> - 5.7.1-7
+- Add new intel_pmu plugin
+
 * Sun Mar 05 2017 Ruben Kerkhof <ruben@rubenkerkhof.com> - 5.7.1-6
 - Move recently added plugins to subpackages
 
index 6333d49a211ac63ff4d4291273d0f5073e5b7619..9c037a4e3b12382d436157b8733db81e31736f84 100644 (file)
@@ -19,6 +19,7 @@ ProtectHome=true
 #   dns             CAP_NET_RAW
 #   exec            CAP_SETUID CAP_SETGID
 #   intel_rdt       CAP_SYS_RAWIO
+#   intel_pmu       CAP_SYS_ADMIN
 #   iptables        CAP_NET_ADMIN
 #   ping            CAP_NET_RAW
 #   smart           CAP_SYS_RAWIO
index 952c5418d1f5143bc4513545ba5cfa1dc9c32aec..fde3afaf76acda63dc77f03b1c28f8d86219ad45 100644 (file)
@@ -38,6 +38,16 @@ message Identifier {
   string type_instance = 5;
 }
 
+message MetadataValue {
+  oneof value {
+    string string_value = 1;
+    int64 int64_value = 2;
+    uint64 uint64_value = 3;
+    double double_value = 4;
+    bool bool_value = 5;
+  };
+}
+
 message Value {
   oneof value {
     uint64 counter = 1;
@@ -56,4 +66,5 @@ message ValueList {
   Identifier identifier = 4;
 
   repeated string ds_names = 5;
-}
+  map<string, MetadataValue> meta_data = 6;
+}
\ No newline at end of file
index 650760c3f14847cb168d6addea0f95cbbdea4723..07b2b57d0700cc0272e828d94bf273fcf05086cc 100644 (file)
@@ -216,29 +216,25 @@ static int config_add(oconfig_item_t *ci) {
     status = -1;
   }
 
-  if (status == 0) {
-    char callback_name[3 * DATA_MAX_NAME_LEN];
-
-    snprintf(callback_name, sizeof(callback_name), "apache/%s/%s",
-             (st->host != NULL) ? st->host : hostname_g,
-             (st->name != NULL) ? st->name : "default");
-
-    status = plugin_register_complex_read(
-        /* group = */ NULL,
-        /* name      = */ callback_name,
-        /* callback  = */ apache_read_host,
-        /* interval  = */ 0,
-        &(user_data_t){
-            .data = st, .free_func = apache_free,
-        });
-  }
-
   if (status != 0) {
     apache_free(st);
     return -1;
   }
 
-  return 0;
+  char callback_name[3 * DATA_MAX_NAME_LEN];
+
+  snprintf(callback_name, sizeof(callback_name), "apache/%s/%s",
+           (st->host != NULL) ? st->host : hostname_g,
+           (st->name != NULL) ? st->name : "default");
+
+  return plugin_register_complex_read(
+      /* group = */ NULL,
+      /* name      = */ callback_name,
+      /* callback  = */ apache_read_host,
+      /* interval  = */ 0,
+      &(user_data_t){
+          .data = st, .free_func = apache_free,
+      });
 } /* int config_add */
 
 static int config(oconfig_item_t *ci) {
index c2284cb3baa70d26ad8f7d4a9e09a031158a1929..8bee8e88e42085caf357d611e51160cfd7a3b7e0 100644 (file)
@@ -418,7 +418,7 @@ static void ceph_daemon_free(struct ceph_daemon *d) {
 }
 
 /* compact_ds_name removed the special characters ":", "_", "-" and "+" from the
- * intput string. Characters following these special characters are capitalized.
+ * input string. Characters following these special characters are capitalized.
  * Trailing "+" and "-" characters are replaces with the strings "Plus" and
  * "Minus". */
 static int compact_ds_name(char *buffer, size_t buffer_size, char const *src) {
index 4c54dad444e49837038477bbee3cad2254731d4c..89f73b8335ee7dd6cb349b57f7cc8ead574671e7 100644 (file)
@@ -71,7 +71,7 @@
 #endif
 #endif /* NAN_ZERO_ZERO */
 
-#include "libcollectdclient/collectd/client.h"
+#include "collectd/client.h"
 
 #define RET_OKAY 0
 #define RET_WARNING 1
index f4b5f141bbaf70ee06fa0b5ccfe71f38439a209e..1f46f6f30af232f11e3b471556c18073e178985a 100644 (file)
@@ -489,7 +489,7 @@ Methods defined here:
 
 =over 4
 
-=item B<dispatch>([type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None.  Dispatch a value list.
+=item B<dispatch>([type][, message][, plugin_instance][, type_instance][, plugin][, host][, time][, severity][, meta]) -> None.  Dispatch a notification.
 
 Dispatch this instance to the collectd process. The object has members for each
 of the possible arguments for this method. For a detailed explanation of these
@@ -515,6 +515,16 @@ generated.
 The severity of this notification. Assign or compare to I<NOTIF_FAILURE>,
 I<NOTIF_WARNING> or I<NOTIF_OKAY>.
 
+=item meta
+
+These are the meta data for the Notification object.
+It has to be a dictionary of numbers, strings or bools. All keys must be
+strings. I<int> and I<long> objects will be dispatched as signed integers unless
+they are between 2**63 and 2**64-1, which will result in a unsigned integer.
+One of these storage classes can be forced by using the classes
+B<collectd.Signed> and B<collectd.Unsigned>. A meta object received by a
+notification callback will always contain B<Signed> or B<Unsigned> objects.
+
 =back
 
 =head1 FUNCTIONS
index edb95060f2baea9015056a038901d06f6a79cd81..d615088e5afdc77fd34c5e64b2a18f13eb389391 100644 (file)
@@ -36,6 +36,8 @@ collectd-snmp - Documentation of collectd's C<snmp plugin>
       Community "community_string"
       Collect "std_traffic"
       Interval 120
+      Timeout 10
+      Retries 1
     </Host>
     <Host "some.server.mydomain.org">
       Address "192.168.0.42"
@@ -60,6 +62,8 @@ collectd-snmp - Documentation of collectd's C<snmp plugin>
       Community "more_communities"
       Collect "powerplus_voltge_input"
       Interval 300
+      Timeout 5
+      Retries 5
     </Host>
   </Plugin>
 
@@ -78,7 +82,7 @@ and ten threads are used.
 =head1 CONFIGURATION
 
 Since the aim of the C<snmp plugin> is to provide a generic interface to SNMP,
-it's configuration is not trivial and may take some time.
+its configuration is not trivial and may take some time.
 
 Since the C<Net-SNMP> library is used you can use all the environment variables
 that are interpreted by that package. See L<snmpcmd(1)> for more details.
@@ -281,6 +285,15 @@ switches, embedded devices, rack monitoring systems and so on. Since the
 B<Step> of generated RRD files depends on this setting it's wise to select a
 reasonable value once and never change it.
 
+=item B<Timeout> I<Seconds>
+
+How long to wait for a response. The C<Net-SNMP> library default is 1 second.
+
+=item B<Retries> I<Integer>
+
+The number of times that a query should be retried after the Timeout expires.
+The C<Net-SNMP> library default is 5.
+
 =back
 
 =head1 SEE ALSO
index d8b2ea19d29bbe2b7e945e4c6f83c036e1b6c34e..48f2dc43cde3851adee94fd86c389ea26ea78a75 100644 (file)
@@ -44,8 +44,8 @@
 
 #include "utils_heap.h"
 
-#include "libcollectdclient/collectd/client.h"
-#include "libcollectdclient/collectd/network.h"
+#include "collectd/client.h"
+#include "collectd/network.h"
 
 #define DEF_NUM_HOSTS 1000
 #define DEF_NUM_PLUGINS 20
index 6b59d4d550aef4ce33842c16f122e03ed3d3751d..261abdfdead47776d678b4f48479d0e93f07f0d1 100644 (file)
 #@BUILD_PLUGIN_GRPC_TRUE@LoadPlugin grpc
 #@BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
 #@BUILD_PLUGIN_HUGEPAGES_TRUE@LoadPlugin hugepages
+#@BUILD_PLUGIN_INTEL_PMU_TRUE@LoadPlugin intel_pmu
 #@BUILD_PLUGIN_INTEL_RDT_TRUE@LoadPlugin intel_rdt
 @BUILD_PLUGIN_INTERFACE_TRUE@@BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
 #@BUILD_PLUGIN_IPC_TRUE@LoadPlugin ipc
 #  ReportByCpu true
 #  ReportByState true
 #  ValuesPercentage false
+#  ReportNumCpu false
+#  ReportGuestState false
+#  SubtractGuestState true
 #</Plugin>
 #
 #<Plugin csv>
 #<Plugin curl_xml>
 #  <URL "http://localhost/stats.xml">
 #    Host "my_host"
+#    #Plugin "stats"
 #    Instance "some_instance"
 #    User "collectd"
 #    Password "thaiNg0I"
 #      Type "magic_level"
 #      #InstancePrefix "prefix-"
 #      InstanceFrom "td[1]"
+#      #PluginInstanceFrom "td[1]"
 #      ValuesFrom "td[2]/span[@class=\"level\"]"
 #    </XPath>
 #  </URL>
 #              </Result>
 #      </Query>
 #      <Database "customers_db">
+#              #Plugin "mycompany"
 #              Driver "mysql"
 #              DriverOption "host" "localhost"
 #              DriverOption "username" "collectd"
 
 #<Plugin filecount>
 #      <Directory "/path/to/dir">
+#              #Plugin "foo"
 #              Instance "foodir"
 #              Name "*.conf"
 #              MTime "-5m"
 #              Size "+10k"
 #              Recursive true
 #              IncludeHidden false
+#              RegularOnly true
+#              #FilesSizeType "bytes"
+#              #FilesCountType "files"
+#              #TypeInstance "instance"
 #      </Directory>
 #</Plugin>
 
 #    ValuesPercentage false
 #</Plugin>
 
+#<Plugin intel_pmu>
+#    ReportHardwareCacheEvents true
+#    ReportKernelPMUEvents true
+#    ReportSoftwareEvents true
+#    EventList "/var/cache/pmu/GenuineIntel-6-2D-core.json"
+#    HardwareEvents "L2_RQSTS.CODE_RD_HIT,L2_RQSTS.CODE_RD_MISS" "L2_RQSTS.ALL_CODE_RD"
+#</Plugin>
+
 #<Plugin "intel_rdt">
 #  Cores "0-2"
 #</Plugin>
 #</Plugin>
 
 #<Plugin ipmi>
-#      Sensor "some_sensor"
-#      Sensor "another_one"
-#      IgnoreSelected false
-#      NotifySensorAdd false
-#      NotifySensorRemove true
-#      NotifySensorNotPresent false
+#      <Instance "local">
+#              Sensor "some_sensor"
+#              Sensor "another_one"
+#              IgnoreSelected false
+#              NotifySensorAdd false
+#              NotifySensorRemove true
+#              NotifySensorNotPresent false
+#              NotifyIPMIConnectionState false
+#              SELEnabled false
+#              SELClearEvent false
+#      </Instance>
+#      <Instance "remote">
+#              Host "server.example.com"
+#              Address  "1.2.3.4"
+#              Username "user"
+#              Password "secret"
+#              #AuthType "md5"
+#              Sensor "some_sensor"
+#              Sensor "another_one"
+#              IgnoreSelected false
+#              NotifySensorAdd false
+#              NotifySensorRemove true
+#              NotifySensorNotPresent false
+#              NotifyIPMIConnectionState false
+#              SELEnabled false
+#              SELClearEvent false
+#      </Instance>
 #</Plugin>
 
 #<Plugin iptables>
 #</Plugin>
 
 #<Plugin mcelog>
-#      McelogClientSocket "/var/run/mcelog-client"
-#      McelogLogfile "/var/log/mcelog"
+#  <Memory>
+#    McelogClientSocket "/var/run/mcelog-client"
+#    PersistentNotification false
+#  </Memory>
+#  McelogLogfile "/var/log/mcelog"
 #</Plugin>
 
 #<Plugin md>
 #              QoS 2
 #              Topic "collectd/#"
 #              CleanSession true
+#              CACert "/etc/ssl/ca.crt"
+#              CertificateFile "/etc/ssl/client.crt"
+#              CertificateKeyFile "/etc/ssl/client.pem"
+#              TLSProtocol "tlsv1.2"
+#              CipherSuite "ciphers"
 #      </Subscribe>
 #</Plugin>
 
 #      CacheFlush 1800
 @LOAD_PLUGIN_NETWORK@</Plugin>
 
+#<Plugin nfs>
+#      ReportV2 false
+#      #ReportV3 false
+#      #ReportV4 false
+#</Plugin>
+
 #<Plugin nginx>
 #      URL "http://localhost/status?auto"
 #      User "www-user"
 #    </Result>
 #  </Query>
 #  <Database "product_information">
+#    #Plugin "warehouse"
 #    ConnectID "db01"
 #    Username "oracle"
 #    Password "secret"
 #              StoreRates true
 #      </Writer>
 #      <Database foo>
+#              #Plugin "kingdom"
 #              Host "hostname"
 #              Port "5432"
 #              User "username"
 #<Plugin processes>
 #      CollectFileDescriptor true
 #      CollectContextSwitch true
+#      CollectMemoryMaps true
 #      Process "name"
 #      ProcessMatch "name" "regex"
 #      <Process "collectd">
 #       Community "community_string"
 #       Collect "std_traffic"
 #       Interval 120
+#      Timeout 10
+#      Retries 1
 #   </Host>
 #   <Host "some.server.mydomain.org">
 #       Address "192.168.0.42"
 #       Community "more_communities"
 #       Collect "powerplus_voltge_input"
 #       Interval 300
+#      Timeout 5
+#      Retries 5
 #   </Host>
 #</Plugin>
 
 #      ReportBytes true
 #      ValuesAbsolute true
 #      ValuesPercentage false
+#      ReportIO true
 #</Plugin>
 
 #<Plugin table>
 #      <Table "/proc/slabinfo">
+#              #Plugin "table"
 #              Instance "slabinfo"
 #              Separator " "
 #              <Result>
 #        Bucket 0.5 1.0   # -> bucket-latency-foo-0.5_1
 #        Bucket 1.0 2.0   # -> bucket-latency-foo-1_2
 #        Bucket 2.0 0     # -> bucket-latency-foo-2_inf
+#        #BucketType "bucket"
 #      </DSType>
 #      Type "latency"
 #      Instance "foo"
 #      SystemManagementInterrupt true
 #      DigitalTemperatureSensor true
 #      PackageThermalManagement true
-#      RunningAveragePowerLimit "7"    
+#      RunningAveragePowerLimit "7"
 #</Plugin>
 
 #<Plugin unixsock>
 #      CollectPurge false         # Varnish 2 only
 #      CollectSession false
 #      CollectSHM true
-#      CollectSMA false           # Varnish 2 only
+#      CollectSMA false           # Varnish 2 & 4 only
 #      CollectSMS false
 #      CollectSM false            # Varnish 2 only
 #      CollectStruct false
 #      CollectVCL false
 #      CollectVSM false           # Varnish 4 only
 #      CollectWorkers false
+#      CollectLock false          # Varnish 4 only
+#      CollectMempool false       # Varnish 4 only
+#      CollectManagement false    # Varnish 4 only
+#      CollectSMF false           # Varnish 4 only
+#      CollectVBE false           # Varnish 4 only
+#      CollectMSE false           # Varnish-Plus 4 only
 #   </Instance>
 #</Plugin>
 
 #              Header "X-Custom-Header: custom_value"
 #              SSLVersion "TLSv1"
 #              Format "Command"
+#              Prefix "collectd"  # metric prefix, only available for KAIROSDB format
 #              Attribute "key" "value"     # only available for KAIROSDB format
 #              TTL 0   # data ttl, only available for KAIROSDB format
 #              Metrics true
index 67ee0a934d788fc19d3e5d1b03ac43d9f0c965a8..4c4c261e3c185346c53d05ff8658623e2db1f2b2 100644 (file)
@@ -1474,6 +1474,19 @@ in the un-aggregated (per-CPU, per-state) mode as well.
 When set to B<true>, reports the number of available CPUs.
 Defaults to B<false>.
 
+=item B<ReportGuestState> B<false>|B<true>
+
+When set to B<true>, reports the "guest" and "guest_nice" CPU states.
+Defaults to B<false>.
+
+=item B<SubtractGuestState> B<false>|B<true>
+
+This option is only considered when B<ReportGuestState> is set to B<true>.
+"guest" and "guest_nice" are included in respectively "user" and "nice".
+If set to B<true>, "guest" will be subtracted from "user" and "guest_nice"
+will be subtracted from "nice".
+Defaults to B<true>.
+
 =back
 
 =head2 Plugin C<cpufreq>
@@ -1611,6 +1624,7 @@ finance page and dispatch the value to collectd.
 
   <Plugin curl>
     <Page "stock_quotes">
+      Plugin "quotes"
       URL "http://finance.google.com/finance?q=NYSE%3AAMD"
       User "foo"
       Password "bar"
@@ -1642,6 +1656,11 @@ The following options are valid within B<Page> blocks:
 
 =over 4
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting values.
+Defaults to C<curl>.
+
 =item B<URL> I<URL>
 
 URL of the web site to retrieve. Since a regular expression will be used to
@@ -1802,6 +1821,11 @@ The following options are valid within B<URL> blocks:
 Use I<Name> as the host name when submitting values. Defaults to the global
 host name setting.
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting values.
+Defaults to C<curl_json>.
+
 =item B<Instance> I<Instance>
 
 Sets the plugin instance to I<Instance>.
@@ -1864,6 +1888,7 @@ The B<curl_xml plugin> uses B<libcurl> (L<http://curl.haxx.se/>) and B<libxml2>
  <Plugin "curl_xml">
    <URL "http://localhost/stats.xml">
      Host "my_host"
+     #Plugin "curl_xml"
      Instance "some_instance"
      User "collectd"
      Password "thaiNg0I"
@@ -1877,6 +1902,7 @@ The B<curl_xml plugin> uses B<libcurl> (L<http://curl.haxx.se/>) and B<libxml2>
        Type "magic_level"
        #InstancePrefix "prefix-"
        InstanceFrom "td[1]"
+       #PluginInstanceFrom "td[1]"
        ValuesFrom "td[2]/span[@class=\"level\"]"
      </XPath>
    </URL>
@@ -1902,10 +1928,16 @@ Within the B<URL> block the following options are accepted:
 Use I<Name> as the host name when submitting values. Defaults to the global
 host name setting.
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting values.
+Defaults to 'curl_xml'.
+
 =item B<Instance> I<Instance>
 
-Use I<Instance> as the plugin instance when submitting values. Defaults to an
-empty string (no plugin instance).
+Use I<Instance> as the plugin instance when submitting values.
+May be overridden by B<PluginInstanceFrom> option inside B<XPath> blocks.
+Defaults to an empty string (no plugin instance).
 
 =item B<Namespace> I<Prefix> I<URL>
 
@@ -1976,9 +2008,19 @@ Specifies a XPath expression to use for determining the I<type instance>. The
 XPath expression must return exactly one element. The element's value is then
 used as I<type instance>, possibly prefixed with I<InstancePrefix> (see above).
 
-This value is required. As a special exception, if the "base XPath expression"
-(the argument to the B<XPath> block) returns exactly one argument, then this
-option may be omitted.
+=item B<PluginInstanceFrom> I<PluginInstanceFrom>
+
+Specifies a XPath expression to use for determining the I<plugin instance>. The
+XPath expression must return exactly one element. The element's value is then
+used as I<plugin instance>.
+
+=back
+
+If the "base XPath expression" (the argument to the B<XPath> block) returns
+exactly one argument, then I<InstanceFrom> and I<PluginInstanceFrom> may be omitted.
+Otherwise, at least one of I<InstanceFrom> or I<PluginInstanceFrom> is required.
+
+=over 4
 
 =item B<ValuesFrom> I<ValuesFrom> [I<ValuesFrom> ...]
 
@@ -1987,6 +2029,7 @@ number of XPath expressions must match the number of data sources in the
 I<type> specified with B<Type> (see above). Each XPath expression must return
 exactly one element. The element's value is then parsed as a number and used as
 value for the appropriate value in the value list dispatched to the daemon.
+This option is required.
 
 =back
 
@@ -2017,6 +2060,7 @@ than those of other plugins. It usually looks something like this:
       </Result>
     </Query>
     <Database "product_information">
+      #Plugin "warehouse"
       Driver "mysql"
       Interval 120
       DriverOption "host" "localhost"
@@ -2198,6 +2242,11 @@ the daemon. Other than that, that name is not used.
 
 =over 4
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting query results from
+this B<Database>. Defaults to C<dbi>.
+
 =item B<Interval> I<Interval>
 
 Sets the interval (in seconds) in which the values will be collected from this
@@ -2730,12 +2779,16 @@ blocks, the following options are recognized:
 
 =over 4
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting values.
+Defaults to B<filecount>.
+
 =item B<Instance> I<Instance>
 
-Sets the plugin instance to I<Instance>. That instance name must be unique, but
-it's your responsibility, the plugin doesn't check for that. If not given, the
-instance is set to the directory name with all slashes replaced by underscores
-and all leading underscores removed.
+Sets the plugin instance to I<Instance>. If not given, the instance is set to
+the directory name with all slashes replaced by underscores and all leading
+underscores removed. Empty value is allowed.
 
 =item B<Name> I<Pattern>
 
@@ -2781,6 +2834,26 @@ Controls whether or not to include "hidden" files and directories in the count.
 "Hidden" files and directories are those, whose name begins with a dot.
 Defaults to I<false>, i.e. by default hidden files and directories are ignored.
 
+=item B<RegularOnly> I<true>|I<false>
+
+Controls whether or not to include only regular files in the count.
+Defaults to I<true>, i.e. by default non regular files are ignored.
+
+=item B<FilesSizeType> I<Type>
+
+Sets the type used to dispatch files combined size. Empty value ("") disables
+reporting. Defaults to B<bytes>.
+
+=item B<FilesCountType> I<Type>
+
+Sets the type used to dispatch number of files. Empty value ("") disables
+reporting. Defaults to B<files>.
+
+=item B<TypeInstance> I<Instance>
+
+Sets the I<type instance> used to dispatch values. Defaults to an empty string
+(no plugin instance).
+
 =back
 
 =head2 Plugin C<GenericJMX>
@@ -3073,6 +3146,92 @@ Defaults to B<false>.
 
 =back
 
+=head2 Plugin C<intel_pmu>
+
+The I<intel_pmu> plugin collects performance counters data on Intel CPUs using
+Linux perf interface. All events are reported on a per core basis.
+
+B<Synopsis:>
+
+  <Plugin intel_pmu>
+    ReportHardwareCacheEvents true
+    ReportKernelPMUEvents true
+    ReportSoftwareEvents true
+    EventList "/var/cache/pmu/GenuineIntel-6-2D-core.json"
+    HardwareEvents "L2_RQSTS.CODE_RD_HIT,L2_RQSTS.CODE_RD_MISS" "L2_RQSTS.ALL_CODE_RD"
+  </Plugin>
+
+B<Options:>
+
+=over 4
+
+=item B<ReportHardwareCacheEvents> B<false>|B<true>
+
+Enable or disable measuring of hardware CPU cache events:
+  - L1-dcache-loads
+  - L1-dcache-load-misses
+  - L1-dcache-stores
+  - L1-dcache-store-misses
+  - L1-dcache-prefetches
+  - L1-dcache-prefetch-misses
+  - L1-icache-loads
+  - L1-icache-load-misses
+  - L1-icache-prefetches
+  - L1-icache-prefetch-misses
+  - LLC-loads
+  - LLC-load-misses
+  - LLC-stores
+  - LLC-store-misses
+  - LLC-prefetches
+  - LLC-prefetch-misses
+  - dTLB-loads
+  - dTLB-load-misses
+  - dTLB-stores
+  - dTLB-store-misses
+  - dTLB-prefetches
+  - dTLB-prefetch-misses
+  - iTLB-loads
+  - iTLB-load-misses
+  - branch-loads
+  - branch-load-misses
+
+=item B<ReportKernelPMUEvents> B<false>|B<true>
+
+Enable or disable measuring of the following events:
+  - cpu-cycles
+  - instructions
+  - cache-references
+  - cache-misses
+  - branches
+  - branch-misses
+  - bus-cycles
+
+=item B<ReportSoftwareEvents> B<false>|B<true>
+
+Enable or disable measuring of software events provided by kernel:
+  - cpu-clock
+  - task-clock
+  - context-switches
+  - cpu-migrations
+  - page-faults
+  - minor-faults
+  - major-faults
+  - alignment-faults
+  - emulation-faults
+
+=item B<EventList> I<filename>
+
+JSON performance counter event list file name. To be able to monitor all Intel
+CPU specific events JSON event list file should be downloaded. Use the pmu-tools
+event_download.py script to download event list for current CPU.
+
+=item B<HardwareEvents> I<events>
+
+This field is a list of event names or groups of comma separated event names.
+This option requires B<EventList> option to be configured.
+
+=back
+
 =head2 Plugin C<intel_rdt>
 
 The I<intel_rdt> plugin collects information provided by monitoring features of
@@ -3197,8 +3356,43 @@ This option is only available on Solaris.
 
 =head2 Plugin C<ipmi>
 
+The B<ipmi plugin> allows to monitor server platform status using the Intelligent
+Platform Management Interface (IPMI). Local and remote interfaces are supported.
+
+The plugin configuration consists of one or more B<Instance> blocks which
+specify one I<ipmi> connection each. Each block requires one unique string
+argument as the instance name. If instances are not configured, an instance with
+the default option values will be created.
+
+For backwards compatibility, any option other than B<Instance> block will trigger
+legacy config handling and it will be treated as an option within B<Instance>
+block. This support will go away in the next major version of Collectd.
+
+Within the B<Instance> blocks, the following options are allowed:
+
 =over 4
 
+=item B<Address> I<Address>
+
+Hostname or IP to connect to. If not specified, plugin will try to connect to
+local management controller (BMC).
+
+=item B<Username> I<Username>
+
+=item B<Password> I<Password>
+
+The username and the password to use for the connection to remote BMC.
+
+=item B<AuthType> I<MD5>|I<rmcp+>
+
+Forces the authentication type to use for the connection to remote BMC.
+By default most secure type is seleted.
+
+=item B<Host> I<Hostname>
+
+Sets the B<host> field of dispatched values. Defaults to the global hostname
+setting.
+
 =item B<Sensor> I<Sensor>
 
 Selects sensors to collect or to ignore, depending on B<IgnoreSelected>.
@@ -3227,6 +3421,24 @@ If a sensor disappears a notification is sent.
 If you have for example dual power supply and one of them is (un)plugged then
 a notification is sent.
 
+=item B<NotifyIPMIConnectionState> I<true>|I<false>
+
+If a IPMI connection state changes after initialization time of a minute
+a notification is sent. Defaults to B<false>.
+
+=item B<SELEnabled> I<true>|I<false>
+
+If system event log (SEL) is enabled, plugin will listen for sensor threshold
+and discrete events. When event is received the notification is sent.
+Defaults to B<false>.
+
+=item B<SELClearEvent> I<true>|I<false>
+
+If SEL clear event is enabled, plugin will delete event from SEL list after
+it is received and successfully handled. In this case other tools that are
+subscribed for SEL events will receive an empty event.
+Defaults to B<false>.
+
 =back
 
 =head2 Plugin C<iptables>
@@ -3478,17 +3690,36 @@ By default the plugin connects to B<"/var/run/mcelog-client"> to check if the
 mcelog server is running. When the server is running, the plugin will tail the
 specified logfile to retrieve machine check exception information and send a
 notification with the details from the logfile. The plugin will use the mcelog
-client protocol to retrieve memory related machine check exceptions.
+client protocol to retrieve memory related machine check exceptions. Note that
+for memory exceptions, notifications are only sent when there is a change in
+the number of corrected/uncorrected memory errors.
 
-=over 4
+=head3 The Memory block
+
+Note: these options cannot be used in conjunction with the logfile options, they are mutually
+exclusive.
+
+=over 3
 
 =item B<McelogClientSocket> I<Path>
 Connect to the mcelog client socket using the UNIX domain socket at I<Path>.
 Defaults to B<"/var/run/mcelog-client">.
 
+=item B<PersistentNotification> B<true>|B<false>
+Override default configuration to only send notifications when sent when there
+is a change in the number of corrected/uncorrected memory errors. When set to
+true notifications will be sent for every read cycle. Default is false. Does
+not affect the stats being dispatched.
+
+=back
+
+=over 4
+
 =item B<McelogLogfile> I<Path>
 
-The mcelog file to parse. Defaults to B<"/var/log/mcelog">.
+The mcelog file to parse. Defaults to B<"/var/log/mcelog">. Note: this option
+cannot be used in conjunction with the memory block options, they are mutually
+exclusive.
 
 =back
 
@@ -3536,6 +3767,7 @@ Synopsis of the configuration:
    <Page "plugin_instance">
      Server "localhost"
      Key "page_key"
+     Plugin "plugin_name"
      <Match>
        Regex "(\\d+) bytes sent"
        DSType CounterAdd
@@ -3563,6 +3795,11 @@ B<Page> block.
 
 When connected to the memcached server, asks for the page I<Key>.
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting values.
+Defaults to C<memcachec>.
+
 =item E<lt>B<Match>E<gt>
 
 Match blocks define which strings to look for and how matches substrings are
@@ -4068,18 +4305,18 @@ the B<collectd> branch.
 Path to the PEM-encoded CA certificate file. Setting this option enables TLS
 communication with the MQTT broker, and as such, B<Port> should be the TLS-enabled
 port of the MQTT broker.
-A valid TLS configuration requires B<CACert>, B<CertificateFile> and B<CertificateKeyFile>.
+This option enables the use of TLS.
 
 =item B<CertificateFile> I<file>
 
 Path to the PEM-encoded certificate file to use as client certificate when
 connecting to the MQTT broker.
-A valid TLS configuration requires B<CACert>, B<CertificateFile> and B<CertificateKeyFile>.
+Only valid if B<CACert> and B<CertificateKeyFile> are also set.
 
 =item B<CertificateKeyFile> I<file>
 
 Path to the unencrypted PEM-encoded key file corresponding to B<CertificateFile>.
-A valid TLS configuration requires B<CACert>, B<CertificateFile> and B<CertificateKeyFile>.
+Only valid if B<CACert> and B<CertificateFile> are also set.
 
 =item B<TLSProtocol> I<protocol>
 
@@ -4087,13 +4324,14 @@ If configured, this specifies the string protocol version (e.g. C<tlsv1>,
 C<tlsv1.2>) to use for the TLS connection to the broker. If not set a default
 version is used which depends on the version of OpenSSL the Mosquitto library
 was linked against.
+Only valid if B<CACert> is set.
 
 =item B<CipherSuite> I<ciphersuite>
 
 A string describing the ciphers available for use. See L<ciphers(1)> and the
 C<openssl ciphers> utility for more information. If unset, the default ciphers
 will be used.
-
+Only valid if B<CACert> is set.
 
 =back
 
@@ -4223,11 +4461,11 @@ If enabled, the plugin sends a notification if the replication slave I/O and /
 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'
+
 =item B<ConnectTimeout> I<Seconds>
 
 Sets the connect timeout for the MySQL client.
@@ -5061,6 +5299,25 @@ statistics available. Defaults to B<false>.
 
 =back
 
+=head2 Plugin C<nfs>
+
+The I<nfs plugin> collects information about the usage of the Network File
+System (NFS). It counts the number of procedure calls for each procedure,
+grouped by version and whether the system runs as server or client.
+
+It is possibly to omit metrics for a specific NFS version by setting one or
+more of the following options to B<false> (all of them default to B<true>).
+
+=over 4
+
+=item B<ReportV2> B<true>|B<false>
+
+=item B<ReportV3> B<true>|B<false>
+
+=item B<ReportV4> B<true>|B<false>
+
+=back
+
 =head2 Plugin C<nginx>
 
 This plugin collects the number of connections and requests handled by the
@@ -5531,15 +5788,12 @@ The OpenVPN plugin reads a status file maintained by OpenVPN and gathers
 traffic statistics about connected clients.
 
 To set up OpenVPN to write to the status file periodically, use the
-B<--status> option of OpenVPN. Since OpenVPN can write two different formats,
-you need to set the required format, too. This is done by setting
-B<--status-version> to B<2>.
+B<--status> option of OpenVPN.
 
 So, in a nutshell you need:
 
   openvpn $OTHER_OPTIONS \
-    --status "/var/run/openvpn-status" 10 \
-    --status-version 2
+    --status "/var/run/openvpn-status" 10
 
 Available options:
 
@@ -5594,6 +5848,7 @@ plugin's documentation above for details.
       </Result>
     </Query>
     <Database "product_information">
+      #Plugin "warehouse"
       ConnectID "db01"
       Username "oracle"
       Password "secret"
@@ -5616,6 +5871,11 @@ values submitted to the daemon. Other than that, that name is not used.
 
 =over 4
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting query results from
+this B<Database>. Defaults to C<oracle>.
+
 =item B<ConnectID> I<ID>
 
 Defines the "database alias" or "service name" to connect to. Usually, these
@@ -5671,7 +5931,7 @@ The address of the OVS DB server JSON-RPC interface used by the plugin. To
 enable the interface, OVS DB daemon should be running with C<--remote=ptcp:>
 option. See L<ovsdb-server(1)> for more details. The option may be either
 network hostname, IPv4 numbers-and-dots notation or IPv6 hexadecimal string
-format. Defaults to B<'localhost'>.
+format. Defaults to C<localhost>.
 
 =item B<Port> I<service>
 
@@ -5737,7 +5997,7 @@ The address of the OVS DB server JSON-RPC interface used by the plugin. To
 enable the interface, OVS DB daemon should be running with C<--remote=ptcp:>
 option. See L<ovsdb-server(1)> for more details. The option may be either
 network hostname, IPv4 numbers-and-dots notation or IPv6 hexadecimal string
-format. Defaults to B<'localhost'>.
+format. Defaults to C<localhost>.
 
 =item B<Port> I<service>
 
@@ -5969,6 +6229,7 @@ L<http://www.postgresql.org/docs/manuals/>.
     </Writer>
 
     <Database foo>
+      Plugin "kingdom"
       Host "hostname"
       Port "5432"
       User "username"
@@ -6290,6 +6551,11 @@ activating this option. The draw-back is, that data covering the specified
 amount of time will be lost, for example, if a single statement within the
 transaction fails or if the database server crashes.
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting query results from
+this B<Database>. Defaults to C<postgresql>.
+
 =item B<Instance> I<name>
 
 Specify the plugin instance name that should be used instead of the database
@@ -6543,39 +6809,81 @@ C<I<prefix>/var/run/collectd-powerdns>.
 
 =head2 Plugin C<processes>
 
-=over 4
+Collects information about processes of local system.
 
-=item B<Process> I<Name>
+By default, with no process matches configured, only general statistics is
+collected: the number of processes in each state and fork rate.
 
-Select more detailed statistics of processes matching this name. The statistics
-collected for these selected processes are:
+Process matches can be configured by B<Process> and B<ProcessMatch> options.
+These may also be a block in which further options may be specified.
+
+The statistics collected for matched processes are:
  - size of the resident segment size (RSS)
  - user- and system-time used
  - number of processes
  - number of threads
  - number of open files (under Linux)
+ - number of memory mapped files (under Linux)
  - io data (where available)
  - context switches (under Linux)
  - minor and major pagefaults.
 
-Some platforms have a limit on the length of process names. I<Name> must stay
-below this limit.
+B<Synopsis:>
+
+ <Plugin processes>
+   CollectFileDescriptor true
+   CollectContextSwitch true
+   Process "name"
+   ProcessMatch "name" "regex"
+   <Process "collectd">
+     CollectFileDescriptor false
+     CollectContextSwitch false
+   </Process>
+   <ProcessMatch "name" "regex">
+     CollectFileDescriptor false
+     CollectContextSwitch true
+   </Process>
+ </Plugin>
+
+=over 4
+
+=item B<Process> I<Name>
+
+Select more detailed statistics of processes matching this name.
+
+Some platforms have a limit on the length of process names.
+I<Name> must stay below this limit.
 
 =item B<ProcessMatch> I<name> I<regex>
 
-Similar to the B<Process> option this allows one to select more detailed
-statistics of processes matching the specified I<regex> (see L<regex(7)> for
-details). The statistics of all matching processes are summed up and
-dispatched to the daemon using the specified I<name> as an identifier. This
-allows one to "group" several processes together. I<name> must not contain
-slashes.
+Select more detailed statistics of processes matching the specified I<regex>
+(see L<regex(7)> for details). The statistics of all matching processes are
+summed up and dispatched to the daemon using the specified I<name> as an
+identifier. This allows one to "group" several processes together.
+I<name> must not contain slashes.
 
 =item B<CollectContextSwitch> I<Boolean>
 
-Collect context switch of the process.
+Collect the number of context switches for matched processes.
+Disabled by default.
+
+=item B<CollectFileDescriptor> I<Boolean>
+
+Collect number of file descriptors of matched processes.
+Disabled by default.
+
+=item B<CollectMemoryMaps> I<Boolean>
+
+Collect the number of memory mapped files of the process.
+The limit for this number is configured via F</proc/sys/vm/max_map_count> in
+the Linux kernel.
 
 =back
 
+Options B<CollectContextSwitch> and B<CollectFileDescriptor> may be used inside
+B<Process> and B<ProcessMatch> blocks - then they affect corresponding match
+only. Otherwise they set the default value for subsequent matches.
+
 =head2 Plugin C<protocols>
 
 Collects a lot of information about various network protocols, such as I<IP>,
@@ -6957,14 +7265,20 @@ one (exclusive).
 
 When the C<rrdtool> plugin uses a cache (by setting B<CacheTimeout>, see below)
 it writes all values for a certain RRD-file if the oldest value is older than
-(or equal to) the number of seconds specified. If some RRD-file is not updated
+(or equal to) the number of seconds specified by B<CacheTimeout>.
+That check happens on new values arriwal. If some RRD-file is not updated
 anymore for some reason (the computer was shut down, the network is broken,
-etc.) some values may still be in the cache. If B<CacheFlush> is set, then the
-entire cache is searched for entries older than B<CacheTimeout> seconds and
-written to disk every I<Seconds> seconds. Since this is kind of expensive and
-does nothing under normal circumstances, this value should not be too small.
-900 seconds might be a good value, though setting this to 7200 seconds doesn't
-normally do much harm either.
+etc.) some values may still be in the cache. If B<CacheFlush> is set, then
+every I<Seconds> seconds the entire cache is searched for entries older than
+B<CacheTimeout> + B<RandomTimeout> seconds. The entries found are written to
+disk. Since scanning the entire cache is kind of expensive and does nothing
+under normal circumstances, this value should not be too small. 900 seconds
+might be a good value, though setting this to 7200 seconds doesn't normally
+do much harm either.
+
+Defaults to 10x B<CacheTimeout>.
+B<CacheFlush> must be larger than or equal to B<CacheTimeout>, otherwise the
+above default is used.
 
 =item B<CacheTimeout> I<Seconds>
 
@@ -7397,6 +7711,13 @@ available and free. Defaults to B<false>.
 This is useful for deploying I<collectd> in a heterogeneous environment, where
 swap sizes differ and you want to specify generic thresholds or similar.
 
+=item B<ReportIO> B<true>|B<false>
+
+Enables or disables reporting swap IO. Defaults to B<true>.
+
+This is useful for the cases when swap IO is not neccessary, is not available,
+or is not reliable.
+
 =back
 
 =head2 Plugin C<syslog>
@@ -7432,6 +7753,7 @@ filesystem or CSV (comma separated values) files.
 
   <Plugin table>
     <Table "/proc/slabinfo">
+      #Plugin "slab"
       Instance "slabinfo"
       Separator " "
       <Result>
@@ -7458,10 +7780,14 @@ The following options are available inside a B<Table> block:
 
 =over 4
 
+=item B<Plugin> I<Plugin>
+
+If specified, I<Plugin> is used as the plugin name when submitting values.
+Defaults to B<table>.
+
 =item B<Instance> I<instance>
 
-If specified, I<instance> is used as the plugin instance. So, in the above
-example, the plugin name C<table-slabinfo> would be used. If omitted, the
+If specified, I<instance> is used as the plugin instance. If omitted, the
 filename of the table is used instead, with all special characters replaced
 with an underscore (C<_>).
 
@@ -7531,6 +7857,7 @@ user using (extended) regular expressions, as described in L<regex(7)>.
 
   <Plugin "tail">
     <File "/var/log/exim4/mainlog">
+      Plugin "mail"
       Instance "exim"
       Interval 60
       <Match>
@@ -7551,6 +7878,7 @@ user using (extended) regular expressions, as described in L<regex(7)>.
         <DSType "Distribution">
           Percentile 99
           Bucket 0 100
+          #BucketType "bucket"
         </DSType>
         Type "latency"
         Instance "foo"
@@ -7562,11 +7890,13 @@ The config consists of one or more B<File> blocks, each of which configures one
 logfile to parse. Within each B<File> block, there are one or more B<Match>
 blocks, which configure a regular expression to search for.
 
-The B<Instance> option in the B<File> block may be used to set the plugin
-instance. So in the above example the plugin name C<tail-foo> would be used.
-This plugin instance is for all B<Match> blocks that B<follow> it, until the
-next B<Instance> option. This way you can extract several plugin instances from
-one logfile, handy when parsing syslog and the like.
+The B<Plugin> and B<Instance> options in the B<File> block may be used to set
+the plugin name and instance respectively. So in the above example the plugin name
+C<mail-exim> would be used.
+
+These options are applied for all B<Match> blocks that B<follow> it, until the
+next B<Plugin> or B<Instance> option. This way you can extract several plugin
+instances from one logfile, handy when parsing syslog and the like.
 
 The B<Interval> option allows you to define the length of time between reads. If
 this is not set, the default Interval will be used.
@@ -7670,6 +8000,7 @@ B<Synopsis:>
   <DSType "Distribution">
     Percentile 99
     Bucket 0 100
+    BucketType "bucket"
   </DSType>
 
 =over 4
@@ -7706,11 +8037,17 @@ the following schema:
   Bucket  20  50
   Bucket  50   0
 
-Metrics are reported with the I<type> C<bucket> and the I<type instance>
+Metrics are reported with the I<type> set by B<BucketType> option (C<bucket> 
+by default) and the I<type instance>
 C<E<lt>TypeE<gt>[-E<lt>InstanceE<gt>]-E<lt>lower_boundE<gt>_E<lt>upper_boundE<gt>>.
 
 This option may be repeated to calculate more than one rate.
 
+=item B<BucketType> I<Type>
+
+Sets the type used to dispatch B<Bucket> metrics.
+Optional, by default C<bucket> will be used.
+
 =back
 
 =back
@@ -7747,7 +8084,8 @@ B<Synopsis:>
        Index 1
    </Metric>
    <File "/var/log/snort/snort.stats">
-       Instance "snort-eth0"
+       Plugin "snortstats"
+       Instance "eth0"
        Interval 600
        Collect "snort-dropped"
    </File>
@@ -7799,6 +8137,11 @@ I<File> block but there can be multiple if you have multiple CSV files.
 
 =over 4
 
+=item B<Plugin> I<Plugin>
+
+Use I<Plugin> as the plugin name when submitting values.
+Defaults to C<tail_csv>.
+
 =item B<Instance> I<PluginInstance>
 
 Sets the I<plugin instance> used when dispatching the values.
@@ -8062,9 +8405,9 @@ collections. The different bits of this bit mask accepted by this plugin are:
 
 Boolean enabling the use of logical core numbering for per core statistics.
 When enabled, C<cpuE<lt>nE<gt>> is used as plugin instance, where I<n> is a
-sequential number assigned by the kernel. Otherwise, C<coreE<lt>nE<gt>> is used
-where I<n> is the n-th core of the socket, causing name conflicts when there is
-more than one socket.
+dynamic number assigned by the kernel. Otherwise, C<coreE<lt>nE<gt>> is used
+if there is only one package and C<pkgE<lt>nE<gt>-coreE<lt>mE<gt>> if there is
+more than one, where I<n> is the n-th core of package I<m>.
 
 =back
 
@@ -8170,6 +8513,12 @@ Synopsis:
      CollectVCL         false
      CollectVSM         false
      CollectWorkers     false
+     CollectLock        false
+     CollectMempool     false
+     CollectManagement  false
+     CollectSMF         false
+     CollectVBE         false
+     CollectMSE         false
    </Instance>
  </Plugin>
 
@@ -8245,8 +8594,9 @@ log messages which is flushed to disk when full. True by default.
 =item B<CollectSMA> B<true>|B<false>
 
 malloc or umem (umem_alloc(3MALLOC) based) storage statistics. The umem storage
-component is Solaris specific. Only available with Varnish 2.x. False by
-default.
+component is Solaris specific. Note: SMA, SMF and MSE share counters, enable
+only the one used by the Varnish instance. Only available with Varnish 2.x.
+False by default.
 
 =item B<CollectSMS> B<true>|B<false>
 
@@ -8255,7 +8605,8 @@ component is used internally only. False by default.
 
 =item B<CollectSM> B<true>|B<false>
 
-file (memory mapped file) storage statistics. Only available with Varnish 2.x.
+file (memory mapped file) storage statistics. Only available with Varnish 2.x.,
+in varnish 4.x. use CollectSMF.
 False by default.
 
 =item B<CollectStruct> B<true>|B<false>
@@ -8286,6 +8637,35 @@ statistics subsystems). Only available with Varnish 4.x. False by default.
 
 Collect statistics about worker threads. False by default.
 
+=item B<CollectVBE> B<true>|B<false>
+
+Backend counters. Only available with Varnish 4.x. False by default.
+
+=item B<CollectSMF> B<true>|B<false>
+
+file (memory mapped file) storage statistics. Only available with Varnish 4.x.
+Note: SMA, SMF and MSE share counters, enable only the one used by the Varnish
+instance. Used to be called SM in Varnish 2.x. False by default.
+
+=item B<CollectManagement> B<true>|B<false>
+
+Management process counters. Only available with Varnish 4.x. False by default.
+
+=item B<CollectLock> B<true>|B<false>
+
+Lock counters. Only available with Varnish 4.x. False by default.
+
+=item B<CollectMempool> B<true>|B<false>
+
+Memory pool counters. Only available with Varnish 4.x. False by default.
+
+=item B<CollectMSE> B<true>|B<false>
+
+Varnish Massive Storage Engine 2.0 (MSE2) is an improved storage backend for
+Varnish, replacing the traditional malloc and file storages. Only available
+with Varnish-Plus 4.x. Note: SMA, SMF and MSE share counters, enable only the
+one used by the Varnish instance. False by default.
+
 =back
 
 =head2 Plugin C<virt>
@@ -8350,7 +8730,7 @@ will be collected.
 =item B<BlockDeviceFormat> B<target>|B<source>
 
 If I<BlockDeviceFormat> is set to B<target>, the default, then the device name
-seen by the guest will be used for reporting metrics. 
+seen by the guest will be used for reporting metrics.
 This corresponds to the C<E<lt>targetE<gt>> node in the XML definition of the
 domain.
 
@@ -8930,6 +9310,12 @@ Sets the Cassandra ttl for the data points.
 
 Please refer to L<http://kairosdb.github.io/docs/build/html/restapi/AddDataPoints.html?highlight=ttl>
 
+=item B<Prefix> I<String>
+
+Only available for the KAIROSDB output format.
+
+Sets the metrics prefix I<string>. Defaults to I<collectd>.
+
 =item B<Metrics> B<true>|B<false>
 
 Controls whether I<metrics> are POSTed to this location. Defaults to B<true>.
@@ -9111,6 +9497,7 @@ Synopsis:
         Prefix "collectd/"
         Database 1
         MaxSetSize -1
+        MaxSetDuration -1
         StoreRates true
     </Node>
   </Plugin>
@@ -9173,6 +9560,12 @@ to C<0>.
 The B<MaxSetSize> option limits the number of items that the I<Sorted Sets> can
 hold. Negative values for I<Items> sets no limit, which is the default behavior.
 
+=item B<MaxSetDuration> I<Seconds>
+
+The B<MaxSetDuration> option limits the duration of items that the
+I<Sorted Sets> can hold. Negative values for I<Items> sets no duration, which
+is the default behavior.
+
 =item B<StoreRates> B<true>|B<false>
 
 If set to B<true> (the default), convert counter values to rates. If set to
index 8e68fc016ac2fea5faa2bfe7d58f0d7c30c51e05..1dd899b808515d588ccc2de5e7c0544408368243 100644 (file)
@@ -40,11 +40,17 @@ the read callbacks once. A return code not equal to zero indicates an error.
 
 =item B<-P> I<E<lt>pid-fileE<gt>>
 
-Specify an alternative pid file. This overwrites any settings in the config 
+Specify an alternative pid file. This overwrites any settings in the config
 file. This is thought for init-scripts that require the PID-file in a certain
 directory to work correctly. For everyday-usage use the B<PIDFile>
 config-option.
 
+=item B<-B>
+
+If set, collectd will I<not> try to create its base directory. If the base
+directory does not exist, it will exit rather than trying to create the
+directory.
+
 =item B<-f>
 
 Don't fork to the background. I<collectd> will also B<not> close standard file
index 4b0db2a7e883c721f0ad49eb14dceddedb84e7c6..54c8081eb6cc4f57e900876612aa0fb21a983b43 100644 (file)
@@ -65,7 +65,7 @@
 #endif
 #endif /* NAN_ZERO_ZERO */
 
-#include "libcollectdclient/collectd/client.h"
+#include "collectd/client.h"
 
 #ifndef PREFIX
 #define PREFIX "/opt/" PACKAGE_NAME
index 307ae499e2baf2e064379c557bfea0ddd7b90b0b..d48ab886332912a1c09e62c477d0048209dc9ab6 100644 (file)
--- a/src/cpu.c
+++ b/src/cpu.c
 #define COLLECTD_CPU_STATE_INTERRUPT 5
 #define COLLECTD_CPU_STATE_SOFTIRQ 6
 #define COLLECTD_CPU_STATE_STEAL 7
-#define COLLECTD_CPU_STATE_IDLE 8
-#define COLLECTD_CPU_STATE_ACTIVE 9 /* sum of (!idle) */
-#define COLLECTD_CPU_STATE_MAX 10   /* #states */
+#define COLLECTD_CPU_STATE_GUEST 8
+#define COLLECTD_CPU_STATE_GUEST_NICE 9
+#define COLLECTD_CPU_STATE_IDLE 10
+#define COLLECTD_CPU_STATE_ACTIVE 11 /* sum of (!idle) */
+#define COLLECTD_CPU_STATE_MAX 12   /* #states */
 
 #if HAVE_STATGRAB_H
 #include <statgrab.h>
 
 static const char *cpu_state_names[] = {"user", "system",    "wait",    "nice",
                                         "swap", "interrupt", "softirq", "steal",
-                                        "idle", "active"};
+                                        "guest", "guest_nice", "idle", "active"};
 
 #ifdef PROCESSOR_CPU_LOAD_INFO
 static mach_port_t port_host;
@@ -193,9 +195,12 @@ static _Bool report_by_cpu = 1;
 static _Bool report_by_state = 1;
 static _Bool report_percent = 0;
 static _Bool report_num_cpu = 0;
+static _Bool report_guest = 0;
+static _Bool subtract_guest = 1;
 
 static const char *config_keys[] = {"ReportByCpu", "ReportByState",
-                                    "ReportNumCpu", "ValuesPercentage"};
+                                    "ReportNumCpu", "ValuesPercentage",
+                                    "ReportGuestState", "SubtractGuestState"};
 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
 
 static int cpu_config(char const *key, char const *value) /* {{{ */
@@ -208,6 +213,10 @@ static int cpu_config(char const *key, char const *value) /* {{{ */
     report_by_state = IS_TRUE(value) ? 1 : 0;
   else if (strcasecmp(key, "ReportNumCpu") == 0)
     report_num_cpu = IS_TRUE(value) ? 1 : 0;
+  else if (strcasecmp(key, "ReportGuestState") == 0)
+    report_guest = IS_TRUE(value) ? 1 : 0;
+  else if (strcasecmp(key, "SubtractGuestState") == 0)
+    subtract_guest = IS_TRUE(value) ? 1 : 0;
   else
     return -1;
 
@@ -524,7 +533,7 @@ static void cpu_commit_without_aggregation(void) /* {{{ */
 static void cpu_commit(void) /* {{{ */
 {
   gauge_t global_rates[COLLECTD_CPU_STATE_MAX] = {
-      NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN /* Batman! */
+      NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN /* Batman! */
   };
 
   if (report_num_cpu)
@@ -545,7 +554,8 @@ static void cpu_commit(void) /* {{{ */
   for (size_t cpu_num = 0; cpu_num < global_cpu_num; cpu_num++) {
     cpu_state_t *this_cpu_states = get_cpu_state(cpu_num, 0);
     gauge_t local_rates[COLLECTD_CPU_STATE_MAX] = {NAN, NAN, NAN, NAN, NAN,
-                                                   NAN, NAN, NAN, NAN, NAN};
+                                                   NAN, NAN, NAN, NAN, NAN,
+                                                   NAN, NAN };
 
     for (size_t state = 0; state < COLLECTD_CPU_STATE_MAX; state++)
       if (this_cpu_states[state].has_value)
@@ -632,7 +642,7 @@ static int cpu_read(void) {
   FILE *fh;
   char buf[1024];
 
-  char *fields[9];
+  char *fields[11];
   int numfields;
 
   if ((fh = fopen("/proc/stat", "r")) == NULL) {
@@ -648,14 +658,15 @@ static int cpu_read(void) {
     if ((buf[3] < '0') || (buf[3] > '9'))
       continue;
 
-    numfields = strsplit(buf, fields, 9);
+    numfields = strsplit(buf, fields, STATIC_ARRAY_SIZE(fields));
     if (numfields < 5)
       continue;
 
     cpu = atoi(fields[0] + 3);
 
-    cpu_stage(cpu, COLLECTD_CPU_STATE_USER, (derive_t)atoll(fields[1]), now);
-    cpu_stage(cpu, COLLECTD_CPU_STATE_NICE, (derive_t)atoll(fields[2]), now);
+    /* Do not stage User and Nice immediately: we may need to alter them later: */
+    long long user_value = atoll(fields[1]);
+    long long nice_value = atoll(fields[2]);
     cpu_stage(cpu, COLLECTD_CPU_STATE_SYSTEM, (derive_t)atoll(fields[3]), now);
     cpu_stage(cpu, COLLECTD_CPU_STATE_IDLE, (derive_t)atoll(fields[4]), now);
 
@@ -665,11 +676,40 @@ static int cpu_read(void) {
                 now);
       cpu_stage(cpu, COLLECTD_CPU_STATE_SOFTIRQ, (derive_t)atoll(fields[7]),
                 now);
+       }
 
-      if (numfields >= 9)
-        cpu_stage(cpu, COLLECTD_CPU_STATE_STEAL, (derive_t)atoll(fields[8]),
-                  now);
+    if (numfields >= 9) { /* Steal (since Linux 2.6.11) */
+      cpu_stage(cpu, COLLECTD_CPU_STATE_STEAL, (derive_t)atoll(fields[8]), now);
     }
+
+    if (numfields >= 10) { /* Guest (since Linux 2.6.24) */
+      if (report_guest) {
+        long long value = atoll(fields[9]);
+        cpu_stage(cpu, COLLECTD_CPU_STATE_GUEST, (derive_t)value, now);
+        /* Guest is included in User; optionally subtract Guest from User: */
+        if (subtract_guest) {
+          user_value -= value;
+          if (user_value < 0) user_value = 0;
+        }
+      }
+    }
+
+    if (numfields >= 11) { /* Guest_nice (since Linux 2.6.33) */
+      if (report_guest) {
+        long long value = atoll(fields[10]);
+        cpu_stage(cpu, COLLECTD_CPU_STATE_GUEST_NICE, (derive_t)value, now);
+        /* Guest_nice is included in Nice; optionally subtract Guest_nice from
+           Nice: */
+        if (subtract_guest) {
+          nice_value -= value;
+          if (nice_value < 0) nice_value = 0;
+        }
+      }
+    }
+
+    /* Eventually stage User and Nice: */
+    cpu_stage(cpu, COLLECTD_CPU_STATE_USER, (derive_t)user_value, now);
+    cpu_stage(cpu, COLLECTD_CPU_STATE_NICE, (derive_t)nice_value, now);
   }
   fclose(fh);
 /* }}} #endif defined(KERNEL_LINUX) */
index 69a4298d662e0f8d66d9f223c2cb900c4f511bae..38951c0fb348fb8179f4dedd1b436bcdebcfc8e7 100644 (file)
@@ -181,6 +181,7 @@ extern PyTypeObject ValuesType;
 
 typedef struct {
   PluginData data;
+  PyObject *meta;   /* dict */
   int severity;
   char message[NOTIF_MAX_MSG_LEN];
 } Notification;
index cc48c77491f8ea281992ecb48c78526323864b66..35ec1f8391f8a3b152b7c33d370fb7018047bfd3 100644 (file)
@@ -53,6 +53,7 @@ struct web_page_s;
 typedef struct web_page_s web_page_t;
 struct web_page_s /* {{{ */
 {
+  char *plugin_name;
   char *instance;
 
   char *url;
@@ -146,6 +147,7 @@ static void cc_web_page_free(web_page_t *wp) /* {{{ */
     curl_easy_cleanup(wp->curl);
   wp->curl = NULL;
 
+  sfree(wp->plugin_name);
   sfree(wp->instance);
 
   sfree(wp->url);
@@ -412,6 +414,7 @@ static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */
     ERROR("curl plugin: calloc failed.");
     return -1;
   }
+  page->plugin_name = NULL;
   page->url = NULL;
   page->user = NULL;
   page->pass = NULL;
@@ -435,7 +438,9 @@ static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
 
-    if (strcasecmp("URL", child->key) == 0)
+    if (strcasecmp("Plugin", child->key) == 0)
+      status = cf_util_get_string(child, &page->plugin_name);
+    else if (strcasecmp("URL", child->key) == 0)
       status = cf_util_get_string(child, &page->url);
     else if (strcasecmp("User", child->key) == 0)
       status = cf_util_get_string(child, &page->user);
@@ -566,7 +571,8 @@ static void cc_submit(const web_page_t *wp, const web_match_t *wm, /* {{{ */
 
   vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.plugin, "curl", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "curl",
+           sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, wm->type, sizeof(vl.type));
   if (wm->instance != NULL)
@@ -581,7 +587,8 @@ static void cc_submit_response_code(const web_page_t *wp, long code) /* {{{ */
 
   vl.values = &(value_t){.gauge = (gauge_t)code};
   vl.values_len = 1;
-  sstrncpy(vl.plugin, "curl", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "curl",
+           sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "response_code", sizeof(vl.type));
 
@@ -594,7 +601,8 @@ static void cc_submit_response_time(const web_page_t *wp, /* {{{ */
 
   vl.values = &(value_t){.gauge = response_time};
   vl.values_len = 1;
-  sstrncpy(vl.plugin, "curl", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "curl",
+           sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, "response_time", sizeof(vl.type));
 
@@ -623,7 +631,7 @@ static int cc_read_page(web_page_t *wp) /* {{{ */
   if (wp->response_time)
     cc_submit_response_time(wp, CDTIME_T_TO_DOUBLE(cdtime() - start));
   if (wp->stats != NULL)
-    curl_stats_dispatch(wp->stats, wp->curl, hostname_g, "curl", wp->instance);
+    curl_stats_dispatch(wp->stats, wp->curl, NULL, "curl", wp->instance);
 
   if (wp->response_code) {
     long response_code = 0;
index 35b11c02d9f08c0e61859ff9a93bafdfcd03274a..a2f287a81668b824986b55f4c122c510034b15e5 100644 (file)
@@ -82,6 +82,7 @@ typedef struct {
 struct cj_s /* {{{ */
 {
   char *instance;
+  char *plugin_name;
   char *host;
 
   char *sock;
@@ -225,11 +226,6 @@ static void cj_advance_array(cj_t *db) {
 #define CJ_CB_ABORT 0
 #define CJ_CB_CONTINUE 1
 
-static int cj_cb_boolean(void *ctx, int boolVal) {
-  cj_advance_array(ctx);
-  return CJ_CB_CONTINUE;
-}
-
 static int cj_cb_null(void *ctx) {
   cj_advance_array(ctx);
   return CJ_CB_CONTINUE;
@@ -291,6 +287,13 @@ static int cj_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) {
   return cj_cb_number(ctx, (const char *)val, len);
 } /* int cj_cb_string */
 
+static int cj_cb_boolean(void *ctx, int boolVal) {
+  if (boolVal)
+    return cj_cb_number(ctx, "1", 1);
+  else
+    return cj_cb_number(ctx, "0", 1);
+} /* int cj_cb_boolean */
+
 static int cj_cb_end(void *ctx) {
   cj_t *db = (cj_t *)ctx;
   memset(&db->state[db->depth], 0, sizeof(db->state[db->depth]));
@@ -396,6 +399,7 @@ static void cj_free(void *arg) /* {{{ */
   db->tree = NULL;
 
   sfree(db->instance);
+  sfree(db->plugin_name);
   sfree(db->host);
 
   sfree(db->sock);
@@ -672,6 +676,8 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
 
     if (strcasecmp("Instance", child->key) == 0)
       status = cf_util_get_string(child, &db->instance);
+    else if (strcasecmp("Plugin", child->key) == 0)
+      status = cf_util_get_string(child, &db->plugin_name);
     else if (strcasecmp("Host", child->key) == 0)
       status = cf_util_get_string(child, &db->host);
     else if (db->url && strcasecmp("User", child->key) == 0)
@@ -805,7 +811,8 @@ static void cj_submit_impl(cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
     sstrncpy(vl.type_instance, key->instance, sizeof(vl.type_instance));
 
   sstrncpy(vl.host, cj_host(db), sizeof(vl.host));
-  sstrncpy(vl.plugin, "curl_json", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, (db->plugin_name != NULL) ? db->plugin_name : "curl_json",
+           sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, key->type, sizeof(vl.type));
 
index e83de73ea6c07cefe0595af6342417a61bc86d48..c99e3f1df058946446469861b54b2317a742e8f2 100644 (file)
@@ -54,6 +54,7 @@ struct cx_xpath_s /* {{{ */
   size_t values_len;
   char *instance_prefix;
   char *instance;
+  char *plugin_instance_from;
   int is_table;
   unsigned long magic;
 };
@@ -71,6 +72,7 @@ typedef struct cx_namespace_s cx_namespace_t;
 struct cx_s /* {{{ */
 {
   char *instance;
+  char *plugin_name;
   char *host;
 
   char *url;
@@ -95,7 +97,7 @@ struct cx_s /* {{{ */
   size_t buffer_size;
   size_t buffer_fill;
 
-  llist_t *list; /* list of xpath blocks */
+  llist_t *xpath_list; /* list of xpath blocks */
 };
 typedef struct cx_s cx_t; /* }}} */
 
@@ -105,9 +107,8 @@ typedef struct cx_s cx_t; /* }}} */
 static size_t cx_curl_callback(void *buf, /* {{{ */
                                size_t size, size_t nmemb, void *user_data) {
   size_t len = size * nmemb;
-  cx_t *db;
 
-  db = user_data;
+  cx_t *db = user_data;
   if (db == NULL) {
     ERROR("curl_xml plugin: cx_curl_callback: "
           "user_data pointer is NULL.");
@@ -118,9 +119,7 @@ static size_t cx_curl_callback(void *buf, /* {{{ */
     return len;
 
   if ((db->buffer_fill + len) >= db->buffer_size) {
-    char *temp;
-
-    temp = realloc(db->buffer, db->buffer_fill + len + 1);
+    char *temp = realloc(db->buffer, db->buffer_fill + len + 1);
     if (temp == NULL) {
       ERROR("curl_xml plugin: realloc failed.");
       return 0;
@@ -144,29 +143,28 @@ static void cx_xpath_free(cx_xpath_t *xpath) /* {{{ */
   sfree(xpath->path);
   sfree(xpath->type);
   sfree(xpath->instance_prefix);
+  sfree(xpath->plugin_instance_from);
   sfree(xpath->instance);
   sfree(xpath->values);
   sfree(xpath);
 } /* }}} void cx_xpath_free */
 
-static void cx_list_free(llist_t *list) /* {{{ */
+static void cx_xpath_list_free(llist_t *list) /* {{{ */
 {
   llentry_t *le;
 
   le = llist_head(list);
   while (le != NULL) {
-    llentry_t *le_next;
+    llentry_t *le_next = le->next;
 
-    le_next = le->next;
-
-    sfree(le->key);
+    /* this also frees xpath->path used for le->key */
     cx_xpath_free(le->value);
 
     le = le_next;
   }
 
   llist_destroy(list);
-} /* }}} void cx_list_free */
+} /* }}} void cx_xpath_list_free */
 
 static void cx_free(void *arg) /* {{{ */
 {
@@ -183,11 +181,12 @@ static void cx_free(void *arg) /* {{{ */
     curl_easy_cleanup(db->curl);
   db->curl = NULL;
 
-  if (db->list != NULL)
-    cx_list_free(db->list);
+  if (db->xpath_list != NULL)
+    cx_xpath_list_free(db->xpath_list);
 
   sfree(db->buffer);
   sfree(db->instance);
+  sfree(db->plugin_name);
   sfree(db->host);
 
   sfree(db->url);
@@ -208,7 +207,7 @@ static void cx_free(void *arg) /* {{{ */
   sfree(db);
 } /* }}} void cx_free */
 
-static const char *cx_host(cx_t *db) /* {{{ */
+static const char *cx_host(const cx_t *db) /* {{{ */
 {
   if (db->host == NULL)
     return hostname_g;
@@ -250,13 +249,11 @@ static int cx_check_type(const data_set_t *ds, cx_xpath_t *xpath) /* {{{ */
   return 0;
 } /* }}} cx_check_type */
 
-static xmlXPathObjectPtr
-cx_evaluate_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
-                  xmlChar *expr) {
-  xmlXPathObjectPtr xpath_obj;
-
-  /* XXX: When to free this? */
-  xpath_obj = xmlXPathEvalExpression(BAD_CAST expr, xpath_ctx);
+static xmlXPathObjectPtr cx_evaluate_xpath(xmlXPathContextPtr xpath_ctx,
+                                           char *expr) /* {{{ */
+{
+  xmlXPathObjectPtr xpath_obj =
+      xmlXPathEvalExpression(BAD_CAST expr, xpath_ctx);
   if (xpath_obj == NULL) {
     WARNING("curl_xml plugin: "
             "Error unable to evaluate xpath expression \"%s\". Skipping...",
@@ -279,51 +276,65 @@ static int cx_if_not_text_node(xmlNodePtr node) /* {{{ */
   return -1;
 } /* }}} cx_if_not_text_node */
 
-static int cx_handle_single_value_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
-                                        cx_xpath_t *xpath, const data_set_t *ds,
-                                        value_list_t *vl, int index) {
-  xmlXPathObjectPtr values_node_obj;
-  xmlNodeSetPtr values_node;
-  int tmp_size;
-  char *node_value;
-
-  values_node_obj =
-      cx_evaluate_xpath(xpath_ctx, BAD_CAST xpath->values[index].path);
+/*
+ * Returned value should be freed with xmlFree().
+ */
+static char *cx_get_text_node_value(xmlXPathContextPtr xpath_ctx, /* {{{ */
+                                    char *expr, const char *from_option) {
+  xmlXPathObjectPtr values_node_obj = cx_evaluate_xpath(xpath_ctx, expr);
   if (values_node_obj == NULL)
-    return -1; /* Error already logged. */
+    return NULL; /* Error already logged. */
 
-  values_node = values_node_obj->nodesetval;
-  tmp_size = (values_node) ? values_node->nodeNr : 0;
+  xmlNodeSetPtr values_node = values_node_obj->nodesetval;
+  size_t tmp_size = (values_node) ? values_node->nodeNr : 0;
 
   if (tmp_size == 0) {
     WARNING("curl_xml plugin: "
-            "relative xpath expression \"%s\" doesn't match any of the nodes. "
-            "Skipping...",
-            xpath->values[index].path);
+            "relative xpath expression \"%s\" from '%s' doesn't match "
+            "any of the nodes.",
+            expr, from_option);
     xmlXPathFreeObject(values_node_obj);
-    return -1;
+    return NULL;
   }
 
   if (tmp_size > 1) {
     WARNING("curl_xml plugin: "
-            "relative xpath expression \"%s\" is expected to return "
-            "only one node. Skipping...",
-            xpath->values[index].path);
+            "relative xpath expression \"%s\" from '%s' is expected to return "
+            "only one text node. Skipping the node.",
+            expr, from_option);
     xmlXPathFreeObject(values_node_obj);
-    return -1;
+    return NULL;
   }
 
   /* ignoring the element if other than textnode/attribute*/
   if (cx_if_not_text_node(values_node->nodeTab[0])) {
     WARNING("curl_xml plugin: "
-            "relative xpath expression \"%s\" is expected to return "
-            "only text/attribute node which is not the case. Skipping...",
-            xpath->values[index].path);
+            "relative xpath expression \"%s\" from '%s' is expected to return "
+            "only text/attribute node which is not the case. "
+            "Skipping the node.",
+            expr, from_option);
     xmlXPathFreeObject(values_node_obj);
-    return -1;
+    return NULL;
   }
 
-  node_value = (char *)xmlNodeGetContent(values_node->nodeTab[0]);
+  char *node_value = (char *)xmlNodeGetContent(values_node->nodeTab[0]);
+
+  /* free up object */
+  xmlXPathFreeObject(values_node_obj);
+
+  return node_value;
+} /* }}} char * cx_get_text_node_value */
+
+static int cx_handle_single_value_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
+                                        cx_xpath_t *xpath, const data_set_t *ds,
+                                        value_list_t *vl, int index) {
+
+  char *node_value = cx_get_text_node_value(
+      xpath_ctx, xpath->values[index].path, "ValuesFrom");
+
+  if (node_value == NULL)
+    return -1;
+
   switch (ds->ds[index].type) {
   case DS_TYPE_COUNTER:
     vl->values[index].counter =
@@ -345,9 +356,7 @@ static int cx_handle_single_value_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
                                               /* endptr = */ NULL);
   }
 
-  /* free up object */
-  xmlXPathFreeObject(values_node_obj);
-  sfree(node_value);
+  xmlFree(node_value);
 
   /* We have reached here which means that
    * we have got something to work */
@@ -358,7 +367,6 @@ static int cx_handle_all_value_xpaths(xmlXPathContextPtr xpath_ctx, /* {{{ */
                                       cx_xpath_t *xpath, const data_set_t *ds,
                                       value_list_t *vl) {
   value_t values[xpath->values_len];
-  int status;
 
   assert(xpath->values_len > 0);
   assert(xpath->values_len == vl->values_len);
@@ -366,8 +374,7 @@ static int cx_handle_all_value_xpaths(xmlXPathContextPtr xpath_ctx, /* {{{ */
   vl->values = values;
 
   for (size_t i = 0; i < xpath->values_len; i++) {
-    status = cx_handle_single_value_xpath(xpath_ctx, xpath, ds, vl, i);
-    if (status != 0)
+    if (cx_handle_single_value_xpath(xpath_ctx, xpath, ds, vl, i) != 0)
       return -1; /* An error has been printed. */
   }              /* for (i = 0; i < xpath->values_len; i++) */
 
@@ -378,150 +385,96 @@ static int cx_handle_all_value_xpaths(xmlXPathContextPtr xpath_ctx, /* {{{ */
 } /* }}} int cx_handle_all_value_xpaths */
 
 static int cx_handle_instance_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
-                                    cx_xpath_t *xpath, value_list_t *vl,
-                                    _Bool is_table) {
-  xmlXPathObjectPtr instance_node_obj = NULL;
-  xmlNodeSetPtr instance_node = NULL;
-
-  memset(vl->type_instance, 0, sizeof(vl->type_instance));
-
-  /* If the base xpath returns more than one block, the result is assumed to be
-   * a table. The `Instance' option is not optional in this case. Check for the
-   * condition and inform the user. */
-  if (is_table && (xpath->instance == NULL)) {
-    WARNING("curl_xml plugin: "
-            "Base-XPath %s is a table (more than one result was returned), "
-            "but no instance-XPath has been defined.",
-            xpath->path);
-    return -1;
-  }
+                                    cx_xpath_t *xpath, value_list_t *vl) {
 
-  /* instance has to be an xpath expression */
+  /* Handle type instance */
   if (xpath->instance != NULL) {
-    int tmp_size;
-
-    instance_node_obj = cx_evaluate_xpath(xpath_ctx, BAD_CAST xpath->instance);
-    if (instance_node_obj == NULL)
-      return -1; /* error is logged already */
-
-    instance_node = instance_node_obj->nodesetval;
-    tmp_size = (instance_node) ? instance_node->nodeNr : 0;
-
-    if (tmp_size <= 0) {
-      WARNING(
-          "curl_xml plugin: "
-          "relative xpath expression for 'InstanceFrom' \"%s\" doesn't match "
-          "any of the nodes. Skipping the node.",
-          xpath->instance);
-      xmlXPathFreeObject(instance_node_obj);
+    char *node_value =
+        cx_get_text_node_value(xpath_ctx, xpath->instance, "InstanceFrom");
+    if (node_value == NULL)
       return -1;
-    }
 
-    if (tmp_size > 1) {
-      WARNING("curl_xml plugin: "
-              "relative xpath expression for 'InstanceFrom' \"%s\" is expected "
-              "to return only one text node. Skipping the node.",
-              xpath->instance);
-      xmlXPathFreeObject(instance_node_obj);
-      return -1;
-    }
-
-    /* ignoring the element if other than textnode/attribute */
-    if (cx_if_not_text_node(instance_node->nodeTab[0])) {
-      WARNING("curl_xml plugin: "
-              "relative xpath expression \"%s\" is expected to return only "
-              "text node "
-              "which is not the case. Skipping the node.",
-              xpath->instance);
-      xmlXPathFreeObject(instance_node_obj);
-      return -1;
-    }
-  } /* if (xpath->instance != NULL) */
-
-  if (xpath->instance_prefix != NULL) {
-    if (instance_node != NULL) {
-      char *node_value = (char *)xmlNodeGetContent(instance_node->nodeTab[0]);
+    if (xpath->instance_prefix != NULL)
       snprintf(vl->type_instance, sizeof(vl->type_instance), "%s%s",
                xpath->instance_prefix, node_value);
-      sfree(node_value);
-    } else
-      sstrncpy(vl->type_instance, xpath->instance_prefix,
-               sizeof(vl->type_instance));
-  } else {
-    /* If instance_prefix and instance_node are NULL, then
-     * don't set the type_instance */
-    if (instance_node != NULL) {
-      char *node_value = (char *)xmlNodeGetContent(instance_node->nodeTab[0]);
+    else
       sstrncpy(vl->type_instance, node_value, sizeof(vl->type_instance));
-      sfree(node_value);
-    }
-  }
 
-  /* Free `instance_node_obj' this late, because `instance_node' points to
-   * somewhere inside this structure. */
-  xmlXPathFreeObject(instance_node_obj);
+    xmlFree(node_value);
+  } else if (xpath->instance_prefix != NULL)
+    sstrncpy(vl->type_instance, xpath->instance_prefix,
+             sizeof(vl->type_instance));
+
+  /* Handle plugin instance */
+  if (xpath->plugin_instance_from != NULL) {
+    char *node_value = cx_get_text_node_value(
+        xpath_ctx, xpath->plugin_instance_from, "PluginInstanceFrom");
+
+    if (node_value == NULL)
+      return -1;
+
+    sstrncpy(vl->plugin_instance, node_value, sizeof(vl->plugin_instance));
+    xmlFree(node_value);
+  }
 
   return 0;
 } /* }}} int cx_handle_instance_xpath */
 
-static int cx_handle_base_xpath(char const *plugin_instance, /* {{{ */
-                                char const *host, xmlXPathContextPtr xpath_ctx,
-                                const data_set_t *ds, char *base_xpath,
-                                cx_xpath_t *xpath) {
-  int total_nodes;
+static int cx_handle_xpath(const cx_t *db, /* {{{ */
+                           xmlXPathContextPtr xpath_ctx, cx_xpath_t *xpath) {
 
-  xmlXPathObjectPtr base_node_obj = NULL;
-  xmlNodeSetPtr base_nodes = NULL;
-
-  value_list_t vl = VALUE_LIST_INIT;
+  const data_set_t *ds = plugin_get_ds(xpath->type);
+  if (cx_check_type(ds, xpath) != 0)
+    return -1;
 
-  base_node_obj = cx_evaluate_xpath(xpath_ctx, BAD_CAST base_xpath);
+  xmlXPathObjectPtr base_node_obj = cx_evaluate_xpath(xpath_ctx, xpath->path);
   if (base_node_obj == NULL)
     return -1; /* error is logged already */
 
-  base_nodes = base_node_obj->nodesetval;
-  total_nodes = (base_nodes) ? base_nodes->nodeNr : 0;
+  xmlNodeSetPtr base_nodes = base_node_obj->nodesetval;
+  int total_nodes = (base_nodes) ? base_nodes->nodeNr : 0;
 
   if (total_nodes == 0) {
     ERROR("curl_xml plugin: "
           "xpath expression \"%s\" doesn't match any of the nodes. "
           "Skipping the xpath block...",
-          base_xpath);
+          xpath->path);
     xmlXPathFreeObject(base_node_obj);
     return -1;
   }
 
   /* If base_xpath returned multiple results, then */
-  /* Instance in the xpath block is required */
-  if (total_nodes > 1 && xpath->instance == NULL) {
+  /* InstanceFrom or PluginInstanceFrom in the xpath block is required */
+  if (total_nodes > 1 && xpath->instance == NULL &&
+      xpath->plugin_instance_from == NULL) {
     ERROR("curl_xml plugin: "
-          "InstanceFrom is must in xpath block since the base xpath expression "
-          "\"%s\" "
+          "InstanceFrom or PluginInstanceFrom is must in xpath block "
+          "since the base xpath expression \"%s\" "
           "returned multiple results. Skipping the xpath block...",
-          base_xpath);
+          xpath->path);
+    xmlXPathFreeObject(base_node_obj);
     return -1;
   }
 
+  value_list_t vl = VALUE_LIST_INIT;
+
   /* set the values for the value_list */
   vl.values_len = ds->ds_num;
   sstrncpy(vl.type, xpath->type, sizeof(vl.type));
-  sstrncpy(vl.plugin, "curl_xml", sizeof(vl.plugin));
-  sstrncpy(vl.host, host, sizeof(vl.host));
-  if (plugin_instance != NULL)
-    sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
+  sstrncpy(vl.plugin, (db->plugin_name != NULL) ? db->plugin_name : "curl_xml",
+           sizeof(vl.plugin));
+  sstrncpy(vl.host, cx_host(db), sizeof(vl.host));
 
   for (int i = 0; i < total_nodes; i++) {
-    int status;
-
     xpath_ctx->node = base_nodes->nodeTab[i];
 
-    status = cx_handle_instance_xpath(xpath_ctx, xpath, &vl,
-                                      /* is_table = */ (total_nodes > 1));
-    if (status != 0)
+    if (db->instance != NULL)
+      sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
+
+    if (cx_handle_instance_xpath(xpath_ctx, xpath, &vl) != 0)
       continue; /* An error has already been reported. */
 
-    status = cx_handle_all_value_xpaths(xpath_ctx, xpath, ds, &vl);
-    if (status != 0)
+    if (cx_handle_all_value_xpaths(xpath_ctx, xpath, ds, &vl) != 0)
       continue; /* An error has been logged. */
   }             /* for (i = 0; i < total_nodes; i++) */
 
@@ -529,24 +482,17 @@ static int cx_handle_base_xpath(char const *plugin_instance, /* {{{ */
   xmlXPathFreeObject(base_node_obj);
 
   return 0;
-} /* }}} cx_handle_base_xpath */
+} /* }}} cx_handle_xpath */
 
-static int cx_handle_parsed_xml(xmlDocPtr doc, /* {{{ */
-                                xmlXPathContextPtr xpath_ctx, cx_t *db) {
-  llentry_t *le;
-  const data_set_t *ds;
-  cx_xpath_t *xpath;
+static int cx_handle_parsed_xml(cx_t *db, xmlDocPtr doc, /* {{{ */
+                                xmlXPathContextPtr xpath_ctx) {
   int status = -1;
 
-  le = llist_head(db->list);
+  llentry_t *le = llist_head(db->xpath_list);
   while (le != NULL) {
-    /* get the ds */
-    xpath = (cx_xpath_t *)le->value;
-    ds = plugin_get_ds(xpath->type);
+    cx_xpath_t *xpath = (cx_xpath_t *)le->value;
 
-    if ((cx_check_type(ds, xpath) == 0) &&
-        (cx_handle_base_xpath(db->instance, cx_host(db), xpath_ctx, ds, le->key,
-                              xpath) == 0))
+    if (cx_handle_xpath(db, xpath_ctx, xpath) == 0)
       status = 0; /* we got atleast one success */
 
     le = le->next;
@@ -555,20 +501,16 @@ static int cx_handle_parsed_xml(xmlDocPtr doc, /* {{{ */
   return status;
 } /* }}} cx_handle_parsed_xml */
 
-static int cx_parse_stats_xml(xmlChar *xml, cx_t *db) /* {{{ */
+static int cx_parse_xml(cx_t *db, char *xml) /* {{{ */
 {
-  int status;
-  xmlDocPtr doc;
-  xmlXPathContextPtr xpath_ctx;
-
   /* Load the XML */
-  doc = xmlParseDoc(xml);
+  xmlDocPtr doc = xmlParseDoc(BAD_CAST xml);
   if (doc == NULL) {
     ERROR("curl_xml plugin: Failed to parse the xml document  - %s", xml);
     return -1;
   }
 
-  xpath_ctx = xmlXPathNewContext(doc);
+  xmlXPathContextPtr xpath_ctx = xmlXPathNewContext(doc);
   if (xpath_ctx == NULL) {
     ERROR("curl_xml plugin: Failed to create the xml context");
     xmlFreeDoc(doc);
@@ -577,7 +519,7 @@ static int cx_parse_stats_xml(xmlChar *xml, cx_t *db) /* {{{ */
 
   for (size_t i = 0; i < db->namespaces_num; i++) {
     cx_namespace_t const *ns = db->namespaces + i;
-    status =
+    int status =
         xmlXPathRegisterNs(xpath_ctx, BAD_CAST ns->prefix, BAD_CAST ns->url);
     if (status != 0) {
       ERROR("curl_xml plugin: "
@@ -589,25 +531,29 @@ static int cx_parse_stats_xml(xmlChar *xml, cx_t *db) /* {{{ */
     }
   }
 
-  status = cx_handle_parsed_xml(doc, xpath_ctx, db);
+  int status = cx_handle_parsed_xml(db, doc, xpath_ctx);
   /* Cleanup */
   xmlXPathFreeContext(xpath_ctx);
   xmlFreeDoc(doc);
   return status;
-} /* }}} cx_parse_stats_xml */
+} /* }}} cx_parse_xml */
 
-static int cx_curl_perform(cx_t *db, CURL *curl) /* {{{ */
+static int cx_read(user_data_t *ud) /* {{{ */
 {
-  int status;
+  if ((ud == NULL) || (ud->data == NULL)) {
+    ERROR("curl_xml plugin: cx_read: Invalid user data.");
+    return -1;
+  }
+
   long rc;
-  char *ptr;
   char *url;
+  cx_t *db = (cx_t *)ud->data;
 
   db->buffer_fill = 0;
 
   curl_easy_setopt(db->curl, CURLOPT_URL, db->url);
 
-  status = curl_easy_perform(curl);
+  int status = curl_easy_perform(db->curl);
   if (status != CURLE_OK) {
     ERROR("curl_xml plugin: curl_easy_perform failed with status %i: %s (%s)",
           status, db->curl_errbuf, db->url);
@@ -617,8 +563,8 @@ static int cx_curl_perform(cx_t *db, CURL *curl) /* {{{ */
     curl_stats_dispatch(db->stats, db->curl, cx_host(db), "curl_xml",
                         db->instance);
 
-  curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
-  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
+  curl_easy_getinfo(db->curl, CURLINFO_EFFECTIVE_URL, &url);
+  curl_easy_getinfo(db->curl, CURLINFO_RESPONSE_CODE, &rc);
 
   /* The response code is zero if a non-HTTP transport was used. */
   if ((rc != 0) && (rc != 200)) {
@@ -628,26 +574,10 @@ static int cx_curl_perform(cx_t *db, CURL *curl) /* {{{ */
     return -1;
   }
 
-  ptr = db->buffer;
-
-  status = cx_parse_stats_xml(BAD_CAST ptr, db);
+  status = cx_parse_xml(db, db->buffer);
   db->buffer_fill = 0;
 
   return status;
-} /* }}} int cx_curl_perform */
-
-static int cx_read(user_data_t *ud) /* {{{ */
-{
-  cx_t *db;
-
-  if ((ud == NULL) || (ud->data == NULL)) {
-    ERROR("curl_xml plugin: cx_read: Invalid user data.");
-    return -1;
-  }
-
-  db = (cx_t *)ud->data;
-
-  return cx_curl_perform(db, db->curl);
 } /* }}} int cx_read */
 
 /* Configuration handling functions {{{ */
@@ -685,18 +615,13 @@ static int cx_config_add_values(const char *name, cx_xpath_t *xpath, /* {{{ */
 
 static int cx_config_add_xpath(cx_t *db, oconfig_item_t *ci) /* {{{ */
 {
-  cx_xpath_t *xpath;
-  char *name;
-  llentry_t *le;
-  int status;
-
-  xpath = calloc(1, sizeof(*xpath));
+  cx_xpath_t *xpath = calloc(1, sizeof(*xpath));
   if (xpath == NULL) {
     ERROR("curl_xml plugin: calloc failed.");
     return -1;
   }
 
-  status = cf_util_get_string(ci, &xpath->path);
+  int status = cf_util_get_string(ci, &xpath->path);
   if (status != 0) {
     cx_xpath_free(xpath);
     return status;
@@ -720,6 +645,8 @@ static int cx_config_add_xpath(cx_t *db, oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string(child, &xpath->instance_prefix);
     else if (strcasecmp("InstanceFrom", child->key) == 0)
       status = cf_util_get_string(child, &xpath->instance);
+    else if (strcasecmp("PluginInstanceFrom", child->key) == 0)
+      status = cf_util_get_string(child, &xpath->plugin_instance_from);
     else if (strcasecmp("ValuesFrom", child->key) == 0)
       status = cx_config_add_values("ValuesFrom", xpath, child);
     else {
@@ -742,37 +669,25 @@ static int cx_config_add_xpath(cx_t *db, oconfig_item_t *ci) /* {{{ */
     return -1;
   }
 
-  if (db->list == NULL) {
-    db->list = llist_create();
-    if (db->list == NULL) {
-      ERROR("curl_xml plugin: list creation failed.");
-      cx_xpath_free(xpath);
-      return -1;
-    }
-  }
-
-  name = strdup(xpath->path);
-  if (name == NULL) {
-    ERROR("curl_xml plugin: strdup failed.");
+  if (xpath->values_len == 0) {
+    WARNING("curl_xml plugin: `ValuesFrom' missing in `xpath' block.");
     cx_xpath_free(xpath);
     return -1;
   }
 
-  le = llentry_create(name, xpath);
+  llentry_t *le = llentry_create(xpath->path, xpath);
   if (le == NULL) {
     ERROR("curl_xml plugin: llentry_create failed.");
     cx_xpath_free(xpath);
-    sfree(name);
     return -1;
   }
 
-  llist_append(db->list, le);
+  llist_append(db->xpath_list, le);
   return 0;
 } /* }}} int cx_config_add_xpath */
 
 static int cx_config_add_namespace(cx_t *db, /* {{{ */
                                    oconfig_item_t *ci) {
-  cx_namespace_t *ns;
 
   if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_STRING) ||
       (ci->values[1].type != OCONFIG_TYPE_STRING)) {
@@ -781,8 +696,8 @@ static int cx_config_add_namespace(cx_t *db, /* {{{ */
     return EINVAL;
   }
 
-  ns = realloc(db->namespaces,
-               sizeof(*db->namespaces) * (db->namespaces_num + 1));
+  cx_namespace_t *ns = realloc(
+      db->namespaces, sizeof(*db->namespaces) * (db->namespaces_num + 1));
   if (ns == NULL) {
     ERROR("curl_xml plugin: realloc failed.");
     return ENOMEM;
@@ -871,43 +786,51 @@ static int cx_init_curl(cx_t *db) /* {{{ */
 
 static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
 {
-  cx_t *db;
-  int status = 0;
-
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
     WARNING("curl_xml plugin: The `URL' block "
             "needs exactly one string argument.");
     return -1;
   }
 
-  db = calloc(1, sizeof(*db));
+  cx_t *db = calloc(1, sizeof(*db));
   if (db == NULL) {
     ERROR("curl_xml plugin: calloc failed.");
     return -1;
   }
 
-  db->timeout = -1;
+  db->instance = strdup("default");
+  if (db->instance == NULL) {
+    ERROR("curl_xml plugin: strdup failed.");
+    sfree(db);
+    return -1;
+  }
 
-  if (strcasecmp("URL", ci->key) == 0) {
-    status = cf_util_get_string(ci, &db->url);
-    if (status != 0) {
-      sfree(db);
-      return status;
-    }
-  } else {
-    ERROR("curl_xml plugin: cx_config: "
-          "Invalid key: %s",
-          ci->key);
-    cx_free(db);
+  db->xpath_list = llist_create();
+  if (db->xpath_list == NULL) {
+    ERROR("curl_xml plugin: list creation failed.");
+    sfree(db->instance);
+    sfree(db);
     return -1;
   }
 
+  db->timeout = -1;
+
+  int status = cf_util_get_string(ci, &db->url);
+  if (status != 0) {
+    llist_destroy(db->xpath_list);
+    sfree(db->instance);
+    sfree(db);
+    return status;
+  }
+
   /* Fill the `cx_t' structure.. */
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("Instance", child->key) == 0)
       status = cf_util_get_string(child, &db->instance);
+    else if (strcasecmp("Plugin", child->key) == 0)
+      status = cf_util_get_string(child, &db->plugin_name);
     else if (strcasecmp("Host", child->key) == 0)
       status = cf_util_get_string(child, &db->host);
     else if (strcasecmp("User", child->key) == 0)
@@ -945,39 +868,34 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
       break;
   }
 
-  if (status == 0) {
-    if (db->list == NULL) {
-      WARNING("curl_xml plugin: No (valid) `Key' block "
-              "within `URL' block `%s'.",
-              db->url);
-      status = -1;
-    }
-    if (status == 0)
-      status = cx_init_curl(db);
+  if (status != 0) {
+    cx_free(db);
+    return status;
   }
 
-  /* If all went well, register this database for reading */
-  if (status == 0) {
-    char *cb_name;
-
-    if (db->instance == NULL)
-      db->instance = strdup("default");
-
-    DEBUG("curl_xml plugin: Registering new read callback: %s", db->instance);
-
-    cb_name = ssnprintf_alloc("curl_xml-%s-%s", db->instance, db->url);
+  if (llist_size(db->xpath_list) == 0) {
+    WARNING("curl_xml plugin: No `xpath' block within `URL' block `%s'.",
+            db->url);
+    cx_free(db);
+    return -1;
+  }
 
-    plugin_register_complex_read(/* group = */ "curl_xml", cb_name, cx_read,
-                                 /* interval = */ 0,
-                                 &(user_data_t){
-                                     .data = db, .free_func = cx_free,
-                                 });
-    sfree(cb_name);
-  } else {
+  if (cx_init_curl(db) != 0) {
     cx_free(db);
     return -1;
   }
 
+  /* If all went well, register this database for reading */
+  DEBUG("curl_xml plugin: Registering new read callback: %s", db->instance);
+
+  char *cb_name = ssnprintf_alloc("curl_xml-%s-%s", db->instance, db->url);
+
+  plugin_register_complex_read(/* group = */ "curl_xml", cb_name, cx_read,
+                               /* interval = */ 0,
+                               &(user_data_t){
+                                   .data = db, .free_func = cx_free,
+                               });
+  sfree(cb_name);
   return 0;
 } /* }}} int cx_config_add_url */
 
@@ -985,19 +903,14 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
 
 static int cx_config(oconfig_item_t *ci) /* {{{ */
 {
-  int success;
-  int errors;
-  int status;
-
-  success = 0;
-  errors = 0;
+  int success = 0;
+  int errors = 0;
 
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("URL", child->key) == 0) {
-      status = cx_config_add_url(child);
-      if (status == 0)
+      if (cx_config_add_url(child) == 0)
         success++;
       else
         errors++;
index 2edfa3774ca763067d5eefcbfa7fde6791c99b60..dd9b12f85669cfc2e490320308b3bd3b0adaf37e 100644 (file)
 #define COLLECTD_LOCALE "C"
 #endif
 
-/*
- * Global variables
- */
-char hostname_g[DATA_MAX_NAME_LEN];
-cdtime_t interval_g;
-int timeout_g;
-#if HAVE_LIBKSTAT
-kstat_ctl_t *kc;
-#endif /* HAVE_LIBKSTAT */
-
 static int loop = 0;
 
 static void *do_flush(void __attribute__((unused)) * arg) {
@@ -91,13 +81,19 @@ static int init_hostname(void) {
   struct addrinfo *ai_list;
   int status;
 
+  long hostname_len = sysconf(_SC_HOST_NAME_MAX);
+  if (hostname_len == -1) {
+    hostname_len = NI_MAXHOST;
+  }
+  char hostname[hostname_len];
+
   str = global_option_get("Hostname");
   if ((str != NULL) && (str[0] != 0)) {
-    sstrncpy(hostname_g, str, sizeof(hostname_g));
+    hostname_set(str);
     return 0;
   }
 
-  if (gethostname(hostname_g, sizeof(hostname_g)) != 0) {
+  if (gethostname(hostname, hostname_len) != 0) {
     fprintf(stderr, "`gethostname' failed and no "
                     "hostname was configured.\n");
     return -1;
@@ -109,14 +105,14 @@ static int init_hostname(void) {
 
   struct addrinfo ai_hints = {.ai_flags = AI_CANONNAME};
 
-  status = getaddrinfo(hostname_g, NULL, &ai_hints, &ai_list);
+  status = getaddrinfo(hostname, NULL, &ai_hints, &ai_list);
   if (status != 0) {
     ERROR("Looking up \"%s\" failed. You have set the "
           "\"FQDNLookup\" option, but I cannot resolve "
           "my hostname to a fully qualified domain "
           "name. Please fix the network "
           "configuration.",
-          hostname_g);
+          hostname);
     return -1;
   }
 
@@ -125,7 +121,7 @@ static int init_hostname(void) {
     if (ai_ptr->ai_canonname == NULL)
       continue;
 
-    sstrncpy(hostname_g, ai_ptr->ai_canonname, sizeof(hostname_g));
+    hostname_set(ai_ptr->ai_canonname);
     break;
   }
 
@@ -158,7 +154,7 @@ static int init_global_variables(void) {
   return 0;
 } /* int init_global_variables */
 
-static int change_basedir(const char *orig_dir) {
+static int change_basedir(const char *orig_dir, _Bool create) {
   char *dir;
   size_t dirlen;
   int status;
@@ -183,7 +179,7 @@ static int change_basedir(const char *orig_dir) {
   if (status == 0) {
     free(dir);
     return 0;
-  } else if (errno != ENOENT) {
+  } else if (!create || (errno != ENOENT)) {
     char errbuf[1024];
     ERROR("change_basedir: chdir (%s): %s", dir,
           sstrerror(errno, errbuf, sizeof(errbuf)));
@@ -250,6 +246,7 @@ __attribute__((noreturn)) static void exit_usage(int status) {
 #if COLLECT_DAEMON
          "    -f              Don't fork to the background.\n"
 #endif
+         "    -B              Don't create the BaseDir\n"
          "    -h              Display help (this message)\n"
          "\nBuiltin defaults:\n"
          "  Config file       " CONFIGFILE "\n"
@@ -454,21 +451,18 @@ static int notify_systemd(void) {
 }
 #endif /* KERNEL_LINUX */
 
-int main(int argc, char **argv) {
-  const char *configfile = CONFIGFILE;
-  int test_config = 0;
-  int test_readall = 0;
-  const char *basedir;
-#if COLLECT_DAEMON
-  pid_t pid;
-  int daemonize = 1;
-#endif
-  int exit_status = 0;
+struct cmdline_config {
+  _Bool test_config;
+  _Bool test_readall;
+  _Bool create_basedir;
+  const char *configfile;
+  _Bool daemonize;
+};
 
+void read_cmdline(int argc, char **argv, struct cmdline_config *config) {
   /* read options */
   while (1) {
     int c;
-
     c = getopt(argc, argv, "htTC:"
 #if COLLECT_DAEMON
                            "fP:"
@@ -479,17 +473,20 @@ int main(int argc, char **argv) {
       break;
 
     switch (c) {
+    case 'B':
+      config->create_basedir = 0;
+      break;
     case 'C':
-      configfile = optarg;
+      config->configfile = optarg;
       break;
     case 't':
-      test_config = 1;
+      config->test_config = 1;
       break;
     case 'T':
-      test_readall = 1;
+      config->test_readall = 1;
       global_option_set("ReadThreads", "-1", 1);
 #if COLLECT_DAEMON
-      daemonize = 0;
+      config->daemonize = 0;
 #endif /* COLLECT_DAEMON */
       break;
 #if COLLECT_DAEMON
@@ -497,7 +494,7 @@ int main(int argc, char **argv) {
       global_option_set("PIDFile", optarg, 1);
       break;
     case 'f':
-      daemonize = 0;
+      config->daemonize = 0;
       break;
 #endif /* COLLECT_DAEMON */
     case 'h':
@@ -507,19 +504,17 @@ int main(int argc, char **argv) {
       exit_usage(1);
     } /* switch (c) */
   }   /* while (1) */
+}
 
-  if (optind < argc)
-    exit_usage(1);
-
-  plugin_init_ctx();
-
+int configure_collectd(struct cmdline_config *config) {
+  const char *basedir;
   /*
    * Read options from the config file, the environment and the command
    * line (in that order, with later options overwriting previous ones in
    * general).
    * Also, this will automatically load modules.
    */
-  if (cf_read(configfile)) {
+  if (cf_read(config->configfile)) {
     fprintf(stderr, "Error: Reading the config file failed!\n"
                     "Read the logs for details.\n");
     return 1;
@@ -533,23 +528,47 @@ int main(int argc, char **argv) {
     fprintf(stderr,
             "Don't have a basedir to use. This should not happen. Ever.");
     return 1;
-  } else if (change_basedir(basedir)) {
+  } else if (change_basedir(basedir, config->create_basedir)) {
     fprintf(stderr, "Error: Unable to change to directory `%s'.\n", basedir);
     return 1;
   }
 
   /*
-   * Set global variables or, if that failes, exit. We cannot run with
+   * Set global variables or, if that fails, exit. We cannot run with
    * them being uninitialized. If nothing is configured, then defaults
    * are being used. So this means that the user has actually done
    * something wrong.
    */
   if (init_global_variables() != 0)
-    exit(EXIT_FAILURE);
+    return 1;
+
+  return 0;
+}
+
+int main(int argc, char **argv) {
+#if COLLECT_DAEMON
+  pid_t pid;
+#endif
+  int exit_status = 0;
 
-  if (test_config)
+  struct cmdline_config config = {
+      .daemonize = 1, .create_basedir = 1, .configfile = CONFIGFILE,
+  };
+
+  read_cmdline(argc, argv, &config);
+
+  if (config.test_config)
     return 0;
 
+  if (optind < argc)
+    exit_usage(1);
+
+  plugin_init_ctx();
+
+  int status;
+  if ((status = configure_collectd(&config)) != 0)
+    exit(EXIT_FAILURE);
+
 #if COLLECT_DAEMON
   /*
    * fork off child
@@ -562,7 +581,7 @@ int main(int argc, char **argv) {
    * Only daemonize if we're not being supervised
    * by upstart or systemd (when using Linux).
    */
-  if (daemonize
+  if (config.daemonize
 #ifdef KERNEL_LINUX
       && notify_upstart() == 0 && notify_systemd() == 0
 #endif
@@ -612,7 +631,7 @@ int main(int argc, char **argv) {
             status);
       return 1;
     }
-  }    /* if (daemonize) */
+  }    /* if (config.daemonize) */
 #endif /* COLLECT_DAEMON */
 
   struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN};
@@ -657,7 +676,7 @@ int main(int argc, char **argv) {
     exit_status = 1;
   }
 
-  if (test_readall) {
+  if (config.test_readall) {
     if (plugin_read_all_once() != 0) {
       ERROR("Error: one or more plugin read callbacks failed.");
       exit_status = 1;
@@ -676,7 +695,7 @@ int main(int argc, char **argv) {
   }
 
 #if COLLECT_DAEMON
-  if (daemonize)
+  if (config.daemonize)
     pidfile_remove();
 #endif /* COLLECT_DAEMON */
 
index 01d484ee082fa3b05edad7da08acf33f2a1f2c83..0558aa400696183a42e9bac546b56e73e019656c 100644 (file)
 #include <sys/param.h>
 #endif
 
-#if HAVE_KSTAT_H
-#include <kstat.h>
-#endif
-
 #ifndef PACKAGE_NAME
 #define PACKAGE_NAME "collectd"
 #endif
 #define GAUGE_FORMAT "%.15g"
 #endif
 
-/* Type for time as used by "utils_time.h" */
-typedef uint64_t cdtime_t;
-
-extern char hostname_g[];
-extern cdtime_t interval_g;
-extern int timeout_g;
+#include "globals.h"
 
 #endif /* COLLECTD_H */
index 3ae61d8592e5df6b3f42986f0e1131ef5682ace3..cf981dc0a3d8b03b0022bf997f430115e7d4cc47 100644 (file)
@@ -212,7 +212,7 @@ void sfree (void **ptr)
 }
 #endif
 
-ssize_t sread(int fd, void *buf, size_t count) {
+int sread(int fd, void *buf, size_t count) {
   char *ptr;
   size_t nleft;
   ssize_t status;
@@ -230,10 +230,7 @@ ssize_t sread(int fd, void *buf, size_t count) {
       return status;
 
     if (status == 0) {
-      DEBUG("Received EOF from fd %i. "
-            "Closing fd and returning error.",
-            fd);
-      close(fd);
+      DEBUG("Received EOF from fd %i. ", fd);
       return -1;
     }
 
@@ -246,7 +243,7 @@ ssize_t sread(int fd, void *buf, size_t count) {
   return 0;
 }
 
-ssize_t swrite(int fd, const void *buf, size_t count) {
+int swrite(int fd, const void *buf, size_t count) {
   const char *ptr;
   size_t nleft;
   ssize_t status;
@@ -269,7 +266,8 @@ ssize_t swrite(int fd, const void *buf, size_t count) {
     if (recv(fd, buffer, sizeof(buffer), MSG_PEEK | MSG_DONTWAIT) == 0) {
       /* if recv returns zero (even though poll() said there is data to be
        * read), that means the connection has been closed */
-      return errno ? errno : -1;
+      errno = ECONNRESET;
+      return -1;
     }
   }
 
index afd292a3d9b5a31bc91200b59694eb57ab2aac62..7f8605215b7f5de425721bba2e8a482b352ee5ce 100644 (file)
@@ -79,8 +79,7 @@ char *sstrerror(int errnum, char *buf, size_t buflen);
  *
  * DESCRIPTION
  *   Reads exactly `n' bytes or fails. Syntax and other behavior is analogous
- *   to `read(2)'. If EOF is received the file descriptor is closed and an
- *   error is returned.
+ *   to `read(2)'.
  *
  * PARAMETERS
  *   `fd'          File descriptor to write to.
@@ -91,7 +90,7 @@ char *sstrerror(int errnum, char *buf, size_t buflen);
  *   Zero upon success or non-zero if an error occurred. `errno' is set in this
  *   case.
  */
-ssize_t sread(int fd, void *buf, size_t count);
+int sread(int fd, void *buf, size_t count);
 
 /*
  * NAME
@@ -110,7 +109,7 @@ ssize_t sread(int fd, void *buf, size_t count);
  *   Zero upon success or non-zero if an error occurred. `errno' is set in this
  *   case.
  */
-ssize_t swrite(int fd, const void *buf, size_t count);
+int swrite(int fd, const void *buf, size_t count);
 
 /*
  * NAME
index 0d295c1cbaf476d36682bef152457b19641ee599..f5086ae64de751f75c8b9bc20139a9e81c074a5f 100644 (file)
@@ -461,9 +461,9 @@ static int cf_ci_replace_child(oconfig_item_t *dst, oconfig_item_t *src,
     return 0;
   }
 
-  temp =
-      realloc(dst->children, sizeof(oconfig_item_t) *
-                                 (dst->children_num + src->children_num - 1));
+  temp = realloc(dst->children,
+                 sizeof(oconfig_item_t) *
+                     (dst->children_num + src->children_num - 1));
   if (temp == NULL) {
     ERROR("configfile: realloc failed.");
     return -1;
@@ -502,8 +502,9 @@ static int cf_ci_append_children(oconfig_item_t *dst, oconfig_item_t *src) {
   if ((src == NULL) || (src->children_num == 0))
     return 0;
 
-  temp = realloc(dst->children, sizeof(oconfig_item_t) *
-                                    (dst->children_num + src->children_num));
+  temp =
+      realloc(dst->children,
+              sizeof(oconfig_item_t) * (dst->children_num + src->children_num));
   if (temp == NULL) {
     ERROR("configfile: realloc failed.");
     return -1;
@@ -874,7 +875,8 @@ const char *global_option_get(const char *option) {
     return NULL;
   }
 
-  return (cf_global_options[i].value != NULL) ? cf_global_options[i].value : cf_global_options[i].def;
+  return (cf_global_options[i].value != NULL) ? cf_global_options[i].value
+                                              : cf_global_options[i].def;
 } /* char *global_option_get */
 
 long global_option_get_long(const char *option, long default_value) {
@@ -1031,6 +1033,7 @@ int cf_read(const char *filename) {
   }
 
   return ret;
+
 } /* int cf_read */
 
 /* Assures the config option is a string, duplicates it and returns the copy in
diff --git a/src/daemon/globals.c b/src/daemon/globals.c
new file mode 100644 (file)
index 0000000..5c6749f
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * collectd - src/globals.c
+ * Copyright (C) 2017  Google LLC
+ *
+ * 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.
+ **/
+
+#include "common.h"
+#include "globals.h"
+
+#if HAVE_KSTAT_H
+#include <kstat.h>
+#endif
+
+/*
+ * Global variables
+ */
+char *hostname_g;
+cdtime_t interval_g;
+int timeout_g;
+#if HAVE_KSTAT_H
+kstat_ctl_t *kc;
+#endif
+
+void hostname_set(char const *hostname) {
+  char *h = strdup(hostname);
+  if (h == NULL)
+    return;
+
+  sfree(hostname_g);
+  hostname_g = h;
+}
diff --git a/src/daemon/globals.h b/src/daemon/globals.h
new file mode 100644 (file)
index 0000000..bc11d6b
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * collectd - src/globals.h
+ * Copyright (C) 2017  Google LLC
+ *
+ * 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.
+ **/
+
+#ifndef GLOBALS_H
+#define GLOBALS_H
+
+#include <inttypes.h>
+
+#ifndef DATA_MAX_NAME_LEN
+#define DATA_MAX_NAME_LEN 128
+#endif
+
+/* Type for time as used by "utils_time.h" */
+typedef uint64_t cdtime_t;
+
+/* hostname_set updates hostname_g */
+void hostname_set(char const *hostname);
+
+extern char *hostname_g;
+extern cdtime_t interval_g;
+extern int pidfile_from_cli;
+extern int timeout_g;
+#endif /* GLOBALS_H */
index 0451e8a757665db7b501fcc98bdd2db18b22c88a..ae99d5fdfa13bb9aef4b7d6efb994dcb6078e01b 100644 (file)
@@ -191,16 +191,21 @@ static int plugin_update_internal_statistics(void) { /* {{{ */
   return 0;
 } /* }}} int plugin_update_internal_statistics */
 
-static void destroy_callback(callback_func_t *cf) /* {{{ */
+static void free_userdata(user_data_t const *ud) /* {{{ */
 {
-  if (cf == NULL)
+  if (ud == NULL)
     return;
 
-  if ((cf->cf_udata.data != NULL) && (cf->cf_udata.free_func != NULL)) {
-    cf->cf_udata.free_func(cf->cf_udata.data);
-    cf->cf_udata.data = NULL;
-    cf->cf_udata.free_func = NULL;
+  if ((ud->data != NULL) && (ud->free_func != NULL)) {
+    ud->free_func(ud->data);
   }
+} /* }}} void free_userdata */
+
+static void destroy_callback(callback_func_t *cf) /* {{{ */
+{
+  if (cf == NULL)
+    return;
+  free_userdata(&cf->cf_udata);
   sfree(cf);
 } /* }}} void destroy_callback */
 
@@ -345,6 +350,7 @@ static int create_register_callback(llist_t **list, /* {{{ */
 
   cf = calloc(1, sizeof(*cf));
   if (cf == NULL) {
+    free_userdata(ud);
     ERROR("plugin: create_register_callback: calloc failed.");
     return -1;
   }
@@ -1120,8 +1126,7 @@ static int plugin_insert_read(read_func_t *rf) {
   if (le != NULL) {
     pthread_mutex_unlock(&read_lock);
     WARNING("The read function \"%s\" is already registered. "
-            "Check for duplicate \"LoadPlugin\" lines "
-            "in your configuration!",
+            "Check for duplicates in your configuration!",
             rf->rf_name);
     return EINVAL;
   }
@@ -1186,6 +1191,7 @@ int plugin_register_complex_read(const char *group, const char *name,
 
   rf = calloc(1, sizeof(*rf));
   if (rf == NULL) {
+    free_userdata(user_data);
     ERROR("plugin_register_complex_read: calloc failed.");
     return ENOMEM;
   }
@@ -1211,6 +1217,7 @@ int plugin_register_complex_read(const char *group, const char *name,
 
   status = plugin_insert_read(rf);
   if (status != 0) {
+    free_userdata(&rf->rf_udata);
     sfree(rf->rf_name);
     sfree(rf);
   }
@@ -1303,11 +1310,7 @@ int plugin_register_flush(const char *name, plugin_flush_cb callback,
         });
 
     sfree(flush_name);
-    if (status != 0) {
-      sfree(cb->name);
-      sfree(cb);
-      return status;
-    }
+    return status;
   }
 
   return 0;
index 4f877e0ecb2f3407b8e475f99768facc12df03da..a9ee72d4c9328c2e9827468cb8dff27998894c95 100644 (file)
 
 #include <pthread.h>
 
-#ifndef DATA_MAX_NAME_LEN
-#define DATA_MAX_NAME_LEN 128
-#endif
-
 #define DS_TYPE_COUNTER 0
 #define DS_TYPE_GAUGE 1
 #define DS_TYPE_DERIVE 2
 #define DS_TYPE_ABSOLUTE 3
 
 #define DS_TYPE_TO_STRING(t)                                                   \
-  (t == DS_TYPE_COUNTER) ? "counter" : (t == DS_TYPE_GAUGE)                    \
-                                           ? "gauge"                           \
-                                           : (t == DS_TYPE_DERIVE)             \
-                                                 ? "derive"                    \
-                                                 : (t == DS_TYPE_ABSOLUTE)     \
-                                                       ? "absolute"            \
-                                                       : "unknown"
+  (t == DS_TYPE_COUNTER)                                                       \
+      ? "counter"                                                              \
+      : (t == DS_TYPE_GAUGE)                                                   \
+            ? "gauge"                                                          \
+            : (t == DS_TYPE_DERIVE)                                            \
+                  ? "derive"                                                   \
+                  : (t == DS_TYPE_ABSOLUTE) ? "absolute" : "unknown"
 
 #ifndef LOG_ERR
 #define LOG_ERR 3
index ca985398af03cec02b34106201d6ecf059ad6b74..6df4c15df691b2c3c162ff6e2c39d76637aa2cb4 100644 (file)
@@ -30,7 +30,7 @@
 kstat_ctl_t *kc = NULL;
 #endif /* HAVE_LIBKSTAT */
 
-char hostname_g[] = "example.com";
+char *hostname_g = "example.com";
 
 void plugin_set_dir(const char *dir) { /* nop */
 }
index 7d6e8fd6eb23a8cdedd837ca0de6cda5588dcf08..ea7c3e33b4ab9e064b26e26ae6657d983dc605ea 100644 (file)
@@ -913,6 +913,15 @@ int uc_iterator_get_interval(uc_iter_t *iter, cdtime_t *ret_interval) {
   return 0;
 } /* int uc_iterator_get_name */
 
+int uc_iterator_get_meta(uc_iter_t *iter, meta_data_t **ret_meta) {
+  if ((iter == NULL) || (iter->entry == NULL) || (ret_meta == NULL))
+    return -1;
+
+  *ret_meta = meta_data_clone(iter->entry->meta);
+
+  return 0;
+} /* int uc_iterator_get_meta */
+
 /*
  * Meta data interface
  */
index 8ba7133ab29bc2bc413db612f89d7af8e6912cf6..08c2f10a7fbddf5db8f165a44397ffc108139d40 100644 (file)
@@ -106,6 +106,8 @@ int uc_iterator_get_values(uc_iter_t *iter, value_t **ret_values,
                            size_t *ret_num);
 /* Return the interval of the value at the current position. */
 int uc_iterator_get_interval(uc_iter_t *iter, cdtime_t *ret_interval);
+/* Return the metadata for the value at the current position. */
+int uc_iterator_get_meta(uc_iter_t *iter, meta_data_t **ret_meta);
 
 /*
  * Meta data interface
index a577aefc766e4ad9326a0ee2cc2b1c844ebe7511..62ef1dc44b4a908b3f75c9cbe4c1f24f495c4663 100644 (file)
--- a/src/dbi.c
+++ b/src/dbi.c
@@ -62,6 +62,7 @@ struct cdbi_database_s /* {{{ */
 {
   char *name;
   char *select_db;
+  char *plugin_name;
 
   cdtime_t interval;
 
@@ -172,7 +173,10 @@ static void cdbi_database_free(cdbi_database_t *db) /* {{{ */
     return;
 
   sfree(db->name);
+  sfree(db->select_db);
+  sfree(db->plugin_name);
   sfree(db->driver);
+  sfree(db->host);
 
   for (size_t i = 0; i < db->driver_options_num; i++) {
     sfree(db->driver_options[i].key);
@@ -184,7 +188,10 @@ static void cdbi_database_free(cdbi_database_t *db) /* {{{ */
   if (db->q_prep_areas)
     for (size_t i = 0; i < db->queries_num; ++i)
       udb_query_delete_preparation_area(db->q_prep_areas[i]);
-  free(db->q_prep_areas);
+  sfree(db->q_prep_areas);
+  /* N.B.: db->queries references objects "owned" by the global queries
+   * variable. Free the array here, but not the content. */
+  sfree(db->queries);
 
   sfree(db);
 } /* }}} void cdbi_database_free */
@@ -298,6 +305,8 @@ static int cdbi_config_add_database(oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string(child, &db->host);
     else if (strcasecmp("Interval", child->key) == 0)
       status = cf_util_get_cdtime(child, &db->interval);
+    else if (strcasecmp("Plugin", child->key) == 0)
+      status = cf_util_get_string(child, &db->plugin_name);
     else {
       WARNING("dbi plugin: Option `%s' not allowed here.", child->key);
       status = -1;
@@ -540,7 +549,8 @@ static int cdbi_read_database_query(cdbi_database_t *db, /* {{{ */
 
   udb_query_prepare_result(
       q, prep_area, (db->host ? db->host : hostname_g),
-      /* plugin = */ "dbi", db->name, column_names, column_num,
+      /* plugin = */ (db->plugin_name != NULL) ? db->plugin_name : "dbi",
+      db->name, column_names, column_num,
       /* interval = */ (db->interval > 0) ? db->interval : 0);
 
   /* 0 = error; 1 = success; */
index efb76947abe653b1453a9eb24009311f08a38c54..32d3d6af4e9d65a396f45362d4db0305c8bfe514 100644 (file)
@@ -488,7 +488,6 @@ static void dpdk_events_gauge_submit(const char *plugin_instance,
                      .plugin = DPDK_EVENTS_PLUGIN,
                      .type = "gauge",
                      .meta = NULL};
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
   plugin_dispatch_values(&vl);
index d36cb8fbbae9a2c44fbe3ffec9a264a6f66407e3..c95ba0c6c3d66423c2dad2eba63da239df857272 100644 (file)
@@ -373,7 +373,6 @@ static void dpdk_stats_counter_submit(const char *plugin_instance,
   vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
   vl.time = port_read_time;
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, DPDK_STATS_PLUGIN, sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
   dpdk_stats_resolve_cnt_type(vl.type, sizeof(vl.type), cnt_name);
index e5f015b2539b6d91e25d4f95e5c9b38bc2ef97cf..00e7413d8601deddd37be261b4bedf00adc8c265 100644 (file)
@@ -402,9 +402,15 @@ static void *open_connection(void __attribute__((unused)) * arg) {
   {
     struct group sg;
     struct group *grp;
-    char grbuf[4096];
     int status;
 
+    long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX);
+    if (grbuf_size <= 0)
+      grbuf_size = sysconf(_SC_PAGESIZE);
+    if (grbuf_size <= 0)
+      grbuf_size = 4096;
+    char grbuf[grbuf_size];
+
     grp = NULL;
     status = getgrnam_r(group, &sg, grbuf, sizeof(grbuf), &grp);
     if (status != 0) {
index 22da3160be6c6bb7054c80f9533e8089d421910f..a9f7be5c4447ad1efc2664932990f5e74d758a58 100644 (file)
@@ -368,11 +368,17 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
 
   struct passwd *sp_ptr;
   struct passwd sp;
-  char nambuf[4096];
 
   if (pl->pid != 0)
     return -1;
 
+  long int nambuf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+  if (nambuf_size <= 0)
+    nambuf_size = sysconf(_SC_PAGESIZE);
+  if (nambuf_size <= 0)
+    nambuf_size = 4096;
+  char nambuf[nambuf_size];
+
   if ((create_pipe(fd_pipe_in) == -1) || (create_pipe(fd_pipe_out) == -1) ||
       (create_pipe(fd_pipe_err) == -1))
     goto failed;
@@ -405,7 +411,14 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
       struct group *gr_ptr = NULL;
       struct group gr;
 
-      status = getgrnam_r(pl->group, &gr, nambuf, sizeof(nambuf), &gr_ptr);
+      long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX);
+      if (grbuf_size <= 0)
+        grbuf_size = sysconf(_SC_PAGESIZE);
+      if (grbuf_size <= 0)
+        grbuf_size = 4096;
+      char grbuf[grbuf_size];
+
+      status = getgrnam_r(pl->group, &gr, grbuf, sizeof(grbuf), &gr_ptr);
       if (status != 0) {
         ERROR("exec plugin: Failed to get group information "
               "for group ``%s'': %s",
index a2ec8e82884e8888bd6eb9f57847b3eec3c04adb..7842aa610c786719f61762b11ca55ad6d9565941 100644 (file)
 
 #define FC_RECURSIVE 1
 #define FC_HIDDEN 2
+#define FC_REGULAR 4
 
 struct fc_directory_conf_s {
   char *path;
+  char *plugin_name;
   char *instance;
+  char *files_size_type;
+  char *files_num_type;
+  char *type_instance;
 
   int options;
 
@@ -58,31 +63,56 @@ typedef struct fc_directory_conf_s fc_directory_conf_t;
 static fc_directory_conf_t **directories = NULL;
 static size_t directories_num = 0;
 
+void fc_free_dir(fc_directory_conf_t *dir) {
+  sfree(dir->path);
+  sfree(dir->plugin_name);
+  sfree(dir->instance);
+  sfree(dir->files_size_type);
+  sfree(dir->files_num_type);
+  sfree(dir->type_instance);
+  sfree(dir->name);
+
+  sfree(dir);
+} /* void fc_free_dir */
+
 static void fc_submit_dir(const fc_directory_conf_t *dir) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  vl.values = &(value_t){.gauge = (gauge_t)dir->files_num};
-  vl.values_len = 1;
-  sstrncpy(vl.plugin, "filecount", sizeof(vl.plugin));
-  sstrncpy(vl.plugin_instance, dir->instance, sizeof(vl.plugin_instance));
-  sstrncpy(vl.type, "files", sizeof(vl.type));
+  sstrncpy(vl.plugin, dir->plugin_name, sizeof(vl.plugin));
+  if (dir->instance != NULL)
+    sstrncpy(vl.plugin_instance, dir->instance, sizeof(vl.plugin_instance));
+  if (dir->type_instance != NULL)
+    sstrncpy(vl.type_instance, dir->type_instance, sizeof(vl.type_instance));
 
-  plugin_dispatch_values(&vl);
+  vl.values_len = 1;
 
-  vl.values = &(value_t){.gauge = (gauge_t)dir->files_size};
-  sstrncpy(vl.type, "bytes", sizeof(vl.type));
+  if (dir->files_num_type != NULL) {
+    vl.values = &(value_t){.gauge = (gauge_t)dir->files_num};
+    sstrncpy(vl.type, dir->files_num_type, sizeof(vl.type));
+    plugin_dispatch_values(&vl);
+  }
 
-  plugin_dispatch_values(&vl);
+  if (dir->files_size_type != NULL) {
+    vl.values = &(value_t){.gauge = (gauge_t)dir->files_size};
+    sstrncpy(vl.type, dir->files_size_type, sizeof(vl.type));
+    plugin_dispatch_values(&vl);
+  }
 } /* void fc_submit_dir */
 
 /*
  * Config:
  * <Plugin filecount>
  *   <Directory /path/to/dir>
+ *     Plugin "foo"
  *     Instance "foobar"
  *     Name "*.conf"
  *     MTime -3600
  *     Size "+10M"
+ *     Recursive true
+ *     IncludeHidden false
+ *     FilesSizeType "bytes"
+ *     FilesCountType "files"
+ *     TypeInstance "instance"
  *   </Directory>
  * </Plugin>
  *
@@ -94,7 +124,6 @@ static void fc_submit_dir(const fc_directory_conf_t *dir) {
 static int fc_config_set_instance(fc_directory_conf_t *dir, const char *str) {
   char buffer[1024];
   char *ptr;
-  char *copy;
 
   sstrncpy(buffer, str, sizeof(buffer));
   for (ptr = buffer; *ptr != 0; ptr++)
@@ -104,10 +133,7 @@ static int fc_config_set_instance(fc_directory_conf_t *dir, const char *str) {
   for (ptr = buffer; *ptr == '_'; ptr++)
     /* do nothing */;
 
-  if (*ptr == 0)
-    return -1;
-
-  copy = strdup(ptr);
+  char *copy = strdup(ptr);
   if (copy == NULL)
     return -1;
 
@@ -130,15 +156,13 @@ static int fc_config_add_dir_instance(fc_directory_conf_t *dir,
 
 static int fc_config_add_dir_name(fc_directory_conf_t *dir,
                                   oconfig_item_t *ci) {
-  char *temp;
-
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
     WARNING("filecount plugin: The `Name' config option needs exactly one "
             "string argument.");
     return -1;
   }
 
-  temp = strdup(ci->values[0].value.string);
+  char *temp = strdup(ci->values[0].value.string);
   if (temp == NULL) {
     ERROR("filecount plugin: strdup failed.");
     return -1;
@@ -152,9 +176,6 @@ static int fc_config_add_dir_name(fc_directory_conf_t *dir,
 
 static int fc_config_add_dir_mtime(fc_directory_conf_t *dir,
                                    oconfig_item_t *ci) {
-  char *endptr;
-  double temp;
-
   if ((ci->values_num != 1) || ((ci->values[0].type != OCONFIG_TYPE_STRING) &&
                                 (ci->values[0].type != OCONFIG_TYPE_NUMBER))) {
     WARNING("filecount plugin: The `MTime' config option needs exactly one "
@@ -168,8 +189,8 @@ static int fc_config_add_dir_mtime(fc_directory_conf_t *dir,
   }
 
   errno = 0;
-  endptr = NULL;
-  temp = strtod(ci->values[0].value.string, &endptr);
+  char *endptr = NULL;
+  double temp = strtod(ci->values[0].value.string, &endptr);
   if ((errno != 0) || (endptr == NULL) ||
       (endptr == ci->values[0].value.string)) {
     WARNING("filecount plugin: Converting `%s' to a number failed.",
@@ -220,9 +241,6 @@ static int fc_config_add_dir_mtime(fc_directory_conf_t *dir,
 
 static int fc_config_add_dir_size(fc_directory_conf_t *dir,
                                   oconfig_item_t *ci) {
-  char *endptr;
-  double temp;
-
   if ((ci->values_num != 1) || ((ci->values[0].type != OCONFIG_TYPE_STRING) &&
                                 (ci->values[0].type != OCONFIG_TYPE_NUMBER))) {
     WARNING("filecount plugin: The `Size' config option needs exactly one "
@@ -236,8 +254,8 @@ static int fc_config_add_dir_size(fc_directory_conf_t *dir,
   }
 
   errno = 0;
-  endptr = NULL;
-  temp = strtod(ci->values[0].value.string, &endptr);
+  char *endptr = NULL;
+  double temp = strtod(ci->values[0].value.string, &endptr);
   if ((errno != 0) || (endptr == NULL) ||
       (endptr == ci->values[0].value.string)) {
     WARNING("filecount plugin: Converting `%s' to a number failed.",
@@ -303,9 +321,6 @@ static int fc_config_add_dir_option(fc_directory_conf_t *dir,
 } /* int fc_config_add_dir_option */
 
 static int fc_config_add_dir(oconfig_item_t *ci) {
-  fc_directory_conf_t *dir;
-  int status;
-
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
     WARNING("filecount plugin: `Directory' needs exactly one string "
             "argument.");
@@ -313,7 +328,7 @@ static int fc_config_add_dir(oconfig_item_t *ci) {
   }
 
   /* Initialize `dir' */
-  dir = calloc(1, sizeof(*dir));
+  fc_directory_conf_t *dir = calloc(1, sizeof(*dir));
   if (dir == NULL) {
     ERROR("filecount plugin: calloc failed.");
     return -1;
@@ -322,23 +337,36 @@ static int fc_config_add_dir(oconfig_item_t *ci) {
   dir->path = strdup(ci->values[0].value.string);
   if (dir->path == NULL) {
     ERROR("filecount plugin: strdup failed.");
-    sfree(dir);
+    fc_free_dir(dir);
     return -1;
   }
 
-  fc_config_set_instance(dir, dir->path);
-
-  dir->options = FC_RECURSIVE;
+  dir->options = FC_RECURSIVE | FC_REGULAR;
 
   dir->name = NULL;
+  dir->plugin_name = strdup("filecount");
+  dir->instance = NULL;
+  dir->type_instance = NULL;
   dir->mtime = 0;
   dir->size = 0;
 
-  status = 0;
+  dir->files_size_type = strdup("bytes");
+  dir->files_num_type = strdup("files");
+
+  if (dir->plugin_name == NULL || dir->files_size_type == NULL ||
+      dir->files_num_type == NULL) {
+    ERROR("filecount plugin: strdup failed.");
+    fc_free_dir(dir);
+    return -1;
+  }
+
+  int status = 0;
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *option = ci->children + i;
 
-    if (strcasecmp("Instance", option->key) == 0)
+    if (strcasecmp("Plugin", option->key) == 0)
+      status = cf_util_get_string(option, &dir->plugin_name);
+    else if (strcasecmp("Instance", option->key) == 0)
       status = fc_config_add_dir_instance(dir, option);
     else if (strcasecmp("Name", option->key) == 0)
       status = fc_config_add_dir_name(dir, option);
@@ -350,6 +378,14 @@ static int fc_config_add_dir(oconfig_item_t *ci) {
       status = fc_config_add_dir_option(dir, option, FC_RECURSIVE);
     else if (strcasecmp("IncludeHidden", option->key) == 0)
       status = fc_config_add_dir_option(dir, option, FC_HIDDEN);
+    else if (strcasecmp("RegularOnly", option->key) == 0)
+      status = fc_config_add_dir_option(dir, option, FC_REGULAR);
+    else if (strcasecmp("FilesSizeType", option->key) == 0)
+      status = cf_util_get_string(option, &dir->files_size_type);
+    else if (strcasecmp("FilesCountType", option->key) == 0)
+      status = cf_util_get_string(option, &dir->files_num_type);
+    else if (strcasecmp("TypeInstance", option->key) == 0)
+      status = cf_util_get_string(option, &dir->type_instance);
     else {
       WARNING("filecount plugin: fc_config_add_dir: "
               "Option `%s' not allowed here.",
@@ -361,28 +397,52 @@ static int fc_config_add_dir(oconfig_item_t *ci) {
       break;
   } /* for (ci->children) */
 
-  if (status == 0) {
-    fc_directory_conf_t **temp;
+  if (status != 0) {
+    fc_free_dir(dir);
+    return -1;
+  }
 
-    temp = realloc(directories, sizeof(*directories) * (directories_num + 1));
-    if (temp == NULL) {
-      ERROR("filecount plugin: realloc failed.");
-      status = -1;
-    } else {
-      directories = temp;
-      directories[directories_num] = dir;
-      directories_num++;
+  /* Set default plugin instance */
+  if (dir->instance == NULL) {
+    fc_config_set_instance(dir, dir->path);
+    if (dir->instance == NULL || strlen(dir->instance) == 0) {
+      ERROR("filecount plugin: failed to build plugin instance name.");
+      fc_free_dir(dir);
+      return -1;
     }
   }
 
-  if (status != 0) {
-    sfree(dir->name);
+  /* Handle disabled types */
+  if (strlen(dir->instance) == 0)
     sfree(dir->instance);
-    sfree(dir->path);
-    sfree(dir);
+
+  if (strlen(dir->files_size_type) == 0)
+    sfree(dir->files_size_type);
+
+  if (strlen(dir->files_num_type) == 0)
+    sfree(dir->files_num_type);
+
+  if (dir->files_size_type == NULL && dir->files_num_type == NULL) {
+    WARNING("filecount plugin: Both `FilesSizeType' and `FilesCountType ' "
+            "are disabled for '%s'. There's no types to report.",
+            dir->path);
+    fc_free_dir(dir);
     return -1;
   }
 
+  /* Ready to add it to list */
+  fc_directory_conf_t **temp =
+      realloc(directories, sizeof(*directories) * (directories_num + 1));
+  if (temp == NULL) {
+    ERROR("filecount plugin: realloc failed.");
+    fc_free_dir(dir);
+    return -1;
+  }
+
+  directories = temp;
+  directories[directories_num] = dir;
+  directories_num++;
+
   return 0;
 } /* int fc_config_add_dir */
 
@@ -414,14 +474,13 @@ static int fc_read_dir_callback(const char *dirname, const char *filename,
   fc_directory_conf_t *dir = user_data;
   char abs_path[PATH_MAX];
   struct stat statbuf;
-  int status;
 
   if (dir == NULL)
     return -1;
 
   snprintf(abs_path, sizeof(abs_path), "%s/%s", dirname, filename);
 
-  status = lstat(abs_path, &statbuf);
+  int status = lstat(abs_path, &statbuf);
   if (status != 0) {
     ERROR("filecount plugin: stat (%s) failed.", abs_path);
     return -1;
@@ -432,7 +491,7 @@ static int fc_read_dir_callback(const char *dirname, const char *filename,
         abs_path, fc_read_dir_callback, dir,
         /* include hidden = */ (dir->options & FC_HIDDEN) ? 1 : 0);
     return status;
-  } else if (!S_ISREG(statbuf.st_mode)) {
+  } else if ((dir->options & FC_REGULAR) && !S_ISREG(statbuf.st_mode)) {
     return 0;
   }
 
@@ -442,6 +501,11 @@ static int fc_read_dir_callback(const char *dirname, const char *filename,
       return 0;
   }
 
+  if (!S_ISREG(statbuf.st_mode)) {
+    dir->files_num++;
+    return 0;
+  }
+
   if (dir->mtime != 0) {
     time_t mtime = dir->now;
 
@@ -478,15 +542,13 @@ static int fc_read_dir_callback(const char *dirname, const char *filename,
 } /* int fc_read_dir_callback */
 
 static int fc_read_dir(fc_directory_conf_t *dir) {
-  int status;
-
   dir->files_num = 0;
   dir->files_size = 0;
 
   if (dir->mtime != 0)
     dir->now = time(NULL);
 
-  status =
+  int status =
       walk_directory(dir->path, fc_read_dir_callback, dir,
                      /* include hidden */ (dir->options & FC_HIDDEN) ? 1 : 0);
   if (status != 0) {
index 2f16dbcb0f23c099b602b415c2e2008bbcc6497f..0f5cfec0427a7ef6e1f06f603208eb706017b40c 100644 (file)
@@ -56,6 +56,8 @@ using collectd::QueryValuesResponse;
 
 using google::protobuf::util::TimeUtil;
 
+typedef google::protobuf::Map<grpc::string, collectd::types::MetadataValue> grpcMetadata;
+
 /*
  * private types
  */
@@ -154,6 +156,124 @@ static grpc::Status unmarshal_ident(const collectd::types::Identifier &msg,
   return grpc::Status::OK;
 } /* unmarshal_ident() */
 
+static grpc::Status marshal_meta_data(meta_data_t *meta,
+                                      grpcMetadata *mutable_meta_data) {
+  char **meta_data_keys = nullptr;
+  int meta_data_keys_len = meta_data_toc(meta, &meta_data_keys);
+  if (meta_data_keys_len < 0) {
+    return grpc::Status(grpc::StatusCode::INTERNAL,
+                        grpc::string("error getting metadata keys"));
+  }
+
+  for (int i = 0; i < meta_data_keys_len; i++) {
+    char *key = meta_data_keys[i];
+    int md_type = meta_data_type(meta, key);
+
+    collectd::types::MetadataValue md_value;
+    md_value.Clear();
+
+    switch (md_type) {
+    case MD_TYPE_STRING:
+      char *md_string;
+      if (meta_data_get_string(meta, key, &md_string) != 0 || md_string == nullptr) {
+        strarray_free(meta_data_keys, meta_data_keys_len);
+        return grpc::Status(grpc::StatusCode::INTERNAL,
+                          grpc::string("missing metadata"));
+      }
+      md_value.set_string_value(md_string);
+      free(md_string);
+      break;
+    case MD_TYPE_SIGNED_INT:
+      int64_t int64_value;
+      if (meta_data_get_signed_int(meta, key, &int64_value) != 0) {
+        strarray_free(meta_data_keys, meta_data_keys_len);
+        return grpc::Status(grpc::StatusCode::INTERNAL,
+                          grpc::string("missing metadata"));
+      }
+      md_value.set_int64_value(int64_value);
+      break;
+    case MD_TYPE_UNSIGNED_INT:
+      uint64_t uint64_value;
+      if (meta_data_get_unsigned_int(meta, key, &uint64_value) != 0) {
+        strarray_free(meta_data_keys, meta_data_keys_len);
+        return grpc::Status(grpc::StatusCode::INTERNAL,
+                          grpc::string("missing metadata"));
+      }
+      md_value.set_uint64_value(uint64_value);
+      break;
+    case MD_TYPE_DOUBLE:
+      double double_value;
+      if (meta_data_get_double(meta, key, &double_value) != 0) {
+        strarray_free(meta_data_keys, meta_data_keys_len);
+        return grpc::Status(grpc::StatusCode::INTERNAL,
+                          grpc::string("missing metadata"));
+      }
+      md_value.set_double_value(double_value);
+      break;
+    case MD_TYPE_BOOLEAN:
+      bool bool_value;
+      if (meta_data_get_boolean(meta, key, &bool_value) != 0) {
+        strarray_free(meta_data_keys, meta_data_keys_len);
+        return grpc::Status(grpc::StatusCode::INTERNAL,
+                          grpc::string("missing metadata"));
+      }
+      md_value.set_bool_value(bool_value);
+      break;
+    default:
+      strarray_free(meta_data_keys, meta_data_keys_len);
+      ERROR("grpc: invalid metadata type (%d)", md_type);
+      return grpc::Status(grpc::StatusCode::INTERNAL,
+                          grpc::string("unknown metadata type"));
+    }
+
+    (*mutable_meta_data)[grpc::string(key)] = md_value;
+
+    strarray_free(meta_data_keys, meta_data_keys_len);
+  }
+
+  return grpc::Status::OK;
+}
+
+static grpc::Status unmarshal_meta_data(const grpcMetadata &rpc_metadata,
+                                        meta_data_t **md_out) {
+  *md_out = meta_data_create();
+  if (*md_out == nullptr) {
+    return grpc::Status(grpc::StatusCode::RESOURCE_EXHAUSTED,
+                        grpc::string("failed to metadata list"));
+  }
+  for (auto kv: rpc_metadata) {
+    auto k = kv.first.c_str();
+    auto v = kv.second;
+
+    // The meta_data collection individually allocates copies of the keys and
+    // string values for each entry, so it's safe for us to pass a reference
+    // to our short-lived strings.
+
+    switch (v.value_case()) {
+    case collectd::types::MetadataValue::ValueCase::kStringValue:
+      meta_data_add_string(*md_out, k, v.string_value().c_str());
+      break;
+    case collectd::types::MetadataValue::ValueCase::kInt64Value:
+      meta_data_add_signed_int(*md_out, k, v.int64_value());
+      break;
+    case collectd::types::MetadataValue::ValueCase::kUint64Value:
+      meta_data_add_unsigned_int(*md_out, k, v.uint64_value());
+      break;
+    case collectd::types::MetadataValue::ValueCase::kDoubleValue:
+      meta_data_add_double(*md_out, k, v.double_value());
+      break;
+    case collectd::types::MetadataValue::ValueCase::kBoolValue:
+      meta_data_add_boolean(*md_out, k, v.bool_value());
+      break;
+    default:
+      meta_data_destroy(*md_out);
+      return  grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+                           grpc::string("Metadata of unknown type"));
+    }
+  }
+  return grpc::Status::OK;
+}
+
 static grpc::Status marshal_value_list(const value_list_t *vl,
                                        collectd::types::ValueList *msg) {
   auto id = msg->mutable_identifier();
@@ -170,9 +290,18 @@ static grpc::Status marshal_value_list(const value_list_t *vl,
   msg->set_allocated_time(new google::protobuf::Timestamp(t));
   msg->set_allocated_interval(new google::protobuf::Duration(d));
 
+  msg->clear_meta_data();
+  if (vl->meta != nullptr) {
+    grpc::Status status = marshal_meta_data(vl->meta, msg->mutable_meta_data());
+    if (!status.ok()) {
+      return status;
+    }
+  }
+
   for (size_t i = 0; i < vl->values_len; ++i) {
     auto v = msg->add_values();
-    switch (ds->ds[i].type) {
+    int value_type = ds->ds[i].type;
+    switch (value_type) {
     case DS_TYPE_COUNTER:
       v->set_counter(vl->values[i].counter);
       break;
@@ -186,6 +315,7 @@ static grpc::Status marshal_value_list(const value_list_t *vl,
       v->set_absolute(vl->values[i].absolute);
       break;
     default:
+      ERROR("grpc: invalid value type (%d)", value_type);
       return grpc::Status(grpc::StatusCode::INTERNAL,
                           grpc::string("unknown value type"));
     }
@@ -207,6 +337,10 @@ static grpc::Status unmarshal_value_list(const collectd::types::ValueList &msg,
   if (!status.ok())
     return status;
 
+  status = unmarshal_meta_data(msg.meta_data(), &vl->meta);
+  if (!status.ok())
+    return status;
+
   value_t *values = NULL;
   size_t values_len = 0;
 
@@ -249,7 +383,8 @@ static grpc::Status unmarshal_value_list(const collectd::types::ValueList &msg,
   if (status.ok()) {
     vl->values = values;
     vl->values_len = values_len;
-  } else if (values) {
+  } else {
+    meta_data_destroy(vl->meta);
     free(values);
   }
 
@@ -280,6 +415,7 @@ public:
       auto vl = value_lists.front();
       value_lists.pop();
       sfree(vl.values);
+      meta_data_destroy(vl.meta);
     }
 
     return status;
@@ -328,7 +464,6 @@ private:
 
       if (!ident_matches(&vl, match))
         continue;
-
       if (uc_iterator_get_time(iter, &vl.time) < 0) {
         status =
             grpc::Status(grpc::StatusCode::INTERNAL,
@@ -346,6 +481,10 @@ private:
                               grpc::string("failed to retrieve values"));
         break;
       }
+      if (uc_iterator_get_meta(iter, &vl.meta) < 0) {
+        status = grpc::Status(grpc::StatusCode::INTERNAL,
+                              grpc::string("failed to retrieve value metadata"));
+      }
 
       value_lists->push(vl);
     } // while (uc_iterator_next(iter, &name) == 0)
diff --git a/src/intel_pmu.c b/src/intel_pmu.c
new file mode 100644 (file)
index 0000000..fd2bd6f
--- /dev/null
@@ -0,0 +1,598 @@
+/**
+ * collectd - src/intel_pmu.c
+ *
+ * Copyright(c) 2017 Intel Corporation. All rights reserved.
+ *
+ * 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:
+ *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+
+#include <jevents.h>
+#include <jsession.h>
+
+#define PMU_PLUGIN "intel_pmu"
+
+#define HW_CACHE_READ_ACCESS                                                   \
+  (((PERF_COUNT_HW_CACHE_OP_READ) << 8) |                                      \
+   ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
+
+#define HW_CACHE_WRITE_ACCESS                                                  \
+  (((PERF_COUNT_HW_CACHE_OP_WRITE) << 8) |                                     \
+   ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
+
+#define HW_CACHE_PREFETCH_ACCESS                                               \
+  (((PERF_COUNT_HW_CACHE_OP_PREFETCH) << 8) |                                  \
+   ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
+
+#define HW_CACHE_READ_MISS                                                     \
+  (((PERF_COUNT_HW_CACHE_OP_READ) << 8) |                                      \
+   ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
+
+#define HW_CACHE_WRITE_MISS                                                    \
+  (((PERF_COUNT_HW_CACHE_OP_WRITE) << 8) |                                     \
+   ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
+
+#define HW_CACHE_PREFETCH_MISS                                                 \
+  (((PERF_COUNT_HW_CACHE_OP_PREFETCH) << 8) |                                  \
+   ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
+
+struct event_info {
+  char *name;
+  uint64_t config;
+};
+typedef struct event_info event_info_t;
+
+struct intel_pmu_ctx_s {
+  _Bool hw_cache_events;
+  _Bool kernel_pmu_events;
+  _Bool sw_events;
+  char event_list_fn[PATH_MAX];
+  char **hw_events;
+  size_t hw_events_count;
+  struct eventlist *event_list;
+};
+typedef struct intel_pmu_ctx_s intel_pmu_ctx_t;
+
+event_info_t g_kernel_pmu_events[] = {
+    {.name = "cpu-cycles", .config = PERF_COUNT_HW_CPU_CYCLES},
+    {.name = "instructions", .config = PERF_COUNT_HW_INSTRUCTIONS},
+    {.name = "cache-references", .config = PERF_COUNT_HW_CACHE_REFERENCES},
+    {.name = "cache-misses", .config = PERF_COUNT_HW_CACHE_MISSES},
+    {.name = "branches", .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS},
+    {.name = "branch-misses", .config = PERF_COUNT_HW_BRANCH_MISSES},
+    {.name = "bus-cycles", .config = PERF_COUNT_HW_BUS_CYCLES},
+};
+
+event_info_t g_hw_cache_events[] = {
+
+    {.name = "L1-dcache-loads",
+     .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_READ_ACCESS)},
+    {.name = "L1-dcache-load-misses",
+     .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_READ_MISS)},
+    {.name = "L1-dcache-stores",
+     .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_WRITE_ACCESS)},
+    {.name = "L1-dcache-store-misses",
+     .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_WRITE_MISS)},
+    {.name = "L1-dcache-prefetches",
+     .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_PREFETCH_ACCESS)},
+    {.name = "L1-dcache-prefetch-misses",
+     .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_PREFETCH_MISS)},
+
+    {.name = "L1-icache-loads",
+     .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_READ_ACCESS)},
+    {.name = "L1-icache-load-misses",
+     .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_READ_MISS)},
+    {.name = "L1-icache-prefetches",
+     .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_PREFETCH_ACCESS)},
+    {.name = "L1-icache-prefetch-misses",
+     .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_PREFETCH_MISS)},
+
+    {.name = "LLC-loads",
+     .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_READ_ACCESS)},
+    {.name = "LLC-load-misses",
+     .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_READ_MISS)},
+    {.name = "LLC-stores",
+     .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_WRITE_ACCESS)},
+    {.name = "LLC-store-misses",
+     .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_WRITE_MISS)},
+    {.name = "LLC-prefetches",
+     .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_PREFETCH_ACCESS)},
+    {.name = "LLC-prefetch-misses",
+     .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_PREFETCH_MISS)},
+
+    {.name = "dTLB-loads",
+     .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_READ_ACCESS)},
+    {.name = "dTLB-load-misses",
+     .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_READ_MISS)},
+    {.name = "dTLB-stores",
+     .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_WRITE_ACCESS)},
+    {.name = "dTLB-store-misses",
+     .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_WRITE_MISS)},
+    {.name = "dTLB-prefetches",
+     .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_PREFETCH_ACCESS)},
+    {.name = "dTLB-prefetch-misses",
+     .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_PREFETCH_MISS)},
+
+    {.name = "iTLB-loads",
+     .config = (PERF_COUNT_HW_CACHE_ITLB | HW_CACHE_READ_ACCESS)},
+    {.name = "iTLB-load-misses",
+     .config = (PERF_COUNT_HW_CACHE_ITLB | HW_CACHE_READ_MISS)},
+
+    {.name = "branch-loads",
+     .config = (PERF_COUNT_HW_CACHE_BPU | HW_CACHE_READ_ACCESS)},
+    {.name = "branch-load-misses",
+     .config = (PERF_COUNT_HW_CACHE_BPU | HW_CACHE_READ_MISS)},
+};
+
+event_info_t g_sw_events[] = {
+    {.name = "cpu-clock", .config = PERF_COUNT_SW_CPU_CLOCK},
+
+    {.name = "task-clock", .config = PERF_COUNT_SW_TASK_CLOCK},
+
+    {.name = "context-switches", .config = PERF_COUNT_SW_CONTEXT_SWITCHES},
+
+    {.name = "cpu-migrations", .config = PERF_COUNT_SW_CPU_MIGRATIONS},
+
+    {.name = "page-faults", .config = PERF_COUNT_SW_PAGE_FAULTS},
+
+    {.name = "minor-faults", .config = PERF_COUNT_SW_PAGE_FAULTS_MIN},
+
+    {.name = "major-faults", .config = PERF_COUNT_SW_PAGE_FAULTS_MAJ},
+
+    {.name = "alignment-faults", .config = PERF_COUNT_SW_ALIGNMENT_FAULTS},
+
+    {.name = "emulation-faults", .config = PERF_COUNT_SW_EMULATION_FAULTS},
+};
+
+static intel_pmu_ctx_t g_ctx;
+
+#if COLLECT_DEBUG
+static void pmu_dump_events() {
+
+  DEBUG(PMU_PLUGIN ": Events:");
+
+  struct event *e;
+
+  for (e = g_ctx.event_list->eventlist; e; e = e->next) {
+    DEBUG(PMU_PLUGIN ":   event       : %s", e->event);
+    DEBUG(PMU_PLUGIN ":     group_lead: %d", e->group_leader);
+    DEBUG(PMU_PLUGIN ":     end_group : %d", e->end_group);
+    DEBUG(PMU_PLUGIN ":     type      : %#x", e->attr.type);
+    DEBUG(PMU_PLUGIN ":     config    : %#x", (unsigned)e->attr.config);
+    DEBUG(PMU_PLUGIN ":     size      : %d", e->attr.size);
+  }
+}
+
+static void pmu_dump_config(void) {
+
+  DEBUG(PMU_PLUGIN ": Config:");
+  DEBUG(PMU_PLUGIN ":   hw_cache_events   : %d", g_ctx.hw_cache_events);
+  DEBUG(PMU_PLUGIN ":   kernel_pmu_events : %d", g_ctx.kernel_pmu_events);
+  DEBUG(PMU_PLUGIN ":   software_events   : %d", g_ctx.sw_events);
+
+  for (size_t i = 0; i < g_ctx.hw_events_count; i++) {
+    DEBUG(PMU_PLUGIN ":   hardware_events[%zu]: %s", i, g_ctx.hw_events[i]);
+  }
+}
+
+#endif /* COLLECT_DEBUG */
+
+static int pmu_config_hw_events(oconfig_item_t *ci) {
+
+  if (strcasecmp("HardwareEvents", ci->key) != 0) {
+    return -EINVAL;
+  }
+
+  g_ctx.hw_events = calloc(ci->values_num, sizeof(char *));
+  if (g_ctx.hw_events == NULL) {
+    ERROR(PMU_PLUGIN ": Failed to allocate hw events.");
+    return -ENOMEM;
+  }
+
+  for (int i = 0; i < ci->values_num; i++) {
+    if (ci->values[i].type != OCONFIG_TYPE_STRING) {
+      WARNING(PMU_PLUGIN ": The %s option requires string arguments.", ci->key);
+      continue;
+    }
+
+    g_ctx.hw_events[g_ctx.hw_events_count] = strdup(ci->values[i].value.string);
+    if (g_ctx.hw_events[g_ctx.hw_events_count] == NULL) {
+      ERROR(PMU_PLUGIN ": Failed to allocate hw events entry.");
+      return -ENOMEM;
+    }
+
+    g_ctx.hw_events_count++;
+  }
+
+  return 0;
+}
+
+static int pmu_config(oconfig_item_t *ci) {
+
+  DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
+
+  for (int i = 0; i < ci->children_num; i++) {
+    int ret = 0;
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("ReportHardwareCacheEvents", child->key) == 0) {
+      ret = cf_util_get_boolean(child, &g_ctx.hw_cache_events);
+    } else if (strcasecmp("ReportKernelPMUEvents", child->key) == 0) {
+      ret = cf_util_get_boolean(child, &g_ctx.kernel_pmu_events);
+    } else if (strcasecmp("EventList", child->key) == 0) {
+      ret = cf_util_get_string_buffer(child, g_ctx.event_list_fn,
+                                      sizeof(g_ctx.event_list_fn));
+    } else if (strcasecmp("HardwareEvents", child->key) == 0) {
+      ret = pmu_config_hw_events(child);
+    } else if (strcasecmp("ReportSoftwareEvents", child->key) == 0) {
+      ret = cf_util_get_boolean(child, &g_ctx.sw_events);
+    } else {
+      ERROR(PMU_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
+      ret = -1;
+    }
+
+    if (ret != 0) {
+      DEBUG(PMU_PLUGIN ": %s:%d ret=%d", __FUNCTION__, __LINE__, ret);
+      return ret;
+    }
+  }
+
+#if COLLECT_DEBUG
+  pmu_dump_config();
+#endif
+
+  return 0;
+}
+
+static void pmu_submit_counter(int cpu, char *event, counter_t value,
+                               meta_data_t *meta) {
+  value_list_t vl = VALUE_LIST_INIT;
+
+  vl.values = &(value_t){.counter = value};
+  vl.values_len = 1;
+
+  sstrncpy(vl.plugin, PMU_PLUGIN, sizeof(vl.plugin));
+  if (cpu == -1) {
+    snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "all");
+  } else {
+    vl.meta = meta;
+    snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%d", cpu);
+  }
+  sstrncpy(vl.type, "counter", sizeof(vl.type));
+  sstrncpy(vl.type_instance, event, sizeof(vl.type_instance));
+
+  plugin_dispatch_values(&vl);
+}
+
+meta_data_t *pmu_meta_data_create(const struct efd *efd) {
+  meta_data_t *meta = NULL;
+
+  /* create meta data only if value was scaled */
+  if (efd->val[1] == efd->val[2] || !efd->val[2]) {
+    return NULL;
+  }
+
+  meta = meta_data_create();
+  if (meta == NULL) {
+    ERROR(PMU_PLUGIN ": meta_data_create failed.");
+    return NULL;
+  }
+
+  meta_data_add_unsigned_int(meta, "intel_pmu:raw_count", efd->val[0]);
+  meta_data_add_unsigned_int(meta, "intel_pmu:time_enabled", efd->val[1]);
+  meta_data_add_unsigned_int(meta, "intel_pmu:time_running", efd->val[2]);
+
+  return meta;
+}
+
+static void pmu_dispatch_data(void) {
+
+  struct event *e;
+
+  for (e = g_ctx.event_list->eventlist; e; e = e->next) {
+    uint64_t all_value = 0;
+    int event_enabled = 0;
+    for (int i = 0; i < g_ctx.event_list->num_cpus; i++) {
+
+      if (e->efd[i].fd < 0)
+        continue;
+
+      event_enabled++;
+
+      /* If there are more events than counters, the kernel uses time
+       * multiplexing. With multiplexing, at the end of the run,
+       * the counter is scaled basing on total time enabled vs time running.
+       * final_count = raw_count * time_enabled/time_running
+       */
+      uint64_t value = event_scaled_value(e, i);
+      all_value += value;
+
+      /* get meta data with information about scaling */
+      meta_data_t *meta = pmu_meta_data_create(&e->efd[i]);
+
+      /* dispatch per CPU value */
+      pmu_submit_counter(i, e->event, value, meta);
+
+      meta_data_destroy(meta);
+    }
+
+    if (event_enabled > 0) {
+      DEBUG(PMU_PLUGIN ": %-20s %'10lu", e->event, all_value);
+      /* dispatch all CPU value */
+      pmu_submit_counter(-1, e->event, all_value, NULL);
+    }
+  }
+}
+
+static int pmu_read(__attribute__((unused)) user_data_t *ud) {
+  int ret;
+
+  DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
+
+  ret = read_all_events(g_ctx.event_list);
+  if (ret != 0) {
+    ERROR(PMU_PLUGIN ": Failed to read values of all events.");
+    return ret;
+  }
+
+  pmu_dispatch_data();
+
+  return 0;
+}
+
+static int pmu_add_events(struct eventlist *el, uint32_t type,
+                          event_info_t *events, size_t count) {
+
+  for (size_t i = 0; i < count; i++) {
+    /* Allocate memory for event struct that contains array of efd structs
+       for all cores */
+    struct event *e =
+        calloc(sizeof(struct event) + sizeof(struct efd) * el->num_cpus, 1);
+    if (e == NULL) {
+      ERROR(PMU_PLUGIN ": Failed to allocate event structure");
+      return -ENOMEM;
+    }
+
+    e->attr.type = type;
+    e->attr.config = events[i].config;
+    e->attr.size = PERF_ATTR_SIZE_VER0;
+    if (!el->eventlist)
+      el->eventlist = e;
+    if (el->eventlist_last)
+      el->eventlist_last->next = e;
+    el->eventlist_last = e;
+    e->event = strdup(events[i].name);
+  }
+
+  return 0;
+}
+
+static int pmu_add_hw_events(struct eventlist *el, char **e, size_t count) {
+
+  for (size_t i = 0; i < count; i++) {
+
+    size_t group_events_count = 0;
+
+    char *events = strdup(e[i]);
+    if (!events)
+      return -1;
+
+    char *s, *tmp;
+    for (s = strtok_r(events, ",", &tmp); s; s = strtok_r(NULL, ",", &tmp)) {
+
+      /* Multiple events parsed in one entry */
+      if (group_events_count == 1) {
+        /* Mark previously added event as group leader */
+        el->eventlist_last->group_leader = 1;
+      }
+
+      /* Allocate memory for event struct that contains array of efd structs
+         for all cores */
+      struct event *e =
+          calloc(sizeof(struct event) + sizeof(struct efd) * el->num_cpus, 1);
+      if (e == NULL) {
+        free(events);
+        return -ENOMEM;
+      }
+
+      if (resolve_event(s, &e->attr) == 0) {
+        e->next = NULL;
+        if (!el->eventlist)
+          el->eventlist = e;
+        if (el->eventlist_last)
+          el->eventlist_last->next = e;
+        el->eventlist_last = e;
+        e->event = strdup(s);
+      } else {
+        DEBUG(PMU_PLUGIN ": Cannot resolve %s", s);
+        sfree(e);
+      }
+
+      group_events_count++;
+    }
+
+    /* Multiple events parsed in one entry */
+    if (group_events_count > 1) {
+      /* Mark last added event as group end */
+      el->eventlist_last->end_group = 1;
+    }
+
+    free(events);
+  }
+
+  return 0;
+}
+
+static void pmu_free_events(struct eventlist *el) {
+
+  if (el == NULL)
+    return;
+
+  struct event *e = el->eventlist;
+
+  while (e) {
+    struct event *next = e->next;
+    sfree(e);
+    e = next;
+  }
+
+  el->eventlist = NULL;
+}
+
+static int pmu_setup_events(struct eventlist *el, bool measure_all,
+                            int measure_pid) {
+  struct event *e, *leader = NULL;
+  int ret = -1;
+
+  for (e = el->eventlist; e; e = e->next) {
+
+    for (int i = 0; i < el->num_cpus; i++) {
+      if (setup_event(e, i, leader, measure_all, measure_pid) < 0) {
+        WARNING(PMU_PLUGIN ": perf event '%s' is not available (cpu=%d).",
+                e->event, i);
+      } else {
+        /* success if at least one event was set */
+        ret = 0;
+      }
+    }
+
+    if (e->group_leader)
+      leader = e;
+    if (e->end_group)
+      leader = NULL;
+  }
+
+  return ret;
+}
+
+static int pmu_init(void) {
+  int ret;
+
+  DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
+
+  g_ctx.event_list = alloc_eventlist();
+  if (g_ctx.event_list == NULL) {
+    ERROR(PMU_PLUGIN ": Failed to allocate event list.");
+    return -ENOMEM;
+  }
+
+  if (g_ctx.hw_cache_events) {
+    ret =
+        pmu_add_events(g_ctx.event_list, PERF_TYPE_HW_CACHE, g_hw_cache_events,
+                       STATIC_ARRAY_SIZE(g_hw_cache_events));
+    if (ret != 0) {
+      ERROR(PMU_PLUGIN ": Failed to add hw cache events.");
+      goto init_error;
+    }
+  }
+
+  if (g_ctx.kernel_pmu_events) {
+    ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_HARDWARE,
+                         g_kernel_pmu_events,
+                         STATIC_ARRAY_SIZE(g_kernel_pmu_events));
+    if (ret != 0) {
+      ERROR(PMU_PLUGIN ": Failed to add kernel PMU events.");
+      goto init_error;
+    }
+  }
+
+  /* parse events names if config option is present and is not empty */
+  if (g_ctx.hw_events_count) {
+
+    ret = read_events(g_ctx.event_list_fn);
+    if (ret != 0) {
+      ERROR(PMU_PLUGIN ": Failed to read event list file '%s'.",
+            g_ctx.event_list_fn);
+      return ret;
+    }
+
+    ret = pmu_add_hw_events(g_ctx.event_list, g_ctx.hw_events,
+                            g_ctx.hw_events_count);
+    if (ret != 0) {
+      ERROR(PMU_PLUGIN ": Failed to add hardware events.");
+      goto init_error;
+    }
+  }
+
+  if (g_ctx.sw_events) {
+    ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_SOFTWARE, g_sw_events,
+                         STATIC_ARRAY_SIZE(g_sw_events));
+    if (ret != 0) {
+      ERROR(PMU_PLUGIN ": Failed to add software events.");
+      goto init_error;
+    }
+  }
+
+#if COLLECT_DEBUG
+  pmu_dump_events();
+#endif
+
+  if (g_ctx.event_list->eventlist != NULL) {
+    /* measure all processes */
+    ret = pmu_setup_events(g_ctx.event_list, true, -1);
+    if (ret != 0) {
+      ERROR(PMU_PLUGIN ": Failed to setup perf events for the event list.");
+      goto init_error;
+    }
+  } else {
+    WARNING(PMU_PLUGIN
+            ": Events list is empty. No events were setup for monitoring.");
+  }
+
+  return 0;
+
+init_error:
+
+  pmu_free_events(g_ctx.event_list);
+  sfree(g_ctx.event_list);
+  for (size_t i = 0; i < g_ctx.hw_events_count; i++) {
+    sfree(g_ctx.hw_events[i]);
+  }
+  sfree(g_ctx.hw_events);
+  g_ctx.hw_events_count = 0;
+
+  return ret;
+}
+
+static int pmu_shutdown(void) {
+
+  DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
+
+  pmu_free_events(g_ctx.event_list);
+  sfree(g_ctx.event_list);
+  for (size_t i = 0; i < g_ctx.hw_events_count; i++) {
+    sfree(g_ctx.hw_events[i]);
+  }
+  sfree(g_ctx.hw_events);
+  g_ctx.hw_events_count = 0;
+
+  return 0;
+}
+
+void module_register(void) {
+  plugin_register_init(PMU_PLUGIN, pmu_init);
+  plugin_register_complex_config(PMU_PLUGIN, pmu_config);
+  plugin_register_complex_read(NULL, PMU_PLUGIN, pmu_read, 0, NULL);
+  plugin_register_shutdown(PMU_PLUGIN, pmu_shutdown);
+}
index c8c80429d0813aa3ef7b2726a8d6d11373ca0d11..f28ac52cbd157d64b44592a2185a959a3150dd6f 100644 (file)
@@ -21,6 +21,7 @@
  *   Florian octo Forster <octo at collectd.org>
  *   Peter Holik <peter at holik.at>
  *   Bruno Prémont <bonbons at linux-vserver.org>
+ *   Pavel Rochnyak <pavel2000 ngs.ru>
  **/
 
 #include "collectd.h"
 #include "plugin.h"
 #include "utils_ignorelist.h"
 
+#include <OpenIPMI/ipmi_auth.h>
 #include <OpenIPMI/ipmi_conn.h>
 #include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_lan.h>
 #include <OpenIPMI/ipmi_posix.h>
 #include <OpenIPMI/ipmi_smi.h>
 #include <OpenIPMI/ipmiif.h>
 
+#define ERR_BUF_SIZE 1024
+
 /*
  * Private data types
  */
 struct c_ipmi_sensor_list_s;
 typedef struct c_ipmi_sensor_list_s c_ipmi_sensor_list_t;
 
+struct c_ipmi_instance_s {
+  char *name;
+  ignorelist_t *ignorelist;
+  _Bool notify_add;
+  _Bool notify_remove;
+  _Bool notify_notpresent;
+  _Bool notify_conn;
+  _Bool sel_enabled;
+  _Bool sel_clear_event;
+
+  char *host;
+  char *connaddr;
+  char *username;
+  char *password;
+  unsigned int authtype;
+
+  _Bool connected;
+  ipmi_con_t *connection;
+  pthread_mutex_t sensor_list_lock;
+  c_ipmi_sensor_list_t *sensor_list;
+
+  _Bool active;
+  pthread_t thread_id;
+  int init_in_progress;
+
+  struct c_ipmi_instance_s *next;
+};
+typedef struct c_ipmi_instance_s c_ipmi_instance_t;
+
 struct c_ipmi_sensor_list_s {
   ipmi_sensor_id_t sensor_id;
   char sensor_name[DATA_MAX_NAME_LEN];
   char sensor_type[DATA_MAX_NAME_LEN];
+  char type_instance[DATA_MAX_NAME_LEN];
   int sensor_not_present;
   c_ipmi_sensor_list_t *next;
+  c_ipmi_instance_t *instance;
+  unsigned int use;
 };
 
+struct c_ipmi_db_type_map_s {
+  enum ipmi_unit_type_e type;
+  const char *type_name;
+};
+typedef struct c_ipmi_db_type_map_s c_ipmi_db_type_map_t;
+
 /*
  * Module global variables
  */
-static pthread_mutex_t sensor_list_lock = PTHREAD_MUTEX_INITIALIZER;
-static c_ipmi_sensor_list_t *sensor_list = NULL;
-
-static int c_ipmi_init_in_progress = 0;
-static int c_ipmi_active = 0;
-static pthread_t thread_id = (pthread_t)0;
-
-static const char *config_keys[] = {"Sensor", "IgnoreSelected",
-                                    "NotifySensorAdd", "NotifySensorRemove",
-                                    "NotifySensorNotPresent"};
-static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
-
-static ignorelist_t *ignorelist = NULL;
-
-static int c_ipmi_nofiy_add = 0;
-static int c_ipmi_nofiy_remove = 0;
-static int c_ipmi_nofiy_notpresent = 0;
+static os_handler_t *os_handler = NULL;
+static c_ipmi_instance_t *instances = NULL;
 
 /*
  * Misc private functions
  */
-static void c_ipmi_error(const char *func, int status) {
-  char errbuf[4096] = {0};
+static void c_ipmi_error(c_ipmi_instance_t *st, const char *func, int status) {
+  char errbuf[ERR_BUF_SIZE] = {0};
 
-  if (IPMI_IS_OS_ERR(status)) {
-    sstrerror(IPMI_GET_OS_ERR(status), errbuf, sizeof(errbuf));
-  } else if (IPMI_IS_IPMI_ERR(status)) {
-    ipmi_get_error_string(IPMI_GET_IPMI_ERR(status), errbuf, sizeof(errbuf));
+  if (IPMI_IS_OS_ERR(status) || IPMI_IS_RMCPP_ERR(status) ||
+      IPMI_IS_IPMI_ERR(status)) {
+    ipmi_get_error_string(status, errbuf, sizeof(errbuf));
   }
 
   if (errbuf[0] == 0) {
@@ -87,40 +114,90 @@ static void c_ipmi_error(const char *func, int status) {
   }
   errbuf[sizeof(errbuf) - 1] = 0;
 
-  ERROR("ipmi plugin: %s failed: %s", func, errbuf);
+  ERROR("ipmi plugin: %s failed for `%s`: %s", func, st->name, errbuf);
 } /* void c_ipmi_error */
 
+static void c_ipmi_log(os_handler_t *handler, const char *format,
+                       enum ipmi_log_type_e log_type, va_list ap) {
+  char msg[ERR_BUF_SIZE];
+
+  vsnprintf(msg, sizeof(msg), format, ap);
+
+  switch (log_type) {
+  case IPMI_LOG_INFO:
+    INFO("ipmi plugin: %s", msg);
+    break;
+  case IPMI_LOG_WARNING:
+    NOTICE("ipmi plugin: %s", msg);
+    break;
+  case IPMI_LOG_SEVERE:
+    WARNING("ipmi plugin: %s", msg);
+    break;
+  case IPMI_LOG_FATAL:
+    ERROR("ipmi plugin: %s", msg);
+    break;
+  case IPMI_LOG_ERR_INFO:
+    ERROR("ipmi plugin: %s", msg);
+    break;
+#if COLLECT_DEBUG
+  case IPMI_LOG_DEBUG_START:
+  case IPMI_LOG_DEBUG:
+    DEBUG("ipmi plugin: %s", msg);
+    break;
+  case IPMI_LOG_DEBUG_CONT:
+  case IPMI_LOG_DEBUG_END:
+    DEBUG("%s", msg);
+    break;
+#else
+  case IPMI_LOG_DEBUG_START:
+  case IPMI_LOG_DEBUG:
+  case IPMI_LOG_DEBUG_CONT:
+  case IPMI_LOG_DEBUG_END:
+    break;
+#endif
+  }
+} /* void c_ipmi_log */
+
+static notification_t c_ipmi_notification_init(c_ipmi_instance_t const *st,
+                                               int severity) {
+  notification_t n = {severity, cdtime(), "", "", "ipmi", "", "", "", NULL};
+
+  sstrncpy(n.host, (st->host != NULL) ? st->host : hostname_g, sizeof(n.host));
+  return n;
+} /* notification_t c_ipmi_notification_init */
+
 /*
  * Sensor handlers
  */
 /* Prototype for sensor_list_remove, so sensor_read_handler can call it. */
-static int sensor_list_remove(ipmi_sensor_t *sensor);
+static int sensor_list_remove(c_ipmi_instance_t *st, ipmi_sensor_t *sensor);
 
 static void sensor_read_handler(ipmi_sensor_t *sensor, int err,
                                 enum ipmi_value_present_e value_present,
                                 unsigned int __attribute__((unused)) raw_value,
-                                double value,
-                                ipmi_states_t __attribute__((unused)) * states,
+                                double value, ipmi_states_t *states,
                                 void *user_data) {
   value_list_t vl = VALUE_LIST_INIT;
 
-  c_ipmi_sensor_list_t *list_item = (c_ipmi_sensor_list_t *)user_data;
+  c_ipmi_sensor_list_t *list_item = user_data;
+  c_ipmi_instance_t *st = list_item->instance;
+
+  list_item->use--;
 
   if (err != 0) {
-    if ((err & 0xff) == IPMI_NOT_PRESENT_CC) {
+    if (IPMI_IS_IPMI_ERR(err) &&
+        IPMI_GET_IPMI_ERR(err) == IPMI_NOT_PRESENT_CC) {
       if (list_item->sensor_not_present == 0) {
         list_item->sensor_not_present = 1;
 
-        INFO("ipmi plugin: sensor_read_handler: sensor %s "
+        INFO("ipmi plugin: sensor_read_handler: sensor `%s` of `%s` "
              "not present.",
-             list_item->sensor_name);
+             list_item->sensor_name, st->name);
 
-        if (c_ipmi_nofiy_notpresent) {
-          notification_t n = {
-              NOTIF_WARNING, cdtime(), "", "", "ipmi", "", "", "", NULL};
+        if (st->notify_notpresent) {
+          notification_t n = c_ipmi_notification_init(st, NOTIF_WARNING);
 
-          sstrncpy(n.host, hostname_g, sizeof(n.host));
-          sstrncpy(n.type_instance, list_item->sensor_name,
+          sstrncpy(n.type_instance, list_item->type_instance,
                    sizeof(n.type_instance));
           sstrncpy(n.type, list_item->sensor_type, sizeof(n.type));
           snprintf(n.message, sizeof(n.message), "sensor %s not present",
@@ -132,44 +209,48 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err,
     } else if (IPMI_IS_IPMI_ERR(err) &&
                IPMI_GET_IPMI_ERR(err) ==
                    IPMI_NOT_SUPPORTED_IN_PRESENT_STATE_CC) {
-      INFO("ipmi plugin: sensor_read_handler: Sensor %s not ready",
-           list_item->sensor_name);
+      INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` not ready.",
+           list_item->sensor_name, st->name);
+    } else if (IPMI_IS_IPMI_ERR(err) &&
+               IPMI_GET_IPMI_ERR(err) == IPMI_TIMEOUT_CC) {
+      INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` timed out.",
+           list_item->sensor_name, st->name);
     } else {
+      char errbuf[ERR_BUF_SIZE] = {0};
+      ipmi_get_error_string(err, errbuf, sizeof(errbuf) - 1);
+
       if (IPMI_IS_IPMI_ERR(err))
-        INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, "
-             "because it failed with IPMI error %#x.",
-             list_item->sensor_name, IPMI_GET_IPMI_ERR(err));
+        INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed: "
+             "%s.",
+             list_item->sensor_name, st->name, errbuf);
       else if (IPMI_IS_OS_ERR(err))
-        INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, "
-             "because it failed with OS error %#x.",
-             list_item->sensor_name, IPMI_GET_OS_ERR(err));
+        INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed: "
+             "%s (%#x).",
+             list_item->sensor_name, st->name, errbuf, IPMI_GET_OS_ERR(err));
       else if (IPMI_IS_RMCPP_ERR(err))
-        INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, "
-             "because it failed with RMCPP error %#x.",
-             list_item->sensor_name, IPMI_GET_RMCPP_ERR(err));
+        INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed: "
+             "%s.",
+             list_item->sensor_name, st->name, errbuf);
       else if (IPMI_IS_SOL_ERR(err))
-        INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, "
-             "because it failed with RMCPP error %#x.",
-             list_item->sensor_name, IPMI_GET_SOL_ERR(err));
+        INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed: "
+             "%s (%#x).",
+             list_item->sensor_name, st->name, errbuf, IPMI_GET_SOL_ERR(err));
       else
-        INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, "
-             "because it failed with error %#x. of class %#x",
-             list_item->sensor_name, err & 0xff, err & 0xffffff00);
-      sensor_list_remove(sensor);
+        INFO("ipmi plugin: sensor_read_handler: Sensor `%s` of `%s` failed "
+             "with error %#x. of class %#x",
+             list_item->sensor_name, st->name, err & 0xff, err & 0xffffff00);
     }
     return;
   } else if (list_item->sensor_not_present == 1) {
     list_item->sensor_not_present = 0;
 
-    INFO("ipmi plugin: sensor_read_handler: sensor %s present.",
-         list_item->sensor_name);
+    INFO("ipmi plugin: sensor_read_handler: sensor `%s` of `%s` present.",
+         list_item->sensor_name, st->name);
 
-    if (c_ipmi_nofiy_notpresent) {
-      notification_t n = {NOTIF_OKAY, cdtime(), "", "",  "ipmi",
-                          "",         "",       "", NULL};
+    if (st->notify_notpresent) {
+      notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY);
 
-      sstrncpy(n.host, hostname_g, sizeof(n.host));
-      sstrncpy(n.type_instance, list_item->sensor_name,
+      sstrncpy(n.type_instance, list_item->type_instance,
                sizeof(n.type_instance));
       sstrncpy(n.type, list_item->sensor_type, sizeof(n.type));
       snprintf(n.message, sizeof(n.message), "sensor %s present",
@@ -180,84 +261,167 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err,
   }
 
   if (value_present != IPMI_BOTH_VALUES_PRESENT) {
-    INFO("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+    INFO("ipmi plugin: sensor_read_handler: Removing sensor `%s` of `%s`, "
          "because it provides %s. If you need this sensor, "
          "please file a bug report.",
-         list_item->sensor_name,
+         list_item->sensor_name, st->name,
          (value_present == IPMI_RAW_VALUE_PRESENT) ? "only the raw value"
                                                    : "no value");
-    sensor_list_remove(sensor);
+    sensor_list_remove(st, sensor);
+    return;
+  }
+
+  if (!ipmi_is_sensor_scanning_enabled(states)) {
+    DEBUG("ipmi plugin: sensor_read_handler: Skipping sensor `%s` of `%s`, "
+          "it is in 'scanning disabled' state.",
+          list_item->sensor_name, st->name);
+    return;
+  }
+
+  if (ipmi_is_initial_update_in_progress(states)) {
+    DEBUG("ipmi plugin: sensor_read_handler: Skipping sensor `%s` of `%s`, "
+          "it is in 'initial update in progress' state.",
+          list_item->sensor_name, st->name);
     return;
   }
 
   vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
+  if (st->host != NULL)
+    sstrncpy(vl.host, st->host, sizeof(vl.host));
   sstrncpy(vl.plugin, "ipmi", sizeof(vl.plugin));
   sstrncpy(vl.type, list_item->sensor_type, sizeof(vl.type));
-  sstrncpy(vl.type_instance, list_item->sensor_name, sizeof(vl.type_instance));
+  sstrncpy(vl.type_instance, list_item->type_instance,
+           sizeof(vl.type_instance));
 
   plugin_dispatch_values(&vl);
 } /* void sensor_read_handler */
 
-static int sensor_list_add(ipmi_sensor_t *sensor) {
+static void sensor_get_name(ipmi_sensor_t *sensor, char *buffer, int buf_len) {
+  char temp[DATA_MAX_NAME_LEN] = {0};
+  ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
+  const char *entity_id_string = ipmi_entity_get_entity_id_string(ent);
+  char sensor_name[DATA_MAX_NAME_LEN] = "";
+  char *sensor_name_ptr;
+
+  if ((buffer == NULL) || (buf_len == 0))
+    return;
+
+  ipmi_sensor_get_name(sensor, temp, sizeof(temp));
+  temp[sizeof(temp) - 1] = 0;
+
+  if (entity_id_string != NULL && strlen(temp))
+    snprintf(sensor_name, sizeof(sensor_name), "%s %s", temp, entity_id_string);
+  else if (entity_id_string != NULL)
+    sstrncpy(sensor_name, entity_id_string, sizeof(sensor_name));
+  else
+    sstrncpy(sensor_name, temp, sizeof(sensor_name));
+
+  if (strlen(temp)) {
+    sstrncpy(temp, sensor_name, sizeof(temp));
+    sensor_name_ptr = strstr(temp, ").");
+    if (sensor_name_ptr != NULL) {
+      /* If name is something like "foo (123).bar",
+       * change that to "bar (123)".
+       * Both, sensor_name_ptr and sensor_id_ptr point to memory within the
+       * `temp' array, which holds a copy of the current `sensor_name'. */
+      char *sensor_id_ptr;
+
+      /* `sensor_name_ptr' points to ").bar". */
+      sensor_name_ptr[1] = 0;
+      /* `temp' holds "foo (123)\0bar\0". */
+      sensor_name_ptr += 2;
+      /* `sensor_name_ptr' now points to "bar". */
+
+      sensor_id_ptr = strstr(temp, "(");
+      if (sensor_id_ptr != NULL) {
+        /* `sensor_id_ptr' now points to "(123)". */
+        snprintf(sensor_name, sizeof(sensor_name), "%s %s", sensor_name_ptr,
+                 sensor_id_ptr);
+      }
+      /* else: don't touch sensor_name. */
+    }
+  }
+  sstrncpy(buffer, sensor_name, buf_len);
+}
+
+static const char *sensor_unit_to_type(ipmi_sensor_t *sensor) {
+  static const c_ipmi_db_type_map_t ipmi_db_type_map[] = {
+      {IPMI_UNIT_TYPE_WATTS, "power"}, {IPMI_UNIT_TYPE_CFM, "flow"}};
+
+  /* check the modifier and rate of the sensor value */
+  if ((ipmi_sensor_get_modifier_unit_use(sensor) != IPMI_MODIFIER_UNIT_NONE) ||
+      (ipmi_sensor_get_rate_unit(sensor) != IPMI_RATE_UNIT_NONE))
+    return NULL;
+
+  /* find the db type by using sensor base unit type */
+  enum ipmi_unit_type_e ipmi_type = ipmi_sensor_get_base_unit(sensor);
+  for (int i = 0; i < STATIC_ARRAY_SIZE(ipmi_db_type_map); i++)
+    if (ipmi_db_type_map[i].type == ipmi_type)
+      return ipmi_db_type_map[i].type_name;
+
+  return NULL;
+} /* const char* sensor_unit_to_type */
+
+static int sensor_list_add(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) {
   ipmi_sensor_id_t sensor_id;
   c_ipmi_sensor_list_t *list_item;
   c_ipmi_sensor_list_t *list_prev;
 
   char buffer[DATA_MAX_NAME_LEN] = {0};
-  const char *entity_id_string;
-  char sensor_name[DATA_MAX_NAME_LEN];
-  char *sensor_name_ptr;
+  char *sensor_name_ptr = buffer;
   int sensor_type;
   const char *type;
-  ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
 
   sensor_id = ipmi_sensor_convert_to_id(sensor);
+  sensor_get_name(sensor, buffer, sizeof(buffer));
+
+  DEBUG("ipmi plugin: sensor_list_add: Found sensor `%s` of `%s`,"
+        " Type: %#x"
+        " Event reading type: %#x"
+        " Direction: %#x"
+        " Event support: %#x",
+        sensor_name_ptr, st->name, ipmi_sensor_get_sensor_type(sensor),
+        ipmi_sensor_get_event_reading_type(sensor),
+        ipmi_sensor_get_sensor_direction(sensor),
+        ipmi_sensor_get_event_support(sensor));
+
+  /* Both `ignorelist' and `sensor_name_ptr' may be NULL. */
+  if (ignorelist_match(st->ignorelist, sensor_name_ptr) != 0)
+    return 0;
 
-  ipmi_sensor_get_name(sensor, buffer, sizeof(buffer));
-  buffer[sizeof(buffer) - 1] = 0;
+  /* FIXME: Use rate unit or base unit to scale the value */
 
-  entity_id_string = ipmi_entity_get_entity_id_string(ent);
+  sensor_type = ipmi_sensor_get_sensor_type(sensor);
 
-  if (entity_id_string == NULL)
-    sstrncpy(sensor_name, buffer, sizeof(sensor_name));
-  else
-    snprintf(sensor_name, sizeof(sensor_name), "%s %s", buffer,
-             entity_id_string);
-
-  sstrncpy(buffer, sensor_name, sizeof(buffer));
-  sensor_name_ptr = strstr(buffer, ").");
-  if (sensor_name_ptr != NULL) {
-    /* If name is something like "foo (123).bar",
-     * change that to "bar (123)".
-     * Both, sensor_name_ptr and sensor_id_ptr point to memory within the
-     * `buffer' array, which holds a copy of the current `sensor_name'. */
-    char *sensor_id_ptr;
-
-    /* `sensor_name_ptr' points to ").bar". */
-    sensor_name_ptr[1] = 0;
-    /* `buffer' holds "foo (123)\0bar\0". */
-    sensor_name_ptr += 2;
-    /* `sensor_name_ptr' now points to "bar". */
-
-    sensor_id_ptr = strstr(buffer, "(");
-    if (sensor_id_ptr != NULL) {
-      /* `sensor_id_ptr' now points to "(123)". */
-      snprintf(sensor_name, sizeof(sensor_name), "%s %s", sensor_name_ptr,
-               sensor_id_ptr);
-    }
-    /* else: don't touch sensor_name. */
+  /*
+   * ipmitool/lib/ipmi_sdr.c sdr_sensor_has_analog_reading() has a notice
+   * about 'Threshold sensors' and 'analog readings'. Discrete sensor may
+   * have analog data, but discrete sensors support is not implemented
+   * in Collectd yet.
+   *
+   * ipmi_sensor_id_get_reading() supports only 'Threshold' sensors.
+   * See lib/sensor.c:4842, stand_ipmi_sensor_get_reading() for details.
+   */
+  if (!ipmi_sensor_get_is_readable(sensor)) {
+    INFO("ipmi plugin: sensor_list_add: Ignore sensor `%s` of `%s`, "
+         "because it isn't readable! Its type: (%#x, %s). ",
+         sensor_name_ptr, st->name, sensor_type,
+         ipmi_sensor_get_sensor_type_string(sensor));
+    return -1;
   }
-  sensor_name_ptr = sensor_name;
-
-  /* Both `ignorelist' and `plugin_instance' may be NULL. */
-  if (ignorelist_match(ignorelist, sensor_name_ptr) != 0)
-    return 0;
 
-  /* FIXME: Use rate unit or base unit to scale the value */
+  if (ipmi_sensor_get_event_reading_type(sensor) !=
+      IPMI_EVENT_READING_TYPE_THRESHOLD) {
+    INFO("ipmi plugin: sensor_list_add: Ignore sensor `%s` of `%s`, "
+         "because it is discrete (%#x)! Its type: (%#x, %s). ",
+         sensor_name_ptr, st->name, sensor_type,
+         ipmi_sensor_get_event_reading_type(sensor),
+         ipmi_sensor_get_sensor_type_string(sensor));
+    return -1;
+  }
 
-  sensor_type = ipmi_sensor_get_sensor_type(sensor);
   switch (sensor_type) {
   case IPMI_SENSOR_TYPE_TEMPERATURE:
     type = "temperature";
@@ -275,22 +439,31 @@ static int sensor_list_add(ipmi_sensor_t *sensor) {
     type = "fanspeed";
     break;
 
+  case IPMI_SENSOR_TYPE_MEMORY:
+    type = "memory";
+    break;
+
   default: {
-    const char *sensor_type_str;
+    /* try to get collectd DB type based on sensor base unit type */
+    if ((type = sensor_unit_to_type(sensor)) != NULL)
+      break;
 
-    sensor_type_str = ipmi_sensor_get_sensor_type_string(sensor);
-    INFO("ipmi plugin: sensor_list_add: Ignore sensor %s, "
-         "because I don't know how to handle its type (%#x, %s). "
-         "If you need this sensor, please file a bug report.",
-         sensor_name_ptr, sensor_type, sensor_type_str);
+    INFO("ipmi plugin: sensor_list_add: Ignore sensor `%s` of `%s`, "
+         "because I don't know how to handle its units (%#x, %#x, %#x). "
+         "Sensor type: (%#x, %s). If you need this sensor, please file "
+         "a bug report at http://collectd.org/.",
+         sensor_name_ptr, st->name, ipmi_sensor_get_base_unit(sensor),
+         ipmi_sensor_get_modifier_unit(sensor),
+         ipmi_sensor_get_rate_unit(sensor), sensor_type,
+         ipmi_sensor_get_sensor_type_string(sensor));
     return -1;
   }
   } /* switch (sensor_type) */
 
-  pthread_mutex_lock(&sensor_list_lock);
+  pthread_mutex_lock(&st->sensor_list_lock);
 
   list_prev = NULL;
-  for (list_item = sensor_list; list_item != NULL;
+  for (list_item = st->sensor_list; list_item != NULL;
        list_item = list_item->next) {
     if (ipmi_cmp_sensor_id(sensor_id, list_item->sensor_id) == 0)
       break;
@@ -298,34 +471,47 @@ static int sensor_list_add(ipmi_sensor_t *sensor) {
   } /* for (list_item) */
 
   if (list_item != NULL) {
-    pthread_mutex_unlock(&sensor_list_lock);
+    pthread_mutex_unlock(&st->sensor_list_lock);
     return 0;
   }
 
   list_item = (c_ipmi_sensor_list_t *)calloc(1, sizeof(c_ipmi_sensor_list_t));
   if (list_item == NULL) {
-    pthread_mutex_unlock(&sensor_list_lock);
+    pthread_mutex_unlock(&st->sensor_list_lock);
     return -1;
   }
 
+  list_item->instance = st;
   list_item->sensor_id = ipmi_sensor_convert_to_id(sensor);
 
   if (list_prev != NULL)
     list_prev->next = list_item;
   else
-    sensor_list = list_item;
+    st->sensor_list = list_item;
+
+  /* if sensor provides the percentage value, use "percent" collectd type
+     and add the `percent` to the type instance of the reported value */
+  if (ipmi_sensor_get_percentage(sensor)) {
+    snprintf(list_item->type_instance, sizeof(list_item->type_instance),
+             "percent-%s", sensor_name_ptr);
+    type = "percent";
+  } else {
+    /* use type instance as a name of the sensor */
+    sstrncpy(list_item->type_instance, sensor_name_ptr,
+             sizeof(list_item->type_instance));
+  }
 
   sstrncpy(list_item->sensor_name, sensor_name_ptr,
            sizeof(list_item->sensor_name));
   sstrncpy(list_item->sensor_type, type, sizeof(list_item->sensor_type));
 
-  pthread_mutex_unlock(&sensor_list_lock);
+  pthread_mutex_unlock(&st->sensor_list_lock);
 
-  if (c_ipmi_nofiy_add && (c_ipmi_init_in_progress == 0)) {
-    notification_t n = {NOTIF_OKAY, cdtime(), "", "", "ipmi", "", "", "", NULL};
+  if (st->notify_add && (st->init_in_progress == 0)) {
+    notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY);
 
-    sstrncpy(n.host, hostname_g, sizeof(n.host));
-    sstrncpy(n.type_instance, list_item->sensor_name, sizeof(n.type_instance));
+    sstrncpy(n.type_instance, list_item->type_instance,
+             sizeof(n.type_instance));
     sstrncpy(n.type, list_item->sensor_type, sizeof(n.type));
     snprintf(n.message, sizeof(n.message), "sensor %s added",
              list_item->sensor_name);
@@ -336,17 +522,17 @@ static int sensor_list_add(ipmi_sensor_t *sensor) {
   return 0;
 } /* int sensor_list_add */
 
-static int sensor_list_remove(ipmi_sensor_t *sensor) {
+static int sensor_list_remove(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) {
   ipmi_sensor_id_t sensor_id;
   c_ipmi_sensor_list_t *list_item;
   c_ipmi_sensor_list_t *list_prev;
 
   sensor_id = ipmi_sensor_convert_to_id(sensor);
 
-  pthread_mutex_lock(&sensor_list_lock);
+  pthread_mutex_lock(&st->sensor_list_lock);
 
   list_prev = NULL;
-  for (list_item = sensor_list; list_item != NULL;
+  for (list_item = st->sensor_list; list_item != NULL;
        list_item = list_item->next) {
     if (ipmi_cmp_sensor_id(sensor_id, list_item->sensor_id) == 0)
       break;
@@ -354,26 +540,25 @@ static int sensor_list_remove(ipmi_sensor_t *sensor) {
   } /* for (list_item) */
 
   if (list_item == NULL) {
-    pthread_mutex_unlock(&sensor_list_lock);
+    pthread_mutex_unlock(&st->sensor_list_lock);
     return -1;
   }
 
   if (list_prev == NULL)
-    sensor_list = list_item->next;
+    st->sensor_list = list_item->next;
   else
     list_prev->next = list_item->next;
 
   list_prev = NULL;
   list_item->next = NULL;
 
-  pthread_mutex_unlock(&sensor_list_lock);
+  pthread_mutex_unlock(&st->sensor_list_lock);
 
-  if (c_ipmi_nofiy_remove && c_ipmi_active) {
-    notification_t n = {NOTIF_WARNING, cdtime(), "", "", "ipmi", "", "", "",
-                        NULL};
+  if (st->notify_remove && st->active) {
+    notification_t n = c_ipmi_notification_init(st, NOTIF_WARNING);
 
-    sstrncpy(n.host, hostname_g, sizeof(n.host));
-    sstrncpy(n.type_instance, list_item->sensor_name, sizeof(n.type_instance));
+    sstrncpy(n.type_instance, list_item->type_instance,
+             sizeof(n.type_instance));
     sstrncpy(n.type, list_item->sensor_type, sizeof(n.type));
     snprintf(n.message, sizeof(n.message), "sensor %s removed",
              list_item->sensor_name);
@@ -385,29 +570,37 @@ static int sensor_list_remove(ipmi_sensor_t *sensor) {
   return 0;
 } /* int sensor_list_remove */
 
-static int sensor_list_read_all(void) {
-  pthread_mutex_lock(&sensor_list_lock);
+static int sensor_list_read_all(c_ipmi_instance_t *st) {
+  pthread_mutex_lock(&st->sensor_list_lock);
 
-  for (c_ipmi_sensor_list_t *list_item = sensor_list; list_item != NULL;
+  for (c_ipmi_sensor_list_t *list_item = st->sensor_list; list_item != NULL;
        list_item = list_item->next) {
+    DEBUG("ipmi plugin: try read sensor `%s` of `%s`, use: %d",
+          list_item->sensor_name, st->name, list_item->use);
+
+    /* Reading already initiated */
+    if (list_item->use)
+      continue;
+
+    list_item->use++;
     ipmi_sensor_id_get_reading(list_item->sensor_id, sensor_read_handler,
-                               /* user data = */ list_item);
+                               /* user data = */ (void *)list_item);
   } /* for (list_item) */
 
-  pthread_mutex_unlock(&sensor_list_lock);
+  pthread_mutex_unlock(&st->sensor_list_lock);
 
   return 0;
 } /* int sensor_list_read_all */
 
-static int sensor_list_remove_all(void) {
+static int sensor_list_remove_all(c_ipmi_instance_t *st) {
   c_ipmi_sensor_list_t *list_item;
 
-  pthread_mutex_lock(&sensor_list_lock);
+  pthread_mutex_lock(&st->sensor_list_lock);
 
-  list_item = sensor_list;
-  sensor_list = NULL;
+  list_item = st->sensor_list;
+  st->sensor_list = NULL;
 
-  pthread_mutex_unlock(&sensor_list_lock);
+  pthread_mutex_unlock(&st->sensor_list_lock);
 
   while (list_item != NULL) {
     c_ipmi_sensor_list_t *list_next = list_item->next;
@@ -420,204 +613,679 @@ static int sensor_list_remove_all(void) {
   return 0;
 } /* int sensor_list_remove_all */
 
+static int sensor_convert_threshold_severity(enum ipmi_thresh_e severity) {
+  switch (severity) {
+  case IPMI_LOWER_NON_CRITICAL:
+  case IPMI_UPPER_NON_CRITICAL:
+    return NOTIF_OKAY;
+  case IPMI_LOWER_CRITICAL:
+  case IPMI_UPPER_CRITICAL:
+    return NOTIF_WARNING;
+  case IPMI_LOWER_NON_RECOVERABLE:
+  case IPMI_UPPER_NON_RECOVERABLE:
+    return NOTIF_FAILURE;
+  default:
+    return NOTIF_OKAY;
+  } /* switch (severity) */
+} /* int sensor_convert_threshold_severity */
+
+static void add_event_common_data(notification_t *n, ipmi_sensor_t *sensor,
+                                  enum ipmi_event_dir_e dir,
+                                  ipmi_event_t *event) {
+  ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
+
+  plugin_notification_meta_add_string(n, "entity_name",
+                                      ipmi_entity_get_entity_id_string(ent));
+  plugin_notification_meta_add_signed_int(n, "entity_id",
+                                          ipmi_entity_get_entity_id(ent));
+  plugin_notification_meta_add_signed_int(n, "entity_instance",
+                                          ipmi_entity_get_entity_instance(ent));
+  plugin_notification_meta_add_boolean(n, "assert", dir == IPMI_ASSERTION);
+
+  if (event)
+    plugin_notification_meta_add_signed_int(n, "event_type",
+                                            ipmi_event_get_type(event));
+} /* void add_event_sensor_meta_data */
+
+static int sensor_threshold_event_handler(
+    ipmi_sensor_t *sensor, enum ipmi_event_dir_e dir,
+    enum ipmi_thresh_e threshold, enum ipmi_event_value_dir_e high_low,
+    enum ipmi_value_present_e value_present, unsigned int raw_value,
+    double value, void *cb_data, ipmi_event_t *event) {
+
+  c_ipmi_instance_t *st = cb_data;
+
+  /* From the IPMI specification Chapter 2: Events.
+   * If a callback handles the event, then all future callbacks called due to
+   * the event will receive a NULL for the event. So be ready to handle a NULL
+   * event in all your event handlers. A NULL may also be passed to an event
+   * handler if the callback was not due to an event. */
+  if (event == NULL)
+    return IPMI_EVENT_NOT_HANDLED;
+
+  notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY);
+  /* offset is a table index and it's represented as enum of strings that are
+     organized in the way - high and low for each threshold severity level */
+  unsigned int offset = (2 * threshold) + high_low;
+  unsigned int event_type = ipmi_sensor_get_event_reading_type(sensor);
+  unsigned int sensor_type = ipmi_sensor_get_sensor_type(sensor);
+  const char *event_state =
+      ipmi_get_reading_name(event_type, sensor_type, offset);
+  sensor_get_name(sensor, n.type_instance, sizeof(n.type_instance));
+  if (value_present != IPMI_NO_VALUES_PRESENT)
+    snprintf(n.message, sizeof(n.message),
+             "sensor %s received event: %s, value is %f", n.type_instance,
+             event_state, value);
+  else
+    snprintf(n.message, sizeof(n.message),
+             "sensor %s received event: %s, value not provided",
+             n.type_instance, event_state);
+
+  DEBUG("Threshold event received for sensor %s", n.type_instance);
+
+  sstrncpy(n.type, ipmi_sensor_get_sensor_type_string(sensor), sizeof(n.type));
+  n.severity = sensor_convert_threshold_severity(threshold);
+  n.time = NS_TO_CDTIME_T(ipmi_event_get_timestamp(event));
+
+  plugin_notification_meta_add_string(&n, "severity",
+                                      ipmi_get_threshold_string(threshold));
+  plugin_notification_meta_add_string(&n, "direction",
+                                      ipmi_get_value_dir_string(high_low));
+
+  switch (value_present) {
+  case IPMI_BOTH_VALUES_PRESENT:
+    plugin_notification_meta_add_double(&n, "val", value);
+  /* both values present, so fall-through to add raw value too */
+  case IPMI_RAW_VALUE_PRESENT: {
+    char buf[DATA_MAX_NAME_LEN] = {0};
+    snprintf(buf, sizeof(buf), "0x%2.2x", raw_value);
+    plugin_notification_meta_add_string(&n, "raw", buf);
+  } break;
+  default:
+    break;
+  } /* switch (value_present) */
+
+  add_event_common_data(&n, sensor, dir, event);
+
+  plugin_dispatch_notification(&n);
+  plugin_notification_meta_free(n.meta);
+
+  /* Delete handled ipmi event from the list */
+  if (st->sel_clear_event) {
+    ipmi_event_delete(event, NULL, NULL);
+    return IPMI_EVENT_HANDLED;
+  }
+
+  return IPMI_EVENT_NOT_HANDLED;
+} /* int sensor_threshold_event_handler */
+
+static int sensor_discrete_event_handler(ipmi_sensor_t *sensor,
+                                         enum ipmi_event_dir_e dir, int offset,
+                                         int severity, int prev_severity,
+                                         void *cb_data, ipmi_event_t *event) {
+
+  c_ipmi_instance_t *st = cb_data;
+
+  /* From the IPMI specification Chapter 2: Events.
+   * If a callback handles the event, then all future callbacks called due to
+   * the event will receive a NULL for the event. So be ready to handle a NULL
+   * event in all your event handlers. A NULL may also be passed to an event
+   * handler if the callback was not due to an event. */
+  if (event == NULL)
+    return IPMI_EVENT_NOT_HANDLED;
+
+  notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY);
+  unsigned int event_type = ipmi_sensor_get_event_reading_type(sensor);
+  unsigned int sensor_type = ipmi_sensor_get_sensor_type(sensor);
+  const char *event_state =
+      ipmi_get_reading_name(event_type, sensor_type, offset);
+  sensor_get_name(sensor, n.type_instance, sizeof(n.type_instance));
+  snprintf(n.message, sizeof(n.message), "sensor %s received event: %s",
+           n.type_instance, event_state);
+
+  DEBUG("Discrete event received for sensor %s", n.type_instance);
+
+  sstrncpy(n.type, ipmi_sensor_get_sensor_type_string(sensor), sizeof(n.type));
+  n.time = NS_TO_CDTIME_T(ipmi_event_get_timestamp(event));
+
+  plugin_notification_meta_add_signed_int(&n, "offset", offset);
+
+  if (severity != -1)
+    plugin_notification_meta_add_signed_int(&n, "severity", severity);
+
+  if (prev_severity != -1)
+    plugin_notification_meta_add_signed_int(&n, "prevseverity", prev_severity);
+
+  add_event_common_data(&n, sensor, dir, event);
+
+  plugin_dispatch_notification(&n);
+  plugin_notification_meta_free(n.meta);
+
+  /* Delete handled ipmi event from the list */
+  if (st->sel_clear_event) {
+    ipmi_event_delete(event, NULL, NULL);
+    return IPMI_EVENT_HANDLED;
+  }
+
+  return IPMI_EVENT_NOT_HANDLED;
+} /* int sensor_discrete_event_handler */
+
 /*
  * Entity handlers
  */
-static void entity_sensor_update_handler(
-    enum ipmi_update_e op, ipmi_entity_t __attribute__((unused)) * entity,
-    ipmi_sensor_t *sensor, void __attribute__((unused)) * user_data) {
-  /* TODO: Ignore sensors we cannot read */
+static void
+entity_sensor_update_handler(enum ipmi_update_e op,
+                             ipmi_entity_t __attribute__((unused)) * entity,
+                             ipmi_sensor_t *sensor, void *user_data) {
+  c_ipmi_instance_t *st = user_data;
 
   if ((op == IPMI_ADDED) || (op == IPMI_CHANGED)) {
     /* Will check for duplicate entries.. */
-    sensor_list_add(sensor);
+    sensor_list_add(st, sensor);
+
+    if (st->sel_enabled) {
+      int status = 0;
+      /* register threshold event if threshold sensor support events */
+      if ((ipmi_sensor_get_event_reading_type(sensor) ==
+           IPMI_EVENT_READING_TYPE_THRESHOLD) &&
+          (ipmi_sensor_get_threshold_access(sensor) !=
+           IPMI_THRESHOLD_ACCESS_SUPPORT_NONE))
+        status = ipmi_sensor_add_threshold_event_handler(
+            sensor, sensor_threshold_event_handler, st);
+      /* register discrete handler if discrete/specific sensor support events */
+      else if (ipmi_sensor_get_event_support(sensor) != IPMI_EVENT_SUPPORT_NONE)
+        status = ipmi_sensor_add_discrete_event_handler(
+            sensor, sensor_discrete_event_handler, st);
+
+      if (status) {
+        char buf[DATA_MAX_NAME_LEN] = {0};
+        sensor_get_name(sensor, buf, sizeof(buf));
+        ERROR("Unable to add sensor %s event handler, status: %d", buf, status);
+      }
+    }
   } else if (op == IPMI_DELETED) {
-    sensor_list_remove(sensor);
+    sensor_list_remove(st, sensor);
+
+    if (st->sel_enabled) {
+      if (ipmi_sensor_get_event_reading_type(sensor) ==
+          IPMI_EVENT_READING_TYPE_THRESHOLD)
+        ipmi_sensor_remove_threshold_event_handler(
+            sensor, sensor_threshold_event_handler, st);
+      else
+        ipmi_sensor_remove_discrete_event_handler(
+            sensor, sensor_discrete_event_handler, st);
+    }
   }
 } /* void entity_sensor_update_handler */
 
 /*
  * Domain handlers
  */
-static void domain_entity_update_handler(
-    enum ipmi_update_e op, ipmi_domain_t __attribute__((unused)) * domain,
-    ipmi_entity_t *entity, void __attribute__((unused)) * user_data) {
+static void
+domain_entity_update_handler(enum ipmi_update_e op,
+                             ipmi_domain_t __attribute__((unused)) * domain,
+                             ipmi_entity_t *entity, void *user_data) {
   int status;
+  c_ipmi_instance_t *st = user_data;
 
   if (op == IPMI_ADDED) {
     status = ipmi_entity_add_sensor_update_handler(
-        entity, entity_sensor_update_handler, /* user data = */ NULL);
+        entity, entity_sensor_update_handler, /* user data = */ (void *)st);
     if (status != 0) {
-      c_ipmi_error("ipmi_entity_add_sensor_update_handler", status);
+      c_ipmi_error(st, "ipmi_entity_add_sensor_update_handler", status);
     }
   } else if (op == IPMI_DELETED) {
     status = ipmi_entity_remove_sensor_update_handler(
-        entity, entity_sensor_update_handler, /* user data = */ NULL);
+        entity, entity_sensor_update_handler, /* user data = */ (void *)st);
     if (status != 0) {
-      c_ipmi_error("ipmi_entity_remove_sensor_update_handler", status);
+      c_ipmi_error(st, "ipmi_entity_remove_sensor_update_handler", status);
     }
   }
 } /* void domain_entity_update_handler */
 
+static void smi_event_handler(ipmi_con_t __attribute__((unused)) * ipmi,
+                              const ipmi_addr_t __attribute__((unused)) * addr,
+                              unsigned int __attribute__((unused)) addr_len,
+                              ipmi_event_t *event, void *cb_data) {
+  unsigned int type = ipmi_event_get_type(event);
+  ipmi_domain_t *domain = cb_data;
+
+  DEBUG("%s: Event received: type %u", __FUNCTION__, type);
+
+  if (type != 0x02)
+    /* It's not a standard IPMI event. */
+    return;
+
+  /* force domain to reread SELs */
+  ipmi_domain_reread_sels(domain, NULL, NULL);
+}
+
 static void domain_connection_change_handler(ipmi_domain_t *domain, int err,
                                              unsigned int conn_num,
                                              unsigned int port_num,
                                              int still_connected,
                                              void *user_data) {
-  int status;
 
   DEBUG("domain_connection_change_handler (domain = %p, err = %i, "
         "conn_num = %u, port_num = %u, still_connected = %i, "
-        "user_data = %p);\n",
+        "user_data = %p);",
         (void *)domain, err, conn_num, port_num, still_connected, user_data);
 
-  status = ipmi_domain_add_entity_update_handler(
-      domain, domain_entity_update_handler, /* user data = */ NULL);
-  if (status != 0) {
-    c_ipmi_error("ipmi_domain_add_entity_update_handler", status);
+  c_ipmi_instance_t *st = user_data;
+
+  if (err != 0)
+    c_ipmi_error(st, "domain_connection_change_handler", err);
+
+  if (!still_connected) {
+
+    if (st->notify_conn && st->connected && st->init_in_progress == 0) {
+      notification_t n = c_ipmi_notification_init(st, NOTIF_FAILURE);
+
+      sstrncpy(n.message, "IPMI connection lost", sizeof(n.plugin));
+
+      plugin_dispatch_notification(&n);
+    }
+
+    st->connected = 0;
+    return;
   }
-} /* void domain_connection_change_handler */
 
-static int thread_init(os_handler_t **ret_os_handler) {
-  os_handler_t *os_handler;
-  ipmi_con_t *smi_connection = NULL;
-  ipmi_domain_id_t domain_id;
-  int status;
+  if (st->notify_conn && !st->connected && st->init_in_progress == 0) {
+    notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY);
 
-  os_handler = ipmi_posix_thread_setup_os_handler(SIGIO);
-  if (os_handler == NULL) {
-    ERROR("ipmi plugin: ipmi_posix_thread_setup_os_handler failed.");
-    return -1;
+    sstrncpy(n.message, "IPMI connection restored", sizeof(n.plugin));
+
+    plugin_dispatch_notification(&n);
   }
 
-  ipmi_init(os_handler);
+  st->connected = 1;
 
-  status = ipmi_smi_setup_con(/* if_num = */ 0, os_handler,
-                              /* user data = */ NULL, &smi_connection);
+  int status = ipmi_domain_add_entity_update_handler(
+      domain, domain_entity_update_handler, /* user data = */ st);
   if (status != 0) {
-    c_ipmi_error("ipmi_smi_setup_con", status);
-    return -1;
+    c_ipmi_error(st, "ipmi_domain_add_entity_update_handler", status);
   }
 
-  ipmi_open_option_t open_option[1] = {[0] = {.option = IPMI_OPEN_OPTION_ALL,
-                                              {.ival = 1}}};
+  status = st->connection->add_event_handler(st->connection, smi_event_handler,
+                                             (void *)domain);
+
+  if (status != 0)
+    c_ipmi_error(st, "Failed to register smi event handler", status);
+} /* void domain_connection_change_handler */
+
+static int c_ipmi_thread_init(c_ipmi_instance_t *st) {
+  ipmi_domain_id_t domain_id;
+  int status;
+
+  if (st->connaddr != NULL) {
+    status = ipmi_ip_setup_con(
+        &st->connaddr, &(char *){IPMI_LAN_STD_PORT_STR}, 1, st->authtype,
+        (unsigned int)IPMI_PRIVILEGE_USER, st->username, strlen(st->username),
+        st->password, strlen(st->password), os_handler,
+        /* user data = */ NULL, &st->connection);
+    if (status != 0) {
+      c_ipmi_error(st, "ipmi_ip_setup_con", status);
+      return -1;
+    }
+  } else {
+    status = ipmi_smi_setup_con(/* if_num = */ 0, os_handler,
+                                /* user data = */ NULL, &st->connection);
+    if (status != 0) {
+      c_ipmi_error(st, "ipmi_smi_setup_con", status);
+      return -1;
+    }
+  }
 
+  ipmi_open_option_t opts[] = {
+      {.option = IPMI_OPEN_OPTION_ALL, {.ival = 1}},
+#ifdef IPMI_OPEN_OPTION_USE_CACHE
+      /* OpenIPMI-2.0.17 and later: Disable SDR cache in local file */
+      {.option = IPMI_OPEN_OPTION_USE_CACHE, {.ival = 0}},
+#endif
+  };
+
+  /*
+   * NOTE: Domain names must be unique. There is static `domains_list` common
+   * to all threads inside lib/domain.c and some ops are done by name.
+   */
   status = ipmi_open_domain(
-      "mydomain", &smi_connection, /* num_con = */ 1,
-      domain_connection_change_handler, /* user data = */ NULL,
-      /* domain_fully_up_handler = */ NULL, /* user data = */ NULL, open_option,
-      sizeof(open_option) / sizeof(open_option[0]), &domain_id);
+      st->name, &st->connection, /* num_con = */ 1,
+      domain_connection_change_handler, /* user data = */ (void *)st,
+      /* domain_fully_up_handler = */ NULL, /* user data = */ NULL, opts,
+      STATIC_ARRAY_SIZE(opts), &domain_id);
   if (status != 0) {
-    c_ipmi_error("ipmi_open_domain", status);
+    c_ipmi_error(st, "ipmi_open_domain", status);
     return -1;
   }
 
-  *ret_os_handler = os_handler;
   return 0;
-} /* int thread_init */
+} /* int c_ipmi_thread_init */
 
-static void *thread_main(void __attribute__((unused)) * user_data) {
-  int status;
-  os_handler_t *os_handler = NULL;
+static void *c_ipmi_thread_main(void *user_data) {
+  c_ipmi_instance_t *st = user_data;
 
-  status = thread_init(&os_handler);
+  int status = c_ipmi_thread_init(st);
   if (status != 0) {
-    ERROR("ipmi plugin: thread_init failed.\n");
+    ERROR("ipmi plugin: c_ipmi_thread_init failed.");
+    st->active = 0;
     return (void *)-1;
   }
 
-  while (c_ipmi_active != 0) {
+  while (st->active != 0) {
     struct timeval tv = {1, 0};
     os_handler->perform_one_op(os_handler, &tv);
   }
+  return (void *)0;
+} /* void *c_ipmi_thread_main */
 
-  ipmi_posix_thread_free_os_handler(os_handler);
+static c_ipmi_instance_t *c_ipmi_init_instance() {
+  c_ipmi_instance_t *st;
 
-  return (void *)0;
-} /* void *thread_main */
-
-static int c_ipmi_config(const char *key, const char *value) {
-  if (ignorelist == NULL)
-    ignorelist = ignorelist_create(/* invert = */ 1);
-  if (ignorelist == NULL)
-    return 1;
-
-  if (strcasecmp("Sensor", key) == 0) {
-    ignorelist_add(ignorelist, value);
-  } else if (strcasecmp("IgnoreSelected", key) == 0) {
-    int invert = 1;
-    if (IS_TRUE(value))
-      invert = 0;
-    ignorelist_set_invert(ignorelist, invert);
-  } else if (strcasecmp("NotifySensorAdd", key) == 0) {
-    if (IS_TRUE(value))
-      c_ipmi_nofiy_add = 1;
-  } else if (strcasecmp("NotifySensorRemove", key) == 0) {
-    if (IS_TRUE(value))
-      c_ipmi_nofiy_remove = 1;
-  } else if (strcasecmp("NotifySensorNotPresent", key) == 0) {
-    if (IS_TRUE(value))
-      c_ipmi_nofiy_notpresent = 1;
-  } else {
-    return -1;
+  st = calloc(1, sizeof(*st));
+  if (st == NULL) {
+    ERROR("ipmi plugin: calloc failed.");
+    return NULL;
   }
 
-  return 0;
-} /* int c_ipmi_config */
+  st->name = strdup("main");
+  if (st->name == NULL) {
+    sfree(st);
+    ERROR("ipmi plugin: strdup() failed.");
+    return NULL;
+  }
 
-static int c_ipmi_init(void) {
-  int status;
+  st->ignorelist = ignorelist_create(/* invert = */ 1);
+  if (st->ignorelist == NULL) {
+    sfree(st->name);
+    sfree(st);
+    ERROR("ipmi plugin: ignorelist_create() failed.");
+    return NULL;
+  }
 
-  /* Don't send `ADD' notifications during startup (~ 1 minute) */
-  time_t iv = CDTIME_T_TO_TIME_T(plugin_get_interval());
-  c_ipmi_init_in_progress = 1 + (60 / iv);
+  st->sensor_list = NULL;
+  pthread_mutex_init(&st->sensor_list_lock, /* attr = */ NULL);
+
+  st->host = NULL;
+  st->connaddr = NULL;
+  st->username = NULL;
+  st->password = NULL;
+  st->authtype = IPMI_AUTHTYPE_DEFAULT;
+
+  st->next = NULL;
+
+  return st;
+} /* c_ipmi_instance_t *c_ipmi_init_instance */
+
+static void c_ipmi_free_instance(c_ipmi_instance_t *st) {
+  if (st == NULL)
+    return;
 
-  c_ipmi_active = 1;
+  assert(st->next == NULL);
+
+  sfree(st->name);
+  sfree(st->host);
+  sfree(st->connaddr);
+  sfree(st->username);
+  sfree(st->password);
+
+  ignorelist_free(st->ignorelist);
+  pthread_mutex_destroy(&st->sensor_list_lock);
+  sfree(st);
+} /* void c_ipmi_free_instance */
+
+static void c_ipmi_add_instance(c_ipmi_instance_t *instance) {
+  if (instances == NULL) {
+    instances = instance;
+    return;
+  }
+
+  c_ipmi_instance_t *last = instances;
+
+  while (last->next != NULL)
+    last = last->next;
+
+  last->next = instance;
+} /* void c_ipmi_add_instance */
+
+static int c_ipmi_config_add_instance(oconfig_item_t *ci) {
+  int status = 0;
+  c_ipmi_instance_t *st = c_ipmi_init_instance();
+  if (st == NULL)
+    return ENOMEM;
+
+  if (strcasecmp(ci->key, "Instance") == 0)
+    status = cf_util_get_string(ci, &st->name);
 
-  status = plugin_thread_create(&thread_id, /* attr = */ NULL, thread_main,
-                                /* user data = */ NULL, "ipmi");
   if (status != 0) {
-    c_ipmi_active = 0;
-    thread_id = (pthread_t)0;
-    ERROR("ipmi plugin: pthread_create failed.");
-    return -1;
+    c_ipmi_free_instance(st);
+    return status;
+  }
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Sensor", child->key) == 0)
+      ignorelist_add(st->ignorelist, ci->values[0].value.string);
+    else if (strcasecmp("IgnoreSelected", child->key) == 0) {
+      _Bool t;
+      status = cf_util_get_boolean(child, &t);
+      if (status != 0)
+        break;
+      ignorelist_set_invert(st->ignorelist, /* invert = */ !t);
+    } else if (strcasecmp("NotifyIPMIConnectionState", child->key) == 0) {
+      status = cf_util_get_boolean(child, &st->notify_conn);
+    } else if (strcasecmp("NotifySensorAdd", child->key) == 0) {
+      status = cf_util_get_boolean(child, &st->notify_add);
+    } else if (strcasecmp("NotifySensorRemove", child->key) == 0) {
+      status = cf_util_get_boolean(child, &st->notify_remove);
+    } else if (strcasecmp("NotifySensorNotPresent", child->key) == 0) {
+      status = cf_util_get_boolean(child, &st->notify_notpresent);
+    } else if (strcasecmp("SELEnabled", child->key) == 0) {
+      status = cf_util_get_boolean(child, &st->sel_enabled);
+    } else if (strcasecmp("SELClearEvent", child->key) == 0) {
+      status = cf_util_get_boolean(child, &st->sel_clear_event);
+    } else if (strcasecmp("Host", child->key) == 0)
+      status = cf_util_get_string(child, &st->host);
+    else if (strcasecmp("Address", child->key) == 0)
+      status = cf_util_get_string(child, &st->connaddr);
+    else if (strcasecmp("Username", child->key) == 0)
+      status = cf_util_get_string(child, &st->username);
+    else if (strcasecmp("Password", child->key) == 0)
+      status = cf_util_get_string(child, &st->password);
+    else if (strcasecmp("AuthType", child->key) == 0) {
+      char tmp[8];
+      status = cf_util_get_string_buffer(child, tmp, sizeof(tmp));
+      if (status != 0)
+        break;
+
+      if (strcasecmp("MD5", tmp) == 0)
+        st->authtype = IPMI_AUTHTYPE_MD5;
+      else if (strcasecmp("rmcp+", tmp) == 0)
+        st->authtype = IPMI_AUTHTYPE_RMCP_PLUS;
+      else
+        WARNING("ipmi plugin: The value \"%s\" is not valid for the "
+                "\"AuthType\" option.",
+                tmp);
+    } else {
+      WARNING("ipmi plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status != 0) {
+    c_ipmi_free_instance(st);
+    return status;
   }
 
+  c_ipmi_add_instance(st);
+
   return 0;
-} /* int c_ipmi_init */
+} /* int c_ipmi_config_add_instance */
+
+static int c_ipmi_config(oconfig_item_t *ci) {
+  _Bool have_instance_block = 0;
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Instance", child->key) == 0) {
+      int status = c_ipmi_config_add_instance(child);
+      if (status != 0)
+        return status;
+
+      have_instance_block = 1;
+    } else if (!have_instance_block) {
+      /* Non-instance option: Assume legacy configuration (without <Instance />
+       * blocks) and call c_ipmi_config_add_instance with the <Plugin /> block.
+       */
+      WARNING("ipmi plugin: Legacy configuration found! Please update your "
+              "config file.");
+      return c_ipmi_config_add_instance(ci);
+    } else {
+      WARNING("ipmi plugin: The configuration option "
+              "\"%s\" is not allowed here. Did you "
+              "forget to add an <Instance /> block "
+              "around the configuration?",
+              child->key);
+      return -1;
+    }
+  } /* for (ci->children) */
+
+  return 0;
+} /* int c_ipmi_config */
 
-static int c_ipmi_read(void) {
-  if ((c_ipmi_active == 0) || (thread_id == (pthread_t)0)) {
+static int c_ipmi_read(user_data_t *user_data) {
+  c_ipmi_instance_t *st = user_data->data;
+
+  if (st->active == 0) {
     INFO("ipmi plugin: c_ipmi_read: I'm not active, returning false.");
     return -1;
   }
 
-  sensor_list_read_all();
+  if (st->connected == 0)
+    return 0;
+
+  sensor_list_read_all(st);
 
-  if (c_ipmi_init_in_progress > 0)
-    c_ipmi_init_in_progress--;
+  if (st->init_in_progress > 0)
+    st->init_in_progress--;
   else
-    c_ipmi_init_in_progress = 0;
+    st->init_in_progress = 0;
 
   return 0;
 } /* int c_ipmi_read */
 
+static int c_ipmi_init(void) {
+  c_ipmi_instance_t *st;
+  char callback_name[3 * DATA_MAX_NAME_LEN];
+
+  if (os_handler != NULL) {
+    return 0;
+  }
+
+  os_handler = ipmi_posix_thread_setup_os_handler(SIGIO);
+  if (os_handler == NULL) {
+    ERROR("ipmi plugin: ipmi_posix_thread_setup_os_handler failed.");
+    return -1;
+  }
+
+  os_handler->set_log_handler(os_handler, c_ipmi_log);
+
+  if (ipmi_init(os_handler) != 0) {
+    ERROR("ipmi plugin: ipmi_init() failed.");
+    os_handler->free_os_handler(os_handler);
+    return -1;
+  };
+
+  if (instances == NULL) {
+    /* No instances were configured, let's start a default instance. */
+    st = c_ipmi_init_instance();
+    if (st == NULL)
+      return ENOMEM;
+
+    c_ipmi_add_instance(st);
+  }
+
+  /* Don't send `ADD' notifications during startup (~ 1 minute) */
+  int cycles = 1 + (60 / CDTIME_T_TO_TIME_T(plugin_get_interval()));
+
+  st = instances;
+  while (NULL != st) {
+    /* The `st->name` is used as "domain name" for ipmi_open_domain().
+     * That value should be unique, so we do plugin_register_complex_read()
+     * at first as it checks the uniqueness. */
+    snprintf(callback_name, sizeof(callback_name), "ipmi/%s", st->name);
+
+    user_data_t ud = {
+        .data = st,
+    };
+
+    int status = plugin_register_complex_read(
+        /* group     = */ "ipmi",
+        /* name      = */ callback_name,
+        /* callback  = */ c_ipmi_read,
+        /* interval  = */ 0,
+        /* user_data = */ &ud);
+
+    if (status != 0) {
+      st = st->next;
+      continue;
+    }
+
+    st->init_in_progress = cycles;
+    st->active = 1;
+
+    status = plugin_thread_create(&st->thread_id, /* attr = */ NULL,
+                                  c_ipmi_thread_main,
+                                  /* user data = */ (void *)st, "ipmi");
+
+    if (status != 0) {
+      st->active = 0;
+      st->thread_id = (pthread_t){0};
+
+      plugin_unregister_read(callback_name);
+
+      ERROR("ipmi plugin: pthread_create failed for `%s`.", callback_name);
+    }
+
+    st = st->next;
+  }
+
+  return 0;
+} /* int c_ipmi_init */
+
 static int c_ipmi_shutdown(void) {
-  c_ipmi_active = 0;
+  c_ipmi_instance_t *st = instances;
+  instances = NULL;
+
+  while (st != NULL) {
+    c_ipmi_instance_t *next = st->next;
+
+    st->next = NULL;
+    st->active = 0;
+
+    if (!pthread_equal(st->thread_id, (pthread_t){0})) {
+      pthread_join(st->thread_id, NULL);
+      st->thread_id = (pthread_t){0};
+    }
+
+    sensor_list_remove_all(st);
+    c_ipmi_free_instance(st);
 
-  if (thread_id != (pthread_t)0) {
-    pthread_join(thread_id, NULL);
-    thread_id = (pthread_t)0;
+    st = next;
   }
 
-  sensor_list_remove_all();
+  os_handler->free_os_handler(os_handler);
+  os_handler = NULL;
 
   return 0;
 } /* int c_ipmi_shutdown */
 
 void module_register(void) {
-  plugin_register_config("ipmi", c_ipmi_config, config_keys, config_keys_num);
+  plugin_register_complex_config("ipmi", c_ipmi_config);
   plugin_register_init("ipmi", c_ipmi_init);
-  plugin_register_read("ipmi", c_ipmi_read);
   plugin_register_shutdown("ipmi", c_ipmi_shutdown);
 } /* void module_register */
index 3ae2e714b7b643a5c8632a883f81d91fa8790352..d47032455325e6e36210d62cc3b6236dd72fd02f 100644 (file)
@@ -24,9 +24,7 @@
  *   Florian octo Forster <octo at collectd.org>
  **/
 
-#if HAVE_CONFIG_H
 #include "config.h"
-#endif
 
 #if !defined(__GNUC__) || !__GNUC__
 #define __attribute__(x) /**/
index 8604ff6b2b2762ec20fcce0f9dd7705e62322a05..ea812c1b6774971239c2259760ed9aa78197bedc 100644 (file)
@@ -27,7 +27,8 @@
 #ifndef LIBCOLLECTD_COLLECTDCLIENT_H
 #define LIBCOLLECTD_COLLECTDCLIENT_H 1
 
-#include "lcc_features.h"
+#include "collectd/lcc_features.h"
+#include "collectd/types.h"
 
 /* COLLECTD_TRACE is the environment variable used to control trace output. When
  * set to something non-zero, all lines sent to / received from the daemon are
 /*
  * Includes (for data types)
  */
-#include <stdint.h>
 #include <inttypes.h>
+#include <stdint.h>
 #include <time.h>
 
-/*
- * Defines
- */
-#define LCC_NAME_LEN 64
-#define LCC_DEFAULT_PORT "25826"
-
-/*
- * Types
- */
-#define LCC_TYPE_COUNTER 0
-#define LCC_TYPE_GAUGE 1
-#define LCC_TYPE_DERIVE 2
-#define LCC_TYPE_ABSOLUTE 3
-
 LCC_BEGIN_DECLS
 
-typedef uint64_t counter_t;
-typedef double gauge_t;
-typedef uint64_t derive_t;
-typedef uint64_t absolute_t;
-
-union value_u {
-  counter_t counter;
-  gauge_t gauge;
-  derive_t derive;
-  absolute_t absolute;
-};
-typedef union value_u value_t;
-
-struct lcc_identifier_s {
-  char host[LCC_NAME_LEN];
-  char plugin[LCC_NAME_LEN];
-  char plugin_instance[LCC_NAME_LEN];
-  char type[LCC_NAME_LEN];
-  char type_instance[LCC_NAME_LEN];
-};
-typedef struct lcc_identifier_s lcc_identifier_t;
-#define LCC_IDENTIFIER_INIT                                                    \
-  { "localhost", "", "", "", "" }
-
-struct lcc_value_list_s {
-  value_t *values;
-  int *values_types;
-  size_t values_len;
-  double time;
-  double interval;
-  lcc_identifier_t identifier;
-};
-typedef struct lcc_value_list_s lcc_value_list_t;
-#define LCC_VALUE_LIST_INIT                                                    \
-  { NULL, NULL, 0, 0, 0, LCC_IDENTIFIER_INIT }
-
 struct lcc_connection_s;
 typedef struct lcc_connection_s lcc_connection_t;
 
index 35fd7d62b54cbf3d9c34ca375ddd2e3b301c23aa..c8a5da54ed75edbdd8d5d45c3e463a4b520cc2eb 100644 (file)
 #ifndef LIBCOLLECTDCLIENT_NETWORK_H
 #define LIBCOLLECTDCLIENT_NETWORK_H 1
 
+#include "collectd/client.h"
+
 #include <inttypes.h>
 #include <stdint.h>
 
-#include "client.h"
-
 #define NET_DEFAULT_V4_ADDR "239.192.74.66"
 #define NET_DEFAULT_V6_ADDR "ff18::efc0:4a42"
 #define NET_DEFAULT_PORT "25826"
index 9f39310331bcc9fc66ef1cbacd7b6f25b4d0fff4..d66b81556478513795462401c8371bb63f032e78 100644 (file)
@@ -27,9 +27,8 @@
 #ifndef LIBCOLLECTDCLIENT_NETWORK_BUFFER_H
 #define LIBCOLLECTDCLIENT_NETWORK_BUFFER_H 1
 
-/* FIXME */
-#include "client.h"
-#include "network.h"
+#include "collectd/network.h" /* for lcc_security_level_t */
+#include "collectd/types.h"
 
 /* Ethernet frame - (IPv6 header + UDP header) */
 #define LCC_NETWORK_BUFFER_SIZE_DEFAULT 1452
diff --git a/src/libcollectdclient/collectd/network_parse.h b/src/libcollectdclient/collectd/network_parse.h
new file mode 100644 (file)
index 0000000..0cd9a79
--- /dev/null
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2017 Florian Forster
+ *
+ * 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:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#ifndef LIBCOLLECTD_NETWORK_PARSE_H
+#define LIBCOLLECTD_NETWORK_PARSE_H 1
+
+#include "collectd/lcc_features.h"
+
+#include "collectd/network.h" /* for lcc_security_level_t */
+#include "collectd/types.h"
+
+#include <stdint.h>
+
+LCC_BEGIN_DECLS
+
+typedef struct {
+  /* writer is the callback used to send incoming lcc_value_list_t to. */
+  lcc_value_list_writer_t writer;
+
+  /* password_lookup is used to look up the password for a given username. */
+  lcc_password_lookup_t password_lookup;
+
+  /* security_level is the minimal required security level. */
+  lcc_security_level_t security_level;
+} lcc_network_parse_options_t;
+
+/* lcc_network_parse parses data received from the network and calls "w" with
+ * the parsed lcc_value_list_ts. */
+int lcc_network_parse(void *buffer, size_t buffer_size,
+                      lcc_network_parse_options_t opts);
+
+LCC_END_DECLS
+
+#endif /* LIBCOLLECTD_NETWORK_PARSE_H */
diff --git a/src/libcollectdclient/collectd/server.h b/src/libcollectdclient/collectd/server.h
new file mode 100644 (file)
index 0000000..ef6b792
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * Copyright 2017 Florian Forster
+ *
+ * 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:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#ifndef LIBCOLLECTD_SERVER_H
+#define LIBCOLLECTD_SERVER_H 1
+
+#include "collectd/lcc_features.h"
+
+#include "collectd/network.h"       /* for lcc_security_level_t */
+#include "collectd/network_parse.h" /* for lcc_network_parse_options_t */
+#include "collectd/types.h"
+
+#include <stdint.h>
+
+#ifndef LCC_NETWORK_BUFFER_SIZE
+#define LCC_NETWORK_BUFFER_SIZE 1452
+#endif
+
+LCC_BEGIN_DECLS
+
+/* lcc_network_parser_t is a callback that parses received network packets. It
+ * is expected to call lcc_network_parse_options_t.writer with each
+ * lcc_value_list_t it parses that has the required security level. */
+typedef int (*lcc_network_parser_t)(void *payload, size_t payload_size,
+                                    lcc_network_parse_options_t opts);
+
+/* lcc_listener_t holds parameters for running a collectd server. */
+typedef struct {
+  /* conn is a UDP socket for the server to listen on. If set to <0 node and
+   * service will be used to open a new UDP socket. If >=0, it is the caller's
+   * job to clean up the socket. */
+  int conn;
+
+  /* node is the local address to listen on if conn is <0. Defaults to "::" (any
+   * address). */
+  char *node;
+
+  /* service is the local address to listen on if conn is <0. Defaults to
+   * LCC_DEFAULT_PORT. */
+  char *service;
+
+  /* parser is the callback used to parse incoming network packets. Defaults to
+   * lcc_network_parse() if set to NULL. */
+  lcc_network_parser_t parser;
+
+  /* parse_options contains options for parser and is passed on verbatimely. */
+  lcc_network_parse_options_t parse_options;
+
+  /* buffer_size determines the maximum packet size to accept. Defaults to
+   * LCC_NETWORK_BUFFER_SIZE if set to zero. */
+  uint16_t buffer_size;
+
+  /* interface is the name of the interface to use when subscribing to a
+   * multicast group. Has no effect when using unicast. */
+  char *interface;
+} lcc_listener_t;
+
+/* lcc_listen_and_write listens on the provided UDP socket (or opens one using
+ * srv.addr if srv.conn is less than zero), parses the received packets and
+ * writes them to the provided lcc_value_list_writer_t. Returns non-zero on
+ * failure and does not return otherwise. */
+int lcc_listen_and_write(lcc_listener_t srv);
+
+LCC_END_DECLS
+
+#endif /* LIBCOLLECTD_SERVER_H */
diff --git a/src/libcollectdclient/collectd/types.h b/src/libcollectdclient/collectd/types.h
new file mode 100644 (file)
index 0000000..7043e67
--- /dev/null
@@ -0,0 +1,97 @@
+/**
+ * libcollectdclient - src/libcollectdclient/collectd/types.h
+ * Copyright (C) 2008-2017  Florian octo Forster
+ *
+ * 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:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#ifndef LIBCOLLECTD_COLLECTD_TYPES_H
+#define LIBCOLLECTD_COLLECTD_TYPES_H 1
+
+#include "collectd/lcc_features.h"
+
+#include <stdint.h>    /* for uint64_t */
+#include <sys/types.h> /* for size_t */
+
+/*
+ * Defines
+ */
+#define LCC_NAME_LEN 64
+#define LCC_DEFAULT_PORT "25826"
+
+/*
+ * Types
+ */
+#define LCC_TYPE_COUNTER 0
+#define LCC_TYPE_GAUGE 1
+#define LCC_TYPE_DERIVE 2
+#define LCC_TYPE_ABSOLUTE 3
+
+LCC_BEGIN_DECLS
+
+typedef uint64_t counter_t;
+typedef double gauge_t;
+typedef uint64_t derive_t;
+typedef uint64_t absolute_t;
+
+union value_u {
+  counter_t counter;
+  gauge_t gauge;
+  derive_t derive;
+  absolute_t absolute;
+};
+typedef union value_u value_t;
+
+struct lcc_identifier_s {
+  char host[LCC_NAME_LEN];
+  char plugin[LCC_NAME_LEN];
+  char plugin_instance[LCC_NAME_LEN];
+  char type[LCC_NAME_LEN];
+  char type_instance[LCC_NAME_LEN];
+};
+typedef struct lcc_identifier_s lcc_identifier_t;
+#define LCC_IDENTIFIER_INIT                                                    \
+  { "localhost", "", "", "", "" }
+
+struct lcc_value_list_s {
+  value_t *values;
+  int *values_types;
+  size_t values_len;
+  double time;
+  double interval;
+  lcc_identifier_t identifier;
+};
+typedef struct lcc_value_list_s lcc_value_list_t;
+#define LCC_VALUE_LIST_INIT                                                    \
+  { NULL, NULL, 0, 0, 0, LCC_IDENTIFIER_INIT }
+
+/* lcc_value_list_writer_t is a write callback to which value lists are
+ * dispatched. */
+typedef int (*lcc_value_list_writer_t)(lcc_value_list_t const *);
+
+/* lcc_password_lookup_t is a callback for looking up the password for a given
+ * user. Must return NULL if the user is not known. */
+typedef char const *(*lcc_password_lookup_t)(char const *);
+
+LCC_END_DECLS
+
+#endif /* LIBCOLLECTD_COLLECTD_TYPES_H */
diff --git a/src/libcollectdclient/network_parse.c b/src/libcollectdclient/network_parse.c
new file mode 100644 (file)
index 0000000..6703411
--- /dev/null
@@ -0,0 +1,623 @@
+/**
+ * Copyright 2017 Florian Forster
+ *
+ * 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:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "config.h"
+
+#if !defined(__GNUC__) || !__GNUC__
+#define __attribute__(x) /**/
+#endif
+
+#include "collectd/lcc_features.h"
+#include "collectd/network_parse.h"
+
+#include <errno.h>
+#include <math.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* for be{16,64}toh */
+#if HAVE_ENDIAN_H
+#include <endian.h>
+#elif HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#endif
+
+#if HAVE_GCRYPT_H
+#define GCRYPT_NO_DEPRECATED
+#include <gcrypt.h>
+#endif
+
+#include <stdio.h>
+#define DEBUG(...) printf(__VA_ARGS__)
+
+#if HAVE_GCRYPT_H
+#if GCRYPT_VERSION_NUMBER < 0x010600
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif
+#endif
+
+/* forward declaration because parse_sign_sha256()/parse_encrypt_aes256() and
+ * network_parse() need to call each other. */
+static int network_parse(void *data, size_t data_size, lcc_security_level_t sl,
+                         lcc_network_parse_options_t const *opts);
+
+#if HAVE_GCRYPT_H
+static int init_gcrypt() {
+  /* http://lists.gnupg.org/pipermail/gcrypt-devel/2003-August/000458.html
+   * Because you can't know in a library whether another library has
+   * already initialized the library */
+  if (gcry_control(GCRYCTL_ANY_INITIALIZATION_P))
+    return (0);
+
+/* http://www.gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html
+ * To ensure thread-safety, it's important to set GCRYCTL_SET_THREAD_CBS
+ * *before* initalizing Libgcrypt with gcry_check_version(), which itself must
+ * be called before any other gcry_* function. GCRYCTL_ANY_INITIALIZATION_P
+ * above doesn't count, as it doesn't implicitly initalize Libgcrypt.
+ *
+ * tl;dr: keep all these gry_* statements in this exact order please. */
+#if GCRYPT_VERSION_NUMBER < 0x010600
+  if (gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread)) {
+    return -1;
+  }
+#endif
+
+  gcry_check_version(NULL);
+
+  if (gcry_control(GCRYCTL_INIT_SECMEM, 32768)) {
+    return -1;
+  }
+
+  gcry_control(GCRYCTL_INITIALIZATION_FINISHED);
+  return 0;
+}
+#endif
+
+typedef struct {
+  uint8_t *data;
+  size_t len;
+} buffer_t;
+
+static int buffer_next(buffer_t *b, void *out, size_t n) {
+  if (b->len < n) {
+    return -1;
+  }
+  memmove(out, b->data, n);
+
+  b->data += n;
+  b->len -= n;
+
+  return 0;
+}
+
+static int buffer_uint16(buffer_t *b, uint16_t *out) {
+  uint16_t tmp;
+  if (buffer_next(b, &tmp, sizeof(tmp)) != 0)
+    return -1;
+
+  *out = be16toh(tmp);
+  return 0;
+}
+
+#define TYPE_HOST 0x0000
+#define TYPE_TIME 0x0001
+#define TYPE_TIME_HR 0x0008
+#define TYPE_PLUGIN 0x0002
+#define TYPE_PLUGIN_INSTANCE 0x0003
+#define TYPE_TYPE 0x0004
+#define TYPE_TYPE_INSTANCE 0x0005
+#define TYPE_VALUES 0x0006
+#define TYPE_INTERVAL 0x0007
+#define TYPE_INTERVAL_HR 0x0009
+#define TYPE_SIGN_SHA256 0x0200
+#define TYPE_ENCR_AES256 0x0210
+
+static int parse_int(void *payload, size_t payload_size, uint64_t *out) {
+  uint64_t tmp;
+
+  if (payload_size != sizeof(tmp))
+    return EINVAL;
+
+  memmove(&tmp, payload, sizeof(tmp));
+  *out = be64toh(tmp);
+  return 0;
+}
+
+static int parse_string(void *payload, size_t payload_size, char *out,
+                        size_t out_size) {
+  char *in = payload;
+
+  if ((payload_size < 1) || (in[payload_size - 1] != 0) ||
+      (payload_size > out_size))
+    return EINVAL;
+
+  strncpy(out, in, out_size);
+  return 0;
+}
+
+static int parse_identifier(uint16_t type, void *payload, size_t payload_size,
+                            lcc_value_list_t *state) {
+  char buf[LCC_NAME_LEN];
+
+  if (parse_string(payload, payload_size, buf, sizeof(buf)) != 0)
+    return EINVAL;
+
+  switch (type) {
+  case TYPE_HOST:
+    memmove(state->identifier.host, buf, LCC_NAME_LEN);
+    break;
+  case TYPE_PLUGIN:
+    memmove(state->identifier.plugin, buf, LCC_NAME_LEN);
+    break;
+  case TYPE_PLUGIN_INSTANCE:
+    memmove(state->identifier.plugin_instance, buf, LCC_NAME_LEN);
+    break;
+  case TYPE_TYPE:
+    memmove(state->identifier.type, buf, LCC_NAME_LEN);
+    break;
+  case TYPE_TYPE_INSTANCE:
+    memmove(state->identifier.type_instance, buf, LCC_NAME_LEN);
+    break;
+  default:
+    return EINVAL;
+  }
+
+  return 0;
+}
+
+static int parse_time(uint16_t type, void *payload, size_t payload_size,
+                      lcc_value_list_t *state) {
+  uint64_t tmp = 0;
+  if (parse_int(payload, payload_size, &tmp))
+    return EINVAL;
+
+  double t = (double)tmp;
+  switch (type) {
+  case TYPE_INTERVAL:
+    state->interval = t;
+    break;
+  case TYPE_INTERVAL_HR:
+    state->interval = t / 1073741824.0;
+    break;
+  case TYPE_TIME:
+    state->time = t;
+    break;
+  case TYPE_TIME_HR:
+    state->time = t / 1073741824.0;
+    break;
+  default:
+    return EINVAL;
+  }
+
+  return 0;
+}
+
+static double ntohd(double val) /* {{{ */
+{
+  static int config = 0;
+
+  union {
+    uint8_t byte[8];
+    double floating;
+  } in = {
+      .floating = val,
+  };
+  union {
+    uint8_t byte[8];
+    double floating;
+  } out = {
+      .byte = {0},
+  };
+
+  if (config == 0) {
+    double d = 8.642135e130;
+    uint8_t b[8];
+
+    memcpy(b, &d, sizeof(b));
+
+    if ((b[0] == 0x2f) && (b[1] == 0x25) && (b[2] == 0xc0) && (b[3] == 0xc7) &&
+        (b[4] == 0x43) && (b[5] == 0x2b) && (b[6] == 0x1f) && (b[7] == 0x5b))
+      config = 1; /* need nothing */
+    else if ((b[7] == 0x2f) && (b[6] == 0x25) && (b[5] == 0xc0) &&
+             (b[4] == 0xc7) && (b[3] == 0x43) && (b[2] == 0x2b) &&
+             (b[1] == 0x1f) && (b[0] == 0x5b))
+      config = 2; /* endian flip */
+    else if ((b[4] == 0x2f) && (b[5] == 0x25) && (b[6] == 0xc0) &&
+             (b[7] == 0xc7) && (b[0] == 0x43) && (b[1] == 0x2b) &&
+             (b[2] == 0x1f) && (b[3] == 0x5b))
+      config = 3; /* int swap */
+    else
+      config = 4;
+  }
+
+  if (memcmp((char[]){0, 0, 0, 0, 0, 0, 0xf8, 0x7f}, in.byte, 8) == 0) {
+    return NAN;
+  } else if (config == 1) {
+    return val;
+  } else if (config == 2) {
+    in.floating = val;
+    out.byte[0] = in.byte[7];
+    out.byte[1] = in.byte[6];
+    out.byte[2] = in.byte[5];
+    out.byte[3] = in.byte[4];
+    out.byte[4] = in.byte[3];
+    out.byte[5] = in.byte[2];
+    out.byte[6] = in.byte[1];
+    out.byte[7] = in.byte[0];
+    return (out.floating);
+  } else if (config == 3) {
+    in.floating = val;
+    out.byte[0] = in.byte[4];
+    out.byte[1] = in.byte[5];
+    out.byte[2] = in.byte[6];
+    out.byte[3] = in.byte[7];
+    out.byte[4] = in.byte[0];
+    out.byte[5] = in.byte[1];
+    out.byte[6] = in.byte[2];
+    out.byte[7] = in.byte[3];
+    return out.floating;
+  } else {
+    /* If in doubt, just copy the value back to the caller. */
+    return val;
+  }
+} /* }}} double ntohd */
+
+static int parse_values(void *payload, size_t payload_size,
+                        lcc_value_list_t *state) {
+  buffer_t *b = &(buffer_t){
+      .data = payload, .len = payload_size,
+  };
+
+  uint16_t n;
+  if (buffer_uint16(b, &n))
+    return EINVAL;
+
+  if (((size_t)n * 9) != b->len)
+    return EINVAL;
+
+  state->values_len = (size_t)n;
+  state->values = calloc(sizeof(*state->values), state->values_len);
+  state->values_types = calloc(sizeof(*state->values_types), state->values_len);
+  if ((state->values == NULL) || (state->values_types == NULL)) {
+    return ENOMEM;
+  }
+
+  for (uint16_t i = 0; i < n; i++) {
+    uint8_t tmp;
+    if (buffer_next(b, &tmp, sizeof(tmp)))
+      return EINVAL;
+    state->values_types[i] = (int)tmp;
+  }
+
+  for (uint16_t i = 0; i < n; i++) {
+    uint64_t tmp;
+    if (buffer_next(b, &tmp, sizeof(tmp)))
+      return EINVAL;
+
+    if (state->values_types[i] == LCC_TYPE_GAUGE) {
+      union {
+        uint64_t i;
+        double d;
+      } conv = {.i = tmp};
+      state->values[i].gauge = ntohd(conv.d);
+      continue;
+    }
+
+    tmp = be64toh(tmp);
+    switch (state->values_types[i]) {
+    case LCC_TYPE_COUNTER:
+      state->values[i].counter = (counter_t)tmp;
+      break;
+    case LCC_TYPE_DERIVE:
+      state->values[i].derive = (derive_t)tmp;
+      break;
+    case LCC_TYPE_ABSOLUTE:
+      state->values[i].absolute = (absolute_t)tmp;
+      break;
+    default:
+      return EINVAL;
+    }
+  }
+
+  return 0;
+}
+
+#if HAVE_GCRYPT_H
+static int verify_sha256(void *payload, size_t payload_size,
+                         char const *username, char const *password,
+                         uint8_t hash_provided[32]) {
+  gcry_md_hd_t hd = NULL;
+
+  gcry_error_t err = gcry_md_open(&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+  if (err != 0) {
+    return (int)err;
+  }
+
+  err = gcry_md_setkey(hd, password, strlen(password));
+  if (err != 0) {
+    gcry_md_close(hd);
+    return (int)err;
+  }
+
+  gcry_md_write(hd, username, strlen(username));
+  gcry_md_write(hd, payload, payload_size);
+
+  unsigned char *hash_calculated = gcry_md_read(hd, GCRY_MD_SHA256);
+  if (!hash_calculated) {
+    gcry_md_close(hd);
+    return -1;
+  }
+
+  int ret = memcmp(hash_provided, hash_calculated, 32);
+
+  gcry_md_close(hd);
+  hash_calculated = NULL;
+
+  return !!ret;
+}
+#else /* !HAVE_GCRYPT_H */
+static int verify_sha256(void *payload, size_t payload_size,
+                         char const *username, char const *password,
+                         uint8_t hash_provided[32]) {
+  return ENOTSUP;
+}
+#endif
+
+static int parse_sign_sha256(void *signature, size_t signature_len,
+                             void *payload, size_t payload_size,
+                             lcc_network_parse_options_t const *opts) {
+  if (opts->password_lookup == NULL) {
+    /* The sender signed the packet but we can't verify it. Handle it as if it
+     * were unsigned, i.e. security level NONE. */
+    return network_parse(payload, payload_size, NONE, opts);
+  }
+
+  buffer_t *b = &(buffer_t){
+      .data = signature, .len = signature_len,
+  };
+
+  uint8_t hash[32];
+  if (buffer_next(b, hash, sizeof(hash)))
+    return EINVAL;
+
+  char username[b->len + 1];
+  memset(username, 0, sizeof(username));
+  if (buffer_next(b, username, sizeof(username) - 1)) {
+    return EINVAL;
+  }
+
+  char const *password = opts->password_lookup(username);
+  if (!password)
+    return network_parse(payload, payload_size, NONE, opts);
+
+  int status = verify_sha256(payload, payload_size, username, password, hash);
+  if (status != 0)
+    return status;
+
+  return network_parse(payload, payload_size, SIGN, opts);
+}
+
+#if HAVE_GCRYPT_H
+static int decrypt_aes256(buffer_t *b, void *iv, size_t iv_size,
+                          char const *password) {
+  gcry_cipher_hd_t cipher = NULL;
+
+  if (gcry_cipher_open(&cipher, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_OFB,
+                       /* flags = */ 0))
+    return -1;
+
+  uint8_t pwhash[32] = {0};
+  gcry_md_hash_buffer(GCRY_MD_SHA256, pwhash, password, strlen(password));
+
+  fprintf(stderr, "sizeof(iv) = %zu\n", sizeof(iv));
+  if (gcry_cipher_setkey(cipher, pwhash, sizeof(pwhash)) ||
+      gcry_cipher_setiv(cipher, iv, iv_size) ||
+      gcry_cipher_decrypt(cipher, b->data, b->len, /* in = */ NULL,
+                          /* in_size = */ 0)) {
+    gcry_cipher_close(cipher);
+    return -1;
+  }
+
+  gcry_cipher_close(cipher);
+  return 0;
+}
+
+static int parse_encrypt_aes256(void *data, size_t data_size,
+                                lcc_network_parse_options_t const *opts) {
+  if (opts->password_lookup == NULL) {
+    /* Without a password source it's (hopefully) impossible to decrypt the
+     * network packet. */
+    return ENOENT;
+  }
+
+  buffer_t *b = &(buffer_t){
+      .data = data, .len = data_size,
+  };
+
+  uint16_t username_len;
+  if (buffer_uint16(b, &username_len))
+    return EINVAL;
+  if ((size_t)username_len > data_size)
+    return ENOMEM;
+  char username[((size_t)username_len) + 1];
+  memset(username, 0, sizeof(username));
+  if (buffer_next(b, username, (size_t)username_len))
+    return EINVAL;
+
+  char const *password = opts->password_lookup(username);
+  if (!password)
+    return ENOENT;
+
+  uint8_t iv[16];
+  if (buffer_next(b, iv, sizeof(iv)))
+    return EINVAL;
+
+  int status = decrypt_aes256(b, iv, sizeof(iv), password);
+  if (status != 0)
+    return status;
+
+  uint8_t hash_provided[20];
+  if (buffer_next(b, hash_provided, sizeof(hash_provided))) {
+    return -1;
+  }
+
+  uint8_t hash_calculated[20];
+  gcry_md_hash_buffer(GCRY_MD_SHA1, hash_calculated, b->data, b->len);
+
+  if (memcmp(hash_provided, hash_calculated, sizeof(hash_provided)) != 0) {
+    return -1;
+  }
+
+  return network_parse(b->data, b->len, ENCRYPT, opts);
+}
+#else /* !HAVE_GCRYPT_H */
+static int parse_encrypt_aes256(void *data, size_t data_size,
+                                lcc_network_parse_options_t const *opts) {
+  return ENOTSUP;
+}
+#endif
+
+static int network_parse(void *data, size_t data_size, lcc_security_level_t sl,
+                         lcc_network_parse_options_t const *opts) {
+  buffer_t *b = &(buffer_t){
+      .data = data, .len = data_size,
+  };
+
+  lcc_value_list_t state = {0};
+
+  while (b->len > 0) {
+    uint16_t type = 0, sz = 0;
+    if (buffer_uint16(b, &type) || buffer_uint16(b, &sz)) {
+      DEBUG("lcc_network_parse(): reading type and/or length failed.\n");
+      return EINVAL;
+    }
+
+    if ((sz < 5) || (((size_t)sz - 4) > b->len)) {
+      DEBUG("lcc_network_parse(): invalid 'sz' field: sz = %" PRIu16
+            ", b->len = %zu\n",
+            sz, b->len);
+      return EINVAL;
+    }
+    sz -= 4;
+
+    uint8_t payload[sz];
+    if (buffer_next(b, payload, sizeof(payload)))
+      return EINVAL;
+
+    switch (type) {
+    case TYPE_HOST:
+    case TYPE_PLUGIN:
+    case TYPE_PLUGIN_INSTANCE:
+    case TYPE_TYPE:
+    case TYPE_TYPE_INSTANCE: {
+      if (parse_identifier(type, payload, sizeof(payload), &state)) {
+        DEBUG("lcc_network_parse(): parse_identifier failed.\n");
+        return EINVAL;
+      }
+      break;
+    }
+
+    case TYPE_INTERVAL:
+    case TYPE_INTERVAL_HR:
+    case TYPE_TIME:
+    case TYPE_TIME_HR: {
+      if (parse_time(type, payload, sizeof(payload), &state)) {
+        DEBUG("lcc_network_parse(): parse_time failed.\n");
+        return EINVAL;
+      }
+      break;
+    }
+
+    case TYPE_VALUES: {
+      lcc_value_list_t vl = state;
+      if (parse_values(payload, sizeof(payload), &vl)) {
+        free(vl.values);
+        free(vl.values_types);
+        DEBUG("lcc_network_parse(): parse_values failed.\n");
+        return EINVAL;
+      }
+
+      int status = 0;
+
+      /* Write metrics if they have the required security level. */
+      if (sl >= opts->security_level)
+        status = opts->writer(&vl);
+
+      free(vl.values);
+      free(vl.values_types);
+
+      if (status != 0)
+        return status;
+      break;
+    }
+
+    case TYPE_SIGN_SHA256: {
+      int status =
+          parse_sign_sha256(payload, sizeof(payload), b->data, b->len, opts);
+      if (status != 0) {
+        DEBUG("lcc_network_parse(): parse_sign_sha256() = %d\n", status);
+        return -1;
+      }
+      /* parse_sign_sha256, if successful, consumes all remaining data. */
+      b->data = NULL;
+      b->len = 0;
+      break;
+    }
+
+    case TYPE_ENCR_AES256: {
+      int status = parse_encrypt_aes256(payload, sizeof(payload), opts);
+      if (status != 0) {
+        DEBUG("lcc_network_parse(): parse_encrypt_aes256() = %d\n", status);
+        return -1;
+      }
+      break;
+    }
+
+    default: {
+      DEBUG("lcc_network_parse(): ignoring unknown type %" PRIu16 "\n", type);
+      return EINVAL;
+    }
+    }
+  }
+
+  return 0;
+}
+
+int lcc_network_parse(void *data, size_t data_size,
+                      lcc_network_parse_options_t opts) {
+  if (opts.password_lookup) {
+#if HAVE_GCRYPT_H
+    int status;
+    if ((status = init_gcrypt())) {
+      return status;
+    }
+#else
+    return ENOTSUP;
+#endif
+  }
+
+  return network_parse(data, data_size, NONE, &opts);
+}
diff --git a/src/libcollectdclient/network_parse_test.c b/src/libcollectdclient/network_parse_test.c
new file mode 100644 (file)
index 0000000..9efd4bd
--- /dev/null
@@ -0,0 +1,511 @@
+/**
+ * Copyright 2017 Florian Forster
+ *
+ * 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:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "collectd/lcc_features.h"
+
+#include "collectd/network_buffer.h" /* for LCC_NETWORK_BUFFER_SIZE_DEFAULT */
+
+#include <assert.h>
+
+#include "network_parse.c" /* sic */
+
+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;
+}
+
+static int nop_writer(lcc_value_list_t const *vl) {
+  if (!strlen(vl->identifier.host) || !strlen(vl->identifier.plugin) ||
+      !strlen(vl->identifier.type)) {
+    return EINVAL;
+  }
+  return 0;
+}
+
+static int test_network_parse() {
+  int ret = 0;
+
+  for (size_t i = 0; i < sizeof(raw_packet_data) / sizeof(raw_packet_data[0]);
+       i++) {
+    uint8_t buffer[LCC_NETWORK_BUFFER_SIZE_DEFAULT];
+    size_t buffer_size = sizeof(buffer);
+    if (decode_string(raw_packet_data[i], buffer, &buffer_size)) {
+      fprintf(
+          stderr,
+          "lcc_network_parse(raw_packet_data[%zu]): decoding string failed\n",
+          i);
+      return -1;
+    }
+
+    int status =
+        lcc_network_parse(buffer, buffer_size, (lcc_network_parse_options_t){
+                                                   .writer = nop_writer,
+                                               });
+    if (status != 0) {
+      fprintf(stderr, "lcc_network_parse(raw_packet_data[%zu]) = %d, want 0\n",
+              i, status);
+      ret = status;
+    }
+
+    printf("ok - lcc_network_parse(raw_packet_data[%zu])\n", i);
+  }
+
+  return ret;
+}
+
+static int test_parse_time() {
+  int ret = 0;
+
+  struct {
+    uint64_t in;
+    double want;
+  } cases[] = {
+      {1439980823, 1439980823.0},
+      {1439981005, 1439981005.0},
+      {1439981150, 1439981150.0},
+  };
+
+  for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
+    lcc_value_list_t vl = LCC_VALUE_LIST_INIT;
+
+    uint64_t be = htobe64(cases[i].in);
+    int status = parse_time(TYPE_TIME, &be, sizeof(be), &vl);
+    if ((status != 0) || (vl.time != cases[i].want)) {
+      fprintf(stderr, "parse_time(%" PRIu64 ") = (%.0f, %d), want (%.0f, 0)\n",
+              cases[i].in, vl.time, status, cases[i].want);
+      ret = -1;
+    }
+  }
+
+  struct {
+    uint64_t in;
+    double want;
+  } cases_hr[] = {
+      {1546167635576736987, 1439980823.152453627},
+      {1546167831554815222, 1439981005.671262017},
+      {1546167986577716567, 1439981150.047589622},
+  };
+
+  for (size_t i = 0; i < sizeof(cases_hr) / sizeof(cases_hr[0]); i++) {
+    lcc_value_list_t vl = LCC_VALUE_LIST_INIT;
+
+    uint64_t be = htobe64(cases_hr[i].in);
+    int status = parse_time(TYPE_TIME_HR, &be, sizeof(be), &vl);
+    if ((status != 0) || (vl.time != cases_hr[i].want)) {
+      fprintf(stderr, "parse_time(%" PRIu64 ") = (%.9f, %d), want (%.9f, 0)\n",
+              cases_hr[i].in, vl.time, status, cases_hr[i].want);
+      ret = -1;
+    }
+  }
+
+  return ret;
+}
+
+static int test_parse_string() {
+  int ret = 0;
+
+  struct {
+    uint8_t *in;
+    size_t in_len;
+    char *want;
+  } cases[] = {
+      {(uint8_t[]){0}, 1, ""},
+      {(uint8_t[]){'t', 'e', 's', 't', 0}, 5, "test"},
+      {(uint8_t[]){'t', 'e', 's', 't'}, 4, NULL}, // null byte missing
+      {(uint8_t[]){'t', 'e', 's', 't', 'x', 0}, 6,
+       NULL}, // output buffer too small
+  };
+
+  for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
+    char got[5] = {0};
+
+    int status = parse_string(cases[i].in, cases[i].in_len, got, sizeof(got));
+    if (cases[i].want == NULL) {
+      if (status == 0) {
+        fprintf(stderr, "parse_string() = (\"%s\", 0), want error\n", got);
+        ret = -1;
+      }
+    } else /* if cases[i].want != NULL */ {
+      if (status != 0) {
+        fprintf(stderr, "parse_string() = %d, want 0\n", status);
+        ret = -1;
+      } else if (strcmp(got, cases[i].want) != 0) {
+        fprintf(stderr, "parse_string() = (\"%s\", 0), want (\"%s\", 0)\n", got,
+                cases[i].want);
+        ret = -1;
+      }
+    }
+  }
+
+  return ret;
+}
+
+static int test_parse_values() {
+  int ret = 0;
+
+  uint8_t testcase[] = {
+      // 0, 6,                          // pkg type
+      // 0, 33,                         // pkg len
+      0, 3,                         // num values
+      1, 2, 1,                      // gauge, derive, gauge
+      0, 0, 0, 0, 0, 0, 0x45, 0x40, // 42.0
+      0, 0, 0, 0, 0, 0, 0x7a, 0x69, // 31337
+      0, 0, 0, 0, 0, 0, 0xf8, 0x7f, // NaN
+  };
+
+  lcc_value_list_t vl = LCC_VALUE_LIST_INIT;
+  int status = parse_values(testcase, sizeof(testcase), &vl);
+  if (status != 0) {
+    fprintf(stderr, "parse_values() = %d, want 0\n", status);
+    return -1;
+  }
+
+  if (vl.values_len != 3) {
+    fprintf(stderr, "parse_values(): vl.values_len = %zu, want 3\n",
+            vl.values_len);
+    return -1;
+  }
+
+  int want_types[] = {LCC_TYPE_GAUGE, LCC_TYPE_DERIVE, LCC_TYPE_GAUGE};
+  for (size_t i = 0; i < sizeof(want_types) / sizeof(want_types[0]); i++) {
+    if (vl.values_types[i] != want_types[i]) {
+      fprintf(stderr, "parse_values(): vl.values_types[%zu] = %d, want %d\n", i,
+              vl.values_types[i], want_types[i]);
+      ret = -1;
+    }
+  }
+
+  if (vl.values[0].gauge != 42.0) {
+    fprintf(stderr, "parse_values(): vl.values[0] = %g, want 42\n",
+            vl.values[0].gauge);
+    ret = -1;
+  }
+  if (vl.values[1].derive != 31337) {
+    fprintf(stderr, "parse_values(): vl.values[1] = %" PRIu64 ", want 31337\n",
+            vl.values[1].derive);
+    ret = -1;
+  }
+  if (!isnan(vl.values[2].gauge)) {
+    fprintf(stderr, "parse_values(): vl.values[2] = %g, want NaN\n",
+            vl.values[2].gauge);
+    ret = -1;
+  }
+
+  free(vl.values);
+  free(vl.values_types);
+
+  return ret;
+}
+
+static int test_verify_sha256() {
+  int ret = 0;
+
+  int status = verify_sha256(
+      (char[]){'c', 'o', 'l', 'l', 'e', 'c', 't', 'd'}, 8, "admin", "admin",
+      (uint8_t[]){
+          0xcd, 0xa5, 0x9a, 0x37, 0xb0, 0x81, 0xc2, 0x31, 0x24, 0x2a, 0x6d,
+          0xbd, 0xfb, 0x44, 0xdb, 0xd7, 0x41, 0x2a, 0xf4, 0x29, 0x83, 0xde,
+          0xa5, 0x11, 0x96, 0xd2, 0xe9, 0x30, 0x21, 0xae, 0xc5, 0x45,
+      });
+  if (status != 0) {
+    fprintf(stderr, "verify_sha256() = %d, want 0\n", status);
+    ret = -1;
+  }
+
+  status = verify_sha256(
+      (char[]){'c', 'o', 'l', 'l', 'E', 'c', 't', 'd'}, 8, "admin", "admin",
+      (uint8_t[]){
+          0xcd, 0xa5, 0x9a, 0x37, 0xb0, 0x81, 0xc2, 0x31, 0x24, 0x2a, 0x6d,
+          0xbd, 0xfb, 0x44, 0xdb, 0xd7, 0x41, 0x2a, 0xf4, 0x29, 0x83, 0xde,
+          0xa5, 0x11, 0x96, 0xd2, 0xe9, 0x30, 0x21, 0xae, 0xc5, 0x45,
+      });
+  if (status != 1) {
+    fprintf(stderr, "verify_sha256() = %d, want 1\n", status);
+    ret = -1;
+  }
+
+  return ret;
+}
+
+static int test_decrypt_aes256() {
+  char const *iv_str = "4cbe2a747c9f9dcfa0e66f0c2fa74875";
+  uint8_t iv[16] = {0};
+  size_t iv_len = sizeof(iv);
+
+  char const *ciphertext_str =
+      "8f023b0b15178f8428da1221a5f653e840f065db4aff032c22e5a3df";
+  uint8_t ciphertext[28] = {0};
+  size_t ciphertext_len = sizeof(ciphertext);
+
+  if (decode_string(iv_str, iv, &iv_len) ||
+      decode_string(ciphertext_str, ciphertext, &ciphertext_len)) {
+    fprintf(stderr, "test_decrypt_aes256: decode_string failed.\n");
+    return -1;
+  }
+  assert(iv_len == sizeof(iv));
+  assert(ciphertext_len == sizeof(ciphertext));
+
+  int status = decrypt_aes256(
+      &(buffer_t){
+          .data = ciphertext, .len = ciphertext_len,
+      },
+      iv, iv_len, "admin");
+  if (status != 0) {
+    fprintf(stderr, "decrypt_aes256() = %d, want 0\n", status);
+    return -1;
+  }
+
+  char const *want = "collectd";
+  char got[9] = {0};
+  memmove(got, &ciphertext[20], sizeof(got) - 1);
+  if (strcmp(got, want) != 0) {
+    fprintf(stderr, "decrypt_aes256() = \"%s\", want \"%s\"\n", got, want);
+    return -1;
+  }
+
+  return 0;
+}
+
+int main(void) {
+  int ret = 0;
+
+  printf("libcollectdclient/server_test.c\n");
+
+  int status;
+  if ((status = test_network_parse())) {
+    ret = status;
+  }
+  if ((status = test_parse_time())) {
+    ret = status;
+  }
+  if ((status = test_parse_string())) {
+    ret = status;
+  }
+  if ((status = test_parse_values())) {
+    ret = status;
+  }
+
+  if ((status = test_verify_sha256())) {
+    ret = status;
+  }
+  if ((status = test_decrypt_aes256())) {
+    ret = status;
+  }
+
+  return ret;
+}
diff --git a/src/libcollectdclient/server.c b/src/libcollectdclient/server.c
new file mode 100644 (file)
index 0000000..d18cc7d
--- /dev/null
@@ -0,0 +1,213 @@
+/**
+ * Copyright 2017 Florian Forster
+ *
+ * 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:
+ *   Florian octo Forster <octo at collectd.org>
+ **/
+
+#include "config.h"
+
+#if !defined(__GNUC__) || !__GNUC__
+#define __attribute__(x) /**/
+#endif
+
+#include "collectd/lcc_features.h"
+#include "collectd/network_parse.h" /* for lcc_network_parse_options_t */
+#include "collectd/server.h"
+
+#include <errno.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#define DEBUG(...) printf(__VA_ARGS__)
+
+static _Bool is_multicast(struct addrinfo const *ai) {
+  if (ai->ai_family == AF_INET) {
+    struct sockaddr_in *addr = (struct sockaddr_in *)ai->ai_addr;
+    return IN_MULTICAST(ntohl(addr->sin_addr.s_addr));
+  } else if (ai->ai_family == AF_INET6) {
+    struct sockaddr_in6 *addr = (struct sockaddr_in6 *)ai->ai_addr;
+    return IN6_IS_ADDR_MULTICAST(&addr->sin6_addr);
+  }
+  return 0;
+}
+
+static int server_multicast_join(lcc_listener_t *srv,
+                                 struct sockaddr_storage *group, int loop_back,
+                                 int ttl) {
+  if (group->ss_family == AF_INET) {
+    struct sockaddr_in *sa = (struct sockaddr_in *)group;
+
+    int status = setsockopt(srv->conn, IPPROTO_IP, IP_MULTICAST_LOOP,
+                            &loop_back, sizeof(loop_back));
+    if (status == -1) {
+      DEBUG("setsockopt(IP_MULTICAST_LOOP, %d) = %d\n", loop_back, errno);
+      return errno;
+    }
+
+    status =
+        setsockopt(srv->conn, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
+    if (status == -1)
+      return errno;
+
+#if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+    struct ip_mreqn mreq = {
+        .imr_address.s_addr = INADDR_ANY,
+        .imr_multiaddr.s_addr = sa->sin_addr.s_addr,
+        .imr_ifindex = if_nametoindex(srv->interface),
+    };
+#else
+    struct ip_mreq mreq = {
+        .imr_address.s_addr = INADDR_ANY, .imr_multiaddr.s_addr = sa->s_addr,
+    };
+#endif
+    status = setsockopt(srv->conn, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
+                        sizeof(mreq));
+    if (status == -1)
+      return errno;
+  } else if (group->ss_family == AF_INET6) {
+    struct sockaddr_in6 *sa = (struct sockaddr_in6 *)group;
+
+    int status = setsockopt(srv->conn, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+                            &loop_back, sizeof(loop_back));
+    if (status == -1)
+      return errno;
+
+    status = setsockopt(srv->conn, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl,
+                        sizeof(ttl));
+    if (status == -1)
+      return errno;
+
+    struct ipv6_mreq mreq6 = {
+        .ipv6mr_interface = if_nametoindex(srv->interface),
+    };
+    memmove(&mreq6.ipv6mr_multiaddr, &sa->sin6_addr, sizeof(struct in6_addr));
+
+    status = setsockopt(srv->conn, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6,
+                        sizeof(mreq6));
+    if (status == -1)
+      return errno;
+  } else {
+    return EINVAL;
+  }
+
+  return 0;
+}
+
+static int server_bind_socket(lcc_listener_t *srv, struct addrinfo const *ai) {
+  /* allow multiple sockets to use the same PORT number */
+  if (setsockopt(srv->conn, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) ==
+      -1) {
+    return errno;
+  }
+
+  if (bind(srv->conn, ai->ai_addr, ai->ai_addrlen) == -1) {
+    return -1;
+  }
+
+  if (is_multicast(ai)) {
+    int status = server_multicast_join(srv, (void *)ai->ai_addr, /* loop = */ 1,
+                                       /* ttl = */ 16);
+    if (status != 0)
+      return status;
+  }
+
+  return 0;
+}
+
+static int server_open(lcc_listener_t *srv) {
+  struct addrinfo *res = NULL;
+  int status = getaddrinfo(srv->node ? srv->node : "::",
+                           srv->service ? srv->service : LCC_DEFAULT_PORT,
+                           &(struct addrinfo){
+                               .ai_flags = AI_ADDRCONFIG,
+                               .ai_family = AF_UNSPEC,
+                               .ai_socktype = SOCK_DGRAM,
+                           },
+                           &res);
+  if (status != 0)
+    return status;
+
+  for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) {
+    srv->conn = socket(ai->ai_family, ai->ai_socktype, 0);
+    if (srv->conn == -1)
+      continue;
+
+    status = server_bind_socket(srv, ai);
+    if (status != 0) {
+      close(srv->conn);
+      srv->conn = -1;
+      continue;
+    }
+
+    break;
+  }
+
+  freeaddrinfo(res);
+
+  if (srv->conn >= 0)
+    return 0;
+  return status != 0 ? status : -1;
+}
+
+int lcc_listen_and_write(lcc_listener_t srv) {
+  _Bool close_socket = 0;
+
+  if (srv.conn < 0) {
+    int status = server_open(&srv);
+    if (status != 0)
+      return status;
+    close_socket = 1;
+  }
+
+  if (srv.buffer_size == 0)
+    srv.buffer_size = LCC_NETWORK_BUFFER_SIZE;
+
+  if (srv.parser == NULL)
+    srv.parser = lcc_network_parse;
+
+  int ret = 0;
+  while (42) {
+    char buffer[srv.buffer_size];
+    ssize_t len = recv(srv.conn, buffer, sizeof(buffer), /* flags = */ 0);
+    if (len == -1) {
+      ret = errno;
+      break;
+    } else if (len == 0) {
+      break;
+    }
+
+    (void)srv.parser(buffer, (size_t)len, srv.parse_options);
+  }
+
+  if (close_socket) {
+    close(srv.conn);
+    srv.conn = -1;
+  }
+
+  return ret;
+}
index b8731c73391ca6796080823cebee64951e984ff1..ae5a7f549646b85bf72dfa25d3c9ead7e55e687e 100644 (file)
@@ -2,7 +2,7 @@
  * collectd - src/mcelog.c
  * MIT License
  *
- * Copyright(c) 2016 Intel Corporation. All rights reserved.
+ * Copyright(c) 2016-2017 Intel Corporation. All rights reserved.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
  *   Krzysztof Matczak <krzysztofx.matczak@intel.com>
  */
 
-#include "common.h"
 #include "collectd.h"
 
+#include "common.h"
+#include "utils_llist.h"
+
 #include <poll.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #define MCELOG_DIMM_NAME "DMI_NAME"
 #define MCELOG_CORRECTED_ERR "corrected memory errors"
 #define MCELOG_UNCORRECTED_ERR "uncorrected memory errors"
+#define MCELOG_CORRECTED_ERR_TIMED "corrected memory timed errors"
+#define MCELOG_UNCORRECTED_ERR_TIMED "uncorrected memory timed errors"
+#define MCELOG_CORRECTED_ERR_TYPE_INS "corrected_memory_errors"
+#define MCELOG_UNCORRECTED_ERR_TYPE_INS "uncorrected_memory_errors"
 
 typedef struct mcelog_config_s {
   char logfile[PATH_MAX]; /* mcelog logfile */
   pthread_t tid;          /* poll thread id */
+  llist_t *dimms_list;    /* DIMMs list */
+  pthread_mutex_t dimms_lock; /* lock for dimms cache */
+  _Bool persist;
 } mcelog_config_t;
 
 typedef struct socket_adapter_s socket_adapter_t;
@@ -80,7 +89,9 @@ static int socket_write(socket_adapter_t *self, const char *msg,
 static int socket_reinit(socket_adapter_t *self);
 static int socket_receive(socket_adapter_t *self, FILE **p_file);
 
-static mcelog_config_t g_mcelog_config = {.logfile = "/var/log/mcelog"};
+static mcelog_config_t g_mcelog_config = {
+    .logfile = "/var/log/mcelog", .persist = 0,
+};
 
 static socket_adapter_t socket_adapter = {
     .sock_fd = -1,
@@ -96,31 +107,129 @@ static socket_adapter_t socket_adapter = {
 };
 
 static _Bool mcelog_thread_running;
+static _Bool mcelog_apply_defaults;
+
+static void mcelog_free_dimms_list_records(llist_t *dimms_list) {
+
+  for (llentry_t *e = llist_head(dimms_list); e != NULL; e = e->next) {
+    sfree(e->key);
+    sfree(e->value);
+  }
+}
+
+/* Create or get dimm by dimm name/location */
+static llentry_t *mcelog_dimm(const mcelog_memory_rec_t *rec,
+                              llist_t *dimms_list) {
+
+  char dimm_name[DATA_MAX_NAME_LEN];
+
+  if (strlen(rec->dimm_name) > 0) {
+    snprintf(dimm_name, sizeof(dimm_name), "%s_%s", rec->location,
+             rec->dimm_name);
+  } else
+    sstrncpy(dimm_name, rec->location, sizeof(dimm_name));
+
+  llentry_t *dimm_le = llist_search(g_mcelog_config.dimms_list, dimm_name);
+
+  if (dimm_le != NULL)
+    return dimm_le;
+
+  /* allocate new linked list entry */
+  mcelog_memory_rec_t *dimm_mr = calloc(1, sizeof(*dimm_mr));
+  if (dimm_mr == NULL) {
+    ERROR(MCELOG_PLUGIN ": Error allocating dimm memory item");
+    return NULL;
+  }
+  char *p_name = strdup(dimm_name);
+  if (p_name == NULL) {
+    ERROR(MCELOG_PLUGIN ": strdup: error");
+    free(dimm_mr);
+    return NULL;
+  }
+
+  /* add new dimm */
+  dimm_le = llentry_create(p_name, dimm_mr);
+  if (dimm_le == NULL) {
+    ERROR(MCELOG_PLUGIN ": llentry_create(): error");
+    free(dimm_mr);
+    free(p_name);
+    return NULL;
+  }
+  pthread_mutex_lock(&g_mcelog_config.dimms_lock);
+  llist_append(g_mcelog_config.dimms_list, dimm_le);
+  pthread_mutex_unlock(&g_mcelog_config.dimms_lock);
+
+  return dimm_le;
+}
+
+static void mcelog_update_dimm_stats(llentry_t *dimm,
+                                     const mcelog_memory_rec_t *rec) {
+  pthread_mutex_lock(&g_mcelog_config.dimms_lock);
+  memcpy(dimm->value, rec, sizeof(mcelog_memory_rec_t));
+  pthread_mutex_unlock(&g_mcelog_config.dimms_lock);
+}
 
 static int mcelog_config(oconfig_item_t *ci) {
+  int use_logfile = 0, use_memory = 0;
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
-    if (strcasecmp("McelogClientSocket", child->key) == 0) {
-      if (cf_util_get_string_buffer(child, socket_adapter.unix_sock.sun_path,
-                                    sizeof(socket_adapter.unix_sock.sun_path)) <
-          0) {
-        ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
+    if (strcasecmp("McelogLogfile", child->key) == 0) {
+      use_logfile = 1;
+      if (use_memory) {
+        ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\", Memory "
+                            "option is already configured.",
               child->key);
         return -1;
       }
-    } else if (strcasecmp("McelogLogfile", child->key) == 0) {
       if (cf_util_get_string_buffer(child, g_mcelog_config.logfile,
                                     sizeof(g_mcelog_config.logfile)) < 0) {
         ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
               child->key);
         return -1;
       }
+      memset(socket_adapter.unix_sock.sun_path, 0,
+             sizeof(socket_adapter.unix_sock.sun_path));
+    } else if (strcasecmp("Memory", child->key) == 0) {
+      if (use_logfile) {
+        ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\", Logfile "
+                            "option is already configured.",
+              child->key);
+        return -1;
+      }
+      use_memory = 1;
+      for (int j = 0; j < child->children_num; j++) {
+        oconfig_item_t *mem_child = child->children + j;
+        if (strcasecmp("McelogClientSocket", mem_child->key) == 0) {
+          if (cf_util_get_string_buffer(
+                  mem_child, socket_adapter.unix_sock.sun_path,
+                  sizeof(socket_adapter.unix_sock.sun_path)) < 0) {
+            ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
+                  mem_child->key);
+            return -1;
+          }
+        } else if (strcasecmp("PersistentNotification", mem_child->key) == 0) {
+          if (cf_util_get_boolean(mem_child, &g_mcelog_config.persist) < 0) {
+            ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
+                  mem_child->key);
+            return -1;
+          }
+        } else {
+          ERROR(MCELOG_PLUGIN ": Invalid Memory configuration option: \"%s\".",
+                mem_child->key);
+          return -1;
+        }
+      }
+      memset(g_mcelog_config.logfile, 0, sizeof(g_mcelog_config.logfile));
     } else {
       ERROR(MCELOG_PLUGIN ": Invalid configuration option: \"%s\".",
             child->key);
       return -1;
     }
   }
+
+  if (!use_logfile && !use_memory)
+    mcelog_apply_defaults = 1;
+
   return 0;
 }
 
@@ -148,7 +257,7 @@ static int socket_write(socket_adapter_t *self, const char *msg,
                         const size_t len) {
   int ret = 0;
   pthread_rwlock_rdlock(&self->lock);
-  if (swrite(self->sock_fd, msg, len) < 0)
+  if (swrite(self->sock_fd, msg, len) != 0)
     ret = -1;
   pthread_rwlock_unlock(&self->lock);
   return ret;
@@ -212,64 +321,82 @@ static int socket_reinit(socket_adapter_t *self) {
   return ret;
 }
 
-static int mcelog_prepare_notification(notification_t *n,
-                                       const mcelog_memory_rec_t *mr) {
-  if (n == NULL || mr == NULL)
-    return -1;
+static int mcelog_dispatch_mem_notifications(const mcelog_memory_rec_t *mr) {
+  notification_t n = {.severity = NOTIF_WARNING,
+                      .time = cdtime(),
+                      .plugin = MCELOG_PLUGIN,
+                      .type = "errors"};
 
-  if ((mr->location[0] != '\0') &&
-      (plugin_notification_meta_add_string(n, MCELOG_SOCKET_STR, mr->location) <
-       0)) {
-    ERROR(MCELOG_PLUGIN ": add memory location meta data failed");
-    return -1;
-  }
-  if ((mr->dimm_name[0] != '\0') &&
-      (plugin_notification_meta_add_string(n, MCELOG_DIMM_NAME, mr->dimm_name) <
-       0)) {
-    ERROR(MCELOG_PLUGIN ": add DIMM name meta data failed");
-    plugin_notification_meta_free(n->meta);
-    return -1;
-  }
-  if (plugin_notification_meta_add_signed_int(n, MCELOG_CORRECTED_ERR,
-                                              mr->corrected_err_total) < 0) {
-    ERROR(MCELOG_PLUGIN ": add corrected errors meta data failed");
-    plugin_notification_meta_free(n->meta);
-    return -1;
-  }
-  if (plugin_notification_meta_add_signed_int(
-          n, "corrected memory timed errors", mr->corrected_err_timed) < 0) {
-    ERROR(MCELOG_PLUGIN ": add corrected timed errors meta data failed");
-    plugin_notification_meta_free(n->meta);
+  int dispatch_corrected_notifs = 0, dispatch_uncorrected_notifs = 0;
+
+  if (mr == NULL)
     return -1;
-  }
-  if ((mr->corrected_err_timed_period[0] != '\0') &&
-      (plugin_notification_meta_add_string(n, "corrected errors time period",
-                                           mr->corrected_err_timed_period) <
-       0)) {
-    ERROR(MCELOG_PLUGIN ": add corrected errors period meta data failed");
-    plugin_notification_meta_free(n->meta);
+
+  llentry_t *dimm = mcelog_dimm(mr, g_mcelog_config.dimms_list);
+  if (dimm == NULL) {
+    ERROR(MCELOG_PLUGIN
+          ": Error adding/getting dimm memory item to/from cache");
     return -1;
   }
-  if (plugin_notification_meta_add_signed_int(n, MCELOG_UNCORRECTED_ERR,
-                                              mr->uncorrected_err_total) < 0) {
-    ERROR(MCELOG_PLUGIN ": add corrected errors meta data failed");
-    plugin_notification_meta_free(n->meta);
-    return -1;
+  mcelog_memory_rec_t *mr_old = dimm->value;
+  if (!g_mcelog_config.persist) {
+
+    if (mr_old->corrected_err_total != mr->corrected_err_total ||
+        mr_old->corrected_err_timed != mr->corrected_err_timed)
+      dispatch_corrected_notifs = 1;
+
+    if (mr_old->uncorrected_err_total != mr->uncorrected_err_total ||
+        mr_old->uncorrected_err_timed != mr->uncorrected_err_timed)
+      dispatch_uncorrected_notifs = 1;
+
+    if (!dispatch_corrected_notifs && !dispatch_uncorrected_notifs) {
+      DEBUG("%s: No new notifications to dispatch", MCELOG_PLUGIN);
+      return 0;
+    }
+  } else {
+    dispatch_corrected_notifs = 1;
+    dispatch_uncorrected_notifs = 1;
   }
-  if (plugin_notification_meta_add_signed_int(n,
-                                              "uncorrected memory timed errors",
-                                              mr->uncorrected_err_timed) < 0) {
-    ERROR(MCELOG_PLUGIN ": add corrected timed errors meta data failed");
-    plugin_notification_meta_free(n->meta);
-    return -1;
+
+  sstrncpy(n.host, hostname_g, sizeof(n.host));
+
+  if (mr->dimm_name[0] != '\0')
+    snprintf(n.plugin_instance, sizeof(n.plugin_instance), "%s_%s",
+             mr->location, mr->dimm_name);
+  else
+    sstrncpy(n.plugin_instance, mr->location, sizeof(n.plugin_instance));
+
+  if (dispatch_corrected_notifs &&
+      (mr->corrected_err_total > 0 || mr->corrected_err_timed > 0)) {
+    /* Corrected Error Notifications */
+    plugin_notification_meta_add_signed_int(&n, MCELOG_CORRECTED_ERR,
+                                            mr->corrected_err_total);
+    plugin_notification_meta_add_signed_int(&n, MCELOG_CORRECTED_ERR_TIMED,
+                                            mr->corrected_err_timed);
+    snprintf(n.message, sizeof(n.message), MCELOG_CORRECTED_ERR);
+    sstrncpy(n.type_instance, MCELOG_CORRECTED_ERR_TYPE_INS,
+             sizeof(n.type_instance));
+    plugin_dispatch_notification(&n);
+    if (n.meta)
+      plugin_notification_meta_free(n.meta);
+    n.meta = NULL;
   }
-  if ((mr->uncorrected_err_timed_period[0] != '\0') &&
-      (plugin_notification_meta_add_string(n, "uncorrected errors time period",
-                                           mr->uncorrected_err_timed_period) <
-       0)) {
-    ERROR(MCELOG_PLUGIN ": add corrected errors period meta data failed");
-    plugin_notification_meta_free(n->meta);
-    return -1;
+
+  if (dispatch_uncorrected_notifs &&
+      (mr->uncorrected_err_total > 0 || mr->uncorrected_err_timed > 0)) {
+    /* Uncorrected Error Notifications */
+    plugin_notification_meta_add_signed_int(&n, MCELOG_UNCORRECTED_ERR,
+                                            mr->uncorrected_err_total);
+    plugin_notification_meta_add_signed_int(&n, MCELOG_UNCORRECTED_ERR_TIMED,
+                                            mr->uncorrected_err_timed);
+    snprintf(n.message, sizeof(n.message), MCELOG_UNCORRECTED_ERR);
+    sstrncpy(n.type_instance, MCELOG_UNCORRECTED_ERR_TYPE_INS,
+             sizeof(n.type_instance));
+    n.severity = NOTIF_FAILURE;
+    plugin_dispatch_notification(&n);
+    if (n.meta)
+      plugin_notification_meta_free(n.meta);
+    n.meta = NULL;
   }
 
   return 0;
@@ -282,13 +409,22 @@ static int mcelog_submit(const mcelog_memory_rec_t *mr) {
     return -1;
   }
 
+  llentry_t *dimm = mcelog_dimm(mr, g_mcelog_config.dimms_list);
+  if (dimm == NULL) {
+    ERROR(MCELOG_PLUGIN
+          ": Error adding/getting dimm memory item to/from cache");
+    return -1;
+  }
+
   value_list_t vl = {
       .values_len = 1,
       .values = &(value_t){.derive = (derive_t)mr->corrected_err_total},
       .time = cdtime(),
       .plugin = MCELOG_PLUGIN,
       .type = "errors",
-      .type_instance = "corrected_memory_errors"};
+      .type_instance = MCELOG_CORRECTED_ERR_TYPE_INS};
+
+  mcelog_update_dimm_stats(dimm, mr);
 
   if (mr->dimm_name[0] != '\0')
     snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s_%s",
@@ -303,7 +439,7 @@ static int mcelog_submit(const mcelog_memory_rec_t *mr) {
   vl.values = &(value_t){.derive = (derive_t)mr->corrected_err_timed};
   plugin_dispatch_values(&vl);
 
-  sstrncpy(vl.type_instance, "uncorrected_memory_errors",
+  sstrncpy(vl.type_instance, MCELOG_UNCORRECTED_ERR_TYPE_INS,
            sizeof(vl.type_instance));
   vl.values = &(value_t){.derive = (derive_t)mr->uncorrected_err_total};
   plugin_dispatch_values(&vl);
@@ -472,14 +608,8 @@ static void *poll_worker(__attribute__((unused)) void *arg) {
         continue;
       }
 
-      notification_t n = {.severity = NOTIF_OKAY,
-                          .time = cdtime(),
-                          .message = "Got memory errors info.",
-                          .plugin = MCELOG_PLUGIN,
-                          .type_instance = "memory_erros"};
-
-      if (mcelog_prepare_notification(&n, &memory_record) == 0)
-        mcelog_dispatch_notification(&n);
+      if (mcelog_dispatch_mem_notifications(&memory_record) != 0)
+        ERROR(MCELOG_PLUGIN ": Failed to submit memory errors notification");
       if (mcelog_submit(&memory_record) != 0)
         ERROR(MCELOG_PLUGIN ": Failed to submit memory errors");
       memset(&memory_record, 0, sizeof(memory_record));
@@ -495,15 +625,29 @@ static void *poll_worker(__attribute__((unused)) void *arg) {
 }
 
 static int mcelog_init(void) {
+  if (mcelog_apply_defaults) {
+    INFO(MCELOG_PLUGIN
+         ": No configuration selected defaulting to memory errors.");
+    memset(g_mcelog_config.logfile, 0, sizeof(g_mcelog_config.logfile));
+  }
+  g_mcelog_config.dimms_list = llist_create();
+  int err = pthread_mutex_init(&g_mcelog_config.dimms_lock, NULL);
+  if (err < 0) {
+    ERROR(MCELOG_PLUGIN ": plugin: failed to initialize cache lock");
+    return -1;
+  }
+
   if (socket_adapter.reinit(&socket_adapter) != 0) {
     ERROR(MCELOG_PLUGIN ": Cannot connect to client socket");
     return -1;
   }
 
-  if (plugin_thread_create(&g_mcelog_config.tid, NULL, poll_worker, NULL,
-                           NULL) != 0) {
-    ERROR(MCELOG_PLUGIN ": Error creating poll thread.");
-    return -1;
+  if (strlen(socket_adapter.unix_sock.sun_path)) {
+    if (plugin_thread_create(&g_mcelog_config.tid, NULL, poll_worker, NULL,
+                             NULL) != 0) {
+      ERROR(MCELOG_PLUGIN ": Error creating poll thread.");
+      return -1;
+    }
   }
   return 0;
 }
@@ -536,7 +680,12 @@ static int mcelog_shutdown(void) {
       ret = -1;
     }
   }
-
+  pthread_mutex_lock(&g_mcelog_config.dimms_lock);
+  mcelog_free_dimms_list_records(g_mcelog_config.dimms_list);
+  llist_destroy(g_mcelog_config.dimms_list);
+  g_mcelog_config.dimms_list = NULL;
+  pthread_mutex_unlock(&g_mcelog_config.dimms_lock);
+  pthread_mutex_destroy(&g_mcelog_config.dimms_lock);
   ret = socket_adapter.close(&socket_adapter) || ret;
   pthread_rwlock_destroy(&(socket_adapter.lock));
   return -ret;
index c2147fd51b1f98025d26ec64bd34195495834828..bd088ecdfce3cb6b67322875e34e416688623e90 100644 (file)
@@ -51,6 +51,7 @@ struct web_page_s;
 typedef struct web_page_s web_page_t;
 struct web_page_s /* {{{ */
 {
+  char *plugin_name;
   char *instance;
 
   char *server;
@@ -94,6 +95,7 @@ static void cmc_web_page_free(web_page_t *wp) /* {{{ */
     memcached_free(wp->memc);
   wp->memc = NULL;
 
+  sfree(wp->plugin_name);
   sfree(wp->instance);
   sfree(wp->server);
   sfree(wp->key);
@@ -302,6 +304,8 @@ static int cmc_config_add_page(oconfig_item_t *ci) /* {{{ */
       status = cmc_config_add_string("Server", &page->server, child);
     else if (strcasecmp("Key", child->key) == 0)
       status = cmc_config_add_string("Key", &page->key, child);
+    else if (strcasecmp("Plugin", child->key) == 0)
+      status = cmc_config_add_string("Plugin", &page->plugin_name, child);
     else if (strcasecmp("Match", child->key) == 0)
       /* Be liberal with failing matches => don't set `status'. */
       cmc_config_add_match(page, child);
@@ -407,7 +411,8 @@ static void cmc_submit(const web_page_t *wp, const web_match_t *wm, /* {{{ */
 
   vl.values = &value;
   vl.values_len = 1;
-  sstrncpy(vl.plugin, "memcachec", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "memcachec",
+           sizeof (vl.plugin));
   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, wm->type, sizeof(vl.type));
   sstrncpy(vl.type_instance, wm->instance, sizeof(vl.type_instance));
index e4ccbce60f4aaddc19fe5c7e3d8c2e379f97a0cd..85794a5ccfcf3055923fbd8ef4191e2be21fd6ce 100644 (file)
@@ -5,6 +5,7 @@
  * Copyright (C) 2009       Doug MacEachern
  * Copyright (C) 2009       Franck Lombardi
  * Copyright (C) 2012       Nicolas Szalay
+ * Copyright (C) 2017       Pavel Rochnyak
  *
  * 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
@@ -26,6 +27,7 @@
  *   Doug MacEachern <dougm at hyperic.com>
  *   Franck Lombardi
  *   Nicolas Szalay
+ *   Pavel Rochnyak <pavel2000 ngs.ru>
  **/
 
 #include "collectd.h"
 #include <netinet/tcp.h>
 #include <sys/un.h>
 
+#include <poll.h>
+
 #define MEMCACHED_DEF_HOST "127.0.0.1"
 #define MEMCACHED_DEF_PORT "11211"
+#define MEMCACHED_CONNECT_TIMEOUT 10000
+#define MEMCACHED_IO_TIMEOUT 5000
+
+struct prev_s {
+  derive_t hits;
+  derive_t gets;
+  derive_t incr_hits;
+  derive_t incr_misses;
+  derive_t decr_hits;
+  derive_t decr_misses;
+};
+
+typedef struct prev_s prev_t;
 
 struct memcached_s {
   char *name;
@@ -47,6 +64,8 @@ struct memcached_s {
   char *socket;
   char *connhost;
   char *connport;
+  int fd;
+  prev_t prev;
 };
 typedef struct memcached_s memcached_t;
 
@@ -57,6 +76,12 @@ static void memcached_free(void *arg) {
   if (st == NULL)
     return;
 
+  if (st->fd >= 0) {
+    shutdown(st->fd, SHUT_RDWR);
+    close(st->fd);
+    st->fd = -1;
+  }
+
   sfree(st->name);
   sfree(st->host);
   sfree(st->socket);
@@ -67,13 +92,12 @@ static void memcached_free(void *arg) {
 
 static int memcached_connect_unix(memcached_t *st) {
   struct sockaddr_un serv_addr = {0};
-  int fd;
 
   serv_addr.sun_family = AF_UNIX;
   sstrncpy(serv_addr.sun_path, st->socket, sizeof(serv_addr.sun_path));
 
   /* create our socket descriptor */
-  fd = socket(AF_UNIX, SOCK_STREAM, 0);
+  int fd = socket(AF_UNIX, SOCK_STREAM, 0);
   if (fd < 0) {
     char errbuf[1024];
     ERROR("memcached plugin: memcached_connect_unix: socket(2) failed: %s",
@@ -86,7 +110,15 @@ static int memcached_connect_unix(memcached_t *st) {
   if (status != 0) {
     shutdown(fd, SHUT_RDWR);
     close(fd);
-    fd = -1;
+    return -1;
+  }
+
+  /* switch to non-blocking mode */
+  int flags = fcntl(fd, F_GETFL);
+  status = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+  if (status != 0) {
+    close(fd);
+    return -1;
   }
 
   return fd;
@@ -94,14 +126,13 @@ static int memcached_connect_unix(memcached_t *st) {
 
 static int memcached_connect_inet(memcached_t *st) {
   struct addrinfo *ai_list;
-  int status;
   int fd = -1;
 
   struct addrinfo ai_hints = {.ai_family = AF_UNSPEC,
                               .ai_flags = AI_ADDRCONFIG,
                               .ai_socktype = SOCK_STREAM};
 
-  status = getaddrinfo(st->connhost, st->connport, &ai_hints, &ai_list);
+  int status = getaddrinfo(st->connhost, st->connport, &ai_hints, &ai_list);
   if (status != 0) {
     char errbuf[1024];
     ERROR("memcached plugin: memcached_connect_inet: "
@@ -124,16 +155,47 @@ static int memcached_connect_inet(memcached_t *st) {
       continue;
     }
 
+    /* switch socket to non-blocking mode */
+    int flags = fcntl(fd, F_GETFL);
+    status = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+    if (status != 0) {
+      close(fd);
+      fd = -1;
+      continue;
+    }
+
     /* connect to the memcached daemon */
     status = (int)connect(fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
-    if (status != 0) {
+    if (status != 0 && errno != EINPROGRESS) {
       shutdown(fd, SHUT_RDWR);
       close(fd);
       fd = -1;
       continue;
     }
 
-    /* A socket could be opened and connecting succeeded. We're done. */
+    /* Wait until connection establishes */
+    struct pollfd pollfd = {
+        .fd = fd, .events = POLLOUT,
+    };
+    do
+      status = poll(&pollfd, 1, MEMCACHED_CONNECT_TIMEOUT);
+    while (status < 0 && errno == EINTR);
+    if (status <= 0) {
+      close(fd);
+      fd = -1;
+      continue;
+    }
+
+    /* Check if all is good */
+    int socket_error;
+    status = getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&socket_error,
+                        &(socklen_t){sizeof(socket_error)});
+    if (status != 0 || socket_error != 0) {
+      close(fd);
+      fd = -1;
+      continue;
+    }
+    /* A socket is opened and connection succeeded. We're done. */
     break;
   }
 
@@ -141,32 +203,55 @@ static int memcached_connect_inet(memcached_t *st) {
   return fd;
 } /* int memcached_connect_inet */
 
-static int memcached_connect(memcached_t *st) {
+static void memcached_connect(memcached_t *st) {
+  if (st->fd >= 0)
+    return;
+
   if (st->socket != NULL)
-    return memcached_connect_unix(st);
+    st->fd = memcached_connect_unix(st);
   else
-    return memcached_connect_inet(st);
+    st->fd = memcached_connect_inet(st);
+
+  if (st->fd >= 0)
+    INFO("memcached plugin: Instance \"%s\": connection established.",
+         st->name);
 }
 
 static int memcached_query_daemon(char *buffer, size_t buffer_size,
                                   memcached_t *st) {
-  int fd, status;
+  int status;
   size_t buffer_fill;
 
-  fd = memcached_connect(st);
-  if (fd < 0) {
+  memcached_connect(st);
+  if (st->fd < 0) {
     ERROR("memcached plugin: Instance \"%s\" could not connect to daemon.",
           st->name);
     return -1;
   }
 
-  status = (int)swrite(fd, "stats\r\n", strlen("stats\r\n"));
+  struct pollfd pollfd = {
+      .fd = st->fd, .events = POLLOUT,
+  };
+
+  do
+    status = poll(&pollfd, 1, MEMCACHED_IO_TIMEOUT);
+  while (status < 0 && errno == EINTR);
+
+  if (status <= 0) {
+    ERROR("memcached plugin: poll() failed for write() call.");
+    close(st->fd);
+    st->fd = -1;
+    return -1;
+  }
+
+  status = (int)swrite(st->fd, "stats\r\n", strlen("stats\r\n"));
   if (status != 0) {
     char errbuf[1024];
-    ERROR("memcached plugin: write(2) failed: %s",
+    ERROR("memcached plugin: Instance \"%s\": write(2) failed: %s", st->name,
           sstrerror(errno, errbuf, sizeof(errbuf)));
-    shutdown(fd, SHUT_RDWR);
-    close(fd);
+    shutdown(st->fd, SHUT_RDWR);
+    close(st->fd);
+    st->fd = -1;
     return -1;
   }
 
@@ -174,27 +259,48 @@ static int memcached_query_daemon(char *buffer, size_t buffer_size,
   memset(buffer, 0, buffer_size);
 
   buffer_fill = 0;
-  while ((status = (int)recv(fd, buffer + buffer_fill,
-                             buffer_size - buffer_fill, /* flags = */ 0)) !=
-         0) {
+  pollfd.events = POLLIN;
+  while (1) {
+    do
+      status = poll(&pollfd, 1, MEMCACHED_IO_TIMEOUT);
+    while (status < 0 && errno == EINTR);
+
+    if (status <= 0) {
+      ERROR("memcached plugin: Instance \"%s\": Timeout reading from socket",
+            st->name);
+      close(st->fd);
+      st->fd = -1;
+      return -1;
+    }
+
+    do
+      status = (int)recv(st->fd, buffer + buffer_fill,
+                         buffer_size - buffer_fill, /* flags = */ 0);
+    while (status < 0 && errno == EINTR);
+
     char const end_token[5] = {'E', 'N', 'D', '\r', '\n'};
     if (status < 0) {
       char errbuf[1024];
 
-      if ((errno == EAGAIN) || (errno == EINTR))
+      if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
         continue;
 
-      ERROR("memcached: Error reading from socket: %s",
-            sstrerror(errno, errbuf, sizeof(errbuf)));
-      shutdown(fd, SHUT_RDWR);
-      close(fd);
+      ERROR("memcached plugin: Instance \"%s\": Error reading from socket: %s",
+            st->name, sstrerror(errno, errbuf, sizeof(errbuf)));
+      shutdown(st->fd, SHUT_RDWR);
+      close(st->fd);
+      st->fd = -1;
       return -1;
     }
 
     buffer_fill += (size_t)status;
     if (buffer_fill > buffer_size) {
       buffer_fill = buffer_size;
-      WARNING("memcached plugin: Message was truncated.");
+      WARNING("memcached plugin: Instance \"%s\": Message was truncated.",
+              st->name);
+      shutdown(st->fd, SHUT_RDWR);
+      close(st->fd);
+      st->fd = -1;
       break;
     }
 
@@ -206,12 +312,11 @@ static int memcached_query_daemon(char *buffer, size_t buffer_size,
 
   status = 0;
   if (buffer_fill == 0) {
-    WARNING("memcached plugin: No data returned by memcached.");
+    WARNING("memcached plugin: Instance \"%s\": No data returned by memcached.",
+            st->name);
     status = -1;
   }
 
-  shutdown(fd, SHUT_RDWR);
-  close(fd);
   return status;
 } /* int memcached_query_daemon */
 
@@ -285,29 +390,74 @@ static void submit_gauge2(const char *type, const char *type_inst,
   plugin_dispatch_values(&vl);
 }
 
+static gauge_t calculate_ratio_percent(derive_t part, derive_t total,
+                                       derive_t *prev_part,
+                                       derive_t *prev_total) {
+  if ((*prev_part == 0) || (*prev_total == 0) || (part < *prev_part) ||
+      (total < *prev_total)) {
+    *prev_part = part;
+    *prev_total = total;
+    return NAN;
+  }
+
+  derive_t num = part - *prev_part;
+  derive_t denom = total - *prev_total;
+
+  *prev_part = part;
+  *prev_total = total;
+
+  if (denom == 0)
+    return NAN;
+
+  if (num == 0)
+    return 0;
+
+  return 100.0 * (gauge_t)num / (gauge_t)denom;
+}
+
+static gauge_t calculate_ratio_percent2(derive_t part1, derive_t part2,
+                                        derive_t *prev1, derive_t *prev2) {
+  if ((*prev1 == 0) || (*prev2 == 0) || (part1 < *prev1) || (part2 < *prev2)) {
+    *prev1 = part1;
+    *prev2 = part2;
+    return NAN;
+  }
+
+  derive_t num = part1 - *prev1;
+  derive_t denom = part2 - *prev2 + num;
+
+  *prev1 = part1;
+  *prev2 = part2;
+
+  if (denom == 0)
+    return NAN;
+
+  if (num == 0)
+    return 0;
+
+  return 100.0 * (gauge_t)num / (gauge_t)denom;
+}
+
 static int memcached_read(user_data_t *user_data) {
   char buf[4096];
   char *fields[3];
-  char *ptr;
   char *line;
-  char *saveptr;
-  int fields_num;
-
-  gauge_t bytes_used = NAN;
-  gauge_t bytes_total = NAN;
-  gauge_t hits = NAN;
-  gauge_t gets = NAN;
-  gauge_t incr_hits = NAN;
-  derive_t incr = 0;
-  gauge_t decr_hits = NAN;
-  derive_t decr = 0;
+
+  derive_t bytes_used = 0;
+  derive_t bytes_total = 0;
+  derive_t get_hits = 0;
+  derive_t cmd_get = 0;
+  derive_t incr_hits = 0;
+  derive_t incr_misses = 0;
+  derive_t decr_hits = 0;
+  derive_t decr_misses = 0;
   derive_t rusage_user = 0;
   derive_t rusage_syst = 0;
   derive_t octets_rx = 0;
   derive_t octets_tx = 0;
 
-  memcached_t *st;
-  st = user_data->data;
+  memcached_t *st = user_data->data;
+  prev_t *prev = &st->prev;
 
   /* get data from daemon */
   if (memcached_query_daemon(buf, sizeof(buf), st) < 0) {
@@ -317,18 +467,15 @@ static int memcached_read(user_data_t *user_data) {
 #define FIELD_IS(cnst)                                                         \
   (((sizeof(cnst) - 1) == name_len) && (strcmp(cnst, fields[1]) == 0))
 
-  ptr = buf;
-  saveptr = NULL;
+  char *ptr = buf;
+  char *saveptr = NULL;
   while ((line = strtok_r(ptr, "\n\r", &saveptr)) != NULL) {
-    int name_len;
-
     ptr = NULL;
 
-    fields_num = strsplit(line, fields, 3);
-    if (fields_num != 3)
+    if (strsplit(line, fields, 3) != 3)
       continue;
 
-    name_len = strlen(fields[1]);
+    int name_len = strlen(fields[1]);
     if (name_len == 0)
       continue;
 
@@ -341,9 +488,10 @@ static int memcached_read(user_data_t *user_data) {
      * CPU time consumed by the memcached process
      */
     if (FIELD_IS("rusage_user")) {
-      rusage_user = atoll(fields[2]);
+      /* Convert to useconds */
+      rusage_user = atof(fields[2]) * 1000000;
     } else if (FIELD_IS("rusage_system")) {
-      rusage_syst = atoll(fields[2]);
+      rusage_syst = atof(fields[2]) * 1000000;
     }
 
     /*
@@ -364,9 +512,9 @@ static int memcached_read(user_data_t *user_data) {
      * Number of bytes used and available (total - used)
      */
     else if (FIELD_IS("bytes")) {
-      bytes_used = atof(fields[2]);
+      bytes_used = atoll(fields[2]);
     } else if (FIELD_IS("limit_maxbytes")) {
-      bytes_total = atof(fields[2]);
+      bytes_total = atoll(fields[2]);
     }
 
     /*
@@ -375,7 +523,14 @@ static int memcached_read(user_data_t *user_data) {
     else if (FIELD_IS("curr_connections")) {
       submit_gauge("memcached_connections", "current", atof(fields[2]), st);
     } else if (FIELD_IS("listen_disabled_num")) {
-      submit_derive("connections", "listen_disabled", atof(fields[2]), st);
+      submit_derive("connections", "listen_disabled", atoll(fields[2]), st);
+    }
+    /*
+     * Total number of connections opened since the server started running
+     * Report this as connection rate.
+     */
+    else if (FIELD_IS("total_connections")) {
+      submit_derive("connections", "opened", atoll(fields[2]), st);
     }
 
     /*
@@ -385,30 +540,24 @@ static int memcached_read(user_data_t *user_data) {
       const char *name = fields[1] + 4;
       submit_derive("memcached_command", name, atoll(fields[2]), st);
       if (strcmp(name, "get") == 0)
-        gets = atof(fields[2]);
+        cmd_get = atoll(fields[2]);
     }
 
     /*
      * Increment/Decrement
      */
     else if (FIELD_IS("incr_misses")) {
-      derive_t incr_count = atoll(fields[2]);
-      submit_derive("memcached_ops", "incr_misses", incr_count, st);
-      incr += incr_count;
+      incr_misses = atoll(fields[2]);
+      submit_derive("memcached_ops", "incr_misses", incr_misses, st);
     } else if (FIELD_IS("incr_hits")) {
-      derive_t incr_count = atoll(fields[2]);
-      submit_derive("memcached_ops", "incr_hits", incr_count, st);
-      incr_hits = atof(fields[2]);
-      incr += incr_count;
+      incr_hits = atoll(fields[2]);
+      submit_derive("memcached_ops", "incr_hits", incr_hits, st);
     } else if (FIELD_IS("decr_misses")) {
-      derive_t decr_count = atoll(fields[2]);
-      submit_derive("memcached_ops", "decr_misses", decr_count, st);
-      decr += decr_count;
+      decr_misses = atoll(fields[2]);
+      submit_derive("memcached_ops", "decr_misses", decr_misses, st);
     } else if (FIELD_IS("decr_hits")) {
-      derive_t decr_count = atoll(fields[2]);
-      submit_derive("memcached_ops", "decr_hits", decr_count, st);
-      decr_hits = atof(fields[2]);
-      decr += decr_count;
+      decr_hits = atoll(fields[2]);
+      submit_derive("memcached_ops", "decr_hits", decr_hits, st);
     }
 
     /*
@@ -418,8 +567,8 @@ static int memcached_read(user_data_t *user_data) {
      * - evictions
      */
     else if (FIELD_IS("get_hits")) {
-      submit_derive("memcached_ops", "hits", atoll(fields[2]), st);
-      hits = atof(fields[2]);
+      get_hits = atoll(fields[2]);
+      submit_derive("memcached_ops", "hits", get_hits, st);
     } else if (FIELD_IS("get_misses")) {
       submit_derive("memcached_ops", "misses", atoll(fields[2]), st);
     } else if (FIELD_IS("evictions")) {
@@ -440,7 +589,7 @@ static int memcached_read(user_data_t *user_data) {
     }
   } /* while ((line = strtok_r (ptr, "\n\r", &saveptr)) != NULL) */
 
-  if (!isnan(bytes_used) && !isnan(bytes_total) && (bytes_used <= bytes_total))
+  if ((bytes_total > 0) && (bytes_used <= bytes_total))
     submit_gauge2("df", "cache", bytes_used, bytes_total - bytes_used, st);
 
   if ((rusage_user != 0) || (rusage_syst != 0))
@@ -449,37 +598,30 @@ static int memcached_read(user_data_t *user_data) {
   if ((octets_rx != 0) || (octets_tx != 0))
     submit_derive2("memcached_octets", NULL, octets_rx, octets_tx, st);
 
-  if (!isnan(gets) && !isnan(hits)) {
-    gauge_t rate = NAN;
-
-    if (gets != 0.0)
-      rate = 100.0 * hits / gets;
-
-    submit_gauge("percent", "hitratio", rate, st);
+  if ((cmd_get != 0) && (get_hits != 0)) {
+    gauge_t ratio =
+        calculate_ratio_percent(get_hits, cmd_get, &prev->hits, &prev->gets);
+    submit_gauge("percent", "hitratio", ratio, st);
   }
 
-  if (!isnan(incr_hits) && incr != 0) {
-    gauge_t incr_rate = 100.0 * incr_hits / incr;
-    submit_gauge("percent", "incr_hitratio", incr_rate, st);
-    submit_derive("memcached_ops", "incr", incr, st);
+  if ((incr_hits != 0) && (incr_misses != 0)) {
+    gauge_t ratio = calculate_ratio_percent2(
+        incr_hits, incr_misses, &prev->incr_hits, &prev->incr_misses);
+    submit_gauge("percent", "incr_hitratio", ratio, st);
+    submit_derive("memcached_ops", "incr", incr_hits + incr_misses, st);
   }
 
-  if (!isnan(decr_hits) && decr != 0) {
-    gauge_t decr_rate = 100.0 * decr_hits / decr;
-    submit_gauge("percent", "decr_hitratio", decr_rate, st);
-    submit_derive("memcached_ops", "decr", decr, st);
+  if ((decr_hits != 0) && (decr_misses != 0)) {
+    gauge_t ratio = calculate_ratio_percent2(
+        decr_hits, decr_misses, &prev->decr_hits, &prev->decr_misses);
+    submit_gauge("percent", "decr_hitratio", ratio, st);
+    submit_derive("memcached_ops", "decr", decr_hits + decr_misses, st);
   }
 
   return 0;
 } /* int memcached_read */
 
-static int memcached_add_read_callback(memcached_t *st) {
-  char callback_name[3 * DATA_MAX_NAME_LEN];
-  int status;
-
-  snprintf(callback_name, sizeof(callback_name), "memcached/%s",
-           (st->name != NULL) ? st->name : "__legacy__");
-
+static int memcached_set_defaults(memcached_t *st) {
   /* If no <Address> used then:
    * - Connect to the destination specified by <Host>, if present.
    *   If not, use the default address.
@@ -516,7 +658,28 @@ static int memcached_add_read_callback(memcached_t *st) {
   assert(st->connhost != NULL);
   assert(st->connport != NULL);
 
-  status = plugin_register_complex_read(
+  st->prev.hits = 0;
+  st->prev.gets = 0;
+  st->prev.incr_hits = 0;
+  st->prev.incr_misses = 0;
+  st->prev.decr_hits = 0;
+  st->prev.decr_misses = 0;
+
+  return 0;
+} /* int memcached_set_defaults */
+
+static int memcached_add_read_callback(memcached_t *st) {
+  char callback_name[3 * DATA_MAX_NAME_LEN];
+
+  if (memcached_set_defaults(st) != 0) {
+    memcached_free(st);
+    return -1;
+  }
+
+  snprintf(callback_name, sizeof(callback_name), "memcached/%s",
+           (st->name != NULL) ? st->name : "__legacy__");
+
+  return plugin_register_complex_read(
       /* group = */ "memcached",
       /* name      = */ callback_name,
       /* callback  = */ memcached_read,
@@ -524,8 +687,6 @@ static int memcached_add_read_callback(memcached_t *st) {
       &(user_data_t){
           .data = st, .free_func = memcached_free,
       });
-
-  return status;
 } /* int memcached_add_read_callback */
 
 /* Configuration handling functiions
@@ -538,13 +699,12 @@ static int memcached_add_read_callback(memcached_t *st) {
  * </Plugin>
  */
 static int config_add_instance(oconfig_item_t *ci) {
-  memcached_t *st;
   int status = 0;
 
   /* Disable automatic generation of default instance in the init callback. */
   memcached_have_instances = 1;
 
-  st = calloc(1, sizeof(*st));
+  memcached_t *st = calloc(1, sizeof(*st));
   if (st == NULL) {
     ERROR("memcached plugin: calloc failed.");
     return ENOMEM;
@@ -556,6 +716,8 @@ static int config_add_instance(oconfig_item_t *ci) {
   st->connhost = NULL;
   st->connport = NULL;
 
+  st->fd = -1;
+
   if (strcasecmp(ci->key, "Instance") == 0)
     status = cf_util_get_string(ci, &st->name);
 
@@ -584,19 +746,15 @@ static int config_add_instance(oconfig_item_t *ci) {
       break;
   }
 
-  if (status == 0)
-    status = memcached_add_read_callback(st);
-
   if (status != 0) {
     memcached_free(st);
     return -1;
   }
 
-  return 0;
-}
+  return memcached_add_read_callback(st);
+} /* int config_add_instance */
 
 static int memcached_config(oconfig_item_t *ci) {
-  int status = 0;
   _Bool have_instance_block = 0;
 
   for (int i = 0; i < ci->children_num; i++) {
@@ -617,18 +775,16 @@ static int memcached_config(oconfig_item_t *ci) {
               child->key);
   } /* for (ci->children) */
 
-  return status;
-}
+  return 0;
+} /* int memcached_config */
 
 static int memcached_init(void) {
-  memcached_t *st;
-  int status;
 
   if (memcached_have_instances)
     return 0;
 
   /* No instances were configured, lets start a default instance. */
-  st = calloc(1, sizeof(*st));
+  memcached_t *st = calloc(1, sizeof(*st));
   if (st == NULL)
     return ENOMEM;
   st->name = NULL;
@@ -637,11 +793,11 @@ static int memcached_init(void) {
   st->connhost = NULL;
   st->connport = NULL;
 
-  status = memcached_add_read_callback(st);
+  st->fd = -1;
+
+  int status = memcached_add_read_callback(st);
   if (status == 0)
     memcached_have_instances = 1;
-  else
-    memcached_free(st);
 
   return status;
 } /* int memcached_init */
index a7ee433c2279f7e0e4f143bf66287b14f5bafe8f..3f9521d8ab7bcf7efb672a5688db367c0c5cc235 100644 (file)
--- a/src/mic.c
+++ b/src/mic.c
@@ -163,7 +163,6 @@ static void mic_submit_temp(int micnumber, const char *type, gauge_t value) {
   vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  strncpy(vl.host, hostname_g, sizeof(vl.host));
   strncpy(vl.plugin, "mic", sizeof(vl.plugin));
   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%i", micnumber);
   strncpy(vl.type, "temperature", sizeof(vl.type));
@@ -205,7 +204,6 @@ static void mic_submit_cpu(int micnumber, const char *type_instance, int core,
   vl.values = &(value_t){.derive = value};
   vl.values_len = 1;
 
-  strncpy(vl.host, hostname_g, sizeof(vl.host));
   strncpy(vl.plugin, "mic", sizeof(vl.plugin));
   if (core < 0) /* global aggregation */
     snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%i", micnumber);
@@ -259,7 +257,6 @@ static void mic_submit_power(int micnumber, const char *type,
   vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
 
-  strncpy(vl.host, hostname_g, sizeof(vl.host));
   strncpy(vl.plugin, "mic", sizeof(vl.plugin));
   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%i", micnumber);
   strncpy(vl.type, type, sizeof(vl.type));
index 851866b082051d38b9f292de35eae691ed6d7a75..516448557f94e326f934729193c63cd094a6bd58 100644 (file)
@@ -525,10 +525,10 @@ static int mqtt_write(const data_set_t *ds, const value_list_t *vl,
  *   StoreRates true
  *   Retain false
  *   QoS 0
- *   CACert "ca.pem"                   Enables TLS if set
- *   CertificateFile "client-cert.pem"         optional
- *   CertificateKeyFile "client-key.pem"               optional
- *   TLSProtocol "tlsv1.2"             optional
+ *   CACert "ca.pem"                      Enables TLS if set
+ *   CertificateFile "client-cert.pem"   optional
+ *   CertificateKeyFile "client-key.pem"  optional
+ *   TLSProtocol "tlsv1.2"                optional
  * </Publish>
  */
 static int mqtt_config_publisher(oconfig_item_t *ci) {
@@ -624,6 +624,10 @@ static int mqtt_config_publisher(oconfig_item_t *ci) {
  *   User "guest"
  *   Password "secret"
  *   Topic "collectd/#"
+ *   CACert "ca.pem"                      Enables TLS if set
+ *   CertificateFile "client-cert.pem"   optional
+ *   CertificateKeyFile "client-key.pem"  optional
+ *   TLSProtocol "tlsv1.2"                optional
  * </Subscribe>
  */
 static int mqtt_config_subscriber(oconfig_item_t *ci) {
@@ -687,6 +691,16 @@ static int mqtt_config_subscriber(oconfig_item_t *ci) {
       cf_util_get_string(child, &conf->topic);
     else if (strcasecmp("CleanSession", child->key) == 0)
       cf_util_get_boolean(child, &conf->clean_session);
+    else if (strcasecmp("CACert", child->key) == 0)
+      cf_util_get_string(child, &conf->cacertificatefile);
+    else if (strcasecmp("CertificateFile", child->key) == 0)
+      cf_util_get_string(child, &conf->certificatefile);
+    else if (strcasecmp("CertificateKeyFile", child->key) == 0)
+      cf_util_get_string(child, &conf->certificatekeyfile);
+    else if (strcasecmp("TLSProtocol", child->key) == 0)
+      cf_util_get_string(child, &conf->tlsprotocol);
+    else if (strcasecmp("CipherSuite", child->key) == 0)
+      cf_util_get_string(child, &conf->ciphersuite);
     else
       ERROR("mqtt plugin: Unknown config option: %s", child->key);
   }
diff --git a/src/msr-index.h b/src/msr-index.h
new file mode 100644 (file)
index 0000000..2adca57
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Partial header file imported from the linux kernel
+ * (arch/x86/include/asm/msr-index.h)
+ * as it is not provided by the kernel sources anymore
+ *
+ * Only the minimal blocks of macro have been included
+ * ----
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ * ----
+ */
+
+#ifndef _ASM_X86_MSR_INDEX_H
+#define _ASM_X86_MSR_INDEX_H
+
+/*
+ * CPU model specific register (MSR) numbers.
+ *
+ * Do not add new entries to this file unless the definitions are shared
+ * between multiple compilation units.
+ */
+
+/* Intel MSRs. Some also available on other CPUs */
+
+/* C-state Residency Counters */
+#define MSR_PKG_C3_RESIDENCY           0x000003f8
+#define MSR_PKG_C6_RESIDENCY           0x000003f9
+#define MSR_ATOM_PKG_C6_RESIDENCY      0x000003fa
+#define MSR_PKG_C7_RESIDENCY           0x000003fa
+#define MSR_CORE_C3_RESIDENCY          0x000003fc
+#define MSR_CORE_C6_RESIDENCY          0x000003fd
+#define MSR_CORE_C7_RESIDENCY          0x000003fe
+#define MSR_KNL_CORE_C6_RESIDENCY      0x000003ff
+#define MSR_PKG_C2_RESIDENCY           0x0000060d
+#define MSR_PKG_C8_RESIDENCY           0x00000630
+#define MSR_PKG_C9_RESIDENCY           0x00000631
+#define MSR_PKG_C10_RESIDENCY          0x00000632
+
+/* Run Time Average Power Limiting (RAPL) Interface */
+
+#define MSR_RAPL_POWER_UNIT            0x00000606
+
+#define MSR_PKG_POWER_LIMIT            0x00000610
+#define MSR_PKG_ENERGY_STATUS          0x00000611
+#define MSR_PKG_PERF_STATUS            0x00000613
+#define MSR_PKG_POWER_INFO             0x00000614
+
+#define MSR_DRAM_POWER_LIMIT           0x00000618
+#define MSR_DRAM_ENERGY_STATUS         0x00000619
+#define MSR_DRAM_PERF_STATUS           0x0000061b
+#define MSR_DRAM_POWER_INFO            0x0000061c
+
+#define MSR_PP0_POWER_LIMIT            0x00000638
+#define MSR_PP0_ENERGY_STATUS          0x00000639
+#define MSR_PP0_POLICY                 0x0000063a
+#define MSR_PP0_PERF_STATUS            0x0000063b
+
+#define MSR_PP1_POWER_LIMIT            0x00000640
+#define MSR_PP1_ENERGY_STATUS          0x00000641
+#define MSR_PP1_POLICY                 0x00000642
+
+
+
+/* Intel defined MSRs. */
+#define MSR_IA32_TSC                   0x00000010
+#define MSR_SMI_COUNT                  0x00000034
+
+#define MSR_IA32_MPERF                 0x000000e7
+#define MSR_IA32_APERF                 0x000000e8
+
+#define MSR_IA32_THERM_STATUS          0x0000019c
+
+#define MSR_IA32_TEMPERATURE_TARGET    0x000001a2
+
+#define MSR_IA32_PACKAGE_THERM_STATUS          0x000001b1
+
+
+#endif /* _ASM_X86_MSR_INDEX_H */
index 72b0fed91c1f32caab8ec7b2846389d70031b513..fc69e02dd4ac1aff8fa4a19e4e7eb199c1212bca 100644 (file)
@@ -62,7 +62,7 @@ static int multimeter_read_value(double *value) {
       struct timeval time_now;
 
       status = swrite(fd, "D", 1);
-      if (status < 0) {
+      if (status != 0) {
         ERROR("multimeter plugin: swrite failed.");
         return -1;
       }
index 375da84738dbbdec8d39fe177212e3086d4aeada..4e684215ac732d36a593b9d2f870b011f60de707 100644 (file)
@@ -1230,9 +1230,9 @@ static int parse_part_encr_aes256(sockent_t *se, /* {{{ */
                             part_size - buffer_offset,
                             /* in = */ NULL, /* in len = */ 0);
   if (err != 0) {
-    sfree(pea.username);
     ERROR("network plugin: gcry_cipher_decrypt returned: %s. Username: %s",
           gcry_strerror(err), pea.username);
+    sfree(pea.username);
     return -1;
   }
 
@@ -1254,8 +1254,6 @@ static int parse_part_encr_aes256(sockent_t *se, /* {{{ */
   parse_packet(se, buffer + buffer_offset, payload_len, flags | PP_ENCRYPTED,
                pea.username);
 
-  /* XXX: Free pea.username?!? */
-
   /* Update return values */
   *ret_buffer = buffer + part_size;
   *ret_buffer_len = buffer_len - part_size;
@@ -2652,10 +2650,10 @@ static int network_write(const data_set_t *ds, const value_list_t *vl,
 
   pthread_mutex_lock(&send_buffer_lock);
 
-  status = add_to_buffer(send_buffer_ptr,
-                         network_config_packet_size -
-                             (send_buffer_fill + BUFF_SIG_SIZE),
-                         &send_buffer_vl, ds, vl);
+  status =
+      add_to_buffer(send_buffer_ptr, network_config_packet_size -
+                                         (send_buffer_fill + BUFF_SIG_SIZE),
+                    &send_buffer_vl, ds, vl);
   if (status >= 0) {
     /* status == bytes added to the buffer */
     send_buffer_fill += status;
@@ -2666,10 +2664,10 @@ static int network_write(const data_set_t *ds, const value_list_t *vl,
   } else {
     flush_buffer();
 
-    status = add_to_buffer(send_buffer_ptr,
-                           network_config_packet_size -
-                               (send_buffer_fill + BUFF_SIG_SIZE),
-                           &send_buffer_vl, ds, vl);
+    status =
+        add_to_buffer(send_buffer_ptr, network_config_packet_size -
+                                           (send_buffer_fill + BUFF_SIG_SIZE),
+                      &send_buffer_vl, ds, vl);
 
     if (status >= 0) {
       send_buffer_fill += status;
index bbe843845ace33b4fd546407eed64306dcf974bd..a7adc920b1d23cf2075d337a85e1d7120c54f963 100644 (file)
--- a/src/nfs.c
+++ b/src/nfs.c
 #include <kstat.h>
 #endif
 
+static const char *config_keys[] = {"ReportV2", "ReportV3", "ReportV4"};
+static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
+static _Bool report_v2 = 1;
+static _Bool report_v3 = 1;
+static _Bool report_v4 = 1;
+
 /*
 see /proc/net/rpc/nfs
 see http://www.missioncriticallinux.com/orph/NFS-Statistics
@@ -295,6 +301,19 @@ static kstat_t *nfs4_ksp_client;
 static kstat_t *nfs4_ksp_server;
 #endif
 
+static int nfs_config(const char *key, const char *value) {
+  if (strcasecmp(key, "ReportV2") == 0)
+    report_v2 = IS_TRUE(value);
+  else if (strcasecmp(key, "ReportV3") == 0)
+    report_v3 = IS_TRUE(value);
+  else if (strcasecmp(key, "ReportV4") == 0)
+    report_v4 = IS_TRUE(value);
+  else
+    return -1;
+
+  return 0;
+}
+
 #if KERNEL_LINUX
 static int nfs_init(void) { return 0; }
 /* #endif KERNEL_LINUX */
@@ -495,18 +514,18 @@ static void nfs_read_linux(FILE *fh, const char *inst) {
     if (fields_num < 3)
       continue;
 
-    if (strcmp(fields[0], "proc2") == 0) {
+    if (strcmp(fields[0], "proc2") == 0 && report_v2) {
       nfs_submit_fields_safe(/* version = */ 2, inst, fields + 2,
                              (size_t)(fields_num - 2), nfs2_procedures_names,
                              nfs2_procedures_names_num);
-    } else if (strncmp(fields[0], "proc3", 5) == 0) {
+    } else if (strncmp(fields[0], "proc3", 5) == 0 && report_v3) {
       nfs_submit_fields_safe(/* version = */ 3, inst, fields + 2,
                              (size_t)(fields_num - 2), nfs3_procedures_names,
                              nfs3_procedures_names_num);
-    } else if (strcmp(fields[0], "proc4ops") == 0) {
+    } else if (strcmp(fields[0], "proc4ops") == 0 && report_v4) {
       if (inst[0] == 's')
         nfs_submit_nfs4_server(inst, fields + 2, (size_t)(fields_num - 2));
-    } else if (strcmp(fields[0], "proc4") == 0) {
+    } else if (strcmp(fields[0], "proc4") == 0 && report_v4) {
       if (inst[0] == 'c')
         nfs_submit_nfs4_client(inst, fields + 2, (size_t)(fields_num - 2));
     }
@@ -561,24 +580,31 @@ static int nfs_read(void) {
 
 #elif HAVE_LIBKSTAT
 static int nfs_read(void) {
-  nfs_read_kstat(nfs2_ksp_client, /* version = */ 2, "client",
-                 nfs2_procedures_names, nfs2_procedures_names_num);
-  nfs_read_kstat(nfs2_ksp_server, /* version = */ 2, "server",
-                 nfs2_procedures_names, nfs2_procedures_names_num);
-  nfs_read_kstat(nfs3_ksp_client, /* version = */ 3, "client",
-                 nfs3_procedures_names, nfs3_procedures_names_num);
-  nfs_read_kstat(nfs3_ksp_server, /* version = */ 3, "server",
-                 nfs3_procedures_names, nfs3_procedures_names_num);
-  nfs_read_kstat(nfs4_ksp_client, /* version = */ 4, "client",
-                 nfs4_procedures_names, nfs4_procedures_names_num);
-  nfs_read_kstat(nfs4_ksp_server, /* version = */ 4, "server",
-                 nfs4_procedures_names, nfs4_procedures_names_num);
+  if (report_v2) {
+    nfs_read_kstat(nfs2_ksp_client, /* version = */ 2, "client",
+                   nfs2_procedures_names, nfs2_procedures_names_num);
+    nfs_read_kstat(nfs2_ksp_server, /* version = */ 2, "server",
+                   nfs2_procedures_names, nfs2_procedures_names_num);
+  }
+  if (report_v3) {
+    nfs_read_kstat(nfs3_ksp_client, /* version = */ 3, "client",
+                   nfs3_procedures_names, nfs3_procedures_names_num);
+    nfs_read_kstat(nfs3_ksp_server, /* version = */ 3, "server",
+                   nfs3_procedures_names, nfs3_procedures_names_num);
+  }
+  if (report_v4) {
+    nfs_read_kstat(nfs4_ksp_client, /* version = */ 4, "client",
+                   nfs4_procedures_names, nfs4_procedures_names_num);
+    nfs_read_kstat(nfs4_ksp_server, /* version = */ 4, "server",
+                   nfs4_procedures_names, nfs4_procedures_names_num);
+  }
 
   return 0;
 }
 #endif /* HAVE_LIBKSTAT */
 
 void module_register(void) {
+  plugin_register_config("nfs", nfs_config, config_keys, config_keys_num);
   plugin_register_init("nfs", nfs_init);
   plugin_register_read("nfs", nfs_read);
 } /* void module_register */
index a19d05c106a13fc858abf09dc485ed13acd4effa..0faf2a2a1a461c427b77177926989286e6e25eb4 100644 (file)
@@ -669,7 +669,7 @@ static int ntpd_send_request(int req_code, int req_items, int req_size,
         (void *)req_data);
 
   status = swrite(sd, (const char *)&req, REQ_LEN_NOMAC);
-  if (status < 0) {
+  if (status != 0) {
     DEBUG("`swrite' failed. Closing socket #%i", sd);
     close(sd);
     sock_descr = sd = -1;
@@ -846,9 +846,9 @@ static int ntpd_read(void) {
   }
 
   /* kerninfo -> estimated error */
-  offset_loop = scale_loop * ((gauge_t)ntohl(ik->offset));
+  offset_loop = (gauge_t)((int32_t)ntohl(ik->offset) * scale_loop);
   freq_loop = ntpd_read_fp(ik->freq);
-  offset_error = scale_error * ((gauge_t)ntohl(ik->esterror));
+  offset_error = (gauge_t)((int32_t)ntohl(ik->esterror) * scale_error);
 
   DEBUG("info_kernel:\n"
         "  pll offset    = %.8g\n"
index 2bd4019bb5efdb9b5e126cf01ef890fed63cc8e9..58c7d797c4d4d8b93083c97e1223064eb246d892 100644 (file)
--- a/src/nut.c
+++ b/src/nut.c
@@ -96,13 +96,13 @@ static int nut_add_ups(const char *name) {
   cb_name = ssnprintf_alloc("nut/%s", name);
 
   status = plugin_register_complex_read(
-    /* group = */ "nut",
-    /* name      = */ cb_name,
-    /* callback  = */ nut_read,
-    /* interval  = */ 0,
-    /* user_data = */ &(user_data_t){
-                        .data = ups, .free_func = free_nut_ups_t,
-                      });
+      /* group     = */ "nut",
+      /* name      = */ cb_name,
+      /* callback  = */ nut_read,
+      /* interval  = */ 0,
+      /* user_data = */ &(user_data_t){
+          .data = ups, .free_func = free_nut_ups_t,
+      });
 
   sfree(cb_name);
 
@@ -191,10 +191,8 @@ static void nut_submit(nut_ups_t *ups, const char *type,
 
   vl.values = &(value_t){.gauge = value};
   vl.values_len = 1;
-  sstrncpy(vl.host,
-           (strcasecmp(ups->hostname, "localhost") == 0) ? hostname_g
-                                                         : ups->hostname,
-           sizeof(vl.host));
+  if (strcasecmp(ups->hostname, "localhost") != 0)
+    sstrncpy(vl.host, ups->hostname, sizeof(vl.host));
   sstrncpy(vl.plugin, "nut", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, ups->upsname, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, type, sizeof(vl.type));
@@ -211,8 +209,8 @@ static int nut_connect(nut_ups_t *ups) {
   tv.tv_sec = connect_timeout / 1000;
   tv.tv_usec = connect_timeout % 1000;
 
-  status = upscli_tryconnect(ups->conn, ups->hostname, ups->port, ssl_flags,
-                             &tv);
+  status =
+      upscli_tryconnect(ups->conn, ups->hostname, ups->port, ssl_flags, &tv);
 #else /* #if HAVE_UPSCLI_TRYCONNECT */
   status = upscli_connect(ups->conn, ups->hostname, ups->port, ssl_flags);
 #endif
index b3fcb10fcbef13c1512fa83a577d84152ff911a5..afe2479e4374792ddab8e024b1c797ea93ddb80a 100644 (file)
@@ -47,7 +47,6 @@ struct cldap_s /* {{{ */
   char *password;
   char *cacert;
   char *host;
-  int state;
   _Bool starttls;
   int timeout;
   char *url;
@@ -58,11 +57,10 @@ struct cldap_s /* {{{ */
 };
 typedef struct cldap_s cldap_t; /* }}} */
 
-static cldap_t **databases = NULL;
-static size_t databases_num = 0;
-
-static void cldap_free(cldap_t *st) /* {{{ */
+static void cldap_free(void *arg) /* {{{ */
 {
+  cldap_t *st = arg;
+
   if (st == NULL)
     return;
 
@@ -73,32 +71,30 @@ static void cldap_free(cldap_t *st) /* {{{ */
   sfree(st->name);
   sfree(st->url);
   if (st->ld)
-    ldap_memfree(st->ld);
+    ldap_unbind_ext_s(st->ld, NULL, NULL);
+
   sfree(st);
 } /* }}} void cldap_free */
 
 /* initialize ldap for each host */
 static int cldap_init_host(cldap_t *st) /* {{{ */
 {
-  LDAP *ld;
   int rc;
 
-  if (st->state && st->ld) {
+  if (st->ld) {
     DEBUG("openldap plugin: Already connected to %s", st->url);
     return 0;
   }
 
-  rc = ldap_initialize(&ld, st->url);
+  rc = ldap_initialize(&st->ld, st->url);
   if (rc != LDAP_SUCCESS) {
     ERROR("openldap plugin: ldap_initialize failed: %s", ldap_err2string(rc));
-    st->state = 0;
-    if (ld != NULL)
-      ldap_unbind_ext_s(ld, NULL, NULL);
+    if (st->ld != NULL)
+      ldap_unbind_ext_s(st->ld, NULL, NULL);
+    st->ld = NULL;
     return (-1);
   }
 
-  st->ld = ld;
-
   ldap_set_option(st->ld, LDAP_OPT_PROTOCOL_VERSION, &st->version);
 
   ldap_set_option(st->ld, LDAP_OPT_TIMEOUT,
@@ -115,13 +111,12 @@ static int cldap_init_host(cldap_t *st) /* {{{ */
   }
 
   if (st->starttls != 0) {
-    rc = ldap_start_tls_s(ld, NULL, NULL);
+    rc = ldap_start_tls_s(st->ld, NULL, NULL);
     if (rc != LDAP_SUCCESS) {
       ERROR("openldap plugin: Failed to start tls on %s: %s", st->url,
             ldap_err2string(rc));
-      st->state = 0;
-      if (st->ld != NULL)
-        ldap_unbind_ext_s(st->ld, NULL, NULL);
+      ldap_unbind_ext_s(st->ld, NULL, NULL);
+      st->ld = NULL;
       return (-1);
     }
   }
@@ -140,13 +135,11 @@ static int cldap_init_host(cldap_t *st) /* {{{ */
   if (rc != LDAP_SUCCESS) {
     ERROR("openldap plugin: Failed to bind to %s: %s", st->url,
           ldap_err2string(rc));
-    st->state = 0;
-    if (st->ld != NULL)
-      ldap_unbind_ext_s(st->ld, NULL, NULL);
+    ldap_unbind_ext_s(st->ld, NULL, NULL);
+    st->ld = NULL;
     return (-1);
   } else {
     DEBUG("openldap plugin: Successfully connected to %s", st->url);
-    st->state = 1;
     return 0;
   }
 } /* }}} static cldap_init_host */
@@ -216,9 +209,8 @@ static int cldap_read_host(user_data_t *ud) /* {{{ */
   if (rc != LDAP_SUCCESS) {
     ERROR("openldap plugin: Failed to execute search: %s", ldap_err2string(rc));
     ldap_msgfree(result);
-    st->state = 0;
-    if (st->ld != NULL)
-      ldap_unbind_ext_s(st->ld, NULL, NULL);
+    ldap_unbind_ext_s(st->ld, NULL, NULL);
+    st->ld = NULL;
     return (-1);
   }
 
@@ -463,42 +455,24 @@ static int cldap_config_add(oconfig_item_t *ci) /* {{{ */
     ldap_free_urldesc(ludpp);
   }
 
-  if (status == 0) {
-    cldap_t **temp;
-
-    temp = (cldap_t **)realloc(databases,
-                               sizeof(*databases) * (databases_num + 1));
-
-    if (temp == NULL) {
-      ERROR("openldap plugin: realloc failed");
-      status = -1;
-    } else {
-      char callback_name[3 * DATA_MAX_NAME_LEN] = {0};
-
-      databases = temp;
-      databases[databases_num] = st;
-      databases_num++;
-
-      snprintf(callback_name, sizeof(callback_name), "openldap/%s/%s",
-               (st->host != NULL) ? st->host : hostname_g,
-               (st->name != NULL) ? st->name : "default");
-
-      status = plugin_register_complex_read(/* group = */ NULL,
-                                            /* name      = */ callback_name,
-                                            /* callback  = */ cldap_read_host,
-                                            /* interval  = */ 0,
-                                            &(user_data_t){
-                                                .data = st,
-                                            });
-    }
-  }
-
   if (status != 0) {
     cldap_free(st);
     return -1;
   }
 
-  return 0;
+  char callback_name[3 * DATA_MAX_NAME_LEN] = {0};
+
+  snprintf(callback_name, sizeof(callback_name), "openldap/%s/%s",
+           (st->host != NULL) ? st->host : hostname_g,
+           (st->name != NULL) ? st->name : "default");
+
+  return plugin_register_complex_read(/* group = */ NULL,
+                                      /* name      = */ callback_name,
+                                      /* callback  = */ cldap_read_host,
+                                      /* interval  = */ 0,
+                                      &(user_data_t){
+                                          .data = st, .free_func = cldap_free,
+                                      });
 } /* }}} int cldap_config_add */
 
 static int cldap_config(oconfig_item_t *ci) /* {{{ */
@@ -532,22 +506,10 @@ static int cldap_init(void) /* {{{ */
   return 0;
 } /* }}} int cldap_init */
 
-static int cldap_shutdown(void) /* {{{ */
-{
-  for (size_t i = 0; i < databases_num; i++)
-    if (databases[i]->ld != NULL)
-      ldap_unbind_ext_s(databases[i]->ld, NULL, NULL);
-  sfree(databases);
-  databases_num = 0;
-
-  return 0;
-} /* }}} int cldap_shutdown */
-
 void module_register(void) /* {{{ */
 {
   plugin_register_complex_config("openldap", cldap_config);
   plugin_register_init("openldap", cldap_init);
-  plugin_register_shutdown("openldap", cldap_shutdown);
 } /* }}} void module_register */
 
 #if defined(__APPLE__)
index 143a770b031420e5beaa48e1215476ca943d85b4..a98649b6de3fbb9c76917464735e3e7555e72e8c 100644 (file)
@@ -23,6 +23,7 @@
  *   Florian octo Forster <octo at collectd.org>
  *   Marco Chiappero <marco at absence.it>
  *   Fabian Schuh <mail at xeroc.org>
+ *   Pavel Rochnyak <pavel2000 ngs.ru>
  **/
 
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
 
-#define V1STRING                                                               \
+/**
+ * There is two main kinds of OpenVPN status file:
+ * - for 'single' mode (point-to-point or client mode)
+ * - for 'multi' mode  (server with multiple clients)
+ *
+ * For 'multi' there is 3 versions of status file format:
+ * - version 1 - First version of status file: without line type tokens,
+ *   comma delimited for easy machine parsing. Currently used by default.
+ *   Added in openvpn-2.0-beta3.
+ * - version 2 - with line type tokens, with 'HEADER' line type, uses comma
+ *   as a delimiter.
+ *   Added in openvpn-2.0-beta15.
+ * - version 3 - The only difference from version 2 is delimiter: in version 3
+ *   tabs are used instead of commas. Set of fields is the same.
+ *   Added in openvpn-2.1_rc14.
+ *
+ * For versions 2/3 there may be different sets of fields in different
+ * OpenVPN versions.
+ *
+ * Versions 2.0, 2.1, 2.2:
+ * Common Name,Real Address,Virtual Address,
+ * Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t)
+ *
+ * Version 2.3:
+ * Common Name,Real Address,Virtual Address,
+ * Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username
+ *
+ * Version 2.4:
+ * Common Name,Real Address,Virtual Address,Virtual IPv6 Address,
+ * Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username,
+ * Client ID,Peer ID
+ *
+ * Current Collectd code tries to handle changes in this field set,
+ * if they are backward-compatible.
+ **/
+
+#define TITLE_SINGLE "OpenVPN STATISTICS\n"
+#define TITLE_V1 "OpenVPN CLIENT LIST\n"
+#define TITLE_V2 "TITLE"
+
+#define V1HEADER                                                               \
   "Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since\n"
-#define V2STRING                                                               \
-  "HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes "         \
-  "Received,Bytes Sent,Connected Since,Connected Since (time_t)\n"
-#define V3STRING                                                               \
-  "HEADER CLIENT_LIST Common Name Real Address Virtual Address Bytes "         \
-  "Received Bytes Sent Connected Since Connected Since (time_t)\n"
-#define V4STRING                                                               \
-  "HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes "         \
-  "Received,Bytes Sent,Connected Since,Connected Since (time_t),Username\n"
-#define VSSTRING "OpenVPN STATISTICS\n"
 
 struct vpn_status_s {
   char *file;
-  enum {
-    MULTI1 = 1, /* status-version 1 */
-    MULTI2,     /* status-version 2 */
-    MULTI3,     /* status-version 3 */
-    MULTI4,     /* status-version 4 */
-    SINGLE = 10 /* currently no versions for single mode, maybe in the future */
-  } version;
   char *name;
 };
 typedef struct vpn_status_s vpn_status_t;
 
-static vpn_status_t **vpn_list = NULL;
-static int vpn_num = 0;
-
 static _Bool new_naming_schema = 0;
 static _Bool collect_compression = 1;
 static _Bool collect_user_count = 0;
@@ -71,16 +92,13 @@ static const char *config_keys[] = {
 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
 
 /* Helper function
- * copy-n-pasted from common.c - changed delim to ","  */
+ * copy-n-pasted from common.c - changed delim to ",\t"  */
 static int openvpn_strsplit(char *string, char **fields, size_t size) {
-  size_t i;
-  char *ptr;
-  char *saveptr;
-
-  i = 0;
-  ptr = string;
-  saveptr = NULL;
-  while ((fields[i] = strtok_r(ptr, ",", &saveptr)) != NULL) {
+  size_t i = 0;
+  char *ptr = string;
+  char *saveptr = NULL;
+
+  while ((fields[i] = strtok_r(ptr, ",\t", &saveptr)) != NULL) {
     ptr = NULL;
     i++;
 
@@ -91,6 +109,13 @@ static int openvpn_strsplit(char *string, char **fields, size_t size) {
   return i;
 } /* int openvpn_strsplit */
 
+static void openvpn_free(void *arg) {
+  vpn_status_t *st = arg;
+
+  sfree(st->file);
+  sfree(st);
+} /* void openvpn_free */
+
 /* dispatches number of users */
 static void numusers_submit(const char *pinst, const char *tinst,
                             gauge_t value) {
@@ -159,25 +184,14 @@ static int single_read(const char *name, FILE *fh) {
   char buffer[1024];
   char *fields[4];
   const int max_fields = STATIC_ARRAY_SIZE(fields);
-  int fields_num, read = 0;
-
-  derive_t link_rx, link_tx;
-  derive_t tun_rx, tun_tx;
-  derive_t pre_compress, post_compress;
-  derive_t pre_decompress, post_decompress;
-  derive_t overhead_rx, overhead_tx;
-
-  link_rx = 0;
-  link_tx = 0;
-  tun_rx = 0;
-  tun_tx = 0;
-  pre_compress = 0;
-  post_compress = 0;
-  pre_decompress = 0;
-  post_decompress = 0;
+
+  derive_t link_rx = 0, link_tx = 0;
+  derive_t tun_rx = 0, tun_tx = 0;
+  derive_t pre_compress = 0, post_compress = 0;
+  derive_t pre_decompress = 0, post_decompress = 0;
 
   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
-    fields_num = openvpn_strsplit(buffer, fields, max_fields);
+    int fields_num = openvpn_strsplit(buffer, fields, max_fields);
 
     /* status file is generated by openvpn/sig.c:print_status()
      * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/sig.c
@@ -213,8 +227,9 @@ static int single_read(const char *name, FILE *fh) {
   iostats_submit(name, "traffic", link_rx, link_tx);
 
   /* we need to force this order to avoid negative values with these unsigned */
-  overhead_rx = (((link_rx - pre_decompress) + post_decompress) - tun_rx);
-  overhead_tx = (((link_tx - post_compress) + pre_compress) - tun_tx);
+  derive_t overhead_rx =
+      (((link_rx - pre_decompress) + post_decompress) - tun_rx);
+  derive_t overhead_tx = (((link_tx - post_compress) + pre_compress) - tun_tx);
 
   iostats_submit(name, "overhead", overhead_rx, overhead_tx);
 
@@ -223,17 +238,16 @@ static int single_read(const char *name, FILE *fh) {
     compression_submit(name, "data_out", pre_compress, post_compress);
   }
 
-  read = 1;
-
-  return read;
+  return 0;
 } /* int single_read */
 
 /* for reading status version 1 */
 static int multi1_read(const char *name, FILE *fh) {
   char buffer[1024];
   char *fields[10];
-  int fields_num, found_header = 0;
+  const int max_fields = STATIC_ARRAY_SIZE(fields);
   long long sum_users = 0;
+  _Bool found_header = 0;
 
   /* read the file until the "ROUTING TABLE" line is found (no more info after)
    */
@@ -241,7 +255,7 @@ static int multi1_read(const char *name, FILE *fh) {
     if (strcmp(buffer, "ROUTING TABLE\n") == 0)
       break;
 
-    if (strcmp(buffer, V1STRING) == 0) {
+    if (strcmp(buffer, V1HEADER) == 0) {
       found_header = 1;
       continue;
     }
@@ -251,7 +265,7 @@ static int multi1_read(const char *name, FILE *fh) {
       /* we can't start reading data until this string is found */
       continue;
 
-    fields_num = openvpn_strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
+    int fields_num = openvpn_strsplit(buffer, fields, max_fields);
     if (fields_num < 4)
       continue;
 
@@ -276,325 +290,196 @@ static int multi1_read(const char *name, FILE *fh) {
   }
 
   if (ferror(fh))
-    return 0;
+    return -1;
+
+  if (found_header == 0) {
+    NOTICE("openvpn plugin: Unknown file format in instance %s, please "
+           "report this as bug. Make sure to include "
+           "your status file, so the plugin can "
+           "be adapted.",
+           name);
+    return -1;
+  }
 
   if (collect_user_count)
     numusers_submit(name, name, sum_users);
 
-  return 1;
+  return 0;
 } /* int multi1_read */
 
-/* for reading status version 2 */
+/* for reading status version 2 / version 3
+ * status file is generated by openvpn/multi.c:multi_print_status()
+ * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
+ */
 static int multi2_read(const char *name, FILE *fh) {
   char buffer[1024];
-  char *fields[10];
+  /* OpenVPN-2.4 has 11 fields of data + 2 fields for "HEADER" and "CLIENT_LIST"
+   * So, set array size to 20 elements, to support future extensions.
+   */
+  char *fields[20];
   const int max_fields = STATIC_ARRAY_SIZE(fields);
-  int fields_num, read = 0;
   long long sum_users = 0;
 
-  while (fgets(buffer, sizeof(buffer), fh) != NULL) {
-    fields_num = openvpn_strsplit(buffer, fields, max_fields);
-
-    /* status file is generated by openvpn/multi.c:multi_print_status()
-     * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
-     *
-     * The line we're expecting has 8 fields. We ignore all lines
-     *  with more or less fields.
-     */
-    if (fields_num != 8)
-      continue;
-
-    if (strcmp(fields[0], "CLIENT_LIST") != 0)
-      continue;
-
-    if (collect_user_count)
-    /* If so, sum all users, ignore the individuals*/
-    {
-      sum_users += 1;
-    }
-    if (collect_individual_users) {
-      if (new_naming_schema) {
-        /* plugin inst = file name, type inst = fields[1] */
-        iostats_submit(name,              /* vpn instance */
-                       fields[1],         /* "Common Name" */
-                       atoll(fields[4]),  /* "Bytes Received" */
-                       atoll(fields[5])); /* "Bytes Sent" */
-      } else {
-        /* plugin inst = fields[1], type inst = "" */
-        iostats_submit(fields[1],         /* "Common Name" */
-                       NULL,              /* unused when in multimode */
-                       atoll(fields[4]),  /* "Bytes Received" */
-                       atoll(fields[5])); /* "Bytes Sent" */
-      }
-    }
-
-    read = 1;
-  }
-
-  if (collect_user_count) {
-    numusers_submit(name, name, sum_users);
-    read = 1;
-  }
-
-  return read;
-} /* int multi2_read */
-
-/* for reading status version 3 */
-static int multi3_read(const char *name, FILE *fh) {
-  char buffer[1024];
-  char *fields[15];
-  const int max_fields = STATIC_ARRAY_SIZE(fields);
-  int fields_num, read = 0;
-  long long sum_users = 0;
+  _Bool found_header = 0;
+  int idx_cname = 0;
+  int idx_bytes_recv = 0;
+  int idx_bytes_sent = 0;
+  int columns = 0;
 
   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
-    fields_num = strsplit(buffer, fields, max_fields);
+    int fields_num = openvpn_strsplit(buffer, fields, max_fields);
 
-    /* status file is generated by openvpn/multi.c:multi_print_status()
-     * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
-     *
-     * The line we're expecting has 12 fields. We ignore all lines
-     *  with more or less fields.
-     */
-    if (fields_num != 12) {
-      continue;
-    } else {
-      if (strcmp(fields[0], "CLIENT_LIST") != 0)
+    /* Try to find section header */
+    if (found_header == 0) {
+      if (fields_num < 2)
+        continue;
+      if (strcmp(fields[0], "HEADER") != 0)
+        continue;
+      if (strcmp(fields[1], "CLIENT_LIST") != 0)
         continue;
 
-      if (collect_user_count)
-      /* If so, sum all users, ignore the individuals*/
-      {
-        sum_users += 1;
-      }
-
-      if (collect_individual_users) {
-        if (new_naming_schema) {
-          iostats_submit(name,              /* vpn instance */
-                         fields[1],         /* "Common Name" */
-                         atoll(fields[4]),  /* "Bytes Received" */
-                         atoll(fields[5])); /* "Bytes Sent" */
-        } else {
-          iostats_submit(fields[1],         /* "Common Name" */
-                         NULL,              /* unused when in multimode */
-                         atoll(fields[4]),  /* "Bytes Received" */
-                         atoll(fields[5])); /* "Bytes Sent" */
+      for (int i = 2; i < fields_num; i++) {
+        if (strcmp(fields[i], "Common Name") == 0) {
+          idx_cname = i - 1;
+        } else if (strcmp(fields[i], "Bytes Received") == 0) {
+          idx_bytes_recv = i - 1;
+        } else if (strcmp(fields[i], "Bytes Sent") == 0) {
+          idx_bytes_sent = i - 1;
         }
       }
 
-      read = 1;
-    }
-  }
-
-  if (collect_user_count) {
-    numusers_submit(name, name, sum_users);
-    read = 1;
-  }
+      DEBUG("openvpn plugin: found MULTI v2/v3 HEADER. "
+            "Column idx: cname: %d, bytes_recv: %d, bytes_sent: %d",
+            idx_cname, idx_bytes_recv, idx_bytes_sent);
 
-  return read;
-} /* int multi3_read */
+      if (idx_cname == 0 || idx_bytes_recv == 0 || idx_bytes_sent == 0)
+        break;
 
-/* for reading status version 4 */
-static int multi4_read(const char *name, FILE *fh) {
-  char buffer[1024];
-  char *fields[11];
-  const int max_fields = STATIC_ARRAY_SIZE(fields);
-  int fields_num, read = 0;
-  long long sum_users = 0;
+      /* Data row has 1 field ("HEADER") less than header row */
+      columns = fields_num - 1;
 
-  while (fgets(buffer, sizeof(buffer), fh) != NULL) {
-    fields_num = openvpn_strsplit(buffer, fields, max_fields);
+      found_header = 1;
+      continue;
+    }
 
-    /* status file is generated by openvpn/multi.c:multi_print_status()
-     * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
-     *
-     * The line we're expecting has 9 fields. We ignore all lines
-     *  with more or less fields.
+    /* Header already found. Check if the line is the section data.
+     * If no match, then section was finished and there is no more data.
+     * Empty section is OK too.
      */
-    if (fields_num != 9)
-      continue;
+    if (fields_num == 0 || strcmp(fields[0], "CLIENT_LIST") != 0)
+      break;
 
-    if (strcmp(fields[0], "CLIENT_LIST") != 0)
-      continue;
+    /* Check if the data line fields count matches header line. */
+    if (fields_num != columns) {
+      ERROR("openvpn plugin: File format error in instance %s: Fields count "
+            "mismatch.",
+            name);
+      return -1;
+    }
+
+    DEBUG("openvpn plugin: found MULTI v2/v3 CLIENT_LIST. "
+          "Columns: cname: %s, bytes_recv: %s, bytes_sent: %s",
+          fields[idx_cname], fields[idx_bytes_recv], fields[idx_bytes_sent]);
 
     if (collect_user_count)
-    /* If so, sum all users, ignore the individuals*/
-    {
       sum_users += 1;
-    }
+
     if (collect_individual_users) {
       if (new_naming_schema) {
         /* plugin inst = file name, type inst = fields[1] */
-        iostats_submit(name,              /* vpn instance */
-                       fields[1],         /* "Common Name" */
-                       atoll(fields[4]),  /* "Bytes Received" */
-                       atoll(fields[5])); /* "Bytes Sent" */
+        iostats_submit(name,                           /* vpn instance     */
+                       fields[idx_cname],              /* "Common Name"    */
+                       atoll(fields[idx_bytes_recv]),  /* "Bytes Received" */
+                       atoll(fields[idx_bytes_sent])); /* "Bytes Sent"     */
       } else {
-        /* plugin inst = fields[1], type inst = "" */
-        iostats_submit(fields[1],         /* "Common Name" */
-                       NULL,              /* unused when in multimode */
-                       atoll(fields[4]),  /* "Bytes Received" */
-                       atoll(fields[5])); /* "Bytes Sent" */
+        /* plugin inst = fields[idx_cname], type inst = "" */
+        iostats_submit(fields[idx_cname], /*              "Common Name"    */
+                       NULL,              /* unused when in multimode      */
+                       atoll(fields[idx_bytes_recv]),  /* "Bytes Received" */
+                       atoll(fields[idx_bytes_sent])); /* "Bytes Sent"     */
       }
     }
+  }
+
+  if (ferror(fh))
+    return -1;
 
-    read = 1;
+  if (found_header == 0) {
+    NOTICE("openvpn plugin: Unknown file format in instance %s, please "
+           "report this as bug. Make sure to include "
+           "your status file, so the plugin can "
+           "be adapted.",
+           name);
+    return -1;
   }
 
   if (collect_user_count) {
     numusers_submit(name, name, sum_users);
-    read = 1;
   }
 
-  return read;
-} /* int multi4_read */
+  return 0;
+} /* int multi2_read */
 
 /* read callback */
-static int openvpn_read(void) {
-  FILE *fh;
-  int read;
-
-  read = 0;
-
-  if (vpn_num == 0)
-    return 0;
-
-  /* call the right read function for every status entry in the list */
-  for (int i = 0; i < vpn_num; i++) {
-    int vpn_read = 0;
-
-    fh = fopen(vpn_list[i]->file, "r");
-    if (fh == NULL) {
-      char errbuf[1024];
-      WARNING("openvpn plugin: fopen(%s) failed: %s", vpn_list[i]->file,
-              sstrerror(errno, errbuf, sizeof(errbuf)));
-
-      continue;
-    }
-
-    switch (vpn_list[i]->version) {
-    case SINGLE:
-      vpn_read = single_read(vpn_list[i]->name, fh);
-      break;
-
-    case MULTI1:
-      vpn_read = multi1_read(vpn_list[i]->name, fh);
-      break;
-
-    case MULTI2:
-      vpn_read = multi2_read(vpn_list[i]->name, fh);
-      break;
-
-    case MULTI3:
-      vpn_read = multi3_read(vpn_list[i]->name, fh);
-      break;
-
-    case MULTI4:
-      vpn_read = multi4_read(vpn_list[i]->name, fh);
-      break;
-    }
-
-    fclose(fh);
-    read += vpn_read;
-  }
-
-  return read ? 0 : -1;
-} /* int openvpn_read */
-
-static int version_detect(const char *filename) {
-  FILE *fh;
+static int openvpn_read(user_data_t *user_data) {
   char buffer[1024];
-  int version = 0;
+  int read = 0;
 
-  /* Sanity checking. We're called from the config handling routine, so
-   * better play it save. */
-  if ((filename == NULL) || (*filename == 0))
-    return 0;
+  vpn_status_t *st = user_data->data;
 
-  fh = fopen(filename, "r");
+  FILE *fh = fopen(st->file, "r");
   if (fh == NULL) {
     char errbuf[1024];
-    WARNING("openvpn plugin: Unable to read \"%s\": %s", filename,
+    WARNING("openvpn plugin: fopen(%s) failed: %s", st->file,
             sstrerror(errno, errbuf, sizeof(errbuf)));
-    return 0;
+
+    return -1;
   }
 
-  /* now search for the specific multimode data format */
-  while ((fgets(buffer, sizeof(buffer), fh)) != NULL) {
-    /* we look at the first line searching for SINGLE mode configuration */
-    if (strcmp(buffer, VSSTRING) == 0) {
-      DEBUG("openvpn plugin: found status file version SINGLE");
-      version = SINGLE;
-      break;
-    }
-    /* searching for multi version 1 */
-    else if (strcmp(buffer, V1STRING) == 0) {
-      DEBUG("openvpn plugin: found status file version MULTI1");
-      version = MULTI1;
-      break;
-    }
-    /* searching for multi version 2 */
-    else if (strcmp(buffer, V2STRING) == 0) {
-      DEBUG("openvpn plugin: found status file version MULTI2");
-      version = MULTI2;
-      break;
-    }
-    /* searching for multi version 3 */
-    else if (strcmp(buffer, V3STRING) == 0) {
-      DEBUG("openvpn plugin: found status file version MULTI3");
-      version = MULTI3;
-      break;
-    }
-    /* searching for multi version 4 */
-    else if (strcmp(buffer, V4STRING) == 0) {
-      DEBUG("openvpn plugin: found status file version MULTI4");
-      version = MULTI4;
-      break;
-    }
+  // Try to detect file format by its first line
+  if ((fgets(buffer, sizeof(buffer), fh)) == NULL) {
+    WARNING("openvpn plugin: failed to get data from: %s", st->file);
+    fclose(fh);
+    return -1;
   }
 
-  if (version == 0) {
-    /* This is only reached during configuration, so complaining to
-     * the user is in order. */
+  if (strcmp(buffer, TITLE_SINGLE) == 0) { // OpenVPN STATISTICS
+    DEBUG("openvpn plugin: found status file SINGLE");
+    read = single_read(st->name, fh);
+  } else if (strcmp(buffer, TITLE_V1) == 0) { // OpenVPN CLIENT LIST
+    DEBUG("openvpn plugin: found status file MULTI version 1");
+    read = multi1_read(st->name, fh);
+  } else if (strncmp(buffer, TITLE_V2, strlen(TITLE_V2)) == 0) { // TITLE
+    DEBUG("openvpn plugin: found status file MULTI version 2/3");
+    read = multi2_read(st->name, fh);
+  } else {
     NOTICE("openvpn plugin: %s: Unknown file format, please "
            "report this as bug. Make sure to include "
            "your status file, so the plugin can "
            "be adapted.",
-           filename);
+           st->file);
+    read = -1;
   }
-
   fclose(fh);
-
-  return version;
-} /* int version_detect */
+  return read;
+} /* int openvpn_read */
 
 static int openvpn_config(const char *key, const char *value) {
   if (strcasecmp("StatusFile", key) == 0) {
-    char *status_file, *status_name, *filename;
-    int status_version;
-    vpn_status_t *temp;
+    char callback_name[3 * DATA_MAX_NAME_LEN];
+    char *status_name;
 
-    /* try to detect the status file format */
-    status_version = version_detect(value);
-
-    if (status_version == 0) {
-      WARNING("openvpn plugin: unable to detect status version, "
-              "discarding status file \"%s\".",
-              value);
-      return 1;
-    }
-
-    status_file = sstrdup(value);
+    char *status_file = strdup(value);
     if (status_file == NULL) {
       char errbuf[1024];
-      WARNING("openvpn plugin: sstrdup failed: %s",
-              sstrerror(errno, errbuf, sizeof(errbuf)));
+      ERROR("openvpn plugin: strdup failed: %s",
+            sstrerror(errno, errbuf, sizeof(errbuf)));
       return 1;
     }
 
     /* it determines the file name as string starting at location filename + 1
      */
-    filename = strrchr(status_file, (int)'/');
+    char *filename = strrchr(status_file, (int)'/');
     if (filename == NULL) {
       /* status_file is already the file name only */
       status_name = status_file;
@@ -603,50 +488,38 @@ static int openvpn_config(const char *key, const char *value) {
       status_name = filename + 1;
     }
 
-    /* scan the list looking for a clone */
-    for (int i = 0; i < vpn_num; i++) {
-      if (strcasecmp(vpn_list[i]->name, status_name) == 0) {
-        WARNING("openvpn plugin: status filename \"%s\" "
-                "already used, please choose a "
-                "different one.",
-                status_name);
-        sfree(status_file);
-        return 1;
-      }
-    }
-
-    /* create a new vpn element since file, version and name are ok */
-    temp = malloc(sizeof(*temp));
-    if (temp == NULL) {
+    /* create a new vpn element */
+    vpn_status_t *instance = calloc(1, sizeof(*instance));
+    if (instance == NULL) {
       char errbuf[1024];
       ERROR("openvpn plugin: malloc failed: %s",
             sstrerror(errno, errbuf, sizeof(errbuf)));
       sfree(status_file);
       return 1;
     }
-    temp->file = status_file;
-    temp->version = status_version;
-    temp->name = status_name;
-
-    vpn_status_t **tmp_list =
-        realloc(vpn_list, (vpn_num + 1) * sizeof(*vpn_list));
-    if (tmp_list == NULL) {
-      char errbuf[1024];
-      ERROR("openvpn plugin: realloc failed: %s",
-            sstrerror(errno, errbuf, sizeof(errbuf)));
-
-      sfree(vpn_list);
-      sfree(temp->file);
-      sfree(temp);
-      return 1;
+    instance->file = status_file;
+    instance->name = status_name;
+
+    snprintf(callback_name, sizeof(callback_name), "openvpn/%s", status_name);
+
+    int status = plugin_register_complex_read(
+        /* group = */ "openvpn",
+        /* name      = */ callback_name,
+        /* callback  = */ openvpn_read,
+        /* interval  = */ 0,
+        &(user_data_t){
+            .data = instance, .free_func = openvpn_free,
+        });
+
+    if (status == EINVAL) {
+      WARNING("openvpn plugin: status filename \"%s\" "
+              "already used, please choose a "
+              "different one.",
+              status_name);
+      return -1;
     }
-    vpn_list = tmp_list;
-
-    vpn_list[vpn_num] = temp;
-    vpn_num++;
-
-    DEBUG("openvpn plugin: status file \"%s\" added", temp->file);
 
+    DEBUG("openvpn plugin: status file \"%s\" added", instance->file);
   } /* if (strcasecmp ("StatusFile", key) == 0) */
   else if ((strcasecmp("CollectCompression", key) == 0) ||
            (strcasecmp("Compression", key) == 0)) /* old, deprecated name */
@@ -683,18 +556,6 @@ static int openvpn_config(const char *key, const char *value) {
   return 0;
 } /* int openvpn_config */
 
-/* shutdown callback */
-static int openvpn_shutdown(void) {
-  for (int i = 0; i < vpn_num; i++) {
-    sfree(vpn_list[i]->file);
-    sfree(vpn_list[i]);
-  }
-
-  sfree(vpn_list);
-
-  return 0;
-} /* int openvpn_shutdown */
-
 static int openvpn_init(void) {
   if (!collect_individual_users && !collect_compression &&
       !collect_user_count) {
@@ -704,9 +565,6 @@ static int openvpn_init(void) {
     return -1;
   }
 
-  plugin_register_read("openvpn", openvpn_read);
-  plugin_register_shutdown("openvpn", openvpn_shutdown);
-
   return 0;
 } /* int openvpn_init */
 
index 44bd5648d1c4e54eff7eeb48a2add5d07d706129..099013e3692757e3609660b2595751a54582800b 100644 (file)
@@ -62,6 +62,7 @@ struct o_database_s {
   char *connect_id;
   char *username;
   char *password;
+  char *plugin_name;
 
   udb_query_preparation_area_t **q_prep_areas;
   udb_query_t **queries;
@@ -141,6 +142,7 @@ static void o_database_free(o_database_t *db) /* {{{ */
   sfree(db->username);
   sfree(db->password);
   sfree(db->queries);
+  sfree(db->plugin_name);
 
   if (db->q_prep_areas != NULL)
     for (size_t i = 0; i < db->queries_num; ++i)
@@ -192,6 +194,7 @@ static int o_config_add_database(oconfig_item_t *ci) /* {{{ */
   db->connect_id = NULL;
   db->username = NULL;
   db->password = NULL;
+  db->plugin_name = NULL;
 
   status = cf_util_get_string(ci, &db->name);
   if (status != 0) {
@@ -211,6 +214,8 @@ static int o_config_add_database(oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string(child, &db->username);
     else if (strcasecmp("Password", child->key) == 0)
       status = cf_util_get_string(child, &db->password);
+    else if (strcasecmp("Plugin", child->key) == 0)
+      status = cf_util_get_string(child, &db->plugin_name);
     else if (strcasecmp("Query", child->key) == 0)
       status = udb_query_pick_from_list(child, queries, queries_num,
                                         &db->queries, &db->queries_num);
@@ -544,7 +549,8 @@ static int o_read_database_query(o_database_t *db, /* {{{ */
 
   status = udb_query_prepare_result(
       q, prep_area, (db->host != NULL) ? db->host : hostname_g,
-      /* plugin = */ "oracle", db->name, column_names, column_num,
+      /* plugin = */ (db->plugin_name != NULL) ? db->plugin_name : "oracle",
+      db->name, column_names, column_num,
       /* interval = */ 0);
   if (status != 0) {
     ERROR("oracle plugin: o_read_database_query (%s, %s): "
index 7c8a6158f71db7a9aff44f7c117998cb5454aea9..671d1f3fa41b1353c35f8fae01d64117aa017f27 100644 (file)
@@ -261,12 +261,6 @@ struct {
                  {"Collectd::NOTIF_WARNING", NOTIF_WARNING},
                  {"Collectd::NOTIF_OKAY", NOTIF_OKAY},
                  {"", 0}};
-
-struct {
-  char name[64];
-  char *var;
-} g_strings[] = {{"Collectd::hostname_g", hostname_g}, {"", NULL}};
-
 /*
  * Helper functions for data type conversion.
  */
@@ -424,8 +418,6 @@ static int hv2value_list(pTHX_ HV *hash, value_list_t *vl) {
 
   if (NULL != (tmp = hv_fetch(hash, "host", 4, 0)))
     sstrncpy(vl->host, SvPV_nolen(*tmp), sizeof(vl->host));
-  else
-    sstrncpy(vl->host, hostname_g, sizeof(vl->host));
 
   if (NULL != (tmp = hv_fetch(hash, "plugin", 6, 0)))
     sstrncpy(vl->plugin, SvPV_nolen(*tmp), sizeof(vl->plugin));
@@ -1626,18 +1618,19 @@ static void _plugin_register_generic_userdata(pTHX, int type,
       ret = plugin_register_flush("perl", perl_flush, /* user_data = */ NULL);
     }
 
-    if (0 == ret)
+    if (0 == ret) {
       ret = plugin_register_flush(pluginname, perl_flush, &userdata);
+    } else {
+      free(userdata.data);
+    }
   } else {
     ret = -1;
   }
 
   if (0 == ret)
     XSRETURN_YES;
-  else {
-    free(userdata.data);
+  else
     XSRETURN_EMPTY;
-  }
 } /* static void _plugin_register_generic_userdata ( ... ) */
 
 /*
@@ -2100,7 +2093,7 @@ static int perl_init(void) {
   /* Lock the base thread to avoid race conditions with c_ithread_create().
    * See https://github.com/collectd/collectd/issues/9 and
    *     https://github.com/collectd/collectd/issues/1706 for details.
-  */
+   */
   assert(aTHX == perl_threads->head->interp);
   pthread_mutex_lock(&perl_threads->mutex);
 
@@ -2191,7 +2184,7 @@ static void perl_log(int level, const char *msg, user_data_t *user_data) {
   /* Lock the base thread if this is not called from one of the read threads
    * to avoid race conditions with c_ithread_create(). See
    * https://github.com/collectd/collectd/issues/9 for details.
-  */
+   */
 
   if (aTHX == perl_threads->head->interp)
     pthread_mutex_lock(&perl_threads->mutex);
@@ -2402,6 +2395,11 @@ static void xs_init(pTHX) {
    * accessing any such variable (this is basically the same as using
    * tie() in Perl) */
   /* global strings */
+  struct {
+    char name[64];
+    char *var;
+  } g_strings[] = {{"Collectd::hostname_g", hostname_g}, {"", NULL}};
+
   for (int i = 0; '\0' != g_strings[i].name[0]; ++i) {
     tmp = get_sv(g_strings[i].name, 1);
     sv_magicext(tmp, NULL, PERL_MAGIC_ext, &g_pv_vtbl, g_strings[i].var, 0);
@@ -2628,6 +2626,12 @@ static int perl_config_plugin(pTHX_ oconfig_item_t *ci) {
   char *plugin;
   HV *config;
 
+  if (NULL == perl_threads) {
+    log_err("A `Plugin' block was encountered but no plugin was loaded yet. "
+            "Put the appropriate `LoadPlugin' option in front of it.");
+    return -1;
+  }
+
   dSP;
 
   if ((1 != ci->values_num) || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
index 56730b47958eecf37183b466224e25d35f9498f3..3b702aeef83491d590a631e142f37fec856151c1 100644 (file)
@@ -139,6 +139,7 @@ typedef struct {
   char *password;
 
   char *instance;
+  char *plugin_name;
 
   char *sslmode;
 
@@ -250,6 +251,8 @@ static c_psql_database_t *c_psql_database_new(const char *name) {
 
   db->instance = sstrdup(name);
 
+  db->plugin_name = NULL;
+
   db->sslmode = NULL;
 
   db->krbsrvname = NULL;
@@ -300,6 +303,8 @@ static void c_psql_database_delete(void *data) {
 
   sfree(db->instance);
 
+  sfree(db->plugin_name);
+
   sfree(db->sslmode);
 
   sfree(db->krbsrvname);
@@ -539,9 +544,11 @@ static int c_psql_exec_query(c_psql_database_t *db, udb_query_t *q,
   else
     host = db->host;
 
-  status =
-      udb_query_prepare_result(q, prep_area, host, "postgresql", db->instance,
-                               column_names, (size_t)column_num, db->interval);
+  status = udb_query_prepare_result(
+      q, prep_area, host,
+      (db->plugin_name != NULL) ? db->plugin_name : "postgresql",
+      db->instance, column_names, (size_t)column_num, db->interval);
+
   if (0 != status) {
     log_err("udb_query_prepare_result failed with status %i.", status);
     BAIL_OUT(-1);
@@ -1139,6 +1146,8 @@ static int c_psql_config_database(oconfig_item_t *ci) {
       cf_util_get_string(c, &db->password);
     else if (0 == strcasecmp(c->key, "Instance"))
       cf_util_get_string(c, &db->instance);
+    else if (0 == strcasecmp (c->key, "Plugin"))
+      cf_util_get_string (c, &db->plugin_name);
     else if (0 == strcasecmp(c->key, "SSLMode"))
       cf_util_get_string(c, &db->sslmode);
     else if (0 == strcasecmp(c->key, "KRBSrvName"))
index 4fec161ed2833e01508d31670ed85f57aba0ea04..89cf3e0ee549437f29b489a35825896aa557d91f 100644 (file)
@@ -33,6 +33,7 @@
  *   Clément Stenac <clement.stenac at diwi.org>
  *   Cosmin Ioiart <cioiart at gmail.com>
  *   Pavel Rochnyack <pavel2000 at ngs.ru>
+ *   Wilfried Goesgens <dothebart at citadel.org>
  **/
 
 #include "collectd.h"
@@ -168,6 +169,7 @@ typedef struct process_entry_s {
   unsigned long num_proc;
   unsigned long num_lwp;
   unsigned long num_fd;
+  unsigned long num_maps;
   unsigned long vmem_size;
   unsigned long vmem_rss;
   unsigned long vmem_data;
@@ -185,6 +187,8 @@ typedef struct process_entry_s {
   derive_t io_wchar;
   derive_t io_syscr;
   derive_t io_syscw;
+  derive_t io_diskr;
+  derive_t io_diskw;
   _Bool has_io;
 
   derive_t cswitch_vol;
@@ -192,6 +196,8 @@ typedef struct process_entry_s {
   _Bool has_cswitch;
 
   _Bool has_fd;
+
+  _Bool has_maps;
 } process_entry_t;
 
 typedef struct procstat_entry_s {
@@ -209,6 +215,8 @@ typedef struct procstat_entry_s {
   derive_t io_wchar;
   derive_t io_syscr;
   derive_t io_syscw;
+  derive_t io_diskr;
+  derive_t io_diskw;
 
   derive_t cswitch_vol;
   derive_t cswitch_invol;
@@ -225,6 +233,7 @@ typedef struct procstat {
   unsigned long num_proc;
   unsigned long num_lwp;
   unsigned long num_fd;
+  unsigned long num_maps;
   unsigned long vmem_size;
   unsigned long vmem_rss;
   unsigned long vmem_data;
@@ -242,11 +251,14 @@ typedef struct procstat {
   derive_t io_wchar;
   derive_t io_syscr;
   derive_t io_syscw;
+  derive_t io_diskr;
+  derive_t io_diskw;
 
   derive_t cswitch_vol;
   derive_t cswitch_invol;
 
   _Bool report_fd_num;
+  _Bool report_maps_num;
   _Bool report_ctx_switch;
 
   struct procstat *next;
@@ -258,6 +270,7 @@ static procstat_t *list_head_g = NULL;
 static _Bool want_init = 1;
 static _Bool report_ctx_switch = 0;
 static _Bool report_fd_num = 0;
+static _Bool report_maps_num = 0;
 
 #if HAVE_THREAD_INFO
 static mach_port_t port_host_self;
@@ -310,10 +323,13 @@ static procstat_t *ps_list_register(const char *name, const char *regexp) {
   new->io_wchar = -1;
   new->io_syscr = -1;
   new->io_syscw = -1;
+  new->io_diskr = -1;
+  new->io_diskw = -1;
   new->cswitch_vol = -1;
   new->cswitch_invol = -1;
 
   new->report_fd_num = report_fd_num;
+  new->report_maps_num = report_maps_num;
   new->report_ctx_switch = report_ctx_switch;
 
 #if HAVE_REGEX_H
@@ -464,6 +480,7 @@ static void ps_list_add(const char *name, const char *cmdline,
     ps->num_proc += entry->num_proc;
     ps->num_lwp += entry->num_lwp;
     ps->num_fd += entry->num_fd;
+    ps->num_maps += entry->num_maps;
     ps->vmem_size += entry->vmem_size;
     ps->vmem_rss += entry->vmem_rss;
     ps->vmem_data += entry->vmem_data;
@@ -480,6 +497,11 @@ static void ps_list_add(const char *name, const char *cmdline,
       ps_update_counter(&ps->io_syscw, &pse->io_syscw, entry->io_syscw);
     }
 
+    if ((entry->io_diskr != -1) && (entry->io_diskw != -1)) {
+      ps_update_counter(&ps->io_diskr, &pse->io_diskr, entry->io_diskr);
+      ps_update_counter(&ps->io_diskw, &pse->io_diskw, entry->io_diskw);
+    }
+
     if ((entry->cswitch_vol != -1) && (entry->cswitch_vol != -1)) {
       ps_update_counter(&ps->cswitch_vol, &pse->cswitch_vol,
                         entry->cswitch_vol);
@@ -508,6 +530,7 @@ static void ps_list_reset(void) {
     ps->num_proc = 0;
     ps->num_lwp = 0;
     ps->num_fd = 0;
+    ps->num_maps = 0;
     ps->vmem_size = 0;
     ps->vmem_rss = 0;
     ps->vmem_data = 0;
@@ -548,6 +571,8 @@ static void ps_tune_instance(oconfig_item_t *ci, procstat_t *ps) {
       cf_util_get_boolean(c, &ps->report_ctx_switch);
     else if (strcasecmp(c->key, "CollectFileDescriptor") == 0)
       cf_util_get_boolean(c, &ps->report_fd_num);
+    else if (strcasecmp(c->key, "CollectMemoryMaps") == 0)
+      cf_util_get_boolean(c, &ps->report_maps_num);
     else {
       ERROR("processes plugin: Option `%s' not allowed here.", c->key);
     }
@@ -606,6 +631,8 @@ static int ps_config(oconfig_item_t *ci) {
       cf_util_get_boolean(c, &report_ctx_switch);
     } else if (strcasecmp(c->key, "CollectFileDescriptor") == 0) {
       cf_util_get_boolean(c, &report_fd_num);
+    } else if (strcasecmp(c->key, "CollectMemoryMaps") == 0) {
+      cf_util_get_boolean(c, &report_maps_num);
     } else {
       ERROR("processes plugin: The `%s' configuration option is not "
             "understood and will be ignored.",
@@ -725,7 +752,7 @@ static void ps_submit_proc_list(procstat_t *ps) {
   plugin_dispatch_values(&vl);
 
   if ((ps->io_rchar != -1) && (ps->io_wchar != -1)) {
-    sstrncpy(vl.type, "ps_disk_octets", sizeof(vl.type));
+    sstrncpy(vl.type, "io_octets", sizeof(vl.type));
     vl.values[0].derive = ps->io_rchar;
     vl.values[1].derive = ps->io_wchar;
     vl.values_len = 2;
@@ -733,13 +760,21 @@ static void ps_submit_proc_list(procstat_t *ps) {
   }
 
   if ((ps->io_syscr != -1) && (ps->io_syscw != -1)) {
-    sstrncpy(vl.type, "ps_disk_ops", sizeof(vl.type));
+    sstrncpy(vl.type, "io_ops", sizeof(vl.type));
     vl.values[0].derive = ps->io_syscr;
     vl.values[1].derive = ps->io_syscw;
     vl.values_len = 2;
     plugin_dispatch_values(&vl);
   }
 
+  if ((ps->io_diskr != -1) && (ps->io_diskw != -1)) {
+    sstrncpy(vl.type, "disk_octets", sizeof(vl.type));
+    vl.values[0].derive = ps->io_diskr;
+    vl.values[1].derive = ps->io_diskw;
+    vl.values_len = 2;
+    plugin_dispatch_values(&vl);
+  }
+
   if (ps->num_fd > 0) {
     sstrncpy(vl.type, "file_handles", sizeof(vl.type));
     vl.values[0].gauge = ps->num_fd;
@@ -747,6 +782,14 @@ static void ps_submit_proc_list(procstat_t *ps) {
     plugin_dispatch_values(&vl);
   }
 
+  if (ps->num_maps > 0) {
+    sstrncpy(vl.type, "file_handles", sizeof(vl.type));
+    sstrncpy(vl.type_instance, "mapped", sizeof(vl.type_instance));
+    vl.values[0].gauge = ps->num_maps;
+    vl.values_len = 1;
+    plugin_dispatch_values(&vl);
+  }
+
   if ((ps->cswitch_vol != -1) && (ps->cswitch_invol != -1)) {
     sstrncpy(vl.type, "contextswitch", sizeof(vl.type));
     sstrncpy(vl.type_instance, "voluntary", sizeof(vl.type_instance));
@@ -761,19 +804,23 @@ static void ps_submit_proc_list(procstat_t *ps) {
     plugin_dispatch_values(&vl);
   }
 
-  DEBUG("name = %s; num_proc = %lu; num_lwp = %lu; num_fd = %lu; "
-        "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; "
-        "vmem_code = %lu; "
-        "vmem_minflt_counter = %" PRIi64 "; vmem_majflt_counter = %" PRIi64 "; "
-        "cpu_user_counter = %" PRIi64 "; cpu_system_counter = %" PRIi64 "; "
-        "io_rchar = %" PRIi64 "; io_wchar = %" PRIi64 "; "
-        "io_syscr = %" PRIi64 "; io_syscw = %" PRIi64 "; "
-        "cswitch_vol = %" PRIi64 "; cswitch_invol = %" PRIi64 ";",
-        ps->name, ps->num_proc, ps->num_lwp, ps->num_fd, ps->vmem_size,
-        ps->vmem_rss, ps->vmem_data, ps->vmem_code, ps->vmem_minflt_counter,
-        ps->vmem_majflt_counter, ps->cpu_user_counter, ps->cpu_system_counter,
-        ps->io_rchar, ps->io_wchar, ps->io_syscr, ps->io_syscw, ps->cswitch_vol,
-        ps->cswitch_invol);
+  DEBUG(
+      "name = %s; num_proc = %lu; num_lwp = %lu; num_fd = %lu; num_maps = %lu; "
+      "vmem_size = %lu; vmem_rss = %lu; vmem_data = %lu; "
+      "vmem_code = %lu; "
+      "vmem_minflt_counter = %" PRIi64 "; vmem_majflt_counter = %" PRIi64 "; "
+      "cpu_user_counter = %" PRIi64 "; cpu_system_counter = %" PRIi64 "; "
+      "io_rchar = %" PRIi64 "; io_wchar = %" PRIi64 "; "
+      "io_syscr = %" PRIi64 "; io_syscw = %" PRIi64 "; "
+      "io_diskr = %" PRIi64 "; io_diskw = %" PRIi64 "; "
+      "cswitch_vol = %" PRIi64 "; cswitch_invol = %" PRIi64 ";",
+      ps->name, ps->num_proc, ps->num_lwp, ps->num_fd, ps->num_maps,
+      ps->vmem_size, ps->vmem_rss, ps->vmem_data, ps->vmem_code,
+      ps->vmem_minflt_counter, ps->vmem_majflt_counter, ps->cpu_user_counter,
+      ps->cpu_system_counter, ps->io_rchar, ps->io_wchar, ps->io_syscr,
+      ps->io_syscw, ps->io_diskr, ps->io_diskw, ps->cswitch_vol,
+      ps->cswitch_invol);
+
 } /* void ps_submit_proc_list */
 
 #if KERNEL_LINUX || KERNEL_SOLARIS
@@ -954,6 +1001,10 @@ static int ps_read_io(process_entry_t *ps) {
       val = &(ps->io_syscr);
     else if (strncasecmp(buffer, "syscw:", 6) == 0)
       val = &(ps->io_syscw);
+    else if (strncasecmp(buffer, "read_bytes:", 11) == 0)
+      val = &(ps->io_diskr);
+    else if (strncasecmp(buffer, "write_bytes:", 12) == 0)
+      val = &(ps->io_diskw);
     else
       continue;
 
@@ -978,6 +1029,31 @@ static int ps_read_io(process_entry_t *ps) {
   return 0;
 } /* int ps_read_io (...) */
 
+static int ps_count_maps(pid_t pid) {
+  FILE *fh;
+  char buffer[1024];
+  char filename[64];
+  int count = 0;
+
+  snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
+  if ((fh = fopen(filename, "r")) == NULL) {
+    DEBUG("ps_count_maps: Failed to open file `%s'", filename);
+    return -1;
+  }
+
+  while (fgets(buffer, sizeof(buffer), fh) != NULL) {
+    if (strchr(buffer, '\n')) {
+      count++;
+    }
+  } /* while (fgets) */
+
+  if (fclose(fh)) {
+    char errbuf[1024];
+    WARNING("processes: fclose: %s", sstrerror(errno, errbuf, sizeof(errbuf)));
+  }
+  return count;
+} /* int ps_count_maps (...) */
+
 static int ps_count_fd(int pid) {
   char dirname[64];
   DIR *dh;
@@ -1014,6 +1090,14 @@ static void ps_fill_details(const procstat_t *ps, process_entry_t *entry) {
     }
   }
 
+  if (ps->report_maps_num) {
+    int num_maps;
+    if (entry->has_maps == 0 && (num_maps = ps_count_maps(entry->id)) > 0) {
+      entry->num_maps = num_maps;
+    }
+    entry->has_maps = 1;
+  }
+
   if (ps->report_fd_num) {
     int num_fd;
     if (entry->has_fd == 0 && (num_fd = ps_count_fd(entry->id)) > 0) {
@@ -1149,6 +1233,8 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) {
   ps->io_wchar = -1;
   ps->io_syscr = -1;
   ps->io_syscw = -1;
+  ps->io_diskr = -1;
+  ps->io_diskw = -1;
 
   ps->cswitch_vol = -1;
   ps->cswitch_invol = -1;
@@ -1393,6 +1479,9 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) {
    */
   ps->num_fd = 0;
 
+  /* Number of memory mappings */
+  ps->num_maps = 0;
+
   /*
    * Calculating input/ouput chars
    * Formula used is total chars / total blocks => chars/block
@@ -1407,6 +1496,8 @@ static int ps_read_process(long pid, process_entry_t *ps, char *state) {
   ps->io_wchar = myUsage->pr_oublk * chars_per_block;
   ps->io_syscr = myUsage->pr_sysc;
   ps->io_syscw = myUsage->pr_sysc;
+  ps->io_diskr = -1;
+  ps->io_diskw = -1;
 
   /*
    * TODO: context switch counters for Solaris
@@ -1620,10 +1711,15 @@ static int ps_read(void) {
         pse.io_wchar = -1;
         pse.io_syscr = -1;
         pse.io_syscw = -1;
+        pse.io_diskr = -1;
+        pse.io_diskw = -1;
 
         /* File descriptor count not implemented */
         pse.num_fd = 0;
 
+        /* Number of memory mappings */
+        pse.num_maps = 0;
+
         pse.vmem_minflt_counter = task_events_info.cow_faults;
         pse.vmem_majflt_counter = task_events_info.faults;
 
@@ -1923,10 +2019,15 @@ static int ps_read(void) {
       pse.io_wchar = -1;
       pse.io_syscr = -1;
       pse.io_syscw = -1;
+      pse.io_diskr = -1;
+      pse.io_diskw = -1;
 
       /* file descriptor count not implemented */
       pse.num_fd = 0;
 
+      /* Number of memory mappings */
+      pse.num_maps = 0;
+
       /* context switch counters not implemented */
       pse.cswitch_vol = -1;
       pse.cswitch_invol = -1;
@@ -2062,10 +2163,15 @@ static int ps_read(void) {
       pse.io_wchar = -1;
       pse.io_syscr = -1;
       pse.io_syscw = -1;
+      pse.io_diskr = -1;
+      pse.io_diskw = -1;
 
       /* file descriptor count not implemented */
       pse.num_fd = 0;
 
+      /* Number of memory mappings */
+      pse.num_maps = 0;
+
       /* context switch counters not implemented */
       pse.cswitch_vol = -1;
       pse.cswitch_invol = -1;
@@ -2225,8 +2331,11 @@ static int ps_read(void) {
       pse.io_wchar = -1;
       pse.io_syscr = -1;
       pse.io_syscw = -1;
+      pse.io_diskr = -1;
+      pse.io_diskw = -1;
 
       pse.num_fd = 0;
+      pse.num_maps = 0;
 
       pse.cswitch_vol = -1;
       pse.cswitch_invol = -1;
index 32c5847df6133e329b1d42b1996395c37f720c3a..e60ba4595f8daf5942ed3a849e8d4f3d01e1e488 100644 (file)
@@ -438,7 +438,7 @@ static int cpy_write_callback(const data_set_t *ds,
   }
   dict = PyDict_New(); /* New reference. */
   if (value_list->meta) {
-    char **table;
+    char **table = NULL;
     meta_data_t *meta = value_list->meta;
 
     int num = meta_data_toc(meta, &table);
@@ -461,19 +461,21 @@ static int cpy_write_callback(const data_set_t *ds,
       } else if (type == MD_TYPE_SIGNED_INT) {
         if (meta_data_get_signed_int(meta, table[i], &si))
           continue;
-        temp = PyObject_CallFunctionObjArgs((void *)&SignedType,
-                                            PyLong_FromLongLong(si),
+        PyObject *sival = PyLong_FromLongLong(si); /* New reference */
+        temp = PyObject_CallFunctionObjArgs((void *)&SignedType, sival,
                                             (void *)0); /* New reference. */
         PyDict_SetItemString(dict, table[i], temp);
         Py_XDECREF(temp);
+        Py_XDECREF(sival);
       } else if (type == MD_TYPE_UNSIGNED_INT) {
         if (meta_data_get_unsigned_int(meta, table[i], &ui))
           continue;
-        temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType,
-                                            PyLong_FromUnsignedLongLong(ui),
+        PyObject *uval = PyLong_FromUnsignedLongLong(ui); /* New reference */
+        temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, uval,
                                             (void *)0); /* New reference. */
         PyDict_SetItemString(dict, table[i], temp);
         Py_XDECREF(temp);
+        Py_XDECREF(uval);
       } else if (type == MD_TYPE_DOUBLE) {
         if (meta_data_get_double(meta, table[i], &d))
           continue;
@@ -525,6 +527,39 @@ static int cpy_notification_callback(const notification_t *notification,
   Notification *n;
 
   CPY_LOCK_THREADS
+  PyObject *dict = PyDict_New(); /* New reference. */
+  for (notification_meta_t *meta = notification->meta; meta != NULL;
+       meta = meta->next) {
+    PyObject *temp = NULL;
+    if (meta->type == NM_TYPE_STRING) {
+      temp = cpy_string_to_unicode_or_bytes(
+          meta->nm_value.nm_string); /* New reference. */
+      PyDict_SetItemString(dict, meta->name, temp);
+      Py_XDECREF(temp);
+    } else if (meta->type == NM_TYPE_SIGNED_INT) {
+      PyObject *sival = PyLong_FromLongLong(meta->nm_value.nm_signed_int);
+      temp = PyObject_CallFunctionObjArgs((void *)&SignedType, sival,
+                                          (void *)0); /* New reference. */
+      PyDict_SetItemString(dict, meta->name, temp);
+      Py_XDECREF(temp);
+      Py_XDECREF(sival);
+    } else if (meta->type == NM_TYPE_UNSIGNED_INT) {
+      PyObject *uval =
+          PyLong_FromUnsignedLongLong(meta->nm_value.nm_unsigned_int);
+      temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, uval,
+                                          (void *)0); /* New reference. */
+      PyDict_SetItemString(dict, meta->name, temp);
+      Py_XDECREF(temp);
+      Py_XDECREF(uval);
+    } else if (meta->type == NM_TYPE_DOUBLE) {
+      temp = PyFloat_FromDouble(meta->nm_value.nm_double); /* New reference. */
+      PyDict_SetItemString(dict, meta->name, temp);
+      Py_XDECREF(temp);
+    } else if (meta->type == NM_TYPE_BOOLEAN) {
+      PyDict_SetItemString(dict, meta->name,
+                           meta->nm_value.nm_boolean ? Py_True : Py_False);
+    }
+  }
   notify = Notification_New(); /* New reference. */
   n = (Notification *)notify;
   sstrncpy(n->data.host, notification->host, sizeof(n->data.host));
@@ -537,6 +572,8 @@ static int cpy_notification_callback(const notification_t *notification,
   n->data.time = CDTIME_T_TO_DOUBLE(notification->time);
   sstrncpy(n->message, notification->message, sizeof(n->message));
   n->severity = notification->severity;
+  Py_CLEAR(n->meta);
+  n->meta = dict; /* Steals a reference. */
   ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data,
                                      (void *)0); /* New reference. */
   Py_XDECREF(notify);
@@ -666,8 +703,9 @@ static PyObject *cpy_get_dataset(PyObject *self, PyObject *args) {
   for (size_t i = 0; i < ds->ds_num; ++i) {
     tuple = PyTuple_New(4);
     PyTuple_SET_ITEM(tuple, 0, cpy_string_to_unicode_or_bytes(ds->ds[i].name));
-    PyTuple_SET_ITEM(tuple, 1, cpy_string_to_unicode_or_bytes(
-                                   DS_TYPE_TO_STRING(ds->ds[i].type)));
+    PyTuple_SET_ITEM(
+        tuple, 1,
+        cpy_string_to_unicode_or_bytes(DS_TYPE_TO_STRING(ds->ds[i].type)));
     PyTuple_SET_ITEM(tuple, 2, float_or_none(ds->ds[i].min));
     PyTuple_SET_ITEM(tuple, 3, float_or_none(ds->ds[i].max));
     PyList_SET_ITEM(list, i, tuple);
@@ -737,7 +775,8 @@ static PyObject *cpy_register_generic_userdata(void *reg, void *handler,
 
   register_function(buf, handler,
                     &(user_data_t){
-                        .data = c, .free_func = cpy_destroy_user_data,
+                        .data = c,
+                        .free_func = cpy_destroy_user_data,
                     });
 
   ++cpy_num_callbacks;
@@ -780,7 +819,8 @@ static PyObject *cpy_register_read(PyObject *self, PyObject *args,
       /* group = */ "python", buf, cpy_read_callback,
       DOUBLE_TO_CDTIME_T(interval),
       &(user_data_t){
-          .data = c, .free_func = cpy_destroy_user_data,
+          .data = c,
+          .free_func = cpy_destroy_user_data,
       });
   ++cpy_num_callbacks;
   return cpy_string_to_unicode_or_bytes(buf);
@@ -1160,8 +1200,9 @@ static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
   values = PyTuple_New(ci->values_num); /* New reference. */
   for (int i = 0; i < ci->values_num; ++i) {
     if (ci->values[i].type == OCONFIG_TYPE_STRING) {
-      PyTuple_SET_ITEM(values, i, cpy_string_to_unicode_or_bytes(
-                                      ci->values[i].value.string));
+      PyTuple_SET_ITEM(
+          values, i,
+          cpy_string_to_unicode_or_bytes(ci->values[i].value.string));
     } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
       PyTuple_SET_ITEM(values, i,
                        PyFloat_FromDouble(ci->values[i].value.number));
index 4f7e8349af6265575f22ccc205f3a09c0a347170..15c1848488e70ead655ea1d2d0744572b371cd2b 100644 (file)
 
 #include "cpython.h"
 
+typedef struct {
+  int (*add_string)(void *, const char *, const char *);
+  int (*add_signed_int)(void *, const char *, int64_t);
+  int (*add_unsigned_int)(void *, const char *, uint64_t);
+  int (*add_double)(void *, const char *, double);
+  int (*add_boolean)(void *, const char *, _Bool);
+} cpy_build_meta_handler_t;
+
 #define FreeAll()                                                              \
   do {                                                                         \
     PyMem_Free(type);                                                          \
@@ -457,26 +465,26 @@ static int Values_init(PyObject *s, PyObject *args, PyObject *kwds) {
   return 0;
 }
 
-static meta_data_t *cpy_build_meta(PyObject *meta) {
+static int cpy_build_meta_generic(PyObject *meta,
+                                  cpy_build_meta_handler_t *meta_func,
+                                  void *m) {
   int s;
-  meta_data_t *m = NULL;
   PyObject *l;
 
   if ((meta == NULL) || (meta == Py_None))
-    return NULL;
+    return -1;
 
   l = PyDict_Items(meta); /* New reference. */
   if (!l) {
     cpy_log_exception("building meta data");
-    return NULL;
+    return -1;
   }
   s = PyList_Size(l);
   if (s <= 0) {
     Py_XDECREF(l);
-    return NULL;
+    return -1;
   }
 
-  m = meta_data_create();
   for (int i = 0; i < s; ++i) {
     const char *string, *keystring;
     PyObject *key, *value, *item, *tmp;
@@ -493,45 +501,45 @@ static meta_data_t *cpy_build_meta(PyObject *meta) {
     value = PyTuple_GET_ITEM(item, 1);
     Py_INCREF(value);
     if (value == Py_True) {
-      meta_data_add_boolean(m, keystring, 1);
+      meta_func->add_boolean(m, keystring, 1);
     } else if (value == Py_False) {
-      meta_data_add_boolean(m, keystring, 0);
+      meta_func->add_boolean(m, keystring, 0);
     } else if (PyFloat_Check(value)) {
-      meta_data_add_double(m, keystring, PyFloat_AsDouble(value));
+      meta_func->add_double(m, keystring, PyFloat_AsDouble(value));
     } else if (PyObject_TypeCheck(value, &SignedType)) {
       long long int lli;
       lli = PyLong_AsLongLong(value);
       if (!PyErr_Occurred() && (lli == (int64_t)lli))
-        meta_data_add_signed_int(m, keystring, lli);
+        meta_func->add_signed_int(m, keystring, lli);
     } else if (PyObject_TypeCheck(value, &UnsignedType)) {
       long long unsigned llu;
       llu = PyLong_AsUnsignedLongLong(value);
       if (!PyErr_Occurred() && (llu == (uint64_t)llu))
-        meta_data_add_unsigned_int(m, keystring, llu);
+        meta_func->add_unsigned_int(m, keystring, llu);
     } else if (PyNumber_Check(value)) {
       long long int lli;
       long long unsigned llu;
       tmp = PyNumber_Long(value);
       lli = PyLong_AsLongLong(tmp);
       if (!PyErr_Occurred() && (lli == (int64_t)lli)) {
-        meta_data_add_signed_int(m, keystring, lli);
+        meta_func->add_signed_int(m, keystring, lli);
       } else {
         PyErr_Clear();
         llu = PyLong_AsUnsignedLongLong(tmp);
         if (!PyErr_Occurred() && (llu == (uint64_t)llu))
-          meta_data_add_unsigned_int(m, keystring, llu);
+          meta_func->add_unsigned_int(m, keystring, llu);
       }
       Py_XDECREF(tmp);
     } else {
       string = cpy_unicode_or_bytes_to_string(&value);
       if (string) {
-        meta_data_add_string(m, keystring, string);
+        meta_func->add_string(m, keystring, string);
       } else {
         PyErr_Clear();
         tmp = PyObject_Str(value);
         string = cpy_unicode_or_bytes_to_string(&tmp);
         if (string)
-          meta_data_add_string(m, keystring, string);
+          meta_func->add_string(m, keystring, string);
         Py_XDECREF(tmp);
       }
     }
@@ -541,9 +549,44 @@ static meta_data_t *cpy_build_meta(PyObject *meta) {
     Py_DECREF(key);
   }
   Py_XDECREF(l);
+  return 0;
+}
+
+#define CPY_BUILD_META_FUNC(meta_type, func, val_type)                         \
+  static int cpy_##func(void *meta, const char *key, val_type val) {           \
+    return func((meta_type *)meta, key, val);                                  \
+  }
+
+#define CPY_BUILD_META_HANDLER(func_prefix, meta_type)                         \
+  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_string, const char *)       \
+  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_signed_int, int64_t)        \
+  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_unsigned_int, uint64_t)     \
+  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_double, double)             \
+  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_boolean, _Bool)             \
+                                                                               \
+  static cpy_build_meta_handler_t cpy_##func_prefix = {                        \
+      .add_string = cpy_##func_prefix##_add_string,                            \
+      .add_signed_int = cpy_##func_prefix##_add_signed_int,                    \
+      .add_unsigned_int = cpy_##func_prefix##_add_unsigned_int,                \
+      .add_double = cpy_##func_prefix##_add_double,                            \
+      .add_boolean = cpy_##func_prefix##_add_boolean}
+
+CPY_BUILD_META_HANDLER(meta_data, meta_data_t);
+CPY_BUILD_META_HANDLER(plugin_notification_meta, notification_t);
+
+static meta_data_t *cpy_build_meta(PyObject *meta) {
+  meta_data_t *m = meta_data_create();
+  if (cpy_build_meta_generic(meta, &cpy_meta_data, (void *)m) < 0) {
+    meta_data_destroy(m);
+    return NULL;
+  }
   return m;
 }
 
+static void cpy_build_notification_meta(notification_t *n, PyObject *meta) {
+  cpy_build_meta_generic(meta, &cpy_plugin_notification_meta, (void *)n);
+}
+
 static PyObject *Values_dispatch(Values *self, PyObject *args, PyObject *kwds) {
   int ret;
   const data_set_t *ds;
@@ -910,6 +953,17 @@ PyTypeObject ValuesType = {
     Values_new       /* tp_new */
 };
 
+static char notification_meta_doc[] =
+    "These are the meta data for the Notification object.\n"
+    "It has to be a dictionary of numbers, strings or bools. All keys must be\n"
+    "strings. int and long objects will be dispatched as signed integers "
+    "unless\n"
+    "they are between 2**63 and 2**64-1, which will result in an unsigned "
+    "integer.\n"
+    "One of these storage classes can be forced by using the classes\n"
+    "collectd.Signed and collectd.Unsigned. A meta object received by a\n"
+    "notification callback will always contain Signed or Unsigned objects.";
+
 static char severity_doc[] =
     "The severity of this notification. Assign or compare to\n"
     "NOTIF_FAILURE, NOTIF_WARNING or NOTIF_OKAY.";
@@ -931,16 +985,17 @@ static int Notification_init(PyObject *s, PyObject *args, PyObject *kwds) {
   int severity = 0;
   double time = 0;
   char *message = NULL;
+  PyObject *meta = NULL;
   char *type = NULL, *plugin_instance = NULL, *type_instance = NULL,
        *plugin = NULL, *host = NULL;
-  static char *kwlist[] = {"type",          "message",  "plugin_instance",
-                           "type_instance", "plugin",   "host",
-                           "time",          "severity", NULL};
+  static char *kwlist[] = {
+      "type", "message", "plugin_instance", "type_instance", "plugin",
+      "host", "time",    "severity",        "meta",          NULL};
 
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist, NULL,
-                                   &type, NULL, &message, NULL,
-                                   &plugin_instance, NULL, &type_instance, NULL,
-                                   &plugin, NULL, &host, &time, &severity))
+  if (!PyArg_ParseTupleAndKeywords(
+          args, kwds, "|etetetetetetdiO", kwlist, NULL, &type, NULL, &message,
+          NULL, &plugin_instance, NULL, &type_instance, NULL, &plugin, NULL,
+          &host, &time, &severity, &meta))
     return -1;
 
   if (type && plugin_get_ds(type) == NULL) {
@@ -963,6 +1018,18 @@ static int Notification_init(PyObject *s, PyObject *args, PyObject *kwds) {
 
   FreeAll();
   PyMem_Free(message);
+
+  if (meta == NULL) {
+    meta = PyDict_New();
+    PyErr_Clear();
+  } else {
+    Py_INCREF(meta);
+  }
+
+  PyObject *tmp = self->meta;
+  self->meta = meta;
+  Py_XDECREF(tmp);
+
   return 0;
 }
 
@@ -972,18 +1039,19 @@ static PyObject *Notification_dispatch(Notification *self, PyObject *args,
   const data_set_t *ds;
   notification_t notification;
   double t = self->data.time;
+  PyObject *meta = self->meta;
   int severity = self->severity;
   char *host = NULL, *plugin = NULL, *plugin_instance = NULL, *type = NULL,
        *type_instance = NULL;
   char *message = NULL;
 
-  static char *kwlist[] = {"type",          "message",  "plugin_instance",
-                           "type_instance", "plugin",   "host",
-                           "time",          "severity", NULL};
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist, NULL,
+  static char *kwlist[] = {
+      "type", "message", "plugin_instance", "type_instance", "plugin",
+      "host", "time",    "severity",        "meta",          NULL};
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdiO", kwlist, NULL,
                                    &type, NULL, &message, NULL,
                                    &plugin_instance, NULL, &type_instance, NULL,
-                                   &plugin, NULL, &host, &t, &severity))
+                                   &plugin, NULL, &host, &t, &severity, &meta))
     return NULL;
 
   notification.time = DOUBLE_TO_CDTIME_T(t);
@@ -1015,6 +1083,11 @@ static PyObject *Notification_dispatch(Notification *self, PyObject *args,
     PyErr_Format(PyExc_TypeError, "Dataset %s not found", notification.type);
     return NULL;
   }
+  if (meta != NULL && meta != Py_None && !PyDict_Check(meta)) {
+    PyErr_Format(PyExc_TypeError, "meta must be a dict");
+    return NULL;
+  }
+  cpy_build_notification_meta(&notification, meta);
 
   if (notification.time == 0)
     notification.time = cdtime();
@@ -1024,6 +1097,8 @@ static PyObject *Notification_dispatch(Notification *self, PyObject *args,
     sstrncpy(notification.plugin, "python", sizeof(notification.plugin));
   Py_BEGIN_ALLOW_THREADS;
   ret = plugin_dispatch_notification(&notification);
+  if (notification.meta)
+    plugin_notification_meta_free(notification.meta);
   Py_END_ALLOW_THREADS;
   if (ret != 0) {
     PyErr_SetString(PyExc_RuntimeError,
@@ -1041,6 +1116,7 @@ static PyObject *Notification_new(PyTypeObject *type, PyObject *args,
   if (self == NULL)
     return NULL;
 
+  self->meta = PyDict_New();
   self->message[0] = 0;
   self->severity = 0;
   return (PyObject *)self;
@@ -1068,17 +1144,21 @@ static int Notification_setstring(PyObject *self, PyObject *value, void *data) {
 
 static PyObject *Notification_repr(PyObject *s) {
   PyObject *ret, *tmp;
-  static PyObject *l_severity = NULL, *l_message = NULL, *l_closing = NULL;
+  static PyObject *l_severity = NULL, *l_message = NULL, *l_meta = NULL,
+                  *l_closing = NULL;
   Notification *self = (Notification *)s;
 
   if (l_severity == NULL)
     l_severity = cpy_string_to_unicode_or_bytes(",severity=");
   if (l_message == NULL)
     l_message = cpy_string_to_unicode_or_bytes(",message=");
+  if (l_meta == NULL)
+    l_meta = cpy_string_to_unicode_or_bytes(",meta=");
   if (l_closing == NULL)
     l_closing = cpy_string_to_unicode_or_bytes(")");
 
-  if (l_severity == NULL || l_message == NULL || l_closing == NULL)
+  if (l_severity == NULL || l_message == NULL || l_meta == NULL ||
+      l_closing == NULL)
     return NULL;
 
   ret = cpy_common_repr(s);
@@ -1094,10 +1174,33 @@ static PyObject *Notification_repr(PyObject *s) {
     CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
     CPY_STRCAT_AND_DEL(&ret, tmp);
   }
+  if (self->meta &&
+      (!PyDict_Check(self->meta) || PyDict_Size(self->meta) > 0)) {
+    CPY_STRCAT(&ret, l_meta);
+    tmp = PyObject_Repr(self->meta);
+    CPY_STRCAT_AND_DEL(&ret, tmp);
+  }
   CPY_STRCAT(&ret, l_closing);
   return ret;
 }
 
+static int Notification_traverse(PyObject *self, visitproc visit, void *arg) {
+  Notification *n = (Notification *)self;
+  Py_VISIT(n->meta);
+  return 0;
+}
+
+static int Notification_clear(PyObject *self) {
+  Notification *n = (Notification *)self;
+  Py_CLEAR(n->meta);
+  return 0;
+}
+
+static void Notification_dealloc(PyObject *self) {
+  Notification_clear(self);
+  self->ob_type->tp_free(self);
+}
+
 static PyMethodDef Notification_methods[] = {
     {"dispatch", (PyCFunction)Notification_dispatch,
      METH_VARARGS | METH_KEYWORDS, dispatch_doc},
@@ -1105,6 +1208,8 @@ static PyMethodDef Notification_methods[] = {
 
 static PyMemberDef Notification_members[] = {
     {"severity", T_INT, offsetof(Notification, severity), 0, severity_doc},
+    {"meta", T_OBJECT_EX, offsetof(Notification, meta), 0,
+     notification_meta_doc},
     {NULL}};
 
 static PyGetSetDef Notification_getseters[] = {
@@ -1113,43 +1218,43 @@ static PyGetSetDef Notification_getseters[] = {
     {NULL}};
 
 PyTypeObject NotificationType = {
-    CPY_INIT_TYPE "collectd.Notification",    /* tp_name */
-    sizeof(Notification),                     /* tp_basicsize */
-    0,                                        /* Will be filled in later */
-    0,                                        /* tp_dealloc */
-    0,                                        /* tp_print */
-    0,                                        /* tp_getattr */
-    0,                                        /* tp_setattr */
-    0,                                        /* tp_compare */
-    Notification_repr,                        /* tp_repr */
-    0,                                        /* tp_as_number */
-    0,                                        /* tp_as_sequence */
-    0,                                        /* tp_as_mapping */
-    0,                                        /* tp_hash */
-    0,                                        /* tp_call */
-    0,                                        /* tp_str */
-    0,                                        /* tp_getattro */
-    0,                                        /* tp_setattro */
-    0,                                        /* tp_as_buffer */
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
-    Notification_doc,                         /* tp_doc */
-    0,                                        /* tp_traverse */
-    0,                                        /* tp_clear */
-    0,                                        /* tp_richcompare */
-    0,                                        /* tp_weaklistoffset */
-    0,                                        /* tp_iter */
-    0,                                        /* tp_iternext */
-    Notification_methods,                     /* tp_methods */
-    Notification_members,                     /* tp_members */
-    Notification_getseters,                   /* tp_getset */
-    0,                                        /* tp_base */
-    0,                                        /* tp_dict */
-    0,                                        /* tp_descr_get */
-    0,                                        /* tp_descr_set */
-    0,                                        /* tp_dictoffset */
-    Notification_init,                        /* tp_init */
-    0,                                        /* tp_alloc */
-    Notification_new                          /* tp_new */
+    CPY_INIT_TYPE "collectd.Notification", /* tp_name */
+    sizeof(Notification),                  /* tp_basicsize */
+    0,                                     /* Will be filled in later */
+    Notification_dealloc,                  /* tp_dealloc */
+    0,                                     /* tp_print */
+    0,                                     /* tp_getattr */
+    0,                                     /* tp_setattr */
+    0,                                     /* tp_compare */
+    Notification_repr,                     /* tp_repr */
+    0,                                     /* tp_as_number */
+    0,                                     /* tp_as_sequence */
+    0,                                     /* tp_as_mapping */
+    0,                                     /* tp_hash */
+    0,                                     /* tp_call */
+    0,                                     /* tp_str */
+    0,                                     /* tp_getattro */
+    0,                                     /* tp_setattro */
+    0,                                     /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+    Notification_doc,                                              /* tp_doc */
+    Notification_traverse,  /* tp_traverse */
+    Notification_clear,     /* tp_clear */
+    0,                      /* tp_richcompare */
+    0,                      /* tp_weaklistoffset */
+    0,                      /* tp_iter */
+    0,                      /* tp_iternext */
+    Notification_methods,   /* tp_methods */
+    Notification_members,   /* tp_members */
+    Notification_getseters, /* tp_getset */
+    0,                      /* tp_base */
+    0,                      /* tp_dict */
+    0,                      /* tp_descr_get */
+    0,                      /* tp_descr_set */
+    0,                      /* tp_dictoffset */
+    Notification_init,      /* tp_init */
+    0,                      /* tp_alloc */
+    Notification_new        /* tp_new */
 };
 
 static char Signed_doc[] =
index fa08b3b08f3e73e62b0af50b1213822cb25336ed..9ea82976d3569e61e62f262b24fc734364bd3b87 100644 (file)
@@ -376,18 +376,17 @@ static int cr_config_router(oconfig_item_t *ci) /* {{{ */
     }
   }
 
-  snprintf(read_name, sizeof(read_name), "routeros/%s", router_data->node);
-  if (status == 0)
-    status = plugin_register_complex_read(
-        /* group = */ NULL, read_name, cr_read, /* interval = */ 0,
-        &(user_data_t){
-            .data = router_data, .free_func = (void *)cr_free_data,
-        });
-
-  if (status != 0)
+  if (status != 0) {
     cr_free_data(router_data);
+    return status;
+  }
 
-  return status;
+  snprintf(read_name, sizeof(read_name), "routeros/%s", router_data->node);
+  return plugin_register_complex_read(
+      /* group = */ NULL, read_name, cr_read, /* interval = */ 0,
+      &(user_data_t){
+          .data = router_data, .free_func = (void *)cr_free_data,
+      });
 } /* }}} int cr_config_router */
 
 static int cr_config(oconfig_item_t *ci) {
index 41289058c4e9f4ebe3fe7e6541dd2cf2dc5ebbbe..5f42561aa39842ab999c920d1a06c3fab9087fbd 100644 (file)
 /*
  * Private types
  */
-struct rrd_cache_s {
+typedef struct rrd_cache_s {
   int values_num;
   char **values;
   cdtime_t first_value;
   cdtime_t last_value;
   int64_t random_variation;
   enum { FLAG_NONE = 0x00, FLAG_QUEUED = 0x01, FLAG_FLUSHQ = 0x02 } flags;
-};
-typedef struct rrd_cache_s rrd_cache_t;
+} rrd_cache_t;
 
 enum rrd_queue_dir_e { QUEUE_INSERT_FRONT, QUEUE_INSERT_BACK };
 typedef enum rrd_queue_dir_e rrd_queue_dir_t;
@@ -87,7 +86,7 @@ static rrdcreate_config_t rrdcreate_config = {
  * ALWAYS lock `cache_lock' first! */
 static cdtime_t cache_timeout = 0;
 static cdtime_t cache_flush_timeout = 0;
-static cdtime_t random_timeout = TIME_T_TO_CDTIME_T_STATIC(1);
+static cdtime_t random_timeout = 0;
 static cdtime_t cache_flush_last;
 static c_avl_tree_t *cache = NULL;
 static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -110,13 +109,10 @@ static int do_shutdown = 0;
 #if HAVE_THREADSAFE_LIBRRD
 static int srrd_update(char *filename, char *template, int argc,
                        const char **argv) {
-  int status;
-
   optind = 0; /* bug in librrd? */
   rrd_clear_error();
 
-  status = rrd_update_r(filename, template, argc, (void *)argv);
-
+  int status = rrd_update_r(filename, template, argc, (void *)argv);
   if (status != 0) {
     WARNING("rrdtool plugin: rrd_update_r (%s) failed: %s", filename,
             rrd_get_error());
@@ -505,7 +501,6 @@ static void rrd_cache_flush(cdtime_t timeout) {
         CDTIME_T_TO_DOUBLE(timeout));
 
   now = cdtime();
-  timeout = TIME_T_TO_CDTIME_T(timeout);
 
   /* Build a list of entries to be flushed */
   iter = c_avl_get_iterator(cache);
@@ -606,23 +601,10 @@ static int rrd_cache_flush_identifier(cdtime_t timeout,
 } /* int rrd_cache_flush_identifier */
 
 static int64_t rrd_get_random_variation(void) {
-  long min;
-  long max;
-
   if (random_timeout == 0)
     return 0;
 
-  /* Assure that "cache_timeout + random_variation" is never negative. */
-  if (random_timeout > cache_timeout) {
-    INFO("rrdtool plugin: Adjusting \"RandomTimeout\" to %.3f seconds.",
-         CDTIME_T_TO_DOUBLE(cache_timeout));
-    random_timeout = cache_timeout;
-  }
-
-  max = (long)(random_timeout / 2);
-  min = max - ((long)random_timeout);
-
-  return (int64_t)cdrand_range(min, max);
+  return (int64_t)cdrand_range(-random_timeout, random_timeout);
 } /* int64_t rrd_get_random_variation */
 
 static int rrd_cache_insert(const char *filename, const char *value,
@@ -740,7 +722,7 @@ static int rrd_cache_insert(const char *filename, const char *value,
 
   if ((cache_timeout > 0) &&
       ((cdtime() - cache_flush_last) > cache_flush_timeout))
-    rrd_cache_flush(cache_flush_timeout);
+    rrd_cache_flush(cache_timeout + random_timeout);
 
   pthread_mutex_unlock(&cache_lock);
 
@@ -808,10 +790,6 @@ static int rrd_compare_numeric(const void *a_ptr, const void *b_ptr) {
 
 static int rrd_write(const data_set_t *ds, const value_list_t *vl,
                      user_data_t __attribute__((unused)) * user_data) {
-  struct stat statbuf;
-  char filename[512];
-  char values[512];
-  int status;
 
   if (do_shutdown)
     return 0;
@@ -821,33 +799,34 @@ static int rrd_write(const data_set_t *ds, const value_list_t *vl,
     return -1;
   }
 
+  char filename[PATH_MAX];
   if (value_list_to_filename(filename, sizeof(filename), vl) != 0)
     return -1;
 
+  char values[32 * ds->ds_num];
   if (value_list_to_string(values, sizeof(values), ds, vl) != 0)
     return -1;
 
+  struct stat statbuf = {0};
   if (stat(filename, &statbuf) == -1) {
     if (errno == ENOENT) {
-      status = cu_rrd_create_file(filename, ds, vl, &rrdcreate_config);
-      if (status != 0)
+      if (cu_rrd_create_file(filename, ds, vl, &rrdcreate_config) != 0) {
         return -1;
-      else if (rrdcreate_config.async)
+      } else if (rrdcreate_config.async) {
         return 0;
+      }
     } else {
       char errbuf[1024];
-      ERROR("stat(%s) failed: %s", filename,
+      ERROR("rrdtool plugin: stat(%s) failed: %s", filename,
             sstrerror(errno, errbuf, sizeof(errbuf)));
       return -1;
     }
   } else if (!S_ISREG(statbuf.st_mode)) {
-    ERROR("stat(%s): Not a regular file!", filename);
+    ERROR("rrdtool plugin: stat(%s): Not a regular file!", filename);
     return -1;
   }
 
-  status = rrd_cache_insert(filename, values, vl->time);
-
-  return status;
+  return rrd_cache_insert(filename, values, vl->time);
 } /* int rrd_write */
 
 static int rrd_flush(cdtime_t timeout, const char *identifier,
@@ -877,7 +856,7 @@ static int rrd_config(const char *key, const char *value) {
     }
     cache_timeout = DOUBLE_TO_CDTIME_T(tmp);
   } else if (strcasecmp("CacheFlush", key) == 0) {
-    int tmp = atoi(value);
+    double tmp = atof(value);
     if (tmp < 0) {
       fprintf(stderr, "rrdtool: `CacheFlush' must "
                       "be greater than 0.\n");
@@ -885,7 +864,7 @@ static int rrd_config(const char *key, const char *value) {
             "be greater than 0.\n");
       return 1;
     }
-    cache_flush_timeout = tmp;
+    cache_flush_timeout = DOUBLE_TO_CDTIME_T(tmp);
   } else if (strcasecmp("DataDir", key) == 0) {
     char *tmp;
     size_t len;
@@ -1044,7 +1023,6 @@ static int rrd_shutdown(void) {
 
 static int rrd_init(void) {
   static int init_once = 0;
-  int status;
 
   if (init_once != 0)
     return 0;
@@ -1065,13 +1043,28 @@ static int rrd_init(void) {
 
   cache_flush_last = cdtime();
   if (cache_timeout == 0) {
+    random_timeout = 0;
     cache_flush_timeout = 0;
-  } else if (cache_flush_timeout < cache_timeout)
+  } else if (cache_flush_timeout < cache_timeout) {
+    INFO("rrdtool plugin: \"CacheFlush %.3f\" is less than \"CacheTimeout "
+         "%.3f\". "
+         "Ajusting \"CacheFlush\" to %.3f seconds.",
+         CDTIME_T_TO_DOUBLE(cache_flush_timeout),
+         CDTIME_T_TO_DOUBLE(cache_timeout),
+         CDTIME_T_TO_DOUBLE(cache_timeout * 10));
     cache_flush_timeout = 10 * cache_timeout;
+  }
+
+  /* Assure that "cache_timeout + random_variation" is never negative. */
+  if (random_timeout > cache_timeout) {
+    INFO("rrdtool plugin: Adjusting \"RandomTimeout\" to %.3f seconds.",
+         CDTIME_T_TO_DOUBLE(cache_timeout));
+    random_timeout = cache_timeout;
+  }
 
   pthread_mutex_unlock(&cache_lock);
 
-  status =
+  int status =
       plugin_thread_create(&queue_thread, /* attr = */ NULL, rrd_queue_thread,
                            /* args = */ NULL, "rrdtool queue");
   if (status != 0) {
index c299db9634406b45e8697572722c852d571bcaa4..0a20e34b7412f2a92f2b24d38f13f8188d679829 100644 (file)
@@ -63,7 +63,7 @@ struct data_definition_s {
   struct data_definition_s *next;
   char **ignores;
   size_t ignores_len;
-  int invert_match;
+  _Bool invert_match;
 };
 typedef struct data_definition_s data_definition_t;
 
@@ -71,6 +71,8 @@ struct host_definition_s {
   char *name;
   char *address;
   int version;
+  cdtime_t timeout;
+  int retries;
 
   /* snmpv1/2 options */
   char *community;
@@ -327,29 +329,14 @@ static int csnmp_config_add_data_blacklist(data_definition_t *dd,
   return 0;
 } /* int csnmp_config_add_data_blacklist */
 
-static int csnmp_config_add_data_blacklist_match_inverted(data_definition_t *dd,
-                                                          oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) {
-    WARNING("snmp plugin: `InvertMatch' needs exactly one boolean argument.");
-    return -1;
-  }
-
-  dd->invert_match = ci->values[0].value.boolean ? 1 : 0;
-
-  return 0;
-} /* int csnmp_config_add_data_blacklist_match_inverted */
-
 static int csnmp_config_add_data(oconfig_item_t *ci) {
-  data_definition_t *dd;
-  int status = 0;
-
-  dd = calloc(1, sizeof(*dd));
+  data_definition_t *dd = calloc(1, sizeof(*dd));
   if (dd == NULL)
     return -1;
 
-  status = cf_util_get_string(ci, &dd->name);
+  int status = cf_util_get_string(ci, &dd->name);
   if (status != 0) {
-    free(dd);
+    sfree(dd);
     return -1;
   }
 
@@ -376,7 +363,7 @@ static int csnmp_config_add_data(oconfig_item_t *ci) {
     else if (strcasecmp("Ignore", option->key) == 0)
       status = csnmp_config_add_data_blacklist(dd, option);
     else if (strcasecmp("InvertMatch", option->key) == 0)
-      status = csnmp_config_add_data_blacklist_match_inverted(dd, option);
+      status = cf_util_get_boolean(option, &dd->invert_match);
     else {
       WARNING("snmp plugin: Option `%s' not allowed here.", option->key);
       status = -1;
@@ -597,6 +584,10 @@ static int csnmp_config_add_host(oconfig_item_t *ci) {
   hd->sess_handle = NULL;
   hd->interval = 0;
 
+  /* These mean that we have not set a timeout or retry value */
+  hd->timeout = 0;
+  hd->retries = -1;
+
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *option = ci->children + i;
     status = 0;
@@ -607,6 +598,10 @@ static int csnmp_config_add_host(oconfig_item_t *ci) {
       status = cf_util_get_string(option, &hd->community);
     else if (strcasecmp("Version", option->key) == 0)
       status = csnmp_config_add_host_version(hd, option);
+    else if (strcasecmp("Timeout", option->key) == 0)
+      cf_util_get_cdtime(option, &hd->timeout);
+    else if (strcasecmp("Retries", option->key) == 0)
+      cf_util_get_int(option, &hd->retries);
     else if (strcasecmp("Collect", option->key) == 0)
       csnmp_config_add_host_collect(hd, option);
     else if (strcasecmp("Interval", option->key) == 0)
@@ -711,7 +706,6 @@ static int csnmp_config_add_host(oconfig_item_t *ci) {
       });
   if (status != 0) {
     ERROR("snmp plugin: Registering complex read function failed.");
-    csnmp_host_definition_destroy(hd);
     return -1;
   }
 
@@ -804,6 +798,15 @@ static void csnmp_host_open_session(host_definition_t *host) {
     sess.community_len = strlen(host->community);
   }
 
+  /* Set timeout & retries, if they have been changed from the default */
+  if (host->timeout != 0) {
+    /* net-snmp expects microseconds */
+    sess.timeout = CDTIME_T_TO_US(host->timeout);
+  }
+  if (host->retries >= 0) {
+    sess.retries = host->retries;
+  }
+
   /* snmp_sess_open will copy the `struct snmp_session *'. */
   host->sess_handle = snmp_sess_open(&sess);
 
@@ -1033,7 +1036,6 @@ static int csnmp_instance_list_add(csnmp_list_instances_t **head,
   struct variable_list *vb;
   oid_t vb_name;
   int status;
-  uint32_t is_matched;
 
   /* Set vb on the last variable */
   for (vb = res->variables; (vb != NULL) && (vb->next_variable != NULL);
@@ -1063,11 +1065,11 @@ static int csnmp_instance_list_add(csnmp_list_instances_t **head,
     char *ptr;
 
     csnmp_strvbcopy(il->instance, vb, sizeof(il->instance));
-    is_matched = 0;
+    _Bool is_matched = 0;
     for (uint32_t i = 0; i < dd->ignores_len; i++) {
       status = fnmatch(dd->ignores[i], il->instance, 0);
       if (status == 0) {
-        if (dd->invert_match == 0) {
+        if (!dd->invert_match) {
           sfree(il);
           return 0;
         } else {
@@ -1076,7 +1078,7 @@ static int csnmp_instance_list_add(csnmp_list_instances_t **head,
         }
       }
     }
-    if (dd->invert_match != 0 && is_matched == 0) {
+    if (dd->invert_match && !is_matched) {
       sfree(il);
       return 0;
     }
@@ -1328,8 +1330,6 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
 
   status = 0;
   while (status == 0) {
-    int oid_list_todo_num;
-
     req = snmp_pdu_create(SNMP_MSG_GETNEXT);
     if (req == NULL) {
       ERROR("snmp plugin: snmp_pdu_create failed.");
@@ -1337,24 +1337,33 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
       break;
     }
 
-    oid_list_todo_num = 0;
+    size_t oid_list_todo_num = 0;
+    size_t var_idx[oid_list_len];
+    memset(var_idx, 0, sizeof(var_idx));
+
     for (i = 0; i < oid_list_len; i++) {
       /* Do not rerequest already finished OIDs */
       if (!oid_list_todo[i])
         continue;
-      oid_list_todo_num++;
       snmp_add_null_var(req, oid_list[i].oid, oid_list[i].oid_len);
+      var_idx[oid_list_todo_num] = i;
+      oid_list_todo_num++;
     }
 
     if (oid_list_todo_num == 0) {
       /* The request is still empty - so we are finished */
       DEBUG("snmp plugin: all variables have left their subtree");
+      snmp_free_pdu(req);
       status = 0;
       break;
     }
 
     res = NULL;
     status = snmp_sess_synch_response(host->sess_handle, req, &res);
+
+    /* snmp_sess_synch_response always frees our req PDU */
+    req = NULL;
+
     if ((status != STAT_SUCCESS) || (res == NULL)) {
       char *errstr = NULL;
 
@@ -1368,8 +1377,6 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
         snmp_free_pdu(res);
       res = NULL;
 
-      /* snmp_synch_response already freed our PDU */
-      req = NULL;
       sfree(errstr);
       csnmp_host_close_session(host);
 
@@ -1389,6 +1396,39 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
       break;
     }
 
+    if (res->errstat != SNMP_ERR_NOERROR) {
+      if (res->errindex != 0) {
+        /* Find the OID which caused error */
+        for (i = 1, vb = res->variables; vb != NULL && i != res->errindex;
+             vb = vb->next_variable, i++)
+          /* do nothing */;
+      }
+
+      if ((res->errindex == 0) || (vb == NULL)) {
+        ERROR("snmp plugin: host %s; data %s: response error: %s (%li) ",
+              host->name, data->name, snmp_errstring(res->errstat),
+              res->errstat);
+        status = -1;
+        break;
+      }
+
+      char oid_buffer[1024] = {0};
+      snprint_objid(oid_buffer, sizeof(oid_buffer) - 1, vb->name,
+                    vb->name_length);
+      NOTICE("snmp plugin: host %s; data %s: OID `%s` failed: %s", host->name,
+             data->name, oid_buffer, snmp_errstring(res->errstat));
+
+      /* Get value index from todo list and skip OID found */
+      assert(res->errindex <= oid_list_todo_num);
+      i = var_idx[res->errindex - 1];
+      assert(i < oid_list_len);
+      oid_list_todo[i] = 0;
+
+      snmp_free_pdu(res);
+      res = NULL;
+      continue;
+    }
+
     for (vb = res->variables, i = 0; (vb != NULL);
          vb = vb->next_variable, i++) {
       /* Calculate value index from todo list */
@@ -1484,10 +1524,6 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
     snmp_free_pdu(res);
   res = NULL;
 
-  if (req != NULL)
-    snmp_free_pdu(req);
-  req = NULL;
-
   if (status == 0)
     csnmp_dispatch_table(host, data, instance_list_head, value_list_head);
 
index a5531c24e86247532ca68323cc1200818e9eebc2..78f05c5f968971ac961c3637935e6aaa7f55108c 100644 (file)
@@ -111,6 +111,7 @@ static int pagesize;
 
 static _Bool values_absolute = 1;
 static _Bool values_percentage = 0;
+static _Bool report_io = 1;
 
 static int swap_config(oconfig_item_t *ci) /* {{{ */
 {
@@ -136,6 +137,8 @@ static int swap_config(oconfig_item_t *ci) /* {{{ */
       cf_util_get_boolean(child, &values_absolute);
     else if (strcasecmp("ValuesPercentage", child->key) == 0)
       cf_util_get_boolean(child, &values_percentage);
+    else if (strcasecmp("ReportIO", child->key) == 0)
+      cf_util_get_boolean(child, &report_io);
     else
       WARNING("swap plugin: Unknown config option: \"%s\"", child->key);
   }
@@ -406,7 +409,8 @@ static int swap_read(void) /* {{{ */
   else
     swap_read_combined();
 
-  swap_read_io();
+  if (report_io)
+    swap_read_io();
 
   return 0;
 } /* }}} int swap_read */
@@ -726,8 +730,11 @@ static int swap_read(void) /* {{{ */
   reserved = (gauge_t)(pmemory.pgsp_rsvd * pagesize);
 
   swap_submit_usage(NULL, total - free, free, "reserved", reserved);
-  swap_submit_derive("in", (derive_t)pmemory.pgspins * pagesize);
-  swap_submit_derive("out", (derive_t)pmemory.pgspouts * pagesize);
+
+  if (report_io) {
+    swap_submit_derive("in", (derive_t)pmemory.pgspins * pagesize);
+    swap_submit_derive("out", (derive_t)pmemory.pgspouts * pagesize);
+  }
 
   return 0;
 } /* }}} int swap_read */
diff --git a/src/synproxy.c b/src/synproxy.c
new file mode 100644 (file)
index 0000000..6c6b997
--- /dev/null
@@ -0,0 +1,112 @@
+/**
+ * collectd - src/synproxy.c
+ * Copyright (C) 2017 Marek Becka
+ *
+ * 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
+ *
+ * Authors:
+ *   Marek Becka <https://github.com/marekbecka>
+ **/
+
+#include "collectd.h"
+
+#include "common.h"
+#include "plugin.h"
+
+#if !KERNEL_LINUX
+#error "No applicable input method."
+#endif
+
+#define SYNPROXY_FIELDS 6
+
+static const char *synproxy_stat_path = "/proc/net/stat/synproxy";
+
+static const char *column_names[SYNPROXY_FIELDS] = {
+    "entries", "syn_received",   "invalid",
+    "valid",   "retransmission", "reopened"};
+static const char *column_types[SYNPROXY_FIELDS] = {
+    "current_connections", "connections", "cookies", "cookies", "cookies",
+    "connections"};
+
+static void synproxy_submit(value_t *results) {
+  value_list_t vl = VALUE_LIST_INIT;
+
+  /* 1st column (entries) is hardcoded to 0 in kernel code */
+  for (size_t n = 1; n < SYNPROXY_FIELDS; n++) {
+    vl.values = &results[n];
+    vl.values_len = 1;
+
+    sstrncpy(vl.plugin, "synproxy", sizeof(vl.plugin));
+    sstrncpy(vl.type, column_types[n], sizeof(vl.type));
+    sstrncpy(vl.type_instance, column_names[n], sizeof(vl.type_instance));
+
+    plugin_dispatch_values(&vl);
+  }
+}
+
+static int synproxy_read(void) {
+  char buf[1024];
+  value_t results[SYNPROXY_FIELDS];
+  int is_header = 1, status = 0;
+
+  FILE *fh = fopen(synproxy_stat_path, "r");
+  if (fh == NULL) {
+    ERROR("synproxy plugin: unable to open %s", synproxy_stat_path);
+    return -1;
+  }
+
+  memset(results, 0, sizeof(results));
+
+  while (fgets(buf, sizeof(buf), fh) != NULL) {
+    char *fields[SYNPROXY_FIELDS], *endprt;
+
+    if (is_header) {
+      is_header = 0;
+      continue;
+    }
+
+    int numfields = strsplit(buf, fields, STATIC_ARRAY_SIZE(fields));
+    if (numfields != SYNPROXY_FIELDS) {
+      ERROR("synproxy plugin: unexpected number of columns in %s",
+            synproxy_stat_path);
+      status = -1;
+      break;
+    }
+
+    /* 1st column (entries) is hardcoded to 0 in kernel code */
+    for (size_t n = 1; n < SYNPROXY_FIELDS; n++) {
+      char *endptr = NULL;
+      errno = 0;
+
+      results[n].derive += strtoull(fields[n], &endprt, 16);
+      if ((endptr == fields[n]) || errno != 0) {
+        ERROR("synproxy plugin: unable to parse value: %s", fields[n]);
+        fclose(fh);
+        return -1;
+      }
+    }
+  }
+
+  fclose(fh);
+
+  if (status == 0) {
+    synproxy_submit(results);
+  }
+
+  return status;
+}
+
+void module_register(void) {
+  plugin_register_read("synproxy", synproxy_read);
+} /* void module_register */
index 578e019ee5b120c28369d00b3c038121ce26cb93..5fb51512ffb8ec1bb474d3f4b67d6a10e02ab9be 100644 (file)
@@ -55,6 +55,7 @@ typedef struct {
 typedef struct {
   char *file;
   char *sep;
+  char *plugin_name;
   char *instance;
 
   tbl_result_t *results;
@@ -92,6 +93,7 @@ static void tbl_result_clear(tbl_result_t *res) {
 static void tbl_setup(tbl_t *tbl, char *file) {
   tbl->file = sstrdup(file);
   tbl->sep = NULL;
+  tbl->plugin_name = NULL;
   tbl->instance = NULL;
 
   tbl->results = NULL;
@@ -103,6 +105,7 @@ static void tbl_setup(tbl_t *tbl, char *file) {
 static void tbl_clear(tbl_t *tbl) {
   sfree(tbl->file);
   sfree(tbl->sep);
+  sfree(tbl->plugin_name);
   sfree(tbl->instance);
 
   for (size_t i = 0; i < tbl->results_num; ++i)
@@ -256,6 +259,8 @@ static int tbl_config_table(oconfig_item_t *ci) {
 
     if (0 == strcasecmp(c->key, "Separator"))
       tbl_config_set_s(c->key, &tbl->sep, c);
+    else if (0 == strcasecmp(c->key, "Plugin"))
+      tbl_config_set_s(c->key, &tbl->plugin_name, c);
     else if (0 == strcasecmp(c->key, "Instance"))
       tbl_config_set_s(c->key, &tbl->instance, c);
     else if (0 == strcasecmp(c->key, "Result"))
@@ -367,7 +372,8 @@ static int tbl_result_dispatch(tbl_t *tbl, tbl_result_t *res, char **fields,
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE(values);
 
-  sstrncpy(vl.plugin, "table", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, (tbl->plugin_name != NULL) ? tbl->plugin_name : "table",
+           sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, tbl->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, res->type, sizeof(vl.type));
 
index 407970ae70e947fe2a5f7d0bf12d1106700baf68..fbba4788fe5b514b27c9891c8b000fcb645ce27d 100644 (file)
@@ -34,7 +34,8 @@
 /*
  *  <Plugin tail>
  *    <File "/var/log/exim4/mainlog">
- *     Instance "exim"
+ *      Plugin "mail"
+ *      Instance "exim"
  *      Interval 60
  *     <Match>
  *       Regex "S=([1-9][0-9]*)"
@@ -134,6 +135,7 @@ static int ctail_config_add_match_dstype(ctail_config_match_t *cm,
 } /* int ctail_config_add_match_dstype */
 
 static int ctail_config_add_match(cu_tail_match_t *tm,
+                                  const char *plugin_name,
                                   const char *plugin_instance,
                                   oconfig_item_t *ci, cdtime_t interval) {
   ctail_config_match_t cm = {0};
@@ -191,7 +193,8 @@ static int ctail_config_add_match(cu_tail_match_t *tm,
   if (status == 0) {
     // TODO(octo): there's nothing "simple" about the latency stuff …
     status = tail_match_add_match_simple(
-        tm, cm.regex, cm.excluderegex, cm.flags, "tail", plugin_instance,
+        tm, cm.regex, cm.excluderegex, cm.flags,
+        (plugin_name != NULL) ? plugin_name : "tail", plugin_instance,
         cm.type, cm.type_instance, cm.latency, interval);
 
     if (status != 0)
@@ -210,6 +213,7 @@ static int ctail_config_add_match(cu_tail_match_t *tm,
 static int ctail_config_add_file(oconfig_item_t *ci) {
   cu_tail_match_t *tm;
   cdtime_t interval = 0;
+  char *plugin_name = NULL;
   char *plugin_instance = NULL;
   int num_matches = 0;
 
@@ -229,12 +233,15 @@ static int ctail_config_add_file(oconfig_item_t *ci) {
     oconfig_item_t *option = ci->children + i;
     int status = 0;
 
-    if (strcasecmp("Instance", option->key) == 0)
+    if (strcasecmp("Plugin", option->key) == 0)
+      status = cf_util_get_string (option, &plugin_name);
+    else if (strcasecmp("Instance", option->key) == 0)
       status = cf_util_get_string(option, &plugin_instance);
     else if (strcasecmp("Interval", option->key) == 0)
       cf_util_get_cdtime(option, &interval);
     else if (strcasecmp("Match", option->key) == 0) {
-      status = ctail_config_add_match(tm, plugin_instance, option, interval);
+      status = ctail_config_add_match(tm, plugin_name, plugin_instance, option,
+                                      interval);
       if (status == 0)
         num_matches++;
       /* Be mild with failed matches.. */
@@ -247,6 +254,7 @@ static int ctail_config_add_file(oconfig_item_t *ci) {
       break;
   } /* for (i = 0; i < ci->children_num; i++) */
 
+  sfree(plugin_name);
   sfree(plugin_instance);
 
   if (num_matches == 0) {
index 9d0a15bee75444d03111eb402250c1b1d96d33d7..2e3ac5f0bbd639a27e4f61b5d74143c9566eb3f6 100644 (file)
@@ -44,6 +44,7 @@ struct metric_definition_s {
 typedef struct metric_definition_s metric_definition_t;
 
 struct instance_definition_s {
+  char *plugin_name;
   char *instance;
   char *path;
   cu_tail_t *tail;
@@ -67,7 +68,8 @@ static int tcsv_submit(instance_definition_t *id, metric_definition_t *md,
   vl.values_len = 1;
   vl.values = &v;
 
-  sstrncpy(vl.plugin, "tail_csv", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, (id->plugin_name != NULL) ? id->plugin_name : "tail_csv",
+           sizeof(vl.plugin));
   if (id->instance != NULL)
     sstrncpy(vl.plugin_instance, id->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, md->type, sizeof(vl.type));
@@ -358,6 +360,7 @@ static void tcsv_instance_definition_destroy(void *arg) {
     cu_tail_destroy(id->tail);
   id->tail = NULL;
 
+  sfree(id->plugin_name);
   sfree(id->instance);
   sfree(id->path);
   sfree(id->metric_list);
@@ -420,6 +423,7 @@ static int tcsv_config_add_file(oconfig_item_t *ci) {
   id = calloc(1, sizeof(*id));
   if (id == NULL)
     return -1;
+  id->plugin_name = NULL;
   id->instance = NULL;
   id->path = NULL;
   id->metric_list = NULL;
@@ -447,6 +451,8 @@ static int tcsv_config_add_file(oconfig_item_t *ci) {
       cf_util_get_cdtime(option, &id->interval);
     else if (strcasecmp("TimeFrom", option->key) == 0)
       status = tcsv_config_get_index(option, &id->time_from);
+    else if (strcasecmp("Plugin", option->key) == 0)
+      status = cf_util_get_string(option, &id->plugin_name);
     else {
       WARNING("tail_csv plugin: Option `%s' not allowed here.", option->key);
       status = -1;
@@ -484,7 +490,6 @@ static int tcsv_config_add_file(oconfig_item_t *ci) {
       });
   if (status != 0) {
     ERROR("tail_csv plugin: Registering complex read function failed.");
-    tcsv_instance_definition_destroy(id);
     return -1;
   }
 
index 94b4e3ab4b6dc9112282969b509d852627dbad74..3b64b75fe2c1ba859535614be60b0a598aeffc49 100644 (file)
--- a/src/ted.c
+++ b/src/ted.c
@@ -88,7 +88,7 @@ static int ted_read_value(double *ret_power, double *ret_voltage) {
 
   status = write(fd, pkt_request, sizeof(pkt_request));
   if (status <= 0) {
-    ERROR("ted plugin: swrite failed.");
+    ERROR("ted plugin: write failed.");
     return -1;
   }
 
index 1049fb2ad3ce0803254e0cb35c8381a40234e86a..e4419b85689ea5003d06b799ec37982abcecfa88 100644 (file)
@@ -41,7 +41,7 @@
 #include "plugin.h"
 #include "utils_time.h"
 
-#include <asm/msr-index.h>
+#include "msr-index.h"
 #include <cpuid.h>
 #ifdef HAVE_SYS_CAPABILITY_H
 #include <sys/capability.h>
@@ -585,7 +585,10 @@ static int submit_counters(struct thread_data *t, struct core_data *c,
 
   /* If not using logical core numbering, set core id */
   if (!config_lcn) {
-    snprintf(name, sizeof(name), "core%02d", c->core_id);
+    if (topology.num_packages > 1)
+      snprintf(name, sizeof(name), "pkg%02d-core%02d", p->package_id, c->core_id);
+    else
+      snprintf(name, sizeof(name), "core%02d", c->core_id);
   }
 
   if (do_core_cstate & (1 << 3))
index c9838e1612aaaee4a68488a0f941fbb58b45c69b..577d28f184c8a67cca60754fa34c0c4618a16834 100644 (file)
@@ -34,6 +34,7 @@ compression_ratio       value:GAUGE:0:2
 connections             value:DERIVE:0:U
 conntrack               value:GAUGE:0:4294967295
 contextswitch           value:DERIVE:0:U
+cookies                 value:DERIVE:0:U
 count                   value:GAUGE:0:U
 counter                 value:COUNTER:U:U
 cpu                     value:DERIVE:0:U
@@ -117,6 +118,7 @@ if_tx_octets            value:DERIVE:0:U
 if_tx_packets           value:DERIVE:0:U
 invocations             value:DERIVE:0:U
 io_octets               rx:DERIVE:0:U, tx:DERIVE:0:U
+io_ops                  read:DERIVE:0:U, write:DERIVE:0:U
 io_packets              rx:DERIVE:0:U, tx:DERIVE:0:U
 ipc                     value:GAUGE:0:U
 ipt_bytes               value:DERIVE:0:U
@@ -189,6 +191,7 @@ ping                    value:GAUGE:0:65535
 ping_droprate           value:GAUGE:0:100
 ping_stddev             value:GAUGE:0:65535
 players                 value:GAUGE:0:1000000
+pools                   value:GAUGE:0:U
 power                   value:GAUGE:U:U
 pressure                value:GAUGE:0:U
 protocol_counter        value:DERIVE:0:U
index 00b930e660f0c126d9db15e0658e8c4c50f14bb3..99e39eee4f39f0a771408c8a08bf09c750857282 100644 (file)
@@ -134,7 +134,13 @@ static int us_open_socket(void) {
     const char *grpname;
     struct group *g;
     struct group sg;
-    char grbuf[4096];
+
+    long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX);
+    if (grbuf_size <= 0)
+      grbuf_size = sysconf(_SC_PAGESIZE);
+    if (grbuf_size <= 0)
+      grbuf_size = 4096;
+    char grbuf[grbuf_size];
 
     grpname = (sock_group != NULL) ? sock_group : COLLECTD_GRP_NAME;
     g = NULL;
index 2be9d79da0d793af64685c8171da2a29887b5edd..d51aa391a09040229113c4accae5bb387375b897 100644 (file)
@@ -25,8 +25,7 @@
 #include "plugin.h"
 
 #if KERNEL_LINUX
-#define STAT_FILE "/proc/stat"
-/* Using /proc filesystem to retrieve the boot time, Linux only. */
+#include <sys/sysinfo.h>
 /* #endif KERNEL_LINUX */
 
 #elif HAVE_LIBKSTAT
@@ -53,8 +52,6 @@
 /*
  * Global variables
  */
-/* boottime always used, no OS distinction */
-static time_t boottime;
 
 #if HAVE_LIBKSTAT
 extern kstat_ctl_t *kc;
@@ -72,8 +69,6 @@ static void uptime_submit(gauge_t value) {
   plugin_dispatch_values(&vl);
 }
 
-static int uptime_init(void) /* {{{ */
-{
 /*
  * On most unix systems the uptime is calculated by looking at the boot
  * time (stored in unix time, since epoch) and the current one. We are
@@ -84,48 +79,21 @@ static int uptime_init(void) /* {{{ */
  * the boot time, the plugin is unregistered and there is no chance to
  * try again later. Nevertheless, this is very unlikely to happen.
  */
-
+static time_t uptime_get_sys(void) { /* {{{ */
+  time_t result;
 #if KERNEL_LINUX
-  unsigned long starttime;
-  char buffer[1024];
-  int ret;
-  FILE *fh;
-
-  ret = 0;
-
-  fh = fopen(STAT_FILE, "r");
+  struct sysinfo info;
+  int status;
 
-  if (fh == NULL) {
+  status = sysinfo(&info);
+  if (status != 0) {
     char errbuf[1024];
-    ERROR("uptime plugin: Cannot open " STAT_FILE ": %s",
+    ERROR("uptime plugin: Error calling sysinfo: %s",
           sstrerror(errno, errbuf, sizeof(errbuf)));
     return -1;
   }
 
-  while (fgets(buffer, 1024, fh) != NULL) {
-    /* look for the btime string and read the value */
-    ret = sscanf(buffer, "btime %lu", &starttime);
-    /* avoid further loops if btime has been found and read
-     * correctly (hopefully) */
-    if (ret == 1)
-      break;
-  }
-
-  fclose(fh);
-
-  /* loop done, check if no value has been found/read */
-  if (ret != 1) {
-    ERROR("uptime plugin: No value read from " STAT_FILE "");
-    return -1;
-  }
-
-  boottime = (time_t)starttime;
-
-  if (boottime == 0) {
-    ERROR("uptime plugin: btime read from " STAT_FILE ", "
-          "but `boottime' is zero!");
-    return -1;
-  }
+  result = (time_t)info.uptime;
 /* #endif KERNEL_LINUX */
 
 #elif HAVE_LIBKSTAT
@@ -159,13 +127,13 @@ static int uptime_init(void) /* {{{ */
     return -1;
   }
 
-  boottime = (time_t)knp->value.ui32;
-
-  if (boottime == 0) {
+  if (knp->value.ui32 == 0) {
     ERROR("uptime plugin: kstat_data_lookup returned success, "
           "but `boottime' is zero!");
     return -1;
   }
+
+  result = time(NULL) - (time_t)knp->value.ui32;
 /* #endif HAVE_LIBKSTAT */
 
 #elif HAVE_SYS_SYSCTL_H
@@ -186,13 +154,13 @@ static int uptime_init(void) /* {{{ */
     return -1;
   }
 
-  boottime = boottv.tv_sec;
-
-  if (boottime == 0) {
+  if (boottv.tv_sec == 0) {
     ERROR("uptime plugin: sysctl(3) returned success, "
           "but `boottime' is zero!");
     return -1;
   }
+
+  result = time(NULL) - boottv.tv_sec;
 /* #endif HAVE_SYS_SYSCTL_H */
 
 #elif HAVE_PERFSTAT
@@ -212,18 +180,18 @@ static int uptime_init(void) /* {{{ */
   if (hertz <= 0)
     hertz = HZ;
 
-  boottime = time(NULL) - cputotal.lbolt / hertz;
+  result = cputotal.lbolt / hertz;
 #endif /* HAVE_PERFSTAT */
 
-  return 0;
-} /* }}} int uptime_init */
+  return result;
+} /* }}} int uptime_get_sys */
 
 static int uptime_read(void) {
   gauge_t uptime;
   time_t elapsed;
 
   /* calculate the amount of time elapsed since boot, AKA uptime */
-  elapsed = time(NULL) - boottime;
+  elapsed = uptime_get_sys();
 
   uptime = (gauge_t)elapsed;
 
@@ -233,6 +201,5 @@ static int uptime_read(void) {
 }
 
 void module_register(void) {
-  plugin_register_init("uptime", uptime_init);
   plugin_register_read("uptime", uptime_read);
 } /* void module_register */
index 2a426646f86905a2b8695dfd55f4a6b095d86e59..2a1d9de3945cc94d24363d021fbdd435b4542e7f 100644 (file)
@@ -218,15 +218,15 @@ int curl_stats_dispatch(curl_stats_t *s, CURL *curl, const char *hostname,
 
   if (s == NULL)
     return 0;
-  if ((curl == NULL) || (hostname == NULL) || (plugin == NULL)) {
+  if ((curl == NULL) || (plugin == NULL)) {
     ERROR("curl stats: dispatch() called with missing arguments "
-          "(curl=%p; hostname=%s; plugin=%s)",
-          curl, hostname == NULL ? "<NULL>" : hostname,
-          plugin == NULL ? "<NULL>" : plugin);
+          "(curl=%p; plugin=%s)",
+          curl, plugin == NULL ? "<NULL>" : plugin);
     return -1;
   }
 
-  sstrncpy(vl.host, hostname, sizeof(vl.host));
+  if (hostname != NULL)
+    sstrncpy(vl.host, hostname, sizeof(vl.host));
   sstrncpy(vl.plugin, plugin, sizeof(vl.plugin));
   if (plugin_instance != NULL)
     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
index 0128c575e37c167093c66ac28b9b715b17822908..460f807a5631c5be49f75f8efe68c2e3452882fa 100644 (file)
@@ -183,7 +183,8 @@ static int value_list_to_kairosdb(char *buffer, size_t buffer_size, /* {{{ */
                                   const data_set_t *ds, const value_list_t *vl,
                                   int store_rates,
                                   char const *const *http_attrs,
-                                  size_t http_attrs_num, int data_ttl) {
+                                  size_t http_attrs_num, int data_ttl,
+                                  char const *metrics_prefix) {
   char temp[512];
   size_t offset = 0;
   int status;
@@ -212,11 +213,13 @@ static int value_list_to_kairosdb(char *buffer, size_t buffer_size, /* {{{ */
   for (size_t i = 0; i < ds->ds_num; i++) {
     /* All value lists have a leading comma. The first one will be replaced with
      * a square bracket in `format_kairosdb_finalize'. */
-    BUFFER_ADD(",{");
+    BUFFER_ADD(",{\"name\":\"");
 
-    BUFFER_ADD("\"name\":\"collectd");
+    if (metrics_prefix != NULL) {
+      BUFFER_ADD("%s.", metrics_prefix);
+    }
 
-    BUFFER_ADD(".%s", vl->plugin);
+    BUFFER_ADD("%s", vl->plugin);
 
     status = values_to_kairosdb(temp, sizeof(temp), ds, vl, store_rates, i);
     if (status != 0)
@@ -263,12 +266,14 @@ static int format_kairosdb_value_list_nocheck(
     char *buffer, /* {{{ */
     size_t *ret_buffer_fill, size_t *ret_buffer_free, const data_set_t *ds,
     const value_list_t *vl, int store_rates, size_t temp_size,
-    char const *const *http_attrs, size_t http_attrs_num, int data_ttl) {
+    char const *const *http_attrs, size_t http_attrs_num, int data_ttl,
+    char const *metrics_prefix) {
   char temp[temp_size];
   int status;
 
   status = value_list_to_kairosdb(temp, sizeof(temp), ds, vl, store_rates,
-                                  http_attrs, http_attrs_num, data_ttl);
+                                  http_attrs, http_attrs_num, data_ttl,
+                                  metrics_prefix);
   if (status != 0)
     return status;
   temp_size = strlen(temp);
@@ -337,7 +342,8 @@ int format_kairosdb_value_list(char *buffer, /* {{{ */
                                size_t *ret_buffer_fill, size_t *ret_buffer_free,
                                const data_set_t *ds, const value_list_t *vl,
                                int store_rates, char const *const *http_attrs,
-                               size_t http_attrs_num, int data_ttl) {
+                               size_t http_attrs_num, int data_ttl,
+                               char const *metrics_prefix) {
   if ((buffer == NULL) || (ret_buffer_fill == NULL) ||
       (ret_buffer_free == NULL) || (ds == NULL) || (vl == NULL))
     return -EINVAL;
@@ -347,7 +353,8 @@ int format_kairosdb_value_list(char *buffer, /* {{{ */
 
   return format_kairosdb_value_list_nocheck(
       buffer, ret_buffer_fill, ret_buffer_free, ds, vl, store_rates,
-      (*ret_buffer_free) - 2, http_attrs, http_attrs_num, data_ttl);
+      (*ret_buffer_free) - 2, http_attrs, http_attrs_num, data_ttl,
+      metrics_prefix);
 } /* }}} int format_kairosdb_value_list */
 
 /* vim: set sw=2 sts=2 et fdm=marker : */
index 3a4c7c7767f3fc14e21aea417d7cd726cbe0ecae..7b9e0e7ac7154cde3798ed5e06aa16d1ffd5b09c 100644 (file)
@@ -41,7 +41,8 @@ int format_kairosdb_value_list(char *buffer, size_t *ret_buffer_fill,
                                size_t *ret_buffer_free, const data_set_t *ds,
                                const value_list_t *vl, int store_rates,
                                char const *const *http_attrs,
-                               size_t http_attrs_num, int data_ttl);
+                               size_t http_attrs_num, int data_ttl,
+                               char const *metrics_prefix);
 int format_kairosdb_finalize(char *buffer, size_t *ret_buffer_fill,
                              size_t *ret_buffer_free);
 
index 0f8c2a269e088b6c97800b87019f1876028b8a32..5eb5b6d9697265a1ef3fd0737170fd36fa993a8c 100644 (file)
@@ -105,6 +105,8 @@ int latency_config(latency_config_t *conf, oconfig_item_t *ci,
       status = latency_config_add_percentile(conf, child, plugin);
     else if (strcasecmp("Bucket", child->key) == 0)
       status = latency_config_add_bucket(conf, child, plugin);
+    else if (strcasecmp("BucketType", child->key) == 0)
+      status = cf_util_get_string(child, &conf->bucket_type);
     else
       WARNING("%s plugin: \"%s\" is not a valid option within a \"%s\" block.",
               plugin, child->key, ci->key);
@@ -137,6 +139,14 @@ int latency_config_copy(latency_config_t *dst, const latency_config_t src) {
     return ENOMEM;
   }
 
+  if (src.bucket_type != NULL) {
+    dst->bucket_type = strdup(src.bucket_type);
+    if (dst->bucket_type == NULL) {
+      latency_config_free(*dst);
+      return ENOMEM;
+    }
+  }
+
   memmove(dst->percentile, src.percentile,
           dst->percentile_num * sizeof(*dst->percentile));
   memmove(dst->buckets, src.buckets, dst->buckets_num * sizeof(*dst->buckets));
@@ -147,4 +157,5 @@ int latency_config_copy(latency_config_t *dst, const latency_config_t src) {
 void latency_config_free(latency_config_t conf) {
   sfree(conf.percentile);
   sfree(conf.buckets);
+  sfree(conf.bucket_type);
 } /* void latency_config_free */
index 9a7a11af985c315729be27e57d9160827945c464..7008fd000bfe5512f38c7ad7abe81400058c5b07 100644 (file)
@@ -44,6 +44,7 @@ typedef struct {
 
   latency_bucket_t *buckets;
   size_t buckets_num;
+  char *bucket_type;
 
   /*
   _Bool lower;
index df70fbecc366ed62b4b7ffad953aa817bac00be5..d3edb57ccbaeac885a429fdc0eb435f54df0b165 100644 (file)
@@ -294,7 +294,7 @@ void match_value_reset(cu_match_value_t *mv) {
   /* Reset GAUGE metrics only and except GAUGE_PERSIST. */
   if ((mv->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) &&
       !(mv->ds_type & UTILS_MATCH_CF_GAUGE_PERSIST)) {
-    mv->value.gauge = NAN;
+    mv->value.gauge = (mv->ds_type & UTILS_MATCH_CF_GAUGE_INC) ? 0 : NAN;
     mv->values_num = 0;
   }
 } /* }}} void match_value_reset */
index af8bbe7c2dcad17b152cd460ff06083fafee73e2..65655dcd12b7b57aefb9070a87c242a394ecc898 100644 (file)
@@ -108,7 +108,6 @@ static int latency_submit_match(cu_match_t *match, void *user_data) {
   if (match_value == NULL)
     return -1;
 
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, data->plugin, sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, data->plugin_instance,
            sizeof(vl.plugin_instance));
@@ -138,7 +137,11 @@ static int latency_submit_match(cu_match_t *match, void *user_data) {
   }
 
   /* Submit buckets */
-  sstrncpy(vl.type, "bucket", sizeof(vl.type));
+  if (data->latency_config.bucket_type != NULL)
+    sstrncpy(vl.type, data->latency_config.bucket_type, sizeof(vl.type));
+  else
+    sstrncpy(vl.type, "bucket", sizeof(vl.type));
+
   for (size_t i = 0; i < data->latency_config.buckets_num; i++) {
     latency_bucket_t bucket = data->latency_config.buckets[i];
 
index 7c5a9ef07fa980adc102d57356da3b9b4b50259c..e4daf4bf84b4e9f59b6215e4462c0d57e383e4ed 100644 (file)
@@ -21,6 +21,7 @@
  *   Jérôme Renard <jerome.renard at gmail.com>
  *   Marc Fournier <marc.fournier at camptocamp.com>
  *   Florian octo Forster <octo at collectd.org>
+ *   Denes Matetelki <dmatetelki at varnish-software.com>
  **/
 
 #include "collectd.h"
@@ -69,6 +70,8 @@ struct user_config_s {
   _Bool collect_sms;
 #if HAVE_VARNISH_V2
   _Bool collect_sm;
+#endif
+#if HAVE_VARNISH_V2 || HAVE_VARNISH_V4
   _Bool collect_sma;
 #endif
   _Bool collect_struct;
@@ -80,6 +83,12 @@ struct user_config_s {
   _Bool collect_workers;
 #if HAVE_VARNISH_V4
   _Bool collect_vsm;
+  _Bool collect_lck;
+  _Bool collect_mempool;
+  _Bool collect_mgt;
+  _Bool collect_smf;
+  _Bool collect_vbe;
+  _Bool collect_mse;
 #endif
 };
 typedef struct user_config_s user_config_t; /* }}} */
@@ -182,6 +191,14 @@ static int varnish_monitor(void *priv,
     else if (strcmp(name, "client_req") == 0)
       return varnish_submit_derive(conf->instance, "connections", "connections",
                                    "received", val);
+#ifdef HAVE_VARNISH_V4
+    else if (strcmp(name, "client_req_400") == 0)
+      return varnish_submit_derive(conf->instance, "connections", "connections",
+                                   "error_400", val);
+    else if (strcmp(name, "client_req_417") == 0)
+      return varnish_submit_derive(conf->instance, "connections", "connections",
+                                   "error_417", val);
+#endif
   }
 
 #ifdef HAVE_VARNISH_V3
@@ -211,6 +228,9 @@ static int varnish_monitor(void *priv,
     else if (strcmp(name, "esi_warnings") == 0)
       return varnish_submit_derive(conf->instance, "esi", "total_operations",
                                    "warning", val);
+    else if (strcmp(name, "esi_maxdepth") == 0)
+      return varnish_submit_derive(conf->instance, "esi", "total_operations",
+                                   "max_depth", val);
   }
 
   if (conf->collect_backend) {
@@ -286,6 +306,20 @@ static int varnish_monitor(void *priv,
     else if (strcmp(name, "fetch_304") == 0)
       return varnish_submit_derive(conf->instance, "fetch", "http_requests",
                                    "no_body_304", val);
+#if HAVE_VARNISH_V4
+    else if (strcmp(name, "fetch_no_thread") == 0)
+      return varnish_submit_derive(conf->instance, "fetch", "http_requests",
+                                   "no_thread", val);
+    else if (strcmp(name, "fetch_none") == 0)
+      return varnish_submit_derive(conf->instance, "fetch", "http_requests",
+                                   "none", val);
+    else if (strcmp(name, "busy_sleep") == 0)
+      return varnish_submit_derive(conf->instance, "fetch", "http_requests",
+                                   "busy_sleep", val);
+    else if (strcmp(name, "busy_wakeup") == 0)
+      return varnish_submit_derive(conf->instance, "fetch", "http_requests",
+                                   "busy_wakeup", val);
+#endif
   }
 
   if (conf->collect_hcb) {
@@ -331,6 +365,14 @@ static int varnish_monitor(void *priv,
     else if (strcmp(name, "n_objoverflow") == 0)
       return varnish_submit_derive(conf->instance, "objects", "total_objects",
                                    "workspace_overflow", val);
+#if HAVE_VARNISH_V4
+    else if (strcmp(name, "exp_mailed") == 0)
+      return varnish_submit_gauge(conf->instance, "struct", "objects",
+                                  "exp_mailed", val);
+    else if (strcmp(name, "exp_received") == 0)
+      return varnish_submit_gauge(conf->instance, "struct", "objects",
+                                  "exp_received", val);
+#endif
   }
 
 #if HAVE_VARNISH_V3
@@ -381,6 +423,34 @@ static int varnish_monitor(void *priv,
     else if (strcmp(name, "bans_dups") == 0)
       return varnish_submit_derive(conf->instance, "ban", "total_operations",
                                    "duplicate", val);
+    else if (strcmp(name, "bans_tested") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_operations",
+                                   "tested", val);
+    else if (strcmp(name, "bans_lurker_contention") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_operations",
+                                   "lurker_contention", val);
+    else if (strcmp(name, "bans_lurker_obj_killed") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_operations",
+                                   "lurker_obj_killed", val);
+    else if (strcmp(name, "bans_lurker_tested") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_operations",
+                                   "lurker_tested", val);
+    else if (strcmp(name, "bans_lurker_tests_tested") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_operations",
+                                   "lurker_tests_tested", val);
+    else if (strcmp(name, "bans_obj_killed") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_operations",
+                                   "obj_killed", val);
+    else if (strcmp(name, "bans_persisted_bytes") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_bytes",
+                                   "persisted_bytes", val);
+    else if (strcmp(name, "bans_persisted_fragmentation") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_bytes",
+                                   "persisted_fragmentation", val);
+    else if (strcmp(name, "bans_tests_tested") == 0)
+      return varnish_submit_derive(conf->instance, "ban", "total_operations",
+                                   "tests_tested", val);
+
   }
 #endif
 
@@ -415,6 +485,14 @@ static int varnish_monitor(void *priv,
     else if (strcmp(name, "sess_herd") == 0)
       return varnish_submit_derive(conf->instance, "session",
                                    "total_operations", "herd", val);
+#if HAVE_VARNISH_V4
+    else if (strcmp(name, "sess_closed_err") == 0)
+      return varnish_submit_derive(conf->instance, "session",
+                                   "total_operations", "closed_err", val);
+    else if (strcmp(name, "sess_dropped") == 0)
+      return varnish_submit_derive(conf->instance, "session",
+                                   "total_operations", "dropped_for_thread", val);
+#endif
   }
 
   if (conf->collect_shm) {
@@ -514,12 +592,18 @@ static int varnish_monitor(void *priv,
     else if (strcmp(name, "s_req_bodybytes") == 0)
       return varnish_submit_derive(conf->instance, "totals", "total_bytes",
                                    "req_body", val);
+    else if (strcmp(name, "s_req_protobytes") == 0)
+      return varnish_submit_derive(conf->instance, "totals", "total_bytes",
+                                   "req_proto", val);
     else if (strcmp(name, "s_resp_hdrbytes") == 0)
       return varnish_submit_derive(conf->instance, "totals", "total_bytes",
                                    "resp_header", val);
     else if (strcmp(name, "s_resp_bodybytes") == 0)
       return varnish_submit_derive(conf->instance, "totals", "total_bytes",
                                    "resp_body", val);
+    else if (strcmp(name, "s_resp_protobytes") == 0)
+      return varnish_submit_derive(conf->instance, "totals", "total_bytes",
+                                   "resp_proto", val);
     else if (strcmp(name, "s_pipe_hdrbytes") == 0)
       return varnish_submit_derive(conf->instance, "totals", "total_bytes",
                                    "pipe_header", val);
@@ -584,7 +668,7 @@ static int varnish_monitor(void *priv,
       return varnish_submit_derive(conf->instance, "workers", "total_threads",
                                    "dropped", val);
     else if (strcmp(name, "thread_queue_len") == 0)
-      return varnish_submit_derive(conf->instance, "workers", "queue_length",
+      return varnish_submit_gauge(conf->instance, "workers", "queue_length",
                                    "threads", val);
     else if (strcmp(name, "n_wrk") == 0)
       return varnish_submit_gauge(conf->instance, "workers", "threads",
@@ -613,6 +697,14 @@ static int varnish_monitor(void *priv,
     else if (strcmp(name, "n_wrk_lqueue") == 0)
       return varnish_submit_derive(conf->instance, "workers", "total_requests",
                                    "queue_length", val);
+#if HAVE_VARNISH_V4
+    else if (strcmp(name, "pools") == 0)
+      return varnish_submit_gauge(conf->instance, "workers", "pools",
+                                  "pools", val);
+    else if (strcmp(name, "busy_killed") == 0)
+      return varnish_submit_derive(conf->instance, "workers", "http_requests",
+                                   "busy_killed", val);
+#endif
   }
 
 #if HAVE_VARNISH_V4
@@ -631,6 +723,260 @@ static int varnish_monitor(void *priv,
       return varnish_submit_derive(conf->instance, "vsm", "total_bytes",
                                    "overflowed", val);
   }
+
+  if (conf->collect_vbe) {
+    /* @TODO figure out the collectd type for bitmap
+    if (strcmp(name, "happy") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "bitmap", "happy_hprobes", val);
+    */
+    if (strcmp(name, "bereq_hdrbytes") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "bereq_hdrbytes", val);
+    else if (strcmp(name, "bereq_bodybytes") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "bereq_bodybytes", val);
+    else if (strcmp(name, "bereq_protobytes") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "bereq_protobytes", val);
+    else if (strcmp(name, "beresp_hdrbytes") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "beresp_hdrbytes", val);
+    else if (strcmp(name, "beresp_bodybytes") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "beresp_bodybytes", val);
+    else if (strcmp(name, "beresp_protobytes") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "beresp_protobytes", val);
+    else if (strcmp(name, "pipe_hdrbytes") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "pipe_hdrbytes", val);
+    else if (strcmp(name, "pipe_out") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "pipe_out", val);
+    else if (strcmp(name, "pipe_in") == 0)
+      return varnish_submit_derive(conf->instance, "vbe",
+                                   "total_bytes", "pipe_in", val);
+    else if (strcmp(name, "conn") == 0)
+      return varnish_submit_derive(conf->instance, "vbe", "connections",
+                                  "c_conns", val);
+    else if (strcmp(name, "req") == 0)
+      return varnish_submit_derive(conf->instance, "vbe", "http_requests",
+                                  "b_reqs", val);
+  }
+
+  /* All Stevedores support these counters */
+  if (conf->collect_sma || conf->collect_smf || conf->collect_mse) {
+
+    char category[4];
+    if (conf->collect_sma)
+      strncpy(category, "sma", 4);
+    else if (conf->collect_smf)
+      strncpy(category, "smf", 4);
+    else
+      strncpy(category, "mse", 4);
+
+    if (strcmp(name, "c_req") == 0)
+      return varnish_submit_derive(conf->instance, category,
+                                   "total_operations", "alloc_req", val);
+    else if (strcmp(name, "c_fail") == 0)
+      return varnish_submit_derive(conf->instance, category,
+                                   "total_operations", "alloc_fail", val);
+    else if (strcmp(name, "c_bytes") == 0)
+      return varnish_submit_derive(conf->instance, category,
+                                   "total_bytes", "bytes_allocated", val);
+    else if (strcmp(name, "c_freed") == 0)
+      return varnish_submit_derive(conf->instance, category,
+                                   "total_bytes", "bytes_freed", val);
+    else if (strcmp(name, "g_alloc") == 0)
+      return varnish_submit_derive(conf->instance, category,
+                                  "total_operations", "alloc_outstanding", val);
+    else if (strcmp(name, "g_bytes") == 0)
+      return varnish_submit_gauge(conf->instance, category, "bytes",
+                                  "bytes_outstanding", val);
+    else if (strcmp(name, "g_space") == 0)
+      return varnish_submit_gauge(conf->instance, category, "bytes",
+                                  "bytes_available", val);
+  }
+
+  /* No SMA specific counters */
+
+  if (conf->collect_smf) {
+    if (strcmp(name, "g_smf") == 0)
+        return varnish_submit_gauge(conf->instance, "smf", "objects",
+                                    "n_struct_smf", val);
+      else if (strcmp(name, "g_smf_frag") == 0)
+        return varnish_submit_gauge(conf->instance, "smf", "objects",
+                                    "n_small_free_smf", val);
+      else if (strcmp(name, "g_smf_large") == 0)
+        return varnish_submit_gauge(conf->instance, "smf", "objects",
+                                    "n_large_free_smf", val);
+  }
+
+  if (conf->collect_mgt) {
+    if (strcmp(name, "uptime") == 0)
+      return varnish_submit_gauge(conf->instance, "mgt", "uptime",
+                                  "mgt_proc_uptime", val);
+    else if (strcmp(name, "child_start") == 0)
+      return varnish_submit_derive(conf->instance, "mgt",
+                                   "total_operations", "child_start", val);
+    else if (strcmp(name, "child_exit") == 0)
+      return varnish_submit_derive(conf->instance, "mgt",
+                                   "total_operations", "child_exit", val);
+    else if (strcmp(name, "child_stop") == 0)
+      return varnish_submit_derive(conf->instance, "mgt",
+                                   "total_operations", "child_stop", val);
+    else if (strcmp(name, "child_died") == 0)
+      return varnish_submit_derive(conf->instance, "mgt",
+                                   "total_operations", "child_died", val);
+    else if (strcmp(name, "child_dump") == 0)
+      return varnish_submit_derive(conf->instance, "mgt",
+                                   "total_operations", "child_dump", val);
+    else if (strcmp(name, "child_panic") == 0)
+      return varnish_submit_derive(conf->instance, "mgt",
+                                   "total_operations", "child_panic", val);
+  }
+
+  if (conf->collect_lck) {
+    if (strcmp(name, "creat") == 0)
+      return varnish_submit_gauge(conf->instance, "lck", "objects",
+                                  "created", val);
+    else if (strcmp(name, "destroy") == 0)
+      return varnish_submit_gauge(conf->instance, "lck", "objects",
+                                  "destroyed", val);
+    else if (strcmp(name, "locks") == 0)
+      return varnish_submit_derive(conf->instance, "lck", "total_operations",
+                                  "lock_ops", val);
+  }
+
+  if (conf->collect_mempool) {
+    if (strcmp(name, "live") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool", "objects",
+                                  "in_use", val);
+    else if (strcmp(name, "pool") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool", "objects",
+                                  "in_pool", val);
+    else if (strcmp(name, "sz_wanted") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool", "bytes",
+                                  "size_requested", val);
+    else if (strcmp(name, "sz_actual") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool", "bytes",
+                                  "size_allocated", val);
+    else if (strcmp(name, "allocs") == 0)
+      return varnish_submit_derive(conf->instance, "mempool",
+                                   "total_operations", "allocations", val);
+    else if (strcmp(name, "frees") == 0)
+      return varnish_submit_derive(conf->instance, "mempool",
+                                   "total_operations", "frees", val);
+    else if (strcmp(name, "recycle") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool",
+                                   "objects", "recycled", val);
+    else if (strcmp(name, "timeout") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool",
+                                   "objects", "timed_out", val);
+    else if (strcmp(name, "toosmall") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool",
+                                   "objects", "too_small", val);
+    else if (strcmp(name, "surplus") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool",
+                                   "objects", "surplus", val);
+    else if (strcmp(name, "randry") == 0)
+      return varnish_submit_gauge(conf->instance, "mempool",
+                                   "objects", "ran_dry", val);
+  }
+
+  if (conf->collect_mse) {
+    if (strcmp(name, "c_full") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_operations", "full_allocs", val);
+    else if (strcmp(name, "c_truncated") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_operations", "truncated_allocs", val);
+    else if (strcmp(name, "c_expanded") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_operations", "expanded_allocs", val);
+    else if (strcmp(name, "c_failed") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_operations", "failed_allocs", val);
+    else if (strcmp(name, "c_bytes") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_bytes", "bytes_allocated", val);
+    else if (strcmp(name, "c_freed") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_bytes", "bytes_freed", val);
+    else if (strcmp(name, "g_fo_alloc") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_operations", "fo_allocs_outstanding", val);
+    else if (strcmp(name, "g_fo_bytes") == 0)
+      return varnish_submit_gauge(conf->instance, "mse",
+                                  "bytes", "fo_bytes_outstanding", val);
+    else if (strcmp(name, "g_membuf_alloc") == 0)
+      return varnish_submit_gauge(conf->instance, "mse",
+                                  "objects", "membufs_allocated", val);
+    else if (strcmp(name, "g_membuf_inuse") == 0)
+      return varnish_submit_gauge(conf->instance, "mse",
+                                  "objects", "membufs_inuse", val);
+    else if (strcmp(name, "g_bans_bytes") == 0)
+      return varnish_submit_gauge(conf->instance, "mse",
+                                  "bytes", "persisted_banspace_used", val);
+    else if (strcmp(name, "g_bans_space") == 0)
+      return varnish_submit_gauge(conf->instance, "mse",
+                                  "bytes", "persisted_banspace_available", val);
+    else if (strcmp(name, "g_bans_persisted") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_operations", "bans_persisted", val);
+    else if (strcmp(name, "g_bans_lost") == 0)
+      return varnish_submit_derive(conf->instance, "mse",
+                                  "total_operations", "bans_lost", val);
+
+     /* mse seg */
+    else if (strcmp(name, "g_journal_bytes") == 0)
+      return varnish_submit_gauge(conf->instance, "mse_reg",
+                                  "bytes", "journal_bytes_used", val);
+    else if (strcmp(name, "g_journal_space") == 0)
+      return varnish_submit_gauge(conf->instance, "mse_reg",
+                                  "bytes", "journal_bytes_free", val);
+
+    /* mse segagg */
+    else if (strcmp(name, "g_bigspace") == 0)
+      return varnish_submit_gauge(conf->instance, "mse_segagg",
+                                  "bytes", "big_extents_bytes_available", val);
+    else if (strcmp(name, "g_extfree") == 0)
+      return varnish_submit_gauge(conf->instance, "mse_segagg",
+                                  "objects", "free_extents", val);
+    else if (strcmp(name, "g_sparenode") == 0)
+      return varnish_submit_gauge(conf->instance, "mse_segagg",
+                                  "objects", "spare_nodes_available", val);
+    else if (strcmp(name, "g_objnode") == 0)
+      return varnish_submit_gauge(conf->instance, "mse_segagg",
+                                  "objects", "object_nodes_in_use", val);
+    else if (strcmp(name, "g_extnode") == 0)
+      return varnish_submit_gauge(conf->instance, "mse_segagg",
+                                  "objects", "extent_nodes_in_use", val);
+    else if (strcmp(name, "g_bigextfree") == 0)
+      return varnish_submit_gauge(conf->instance, "mse_segagg",
+                                  "objects", "free_big_extents", val);
+    else if (strcmp(name, "c_pruneloop") == 0)
+      return varnish_submit_derive(conf->instance, "mse_segagg",
+                                  "total_operations", "prune_loops", val);
+    else if (strcmp(name, "c_pruned") == 0)
+      return varnish_submit_derive(conf->instance, "mse_segagg",
+                                  "total_objects", "pruned_objects", val);
+    else if (strcmp(name, "c_spared") == 0)
+      return varnish_submit_derive(conf->instance, "mse_segagg",
+                                  "total_operations", "spared_objects", val);
+    else if (strcmp(name, "c_skipped") == 0)
+      return varnish_submit_derive(conf->instance, "mse_segagg",
+                                  "total_operations", "missed_objects", val);
+    else if (strcmp(name, "c_nuked") == 0)
+      return varnish_submit_derive(conf->instance, "mse_segagg",
+                                  "total_operations", "nuked_objects", val);
+    else if (strcmp(name, "c_sniped") == 0)
+      return varnish_submit_derive(conf->instance, "mse_segagg",
+                                  "total_operations", "sniped_objects", val);
+
+  }
+
 #endif
 
   return 0;
@@ -1100,6 +1446,8 @@ static int varnish_config_apply_default(user_config_t *conf) /* {{{ */
   conf->collect_shm = 1;
 #if HAVE_VARNISH_V2
   conf->collect_sm = 0;
+#endif
+#if HAVE_VARNISH_V2 || HAVE_VARNISH_V4
   conf->collect_sma = 0;
 #endif
   conf->collect_sms = 0;
@@ -1112,6 +1460,12 @@ static int varnish_config_apply_default(user_config_t *conf) /* {{{ */
   conf->collect_workers = 0;
 #if HAVE_VARNISH_V4
   conf->collect_vsm = 0;
+  conf->collect_lck = 0;
+  conf->collect_mempool = 0;
+  conf->collect_mgt = 0;
+  conf->collect_smf = 0;
+  conf->collect_vbe = 0;
+  conf->collect_mse = 0;
 #endif
 
   return 0;
@@ -1223,11 +1577,11 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */
     else if (strcasecmp("CollectSMS", child->key) == 0)
       cf_util_get_boolean(child, &conf->collect_sms);
     else if (strcasecmp("CollectSMA", child->key) == 0)
-#if HAVE_VARNISH_V2
+#if HAVE_VARNISH_V2 || HAVE_VARNISH_V4
       cf_util_get_boolean(child, &conf->collect_sma);
 #else
       WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
-              child->key, "v2");
+              child->key, "v2 and v4");
 #endif
     else if (strcasecmp("CollectSM", child->key) == 0)
 #if HAVE_VARNISH_V2
@@ -1257,6 +1611,55 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */
 #else
       WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
               child->key, "v4");
+#endif
+    else if (strcasecmp("CollectLock", child->key) == 0)
+#if HAVE_VARNISH_V4
+      cf_util_get_boolean(child, &conf->collect_lck);
+#else
+      WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
+              child->key, "v4");
+#endif
+    else if (strcasecmp("CollectMempool", child->key) == 0)
+#if HAVE_VARNISH_V4
+      cf_util_get_boolean(child, &conf->collect_mempool);
+#else
+      WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
+              child->key, "v4");
+#endif
+    else if (strcasecmp("CollectManagement", child->key) == 0)
+#if HAVE_VARNISH_V4
+      cf_util_get_boolean(child, &conf->collect_mgt);
+#else
+      WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
+              child->key, "v4");
+#endif
+    else if (strcasecmp("CollectSMF", child->key) == 0)
+#if HAVE_VARNISH_V4
+      cf_util_get_boolean(child, &conf->collect_smf);
+#else
+      WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
+              child->key, "v4");
+#endif
+    else if (strcasecmp("CollectSMF", child->key) == 0)
+#if HAVE_VARNISH_V4
+      cf_util_get_boolean(child, &conf->collect_smf);
+#else
+      WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
+              child->key, "v4");
+#endif
+    else if (strcasecmp("CollectVBE", child->key) == 0)
+#if HAVE_VARNISH_V4
+      cf_util_get_boolean(child, &conf->collect_vbe);
+#else
+      WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
+              child->key, "v4");
+#endif
+    else if (strcasecmp("CollectMSE", child->key) == 0)
+#if HAVE_VARNISH_V4
+      cf_util_get_boolean(child, &conf->collect_mse);
+#else
+      WARNING("Varnish plugin: \"%s\" is available for Varnish %s only.",
+              child->key, "Plus v4");
 #endif
     else {
       WARNING("Varnish plugin: Ignoring unknown "
@@ -1280,7 +1683,10 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */
 #endif
       && !conf->collect_session && !conf->collect_shm && !conf->collect_sms
 #if HAVE_VARNISH_V2
-      && !conf->collect_sma && !conf->collect_sm
+      && !conf->collect_sm
+#endif
+#if HAVE_VARNISH_V2 || HAVE_VARNISH_V4
+      && !conf->collect_sma
 #endif
       && !conf->collect_struct && !conf->collect_totals
 #if HAVE_VARNISH_V3 || HAVE_VARNISH_V4
@@ -1288,7 +1694,9 @@ static int varnish_config_instance(const oconfig_item_t *ci) /* {{{ */
 #endif
       && !conf->collect_vcl && !conf->collect_workers
 #if HAVE_VARNISH_V4
-      && !conf->collect_vsm
+      && !conf->collect_vsm && !conf->collect_vbe && !conf->collect_smf
+      && !conf->collect_mgt && !conf->collect_lck && !conf->collect_mempool
+      && !conf->collect_mse
 #endif
       ) {
     WARNING("Varnish plugin: No metric has been configured for "
index 06327edbb1019bb9bd9f8a037bd1767151f78af9..87e518b6857605a814abed1460a014958dc016ec 100644 (file)
 #define WRITE_HTTP_DEFAULT_BUFFER_SIZE 4096
 #endif
 
+#ifndef WRITE_HTTP_DEFAULT_PREFIX
+#define WRITE_HTTP_DEFAULT_PREFIX "collectd"
+#endif
+
 /*
  * Private variables
  */
@@ -80,6 +84,7 @@ struct wh_callback_s {
   pthread_mutex_t send_lock;
 
   int data_ttl;
+  char *metrics_prefix;
 };
 typedef struct wh_callback_s wh_callback_t;
 
@@ -328,6 +333,7 @@ static void wh_callback_free(void *data) /* {{{ */
   sfree(cb->clientcert);
   sfree(cb->clientkeypass);
   sfree(cb->send_buffer);
+  sfree(cb->metrics_prefix);
 
   sfree(cb);
 } /* }}} void wh_callback_free */
@@ -476,7 +482,7 @@ static int wh_write_kairosdb(const data_set_t *ds,
   status = format_kairosdb_value_list(
       cb->send_buffer, &cb->send_buffer_fill, &cb->send_buffer_free, ds, vl,
       cb->store_rates, (char const *const *)http_attrs, http_attrs_num,
-      cb->data_ttl);
+      cb->data_ttl, cb->metrics_prefix);
   if (status == -ENOMEM) {
     status = wh_flush_nolock(/* timeout = */ 0, cb);
     if (status != 0) {
@@ -488,7 +494,7 @@ static int wh_write_kairosdb(const data_set_t *ds,
     status = format_kairosdb_value_list(
         cb->send_buffer, &cb->send_buffer_fill, &cb->send_buffer_free, ds, vl,
         cb->store_rates, (char const *const *)http_attrs, http_attrs_num,
-        cb->data_ttl);
+        cb->data_ttl, cb->metrics_prefix);
   }
   if (status != 0) {
     pthread_mutex_unlock(&cb->send_lock);
@@ -629,6 +635,13 @@ static int wh_config_node(oconfig_item_t *ci) /* {{{ */
   cb->send_metrics = 1;
   cb->send_notifications = 0;
   cb->data_ttl = 0;
+  cb->metrics_prefix = strdup(WRITE_HTTP_DEFAULT_PREFIX);
+
+  if (cb->metrics_prefix == NULL) {
+    ERROR("write_http plugin: strdup failed.");
+    sfree(cb);
+    return -1;
+  }
 
   pthread_mutex_init(&cb->send_lock, /* attr = */ NULL);
 
@@ -740,6 +753,8 @@ static int wh_config_node(oconfig_item_t *ci) /* {{{ */
       sfree(val);
     } else if (strcasecmp("TTL", child->key) == 0) {
       status = cf_util_get_int(child, &cb->data_ttl);
+    } else if (strcasecmp("Prefix", child->key) == 0) {
+      status = cf_util_get_string(child, &cb->metrics_prefix);
     } else {
       ERROR("write_http plugin: Invalid configuration "
             "option: %s.",
@@ -770,6 +785,9 @@ static int wh_config_node(oconfig_item_t *ci) /* {{{ */
     return -1;
   }
 
+  if (strlen(cb->metrics_prefix) == 0)
+    sfree(cb->metrics_prefix);
+
   if (cb->low_speed_limit > 0)
     cb->low_speed_time = CDTIME_T_TO_TIME_T(plugin_get_interval());
 
index eac0d68efb672607f684bede0ace98630a27bf07..a005ffb28cf20e6add071c60697721d5c00d852e 100644 (file)
 
 #include <microhttpd.h>
 
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
 #ifndef PROMETHEUS_DEFAULT_STALENESS_DELTA
 #define PROMETHEUS_DEFAULT_STALENESS_DELTA TIME_T_TO_CDTIME_T_STATIC(300)
 #endif
@@ -727,6 +731,90 @@ metric_family_get(data_set_t const *ds, value_list_t const *vl, size_t ds_index,
 }
 /* }}} */
 
+#if MHD_VERSION >= 0x00090000
+static int prom_open_socket(int addrfamily) {
+  /* {{{ */
+  char service[NI_MAXSERV];
+  snprintf(service, sizeof(service), "%hu", httpd_port);
+
+  struct addrinfo *res;
+  int status = getaddrinfo(NULL, service,
+                           &(struct addrinfo){
+                               .ai_flags = AI_PASSIVE | AI_ADDRCONFIG,
+                               .ai_family = addrfamily,
+                               .ai_socktype = SOCK_STREAM,
+                           },
+                           &res);
+  if (status != 0) {
+    return -1;
+  }
+
+  int fd = -1;
+  for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) {
+    fd = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC, 0);
+    if (fd == -1)
+      continue;
+
+    if (bind(fd, ai->ai_addr, ai->ai_addrlen) != 0) {
+      close(fd);
+      fd = -1;
+      continue;
+    }
+
+    if (listen(fd, /* backlog = */ 16) != 0) {
+      close(fd);
+      fd = -1;
+      continue;
+    }
+
+    break;
+  }
+
+  freeaddrinfo(res);
+
+  return fd;
+} /* }}} int prom_open_socket */
+
+static struct MHD_Daemon *prom_start_daemon() {
+  /* {{{ */
+  int fd = prom_open_socket(PF_INET6);
+  if (fd == -1)
+    fd = prom_open_socket(PF_INET);
+  if (fd == -1) {
+    ERROR("write_prometheus plugin: Opening a listening socket failed.");
+    return NULL;
+  }
+
+  struct MHD_Daemon *d =
+      MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, 0,
+                       /* MHD_AcceptPolicyCallback = */ NULL,
+                       /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler,
+                       NULL, MHD_OPTION_LISTEN_SOCKET, fd, MHD_OPTION_END);
+  if (d == NULL) {
+    ERROR("write_prometheus plugin: MHD_start_daemon() failed.");
+    close(fd);
+    return NULL;
+  }
+
+  return d;
+} /* }}} struct MHD_Daemon *prom_start_daemon */
+#else /* if MHD_VERSION < 0x00090000 */
+static struct MHD_Daemon *prom_start_daemon() {
+  /* {{{ */
+  struct MHD_Daemon *d =
+      MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, 0,
+                       /* MHD_AcceptPolicyCallback = */ NULL,
+                       /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler,
+                       NULL, MHD_OPTION_END);
+  if (d == NULL) {
+    ERROR("write_prometheus plugin: MHD_start_daemon() failed.");
+    return NULL;
+  }
+
+  return d;
+} /* }}} struct MHD_Daemon *prom_start_daemon */
+#endif
+
 /*
  * collectd callbacks
  */
@@ -760,15 +848,7 @@ static int prom_init() {
   }
 
   if (httpd == NULL) {
-    unsigned int flags = MHD_USE_THREAD_PER_CONNECTION;
-#if MHD_VERSION >= 0x00093300
-    flags |= MHD_USE_DUAL_STACK;
-#endif
-
-    httpd = MHD_start_daemon(flags, httpd_port,
-                             /* MHD_AcceptPolicyCallback = */ NULL,
-                             /* MHD_AcceptPolicyCallback arg = */ NULL,
-                             http_handler, NULL, MHD_OPTION_END);
+    httpd = prom_start_daemon();
     if (httpd == NULL) {
       ERROR("write_prometheus plugin: MHD_start_daemon() failed.");
       return -1;
index 5a029de2761a4c75c618ea8f11cd448831c1e1ff..7dd5029ce1a6da94c0d4d1773c5a7eaa91beb198 100644 (file)
@@ -45,6 +45,7 @@ struct wr_node_s {
   char *prefix;
   int database;
   int max_set_size;
+  int max_set_duration;
   _Bool store_rates;
 
   redisContext *conn;
@@ -125,6 +126,20 @@ static int wr_write(const data_set_t *ds, /* {{{ */
       freeReplyObject(rr);
   }
 
+  if (node->max_set_duration > 0) {
+    /*
+     * remove element, scored less than 'current-max_set_duration'
+     * '(%d' indicates 'less than' in redis CLI.
+     */
+    rr = redisCommand(node->conn, "ZREMRANGEBYSCORE %s -1 (%d", key,
+                      (time - node->max_set_duration) + 1);
+    if (rr == NULL)
+      WARNING("ZREMRANGEBYSCORE command error. key:%s message:%s", key,
+              node->conn->errstr);
+    else
+      freeReplyObject(rr);
+  }
+
   /* TODO(octo): This is more overhead than necessary. Use the cache and
    * metadata to determine if it is a new metric and call SADD only once for
    * each metric. */
@@ -175,6 +190,7 @@ static int wr_config_node(oconfig_item_t *ci) /* {{{ */
   node->prefix = NULL;
   node->database = 0;
   node->max_set_size = -1;
+  node->max_set_duration = -1;
   node->store_rates = 1;
   pthread_mutex_init(&node->lock, /* attr = */ NULL);
 
@@ -205,6 +221,8 @@ static int wr_config_node(oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_int(child, &node->database);
     } else if (strcasecmp("MaxSetSize", child->key) == 0) {
       status = cf_util_get_int(child, &node->max_set_size);
+    } else if (strcasecmp("MaxSetDuration", child->key) == 0) {
+      status = cf_util_get_int(child, &node->max_set_duration);
     } else if (strcasecmp("StoreRates", child->key) == 0) {
       status = cf_util_get_boolean(child, &node->store_rates);
     } else
index 10f636c5aad5afa936eb862d3c2c0feb7322ebaa..eb6ceb3f668dfb1129491147eb286c2ccd140211 100644 (file)
@@ -111,7 +111,7 @@ static int wt_send_buffer(struct wt_callback *cb) {
   ssize_t status = 0;
 
   status = swrite(cb->sock_fd, cb->send_buf, strlen(cb->send_buf));
-  if (status < 0) {
+  if (status != 0) {
     char errbuf[1024];
     ERROR("write_tsdb plugin: send failed with status %zi (%s)", status,
           sstrerror(errno, errbuf, sizeof(errbuf)));
index 730d30483b5eff16961e7167b58fa3a3e62f7864..e589184c2cf0fe6b898762270a47462c33d299fa 100644 (file)
 /*
  * Global variables
  */
+static value_to_rate_state_t arc_hits_state;
+static value_to_rate_state_t arc_misses_state;
+static value_to_rate_state_t l2_hits_state;
+static value_to_rate_state_t l2_misses_state;
 
 #if defined(KERNEL_LINUX)
 #include "utils_llist.h"
@@ -313,14 +317,25 @@ static int za_read(void) {
   za_read_derive(ksp, "mru_hits", "cache_result", "mru-hit");
   za_read_derive(ksp, "mru_ghost_hits", "cache_result", "mru_ghost-hit");
 
+  cdtime_t now = cdtime();
+
   /* Ratios */
-  arc_hits = (gauge_t)get_zfs_value(ksp, "hits");
-  arc_misses = (gauge_t)get_zfs_value(ksp, "misses");
-  l2_hits = (gauge_t)get_zfs_value(ksp, "l2_hits");
-  l2_misses = (gauge_t)get_zfs_value(ksp, "l2_misses");
+  if ((value_to_rate(&arc_hits, (value_t){.derive = get_zfs_value(ksp, "hits")},
+                     DS_TYPE_DERIVE, now, &arc_hits_state) == 0) &&
+      (value_to_rate(&arc_misses,
+                     (value_t){.derive = get_zfs_value(ksp, "misses")},
+                     DS_TYPE_DERIVE, now, &arc_misses_state) == 0)) {
+    za_submit_ratio("arc", arc_hits, arc_misses);
+  }
 
-  za_submit_ratio("arc", arc_hits, arc_misses);
-  za_submit_ratio("L2", l2_hits, l2_misses);
+  if ((value_to_rate(&l2_hits,
+                     (value_t){.derive = get_zfs_value(ksp, "l2_hits")},
+                     DS_TYPE_DERIVE, now, &l2_hits_state) == 0) &&
+      (value_to_rate(&l2_misses,
+                     (value_t){.derive = get_zfs_value(ksp, "l2_misses")},
+                     DS_TYPE_DERIVE, now, &l2_misses_state) == 0)) {
+    za_submit_ratio("L2", l2_hits, l2_misses);
+  }
 
   /* I/O */
   value_t l2_io[] = {