Merge pull request #2806 from rorysexton/dev_turbostat_plugin
authorPavel Rochnyak <pavel2000@ngs.ru>
Sat, 20 Oct 2018 11:17:11 +0000 (18:17 +0700)
committerGitHub <noreply@github.com>
Sat, 20 Oct 2018 11:17:11 +0000 (18:17 +0700)
turbostat plugin: New metrics: P-states,Turboboost,Platform TDP,Uncore bus ratio

122 files changed:
.gitmodules [new file with mode: 0644]
.travis.yml
Makefile.am
README
build.sh
configure.ac
contrib/docker/rootfs_prefix/rootfs_prefix.c
gnulib [new submodule]
src/amqp1.c
src/bind.c
src/ceph.c
src/chrony.c
src/collectd-snmp.pod
src/collectd-tg.c
src/collectd.conf.in
src/collectd.conf.pod
src/curl.c
src/curl_json.c
src/curl_xml.c
src/daemon/cmd.c [new file with mode: 0644]
src/daemon/cmd.h [new file with mode: 0644]
src/daemon/cmd_windows.c [new file with mode: 0755]
src/daemon/collectd.c
src/daemon/collectd.h
src/daemon/common.c
src/daemon/common.h
src/daemon/configfile.c
src/daemon/globals.h
src/daemon/plugin.c
src/daemon/plugin.h
src/daemon/plugin_mock.c
src/daemon/utils_avltree.c
src/daemon/utils_avltree_test.c
src/daemon/utils_cache_mock.c
src/daemon/utils_heap.c
src/daemon/utils_random.c
src/dbi.c
src/df.c
src/disk.c
src/dpdkevents.c
src/email.c
src/exec.c
src/filecount.c
src/gmond.c
src/gps.c
src/intel_rdt.c
src/iptables.c
src/java.c
src/libcollectdclient/client.c
src/libcollectdclient/collectd/network.h
src/libcollectdclient/collectd/server.h
src/libcollectdclient/network.c
src/libcollectdclient/network_buffer.c
src/libcollectdclient/server.c
src/liboconfig/parser.y
src/liboconfig/scanner.l
src/lua.c
src/memcachec.c
src/memcached.c
src/modbus.c
src/netapp.c
src/network.c
src/nfs.c
src/notify_email.c
src/ntpd.c
src/oracle.c
src/pcie_errors.c [new file with mode: 0644]
src/pcie_errors_test.c [new file with mode: 0644]
src/perl.c
src/postgresql.c
src/powerdns.c
src/processes.c
src/protocols.c
src/redis.c
src/routeros.c
src/rrdtool.c
src/sensors.c
src/snmp.c
src/snmp_agent.c
src/snmp_agent_test.c [new file with mode: 0644]
src/statsd.c
src/table.c
src/tail.c
src/tail_csv.c
src/testing.h
src/threshold.c
src/turbostat.c
src/types.db
src/utils_db_query.c
src/utils_db_query.h
src/utils_dpdk.c
src/utils_format_graphite.c
src/utils_format_graphite.h
src/utils_format_graphite_test.c
src/utils_format_json.c
src/utils_format_kairosdb.c
src/utils_format_stackdriver.c [new file with mode: 0644]
src/utils_format_stackdriver.h [new file with mode: 0644]
src/utils_format_stackdriver_test.c [new file with mode: 0644]
src/utils_gce.c [new file with mode: 0644]
src/utils_gce.h [new file with mode: 0644]
src/utils_latency.c
src/utils_latency_config.c
src/utils_latency_config.h
src/utils_lua.c
src/utils_lua.h
src/utils_oauth.c [new file with mode: 0644]
src/utils_oauth.h [new file with mode: 0644]
src/utils_oauth_test.c [new file with mode: 0644]
src/utils_rrdcreate.c
src/utils_tail.c
src/utils_tail_match.c
src/utils_tail_match.h
src/virt.c
src/wireless.c
src/write_graphite.c
src/write_kafka.c
src/write_prometheus.c
src/write_redis.c
src/write_riemann.c
src/write_sensu.c
src/write_stackdriver.c [new file with mode: 0644]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..009ae43
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "gnulib"]
+       path = gnulib
+       url = git://git.savannah.gnu.org/gnulib.git
index 1bd6142..97115d1 100644 (file)
@@ -75,7 +75,7 @@ before_script: autoreconf -fi
 script:
   - if [[ "${TRAVIS_BRANCH}" == "coverity_scan" ]]; then exit 0; fi
   - ./configure
-  - make -j 4
+  - make -j $(nproc)
   - make check
 
 addons:
@@ -85,5 +85,5 @@ addons:
       description: "Build submitted via Travis CI"
     notification_email: collectd-changes@verplant.org
     build_command_prepend: "./configure; make clean"
-    build_command: "make -j 4"
+    build_command: "make -j $(nproc)"
     branch_pattern: coverity_scan
index 612346e..f929ffc 100644 (file)
@@ -1,6 +1,15 @@
 ACLOCAL_AMFLAGS = -I m4
 AM_YFLAGS = -d
 
+if BUILD_WIN32
+cpkgdatadir=$(datadir)
+cpkglibdir=$(libdir)/plugins
+cpkglocalstatedir=${localstatedir}
+else
+cpkgdatadir=$(pkgdatadir)
+cpkglibdir=$(pkglibdir)
+cpkglocalstatedir=${localstatedir}/lib/${PACKAGE_NAME}
+endif
 
 BUILT_SOURCES = \
        src/libcollectdclient/collectd/lcc_features.h \
@@ -99,7 +108,13 @@ pkginclude_HEADERS = \
 
 lib_LTLIBRARIES = libcollectdclient.la
 
+if BUILD_WIN32
+# TODO: Build all executables on Windows as well.
+sbin_PROGRAMS = \
+        collectd
 
+bin_PROGRAMS =
+else
 sbin_PROGRAMS = \
        collectd \
        collectdmon
@@ -109,6 +124,7 @@ bin_PROGRAMS = \
        collectd-nagios \
        collectd-tg \
        collectdctl
+endif # BUILD_WIN32
 
 
 noinst_LTLIBRARIES = \
@@ -151,7 +167,7 @@ TESTS = $(check_PROGRAMS)
 LOG_COMPILER = env VALGRIND="@VALGRIND@" $(abs_srcdir)/testwrapper.sh
 
 
-jardir = $(pkgdatadir)/java
+jardir = $(cpkgdatadir)/java
 
 pkglib_LTLIBRARIES =
 
@@ -160,6 +176,9 @@ PLUGIN_LDFLAGS = \
        -module \
        -avoid-version \
        -export-symbols-regex '\<module_register\>'
+if BUILD_WIN32
+PLUGIN_LDFLAGS += -shared -no-undefined -lcollectd -L.
+endif
 
 
 AM_CPPFLAGS = \
@@ -167,13 +186,26 @@ AM_CPPFLAGS = \
        -DPREFIX='"${prefix}"' \
        -DCONFIGFILE='"${sysconfdir}/${PACKAGE_NAME}.conf"' \
        -DLOCALSTATEDIR='"${localstatedir}"' \
-       -DPKGLOCALSTATEDIR='"${localstatedir}/lib/${PACKAGE_NAME}"' \
-       -DPLUGINDIR='"${pkglibdir}"' \
-       -DPKGDATADIR='"${pkgdatadir}"'
+       -DPKGLOCALSTATEDIR='"${cpkglocalstatedir}"' \
+       -DPLUGINDIR='"${cpkglibdir}"' \
+       -DPKGDATADIR='"${cpkgdatadir}"'
+if BUILD_WIN32
+AM_CPPFLAGS += -DNOGDI
+endif
 
+COMMON_DEPS =
+if BUILD_WIN32
+COMMON_DEPS += collectd.exe
+endif
 
 # Link to these libraries..
 COMMON_LIBS = $(PTHREAD_LIBS)
+if BUILD_WIN32
+COMMON_LIBS += -lws2_32
+endif
+if BUILD_WITH_GNULIB
+COMMON_LIBS += -lgnu
+endif
 if BUILD_WITH_CAPABILITY
 COMMON_LIBS += -lcap
 endif
@@ -195,6 +227,7 @@ endif
 
 
 collectd_SOURCES = \
+       src/daemon/cmd.h \
        src/daemon/collectd.c \
        src/daemon/collectd.h \
        src/daemon/configfile.c \
@@ -237,6 +270,13 @@ collectd_LDADD = \
        $(COMMON_LIBS) \
        $(DLOPEN_LIBS)
 
+if BUILD_WIN32
+collectd_SOURCES += src/daemon/cmd_windows.c
+collectd_LDFLAGS += -ldl -Wl,--out-implib,libcollectd.a
+else
+collectd_SOURCES += src/daemon/cmd.c
+endif
+       
 if BUILD_FEATURE_DAEMON
 collectd_CPPFLAGS += -DPIDFILE='"${localstatedir}/run/${PACKAGE_NAME}.pid"'
 endif
@@ -248,6 +288,9 @@ collectd_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
 collectd_LDADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
 endif
 
+if BUILD_WIN32
+collectd_LDFLAGS += -Wl,--out-implib,libcollectd.a
+endif
 
 collectdmon_SOURCES = src/collectdmon.c
 
@@ -499,6 +542,10 @@ libcollectdclient_la_CPPFLAGS = \
        -I$(srcdir)/src/daemon
 libcollectdclient_la_LDFLAGS = -version-info 2:0:1
 libcollectdclient_la_LIBADD = -lm
+if BUILD_WIN32
+libcollectdclient_la_LDFLAGS += -shared -no-undefined
+libcollectdclient_la_LIBADD += -lgnu -lws2_32 -liphlpapi
+endif
 if BUILD_WITH_LIBGCRYPT
 libcollectdclient_la_CPPFLAGS += $(GCRYPT_CPPFLAGS)
 libcollectdclient_la_LDFLAGS += $(GCRYPT_LDFLAGS)
@@ -527,6 +574,68 @@ liboconfig_la_SOURCES = \
 liboconfig_la_CPPFLAGS = -I$(srcdir)/src/liboconfig $(AM_CPPFLAGS)
 liboconfig_la_LDFLAGS = -avoid-version $(LEXLIB)
 
+if BUILD_WITH_LIBCURL
+if BUILD_WITH_LIBSSL
+if BUILD_WITH_LIBYAJL2
+noinst_LTLIBRARIES += liboauth.la
+liboauth_la_SOURCES = \
+       src/utils_oauth.c \
+       src/utils_oauth.h
+liboauth_la_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       $(BUILD_WITH_LIBCURL_CFLAGS) \
+       $(BUILD_WITH_LIBSSL_CFLAGS) \
+       $(BUILD_WITH_LIBYAJL_CPPFLAGS)
+liboauth_la_LIBADD = \
+       $(BUILD_WITH_LIBCURL_LIBS) \
+       $(BUILD_WITH_LIBSSL_LIBS) \
+       $(BUILD_WITH_LIBYAJL_LIBS)
+
+check_PROGRAMS += test_utils_oauth
+TESTS += test_utils_oauth
+test_utils_oauth_SOURCES = \
+       src/utils_oauth_test.c
+test_utils_oauth_LDADD = \
+       liboauth.la \
+       libcommon.la \
+       libplugin_mock.la
+
+noinst_LTLIBRARIES += libgce.la
+libgce_la_SOURCES = \
+       src/utils_gce.c \
+       src/utils_gce.h
+libgce_la_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       $(BUILD_WITH_LIBCURL_CFLAGS)
+libgce_la_LIBADD = \
+       $(BUILD_WITH_LIBCURL_LIBS)
+endif
+endif
+endif
+
+if BUILD_WITH_LIBYAJL2
+noinst_LTLIBRARIES += libformat_stackdriver.la
+libformat_stackdriver_la_SOURCES = \
+       src/utils_format_stackdriver.c \
+       src/utils_format_stackdriver.h
+libformat_stackdriver_la_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       $(BUILD_WITH_LIBYAJL_CPPFLAGS)
+libformat_stackdriver_la_LIBADD = \
+       libavltree.la \
+       $(BUILD_WITH_LIBSSL_LIBS) \
+       $(BUILD_WITH_LIBYAJL_LIBS)
+
+check_PROGRAMS += test_format_stackdriver
+TESTS += test_format_stackdriver
+test_format_stackdriver_SOURCES = \
+       src/utils_format_stackdriver_test.c \
+       src/testing.h
+test_format_stackdriver_LDADD = \
+       libformat_stackdriver.la \
+       libplugin_mock.la \
+       -lm
+endif
 
 if BUILD_PLUGIN_AGGREGATION
 pkglib_LTLIBRARIES += aggregation.la
@@ -1040,6 +1149,7 @@ if BUILD_PLUGIN_LOGFILE
 pkglib_LTLIBRARIES += logfile.la
 logfile_la_SOURCES = src/logfile.c
 logfile_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+logfile_la_DEPENDENCIES = $(COMMON_DEPS)
 endif
 
 if BUILD_PLUGIN_LOG_LOGSTASH
@@ -1379,6 +1489,24 @@ ovs_stats_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBYAJL_LDFLAGS)
 ovs_stats_la_LIBADD = $(BUILD_WITH_LIBYAJL_LIBS)
 endif
 
+if BUILD_PLUGIN_PCIE_ERRORS
+pkglib_LTLIBRARIES += pcie_errors.la
+pcie_errors_la_SOURCES = src/pcie_errors.c
+pcie_errors_la_CPPFLAGS = $(AM_CPPFLAGS)
+pcie_errors_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+
+test_plugin_pcie_errors_SOURCES = \
+       src/pcie_errors_test.c \
+       src/daemon/utils_llist.c \
+       src/daemon/configfile.c \
+       src/daemon/types_list.c
+test_plugin_pcie_errors_CPPFLAGS = $(AM_CPPFLAGS)
+test_plugin_pcie_errors_LDFLAGS = $(PLUGIN_LDFLAGS)
+test_plugin_pcie_errors_LDADD = liboconfig.la libplugin_mock.la
+check_PROGRAMS += test_plugin_pcie_errors
+TESTS += test_plugin_pcie_errors
+endif
+
 if BUILD_PLUGIN_PERL
 pkglib_LTLIBRARIES += perl.la
 perl_la_SOURCES = src/perl.c
@@ -1556,7 +1684,7 @@ pkglib_LTLIBRARIES += snmp.la
 snmp_la_SOURCES = src/snmp.c
 snmp_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBNETSNMP_CPPFLAGS)
 snmp_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBNETSNMP_LDFLAGS)
-snmp_la_LIBADD = $(BUILD_WITH_LIBNETSNMP_LIBS)
+snmp_la_LIBADD = libignorelist.la $(BUILD_WITH_LIBNETSNMP_LIBS)
 endif
 
 if BUILD_PLUGIN_SNMP_AGENT
@@ -1565,6 +1693,23 @@ snmp_agent_la_SOURCES = src/snmp_agent.c
 snmp_agent_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBNETSNMPAGENT_CPPFLAGS)
 snmp_agent_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBNETSNMPAGENT_LDFLAGS)
 snmp_agent_la_LIBADD = $(BUILD_WITH_LIBNETSNMPAGENT_LIBS)
+
+test_plugin_snmp_agent_SOURCES = src/snmp_agent_test.c \
+                                 src/daemon/utils_avltree.c \
+                                 src/daemon/utils_llist.c \
+                                 src/daemon/configfile.c \
+                                 src/daemon/types_list.c
+test_plugin_snmp_agent_CPPFLAGS = $(AM_CPPFLAGS) \
+       $(BUILD_WITH_LIBNETSNMPAGENT_CPPFLAGS)
+test_plugin_snmp_agent_LDFLAGS = $(PLUGIN_LDFLAGS) \
+       $(BUILD_WITH_LIBNETSNMPAGENT_LDFLAGS)
+test_plugin_snmp_agent_LDADD = liboconfig.la libplugin_mock.la \
+       $(BUILD_WITH_LIBNETSNMPAGENT_LIBS) $(BUILD_WITH_LIBNETSNMP_LIBS)
+
+check_PROGRAMS += test_plugin_snmp_agent
+TESTS += test_plugin_snmp_agent
+
+
 endif
 
 if BUILD_PLUGIN_STATSD
@@ -1894,6 +2039,15 @@ write_sensu_la_SOURCES = src/write_sensu.c
 write_sensu_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 endif
 
+if BUILD_PLUGIN_WRITE_STACKDRIVER
+pkglib_LTLIBRARIES += write_stackdriver.la
+write_stackdriver_la_SOURCES = src/write_stackdriver.c
+write_stackdriver_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+write_stackdriver_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBCURL_CFLAGS)
+write_stackdriver_la_LIBADD = libformat_stackdriver.la libgce.la liboauth.la \
+                     $(BUILD_WITH_LIBCURL_LIBS)
+endif
+
 if BUILD_PLUGIN_WRITE_TSDB
 pkglib_LTLIBRARIES += write_tsdb.la
 write_tsdb_la_SOURCES = src/write_tsdb.c
@@ -2013,15 +2167,15 @@ install-exec-hook:
        else \
                $(INSTALL) -m 0640 $(builddir)/src/collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf; \
        fi; \
-       $(mkinstalldirs) $(DESTDIR)$(pkgdatadir)
-       $(INSTALL) -m 0644 $(srcdir)/src/types.db $(DESTDIR)$(pkgdatadir)/types.db;
+       $(mkinstalldirs) $(DESTDIR)$(cpkgdatadir)
+       $(INSTALL) -m 0644 $(srcdir)/src/types.db $(DESTDIR)$(cpkgdatadir)/types.db;
        $(INSTALL) -m 0644 $(srcdir)/src/postgresql_default.conf \
-               $(DESTDIR)$(pkgdatadir)/postgresql_default.conf;
+               $(DESTDIR)$(cpkgdatadir)/postgresql_default.conf;
 
 uninstall-hook:
-       rm -f $(DESTDIR)$(pkgdatadir)/types.db;
+       rm -f $(DESTDIR)$(cpkgdatadir)/types.db;
        rm -f $(DESTDIR)$(sysconfdir)/collectd.conf
-       rm -f $(DESTDIR)$(pkgdatadir)/postgresql_default.conf;
+       rm -f $(DESTDIR)$(cpkgdatadir)/postgresql_default.conf;
 
 all-local: @PERL_BINDINGS@
 
@@ -2101,3 +2255,4 @@ generic-jmx.jar: $(JAVA_TIMESTAMP_FILE)
 
 jar_DATA = collectd-api.jar generic-jmx.jar
 endif
+
diff --git a/README b/README
index 2210b2b..a594703 100644 (file)
--- a/README
+++ b/README
@@ -314,6 +314,10 @@ Features
       OVS documentation.
       <http://openvswitch.org/support/dist-docs/INSTALL.rst.html>
 
+    - pcie_errors
+      Read errors from PCI Express Device Status and AER extended capabilities.
+      <https://www.design-reuse.com/articles/38374/pcie-error-logging-and-handling-on-a-typical-soc.html>
+
     - perl
       The perl plugin implements a Perl-interpreter into collectd. You can
       write your own plugins in Perl and return arbitrary values using this
@@ -1040,6 +1044,37 @@ To generate the `configure` script, you'll need the following dependencies:
 The `build.sh' script takes no arguments.
 
 
+Building on Windows
+-----------------------------------------------
+
+Collectd can be built on Windows using Cygwin, and the result is a binary that
+runs natively on Windows. That is, Cygwin is only needed for building, not running,
+collectd.
+
+You will need to install the following Cygwin packages:
+- automake
+- bison
+- flex
+- git
+- libtool
+- make
+- mingw64-x86_64-dlfcn
+- mingw64-x86_64-gcc-core
+- mingw64-x86_64-zlib
+- pkg-config
+
+To build, just run the `build.sh' script in your Cygwin terminal. By default, it installs
+to "C:/Program Files/collectd". You can change the location by setting the INSTALL_DIR
+variable:
+
+$ export INSTALL_DIR="C:/some/other/install/directory"
+$ ./build.sh
+
+or:
+
+$ INSTALL_DIR="C:/some/other/install/directory" ./build.sh
+
+
 Crosscompiling
 --------------
 
index bd4c1a3..c0ccce3 100755 (executable)
--- a/build.sh
+++ b/build.sh
@@ -18,34 +18,156 @@ EOF
     done
 }
 
-check_for_application lex bison autoheader aclocal automake autoconf pkg-config
-
-libtoolize=""
-libtoolize --version >/dev/null 2>/dev/null
-if test $? -eq 0; then
-    libtoolize=libtoolize
-else
-    glibtoolize --version >/dev/null 2>/dev/null
+setup_libtool()
+{
+    libtoolize=""
+    libtoolize --version >/dev/null 2>/dev/null
     if test $? -eq 0; then
-        libtoolize=glibtoolize
+        libtoolize=libtoolize
     else
-        cat >&2 <<EOF
+        glibtoolize --version >/dev/null 2>/dev/null
+        if test $? -eq 0; then
+            libtoolize=glibtoolize
+        else
+            cat >&2 <<EOF
 WARNING: Neither \`libtoolize' nor \`glibtoolize' have been found!
     Please make sure that one of them is installed and is in one of the
     directories listed in the PATH environment variable.
 EOF
-        GLOBAL_ERROR_INDICATOR=1
+            GLOBAL_ERROR_INDICATOR=1
+        fi
     fi
- fi
 
-if test "$GLOBAL_ERROR_INDICATOR" != "0"; then
-    exit 1
-fi
+    if test "$GLOBAL_ERROR_INDICATOR" != "0"; then
+        exit 1
+    fi
+}
+
+build()
+{
+    echo "Building..."
+    check_for_application lex bison autoheader aclocal automake autoconf pkg-config
+    setup_libtool
+
+    set -x
+    autoheader \
+    && aclocal -I m4 \
+    && $libtoolize --copy --force \
+    && automake --add-missing --copy \
+    && autoconf
+}
+
+build_cygwin()
+{
+    echo "Building for Cygwin..."
+    check_for_application aclocal autoconf autoheader automake bison flex git make pkg-config x86_64-w64-mingw32-gcc
+    setup_libtool
+
+    set -e
+
+    : ${INSTALL_DIR:="C:/PROGRA~1/collectd"}
+    : ${LIBDIR:="${INSTALL_DIR}"}
+    : ${BINDIR:="${INSTALL_DIR}"}
+    : ${SBINDIR:="${INSTALL_DIR}"}
+    : ${SYSCONFDIR:="${INSTALL_DIR}"}
+    : ${LOCALSTATEDIR:="${INSTALL_DIR}"}
+    : ${DATAROOTDIR:="${INSTALL_DIR}"}
+    : ${DATADIR:="${INSTALL_DIR}"}
 
-set -x
+    echo "Installing collectd to ${INSTALL_DIR}."
+    TOP_SRCDIR="$(pwd)"
+    MINGW_ROOT="$(x86_64-w64-mingw32-gcc -print-sysroot)/mingw"
+    export GNULIB_DIR="${TOP_SRCDIR}/gnulib/build/gllib"
+
+    export CC="x86_64-w64-mingw32-gcc"
+
+    if [ -d "${TOP_SRCDIR}/gnulib/build" ]; then
+        echo "Assuming that gnulib is already built, because gnulib/build exists."
+    else
+        git submodule init
+        git submodule update
+        cd gnulib
+        ./gnulib-tool \
+          --create-testdir \
+          --source-base=lib \
+          --dir=${TOP_SRCDIR}/gnulib/build \
+          canonicalize-lgpl \
+          fcntl-h \
+          fnmatch \
+          getsockopt \
+          gettimeofday \
+          nanosleep \
+          netdb \
+          net_if \
+          poll \
+          recv \
+          regex \
+          sendto \
+          setlocale \
+          strtok_r \
+          sys_resource \
+          sys_socket \
+          sys_stat \
+          sys_wait \
+          time_r
+
+        cd ${TOP_SRCDIR}/gnulib/build
+        ./configure --host="mingw32" LIBS="-lws2_32 -lpthread"
+        make 
+        cd gllib
+
+        # We have to rebuild libgnu.a to get the list of *.o files to build a dll later
+        rm libgnu.a
+        OBJECT_LIST=`make V=1 | grep "ar" | cut -d' ' -f4-`
+        $CC -shared -o libgnu.dll $OBJECT_LIST -lws2_32 -lpthread
+        rm libgnu.a # get rid of it, to use libgnu.dll
+       fi
+    cd "${TOP_SRCDIR}"
+
+    set -x
+    autoreconf --install
+
+    export LDFLAGS="-L${GNULIB_DIR}"
+    export LIBS="-lgnu"
+    export CFLAGS="-Drestrict=__restrict -I${GNULIB_DIR}"
+
+    ./configure \
+      --prefix="${INSTALL_DIR}" \
+      --libdir="${LIBDIR}" \
+      --bindir="${BINDIR}" \
+      --sbindir="${SBINDIR}" \
+      --sysconfdir="${SYSCONFDIR}" \
+      --localstatedir="${LOCALSTATEDIR}" \
+      --datarootdir="${DATAROOTDIR}" \
+      --datarootdir="${DATADIR}" \
+      --disable-all-plugins \
+      --host="mingw32" \
+      --enable-logfile \
+      --enable-match_regex \
+      --enable-target_replace \
+      --enable-target_set
+
+    cp ${GNULIB_DIR}/../config.h src/gnulib_config.h
+    echo "#include <config.h.in>" >> src/gnulib_config.h
+
+    cp libtool libtool_bak
+    sed -i "s%\$LTCC \$LTCFLAGS\(.*cwrapper.*\)%\$LTCC \1%" libtool
+
+    make
+    make install
+
+    cp "${GNULIB_DIR}/libgnu.dll" "${INSTALL_DIR}"
+    cp "${MINGW_ROOT}/bin/zlib1.dll" "${INSTALL_DIR}"
+    cp "${MINGW_ROOT}/bin/libwinpthread-1.dll" "${INSTALL_DIR}"
+    cp "${MINGW_ROOT}/bin/libdl.dll" "${INSTALL_DIR}"
+
+    echo "Done."
+}
+
+os_name="$(uname)"
+if test "${os_name#CYGWIN}" != "$os_name"; then
+    build_cygwin
+else
+    build
+fi
 
-autoheader \
-&& aclocal -I m4 \
-&& $libtoolize --copy --force \
-&& automake --add-missing --copy \
-&& autoconf
index 1e31e21..4627965 100644 (file)
@@ -99,6 +99,10 @@ case $host_os in
     AC_DEFINE([KERNEL_SOLARIS], [1], [True if program is to be compiled for a Solaris kernel])
     ac_system="Solaris"
     ;;
+  *mingw32*)
+    AC_DEFINE([KERNEL_WIN32], [1], [True if program is to be compiled for a Windows kernel])
+    ac_system="Windows"
+    ;;
   *)
     ac_system="unknown"
     ;;
@@ -111,6 +115,7 @@ AM_CONDITIONAL([BUILD_FREEBSD], [test "x$ac_system" = "xFreeBSD"])
 AM_CONDITIONAL([BUILD_LINUX], [test "x$ac_system" = "xLinux"])
 AM_CONDITIONAL([BUILD_OPENBSD], [test "x$ac_system" = "xOpenBSD"])
 AM_CONDITIONAL([BUILD_SOLARIS], [test "x$ac_system" = "xSolaris"])
+AM_CONDITIONAL([BUILD_WIN32], [test "x$ac_system" = "xWindows"])
 
 if test "x$ac_system" = "xSolaris"; then
   AC_DEFINE([_POSIX_PTHREAD_SEMANTICS], [1], [Define to enforce POSIX thread semantics under Solaris.])
@@ -550,6 +555,12 @@ if test "x$ac_system" = "xLinux"; then
     AC_DEFINE([HAVE_CAPABILITY], [1], [Define to 1 if you have cap_get_proc() (-lcap).])
   fi
 
+  # For pcie_errors plugin
+  AC_CHECK_HEADERS([linux/pci_regs.h],
+    [have_pci_regs_h="yes"],
+    [have_pci_regs_h="no (linux/pci_regs.h not found)"]
+  )
+
 else
   have_linux_raid_md_u_h="no"
   have_linux_wireless_h="no"
@@ -745,6 +756,7 @@ AC_CHECK_FUNCS_ONCE([ \
     getaddrinfo \
     getgrnam_r \
     getnameinfo \
+    getpwnam \
     getpwnam_r \
     gettimeofday \
     if_indextoname \
@@ -767,6 +779,12 @@ AC_FUNC_STRERROR_R
 
 SAVE_CFLAGS="$CFLAGS"
 CFLAGS="-Wall -Werror"
+SAVE_LDFLAGS="$LDFLAGS"
+LDFLAGS=""
+if test "x$ac_system" = "xWindows"; then
+  # This is exported from build.sh
+  LDFLAGS="$LDFLAGS -L${GNULIB_DIR}"
+fi
 
 AC_CACHE_CHECK([for strtok_r],
   [c_cv_have_strtok_r_default],
@@ -839,6 +857,7 @@ if test "x$c_cv_have_strtok_r_default" = "xno"; then
 fi
 
 CFLAGS="$SAVE_CFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
 if test "x$c_cv_have_strtok_r_reentrant" = "xyes"; then
   CFLAGS="$CFLAGS -D_REENTRANT=1"
 fi
@@ -848,11 +867,17 @@ AC_CHECK_FUNCS([socket],
   [
     AC_CHECK_LIB([socket], [socket],
       [socket_needs_socket="yes"],
-      [AC_MSG_ERROR([cannot find socket() in libsocket])]
+      [
+        AC_CHECK_LIB([gnu], [rpl_socket],
+          [socket_needs_gnulib="yes"],
+          [AC_MSG_ERROR([cannot find socket() in libsocket])]
+        )
+      ]
     )
   ]
 )
 AM_CONDITIONAL([BUILD_WITH_LIBSOCKET], [test "x$socket_needs_socket" = "xyes"])
+AM_CONDITIONAL([BUILD_WITH_GNULIB], [test "x$socket_needs_gnulib" = "xyes"])
 
 clock_gettime_needs_posix4="no"
 AC_CHECK_FUNCS([clock_gettime],
@@ -2279,6 +2304,8 @@ fi
 
 AC_SUBST(BUILD_WITH_LIBCURL_CFLAGS)
 AC_SUBST(BUILD_WITH_LIBCURL_LIBS)
+
+AM_CONDITIONAL([BUILD_WITH_LIBCURL], [test "x$with_libcurl" = "xyes"])
 # }}}
 
 # --with-libdbi {{{
@@ -3814,7 +3841,7 @@ if test "x$with_libnetsnmp" = "xyes"; then
   LDFLAGS="$LDFLAGS $with_libnetsnmp_ldflags"
 
   AC_CHECK_LIB([netsnmp], [init_snmp],
-    [with_libnetsmp="yes"],
+    [with_libnetsnmp="yes"],
     [with_libnetsnmp="no (libnetsnmp not found)"]
   )
 
@@ -3822,6 +3849,62 @@ if test "x$with_libnetsnmp" = "xyes"; then
 fi
 
 if test "x$with_libnetsnmp" = "xyes"; then
+  SAVE_LDFLAGS="$LDFLAGS"
+  LDFLAGS="$LDFLAGS $with_libnetsnmp_ldflags"
+
+  AC_CHECK_LIB([netsnmp], [netsnmp_get_version],
+    [with_libnetsnmp="yes"],
+    [with_libnetsnmp="no (couldn't get libnetsnmp version)"]
+  )
+
+  LDFLAGS="$SAVE_LDFLAGS"
+fi
+
+if test "x$with_libnetsnmp" = "xyes"; then
+  SAVE_CPPFLAGS="$CPPFLAGS"
+  SAVE_LDFLAGS="$LDFLAGS"
+  SAVE_LIBS="$LIBS"
+  CPPFLAGS="$CPPFLAGS $with_libnetsnmp_cppflags -Wall -Werror"
+  LDFLAGS="$LDFLAGS $with_libnetsnmp_ldflags"
+  LIBS="$LIBS -lnetsnmp"
+
+  AC_CACHE_CHECK([whether netsnmp library has old API],
+    [c_cv_have_netsnmp_old_api],
+    [
+      AC_LINK_IFELSE(
+        [
+          AC_LANG_PROGRAM(
+            [[
+              #include <net-snmp/net-snmp-config.h>
+              #include <net-snmp/net-snmp-includes.h>
+            ]],
+            [[
+              netsnmp_variable_list *key = SNMP_MALLOC_TYPEDEF(netsnmp_variable_list);;
+              int val;
+              u_char type = ASN_INTEGER;
+              snmp_set_var_value(key, &val, sizeof(val));
+              snmp_set_var_typed_value(key, type, &val, sizeof(val));
+              return 0;
+            ]]
+          )
+        ],
+        [c_cv_have_netsnmp_old_api="no"],
+        [c_cv_have_netsnmp_old_api="yes"]
+      )
+    ]
+  )
+
+  if test "x$c_cv_have_netsnmp_old_api" = "xyes"; then
+    AC_DEFINE([HAVE_NETSNMP_OLD_API], [1],
+              ["Define 1 if you have old netsnmp API]")
+  fi
+
+  CPPFLAGS="$SAVE_CPPFLAGS"
+  LDFLAGS="$SAVE_LDFLAGS"
+  LIBS="$SAVE_LIBS"
+fi
+
+if test "x$with_libnetsnmp" = "xyes"; then
   BUILD_WITH_LIBNETSNMP_CPPFLAGS="$with_libnetsnmp_cppflags"
   BUILD_WITH_LIBNETSNMP_LDFLAGS="$with_libnetsnmp_ldflags"
   BUILD_WITH_LIBNETSNMP_LIBS="-lnetsnmp"
@@ -3832,7 +3915,7 @@ AC_SUBST([BUILD_WITH_LIBNETSNMP_LDFLAGS])
 AC_SUBST([BUILD_WITH_LIBNETSNMP_LIBS])
 # }}}
 
-# --with-libnetsmpagent {{{
+# --with-libnetsnmpagent {{{
 AC_ARG_WITH([libnetsnmpagent],
   [AS_HELP_STRING([--with-libnetsnmpagent@<:@=PREFIX@:>@], [Path to libnetsnmpagent.])],
   [
@@ -5153,6 +5236,55 @@ PKG_CHECK_MODULES([LIBSIGROK], [libsigrok < 0.4],
 )
 # }}}
 
+# --with-libssl {{{
+with_libssl_cflags=""
+with_libssl_ldflags=""
+AC_ARG_WITH([libssl], [AS_HELP_STRING([--with-libssl@<:@=PREFIX@:>@], [Path to libssl.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"; then
+               with_libssl_cppflags="-I$withval/include"
+               with_libssl_ldflags="-L$withval/lib"
+               with_libssl="yes"
+       else
+               with_libssl="$withval"
+       fi
+],
+[
+       with_libssl="yes"
+])
+if test "x$with_libssl" = "xyes"; then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libssl_cppflags"
+
+  AC_CHECK_HEADERS([openssl/sha.h openssl/blowfish.h openssl/rand.h],
+    [with_libssl="yes"],
+    [with_libssl="no (ssl header not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libssl" = "xyes"; then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libssl_cppflags"
+       LDFLAGS="$LDFLAGS $with_libssl_ldflags"
+
+       AC_CHECK_LIB([ssl], [OPENSSL_init_ssl], [with_libssl="yes"], [with_libssl="no (Symbol 'SSL_library_init' not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libssl" = "xyes"; then
+       BUILD_WITH_LIBSSL_CFLAGS="$with_libssl_cflags"
+       BUILD_WITH_LIBSSL_LDFLAGS="$with_libssl_ldflags"
+       BUILD_WITH_LIBSSL_LIBS="-lssl -lcrypto"
+       AC_SUBST([BUILD_WITH_LIBSSL_CFLAGS])
+       AC_SUBST([BUILD_WITH_LIBSSL_LDFLAGS])
+       AC_SUBST([BUILD_WITH_LIBSSL_LIBS])
+       AC_DEFINE([HAVE_LIBSSL], [1], [Define if libssl is present and usable.])
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBSSL, test "x$with_libssl" = "xyes")
+# }}}
+
 # --with-libstatgrab {{{
 AC_ARG_WITH([libstatgrab],
   [AS_HELP_STRING([--with-libstatgrab@<:@=PREFIX@:>@], [Path to libstatgrab.])],
@@ -5708,6 +5840,7 @@ AC_SUBST([BUILD_WITH_LIBYAJL_LDFLAGS])
 AC_SUBST([BUILD_WITH_LIBYAJL_LIBS])
 
 AM_CONDITIONAL([BUILD_WITH_LIBYAJL], [test "x$with_libyajl" = "xyes"])
+AM_CONDITIONAL([BUILD_WITH_LIBYAJL2], [test "x$with_libyajl$with_libyajl2" = "xyesyes"])
 # }}}
 
 # --with-mic {{{
@@ -5839,32 +5972,41 @@ AC_SUBST([BUILD_WITH_LIBVARNISH_CFLAGS])
 AC_SUBST([BUILD_WITH_LIBVARNISH_LIBS])
 # }}}
 
-# pkg-config --exists 'libxml-2.0'; pkg-config --exists libvirt {{{
-$PKG_CONFIG --exists 'libxml-2.0' 2>/dev/null
-if test $? -eq 0; then
-  with_libxml2="yes"
-else
-  with_libxml2="no (pkg-config doesn't know libxml-2.0)"
-fi
-
-$PKG_CONFIG --exists libvirt 2>/dev/null
-if test $? = 0; then
-  with_libvirt="yes"
-else
-  with_libvirt="no (pkg-config doesn't know libvirt)"
-fi
-
-if test "x$with_libxml2" = "xyes"; then
-  with_libxml2_cflags="`$PKG_CONFIG --cflags libxml-2.0`"
-  if test $? -ne 0; then
-    with_libxml2="no"
-  fi
-
-  with_libxml2_ldflags="`$PKG_CONFIG --libs libxml-2.0`"
-  if test $? -ne 0; then
-    with_libxml2="no"
-  fi
-fi
+# --with-libxml2 {{{
+AC_ARG_WITH(libxml2,
+  [AS_HELP_STRING([--with-libxml2@<:@=PREFIX@:>@], [Path to libxml2.])],
+  [
+    if test "x$withval" = "xno"; then
+      with_libxml2="no"
+    else if test "x$withval" = "xyes"; then
+      $PKG_CONFIG --exists 'libxml-2.0' 2>/dev/null
+      if test $? -eq 0; then
+        with_libxml2="yes"
+        with_libxml2_cflags="`$PKG_CONFIG --cflags libxml-2.0`"
+        with_libxml2_ldflags="`$PKG_CONFIG --libs libxml-2.0`"
+      else
+        with_libxml2="no (pkg-config doesn't know libxml-2.0)"
+      fi
+    else
+      with_libxml2="yes"
+      with_libxml2_cflags="-I$withval/include"
+      with_libxml2_ldflags="-L$withval/lib"
+    fi; fi
+  ],
+  dnl  if no argument --with-libxml2 was passed, find the library locations
+  dnl  with pkg-config just like above, when --with-libxml2=yes.
+  [
+    with_libxml2="yes"
+    $PKG_CONFIG --exists 'libxml-2.0' 2>/dev/null
+    if test $? -eq 0; then
+      with_libxml2="yes"
+      with_libxml2_cflags="`$PKG_CONFIG --cflags libxml-2.0`"
+      with_libxml2_ldflags="`$PKG_CONFIG --libs libxml-2.0`"
+    else
+      with_libxml2="no (pkg-config doesn't know libxml-2.0)"
+    fi
+  ]
+)
 
 if test "x$with_libxml2" = "xyes"; then
   SAVE_CPPFLAGS="$CPPFLAGS"
@@ -5897,6 +6039,15 @@ fi
 
 AC_SUBST([BUILD_WITH_LIBXML2_CFLAGS])
 AC_SUBST([BUILD_WITH_LIBXML2_LIBS])
+# }}}
+
+# pkg-config --exists libvirt {{{
+$PKG_CONFIG --exists libvirt 2>/dev/null
+if test $? = 0; then
+  with_libvirt="yes"
+else
+  with_libvirt="no (pkg-config doesn't know libvirt)"
+fi
 
 if test "x$with_libvirt" = "xyes"; then
   with_libvirt_cflags="`$PKG_CONFIG --cflags libvirt`"
@@ -6229,6 +6380,7 @@ plugin_nfs="no"
 plugin_numa="no"
 plugin_ovs_events="no"
 plugin_ovs_stats="no"
+plugin_pcie_errors="no"
 plugin_perl="no"
 plugin_pinba="no"
 plugin_processes="no"
@@ -6250,6 +6402,7 @@ plugin_vmem="no"
 plugin_vserver="no"
 plugin_wireless="no"
 plugin_write_prometheus="no"
+plugin_write_stackdriver="no"
 plugin_xencpu="no"
 plugin_zfs_arc="no"
 plugin_zone="no"
@@ -6307,6 +6460,10 @@ if test "x$ac_system" = "xLinux"; then
     plugin_ovs_events="yes"
     plugin_ovs_stats="yes"
   fi
+
+  if test "x$have_pci_regs_h" = "xyes"; then
+    plugin_pcie_errors="yes"
+  fi
 fi
 
 if test "x$ac_system" = "xOpenBSD"; then
@@ -6404,6 +6561,10 @@ if test "x$with_libcurl" = "xyes" && test "x$with_libyajl" = "xyes"; then
   plugin_curl_json="yes"
 fi
 
+if test "x$with_libcurl" = "xyes" && test "x$with_libssl" = "xyes" && test "x$with_libyajl" = "xyes" && test "x$with_libyajl2" = "xyes"; then
+  plugin_write_stackdriver="yes"
+fi
+
 if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes"; then
   plugin_curl_xml="yes"
 fi
@@ -6592,162 +6753,164 @@ AC_ARG_ENABLE([all-plugins],
 
 m4_divert_once([HELP_ENABLE], [])
 
-AC_PLUGIN([aggregation],         [yes],                     [Aggregation plugin])
-AC_PLUGIN([amqp],                [$with_librabbitmq],       [AMQP output plugin])
-AC_PLUGIN([amqp1],               [$with_libqpid_proton],    [AMQP 1.0 output plugin])
-AC_PLUGIN([apache],              [$with_libcurl],           [Apache httpd statistics])
-AC_PLUGIN([apcups],              [yes],                     [Statistics of UPSes by APC])
-AC_PLUGIN([apple_sensors],       [$with_libiokit],          [Apple hardware sensors])
-AC_PLUGIN([aquaero],             [$with_libaquaero5],       [Aquaero hardware sensors])
-AC_PLUGIN([ascent],              [$plugin_ascent],          [AscentEmu player statistics])
-AC_PLUGIN([barometer],           [$plugin_barometer],       [Barometer sensor on I2C])
-AC_PLUGIN([battery],             [$plugin_battery],         [Battery statistics])
-AC_PLUGIN([bind],                [$plugin_bind],            [ISC Bind nameserver statistics])
-AC_PLUGIN([ceph],                [$plugin_ceph],            [Ceph daemon statistics])
-AC_PLUGIN([cgroups],             [$plugin_cgroups],         [CGroups CPU usage accounting])
-AC_PLUGIN([chrony],              [yes],                     [Chrony statistics])
-AC_PLUGIN([conntrack],           [$plugin_conntrack],       [nf_conntrack statistics])
-AC_PLUGIN([contextswitch],       [$plugin_contextswitch],   [context switch statistics])
-AC_PLUGIN([cpu],                 [$plugin_cpu],             [CPU usage statistics])
-AC_PLUGIN([cpufreq],             [$plugin_cpufreq],         [CPU frequency statistics])
-AC_PLUGIN([cpusleep],            [$plugin_cpusleep],        [CPU sleep statistics])
-AC_PLUGIN([csv],                 [yes],                     [CSV output plugin])
-AC_PLUGIN([curl],                [$with_libcurl],           [CURL generic web statistics])
-AC_PLUGIN([curl_json],           [$plugin_curl_json],       [CouchDB statistics])
-AC_PLUGIN([curl_xml],            [$plugin_curl_xml],        [CURL generic xml statistics])
-AC_PLUGIN([dbi],                 [$with_libdbi],            [General database statistics])
-AC_PLUGIN([df],                  [$plugin_df],              [Filesystem usage statistics])
-AC_PLUGIN([disk],                [$plugin_disk],            [Disk usage statistics])
-AC_PLUGIN([dns],                 [$with_libpcap],           [DNS traffic analysis])
-AC_PLUGIN([dpdkevents],          [$plugin_dpdkevents],      [Events from DPDK])
-AC_PLUGIN([dpdkstat],            [$plugin_dpdkstat],        [Stats from DPDK])
-AC_PLUGIN([drbd],                [$plugin_drbd],            [DRBD statistics])
-AC_PLUGIN([email],               [yes],                     [EMail statistics])
-AC_PLUGIN([entropy],             [$plugin_entropy],         [Entropy statistics])
-AC_PLUGIN([ethstat],             [$plugin_ethstat],         [Stats from NIC driver])
-AC_PLUGIN([exec],                [yes],                     [Execution of external programs])
-AC_PLUGIN([fhcount],             [$plugin_fhcount],         [File handles statistics])
-AC_PLUGIN([filecount],           [yes],                     [Count files in directories])
-AC_PLUGIN([fscache],             [$plugin_fscache],         [fscache statistics])
-AC_PLUGIN([gmond],               [$with_libganglia],        [Ganglia plugin])
-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])
-AC_PLUGIN([ipmi],                [$plugin_ipmi],            [IPMI sensor statistics])
-AC_PLUGIN([iptables],            [$with_libiptc],           [IPTables rule counters])
-AC_PLUGIN([ipvs],                [$plugin_ipvs],            [IPVS connection statistics])
-AC_PLUGIN([irq],                 [$plugin_irq],             [IRQ statistics])
-AC_PLUGIN([java],                [$with_java],              [Embed the Java Virtual Machine])
-AC_PLUGIN([load],                [$plugin_load],            [System load])
-AC_PLUGIN([log_logstash],        [$plugin_log_logstash],    [Logstash json_event compatible logging])
-AC_PLUGIN([logfile],             [yes],                     [File logging plugin])
-AC_PLUGIN([lpar],                [$with_perfstat],          [AIX logical partitions statistics])
-AC_PLUGIN([lua],                 [$with_liblua],            [Lua plugin])
-AC_PLUGIN([lvm],                 [$with_liblvm2app],        [LVM statistics])
-AC_PLUGIN([madwifi],             [$have_linux_wireless_h],  [Madwifi wireless statistics])
-AC_PLUGIN([match_empty_counter], [yes],                     [The empty counter match])
-AC_PLUGIN([match_hashed],        [yes],                     [The hashed match])
-AC_PLUGIN([match_regex],         [yes],                     [The regex match])
-AC_PLUGIN([match_timediff],      [yes],                     [The timediff match])
-AC_PLUGIN([match_value],         [yes],                     [The value match])
-AC_PLUGIN([mbmon],               [yes],                     [Query mbmond])
-AC_PLUGIN([mcelog],              [$plugin_mcelog],          [Machine Check Exceptions notifications])
-AC_PLUGIN([md],                  [$have_linux_raid_md_u_h], [md (Linux software RAID) devices])
-AC_PLUGIN([memcachec],           [$with_libmemcached],      [memcachec statistics])
-AC_PLUGIN([memcached],           [yes],                     [memcached statistics])
-AC_PLUGIN([memory],              [$plugin_memory],          [Memory usage])
-AC_PLUGIN([mic],                 [$with_mic],               [Intel Many Integrated Core stats])
-AC_PLUGIN([modbus],              [$with_libmodbus],         [Modbus plugin])
-AC_PLUGIN([mqtt],                [$with_libmosquitto],      [MQTT output plugin])
-AC_PLUGIN([multimeter],          [$plugin_multimeter],      [Read multimeter values])
-AC_PLUGIN([mysql],               [$with_libmysql],          [MySQL statistics])
-AC_PLUGIN([netapp],              [$with_libnetapp],         [NetApp plugin])
-AC_PLUGIN([netlink],             [$with_libmnl],            [Enhanced Linux network statistics])
-AC_PLUGIN([network],             [yes],                     [Network communication plugin])
-AC_PLUGIN([nfs],                 [$plugin_nfs],             [NFS statistics])
-AC_PLUGIN([nginx],               [$with_libcurl],           [nginx statistics])
-AC_PLUGIN([notify_desktop],      [$with_libnotify],         [Desktop notifications])
-AC_PLUGIN([notify_email],        [$with_libesmtp],          [Email notifier])
-AC_PLUGIN([notify_nagios],       [yes],                     [Nagios notification plugin])
-AC_PLUGIN([ntpd],                [yes],                     [NTPd statistics])
-AC_PLUGIN([numa],                [$plugin_numa],            [NUMA virtual memory statistics])
-AC_PLUGIN([nut],                 [$with_libupsclient],      [Network UPS tools statistics])
-AC_PLUGIN([olsrd],               [yes],                     [olsrd statistics])
-AC_PLUGIN([onewire],             [$with_libowcapi],         [OneWire sensor statistics])
-AC_PLUGIN([openldap],            [$with_libldap],           [OpenLDAP statistics])
-AC_PLUGIN([openvpn],             [yes],                     [OpenVPN client statistics])
-AC_PLUGIN([oracle],              [$with_oracle],            [Oracle plugin])
-AC_PLUGIN([ovs_events],          [$plugin_ovs_events],      [OVS events plugin])
-AC_PLUGIN([ovs_stats],           [$plugin_ovs_stats],       [OVS statistics plugin])
-AC_PLUGIN([perl],                [$plugin_perl],            [Embed a Perl interpreter])
-AC_PLUGIN([pf],                  [$have_net_pfvar_h],       [BSD packet filter (PF) statistics])
+AC_PLUGIN([aggregation],         [yes],                       [Aggregation plugin])
+AC_PLUGIN([amqp],                [$with_librabbitmq],         [AMQP output plugin])
+AC_PLUGIN([amqp1],               [$with_libqpid_proton],      [AMQP 1.0 output plugin])
+AC_PLUGIN([apache],              [$with_libcurl],             [Apache httpd statistics])
+AC_PLUGIN([apcups],              [yes],                       [Statistics of UPSes by APC])
+AC_PLUGIN([apple_sensors],       [$with_libiokit],            [Apple hardware sensors])
+AC_PLUGIN([aquaero],             [$with_libaquaero5],         [Aquaero hardware sensors])
+AC_PLUGIN([ascent],              [$plugin_ascent],            [AscentEmu player statistics])
+AC_PLUGIN([barometer],           [$plugin_barometer],         [Barometer sensor on I2C])
+AC_PLUGIN([battery],             [$plugin_battery],           [Battery statistics])
+AC_PLUGIN([bind],                [$plugin_bind],              [ISC Bind nameserver statistics])
+AC_PLUGIN([ceph],                [$plugin_ceph],              [Ceph daemon statistics])
+AC_PLUGIN([cgroups],             [$plugin_cgroups],           [CGroups CPU usage accounting])
+AC_PLUGIN([chrony],              [yes],                       [Chrony statistics])
+AC_PLUGIN([conntrack],           [$plugin_conntrack],         [nf_conntrack statistics])
+AC_PLUGIN([contextswitch],       [$plugin_contextswitch],     [context switch statistics])
+AC_PLUGIN([cpu],                 [$plugin_cpu],               [CPU usage statistics])
+AC_PLUGIN([cpufreq],             [$plugin_cpufreq],           [CPU frequency statistics])
+AC_PLUGIN([cpusleep],            [$plugin_cpusleep],          [CPU sleep statistics])
+AC_PLUGIN([csv],                 [yes],                       [CSV output plugin])
+AC_PLUGIN([curl],                [$with_libcurl],             [CURL generic web statistics])
+AC_PLUGIN([curl_json],           [$plugin_curl_json],         [CouchDB statistics])
+AC_PLUGIN([curl_xml],            [$plugin_curl_xml],          [CURL generic xml statistics])
+AC_PLUGIN([dbi],                 [$with_libdbi],              [General database statistics])
+AC_PLUGIN([df],                  [$plugin_df],                [Filesystem usage statistics])
+AC_PLUGIN([disk],                [$plugin_disk],              [Disk usage statistics])
+AC_PLUGIN([dns],                 [$with_libpcap],             [DNS traffic analysis])
+AC_PLUGIN([dpdkevents],          [$plugin_dpdkevents],        [Events from DPDK])
+AC_PLUGIN([dpdkstat],            [$plugin_dpdkstat],          [Stats from DPDK])
+AC_PLUGIN([drbd],                [$plugin_drbd],              [DRBD statistics])
+AC_PLUGIN([email],               [yes],                       [EMail statistics])
+AC_PLUGIN([entropy],             [$plugin_entropy],           [Entropy statistics])
+AC_PLUGIN([ethstat],             [$plugin_ethstat],           [Stats from NIC driver])
+AC_PLUGIN([exec],                [yes],                       [Execution of external programs])
+AC_PLUGIN([fhcount],             [$plugin_fhcount],           [File handles statistics])
+AC_PLUGIN([filecount],           [yes],                       [Count files in directories])
+AC_PLUGIN([fscache],             [$plugin_fscache],           [fscache statistics])
+AC_PLUGIN([gmond],               [$with_libganglia],          [Ganglia plugin])
+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])
+AC_PLUGIN([ipmi],                [$plugin_ipmi],              [IPMI sensor statistics])
+AC_PLUGIN([iptables],            [$with_libiptc],             [IPTables rule counters])
+AC_PLUGIN([ipvs],                [$plugin_ipvs],              [IPVS connection statistics])
+AC_PLUGIN([irq],                 [$plugin_irq],               [IRQ statistics])
+AC_PLUGIN([java],                [$with_java],                [Embed the Java Virtual Machine])
+AC_PLUGIN([load],                [$plugin_load],              [System load])
+AC_PLUGIN([log_logstash],        [$plugin_log_logstash],      [Logstash json_event compatible logging])
+AC_PLUGIN([logfile],             [yes],                       [File logging plugin])
+AC_PLUGIN([lpar],                [$with_perfstat],            [AIX logical partitions statistics])
+AC_PLUGIN([lua],                 [$with_liblua],              [Lua plugin])
+AC_PLUGIN([lvm],                 [$with_liblvm2app],          [LVM statistics])
+AC_PLUGIN([madwifi],             [$have_linux_wireless_h],    [Madwifi wireless statistics])
+AC_PLUGIN([match_empty_counter], [yes],                       [The empty counter match])
+AC_PLUGIN([match_hashed],        [yes],                       [The hashed match])
+AC_PLUGIN([match_regex],         [yes],                       [The regex match])
+AC_PLUGIN([match_timediff],      [yes],                       [The timediff match])
+AC_PLUGIN([match_value],         [yes],                       [The value match])
+AC_PLUGIN([mbmon],               [yes],                       [Query mbmond])
+AC_PLUGIN([mcelog],              [$plugin_mcelog],            [Machine Check Exceptions notifications])
+AC_PLUGIN([md],                  [$have_linux_raid_md_u_h],   [md (Linux software RAID) devices])
+AC_PLUGIN([memcachec],           [$with_libmemcached],        [memcachec statistics])
+AC_PLUGIN([memcached],           [yes],                       [memcached statistics])
+AC_PLUGIN([memory],              [$plugin_memory],            [Memory usage])
+AC_PLUGIN([mic],                 [$with_mic],                 [Intel Many Integrated Core stats])
+AC_PLUGIN([modbus],              [$with_libmodbus],           [Modbus plugin])
+AC_PLUGIN([mqtt],                [$with_libmosquitto],        [MQTT output plugin])
+AC_PLUGIN([multimeter],          [$plugin_multimeter],        [Read multimeter values])
+AC_PLUGIN([mysql],               [$with_libmysql],            [MySQL statistics])
+AC_PLUGIN([netapp],              [$with_libnetapp],           [NetApp plugin])
+AC_PLUGIN([netlink],             [$with_libmnl],              [Enhanced Linux network statistics])
+AC_PLUGIN([network],             [yes],                       [Network communication plugin])
+AC_PLUGIN([nfs],                 [$plugin_nfs],               [NFS statistics])
+AC_PLUGIN([nginx],               [$with_libcurl],             [nginx statistics])
+AC_PLUGIN([notify_desktop],      [$with_libnotify],           [Desktop notifications])
+AC_PLUGIN([notify_email],        [$with_libesmtp],            [Email notifier])
+AC_PLUGIN([notify_nagios],       [yes],                       [Nagios notification plugin])
+AC_PLUGIN([ntpd],                [yes],                       [NTPd statistics])
+AC_PLUGIN([numa],                [$plugin_numa],              [NUMA virtual memory statistics])
+AC_PLUGIN([nut],                 [$with_libupsclient],        [Network UPS tools statistics])
+AC_PLUGIN([olsrd],               [yes],                       [olsrd statistics])
+AC_PLUGIN([onewire],             [$with_libowcapi],           [OneWire sensor statistics])
+AC_PLUGIN([openldap],            [$with_libldap],             [OpenLDAP statistics])
+AC_PLUGIN([openvpn],             [yes],                       [OpenVPN client statistics])
+AC_PLUGIN([oracle],              [$with_oracle],              [Oracle plugin])
+AC_PLUGIN([ovs_events],          [$plugin_ovs_events],        [OVS events plugin])
+AC_PLUGIN([ovs_stats],           [$plugin_ovs_stats],         [OVS statistics plugin])
+AC_PLUGIN([pcie_errors],         [$plugin_pcie_errors],       [PCIe errors plugin])
+AC_PLUGIN([perl],                [$plugin_perl],              [Embed a Perl interpreter])
+AC_PLUGIN([pf],                  [$have_net_pfvar_h],         [BSD packet filter (PF) statistics])
 # FIXME: Check for libevent, too.
-AC_PLUGIN([pinba],               [$plugin_pinba],           [Pinba statistics])
-AC_PLUGIN([ping],                [$with_liboping],          [Network latency statistics])
-AC_PLUGIN([postgresql],          [$with_libpq],             [PostgreSQL database statistics])
-AC_PLUGIN([powerdns],            [yes],                     [PowerDNS statistics])
-AC_PLUGIN([processes],           [$plugin_processes],       [Process statistics])
-AC_PLUGIN([protocols],           [$plugin_protocols],       [Protocol (IP, TCP, ...) statistics])
-AC_PLUGIN([python],              [$plugin_python],          [Embed a Python interpreter])
-AC_PLUGIN([redis],               [$with_libhiredis],        [Redis plugin])
-AC_PLUGIN([routeros],            [$with_librouteros],       [RouterOS plugin])
-AC_PLUGIN([rrdcached],           [$librrd_rrdc_update],     [RRDTool output plugin])
-AC_PLUGIN([rrdtool],             [$with_librrd],            [RRDTool output plugin])
-AC_PLUGIN([sensors],             [$with_libsensors],        [lm_sensors statistics])
-AC_PLUGIN([serial],              [$plugin_serial],          [serial port traffic])
-AC_PLUGIN([sigrok],              [$with_libsigrok],         [sigrok acquisition sources])
-AC_PLUGIN([smart],               [$plugin_smart],           [SMART statistics])
-AC_PLUGIN([snmp],                [$with_libnetsnmp],        [SNMP querying plugin])
-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])
-AC_PLUGIN([tail_csv],            [yes],                     [Parsing of CSV files])
-AC_PLUGIN([tape],                [$plugin_tape],            [Tape drive statistics])
-AC_PLUGIN([target_notification], [yes],                     [The notification target])
-AC_PLUGIN([target_replace],      [yes],                     [The replace target])
-AC_PLUGIN([target_scale],        [yes],                     [The scale target])
-AC_PLUGIN([target_set],          [yes],                     [The set target])
-AC_PLUGIN([target_v5upgrade],    [yes],                     [The v5upgrade target])
-AC_PLUGIN([tcpconns],            [$plugin_tcpconns],        [TCP connection statistics])
-AC_PLUGIN([teamspeak2],          [yes],                     [TeamSpeak2 server statistics])
-AC_PLUGIN([ted],                 [$plugin_ted],             [Read The Energy Detective values])
-AC_PLUGIN([thermal],             [$plugin_thermal],         [Linux ACPI thermal zone statistics])
-AC_PLUGIN([threshold],           [yes],                     [Threshold checking plugin])
-AC_PLUGIN([tokyotyrant],         [$with_libtokyotyrant],    [TokyoTyrant database statistics])
-AC_PLUGIN([turbostat],           [$plugin_turbostat],       [Advanced statistic on Intel cpu states])
-AC_PLUGIN([unixsock],            [yes],                     [Unixsock communication plugin])
-AC_PLUGIN([uptime],              [$plugin_uptime],          [Uptime statistics])
-AC_PLUGIN([users],               [$plugin_users],           [User statistics])
-AC_PLUGIN([uuid],                [yes],                     [UUID as hostname plugin])
-AC_PLUGIN([varnish],             [$with_libvarnish],        [Varnish cache statistics])
-AC_PLUGIN([virt],                [$plugin_virt],            [Virtual machine statistics])
-AC_PLUGIN([vmem],                [$plugin_vmem],            [Virtual memory statistics])
-AC_PLUGIN([vserver],             [$plugin_vserver],         [Linux VServer statistics])
-AC_PLUGIN([wireless],            [$plugin_wireless],        [Wireless statistics])
-AC_PLUGIN([write_graphite],      [yes],                     [Graphite / Carbon output plugin])
-AC_PLUGIN([write_http],          [$with_libcurl],           [HTTP output plugin])
-AC_PLUGIN([write_kafka],         [$with_librdkafka],        [Kafka output plugin])
-AC_PLUGIN([write_log],           [yes],                     [Log output plugin])
-AC_PLUGIN([write_mongodb],       [$with_libmongoc],         [MongoDB output plugin])
-AC_PLUGIN([write_prometheus],    [$plugin_write_prometheus], [Prometheus write plugin])
-AC_PLUGIN([write_redis],         [$with_libhiredis],        [Redis output plugin])
-AC_PLUGIN([write_riemann],       [$with_libriemann_client], [Riemann output plugin])
-AC_PLUGIN([write_sensu],         [yes],                     [Sensu output plugin])
-AC_PLUGIN([write_tsdb],          [yes],                     [TSDB output plugin])
-AC_PLUGIN([xencpu],              [$plugin_xencpu],          [Xen Host CPU usage])
-AC_PLUGIN([xmms],                [$with_libxmms],           [XMMS statistics])
-AC_PLUGIN([zfs_arc],             [$plugin_zfs_arc],         [ZFS ARC statistics])
-AC_PLUGIN([zone],                [$plugin_zone],            [Solaris container statistics])
-AC_PLUGIN([zookeeper],           [yes],                     [Zookeeper statistics])
+AC_PLUGIN([pinba],               [$plugin_pinba],             [Pinba statistics])
+AC_PLUGIN([ping],                [$with_liboping],            [Network latency statistics])
+AC_PLUGIN([postgresql],          [$with_libpq],               [PostgreSQL database statistics])
+AC_PLUGIN([powerdns],            [yes],                       [PowerDNS statistics])
+AC_PLUGIN([processes],           [$plugin_processes],         [Process statistics])
+AC_PLUGIN([protocols],           [$plugin_protocols],         [Protocol (IP, TCP, ...) statistics])
+AC_PLUGIN([python],              [$plugin_python],            [Embed a Python interpreter])
+AC_PLUGIN([redis],               [$with_libhiredis],          [Redis plugin])
+AC_PLUGIN([routeros],            [$with_librouteros],         [RouterOS plugin])
+AC_PLUGIN([rrdcached],           [$librrd_rrdc_update],       [RRDTool output plugin])
+AC_PLUGIN([rrdtool],             [$with_librrd],              [RRDTool output plugin])
+AC_PLUGIN([sensors],             [$with_libsensors],          [lm_sensors statistics])
+AC_PLUGIN([serial],              [$plugin_serial],            [serial port traffic])
+AC_PLUGIN([sigrok],              [$with_libsigrok],           [sigrok acquisition sources])
+AC_PLUGIN([smart],               [$plugin_smart],             [SMART statistics])
+AC_PLUGIN([snmp],                [$with_libnetsnmp],          [SNMP querying plugin])
+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])
+AC_PLUGIN([tail_csv],            [yes],                       [Parsing of CSV files])
+AC_PLUGIN([tape],                [$plugin_tape],              [Tape drive statistics])
+AC_PLUGIN([target_notification], [yes],                       [The notification target])
+AC_PLUGIN([target_replace],      [yes],                       [The replace target])
+AC_PLUGIN([target_scale],        [yes],                       [The scale target])
+AC_PLUGIN([target_set],          [yes],                       [The set target])
+AC_PLUGIN([target_v5upgrade],    [yes],                       [The v5upgrade target])
+AC_PLUGIN([tcpconns],            [$plugin_tcpconns],          [TCP connection statistics])
+AC_PLUGIN([teamspeak2],          [yes],                       [TeamSpeak2 server statistics])
+AC_PLUGIN([ted],                 [$plugin_ted],               [Read The Energy Detective values])
+AC_PLUGIN([thermal],             [$plugin_thermal],           [Linux ACPI thermal zone statistics])
+AC_PLUGIN([threshold],           [yes],                       [Threshold checking plugin])
+AC_PLUGIN([tokyotyrant],         [$with_libtokyotyrant],      [TokyoTyrant database statistics])
+AC_PLUGIN([turbostat],           [$plugin_turbostat],         [Advanced statistic on Intel cpu states])
+AC_PLUGIN([unixsock],            [yes],                       [Unixsock communication plugin])
+AC_PLUGIN([uptime],              [$plugin_uptime],            [Uptime statistics])
+AC_PLUGIN([users],               [$plugin_users],             [User statistics])
+AC_PLUGIN([uuid],                [yes],                       [UUID as hostname plugin])
+AC_PLUGIN([varnish],             [$with_libvarnish],          [Varnish cache statistics])
+AC_PLUGIN([virt],                [$plugin_virt],              [Virtual machine statistics])
+AC_PLUGIN([vmem],                [$plugin_vmem],              [Virtual memory statistics])
+AC_PLUGIN([vserver],             [$plugin_vserver],           [Linux VServer statistics])
+AC_PLUGIN([wireless],            [$plugin_wireless],          [Wireless statistics])
+AC_PLUGIN([write_graphite],      [yes],                       [Graphite / Carbon output plugin])
+AC_PLUGIN([write_http],          [$with_libcurl],             [HTTP output plugin])
+AC_PLUGIN([write_stackdriver],   [$plugin_write_stackdriver], [Google Stackdriver Monitoring output plugin])
+AC_PLUGIN([write_kafka],         [$with_librdkafka],          [Kafka output plugin])
+AC_PLUGIN([write_log],           [yes],                       [Log output plugin])
+AC_PLUGIN([write_mongodb],       [$with_libmongoc],           [MongoDB output plugin])
+AC_PLUGIN([write_prometheus],    [$plugin_write_prometheus],  [Prometheus write plugin])
+AC_PLUGIN([write_redis],         [$with_libhiredis],          [Redis output plugin])
+AC_PLUGIN([write_riemann],       [$with_libriemann_client],   [Riemann output plugin])
+AC_PLUGIN([write_sensu],         [yes],                       [Sensu output plugin])
+AC_PLUGIN([write_tsdb],          [yes],                       [TSDB output plugin])
+AC_PLUGIN([xencpu],              [$plugin_xencpu],            [Xen Host CPU usage])
+AC_PLUGIN([xmms],                [$with_libxmms],             [XMMS statistics])
+AC_PLUGIN([zfs_arc],             [$plugin_zfs_arc],           [ZFS ARC statistics])
+AC_PLUGIN([zone],                [$plugin_zone],              [Solaris container statistics])
+AC_PLUGIN([zookeeper],           [yes],                       [Zookeeper statistics])
 
 dnl Default configuration file
 # Load either syslog or logfile
@@ -6991,6 +7154,7 @@ AC_MSG_RESULT([    librouteros . . . . . $with_librouteros])
 AC_MSG_RESULT([    librrd  . . . . . . . $with_librrd])
 AC_MSG_RESULT([    libsensors  . . . . . $with_libsensors])
 AC_MSG_RESULT([    libsigrok   . . . . . $with_libsigrok])
+AC_MSG_RESULT([    libssl  . . . . . . . $with_libssl])
 AC_MSG_RESULT([    libstatgrab . . . . . $with_libstatgrab])
 AC_MSG_RESULT([    libtokyotyrant  . . . $with_libtokyotyrant])
 AC_MSG_RESULT([    libudev . . . . . . . $with_libudev])
@@ -7105,6 +7269,7 @@ AC_MSG_RESULT([    openvpn . . . . . . . $enable_openvpn])
 AC_MSG_RESULT([    oracle  . . . . . . . $enable_oracle])
 AC_MSG_RESULT([    ovs_events  . . . . . $enable_ovs_events])
 AC_MSG_RESULT([    ovs_stats . . . . . . $enable_ovs_stats])
+AC_MSG_RESULT([    pcie_errors . . . . . $enable_pcie_errors])
 AC_MSG_RESULT([    perl  . . . . . . . . $enable_perl])
 AC_MSG_RESULT([    pf  . . . . . . . . . $enable_pf])
 AC_MSG_RESULT([    pinba . . . . . . . . $enable_pinba])
@@ -7162,6 +7327,7 @@ AC_MSG_RESULT([    write_prometheus. . . $enable_write_prometheus])
 AC_MSG_RESULT([    write_redis . . . . . $enable_write_redis])
 AC_MSG_RESULT([    write_riemann . . . . $enable_write_riemann])
 AC_MSG_RESULT([    write_sensu . . . . . $enable_write_sensu])
+AC_MSG_RESULT([    write_stackdriver . . $enable_write_stackdriver])
 AC_MSG_RESULT([    write_tsdb  . . . . . $enable_write_tsdb])
 AC_MSG_RESULT([    xencpu  . . . . . . . $enable_xencpu])
 AC_MSG_RESULT([    xmms  . . . . . . . . $enable_xmms])
@@ -7179,3 +7345,4 @@ if test "x$dependency_warning" = "xyes"; then
 fi
 
 # vim: set fdm=marker sw=2 sts=2 ts=2 et :
+
index 2b83151..fdacf74 100644 (file)
@@ -1,3 +1,33 @@
+/**
+ * collectd - contrib/docker/rootfs_prefix/rootfs_prefix.c
+ * Copyright (C) 2016-2018  Marc Fournier
+ * Copyright (C) 2016-2018  Ruben Kerkhof
+ *
+ * MIT License:
+ *
+ * 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:
+ *   Marc Fournier <marc.fournier at camptocamp.com>
+ *   Ruben Kerkhof <ruben at rubenkerkhof.com>
+ **/
+
 #define _GNU_SOURCE
 
 #include <dirent.h>
diff --git a/gnulib b/gnulib
new file mode 160000 (submodule)
index 0000000..2f8140b
--- /dev/null
+++ b/gnulib
@@ -0,0 +1 @@
+Subproject commit 2f8140bc8ce5501e31dcc665b42b5df64f84c20c
index 635af0f..87bb50c 100644 (file)
@@ -372,7 +372,7 @@ static int amqp1_notify(notification_t const *n,
   }
 
   cd_message_t *cdm = malloc(sizeof(*cdm));
-  if (cdm == NULL ){
+  if (cdm == NULL{
     ERROR("amqp1 plugin: notify failed");
     return -1;
   }
@@ -471,15 +471,16 @@ static int amqp1_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
     format_json_initialize((char *)cdm->mbuf.start, &bfill, &bfree);
     format_json_value_list((char *)cdm->mbuf.start, &bfill, &bfree, ds, vl,
                            instance->store_rates);
-    status= format_json_finalize((char *)cdm->mbuf.start, &bfill, &bfree);
+    status = format_json_finalize((char *)cdm->mbuf.start, &bfill, &bfree);
     if (status != 0) {
-      ERROR("amqp1 plugin: format_json_finalize failed with status %i.", status);
+      ERROR("amqp1 plugin: format_json_finalize failed with status %i.",
+            status);
       cd_message_free(cdm);
-      return(status);
+      return status;
     }
     cdm->mbuf.size = strlen(cdm->mbuf.start);
     if (cdm->mbuf.size >= BUFSIZE) {
-      ERROR("amqp1 plugin: format graphite failed");
+      ERROR("amqp1 plugin: format json failed");
       cd_message_free(cdm);
       return -1;
     }
index 3a5e3c3..fe3480d 100644 (file)
@@ -73,9 +73,9 @@ typedef int (*list_callback_t)(const char *name, value_t value,
 struct cb_view_s {
   char *name;
 
-  int qtypes;
-  int resolver_stats;
-  int cacherrsets;
+  _Bool qtypes;
+  _Bool resolver_stats;
+  _Bool cacherrsets;
 
   char **zones;
   size_t zones_num;
@@ -107,12 +107,12 @@ typedef struct list_info_ptr_s list_info_ptr_t;
 static bool config_parse_time = true;
 
 static char *url;
-static int global_opcodes = 1;
-static int global_qtypes = 1;
-static int global_server_stats = 1;
-static int global_zone_maint_stats = 1;
-static int global_resolver_stats;
-static int global_memory_stats = 1;
+static _Bool global_opcodes = 1;
+static _Bool global_qtypes = 1;
+static _Bool global_server_stats = 1;
+static _Bool global_zone_maint_stats = 1;
+static _Bool global_resolver_stats;
+static _Bool global_memory_stats = 1;
 static int timeout = -1;
 
 static cb_view_t *views;
@@ -343,8 +343,6 @@ static int bind_xml_read_derive(xmlDoc *doc, xmlNode *node, /* {{{ */
 
   int status = parse_value(str_ptr, &value, DS_TYPE_DERIVE);
   if (status != 0) {
-    ERROR("bind plugin: Parsing string \"%s\" to derive value failed.",
-          str_ptr);
     xmlFree(str_ptr);
     return -1;
   }
@@ -1410,22 +1408,6 @@ static int bind_xml(const char *data) /* {{{ */
   return ret;
 } /* }}} int bind_xml */
 
-static int bind_config_set_bool(const char *name, int *var, /* {{{ */
-                                oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) {
-    WARNING("bind plugin: The `%s' option needs "
-            "exactly one boolean argument.",
-            name);
-    return -1;
-  }
-
-  if (ci->values[0].value.boolean)
-    *var = 1;
-  else
-    *var = 0;
-  return 0;
-} /* }}} int bind_config_set_bool */
-
 static int bind_config_add_view_zone(cb_view_t *view, /* {{{ */
                                      oconfig_item_t *ci) {
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
@@ -1484,11 +1466,11 @@ static int bind_config_add_view(oconfig_item_t *ci) /* {{{ */
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("QTypes", child->key) == 0)
-      bind_config_set_bool("QTypes", &tmp->qtypes, child);
+      cf_util_get_boolean(child, &tmp->qtypes);
     else if (strcasecmp("ResolverStats", child->key) == 0)
-      bind_config_set_bool("ResolverStats", &tmp->resolver_stats, child);
+      cf_util_get_boolean(child, &tmp->resolver_stats);
     else if (strcasecmp("CacheRRSets", child->key) == 0)
-      bind_config_set_bool("CacheRRSets", &tmp->cacherrsets, child);
+      cf_util_get_boolean(child, &tmp->cacherrsets);
     else if (strcasecmp("Zone", child->key) == 0)
       bind_config_add_view_zone(tmp, child);
     else {
@@ -1508,27 +1490,19 @@ static int bind_config(oconfig_item_t *ci) /* {{{ */
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("Url", child->key) == 0) {
-      if ((child->values_num != 1) ||
-          (child->values[0].type != OCONFIG_TYPE_STRING)) {
-        WARNING("bind plugin: The `Url' option needs "
-                "exactly one string argument.");
-        return -1;
-      }
-
-      sfree(url);
-      url = strdup(child->values[0].value.string);
+      cf_util_get_string(child, &url);
     } else if (strcasecmp("OpCodes", child->key) == 0)
-      bind_config_set_bool("OpCodes", &global_opcodes, child);
+      cf_util_get_boolean(child, &global_opcodes);
     else if (strcasecmp("QTypes", child->key) == 0)
-      bind_config_set_bool("QTypes", &global_qtypes, child);
+      cf_util_get_boolean(child, &global_qtypes);
     else if (strcasecmp("ServerStats", child->key) == 0)
-      bind_config_set_bool("ServerStats", &global_server_stats, child);
+      cf_util_get_boolean(child, &global_server_stats);
     else if (strcasecmp("ZoneMaintStats", child->key) == 0)
-      bind_config_set_bool("ZoneMaintStats", &global_zone_maint_stats, child);
+      cf_util_get_boolean(child, &global_zone_maint_stats);
     else if (strcasecmp("ResolverStats", child->key) == 0)
-      bind_config_set_bool("ResolverStats", &global_resolver_stats, child);
+      cf_util_get_boolean(child, &global_resolver_stats);
     else if (strcasecmp("MemoryStats", child->key) == 0)
-      bind_config_set_bool("MemoryStats", &global_memory_stats, child);
+      cf_util_get_boolean(child, &global_memory_stats);
     else if (strcasecmp("View", child->key) == 0)
       bind_config_add_view(child);
     else if (strcasecmp("ParseTime", child->key) == 0)
index 92cfff6..c7fc576 100644 (file)
@@ -1142,8 +1142,8 @@ static int cconn_validate_revents(struct cconn *io, int revents) {
 }
 
 /** Handle a network event for a connection */
-static int cconn_handle_event(struct cconn *io) {
-  int ret;
+static ssize_t cconn_handle_event(struct cconn *io) {
+  ssize_t ret;
   switch (io->state) {
   case CSTATE_UNCONNECTED:
     ERROR("ceph plugin: cconn_handle_event(name=%s) got to illegal "
@@ -1158,7 +1158,7 @@ static int cconn_handle_event(struct cconn *io) {
     size_t cmd_len = strlen(cmd);
     RETRY_ON_EINTR(
         ret, write(io->asok, ((char *)&cmd) + io->amt, cmd_len - io->amt));
-    DEBUG("ceph plugin: cconn_handle_event(name=%s,state=%d,amt=%d,ret=%d)",
+    DEBUG("ceph plugin: cconn_handle_event(name=%s,state=%d,amt=%d,ret=%zd)",
           io->d->name, io->state, io->amt, ret);
     if (ret < 0) {
       return ret;
@@ -1180,7 +1180,7 @@ static int cconn_handle_event(struct cconn *io) {
   case CSTATE_READ_VERSION: {
     RETRY_ON_EINTR(ret, read(io->asok, ((char *)(&io->d->version)) + io->amt,
                              sizeof(io->d->version) - io->amt));
-    DEBUG("ceph plugin: cconn_handle_event(name=%s,state=%d,ret=%d)",
+    DEBUG("ceph plugin: cconn_handle_event(name=%s,state=%d,ret=%zd)",
           io->d->name, io->state, ret);
     if (ret < 0) {
       return ret;
@@ -1206,7 +1206,7 @@ static int cconn_handle_event(struct cconn *io) {
   case CSTATE_READ_AMT: {
     RETRY_ON_EINTR(ret, read(io->asok, ((char *)(&io->json_len)) + io->amt,
                              sizeof(io->json_len) - io->amt));
-    DEBUG("ceph plugin: cconn_handle_event(name=%s,state=%d,ret=%d)",
+    DEBUG("ceph plugin: cconn_handle_event(name=%s,state=%d,ret=%zd)",
           io->d->name, io->state, ret);
     if (ret < 0) {
       return ret;
@@ -1227,7 +1227,7 @@ static int cconn_handle_event(struct cconn *io) {
   case CSTATE_READ_JSON: {
     RETRY_ON_EINTR(ret,
                    read(io->asok, io->json + io->amt, io->json_len - io->amt));
-    DEBUG("ceph plugin: cconn_handle_event(name=%s,state=%d,ret=%d)",
+    DEBUG("ceph plugin: cconn_handle_event(name=%s,state=%d,ret=%zd)",
           io->d->name, io->state, ret);
     if (ret < 0) {
       return ret;
@@ -1296,8 +1296,8 @@ static int cconn_prepare(struct cconn *io, struct pollfd *fds) {
  */
 static int milli_diff(const struct timeval *t1, const struct timeval *t2) {
   int64_t ret;
-  int sec_diff = t1->tv_sec - t2->tv_sec;
-  int usec_diff = t1->tv_usec - t2->tv_usec;
+  long sec_diff = t1->tv_sec - t2->tv_sec;
+  long usec_diff = t1->tv_usec - t2->tv_usec;
   ret = usec_diff / 1000;
   ret += (sec_diff * 1000);
   return (ret > INT_MAX) ? INT_MAX : ((ret < INT_MIN) ? INT_MIN : (int)ret);
@@ -1305,8 +1305,9 @@ static int milli_diff(const struct timeval *t1, const struct timeval *t2) {
 
 /** This handles the actual network I/O to talk to the Ceph daemons.
  */
-static int cconn_main_loop(uint32_t request_type) {
-  int ret, some_unreachable = 0;
+static ssize_t cconn_main_loop(uint32_t request_type) {
+  int some_unreachable = 0;
+  ssize_t ret;
   struct timeval end_tv;
   struct cconn io_array[g_num_daemons];
 
@@ -1343,7 +1344,7 @@ static int cconn_main_loop(uint32_t request_type) {
       struct cconn *io = io_array + i;
       ret = cconn_prepare(io, fds + nfds);
       if (ret < 0) {
-        WARNING("ceph plugin: cconn_prepare(name=%s,i=%" PRIsz ",st=%d)=%d",
+        WARNING("ceph plugin: cconn_prepare(name=%s,i=%" PRIsz ",st=%d)=%zd",
                 io->d->name, i, io->state, ret);
         cconn_close(io);
         io->request_type = ASOK_REQ_NONE;
@@ -1367,7 +1368,7 @@ static int cconn_main_loop(uint32_t request_type) {
     }
     RETRY_ON_EINTR(ret, poll(fds, nfds, diff));
     if (ret < 0) {
-      ERROR("ceph plugin: poll(2) error: %d", ret);
+      ERROR("ceph plugin: poll(2) error: %zd", ret);
       goto done;
     }
     for (int i = 0; i < nfds; ++i) {
@@ -1388,7 +1389,7 @@ static int cconn_main_loop(uint32_t request_type) {
         ret = cconn_handle_event(io);
         if (ret) {
           WARNING("ceph plugin: cconn_handle_event(name=%s,"
-                  "i=%d,st=%d): error %d",
+                  "i=%d,st=%d): error %zd",
                   io->d->name, i, io->state, ret);
           cconn_close(io);
           io->request_type = ASOK_REQ_NONE;
@@ -1409,7 +1410,7 @@ done:
   return ret;
 }
 
-static int ceph_read(void) { return cconn_main_loop(ASOK_REQ_DATA); }
+static int ceph_read(void) { return (int)cconn_main_loop(ASOK_REQ_DATA); }
 
 /******* lifecycle *******/
 static int ceph_init(void) {
@@ -1436,7 +1437,7 @@ static int ceph_init(void) {
     return ENOENT;
   }
 
-  return cconn_main_loop(ASOK_REQ_VERSION);
+  return (int)cconn_main_loop(ASOK_REQ_VERSION);
 }
 
 static int ceph_shutdown(void) {
index 6fb369a..913aab9 100644 (file)
 #include <arpa/inet.h> /* ntohs/ntohl */
 #endif
 
+/* AIX doesn't have MSG_DONTWAIT */
+#ifndef MSG_DONTWAIT
+#define MSG_DONTWAIT MSG_NONBLOCK
+#endif
+
 #define CONFIG_KEY_HOST "Host"
 #define CONFIG_KEY_PORT "Port"
 #define CONFIG_KEY_TIMEOUT "Timeout"
@@ -440,6 +445,15 @@ static int chrony_recv_response(tChrony_Response *p_resp,
   }
 }
 
+static void chrony_flush_recv_queue(void) {
+  char buf[1];
+
+  if (g_chrony_is_connected) {
+    while (recv(g_chrony_socket, buf, sizeof(buf), MSG_DONTWAIT) > 0)
+      ;
+  }
+}
+
 static int chrony_query(const int p_command, tChrony_Request *p_req,
                         tChrony_Response *p_resp, size_t *p_resp_size) {
   /* Check connection. We simply perform one try as collectd already handles
@@ -964,6 +978,9 @@ static int chrony_read(void) {
     g_chrony_seq_is_initialized = 1;
   }
 
+  /* Ignore late responses that may have been received */
+  chrony_flush_recv_queue();
+
   /* Get daemon stats */
   rc = chrony_request_daemon_stats();
   if (rc != CHRONY_RC_OK)
index d615088..9d508d1 100644 (file)
@@ -10,23 +10,24 @@ collectd-snmp - Documentation of collectd's C<snmp plugin>
   # ...
   <Plugin snmp>
     <Data "powerplus_voltge_input">
-      Type "voltage"
       Table false
-      Instance "input_line1"
+      Type "voltage"
+      TypeInstance "input_line1"
       Scale 0.1
       Values "SNMPv2-SMI::enterprises.6050.5.4.1.1.2.1"
     </Data>
     <Data "hr_users">
-      Type "users"
       Table false
-      Instance ""
+      Type "users"
       Shift -1
       Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
     </Data>
     <Data "std_traffic">
-      Type "if_octets"
       Table true
-      Instance "IF-MIB::ifDescr"
+      Type "if_octets"
+      TypeInstanceOID "IF-MIB::ifDescr"
+      #FilterOID "IF-MIB::ifOperStatus"
+      #FilterValues "1", "2"
       Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
     </Data>
 
@@ -114,8 +115,9 @@ queried using the C<GET> SNMP command (see L<snmpget(1)>) and transmitted to
 collectd. B<One> value list is dispatched and, eventually, one file will be
 written.
 
-When B<Table> is set to B<true>, the OIDs given to B<Values> (see below) are
-queried using the C<GETNEXT> SNMP command until the subtree is left. After all
+When B<Table> is set to B<true>, the OIDs given to B<Values>, B<TypeInstanceOID>,
+B<PluginInstanceOID>, B<HostOID> and B<FilterOID> (see below) are queried using
+the C<GETNEXT> SNMP command until the subtree is left. After all
 the lists (think: all columns of the table) have been read B<several> values
 sets will be dispatches and, eventually, several files will be written. If you
 configure a B<Type> (see above) which needs more than one data source (for
@@ -138,33 +140,66 @@ C<IF-MIB::ifHCInOctets> and C<IF-MIB::ifHCOutOctets>. But, this is because of
 the B<Type> setting, not the B<Table> setting.
 
 Since the semantic of B<Instance> and B<Values> depends on this setting you
-need to set it before setting them. Doing vice verse will result in undefined
+need to set it before setting them. Doing vice versa will result in undefined
 behavior.
 
-=item B<Instance> I<Instance>
+=item B<Plugin> I<Plugin>
 
-Sets the type-instance of the values that are dispatched. The meaning of this
-setting depends on whether B<Table> is set to I<true> or I<false>:
+Use I<Plugin> as the plugin name of the values that are dispatched.
+Defaults to C<snmp>.
 
-If B<Table> is set to I<true>, I<Instance> is interpreted as an SNMP-prefix
-that will return a list of values. Those values are then used as the actual
-type-instance. An example would be the C<IF-MIB::ifDescr> subtree.
-L<variables(5)> from the SNMP distribution describes the format of OIDs.
+=item B<PluginInstance> I<Instance>
 
-If B<Table> is set to I<true> and B<Instance> is omitted, then "SUBID" will be
-used as the instance.
+Sets the plugin-instance of the values that are dispatched to I<Instance> value.
 
-If B<Table> is set to I<false> the actual string configured for I<Instance> is
-copied into the value-list. In this case I<Instance> may be empty, i.E<nbsp>e.
-"".
+When B<Table> is set to I<true> and B<PluginInstanceOID> is set then this option
+has no effect.
 
-=item B<InstancePrefix> I<String>
+Defaults to an empty string.
+
+=item B<TypeInstance> I<Instance>
+
+Sets the type-instance of the values that are dispatched to I<Instance> value.
 
-If B<Table> is set to I<true>, you may feel the need to add something to the
-instance of the files. If set, I<String> is prepended to the instance as
-determined by querying the agent. When B<Table> is set to I<false> this option
+When B<Table> is set to I<true> and B<TypeInstanceOID> is set then this option
 has no effect.
 
+Defaults to an empty string.
+
+=item B<TypeInstanceOID> I<OID>
+
+=item B<PluginInstanceOID> I<OID>
+
+=item B<HostOID> I<OID>
+
+If B<Table> is set to I<true>, I<OID> is interpreted as an SNMP-prefix that will
+return a list of values. Those values are then used as the actual type-instance,
+plugin-instance or host of dispatched metrics. An example would be the
+C<IF-MIB::ifDescr> subtree. L<variables(5)> from the SNMP distribution describes
+the format of OIDs. When option is set to empty string, then "SUBID" will be used
+as the value.
+
+Prefix may be set for values with use of appropriate B<TypeInstancePrefix>,
+B<PluginInstancePrefix> and B<HostPrefix> options.
+
+When B<Table> is set to I<false> these options has no effect.
+
+Defaults: When no one of these options is configured explicitly,
+B<TypeInstanceOID> defaults to an empty string.
+
+=item B<TypeInstancePrefix>
+
+=item B<PluginInstancePrefix>
+
+=item B<HostPrefix>
+
+These options are intented to be used together with B<TypeInstanceOID>,
+B<PluginInstanceOID> and B<HostOID> respectively.
+
+If set, I<String> is preprended to values received by querying the agent.
+
+When B<Table> is set to I<false> these options has no effect.
+
 The C<UPS-MIB> is an example where you need this setting: It has voltages of
 the inlets, outlets and the battery of an UPS. However, it doesn't provide a
 descriptive column for these voltages. In this case having 1, 2,E<nbsp>... as
@@ -172,6 +207,25 @@ instances is not enough, because the inlet voltages and outlet voltages may
 both have the subids 1, 2,E<nbsp>... You can use this setting to distinguish
 between the different voltages.
 
+=item B<Instance> I<Instance>
+
+Attention: this option exists for backwards compatibility only and will be
+removed in next major release. Please use B<TypeInstance> / B<TypeInstanceOID>
+instead.
+
+The meaning of this setting depends on whether B<Table> is set to I<true> or
+I<false>.
+
+If B<Table> is set to I<true>, option behaves as B<TypeInstanceOID>.
+If B<Table> is set to I<false>, option behaves as B<TypeInstance>.
+
+Note what B<Table> option must be set before setting B<Instance>.
+
+=item B<InstancePrefix> I<String>
+
+Attention: this option exists for backwards compatibility only and will be
+removed in next major release. Please use B<TypeInstancePrefix> instead.
+
 =item B<Values> I<OID> [I<OID> ...]
 
 Configures the values to be queried from the SNMP host. The meaning slightly
@@ -208,16 +262,39 @@ This value is not applied to counter-values.
 
 =item B<Ignore> I<Value> [, I<Value> ...]
 
-The ignore values allows one to ignore Instances based on their name and the
-patterns specified by the various values you've entered. The match is a
+The ignore values allows one to ignore TypeInstances based on their name and
+the patterns specified by the various values you've entered. The match is a
 glob-type shell matching.
 
+When B<Table> is set to I<false> then this option has no effect.
+
 =item B<InvertMatch> I<true|false(default)>
 
 The invertmatch value should be use in combination of the Ignore option.
 It changes the behaviour of the Ignore option, from a blacklist behaviour
 when InvertMatch is set to false, to a whitelist when specified to true.
 
+=item B<FilterOID> I<OID>
+
+=item B<FilterValues> I<Value> [, I<Value> ...]
+
+=item B<FilterIgnoreSelected> I<true|false(default)>
+
+When B<Table> is set to I<true>, these options allow to configure filtering
+based on MIB values.
+
+The B<FilterOID> declares I<OID> to fill table column with values.
+The B<FilterValues> declares values list to do match. Whether table row will be
+collected or ignored depends on the B<FilterIgnoreSelected> setting.
+As with other plugins that use the daemon's ignorelist functionality, a string
+that starts and ends with a slash is interpreted as a regular expression.
+
+If no selection is configured at all, B<all> table rows are selected.
+
+When B<Table> is set to I<false> then these options has no effect.
+
+See B<Table> and F</"IGNORELISTS"> for details.
+
 =back
 
 =head2 The Host block
index 4669c65..2d83bbf 100644 (file)
@@ -105,7 +105,7 @@ static double dtime(void) /* {{{ */
 {
   struct timespec ts = {0};
 
-  if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+  if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
     perror("clock_gettime");
 
   return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
index c2aa915..9421475 100644 (file)
 #@BUILD_PLUGIN_ORACLE_TRUE@LoadPlugin oracle
 #@BUILD_PLUGIN_OVS_EVENTS_TRUE@LoadPlugin ovs_events
 #@BUILD_PLUGIN_OVS_STATS_TRUE@LoadPlugin ovs_stats
+#@BUILD_PLUGIN_PCIE_ERRORS_TRUE@LoadPlugin pcie_errors
 #@BUILD_PLUGIN_PERL_TRUE@LoadPlugin perl
 #@BUILD_PLUGIN_PINBA_TRUE@LoadPlugin pinba
 #@BUILD_PLUGIN_PING_TRUE@LoadPlugin ping
 #@BUILD_PLUGIN_WRITE_REDIS_TRUE@LoadPlugin write_redis
 #@BUILD_PLUGIN_WRITE_RIEMANN_TRUE@LoadPlugin write_riemann
 #@BUILD_PLUGIN_WRITE_SENSU_TRUE@LoadPlugin write_sensu
+#@BUILD_PLUGIN_WRITE_STACKDRIVER_TRUE@LoadPlugin write_stackdriver
 #@BUILD_PLUGIN_WRITE_TSDB_TRUE@LoadPlugin write_tsdb
 #@BUILD_PLUGIN_XENCPU_TRUE@LoadPlugin xencpu
 #@BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
 #  Bridges "br0" "br_ext"
 #</Plugin>
 
+#<Plugin pcie_errors>
+#  Source "sysfs"
+#  ReportMasked false
+#  PersistentNotifications false
+#</Plugin>
+
 #<Plugin perl>
 #      IncludeDir "/my/include/path"
 #      BaseName "Collectd::Plugins"
 #   <Node example>
 #      Host "redis.example.com"
 #      Port "6379"
+#      #Socket "/var/run/redis/redis.sock"
 #      Timeout 2000
 #      <Query "LLEN myqueue">
 #        #Database 0
 #              CollectMemory true
 #              CollectDF true
 #              CollectDisk true
+#              CollectHealth true
 #      </Router>
 #</Plugin>
 
 
 #<Plugin snmp>
 #   <Data "powerplus_voltge_input">
-#       Type "voltage"
 #       Table false
-#       Instance "input_line1"
+#       Type "voltage"
+#       TypeInstance "input_line1"
 #       Values "SNMPv2-SMI::enterprises.6050.5.4.1.1.2.1"
 #   </Data>
 #   <Data "hr_users">
-#       Type "users"
 #       Table false
-#       Instance ""
+#       Type "users"
+#       TypeInstance ""
 #       Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
 #   </Data>
 #   <Data "std_traffic">
+#       Table true
 #       Type "if_octets"
+#       TypeInstanceOID "IF-MIB::ifDescr"
+#       #TypeInstancePrefix "port"
+#       Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+#       #FilterOID "IF-MIB::ifOperStatus"
+#       #FilterValues "1", "2"
+#   </Data>
+#   <Data "interface_traffic">
 #       Table true
-#       Instance "IF-MIB::ifDescr"
+#       Type "if_octets"
+#       Plugin "interface"
+#       PluginInstanceOID "IF-MIB::ifDescr"
 #       Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
 #   </Data>
 #
 #    IndexOID "IF-MIB::ifIndex"
 #    SizeOID "IF-MIB::ifNumber"
 #    <Data "ifDescr">
-#      Instance true
+#      <IndexKey>
+#        Source "PluginInstance"
+#      </IndexKey>
 #      Plugin "interface"
 #      OIDs "IF-MIB::ifDescr"
 #    </Data>
 #      Attribute "foo" "bar"
 #</Plugin>
 
+#<Plugin write_stackdriver>
+#  Project "stackdriver-account"
+#  CredentialFile "/path/to/gcp-project-id-12345.json"
+#  Email "123456789012@developer.gserviceaccount.com"
+#  <Resource "global">
+#    Label "project_id" "gcp-project-id"
+#  </Resource>
+#  Url "https://monitoring.googleapis.com/v3"
+#</Plugin>
+
 #<Plugin write_tsdb>
 #      <Node>
 #              Host "localhost"
index 69a1b1e..e11e514 100644 (file)
@@ -1908,6 +1908,11 @@ plugin below on how matches are defined. If the B<MeasureResponseTime> or
 B<MeasureResponseCode> options are set to B<true>, B<Match> blocks are
 optional.
 
+=item B<Interval> I<Interval>
+
+Sets the interval (in seconds) in which the values will be collected from this
+URL. By default the global B<Interval> setting will be used.
+
 =item B<Timeout> I<Milliseconds>
 
 The B<Timeout> option sets the overall timeout for HTTP requests to B<URL>, in
@@ -2104,6 +2109,11 @@ 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<Interval> I<Interval>
+
+Sets the interval (in seconds) in which the values will be collected from this
+URL. By default the global B<Interval> setting will be used.
+
 =item B<Namespace> I<Prefix> I<URL>
 
 If an XPath expression references namespaces, they must be specified
@@ -5438,6 +5448,12 @@ behavior is to let the kernel choose the appropriate interface. Be warned
 that the manual selection of an interface for unicast traffic is only
 necessary in rare cases.
 
+=item B<BindAddress> I<IP Address>
+
+Set the outgoing IP address for IP packets. This option can be used instead of
+the I<Interface> option to explicitly define the IP address which will be used
+to send Packets to the remote server. 
+
 =item B<ResolveInterval> I<Seconds>
 
 Sets the interval at which to re-resolve the DNS for the I<Host>. This is
@@ -6265,6 +6281,52 @@ Default: empty (monitor all bridges)
 
 =back
 
+=head2 Plugin C<pcie_errors>
+
+The I<pcie_errors> plugin collects PCI Express errors from Device Status in Capability
+structure and from Advanced Error Reporting Extended Capability where available.
+At every read it polls config space of PCI Express devices and dispatches
+notification for every error that is set. It checks for new errors at every read.
+The device is indicated in plugin_instance according to format "domain:bus:dev.fn".
+Errors are divided into categories indicated by type_instance: "correctable", and
+for uncorrectable errors "non_fatal" or "fatal".
+Fatal errors are reported as I<NOTIF_FAILURE> and all others as I<NOTIF_WARNING>.
+
+B<Synopsis:>
+
+  <Plugin "pcie_errors">
+    Source "sysfs"
+    AccessDir "/sys/bus/pci"
+    ReportMasked false
+    PersistentNotifications false
+  </Plugin>
+
+B<Options:>
+
+=over 4
+
+=item B<Source> B<sysfs>|B<proc>
+
+Use B<sysfs> or B<proc> to read data from /sysfs or /proc.
+The default value is B<sysfs>.
+
+=item B<AccessDir> I<dir>
+
+Directory used to access device config space. It is optional and defaults to
+/sys/bus/pci for B<sysfs> and to /proc/bus/pci for B<proc>.
+
+=item B<ReportMasked> B<false>|B<true>
+
+If true plugin will notify about errors that are set to masked in Error Mask register.
+Such errors are not reported to the PCI Express Root Complex. Defaults to B<false>.
+
+=item B<PersistentNotifications> B<false>|B<true>
+
+If false plugin will dispatch notification only on set/clear of error.
+The ones already reported will be ignored. Defaults to B<false>.
+
+=back
+
 =head2 Plugin C<perl>
 
 This plugin embeds a Perl-interpreter into collectd and provides an interface
@@ -7217,6 +7279,7 @@ multiple routers:
       CollectRegistrationTable true
       CollectDF true
       CollectDisk true
+      CollectHealth true
     </Router>
   </Plugin>
 
@@ -7277,30 +7340,37 @@ Defaults to B<false>.
 When enabled, the number of sectors written and bad blocks will be collected.
 Defaults to B<false>.
 
+=item B<CollectHealth> B<true>|B<false>
+
+When enabled, the health statistics will be collected. This includes the
+voltage and temperature on supported hardware.
+Defaults to B<false>.
+
 =back
 
 =head2 Plugin C<redis>
 
-The I<Redis plugin> connects to one or more Redis servers and gathers
-information about each server's state. For each server there is a I<Node> block
-which configures the connection parameters for this node.
+The I<Redis plugin> connects to one or more Redis servers, gathers
+information about each server's state and executes user-defined queries.
+For each server there is a I<Node> block which configures the connection
+parameters and set of user-defined queries for this node.
 
   <Plugin redis>
     <Node "example">
         Host "localhost"
         Port "6379"
+        #Socket "/var/run/redis/redis.sock"
         Timeout 2000
+        ReportCommandStats false
+        ReportCpuUsage true
         <Query "LLEN myqueue">
           #Database 0
           Type "queue_length"
           Instance "myqueue"
-        <Query>
+        </Query>
     </Node>
   </Plugin>
 
-The information shown in the synopsis above is the I<default configuration>
-which is used by the plugin if no configuration is present.
-
 =over 4
 
 =item B<Node> I<Nodename>
@@ -7308,7 +7378,9 @@ which is used by the plugin if no configuration is present.
 The B<Node> block identifies a new Redis node, that is a new Redis instance
 running in an specified host and port. The name for node is a canonical
 identifier which is used as I<plugin instance>. It is limited to
-64E<nbsp>characters in length.
+128E<nbsp>characters in length.
+
+When no B<Node> is configured explicitly, plugin connects to "localhost:6379".
 
 =item B<Host> I<Hostname>
 
@@ -7321,6 +7393,11 @@ The B<Port> option is the TCP port on which the Redis instance accepts
 connections. Either a service name of a port number may be given. Please note
 that numerical port numbers must be given as a string, too.
 
+=item B<Socket> I<Path>
+
+Connect to Redis using the UNIX domain socket at I<Path>. If this
+setting is given, the B<Hostname> and B<Port> settings are ignored.
+
 =item B<Password> I<Password>
 
 Use I<Password> to authenticate when connecting to I<Redis>.
@@ -7328,30 +7405,47 @@ Use I<Password> to authenticate when connecting to I<Redis>.
 =item B<Timeout> I<Milliseconds>
 
 The B<Timeout> option set the socket timeout for node response. Since the Redis
-read function is blocking, you should keep this value as low as possible. Keep
-in mind that the sum of all B<Timeout> values for all B<Nodes> should be lower
-than B<Interval> defined globally.
+read function is blocking, you should keep this value as low as possible.
+It is expected what B<Timeout> values should be lower than B<Interval> defined
+globally.
 
-=item B<Query> I<Querystring>
+Defaults to 2000 (2 seconds).
 
-The B<Query> block identifies a query to execute against the redis server.
-There may be an arbitrary number of queries to execute.
+=item B<ReportCommandStats> B<false>|B<true>
 
-=item B<Database> I<Index>
+Enables or disables reporting of statistics based on the command type, including
+rate of command calls and average CPU time consumed by command processing.
+Defaults to B<false>.
 
-This index selects the Redis logical database to use for query. Defaults
-to C<0>.
+=item B<ReportCpuUsage> B<true>|B<false>
+
+Enables or disables reporting of CPU consumption statistics.
+Defaults to B<true>.
+
+=item B<Query> I<Querystring>
+
+The B<Query> block identifies a query to execute against the redis server.
+There may be an arbitrary number of queries to execute. Each query should
+return single string or integer.
 
 =item B<Type> I<Collectd type>
 
-Within a query definition, a valid collectd type to use as when submitting
+Within a query definition, a valid I<collectd type> to use as when submitting
 the result of the query. When not supplied, will default to B<gauge>.
 
+Currently only types with one datasource are supported.
+See L<types.db(5)> for more details on types and their configuration.
+
 =item B<Instance> I<Type instance>
 
 Within a query definition, an optional type instance to use when submitting
 the result of the query. When not supplied will default to the escaped
-command, up to 64 chars.
+command, up to 128 chars.
+
+=item B<Database> I<Index>
+
+This index selects the Redis logical database to use for query. Defaults
+to C<0>.
 
 =back
 
@@ -7791,7 +7885,9 @@ B<Synopsis:>
       IndexOID "IF-MIB::ifIndex"
       SizeOID "IF-MIB::ifNumber"
       <Data "ifDescr">
-        Instance true
+        <IndexKey>
+          Source "PluginInstance"
+        </IndexKey>
         Plugin "interface"
         OIDs "IF-MIB::ifDescr"
       </Data>
@@ -7802,12 +7898,44 @@ B<Synopsis:>
         OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
       </Data>
     </Table>
+    <Table "CPUAffinityTable">
+      <Data "DomainName">
+        <IndexKey>
+          Source "PluginInstance"
+        </IndexKey>
+        Plugin "virt"
+        OIDs "LIBVIRT-HYPERVISOR-MIB::lvhAffinityDomainName"
+      </Data>
+      <Data "VCPU">
+        Plugin "virt"
+        <IndexKey>
+          Source "TypeInstance"
+          Regex "^vcpu_([0-9]{1,3})-cpu_[0-9]{1,3}$"
+          Group 1
+        </IndexKey>
+        OIDs "LIBVIRT-HYPERVISOR-MIB::lvhVCPUIndex"
+      </Data>
+      <Data "CPU">
+        Plugin "virt"
+        <IndexKey>
+          Source "TypeInstance"
+          Regex "^vcpu_[0-9]{1,3}-cpu_([0-9]{1,3})$"
+          Group 1
+        </IndexKey>
+        OIDs "LIBVIRT-HYPERVISOR-MIB::lvhCPUIndex"
+      </Data>
+      <Data "CPUAffinity">
+        Plugin "virt"
+        Type "cpu_affinity"
+        OIDs "LIBVIRT-HYPERVISOR-MIB::lvhCPUAffinity"
+      </Data>
+    </Table>
   </Plugin>
 
 There are two types of blocks that can be contained in the
 C<E<lt>PluginE<nbsp> snmp_agentE<gt>> block: B<Data> and B<Table>:
 
-=head3 The B<Data> block
+=head3 B<Data> block
 
 The B<Data> block defines a list OIDs that are to be handled. This block can
 define scalar or table OIDs. If B<Data> block is defined inside of B<Table>
@@ -7816,11 +7944,34 @@ The following options can be set:
 
 =over 4
 
-=item B<Instance> I<true|false>
+=item B<IndexKey> block
 
-When B<Instance> is set to B<true>, the value for requested OID is copied from
-plugin instance field of corresponding collectd value. If B<Data> block defines
-scalar data type B<Instance> has no effect and can be omitted.
+B<IndexKey> block contains all data needed for proper index build of snmp table.
+In case more than
+one table B<Data> block has B<IndexKey> block present then multiple key index is
+built. If B<Data> block defines scalar data type B<IndexKey> has no effect and can
+be omitted.
+
+=over 8
+
+=item B<Source> I<String>
+
+B<Source> can be set to one of the following values: "Hostname", "Plugin",
+"PluginInstance", "Type", "TypeInstance". This value indicates which field of
+corresponding collectd metric is taken as a SNMP table index.
+
+=item B<Regex> I<String>
+
+B<Regex> option can also be used to parse strings or numbers out of
+specific field. For example: type-instance field which is "vcpu1-cpu2" can be
+parsed into two numeric fields CPU = 2 and VCPU = 1 and can be later used
+as a table index.
+
+=item B<Group> I<Number>
+
+B<Group> number can be specified in case groups are used in regex.
+
+=back
 
 =item B<Plugin> I<String>
 
@@ -8357,13 +8508,14 @@ B<Synopsis:>
    <Metric "snort-dropped">
        Type "percent"
        Instance "dropped"
-       Index 1
+       ValueFrom 1
    </Metric>
    <File "/var/log/snort/snort.stats">
        Plugin "snortstats"
        Instance "eth0"
        Interval 600
        Collect "snort-dropped"
+       #TimeFrom 0
    </File>
  </Plugin>
 
@@ -8954,6 +9106,40 @@ only on the host system.
 
 Only I<Connection> is required.
 
+Consider the following example config:
+
+ <Plugin "virt">
+   Connection "qemu:///system"
+   HostnameFormat "hostname"
+   InterfaceFormat "address"
+   PluginInstanceFormat "name"
+ </Plugin>
+
+It will generate the following values:
+
+  node42.example.com/virt-instance-0006f26c/disk_octets-vda
+  node42.example.com/virt-instance-0006f26c/disk_ops-vda
+  node42.example.com/virt-instance-0006f26c/if_dropped-ca:fe:ca:fe:ca:fe
+  node42.example.com/virt-instance-0006f26c/if_errors-ca:fe:ca:fe:ca:fe
+  node42.example.com/virt-instance-0006f26c/if_octets-ca:fe:ca:fe:ca:fe
+  node42.example.com/virt-instance-0006f26c/if_packets-ca:fe:ca:fe:ca:fe
+  node42.example.com/virt-instance-0006f26c/memory-actual_balloon
+  node42.example.com/virt-instance-0006f26c/memory-available
+  node42.example.com/virt-instance-0006f26c/memory-last_update
+  node42.example.com/virt-instance-0006f26c/memory-major_fault
+  node42.example.com/virt-instance-0006f26c/memory-minor_fault
+  node42.example.com/virt-instance-0006f26c/memory-rss
+  node42.example.com/virt-instance-0006f26c/memory-swap_in
+  node42.example.com/virt-instance-0006f26c/memory-swap_out
+  node42.example.com/virt-instance-0006f26c/memory-total
+  node42.example.com/virt-instance-0006f26c/memory-unused
+  node42.example.com/virt-instance-0006f26c/memory-usable
+  node42.example.com/virt-instance-0006f26c/virt_cpu_total
+  node42.example.com/virt-instance-0006f26c/virt_vcpu-0
+
+You can get information on the metric's units from the online libvirt documentation.
+For instance, I<virt_cpu_total> is in nanoseconds.
+
 =over 4
 
 =item B<Connection> I<uri>
@@ -9056,7 +9242,8 @@ B<uuid> means use the guest's UUID. This is useful if you want to track the
 same guest across migrations.
 
 B<hostname> means to use the global B<Hostname> setting, which is probably not
-useful on its own because all guests will appear to have the same name.
+useful on its own because all guests will appear to have the same name. This is
+useful in conjunction with B<PluginInstanceFormat> though.
 
 You can also specify combinations of these fields. For example B<name uuid>
 means to concatenate the guest name and UUID (with a literal colon character
@@ -9202,6 +9389,7 @@ Synopsis:
      Protocol "tcp"
      LogSendErrors true
      Prefix "collectd"
+     UseTags false
    </Node>
  </Plugin>
 
@@ -9239,13 +9427,20 @@ approach and logging errors fills syslog with unneeded messages.
 
 =item B<Prefix> I<String>
 
-When set, I<String> is added in front of the host name. Dots and whitespace are
-I<not> escaped in this string (see B<EscapeCharacter> below).
+When B<UseTags> is I<false>, B<Prefix> value is added in front of the host name.
+When B<UseTags> is I<true>, B<Prefix> value is added in front of series name.
+
+Dots and whitespace are I<not> escaped in this string (see B<EscapeCharacter>
+below).
 
 =item B<Postfix> I<String>
 
-When set, I<String> is appended to the host name. Dots and whitespace are
-I<not> escaped in this string (see B<EscapeCharacter> below).
+When B<UseTags> is I<false>, B<Postfix> value appended to the host name.
+When B<UseTags> is I<true>, B<Postgix> value appended to the end of series name
+(before the first ; that separates the name from the tags).
+
+Dots and whitespace are I<not> escaped in this string (see B<EscapeCharacter>
+below).
 
 =item B<EscapeCharacter> I<Char>
 
@@ -9267,6 +9462,8 @@ path component, for example C<host.cpu.0.cpu.idle>. If set to B<false> (the
 default), the plugin and plugin instance (and likewise the type and type
 instance) are put into one component, for example C<host.cpu-0.cpu-idle>.
 
+Option value is not used when B<UseTags> is I<true>.
+
 =item B<AlwaysAppendDS> B<false>|B<true>
 
 If set to B<true>, append the name of the I<Data Source> (DS) to the "metric"
@@ -9279,12 +9476,31 @@ If set to B<false> (the default) the C<.> (dot) character is replaced with
 I<EscapeCharacter>. Otherwise, if set to B<true>, the C<.> (dot) character
 is preserved, i.e. passed through.
 
+Option value is not used when B<UseTags> is I<true>.
+
 =item B<DropDuplicateFields> B<false>|B<true>
 
 If set to B<true>, detect and remove duplicate components in Graphite metric
 names. For example, the metric name  C<host.load.load.shortterm> will
 be shortened to C<host.load.shortterm>.
 
+=item B<UseTags> B<false>|B<true>
+
+If set to B<true>, Graphite metric names will be generated as tagged series.
+This allows for much more flexibility than the traditional hierarchical layout.
+
+Example:
+C<test.single;host=example.com;plugin=test;plugin_instance=foo;type=single;type_instance=bar>
+
+You can use B<Postfix> option to add more tags by specifying it like
+C<;tag1=value1;tag2=value2>. Note what tagging support was added since Graphite
+version 1.1.x.
+
+If set to B<true>, the B<SeparateInstances> and B<PreserveSeparator> settings
+are not used.
+
+Default value: B<false>.
+
 =back
 
 =head2 Plugin C<write_log>
@@ -9710,17 +9926,26 @@ been set to B<JSON>.
 =item B<GraphitePrefix> (B<Format>=I<Graphite> only)
 
 A prefix can be added in the metric name when outputting in the I<Graphite>
-format. It's added before the I<Host> name.
+format.
+
+When B<GraphiteUseTags> is I<false>, prefix is added before the I<Host> name.
 Metric name will be
 C<E<lt>prefixE<gt>E<lt>hostE<gt>E<lt>postfixE<gt>E<lt>pluginE<gt>E<lt>typeE<gt>E<lt>nameE<gt>>
 
+When B<GraphiteUseTags> is I<true>, prefix is added in front of series name.
+
 =item B<GraphitePostfix> (B<Format>=I<Graphite> only)
 
 A postfix can be added in the metric name when outputting in the I<Graphite>
-format. It's added after the I<Host> name.
+format.
+
+When B<GraphiteUseTags> is I<false>, postfix is added after the I<Host> name.
 Metric name will be
 C<E<lt>prefixE<gt>E<lt>hostE<gt>E<lt>postfixE<gt>E<lt>pluginE<gt>E<lt>typeE<gt>E<lt>nameE<gt>>
 
+When B<GraphiteUseTags> is I<true>, prefix value appended to the end of series
+name (before the first ; that separates the name from the tags).
+
 =item B<GraphiteEscapeChar> (B<Format>=I<Graphite> only)
 
 Specify a character to replace dots (.) in the host part of the metric name.
@@ -9735,6 +9960,8 @@ path component, for example C<host.cpu.0.cpu.idle>. If set to B<false> (the
 default), the plugin and plugin instance (and likewise the type and type
 instance) are put into one component, for example C<host.cpu-0.cpu-idle>.
 
+Option value is not used when B<GraphiteUseTags> is I<true>.
+
 =item B<GraphiteAlwaysAppendDS> B<true>|B<false>
 
 If set to B<true>, append the name of the I<Data Source> (DS) to the "metric"
@@ -9747,6 +9974,14 @@ If set to B<false> (the default) the C<.> (dot) character is replaced with
 I<GraphiteEscapeChar>. Otherwise, if set to B<true>, the C<.> (dot) character
 is preserved, i.e. passed through.
 
+Option value is not used when B<GraphiteUseTags> is I<true>.
+
+=item B<GraphiteUseTags> B<false>|B<true>
+
+If set to B<true> Graphite metric names will be generated as tagged series.
+
+Default value: B<false>.
+
 =item B<StoreRates> B<true>|B<false>
 
 If set to B<true> (the default), convert counter values to rates. If set to
@@ -10107,6 +10342,133 @@ attribute for each metric being sent out to I<Sensu>.
 
 =back
 
+=head2 Plugin C<write_stackdriver>
+
+The C<write_stackdriver> plugin writes metrics to the
+I<Google Stackdriver Monitoring> service.
+
+This plugin supports two authentication methods: When configured, credentials
+are read from the JSON credentials file specified with B<CredentialFile>.
+Alternatively, when running on
+I<Google Compute Engine> (GCE), an I<OAuth> token is retrieved from the
+I<metadata server> and used to authenticate to GCM.
+
+B<Synopsis:>
+
+ <Plugin write_stackdriver>
+   CredentialFile "/path/to/service_account.json"
+   <Resource "global">
+     Label "project_id" "monitored_project"
+   </Resource>
+ </Plugin>
+
+=over 4
+
+=item B<CredentialFile> I<file>
+
+Path to a JSON credentials file holding the credentials for a GCP service
+account.
+
+If B<CredentialFile> is not specified, the plugin uses I<Application Default
+Credentials>. That means which credentials are used depends on the environment:
+
+=over 4
+
+=item
+
+The environment variable C<GOOGLE_APPLICATION_CREDENTIALS> is checked. If this
+variable is specified it should point to a JSON file that defines the
+credentials.
+
+=item
+
+The path C<${HOME}/.config/gcloud/application_default_credentials.json> is
+checked. This where credentials used by the I<gcloud> command line utility are
+stored. You can use C<gcloud auth application-default login> to create these
+credentials.
+
+Please note that these credentials are often of your personal account, not a
+service account, and are therefore unfit to be used in a production
+environment.
+
+=item
+
+When running on GCE, the built-in service account associated with the virtual
+machine instance is used.
+See also the B<Email> option below.
+
+=back
+
+=item B<Project> I<Project>
+
+The I<Project ID> or the I<Project Number> of the I<Stackdriver Account>. The
+I<Project ID> is a string identifying the GCP project, which you can chose
+freely when creating a new project. The I<Project Number> is a 12-digit decimal
+number. You can look up both on the I<Developer Console>.
+
+This setting is optional. If not set, the project ID is read from the
+credentials file or determined from the GCE's metadata service.
+
+=item B<Email> I<Email> (GCE only)
+
+Choses the GCE I<Service Account> used for authentication.
+
+Each GCE instance has a C<default> I<Service Account> but may also be
+associated with additional I<Service Accounts>. This is often used to restrict
+the permissions of services running on the GCE instance to the required
+minimum. The I<write_stackdriver plugin> requires the
+C<https://www.googleapis.com/auth/monitoring> scope. When multiple I<Service
+Accounts> are available, this option selects which one is used by
+I<write_stackdriver plugin>.
+
+=item B<Resource> I<ResourceType>
+
+Configures the I<Monitored Resource> to use when storing metrics.
+More information on I<Monitored Resources> and I<Monitored Resource Types> are
+available at L<https://cloud.google.com/monitoring/api/resources>.
+
+This block takes one string argument, the I<ResourceType>. Inside the block are
+one or more B<Label> options which configure the resource labels.
+
+This block is optional. The default value depends on the runtime environment:
+on GCE, the C<gce_instance> resource type is used, otherwise the C<global>
+resource type ist used:
+
+=over 4
+
+=item
+
+B<On GCE>, defaults to the equivalent of this config:
+
+  <Resource "gce_instance">
+    Label "project_id" "<project_id>"
+    Label "instance_id" "<instance_id>"
+    Label "zone" "<zone>"
+  </Resource>
+
+The values for I<project_id>, I<instance_id> and I<zone> are read from the GCE
+metadata service.
+
+=item
+
+B<Elsewhere>, i.e. not on GCE, defaults to the equivalent of this config:
+
+  <Resource "global">
+    Label "project_id" "<Project>"
+  </Resource>
+
+Where I<Project> refers to the value of the B<Project> option or the project ID
+inferred from the B<CredentialFile>.
+
+=back
+
+=item B<Url> I<Url>
+
+URL of the I<Stackdriver Monitoring> API. Defaults to
+C<https://monitoring.googleapis.com/v3>.
+
+=back
+
 =head2 Plugin C<xencpu>
 
 This plugin collects metrics of hardware CPU load for machine running Xen
index 4bfd1e4..4925ad0 100644 (file)
@@ -78,18 +78,13 @@ struct web_page_s /* {{{ */
   size_t buffer_fill;
 
   web_match_t *matches;
-
-  web_page_t *next;
 }; /* }}} */
 
 /*
- * Global variables;
- */
-static web_page_t *pages_g;
-
-/*
  * Private functions
  */
+static int cc_read_page(user_data_t *ud);
+
 static size_t cc_curl_callback(void *buf, /* {{{ */
                                size_t size, size_t nmemb, void *user_data) {
   web_page_t *wp;
@@ -137,8 +132,9 @@ static void cc_web_match_free(web_match_t *wm) /* {{{ */
   sfree(wm);
 } /* }}} void cc_web_match_free */
 
-static void cc_web_page_free(web_page_t *wp) /* {{{ */
+static void cc_web_page_free(void *arg) /* {{{ */
 {
+  web_page_t *wp = (web_page_t *)arg;
   if (wp == NULL)
     return;
 
@@ -161,7 +157,6 @@ static void cc_web_page_free(web_page_t *wp) /* {{{ */
   sfree(wp->buffer);
 
   cc_web_match_free(wp->matches);
-  cc_web_page_free(wp->next);
   sfree(wp);
 } /* }}} void cc_web_page_free */
 
@@ -400,6 +395,7 @@ static int cc_page_init_curl(web_page_t *wp) /* {{{ */
 
 static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */
 {
+  cdtime_t interval = 0;
   web_page_t *page;
   int status;
 
@@ -464,6 +460,8 @@ static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */
       status = cc_config_append_string("Header", &page->headers, child);
     else if (strcasecmp("Post", child->key) == 0)
       status = cf_util_get_string(child, &page->post_body);
+    else if (strcasecmp("Interval", child->key) == 0)
+      status = cf_util_get_cdtime(child, &interval);
     else if (strcasecmp("Timeout", child->key) == 0)
       status = cf_util_get_int(child, &page->timeout);
     else if (strcasecmp("Statistics", child->key) == 0) {
@@ -507,17 +505,15 @@ static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */
     return status;
   }
 
-  /* Add the new page to the linked list */
-  if (pages_g == NULL)
-    pages_g = page;
-  else {
-    web_page_t *prev;
+  /* If all went well, register this page for reading */
+  char *cb_name = ssnprintf_alloc("curl-%s-%s", page->instance, page->url);
 
-    prev = pages_g;
-    while (prev->next != NULL)
-      prev = prev->next;
-    prev->next = page;
-  }
+  plugin_register_complex_read(/* group = */ NULL, cb_name, cc_read_page,
+                               interval,
+                               &(user_data_t){
+                                   .data = page, .free_func = cc_web_page_free,
+                               });
+  sfree(cb_name);
 
   return 0;
 } /* }}} int cc_config_add_page */
@@ -556,10 +552,6 @@ static int cc_config(oconfig_item_t *ci) /* {{{ */
 
 static int cc_init(void) /* {{{ */
 {
-  if (pages_g == NULL) {
-    INFO("curl plugin: No pages have been defined.");
-    return -1;
-  }
   curl_global_init(CURL_GLOBAL_SSL);
   return 0;
 } /* }}} int cc_init */
@@ -608,8 +600,16 @@ static void cc_submit_response_time(const web_page_t *wp, /* {{{ */
   plugin_dispatch_values(&vl);
 } /* }}} void cc_submit_response_time */
 
-static int cc_read_page(web_page_t *wp) /* {{{ */
+static int cc_read_page(user_data_t *ud) /* {{{ */
 {
+
+  if ((ud == NULL) || (ud->data == NULL)) {
+    ERROR("curl plugin: cc_read_page: Invalid user data.");
+    return -1;
+  }
+
+  web_page_t *wp = (web_page_t *)ud->data;
+
   int status;
   cdtime_t start = 0;
 
@@ -666,25 +666,7 @@ static int cc_read_page(web_page_t *wp) /* {{{ */
   return 0;
 } /* }}} int cc_read_page */
 
-static int cc_read(void) /* {{{ */
-{
-  for (web_page_t *wp = pages_g; wp != NULL; wp = wp->next)
-    cc_read_page(wp);
-
-  return 0;
-} /* }}} int cc_read */
-
-static int cc_shutdown(void) /* {{{ */
-{
-  cc_web_page_free(pages_g);
-  pages_g = NULL;
-
-  return 0;
-} /* }}} int cc_shutdown */
-
 void module_register(void) {
   plugin_register_complex_config("curl", cc_config);
   plugin_register_init("curl", cc_init);
-  plugin_register_read("curl", cc_read);
-  plugin_register_shutdown("curl", cc_shutdown);
 } /* void module_register */
index f0badc9..dedfed0 100644 (file)
@@ -97,7 +97,6 @@ struct cj_s /* {{{ */
   char *cacert;
   struct curl_slist *headers;
   char *post_body;
-  cdtime_t interval;
   int timeout;
   curl_stats_t *stats;
 
@@ -256,7 +255,6 @@ static int cj_cb_number(void *ctx, const char *number, yajl_len_t number_len) {
   value_t vt;
   int status = parse_value(buffer, &vt, type);
   if (status != 0) {
-    NOTICE("curl_json plugin: Unable to parse number: \"%s\"", buffer);
     cj_advance_array(ctx);
     return CJ_CB_CONTINUE;
   }
@@ -624,9 +622,6 @@ static int cj_init_curl(cj_t *db) /* {{{ */
 #ifdef HAVE_CURLOPT_TIMEOUT_MS
   if (db->timeout >= 0)
     curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS, (long)db->timeout);
-  else if (db->interval > 0)
-    curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
-                     (long)CDTIME_T_TO_MS(db->interval));
   else
     curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
                      (long)CDTIME_T_TO_MS(plugin_get_interval()));
@@ -639,6 +634,7 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
 {
   cj_t *db;
   int status = 0;
+  cdtime_t interval = 0;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
     WARNING("curl_json plugin: The `URL' block "
@@ -699,7 +695,7 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
     else if (strcasecmp("Key", child->key) == 0)
       status = cj_config_add_key(db, child);
     else if (strcasecmp("Interval", child->key) == 0)
-      status = cf_util_get_cdtime(child, &db->interval);
+      status = cf_util_get_cdtime(child, &interval);
     else if (strcasecmp("Timeout", child->key) == 0)
       status = cf_util_get_int(child, &db->timeout);
     else if (strcasecmp("Statistics", child->key) == 0) {
@@ -737,8 +733,7 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
     cb_name = ssnprintf_alloc("curl_json-%s-%s", db->instance,
                               db->url ? db->url : db->sock);
 
-    plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read,
-                                 /* interval = */ db->interval,
+    plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read, interval,
                                  &(user_data_t){
                                      .data = db, .free_func = cj_free,
                                  });
@@ -816,9 +811,6 @@ static void cj_submit_impl(cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
   sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, key->type, sizeof(vl.type));
 
-  if (db->interval > 0)
-    vl.interval = db->interval;
-
   plugin_dispatch_values(&vl);
 } /* }}} int cj_submit_impl */
 
index 654bb67..0bed05a 100644 (file)
@@ -823,6 +823,8 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
     return status;
   }
 
+  cdtime_t interval = 0;
+
   /* Fill the `cx_t' structure.. */
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
@@ -853,6 +855,8 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string(child, &db->post_body);
     else if (strcasecmp("Namespace", child->key) == 0)
       status = cx_config_add_namespace(db, child);
+    else if (strcasecmp("Interval", child->key) == 0)
+      status = cf_util_get_cdtime(child, &interval);
     else if (strcasecmp("Timeout", child->key) == 0)
       status = cf_util_get_int(child, &db->timeout);
     else if (strcasecmp("Statistics", child->key) == 0) {
@@ -891,7 +895,7 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
   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,
+                               /* interval = */ interval,
                                &(user_data_t){
                                    .data = db, .free_func = cx_free,
                                });
diff --git a/src/daemon/cmd.c b/src/daemon/cmd.c
new file mode 100644 (file)
index 0000000..7b77995
--- /dev/null
@@ -0,0 +1,271 @@
+/**
+ * collectd - src/daemon/cmd.c
+ * Copyright (C) 2005-2007  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.
+ **/
+
+#include "cmd.h"
+#include "collectd.h"
+
+#include "common.h"
+#include <sys/un.h>
+
+static void *do_flush(void __attribute__((unused)) * arg) {
+  INFO("Flushing all data.");
+  plugin_flush(/* plugin = */ NULL,
+               /* timeout = */ 0,
+               /* ident = */ NULL);
+  INFO("Finished flushing all data.");
+  pthread_exit(NULL);
+  return NULL;
+}
+
+static void sig_int_handler(int __attribute__((unused)) signal) {
+  stop_collectd();
+}
+
+static void sig_term_handler(int __attribute__((unused)) signal) {
+  stop_collectd();
+}
+
+static void sig_usr1_handler(int __attribute__((unused)) signal) {
+  pthread_t thread;
+  pthread_attr_t attr;
+
+  /* flushing the data might take a while,
+   * so it should be done asynchronously */
+  pthread_attr_init(&attr);
+  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+  pthread_create(&thread, &attr, do_flush, NULL);
+  pthread_attr_destroy(&attr);
+}
+
+#if COLLECT_DAEMON
+static int pidfile_create(void) {
+  FILE *fh;
+  const char *file = global_option_get("PIDFile");
+
+  if ((fh = fopen(file, "w")) == NULL) {
+    ERROR("fopen (%s): %s", file, STRERRNO);
+    return 1;
+  }
+
+  fprintf(fh, "%i\n", (int)getpid());
+  fclose(fh);
+
+  return 0;
+} /* static int pidfile_create (const char *file) */
+
+static int pidfile_remove(void) {
+  const char *file = global_option_get("PIDFile");
+  if (file == NULL)
+    return 0;
+
+  return unlink(file);
+} /* static int pidfile_remove (const char *file) */
+#endif /* COLLECT_DAEMON */
+
+#ifdef KERNEL_LINUX
+static int notify_upstart(void) {
+  char const *upstart_job = getenv("UPSTART_JOB");
+
+  if (upstart_job == NULL)
+    return 0;
+
+  if (strcmp(upstart_job, "collectd") != 0) {
+    WARNING("Environment specifies unexpected UPSTART_JOB=\"%s\", expected "
+            "\"collectd\". Ignoring the variable.",
+            upstart_job);
+    return 0;
+  }
+
+  NOTICE("Upstart detected, stopping now to signal readiness.");
+  raise(SIGSTOP);
+  unsetenv("UPSTART_JOB");
+
+  return 1;
+}
+
+static int notify_systemd(void) {
+  size_t su_size;
+  const char *notifysocket = getenv("NOTIFY_SOCKET");
+  if (notifysocket == NULL)
+    return 0;
+
+  if ((strlen(notifysocket) < 2) ||
+      ((notifysocket[0] != '@') && (notifysocket[0] != '/'))) {
+    ERROR("invalid notification socket NOTIFY_SOCKET=\"%s\": path must be "
+          "absolute",
+          notifysocket);
+    return 0;
+  }
+  NOTICE("Systemd detected, trying to signal readiness.");
+
+  unsetenv("NOTIFY_SOCKET");
+
+  int fd;
+#if defined(SOCK_CLOEXEC)
+  fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, /* protocol = */ 0);
+#else
+  fd = socket(AF_UNIX, SOCK_DGRAM, /* protocol = */ 0);
+#endif
+  if (fd < 0) {
+    ERROR("creating UNIX socket failed: %s", STRERRNO);
+    return 0;
+  }
+
+  struct sockaddr_un su = {0};
+  su.sun_family = AF_UNIX;
+  if (notifysocket[0] != '@') {
+    /* regular UNIX socket */
+    sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path));
+    su_size = sizeof(su);
+  } else {
+    /* Linux abstract namespace socket: specify address as "\0foo", i.e.
+     * start with a null byte. Since null bytes have no special meaning in
+     * that case, we have to set su_size correctly to cover only the bytes
+     * that are part of the address. */
+    sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path));
+    su.sun_path[0] = 0;
+    su_size = sizeof(sa_family_t) + strlen(notifysocket);
+    if (su_size > sizeof(su))
+      su_size = sizeof(su);
+  }
+
+  const char buffer[] = "READY=1\n";
+  if (sendto(fd, buffer, strlen(buffer), MSG_NOSIGNAL, (void *)&su,
+             (socklen_t)su_size) < 0) {
+    ERROR("sendto(\"%s\") failed: %s", notifysocket, STRERRNO);
+    close(fd);
+    return 0;
+  }
+
+  unsetenv("NOTIFY_SOCKET");
+  close(fd);
+  return 1;
+}
+#endif /* KERNEL_LINUX */
+
+int main(int argc, char **argv) {
+  struct cmdline_config config = init_config(argc, argv);
+
+#if COLLECT_DAEMON
+  /*
+   * fork off child
+   */
+  struct sigaction sig_chld_action = {.sa_handler = SIG_IGN};
+
+  sigaction(SIGCHLD, &sig_chld_action, NULL);
+
+  /*
+   * Only daemonize if we're not being supervised
+   * by upstart or systemd (when using Linux).
+   */
+  if (config.daemonize
+#ifdef KERNEL_LINUX
+      && notify_upstart() == 0 && notify_systemd() == 0
+#endif
+      ) {
+    pid_t pid;
+    if ((pid = fork()) == -1) {
+      /* error */
+      fprintf(stderr, "fork: %s", STRERRNO);
+      return 1;
+    } else if (pid != 0) {
+      /* parent */
+      /* printf ("Running (PID %i)\n", pid); */
+      return 0;
+    }
+
+    /* Detach from session */
+    setsid();
+
+    /* Write pidfile */
+    if (pidfile_create())
+      exit(2);
+
+    /* close standard descriptors */
+    close(2);
+    close(1);
+    close(0);
+
+    int status = open("/dev/null", O_RDWR);
+    if (status != 0) {
+      ERROR("Error: Could not connect `STDIN' to `/dev/null' (status %d)",
+            status);
+      return 1;
+    }
+
+    status = dup(0);
+    if (status != 1) {
+      ERROR("Error: Could not connect `STDOUT' to `/dev/null' (status %d)",
+            status);
+      return 1;
+    }
+
+    status = dup(0);
+    if (status != 2) {
+      ERROR("Error: Could not connect `STDERR' to `/dev/null', (status %d)",
+            status);
+      return 1;
+    }
+  }    /* if (config.daemonize) */
+#endif /* COLLECT_DAEMON */
+
+  struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN};
+
+  sigaction(SIGPIPE, &sig_pipe_action, NULL);
+
+  /*
+   * install signal handlers
+   */
+  struct sigaction sig_int_action = {.sa_handler = sig_int_handler};
+
+  if (sigaction(SIGINT, &sig_int_action, NULL) != 0) {
+    ERROR("Error: Failed to install a signal handler for signal INT: %s",
+          STRERRNO);
+    return 1;
+  }
+
+  struct sigaction sig_term_action = {.sa_handler = sig_term_handler};
+
+  if (sigaction(SIGTERM, &sig_term_action, NULL) != 0) {
+    ERROR("Error: Failed to install a signal handler for signal TERM: %s",
+          STRERRNO);
+    return 1;
+  }
+
+  struct sigaction sig_usr1_action = {.sa_handler = sig_usr1_handler};
+
+  if (sigaction(SIGUSR1, &sig_usr1_action, NULL) != 0) {
+    ERROR("Error: Failed to install a signal handler for signal USR1: %s",
+          STRERRNO);
+    return 1;
+  }
+
+  int exit_status = run_loop(config.test_readall);
+
+#if COLLECT_DAEMON
+  if (config.daemonize)
+    pidfile_remove();
+#endif /* COLLECT_DAEMON */
+
+  return exit_status;
+} /* int main */
diff --git a/src/daemon/cmd.h b/src/daemon/cmd.h
new file mode 100644 (file)
index 0000000..152ee63
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * collectd - src/daemon/cmd.h
+ * Copyright (C) 2018  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.
+ **/
+
+#ifndef CMD_H
+#define CMD_H
+
+#include <stdbool.h>
+
+struct cmdline_config {
+  bool test_config;
+  bool test_readall;
+  bool create_basedir;
+  const char *configfile;
+  bool daemonize;
+};
+
+void stop_collectd(void);
+struct cmdline_config init_config(int argc, char **argv);
+int run_loop(bool test_readall);
+
+#endif /* CMD_H */
diff --git a/src/daemon/cmd_windows.c b/src/daemon/cmd_windows.c
new file mode 100755 (executable)
index 0000000..2542be5
--- /dev/null
@@ -0,0 +1,40 @@
+/**\r
+ * collectd - src/collectd_windows.c\r
+ * Copyright (C) 2017  Google LLC\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining a\r
+ * copy of this software and associated documentation files (the "Software"),\r
+ * to deal in the Software without restriction, including without limitation\r
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+ * and/or sell copies of the Software, and to permit persons to whom the\r
+ * Software is furnished to do so, subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be included in\r
+ * all copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r
+ * DEALINGS IN THE SOFTWARE.\r
+ **/\r
+\r
+#include "cmd.h"\r
+#include "plugin.h"\r
+#include <stdio.h>\r
+#include <windows.h>\r
+\r
+int main(int argc, char **argv) {\r
+  WSADATA wsaData;\r
+  WORD wVersionRequested = MAKEWORD(2, 2);\r
+  int err = WSAStartup(wVersionRequested, &wsaData);\r
+  if (err != 0) {\r
+    ERROR("WSAStartup failed with error: %d\n", err);\r
+    return 1;\r
+  }\r
+\r
+  struct cmdline_config config = init_config(argc, argv);\r
+  return run_loop(config.test_readall);\r
+}\r
index 7e85975..f1a4923 100644 (file)
@@ -25,6 +25,7 @@
  *   Alvaro Barcellos <alvaro.barcellos at gmail.com>
  **/
 
+#include "cmd.h"
 #include "collectd.h"
 
 #include "common.h"
@@ -33,7 +34,6 @@
 
 #include <netdb.h>
 #include <sys/types.h>
-#include <sys/un.h>
 
 #if HAVE_LOCALE_H
 #include <locale.h>
 #define COLLECTD_LOCALE "C"
 #endif
 
-static int loop;
-
-static void *do_flush(void __attribute__((unused)) * arg) {
-  INFO("Flushing all data.");
-  plugin_flush(/* plugin = */ NULL,
-               /* timeout = */ 0,
-               /* ident = */ NULL);
-  INFO("Finished flushing all data.");
-  pthread_exit(NULL);
-  return NULL;
-}
-
-static void sig_int_handler(int __attribute__((unused)) signal) { loop++; }
-
-static void sig_term_handler(int __attribute__((unused)) signal) { loop++; }
-
-static void sig_usr1_handler(int __attribute__((unused)) signal) {
-  pthread_t thread;
-  pthread_attr_t attr;
+#ifdef WIN32
+#undef COLLECT_DAEMON
+#include <unistd.h>
+#undef gethostname
+#include <locale.h>
+#include <winsock2.h>
+#endif
 
-  /* flushing the data might take a while,
-   * so it should be done asynchronously */
-  pthread_attr_init(&attr);
-  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-  pthread_create(&thread, &attr, do_flush, NULL);
-  pthread_attr_destroy(&attr);
-}
+static int loop;
 
 static int init_hostname(void) {
   const char *str = global_option_get("Hostname");
@@ -86,10 +68,14 @@ static int init_hostname(void) {
     return 0;
   }
 
+#ifdef WIN32
+  long hostname_len = NI_MAXHOST;
+#else
   long hostname_len = sysconf(_SC_HOST_NAME_MAX);
   if (hostname_len == -1) {
     hostname_len = NI_MAXHOST;
   }
+#endif /* WIN32 */
   char hostname[hostname_len];
 
   if (gethostname(hostname, hostname_len) != 0) {
@@ -318,124 +304,10 @@ static int do_shutdown(void) {
   return plugin_shutdown_all();
 } /* int do_shutdown */
 
-#if COLLECT_DAEMON
-static int pidfile_create(void) {
-  FILE *fh;
-  const char *file = global_option_get("PIDFile");
-
-  if ((fh = fopen(file, "w")) == NULL) {
-    ERROR("fopen (%s): %s", file, STRERRNO);
-    return 1;
-  }
-
-  fprintf(fh, "%i\n", (int)getpid());
-  fclose(fh);
-
-  return 0;
-} /* static int pidfile_create (const char *file) */
-
-static int pidfile_remove(void) {
-  const char *file = global_option_get("PIDFile");
-  if (file == NULL)
-    return 0;
-
-  return unlink(file);
-} /* static int pidfile_remove (const char *file) */
-#endif /* COLLECT_DAEMON */
-
-#ifdef KERNEL_LINUX
-static int notify_upstart(void) {
-  char const *upstart_job = getenv("UPSTART_JOB");
-
-  if (upstart_job == NULL)
-    return 0;
-
-  if (strcmp(upstart_job, "collectd") != 0) {
-    WARNING("Environment specifies unexpected UPSTART_JOB=\"%s\", expected "
-            "\"collectd\". Ignoring the variable.",
-            upstart_job);
-    return 0;
-  }
-
-  NOTICE("Upstart detected, stopping now to signal readiness.");
-  raise(SIGSTOP);
-  unsetenv("UPSTART_JOB");
-
-  return 1;
-}
-
-static int notify_systemd(void) {
-  size_t su_size;
-  const char *notifysocket = getenv("NOTIFY_SOCKET");
-  if (notifysocket == NULL)
-    return 0;
-
-  if ((strlen(notifysocket) < 2) ||
-      ((notifysocket[0] != '@') && (notifysocket[0] != '/'))) {
-    ERROR("invalid notification socket NOTIFY_SOCKET=\"%s\": path must be "
-          "absolute",
-          notifysocket);
-    return 0;
-  }
-  NOTICE("Systemd detected, trying to signal readiness.");
-
-  unsetenv("NOTIFY_SOCKET");
-
-  int fd;
-#if defined(SOCK_CLOEXEC)
-  fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, /* protocol = */ 0);
-#else
-  fd = socket(AF_UNIX, SOCK_DGRAM, /* protocol = */ 0);
-#endif
-  if (fd < 0) {
-    ERROR("creating UNIX socket failed: %s", STRERRNO);
-    return 0;
-  }
-
-  struct sockaddr_un su = {0};
-  su.sun_family = AF_UNIX;
-  if (notifysocket[0] != '@') {
-    /* regular UNIX socket */
-    sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path));
-    su_size = sizeof(su);
-  } else {
-    /* Linux abstract namespace socket: specify address as "\0foo", i.e.
-     * start with a null byte. Since null bytes have no special meaning in
-     * that case, we have to set su_size correctly to cover only the bytes
-     * that are part of the address. */
-    sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path));
-    su.sun_path[0] = 0;
-    su_size = sizeof(sa_family_t) + strlen(notifysocket);
-    if (su_size > sizeof(su))
-      su_size = sizeof(su);
-  }
-
-  const char buffer[] = "READY=1\n";
-  if (sendto(fd, buffer, strlen(buffer), MSG_NOSIGNAL, (void *)&su,
-             (socklen_t)su_size) < 0) {
-    ERROR("sendto(\"%s\") failed: %s", notifysocket, STRERRNO);
-    close(fd);
-    return 0;
-  }
-
-  unsetenv("NOTIFY_SOCKET");
-  close(fd);
-  return 1;
-}
-#endif /* KERNEL_LINUX */
-
-struct cmdline_config {
-  bool test_config;
-  bool test_readall;
-  bool create_basedir;
-  const char *configfile;
-  bool daemonize;
-};
-
 static void read_cmdline(int argc, char **argv, struct cmdline_config *config) {
   /* read options */
   while (1) {
-    int c = getopt(argc, argv, "htTC:"
+    int c = getopt(argc, argv, "BhtTC:"
 #if COLLECT_DAEMON
                                "fP:"
 #endif
@@ -516,9 +388,9 @@ static int configure_collectd(struct cmdline_config *config) {
   return 0;
 }
 
-int main(int argc, char **argv) {
-  int exit_status = 0;
+void stop_collectd(void) { loop++; }
 
+struct cmdline_config init_config(int argc, char **argv) {
   struct cmdline_config config = {
       .daemonize = true, .create_basedir = true, .configfile = CONFIGFILE,
   };
@@ -526,7 +398,7 @@ int main(int argc, char **argv) {
   read_cmdline(argc, argv, &config);
 
   if (config.test_config)
-    return 0;
+    exit(EXIT_SUCCESS);
 
   if (optind < argc)
     exit_usage(1);
@@ -536,109 +408,18 @@ int main(int argc, char **argv) {
   if (configure_collectd(&config) != 0)
     exit(EXIT_FAILURE);
 
-#if COLLECT_DAEMON
-  /*
-   * fork off child
-   */
-  struct sigaction sig_chld_action = {.sa_handler = SIG_IGN};
-
-  sigaction(SIGCHLD, &sig_chld_action, NULL);
-
-  /*
-   * Only daemonize if we're not being supervised
-   * by upstart or systemd (when using Linux).
-   */
-  if (config.daemonize
-#ifdef KERNEL_LINUX
-      && notify_upstart() == 0 && notify_systemd() == 0
-#endif
-      ) {
-    pid_t pid;
-    if ((pid = fork()) == -1) {
-      /* error */
-      fprintf(stderr, "fork: %s", STRERRNO);
-      return 1;
-    } else if (pid != 0) {
-      /* parent */
-      /* printf ("Running (PID %i)\n", pid); */
-      return 0;
-    }
-
-    /* Detach from session */
-    setsid();
-
-    /* Write pidfile */
-    if (pidfile_create())
-      exit(2);
-
-    /* close standard descriptors */
-    close(2);
-    close(1);
-    close(0);
-
-    int status = open("/dev/null", O_RDWR);
-    if (status != 0) {
-      ERROR("Error: Could not connect `STDIN' to `/dev/null' (status %d)",
-            status);
-      return 1;
-    }
-
-    status = dup(0);
-    if (status != 1) {
-      ERROR("Error: Could not connect `STDOUT' to `/dev/null' (status %d)",
-            status);
-      return 1;
-    }
-
-    status = dup(0);
-    if (status != 2) {
-      ERROR("Error: Could not connect `STDERR' to `/dev/null', (status %d)",
-            status);
-      return 1;
-    }
-  }    /* if (config.daemonize) */
-#endif /* COLLECT_DAEMON */
-
-  struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN};
-
-  sigaction(SIGPIPE, &sig_pipe_action, NULL);
-
-  /*
-   * install signal handlers
-   */
-  struct sigaction sig_int_action = {.sa_handler = sig_int_handler};
-
-  if (sigaction(SIGINT, &sig_int_action, NULL) != 0) {
-    ERROR("Error: Failed to install a signal handler for signal INT: %s",
-          STRERRNO);
-    return 1;
-  }
-
-  struct sigaction sig_term_action = {.sa_handler = sig_term_handler};
-
-  if (sigaction(SIGTERM, &sig_term_action, NULL) != 0) {
-    ERROR("Error: Failed to install a signal handler for signal TERM: %s",
-          STRERRNO);
-    return 1;
-  }
-
-  struct sigaction sig_usr1_action = {.sa_handler = sig_usr1_handler};
+  return config;
+}
 
-  if (sigaction(SIGUSR1, &sig_usr1_action, NULL) != 0) {
-    ERROR("Error: Failed to install a signal handler for signal USR1: %s",
-          STRERRNO);
-    return 1;
-  }
+int run_loop(bool test_readall) {
+  int exit_status = 0;
 
-  /*
-   * run the actual loops
-   */
   if (do_init() != 0) {
     ERROR("Error: one or more plugin init callbacks failed.");
     exit_status = 1;
   }
 
-  if (config.test_readall) {
+  if (test_readall) {
     if (plugin_read_all_once() != 0) {
       ERROR("Error: one or more plugin read callbacks failed.");
       exit_status = 1;
@@ -656,10 +437,5 @@ int main(int argc, char **argv) {
     exit_status = 1;
   }
 
-#if COLLECT_DAEMON
-  if (config.daemonize)
-    pidfile_remove();
-#endif /* COLLECT_DAEMON */
-
   return exit_status;
-} /* int main */
+} /* int run_loop */
index 87f05a2..07597d3 100644 (file)
 #ifndef COLLECTD_H
 #define COLLECTD_H
 
+#ifdef WIN32
+typedef int uid_t;
+#include "gnulib_config.h"
+#endif
+
 #if HAVE_CONFIG_H
 #include "config.h"
 #endif
 #include <sys/isa_defs.h>
 #endif
 
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
 #ifndef BYTE_ORDER
 #if defined(_BYTE_ORDER)
 #define BYTE_ORDER _BYTE_ORDER
 #endif
 #endif
 
-#if HAVE_SYS_PARAM_H
-#include <sys/param.h>
-#endif
-
 #ifndef PACKAGE_NAME
 #define PACKAGE_NAME "collectd"
 #endif
 #define __attribute__(x) /**/
 #endif
 
-#if defined(COLLECT_DEBUG) && COLLECT_DEBUG && defined(__GNUC__) && __GNUC__
-#undef strcpy
-#undef strcat
-#undef strtok
-#pragma GCC poison strcpy strcat strtok
-#endif
-
-/*
- * Special hack for the perl plugin: Because the later included perl.h defines
- * a macro which is never used, but contains `sprintf', we cannot poison that
- * identifies just yet. The parl plugin will do that itself once perl.h is
- * included.
- */
-#ifndef DONT_POISON_SPRINTF_YET
-#if defined(COLLECT_DEBUG) && COLLECT_DEBUG && defined(__GNUC__) && __GNUC__
-#undef sprintf
-#pragma GCC poison sprintf
-#endif
-#endif
-
 #ifndef GAUGE_FORMAT
 #define GAUGE_FORMAT "%.15g"
 #endif
index 582d6b2..99f48ca 100644 (file)
  *   MichaÅ‚ MirosÅ‚aw <mirq-linux at rere.qmqm.pl>
 **/
 
-#if HAVE_CONFIG_H
-#include "config.h"
-#endif
-
 #include "collectd.h"
 
 #include "common.h"
 extern kstat_ctl_t *kc;
 #endif
 
+#if !defined(MSG_DONTWAIT)
+#if defined(MSG_NONBLOCK)
 /* AIX doesn't have MSG_DONTWAIT */
-#ifndef MSG_DONTWAIT
 #define MSG_DONTWAIT MSG_NONBLOCK
-#endif
+#else
+/* Windows doesn't have MSG_DONTWAIT or MSG_NONBLOCK */
+#define MSG_DONTWAIT 0
+#endif /* defined(MSG_NONBLOCK) */
+#endif /* !defined(MSG_DONTWAIT) */
 
-#if !HAVE_GETPWNAM_R
+#if !HAVE_GETPWNAM_R && defined(HAVE_GETPWNAM)
 static pthread_mutex_t getpwnam_r_lock = PTHREAD_MUTEX_INITIALIZER;
 #endif
 
@@ -417,7 +418,7 @@ int strunescape(char *buf, size_t buf_len) {
       continue;
 
     if (((i + 1) >= buf_len) || (buf[i + 1] == 0)) {
-      ERROR("string unescape: backslash found at end of string.");
+      P_ERROR("string unescape: backslash found at end of string.");
       /* Ensure null-byte at the end of the buffer. */
       buf[i] = 0;
       return -1;
@@ -544,9 +545,8 @@ int timeval_cmp(struct timeval tv0, struct timeval tv1, struct timeval *delta) {
 int check_create_dir(const char *file_orig) {
   struct stat statbuf;
 
-  char file_copy[512];
-  char dir[512];
-  int dir_len = 512;
+  char file_copy[PATH_MAX];
+  char dir[PATH_MAX];
   char *fields[16];
   int fields_num;
   char *ptr;
@@ -563,8 +563,10 @@ int check_create_dir(const char *file_orig) {
 
   if ((len = strlen(file_orig)) < 1)
     return -1;
-  else if (len >= sizeof(file_copy))
+  else if (len >= sizeof(file_copy)) {
+    ERROR("check_create_dir: name (%s) is too long.", file_orig);
     return -1;
+  }
 
   /*
    * If `file_orig' ends in a slash the last component is a directory,
@@ -605,9 +607,9 @@ int check_create_dir(const char *file_orig) {
      * behavior.
      */
     if (fields[i][0] == '.') {
-      ERROR("Cowardly refusing to create a directory that "
-            "begins with a `.' (dot): `%s'",
-            file_orig);
+      P_ERROR("Cowardly refusing to create a directory that "
+              "begins with a `.' (dot): `%s'",
+              file_orig);
       return -2;
     }
 
@@ -615,9 +617,10 @@ int check_create_dir(const char *file_orig) {
      * Join the components together again
      */
     dir[0] = '/';
-    if (strjoin(dir + path_is_absolute, (size_t)(dir_len - path_is_absolute),
-                fields, (size_t)(i + 1), "/") < 0) {
-      ERROR("strjoin failed: `%s', component #%i", file_orig, i);
+    if (strjoin(dir + path_is_absolute,
+                (size_t)(sizeof(dir) - path_is_absolute), fields,
+                (size_t)(i + 1), "/") < 0) {
+      P_ERROR("strjoin failed: `%s', component #%i", file_orig, i);
       return -1;
     }
 
@@ -633,16 +636,16 @@ int check_create_dir(const char *file_orig) {
           if (EEXIST == errno)
             continue;
 
-          ERROR("check_create_dir: mkdir (%s): %s", dir, STRERRNO);
+          P_ERROR("check_create_dir: mkdir (%s): %s", dir, STRERRNO);
           return -1;
         } else {
-          ERROR("check_create_dir: stat (%s): %s", dir, STRERRNO);
+          P_ERROR("check_create_dir: stat (%s): %s", dir, STRERRNO);
           return -1;
         }
       } else if (!S_ISDIR(statbuf.st_mode)) {
-        ERROR("check_create_dir: `%s' exists but is not "
-              "a directory!",
-              dir);
+        P_ERROR("check_create_dir: `%s' exists but is not "
+                "a directory!",
+                dir);
         return -1;
       }
       break;
@@ -665,12 +668,12 @@ int get_kstat(kstat_t **ksp_ptr, char *module, int instance, char *name) {
 
   *ksp_ptr = kstat_lookup(kc, module, instance, name);
   if (*ksp_ptr == NULL) {
-    ERROR("get_kstat: Cound not find kstat %s", ident);
+    P_ERROR("get_kstat: Cound not find kstat %s", ident);
     return -1;
   }
 
   if ((*ksp_ptr)->ks_type != KSTAT_TYPE_NAMED) {
-    ERROR("get_kstat: kstat %s has wrong type", ident);
+    P_ERROR("get_kstat: kstat %s has wrong type", ident);
     *ksp_ptr = NULL;
     return -1;
   }
@@ -681,12 +684,12 @@ int get_kstat(kstat_t **ksp_ptr, char *module, int instance, char *name) {
 #endif
 
   if (kstat_read(kc, *ksp_ptr, NULL) == -1) {
-    ERROR("get_kstat: kstat %s could not be read", ident);
+    P_ERROR("get_kstat: kstat %s could not be read", ident);
     return -1;
   }
 
   if ((*ksp_ptr)->ks_type != KSTAT_TYPE_NAMED) {
-    ERROR("get_kstat: kstat %s has wrong type", ident);
+    P_ERROR("get_kstat: kstat %s has wrong type", ident);
     return -1;
   }
 
@@ -698,12 +701,12 @@ long long get_kstat_value(kstat_t *ksp, char *name) {
   long long retval = -1LL;
 
   if (ksp == NULL) {
-    ERROR("get_kstat_value (\"%s\"): ksp is NULL.", name);
+    P_ERROR("get_kstat_value (\"%s\"): ksp is NULL.", name);
     return -1LL;
   } else if (ksp->ks_type != KSTAT_TYPE_NAMED) {
-    ERROR("get_kstat_value (\"%s\"): ksp->ks_type (%#x) "
-          "is not KSTAT_TYPE_NAMED (%#x).",
-          name, (unsigned int)ksp->ks_type, (unsigned int)KSTAT_TYPE_NAMED);
+    P_ERROR("get_kstat_value (\"%s\"): ksp->ks_type (%#x) "
+            "is not KSTAT_TYPE_NAMED (%#x).",
+            name, (unsigned int)ksp->ks_type, (unsigned int)KSTAT_TYPE_NAMED);
     return -1LL;
   }
 
@@ -721,7 +724,7 @@ long long get_kstat_value(kstat_t *ksp, char *name) {
   else if (kn->data_type == KSTAT_DATA_UINT64)
     retval = (long long)kn->value.ui64; /* XXX: Might overflow! */
   else
-    WARNING("get_kstat_value: Not a numeric value: %s", name);
+    P_WARNING("get_kstat_value: Not a numeric value: %s", name);
 
   return retval;
 }
@@ -1033,19 +1036,19 @@ int parse_value(const char *value_orig, value_t *ret_value, int ds_type) {
 
   default:
     sfree(value);
-    ERROR("parse_value: Invalid data source type: %i.", ds_type);
+    P_ERROR("parse_value: Invalid data source type: %i.", ds_type);
     return -1;
   }
 
   if (value == endptr) {
-    ERROR("parse_value: Failed to parse string as %s: \"%s\".",
-          DS_TYPE_TO_STRING(ds_type), value);
+    P_ERROR("parse_value: Failed to parse string as %s: \"%s\".",
+            DS_TYPE_TO_STRING(ds_type), value);
     sfree(value);
     return -1;
   } else if ((NULL != endptr) && ('\0' != *endptr))
-    INFO("parse_value: Ignoring trailing garbage \"%s\" after %s value. "
-         "Input string was \"%s\".",
-         endptr, DS_TYPE_TO_STRING(ds_type), value_orig);
+    P_INFO("parse_value: Ignoring trailing garbage \"%s\" after %s value. "
+           "Input string was \"%s\".",
+           endptr, DS_TYPE_TO_STRING(ds_type), value_orig);
 
   sfree(value);
   return 0;
@@ -1130,6 +1133,9 @@ int parse_value_file(char const *path, value_t *ret_value, int ds_type) {
 #if !HAVE_GETPWNAM_R
 int getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen,
                struct passwd **pwbufp) {
+#ifndef HAVE_GETPWNAM
+  return -1;
+#else
   int status = 0;
   struct passwd *pw;
 
@@ -1172,6 +1178,7 @@ int getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen,
   pthread_mutex_unlock(&getpwnam_r_lock);
 
   return status;
+#endif /* HAVE_GETPWNAM */
 } /* int getpwnam_r */
 #endif /* !HAVE_GETPWNAM_R */
 
@@ -1210,7 +1217,7 @@ int walk_directory(const char *dir, dirwalk_callback_f callback,
   failure = 0;
 
   if ((dh = opendir(dir)) == NULL) {
-    ERROR("walk_directory: Cannot open '%s': %s", dir, STRERRNO);
+    P_ERROR("walk_directory: Cannot open '%s': %s", dir, STRERRNO);
     return -1;
   }
 
@@ -1250,7 +1257,7 @@ ssize_t read_file_contents(const char *filename, char *buf, size_t bufsize) {
 
   ret = (ssize_t)fread(buf, 1, bufsize, fh);
   if ((ret == 0) && (ferror(fh) != 0)) {
-    ERROR("read_file_contents: Reading file \"%s\" failed.", filename);
+    P_ERROR("read_file_contents: Reading file \"%s\" failed.", filename);
     ret = -1;
   }
 
@@ -1409,8 +1416,8 @@ int service_name_to_port_number(const char *service_name) {
 
   status = getaddrinfo(/* node = */ NULL, service_name, &ai_hints, &ai_list);
   if (status != 0) {
-    ERROR("service_name_to_port_number: getaddrinfo failed: %s",
-          gai_strerror(status));
+    P_ERROR("service_name_to_port_number: getaddrinfo failed: %s",
+            gai_strerror(status));
     return -1;
   }
 
@@ -1448,7 +1455,7 @@ void set_sock_opts(int sockfd) /* {{{ */
   status = getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &socktype,
                       &(socklen_t){sizeof(socktype)});
   if (status != 0) {
-    WARNING("set_sock_opts: failed to determine socket type");
+    P_WARNING("set_sock_opts: failed to determine socket type");
     return;
   }
 
@@ -1456,14 +1463,14 @@ void set_sock_opts(int sockfd) /* {{{ */
     status =
         setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &(int){1}, sizeof(int));
     if (status != 0)
-      WARNING("set_sock_opts: failed to set socket keepalive flag");
+      P_WARNING("set_sock_opts: failed to set socket keepalive flag");
 
 #ifdef TCP_KEEPIDLE
     int tcp_keepidle = ((CDTIME_T_TO_MS(plugin_get_interval()) - 1) / 100 + 1);
     status = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepidle,
                         sizeof(tcp_keepidle));
     if (status != 0)
-      WARNING("set_sock_opts: failed to set socket tcp keepalive time");
+      P_WARNING("set_sock_opts: failed to set socket tcp keepalive time");
 #endif
 
 #ifdef TCP_KEEPINTVL
@@ -1472,7 +1479,7 @@ void set_sock_opts(int sockfd) /* {{{ */
     status = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepintvl,
                         sizeof(tcp_keepintvl));
     if (status != 0)
-      WARNING("set_sock_opts: failed to set socket tcp keepalive interval");
+      P_WARNING("set_sock_opts: failed to set socket tcp keepalive interval");
 #endif
   }
 } /* }}} void set_sock_opts */
@@ -1556,12 +1563,12 @@ int check_capability(int arg) /* {{{ */
     return -1;
 
   if (!(cap = cap_get_proc())) {
-    ERROR("check_capability: cap_get_proc failed.");
+    P_ERROR("check_capability: cap_get_proc failed.");
     return -1;
   }
 
   if (cap_get_flag(cap, cap_value, CAP_EFFECTIVE, &cap_flag_value) < 0) {
-    ERROR("check_capability: cap_get_flag failed.");
+    P_ERROR("check_capability: cap_get_flag failed.");
     cap_free(cap);
     return -1;
   }
@@ -1572,8 +1579,8 @@ int check_capability(int arg) /* {{{ */
 #else
 int check_capability(__attribute__((unused)) int arg) /* {{{ */
 {
-  WARNING("check_capability: unsupported capability implementation. "
-          "Some plugin(s) may require elevated privileges to work properly.");
+  P_WARNING("check_capability: unsupported capability implementation. "
+            "Some plugin(s) may require elevated privileges to work properly.");
   return 0;
 } /* }}} int check_capability */
 #endif /* HAVE_CAPABILITY */
index db1b465..addf06d 100644 (file)
@@ -335,6 +335,7 @@ int parse_values(char *buffer, value_list_t *vl, const data_set_t *ds);
 int parse_value_file(char const *path, value_t *ret_value, int ds_type);
 
 #if !HAVE_GETPWNAM_R
+struct passwd;
 int getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen,
                struct passwd **pwbufp);
 #endif
@@ -357,7 +358,7 @@ ssize_t read_file_contents(char const *filename, char *buf, size_t bufsize);
 counter_t counter_diff(counter_t old_value, counter_t new_value);
 
 /* Convert a rate back to a value_t. When converting to a derive_t, counter_t
- * or absoltue_t, take fractional residuals into account. This is important
+ * or absolute_t, take fractional residuals into account. This is important
  * when scaling counters, for example.
  * Returns zero on success. Returns EAGAIN when called for the first time; in
  * this case the value_t is invalid and the next call should succeed. Other
index 2830a86..8750bef 100644 (file)
@@ -190,8 +190,12 @@ static int cf_dispatch(const char *type, const char *orig_key,
 } /* int cf_dispatch */
 
 static int dispatch_global_option(const oconfig_item_t *ci) {
-  if (ci->values_num != 1)
+  if (ci->values_num != 1) {
+    ERROR("configfile: Global option `%s' needs exactly one argument.",
+          ci->key);
     return -1;
+  }
+
   if (ci->values[0].type == OCONFIG_TYPE_STRING)
     return global_option_set(ci->key, ci->values[0].value.string, 0);
   else if (ci->values[0].type == OCONFIG_TYPE_NUMBER) {
@@ -205,6 +209,8 @@ static int dispatch_global_option(const oconfig_item_t *ci) {
       return global_option_set(ci->key, "false", 0);
   }
 
+  ERROR("configfile: Global option `%s' argument has unknown type.", ci->key);
+
   return -1;
 } /* int dispatch_global_option */
 
@@ -234,37 +240,37 @@ static int dispatch_value_typesdb(oconfig_item_t *ci) {
 static int dispatch_value_plugindir(oconfig_item_t *ci) {
   assert(strcasecmp(ci->key, "PluginDir") == 0);
 
-  if (ci->values_num != 1)
-    return -1;
-  if (ci->values[0].type != OCONFIG_TYPE_STRING)
+  if (ci->values_num != 1 || ci->values[0].type != OCONFIG_TYPE_STRING) {
+    ERROR("configfile: The `PluginDir' option needs exactly one string "
+          "argument.");
     return -1;
+  }
 
   plugin_set_dir(ci->values[0].value.string);
   return 0;
 }
 
 static int dispatch_loadplugin(oconfig_item_t *ci) {
-  const char *name;
   bool global = false;
-  plugin_ctx_t ctx = {0};
-  plugin_ctx_t old_ctx;
-  int ret_val;
 
   assert(strcasecmp(ci->key, "LoadPlugin") == 0);
 
-  if (ci->values_num != 1)
-    return -1;
-  if (ci->values[0].type != OCONFIG_TYPE_STRING)
+  if (ci->values_num != 1 || ci->values[0].type != OCONFIG_TYPE_STRING) {
+    ERROR("configfile: The `LoadPlugin' block needs exactly one string "
+          "argument.");
     return -1;
+  }
 
-  name = ci->values[0].value.string;
+  const char *name = ci->values[0].value.string;
   if (strcmp("libvirt", name) == 0)
     name = "virt";
 
   /* default to the global interval set before loading this plugin */
-  ctx.interval = cf_get_default_interval();
-  ctx.flush_interval = 0;
-  ctx.flush_timeout = 0;
+  plugin_ctx_t ctx = {
+      .interval = cf_get_default_interval(), .name = strdup(name),
+  };
+  if (ctx.name == NULL)
+    return ENOMEM;
 
   for (int i = 0; i < ci->children_num; ++i) {
     oconfig_item_t *child = ci->children + i;
@@ -280,12 +286,12 @@ static int dispatch_loadplugin(oconfig_item_t *ci) {
     else {
       WARNING("Ignoring unknown LoadPlugin option \"%s\" "
               "for plugin \"%s\"",
-              child->key, ci->values[0].value.string);
+              child->key, name);
     }
   }
 
-  old_ctx = plugin_set_ctx(ctx);
-  ret_val = plugin_load(name, global);
+  plugin_ctx_t old_ctx = plugin_set_ctx(ctx);
+  int ret_val = plugin_load(name, global);
   /* reset to the "global" context */
   plugin_set_ctx(old_ctx);
 
@@ -333,6 +339,9 @@ static int dispatch_value(oconfig_item_t *ci) {
       break;
     }
 
+  if (ret != 0)
+    return ret;
+
   for (int i = 0; i < cf_global_options_num; i++)
     if (strcasecmp(cf_global_options[i].key, ci->key) == 0) {
       ret = dispatch_global_option(ci);
@@ -343,16 +352,18 @@ static int dispatch_value(oconfig_item_t *ci) {
 } /* int dispatch_value */
 
 static int dispatch_block_plugin(oconfig_item_t *ci) {
-  const char *name;
+  assert(strcasecmp(ci->key, "Plugin") == 0);
 
-  if (strcasecmp(ci->key, "Plugin") != 0)
-    return -1;
-  if (ci->values_num < 1)
+  if (ci->values_num < 1) {
+    ERROR("configfile: The `Plugin' block requires arguments.");
     return -1;
-  if (ci->values[0].type != OCONFIG_TYPE_STRING)
+  }
+  if (ci->values[0].type != OCONFIG_TYPE_STRING) {
+    ERROR("configfile: First argument of `Plugin' block should be a string.");
     return -1;
+  }
 
-  name = ci->values[0].value.string;
+  const char *name = ci->values[0].value.string;
   if (strcmp("libvirt", name) == 0) {
     /* TODO(octo): Remove this legacy. */
     WARNING("The \"libvirt\" plugin has been renamed to \"virt\" to avoid "
@@ -370,6 +381,7 @@ static int dispatch_block_plugin(oconfig_item_t *ci) {
 
     /* default to the global interval set before loading this plugin */
     ctx.interval = cf_get_default_interval();
+    ctx.name = strdup(name);
 
     old_ctx = plugin_set_ctx(ctx);
     status = plugin_load(name, /* flags = */ false);
@@ -1035,16 +1047,12 @@ int cf_read(const char *filename) {
  * success. */
 int cf_util_get_string(const oconfig_item_t *ci, char **ret_string) /* {{{ */
 {
-  char *string;
-
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    ERROR("cf_util_get_string: The %s option requires "
-          "exactly one string argument.",
-          ci->key);
+    P_ERROR("The `%s' option requires exactly one string argument.", ci->key);
     return -1;
   }
 
-  string = strdup(ci->values[0].value.string);
+  char *string = strdup(ci->values[0].value.string);
   if (string == NULL)
     return -1;
 
@@ -1063,9 +1071,7 @@ int cf_util_get_string_buffer(const oconfig_item_t *ci, char *buffer, /* {{{ */
     return EINVAL;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    ERROR("cf_util_get_string_buffer: The %s option requires "
-          "exactly one string argument.",
-          ci->key);
+    P_ERROR("The `%s' option requires exactly one string argument.", ci->key);
     return -1;
   }
 
@@ -1082,9 +1088,7 @@ int cf_util_get_int(const oconfig_item_t *ci, int *ret_value) /* {{{ */
     return EINVAL;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
-    ERROR("cf_util_get_int: The %s option requires "
-          "exactly one numeric argument.",
-          ci->key);
+    P_ERROR("The `%s' option requires exactly one numeric argument.", ci->key);
     return -1;
   }
 
@@ -1099,9 +1103,7 @@ int cf_util_get_double(const oconfig_item_t *ci, double *ret_value) /* {{{ */
     return EINVAL;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
-    ERROR("cf_util_get_double: The %s option requires "
-          "exactly one numeric argument.",
-          ci->key);
+    P_ERROR("The `%s' option requires exactly one numeric argument.", ci->key);
     return -1;
   }
 
@@ -1117,9 +1119,7 @@ int cf_util_get_boolean(const oconfig_item_t *ci, bool *ret_bool) /* {{{ */
 
   if ((ci->values_num != 1) || ((ci->values[0].type != OCONFIG_TYPE_BOOLEAN) &&
                                 (ci->values[0].type != OCONFIG_TYPE_STRING))) {
-    ERROR("cf_util_get_boolean: The %s option requires "
-          "exactly one boolean argument.",
-          ci->key);
+    P_ERROR("The `%s' option requires exactly one boolean argument.", ci->key);
     return -1;
   }
 
@@ -1128,19 +1128,19 @@ int cf_util_get_boolean(const oconfig_item_t *ci, bool *ret_bool) /* {{{ */
     *ret_bool = ci->values[0].value.boolean ? true : false;
     break;
   case OCONFIG_TYPE_STRING:
-    WARNING("cf_util_get_boolean: Using string value `%s' for boolean option "
-            "`%s' is deprecated and will be removed in future releases. "
-            "Use unquoted true or false instead.",
-            ci->values[0].value.string, ci->key);
+    P_WARNING("Using string value `%s' for boolean option `%s' is deprecated "
+              "and will be removed in future releases. Use unquoted true or "
+              "false instead.",
+              ci->values[0].value.string, ci->key);
 
     if (IS_TRUE(ci->values[0].value.string))
       *ret_bool = true;
     else if (IS_FALSE(ci->values[0].value.string))
       *ret_bool = false;
     else {
-      ERROR("cf_util_get_boolean: Cannot parse string value `%s' of the `%s' "
-            "option as a boolean value.",
-            ci->values[0].value.string, ci->key);
+      P_ERROR("Cannot parse string value `%s' of the `%s' option as a boolean "
+              "value.",
+              ci->values[0].value.string, ci->key);
       return -1;
     }
     break;
@@ -1181,9 +1181,7 @@ int cf_util_get_port_number(const oconfig_item_t *ci) /* {{{ */
 
   if ((ci->values_num != 1) || ((ci->values[0].type != OCONFIG_TYPE_STRING) &&
                                 (ci->values[0].type != OCONFIG_TYPE_NUMBER))) {
-    ERROR("cf_util_get_port_number: The \"%s\" option requires "
-          "exactly one string argument.",
-          ci->key);
+    P_ERROR("The `%s' option requires exactly one string argument.", ci->key);
     return -1;
   }
 
@@ -1193,11 +1191,9 @@ int cf_util_get_port_number(const oconfig_item_t *ci) /* {{{ */
   assert(ci->values[0].type == OCONFIG_TYPE_NUMBER);
   tmp = (int)(ci->values[0].value.number + 0.5);
   if ((tmp < 1) || (tmp > 65535)) {
-    ERROR("cf_util_get_port_number: The \"%s\" option requires "
-          "a service name or a port number. The number "
-          "you specified, %i, is not in the valid "
-          "range of 1-65535.",
-          ci->key, tmp);
+    P_ERROR("The `%s' option requires a service name or a port number. The "
+            "number you specified, %i, is not in the valid range of 1-65535.",
+            ci->key, tmp);
     return -1;
   }
 
@@ -1211,18 +1207,15 @@ int cf_util_get_service(const oconfig_item_t *ci, char **ret_string) /* {{{ */
   int status;
 
   if (ci->values_num != 1) {
-    ERROR("cf_util_get_service: The %s option requires exactly "
-          "one argument.",
-          ci->key);
+    P_ERROR("The `%s` option requires exactly one argument.", ci->key);
     return -1;
   }
 
   if (ci->values[0].type == OCONFIG_TYPE_STRING)
     return cf_util_get_string(ci, ret_string);
   if (ci->values[0].type != OCONFIG_TYPE_NUMBER) {
-    ERROR("cf_util_get_service: The %s option requires "
-          "exactly one string or numeric argument.",
-          ci->key);
+    P_ERROR("The `%s` option requires exactly one string or numeric argument.",
+            ci->key);
   }
 
   port = 0;
@@ -1230,16 +1223,14 @@ int cf_util_get_service(const oconfig_item_t *ci, char **ret_string) /* {{{ */
   if (status != 0)
     return status;
   else if ((port < 1) || (port > 65535)) {
-    ERROR("cf_util_get_service: The port number given "
-          "for the %s option is out of "
-          "range (%i).",
-          ci->key, port);
+    P_ERROR("The port number given for the `%s` option is out of range (%i).",
+            ci->key, port);
     return -1;
   }
 
   service = malloc(6);
   if (service == NULL) {
-    ERROR("cf_util_get_service: Out of memory.");
+    P_ERROR("cf_util_get_service: Out of memory.");
     return -1;
   }
   snprintf(service, 6, "%i", port);
@@ -1256,16 +1247,13 @@ int cf_util_get_cdtime(const oconfig_item_t *ci, cdtime_t *ret_value) /* {{{ */
     return EINVAL;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
-    ERROR("cf_util_get_cdtime: The %s option requires "
-          "exactly one numeric argument.",
-          ci->key);
+    P_ERROR("The `%s' option requires exactly one numeric argument.", ci->key);
     return -1;
   }
 
   if (ci->values[0].value.number < 0.0) {
-    ERROR("cf_util_get_cdtime: The numeric argument of the %s "
-          "option must not be negative.",
-          ci->key);
+    P_ERROR("The numeric argument of the `%s' option must not be negative.",
+            ci->key);
     return -1;
   }
 
index 5a277c0..9ec72f0 100644 (file)
 #endif
 
 #ifndef PRIsz
+#ifdef WIN32
+#define PRIsz "Iu"
+#else
 #define PRIsz "zu"
-#endif /* PRIsz */
+#endif /* WIN32 */
+#endif /* !PRIsz */
 
 /* Type for time as used by "utils_time.h" */
 typedef uint64_t cdtime_t;
index 427a813..c2017ac 100644 (file)
 #include "utils_random.h"
 #include "utils_time.h"
 
+#ifdef WIN32
+#define EXPORT __declspec(dllexport)
+#include <sys/stat.h>
+#include <unistd.h>
+#else
+#define EXPORT
+#endif
+
 #if HAVE_PTHREAD_NP_H
 #include <pthread_np.h> /* for pthread_set_name_np(3) */
 #endif
@@ -138,6 +146,7 @@ static bool plugin_ctx_key_initialized;
 static long write_limit_high;
 static long write_limit_low;
 
+static pthread_mutex_t statistics_lock = PTHREAD_MUTEX_INITIALIZER;
 static derive_t stats_values_dropped;
 static bool record_statistics;
 
@@ -292,10 +301,10 @@ static int register_callback(llist_t **list, /* {{{ */
     old_cf = le->value;
     le->value = cf;
 
-    WARNING("plugin: register_callback: "
-            "a callback named `%s' already exists - "
-            "overwriting the old entry!",
-            name);
+    P_WARNING("register_callback: "
+              "a callback named `%s' already exists - "
+              "overwriting the old entry!",
+              name);
 
     destroy_callback(old_cf);
     sfree(key);
@@ -315,7 +324,7 @@ static void log_list_callbacks(llist_t **list, /* {{{ */
 
   n = llist_size(*list);
   if (n == 0) {
-    INFO("%s [none]", comment);
+    INFO("%s: [none]", comment);
     return;
   }
 
@@ -346,19 +355,22 @@ static void log_list_callbacks(llist_t **list, /* {{{ */
 static int create_register_callback(llist_t **list, /* {{{ */
                                     const char *name, void *callback,
                                     user_data_t const *ud) {
-  callback_func_t *cf;
 
-  cf = calloc(1, sizeof(*cf));
+  if (name == NULL || callback == NULL)
+    return EINVAL;
+
+  callback_func_t *cf = calloc(1, sizeof(*cf));
   if (cf == NULL) {
     free_userdata(ud);
     ERROR("plugin: create_register_callback: calloc failed.");
-    return -1;
+    return ENOMEM;
   }
 
   cf->cf_callback = callback;
   if (ud == NULL) {
-    cf->cf_udata.data = NULL;
-    cf->cf_udata.free_func = NULL;
+    cf->cf_udata = (user_data_t){
+        .data = NULL, .free_func = NULL,
+    };
   } else {
     cf->cf_udata = *ud;
   }
@@ -637,7 +649,7 @@ static void start_read_threads(size_t num) /* {{{ */
     }
 
     char name[THREAD_NAME_MAX];
-    snprintf(name, sizeof(name), "reader#%" PRIsz, read_threads_num);
+    snprintf(name, sizeof(name), "reader#%" PRIu64, (uint64_t)read_threads_num);
     set_thread_name(read_threads[read_threads_num], name);
 
     read_threads_num++;
@@ -710,25 +722,8 @@ plugin_value_list_clone(value_list_t const *vl_orig) /* {{{ */
     vl->time = cdtime();
 
   /* Fill in the interval from the thread context, if it is zero. */
-  if (vl->interval == 0) {
-    plugin_ctx_t ctx = plugin_get_ctx();
-
-    if (ctx.interval != 0)
-      vl->interval = ctx.interval;
-    else {
-      char name[6 * DATA_MAX_NAME_LEN];
-      FORMAT_VL(name, sizeof(name), vl);
-      ERROR("plugin_value_list_clone: Unable to determine "
-            "interval from context for "
-            "value list \"%s\". "
-            "This indicates a broken plugin. "
-            "Please report this problem to the "
-            "collectd mailing list or at "
-            "<http://collectd.org/bugs/>.",
-            name);
-      vl->interval = cf_get_default_interval();
-    }
-  }
+  if (vl->interval == 0)
+    vl->interval = plugin_get_interval();
 
   return vl;
 } /* }}} value_list_t *plugin_value_list_clone */
@@ -843,7 +838,8 @@ static void start_write_threads(size_t num) /* {{{ */
     }
 
     char name[THREAD_NAME_MAX];
-    snprintf(name, sizeof(name), "writer#%" PRIsz, write_threads_num);
+    snprintf(name, sizeof(name), "writer#%" PRIu64,
+             (uint64_t)write_threads_num);
     set_thread_name(write_threads[write_threads_num], name);
 
     write_threads_num++;
@@ -954,6 +950,11 @@ static void plugin_free_loaded(void) {
 }
 
 #define BUFSIZE 512
+#ifdef WIN32
+#define SHLIB_SUFFIX ".dll"
+#else
+#define SHLIB_SUFFIX ".so"
+#endif
 int plugin_load(char const *plugin_name, bool global) {
   DIR *dh;
   const char *dir;
@@ -990,11 +991,12 @@ int plugin_load(char const *plugin_name, bool global) {
       (strcasecmp("python", plugin_name) == 0))
     global = true;
 
-  /* `cpu' should not match `cpufreq'. To solve this we add `.so' to the
+  /* `cpu' should not match `cpufreq'. To solve this we add SHLIB_SUFFIX to the
    * type when matching the filename */
-  status = snprintf(typename, sizeof(typename), "%s.so", plugin_name);
+  status = snprintf(typename, sizeof(typename), "%s" SHLIB_SUFFIX, plugin_name);
   if ((status < 0) || ((size_t)status >= sizeof(typename))) {
-    WARNING("plugin_load: Filename too long: \"%s.so\"", plugin_name);
+    WARNING("plugin_load: Filename too long: \"%s" SHLIB_SUFFIX "\"",
+            plugin_name);
     return -1;
   }
 
@@ -1047,19 +1049,20 @@ int plugin_load(char const *plugin_name, bool global) {
 /*
  * The `register_*' functions follow
  */
-int plugin_register_config(const char *name,
-                           int (*callback)(const char *key, const char *val),
-                           const char **keys, int keys_num) {
+EXPORT int plugin_register_config(const char *name,
+                                  int (*callback)(const char *key,
+                                                  const char *val),
+                                  const char **keys, int keys_num) {
   cf_register(name, callback, keys, keys_num);
   return 0;
 } /* int plugin_register_config */
 
-int plugin_register_complex_config(const char *type,
-                                   int (*callback)(oconfig_item_t *)) {
+EXPORT int plugin_register_complex_config(const char *type,
+                                          int (*callback)(oconfig_item_t *)) {
   return cf_register_complex(type, callback);
 } /* int plugin_register_complex_config */
 
-int plugin_register_init(const char *name, int (*callback)(void)) {
+EXPORT int plugin_register_init(const char *name, int (*callback)(void)) {
   return create_register_callback(&list_init, name, (void *)callback, NULL);
 } /* plugin_register_init */
 
@@ -1111,9 +1114,9 @@ static int plugin_insert_read(read_func_t *rf) {
   le = llist_search(read_list, rf->rf_name);
   if (le != NULL) {
     pthread_mutex_unlock(&read_lock);
-    WARNING("The read function \"%s\" is already registered. "
-            "Check for duplicates in your configuration!",
-            rf->rf_name);
+    P_WARNING("The read function \"%s\" is already registered. "
+              "Check for duplicates in your configuration!",
+              rf->rf_name);
     return EINVAL;
   }
 
@@ -1141,7 +1144,7 @@ static int plugin_insert_read(read_func_t *rf) {
   return 0;
 } /* int plugin_insert_read */
 
-int plugin_register_read(const char *name, int (*callback)(void)) {
+EXPORT int plugin_register_read(const char *name, int (*callback)(void)) {
   read_func_t *rf;
   int status;
 
@@ -1159,6 +1162,7 @@ int plugin_register_read(const char *name, int (*callback)(void)) {
   rf->rf_name = strdup(name);
   rf->rf_type = RF_SIMPLE;
   rf->rf_interval = plugin_get_interval();
+  rf->rf_ctx.interval = rf->rf_interval;
 
   status = plugin_insert_read(rf);
   if (status != 0) {
@@ -1169,9 +1173,10 @@ int plugin_register_read(const char *name, int (*callback)(void)) {
   return status;
 } /* int plugin_register_read */
 
-int plugin_register_complex_read(const char *group, const char *name,
-                                 plugin_read_cb callback, cdtime_t interval,
-                                 user_data_t const *user_data) {
+EXPORT int plugin_register_complex_read(const char *group, const char *name,
+                                        plugin_read_cb callback,
+                                        cdtime_t interval,
+                                        user_data_t const *user_data) {
   read_func_t *rf;
   int status;
 
@@ -1200,6 +1205,7 @@ int plugin_register_complex_read(const char *group, const char *name,
   }
 
   rf->rf_ctx = plugin_get_ctx();
+  rf->rf_ctx.interval = rf->rf_interval;
 
   status = plugin_insert_read(rf);
   if (status != 0) {
@@ -1211,8 +1217,8 @@ int plugin_register_complex_read(const char *group, const char *name,
   return status;
 } /* int plugin_register_complex_read */
 
-int plugin_register_write(const char *name, plugin_write_cb callback,
-                          user_data_t const *ud) {
+EXPORT int plugin_register_write(const char *name, plugin_write_cb callback,
+                                 user_data_t const *ud) {
   return create_register_callback(&list_write, name, (void *)callback, ud);
 } /* int plugin_register_write */
 
@@ -1253,8 +1259,8 @@ static char *plugin_flush_callback_name(const char *name) {
   return flush_name;
 } /* static char *plugin_flush_callback_name */
 
-int plugin_register_flush(const char *name, plugin_flush_cb callback,
-                          user_data_t const *ud) {
+EXPORT int plugin_register_flush(const char *name, plugin_flush_cb callback,
+                                 user_data_t const *ud) {
   int status;
   plugin_ctx_t ctx = plugin_get_ctx();
 
@@ -1302,12 +1308,12 @@ int plugin_register_flush(const char *name, plugin_flush_cb callback,
   return 0;
 } /* int plugin_register_flush */
 
-int plugin_register_missing(const char *name, plugin_missing_cb callback,
-                            user_data_t const *ud) {
+EXPORT int plugin_register_missing(const char *name, plugin_missing_cb callback,
+                                   user_data_t const *ud) {
   return create_register_callback(&list_missing, name, (void *)callback, ud);
 } /* int plugin_register_missing */
 
-int plugin_register_shutdown(const char *name, int (*callback)(void)) {
+EXPORT int plugin_register_shutdown(const char *name, int (*callback)(void)) {
   return create_register_callback(&list_shutdown, name, (void *)callback, NULL);
 } /* int plugin_register_shutdown */
 
@@ -1330,7 +1336,7 @@ static void plugin_free_data_sets(void) {
   data_sets = NULL;
 } /* void plugin_free_data_sets */
 
-int plugin_register_data_set(const data_set_t *ds) {
+EXPORT int plugin_register_data_set(const data_set_t *ds) {
   data_set_t *ds_copy;
 
   if ((data_sets != NULL) && (c_avl_get(data_sets, ds->type, NULL) == 0)) {
@@ -1359,33 +1365,33 @@ int plugin_register_data_set(const data_set_t *ds) {
   return c_avl_insert(data_sets, (void *)ds_copy->type, (void *)ds_copy);
 } /* int plugin_register_data_set */
 
-int plugin_register_log(const char *name, plugin_log_cb callback,
-                        user_data_t const *ud) {
+EXPORT int plugin_register_log(const char *name, plugin_log_cb callback,
+                               user_data_t const *ud) {
   return create_register_callback(&list_log, name, (void *)callback, ud);
 } /* int plugin_register_log */
 
-int plugin_register_notification(const char *name,
-                                 plugin_notification_cb callback,
-                                 user_data_t const *ud) {
+EXPORT int plugin_register_notification(const char *name,
+                                        plugin_notification_cb callback,
+                                        user_data_t const *ud) {
   return create_register_callback(&list_notification, name, (void *)callback,
                                   ud);
 } /* int plugin_register_log */
 
-int plugin_unregister_config(const char *name) {
+EXPORT int plugin_unregister_config(const char *name) {
   cf_unregister(name);
   return 0;
 } /* int plugin_unregister_config */
 
-int plugin_unregister_complex_config(const char *name) {
+EXPORT int plugin_unregister_complex_config(const char *name) {
   cf_unregister_complex(name);
   return 0;
 } /* int plugin_unregister_complex_config */
 
-int plugin_unregister_init(const char *name) {
+EXPORT int plugin_unregister_init(const char *name) {
   return plugin_unregister(list_init, name);
 }
 
-int plugin_unregister_read(const char *name) /* {{{ */
+EXPORT int plugin_unregister_read(const char *name) /* {{{ */
 {
   llentry_t *le;
   read_func_t *rf;
@@ -1422,7 +1428,7 @@ int plugin_unregister_read(const char *name) /* {{{ */
   return 0;
 } /* }}} int plugin_unregister_read */
 
-void plugin_log_available_writers(void) {
+EXPORT void plugin_log_available_writers(void) {
   log_list_callbacks(&list_write, "Available write targets:");
 }
 
@@ -1434,7 +1440,7 @@ static int compare_read_func_group(llentry_t *e, void *ud) /* {{{ */
   return strcmp(rf->rf_group, (const char *)group);
 } /* }}} int compare_read_func_group */
 
-int plugin_unregister_read_group(const char *group) /* {{{ */
+EXPORT int plugin_unregister_read_group(const char *group) /* {{{ */
 {
   llentry_t *le;
   read_func_t *rf;
@@ -1484,11 +1490,11 @@ int plugin_unregister_read_group(const char *group) /* {{{ */
   return 0;
 } /* }}} int plugin_unregister_read_group */
 
-int plugin_unregister_write(const char *name) {
+EXPORT int plugin_unregister_write(const char *name) {
   return plugin_unregister(list_write, name);
 }
 
-int plugin_unregister_flush(const char *name) {
+EXPORT int plugin_unregister_flush(const char *name) {
   plugin_ctx_t ctx = plugin_get_ctx();
 
   if (ctx.flush_interval != 0) {
@@ -1504,15 +1510,15 @@ int plugin_unregister_flush(const char *name) {
   return plugin_unregister(list_flush, name);
 }
 
-int plugin_unregister_missing(const char *name) {
+EXPORT int plugin_unregister_missing(const char *name) {
   return plugin_unregister(list_missing, name);
 }
 
-int plugin_unregister_shutdown(const char *name) {
+EXPORT int plugin_unregister_shutdown(const char *name) {
   return plugin_unregister(list_shutdown, name);
 }
 
-int plugin_unregister_data_set(const char *name) {
+EXPORT int plugin_unregister_data_set(const char *name) {
   data_set_t *ds;
 
   if (data_sets == NULL)
@@ -1527,15 +1533,15 @@ int plugin_unregister_data_set(const char *name) {
   return 0;
 } /* int plugin_unregister_data_set */
 
-int plugin_unregister_log(const char *name) {
+EXPORT int plugin_unregister_log(const char *name) {
   return plugin_unregister(list_log, name);
 }
 
-int plugin_unregister_notification(const char *name) {
+EXPORT int plugin_unregister_notification(const char *name) {
   return plugin_unregister(list_notification, name);
 }
 
-int plugin_init_all(void) {
+EXPORT int plugin_init_all(void) {
   char const *chain_name;
   llentry_t *le;
   int status;
@@ -1634,14 +1640,14 @@ int plugin_init_all(void) {
 } /* void plugin_init_all */
 
 /* TODO: Rename this function. */
-void plugin_read_all(void) {
+EXPORT void plugin_read_all(void) {
   uc_check_timeout();
 
   return;
 } /* void plugin_read_all */
 
 /* Read function called when the `-T' command line argument is given. */
-int plugin_read_all_once(void) {
+EXPORT int plugin_read_all_once(void) {
   int status;
   int return_status = 0;
 
@@ -1686,8 +1692,8 @@ int plugin_read_all_once(void) {
   return return_status;
 } /* int plugin_read_all_once */
 
-int plugin_write(const char *plugin, /* {{{ */
-                 const data_set_t *ds, const value_list_t *vl) {
+EXPORT int plugin_write(const char *plugin, /* {{{ */
+                        const data_set_t *ds, const value_list_t *vl) {
   llentry_t *le;
   int status;
 
@@ -1714,8 +1720,12 @@ int plugin_write(const char *plugin, /* {{{ */
       callback_func_t *cf = le->value;
       plugin_write_cb callback;
 
-      /* do not switch plugin context; rather keep the context (interval)
-       * information of the calling read plugin */
+      /* Keep the read plugin's interval and flush information but update the
+       * plugin name. */
+      plugin_ctx_t old_ctx = plugin_get_ctx();
+      plugin_ctx_t ctx = old_ctx;
+      ctx.name = cf->cf_ctx.name;
+      plugin_set_ctx(ctx);
 
       DEBUG("plugin: plugin_write: Writing values via %s.", le->key);
       callback = cf->cf_callback;
@@ -1725,6 +1735,7 @@ int plugin_write(const char *plugin, /* {{{ */
       else
         success++;
 
+      plugin_set_ctx(old_ctx);
       le = le->next;
     }
 
@@ -1761,7 +1772,8 @@ int plugin_write(const char *plugin, /* {{{ */
   return status;
 } /* }}} int plugin_write */
 
-int plugin_flush(const char *plugin, cdtime_t timeout, const char *identifier) {
+EXPORT int plugin_flush(const char *plugin, cdtime_t timeout,
+                        const char *identifier) {
   llentry_t *le;
 
   if (list_flush == NULL)
@@ -1791,7 +1803,7 @@ int plugin_flush(const char *plugin, cdtime_t timeout, const char *identifier) {
   return 0;
 } /* int plugin_flush */
 
-int plugin_shutdown_all(void) {
+EXPORT int plugin_shutdown_all(void) {
   llentry_t *le;
   int ret = 0; // Assume success.
 
@@ -1857,7 +1869,7 @@ int plugin_shutdown_all(void) {
   return ret;
 } /* void plugin_shutdown_all */
 
-int plugin_dispatch_missing(const value_list_t *vl) /* {{{ */
+EXPORT int plugin_dispatch_missing(const value_list_t *vl) /* {{{ */
 {
   if (list_missing == NULL)
     return 0;
@@ -2063,9 +2075,8 @@ static bool check_drop_value(void) /* {{{ */
     return false;
 } /* }}} bool check_drop_value */
 
-int plugin_dispatch_values(value_list_t const *vl) {
+EXPORT int plugin_dispatch_values(value_list_t const *vl) {
   int status;
-  static pthread_mutex_t statistics_lock = PTHREAD_MUTEX_INITIALIZER;
 
   if (check_drop_value()) {
     if (record_statistics) {
@@ -2095,6 +2106,15 @@ plugin_dispatch_multivalue(value_list_t const *template, /* {{{ */
   gauge_t sum = 0.0;
   va_list ap;
 
+  if (check_drop_value()) {
+    if (record_statistics) {
+      pthread_mutex_lock(&statistics_lock);
+      stats_values_dropped++;
+      pthread_mutex_unlock(&statistics_lock);
+    }
+    return 0;
+  }
+
   assert(template->values_len == 1);
 
   /* Calculate sum for Gauge to calculate percent if needed */
@@ -2162,7 +2182,7 @@ plugin_dispatch_multivalue(value_list_t const *template, /* {{{ */
   return failed;
 } /* }}} int plugin_dispatch_multivalue */
 
-int plugin_dispatch_notification(const notification_t *notif) {
+EXPORT int plugin_dispatch_notification(const notification_t *notif) {
   llentry_t *le;
   /* Possible TODO: Add flap detection here */
 
@@ -2199,7 +2219,7 @@ int plugin_dispatch_notification(const notification_t *notif) {
   return 0;
 } /* int plugin_dispatch_notification */
 
-void plugin_log(int level, const char *format, ...) {
+EXPORT void plugin_log(int level, const char *format, ...) {
   char msg[1024];
   va_list ap;
   llentry_t *le;
@@ -2236,6 +2256,21 @@ void plugin_log(int level, const char *format, ...) {
   }
 } /* void plugin_log */
 
+void daemon_log(int level, const char *format, ...) {
+  char msg[1024] = ""; // Size inherits from plugin_log()
+
+  char const *name = plugin_get_ctx().name;
+  if (name == NULL)
+    name = "UNKNOWN";
+
+  va_list ap;
+  va_start(ap, format);
+  vsnprintf(msg, sizeof(msg), format, ap);
+  va_end(ap);
+
+  plugin_log(level, "%s plugin: %s", name, msg);
+} /* void daemon_log */
+
 int parse_log_severity(const char *severity) {
   int log_level = -1;
 
@@ -2257,7 +2292,7 @@ int parse_log_severity(const char *severity) {
   return log_level;
 } /* int parse_log_severity */
 
-int parse_notif_severity(const char *severity) {
+EXPORT int parse_notif_severity(const char *severity) {
   int notif_severity = -1;
 
   if (strcasecmp(severity, "FAILURE") == 0)
@@ -2271,11 +2306,11 @@ int parse_notif_severity(const char *severity) {
   return notif_severity;
 } /* int parse_notif_severity */
 
-const data_set_t *plugin_get_ds(const char *name) {
+EXPORT const data_set_t *plugin_get_ds(const char *name) {
   data_set_t *ds;
 
   if (data_sets == NULL) {
-    ERROR("plugin_get_ds: No data sets are defined yet.");
+    P_ERROR("plugin_get_ds: No data sets are defined yet.");
     return NULL;
   }
 
@@ -2458,12 +2493,12 @@ static plugin_ctx_t *plugin_ctx_create(void) {
   return ctx;
 } /* int plugin_ctx_create */
 
-void plugin_init_ctx(void) {
+EXPORT void plugin_init_ctx(void) {
   pthread_key_create(&plugin_ctx_key, plugin_ctx_destructor);
   plugin_ctx_key_initialized = true;
 } /* void plugin_init_ctx */
 
-plugin_ctx_t plugin_get_ctx(void) {
+EXPORT plugin_ctx_t plugin_get_ctx(void) {
   plugin_ctx_t *ctx;
 
   assert(plugin_ctx_key_initialized);
@@ -2479,7 +2514,7 @@ plugin_ctx_t plugin_get_ctx(void) {
   return *ctx;
 } /* plugin_ctx_t plugin_get_ctx */
 
-plugin_ctx_t plugin_set_ctx(plugin_ctx_t ctx) {
+EXPORT plugin_ctx_t plugin_set_ctx(plugin_ctx_t ctx) {
   plugin_ctx_t *c;
   plugin_ctx_t old;
 
@@ -2499,13 +2534,15 @@ plugin_ctx_t plugin_set_ctx(plugin_ctx_t ctx) {
   return old;
 } /* void plugin_set_ctx */
 
-cdtime_t plugin_get_interval(void) {
+EXPORT cdtime_t plugin_get_interval(void) {
   cdtime_t interval;
 
   interval = plugin_get_ctx().interval;
   if (interval > 0)
     return interval;
 
+  P_ERROR("plugin_get_interval: Unable to determine Interval from context.");
+
   return cf_get_default_interval();
 } /* cdtime_t plugin_get_interval */
 
index 0369067..616889a 100644 (file)
@@ -34,6 +34,7 @@
 #include "meta_data.h"
 #include "utils_time.h"
 
+#include <inttypes.h>
 #include <pthread.h>
 
 #define DS_TYPE_COUNTER 0
@@ -171,6 +172,7 @@ struct user_data_s {
 typedef struct user_data_s user_data_t;
 
 struct plugin_ctx_s {
+  char *name;
   cdtime_t interval;
   cdtime_t flush_interval;
   cdtime_t flush_timeout;
@@ -243,7 +245,7 @@ int plugin_shutdown_all(void);
  *
  * DESCRIPTION
  *  Calls the write function of the given plugin with the provided data set and
- *  value list. It differs from `plugin_dispatch_value' in that it does not
+ *  value list. It differs from `plugin_dispatch_values' in that it does not
  *  update the cache, does not do threshold checking, call the chain subsystem
  *  and so on. It looks up the requested plugin and invokes the function, end
  *  of story.
@@ -398,6 +400,15 @@ int parse_notif_severity(const char *severity);
 #define DEBUG(...) /* noop */
 #endif             /* ! COLLECT_DEBUG */
 
+/* This will log messages, prefixed by plugin name */
+void daemon_log(int level, const char *format, ...)
+    __attribute__((format(printf, 2, 3)));
+
+#define P_ERROR(...) daemon_log(LOG_ERR, __VA_ARGS__)
+#define P_WARNING(...) daemon_log(LOG_WARNING, __VA_ARGS__)
+#define P_NOTICE(...) daemon_log(LOG_NOTICE, __VA_ARGS__)
+#define P_INFO(...) daemon_log(LOG_INFO, __VA_ARGS__)
+
 const data_set_t *plugin_get_ds(const char *name);
 
 int plugin_notification_meta_add_string(notification_t *n, const char *name,
index 8f8334e..1624f0e 100644 (file)
@@ -56,7 +56,19 @@ int plugin_register_init(const char *name, plugin_init_cb callback) {
   return ENOTSUP;
 }
 
-int plugin_register_read(const char *name, int (*callback)(void)) {
+int plugin_register_read(__attribute__((unused)) const char *name,
+                         __attribute__((unused)) int (*callback)(void)) {
+  return ENOTSUP;
+}
+
+int plugin_register_write(__attribute__((unused)) const char *name,
+                          __attribute__((unused)) plugin_write_cb callback,
+                          __attribute__((unused)) user_data_t const *ud) {
+  return ENOTSUP;
+}
+
+int plugin_register_missing(const char *name, plugin_missing_cb callback,
+                            user_data_t const *ud) {
   return ENOTSUP;
 }
 
@@ -158,6 +170,17 @@ void plugin_log(int level, char const *format, ...) {
   printf("plugin_log (%i, \"%s\");\n", level, buffer);
 }
 
+void daemon_log(int level, char const *format, ...) {
+  char buffer[1024];
+  va_list ap;
+
+  va_start(ap, format);
+  vsnprintf(buffer, sizeof(buffer), format, ap);
+  va_end(ap);
+
+  printf("daemon_log (%i, \"%s\");\n", level, buffer);
+}
+
 void plugin_init_ctx(void) { /* nop */
 }
 
index 87568fb..568d68c 100644 (file)
@@ -621,7 +621,7 @@ int c_avl_iterator_prev(c_avl_iterator_t *iter, void **key, void **value) {
     return -1;
 
   if (iter->node == NULL) {
-    for (n = iter->tree->root; n != NULL; n = n->left)
+    for (n = iter->tree->root; n != NULL; n = n->right)
       if (n->right == NULL)
         break;
     iter->node = n;
index 3171246..4be4941 100644 (file)
@@ -45,11 +45,17 @@ static int compare_callback(void const *v0, void const *v1) {
   return strcmp(v0, v1);
 }
 
+struct kv_t {
+  char *key;
+  char *value;
+};
+
+static int kv_compare(const void *a_ptr, const void *b_ptr) {
+  return strcmp(((struct kv_t *)a_ptr)->key, ((struct kv_t *)b_ptr)->key);
+}
+
 DEF_TEST(success) {
-  struct {
-    char *key;
-    char *value;
-  } cases[] = {
+  struct kv_t cases[] = {
       {"Eeph7chu", "vai1reiV"}, {"igh3Paiz", "teegh1Ee"},
       {"caip6Uu8", "ooteQu8n"}, {"Aech6vah", "AijeeT0l"},
       {"Xah0et2L", "gah8Taep"}, {"BocaeB8n", "oGaig8io"},
@@ -62,6 +68,11 @@ DEF_TEST(success) {
       {"ieN5engi", "Aevou1ah"}, {"ooTe4OhP", "aingai5Y"},
   };
 
+  struct kv_t sorted_cases[STATIC_ARRAY_SIZE(cases)];
+  memcpy(sorted_cases, cases, sizeof(cases));
+  qsort(sorted_cases, STATIC_ARRAY_SIZE(cases), sizeof(struct kv_t),
+        kv_compare);
+
   c_avl_tree_t *t;
 
   RESET_COUNTS();
@@ -91,6 +102,37 @@ DEF_TEST(success) {
     EXPECT_EQ_STR(cases[i].value, value_ret);
   }
 
+  /* iterate forward */
+  {
+    c_avl_iterator_t *iter = c_avl_get_iterator(t);
+    char *key;
+    char *value;
+    size_t i = 0;
+    while (c_avl_iterator_next(iter, (void **)&key, (void **)&value) == 0) {
+      EXPECT_EQ_STR(sorted_cases[i].key, key);
+      EXPECT_EQ_STR(sorted_cases[i].value, value);
+      i++;
+    }
+    c_avl_iterator_destroy(iter);
+    EXPECT_EQ_INT(i, STATIC_ARRAY_SIZE(cases));
+  }
+
+  /* iterate backward */
+  {
+    c_avl_iterator_t *iter = c_avl_get_iterator(t);
+    char *key;
+    char *value;
+    size_t i = 0;
+    while (c_avl_iterator_prev(iter, (void **)&key, (void **)&value) == 0) {
+      EXPECT_EQ_STR(sorted_cases[STATIC_ARRAY_SIZE(cases) - 1 - i].key, key);
+      EXPECT_EQ_STR(sorted_cases[STATIC_ARRAY_SIZE(cases) - 1 - i].value,
+                    value);
+      i++;
+    }
+    c_avl_iterator_destroy(iter);
+    EXPECT_EQ_INT(i, STATIC_ARRAY_SIZE(cases));
+  }
+
   /* remove half */
   for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases) / 2; i++) {
     char *key = NULL;
index 5389d12..7d7e436 100644 (file)
  *   Florian octo Forster <octo at collectd.org>
  */
 
-#include <errno.h>
 #include "utils_cache.h"
+#include <errno.h>
+
+#include <errno.h>
 
 gauge_t *uc_get_rate(__attribute__((unused)) data_set_t const *ds,
                      __attribute__((unused)) value_list_t const *vl) {
@@ -41,3 +43,28 @@ int uc_get_rate_by_name(const char *name, gauge_t **ret_values,
 int uc_get_names(char ***ret_names, cdtime_t **ret_times, size_t *ret_number) {
   return ENOTSUP;
 }
+
+int uc_get_value_by_name(const char *name, value_t **ret_values,
+                         size_t *ret_values_num) {
+  return ENOTSUP;
+}
+
+int uc_meta_data_get_signed_int(const value_list_t *vl, const char *key,
+                                int64_t *value) {
+  return -ENOENT;
+}
+
+int uc_meta_data_get_unsigned_int(const value_list_t *vl, const char *key,
+                                  uint64_t *value) {
+  return -ENOENT;
+}
+
+int uc_meta_data_add_signed_int(const value_list_t *vl, const char *key,
+                                int64_t value) {
+  return 0;
+}
+
+int uc_meta_data_add_unsigned_int(const value_list_t *vl, const char *key,
+                                  uint64_t value) {
+  return 0;
+}
index d36d410..3cecd89 100644 (file)
@@ -24,6 +24,8 @@
  *   Florian octo Forster <octo at collectd.org>
  **/
 
+#include "collectd.h"
+
 #include <assert.h>
 #include <errno.h>
 #include <pthread.h>
index 7a9ce7b..5500aaa 100644 (file)
 
 #include <pthread.h>
 
+#ifdef WIN32
+double erand48(unsigned short unused[3]) {
+  return (double)rand() / (double)RAND_MAX;
+}
+
+long int jrand48(unsigned short unused[3]) { return rand(); }
+#endif
+
 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 static bool have_seed;
 static unsigned short seed[3];
@@ -47,6 +55,10 @@ static void cdrand_seed(void) {
   seed[1] = (unsigned short)(t >> 16);
   seed[2] = (unsigned short)(t >> 32);
 
+#ifdef WIN32
+  srand((unsigned)t);
+#endif
+
   have_seed = true;
 }
 
index 1909d8c..899c802 100644 (file)
--- a/src/dbi.c
+++ b/src/dbi.c
@@ -64,8 +64,6 @@ struct cdbi_database_s /* {{{ */
   char *select_db;
   char *plugin_name;
 
-  cdtime_t interval;
-
   char *driver;
   char *host;
   cdbi_driver_option_t *driver_options;
@@ -267,6 +265,7 @@ static int cdbi_config_add_database_driver_option(cdbi_database_t *db, /* {{{ */
 
 static int cdbi_config_add_database(oconfig_item_t *ci) /* {{{ */
 {
+  cdtime_t interval = 0;
   cdbi_database_t *db;
   int status;
 
@@ -304,7 +303,7 @@ static int cdbi_config_add_database(oconfig_item_t *ci) /* {{{ */
     else if (strcasecmp("Host", child->key) == 0)
       status = cf_util_get_string(child, &db->host);
     else if (strcasecmp("Interval", child->key) == 0)
-      status = cf_util_get_cdtime(child, &db->interval);
+      status = cf_util_get_cdtime(child, &interval);
     else if (strcasecmp("Plugin", child->key) == 0)
       status = cf_util_get_string(child, &db->plugin_name);
     else {
@@ -370,7 +369,7 @@ static int cdbi_config_add_database(oconfig_item_t *ci) /* {{{ */
           /* group = */ NULL,
           /* name = */ name ? name : db->name,
           /* callback = */ cdbi_read_database,
-          /* interval = */ (db->interval > 0) ? db->interval : 0,
+          /* interval = */ interval,
           &(user_data_t){
               .data = db,
           });
@@ -550,8 +549,7 @@ static int cdbi_read_database_query(cdbi_database_t *db, /* {{{ */
   status = udb_query_prepare_result(
       q, prep_area, (db->host ? db->host : hostname_g),
       /* plugin = */ (db->plugin_name != NULL) ? db->plugin_name : "dbi",
-      db->name, column_names, column_num,
-      /* interval = */ (db->interval > 0) ? db->interval : 0);
+      db->name, column_names, column_num);
 
   if (status != 0) {
     ERROR("dbi plugin: udb_query_prepare_result failed with status %i.",
@@ -605,15 +603,16 @@ static int cdbi_read_database_query(cdbi_database_t *db, /* {{{ */
     } /* }}} */
 
     /* Get the next row from the database. */
+    if (!dbi_result_has_next_row(res))
+      break;
+
     status = dbi_result_next_row(res); /* {{{ */
     if (status != 1) {
-      if (dbi_conn_error(db->connection, NULL) != 0) {
-        char errbuf[1024];
-        WARNING("dbi plugin: cdbi_read_database_query (%s, %s): "
-                "dbi_result_next_row failed: %s.",
-                db->name, udb_query_get_name(q),
-                cdbi_strerror(db->connection, errbuf, sizeof(errbuf)));
-      }
+      char errbuf[1024];
+      WARNING("dbi plugin: cdbi_read_database_query (%s, %s): "
+              "dbi_result_next_row failed: %s.",
+              db->name, udb_query_get_name(q),
+              cdbi_strerror(db->connection, errbuf, sizeof(errbuf)));
       break;
     } /* }}} */
   }   /* }}} while (42) */
index e3aa453..8877b74 100644 (file)
--- a/src/df.c
+++ b/src/df.c
@@ -225,12 +225,10 @@ static int df_read(void) {
       if (strcmp(mnt_ptr->dir, "/") == 0)
         sstrncpy(disk_name, "root", sizeof(disk_name));
       else {
-        int len;
-
         sstrncpy(disk_name, mnt_ptr->dir + 1, sizeof(disk_name));
-        len = strlen(disk_name);
+        size_t len = strlen(disk_name);
 
-        for (int i = 0; i < len; i++)
+        for (size_t i = 0; i < len; i++)
           if (disk_name[i] == '/')
             disk_name[i] = '-';
       }
index 206862b..c0408ce 100644 (file)
@@ -303,9 +303,7 @@ static void submit_io_time(char const *plugin_instance, derive_t io_time,
 
   plugin_dispatch_values(&vl);
 } /* void submit_io_time */
-#endif /* KERNEL_FREEBSD || KERNEL_LINUX */
 
-#if KERNEL_LINUX
 static void submit_in_progress(char const *disk_name, gauge_t in_progress) {
   value_list_t vl = VALUE_LIST_INIT;
 
@@ -317,7 +315,9 @@ static void submit_in_progress(char const *disk_name, gauge_t in_progress) {
 
   plugin_dispatch_values(&vl);
 }
+#endif /* KERNEL_FREEBSD || KERNEL_LINUX */
 
+#if KERNEL_LINUX
 static counter_t disk_calc_time_incr(counter_t delta_time,
                                      counter_t delta_ops) {
   double interval = CDTIME_T_TO_DOUBLE(plugin_get_interval());
@@ -544,6 +544,7 @@ static int disk_read(void) {
 
   const char *disk_name;
   long double read_time, write_time, busy_time, total_duration;
+  uint64_t queue_length;
 
   for (retry = 0, dirty = 1; retry < 5 && dirty == 1; retry++) {
     if (snap != NULL)
@@ -646,10 +647,12 @@ static int disk_read(void) {
     }
     if (devstat_compute_statistics(snap_iter, NULL, 1.0, DSM_TOTAL_BUSY_TIME,
                                    &busy_time, DSM_TOTAL_DURATION,
-                                   &total_duration, DSM_NONE) != 0) {
+                                   &total_duration, DSM_QUEUE_LENGTH,
+                                   &queue_length, DSM_NONE) != 0) {
       WARNING("%s", devstat_errbuf);
     } else {
       submit_io_time(disk_name, busy_time, total_duration);
+      submit_in_progress(disk_name, (gauge_t)queue_length);
     }
   }
   geom_stats_snapshot_free(snap);
@@ -660,7 +663,6 @@ static int disk_read(void) {
 
   char *fields[32];
   int numfields;
-  int fieldshift = 0;
 
   int minor = 0;
 
@@ -681,14 +683,8 @@ static int disk_read(void) {
   diskstats_t *ds, *pre_ds;
 
   if ((fh = fopen("/proc/diskstats", "r")) == NULL) {
-    fh = fopen("/proc/partitions", "r");
-    if (fh == NULL) {
-      ERROR("disk plugin: fopen (/proc/{diskstats,partitions}) failed.");
-      return -1;
-    }
-
-    /* Kernel is 2.4.* */
-    fieldshift = 1;
+    ERROR("disk plugin: fopen(\"/proc/diskstats\"): %s", STRERRNO);
+    return -1;
   }
 
   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
@@ -696,13 +692,12 @@ static int disk_read(void) {
     char *output_name;
 
     numfields = strsplit(buffer, fields, 32);
-
-    if ((numfields != (14 + fieldshift)) && (numfields != 7))
+    if ((numfields != 14) && (numfields != 7))
       continue;
 
     minor = atoll(fields[1]);
 
-    disk_name = fields[2 + fieldshift];
+    disk_name = fields[2];
 
     for (ds = disklist, pre_ds = disklist; ds != NULL;
          pre_ds = ds, ds = ds->next)
@@ -731,24 +726,24 @@ static int disk_read(void) {
       read_sectors = atoll(fields[4]);
       write_ops = atoll(fields[5]);
       write_sectors = atoll(fields[6]);
-    } else if (numfields == (14 + fieldshift)) {
-      read_ops = atoll(fields[3 + fieldshift]);
-      write_ops = atoll(fields[7 + fieldshift]);
+    } else if (numfields == 14) {
+      read_ops = atoll(fields[3]);
+      write_ops = atoll(fields[7]);
 
-      read_sectors = atoll(fields[5 + fieldshift]);
-      write_sectors = atoll(fields[9 + fieldshift]);
+      read_sectors = atoll(fields[5]);
+      write_sectors = atoll(fields[9]);
 
-      if ((fieldshift == 0) || (minor == 0)) {
+      if (minor == 0) {
         is_disk = 1;
-        read_merged = atoll(fields[4 + fieldshift]);
-        read_time = atoll(fields[6 + fieldshift]);
-        write_merged = atoll(fields[8 + fieldshift]);
-        write_time = atoll(fields[10 + fieldshift]);
+        read_merged = atoll(fields[4]);
+        read_time = atoll(fields[6]);
+        write_merged = atoll(fields[8]);
+        write_time = atoll(fields[10]);
 
-        in_progress = atof(fields[11 + fieldshift]);
+        in_progress = atof(fields[11]);
 
-        io_time = atof(fields[12 + fieldshift]);
-        weighted_time = atof(fields[13 + fieldshift]);
+        io_time = atof(fields[12]);
+        weighted_time = atof(fields[13]);
       }
     } else {
       DEBUG("numfields = %i; => unknown file format.", numfields);
index 9970be0..2a44b2c 100644 (file)
@@ -419,8 +419,12 @@ static int dpdk_events_config(oconfig_item_t *ci) {
 static int dpdk_helper_link_status_get(dpdk_helper_ctx_t *phc) {
   dpdk_events_ctx_t *ec = DPDK_EVENTS_CTX_GET(phc);
 
-  /* get Link Status values from DPDK */
+/* get Link Status values from DPDK */
+#if RTE_VERSION < RTE_VERSION_NUM(18, 05, 0, 0)
   uint8_t nb_ports = rte_eth_dev_count();
+#else
+  uint8_t nb_ports = rte_eth_dev_count_avail();
+#endif
   if (nb_ports == 0) {
     DPDK_CHILD_LOG("dpdkevent-helper: No DPDK ports available. "
                    "Check bound devices to DPDK driver.\n");
index 376dd6a..f8a94fb 100644 (file)
@@ -260,7 +260,6 @@ static void *collect(void *arg) {
     while (42) {
       /* 256 bytes ought to be enough for anybody ;-) */
       char line[256 + 1]; /* line + '\0' */
-      int len = 0;
 
       errno = 0;
       if (fgets(line, sizeof(line), this->socket) == NULL) {
@@ -272,7 +271,7 @@ static void *collect(void *arg) {
         break;
       }
 
-      len = strlen(line);
+      size_t len = strlen(line);
       if ((line[len - 1] != '\n') && (line[len - 1] != '\r')) {
         log_warn("collect: line too long (> %" PRIsz " characters): "
                  "'%s' (truncated)",
@@ -287,7 +286,7 @@ static void *collect(void *arg) {
         continue;
       }
 
-      line[len - 1] = 0;
+      line[len - 1] = '\0';
 
       log_debug("collect: line = '%s'", line);
 
index b145e81..77b1375 100644 (file)
@@ -78,6 +78,11 @@ typedef struct program_list_and_notification_s {
 } program_list_and_notification_t;
 
 /*
+ * constants
+ */
+const long int MAX_GRBUF_SIZE = 65536;
+
+/*
  * Private variables
  */
 static program_list_t *pl_head;
@@ -340,6 +345,65 @@ static void close_pipe(int fd_pipe[2]) /* {{{ */
 } /* }}} void close_pipe */
 
 /*
+ * Get effective group ID from group name.
+ * Input arguments:
+ *       pl  :program list struct with group name
+ *       gid :group id to fallback in case egid cannot be determined.
+ * Returns:
+ *       egid effective group id if successfull,
+ *            -1 if group is not defined/not found.
+ *            -2 for any buffer allocation error.
+ */
+static int getegr_id(program_list_t *pl, int gid) /* {{{ */
+{
+  if (pl->group == NULL) {
+    return -1;
+  }
+  if (strcmp(pl->group, "") == 0) {
+    return gid;
+  }
+  struct group *gr_ptr = NULL;
+  struct group gr;
+
+  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 *temp = NULL;
+  char *grbuf = NULL;
+
+  do {
+    temp = realloc(grbuf, grbuf_size);
+    if (temp == NULL) {
+      ERROR("exec plugin: getegr_id for %s: realloc buffer[%ld] failed ",
+            pl->group, grbuf_size);
+      sfree(grbuf);
+      return -2;
+    }
+    grbuf = temp;
+    if (getgrnam_r(pl->group, &gr, grbuf, grbuf_size, &gr_ptr) == 0) {
+      sfree(grbuf);
+      if (gr_ptr == NULL) {
+        ERROR("exec plugin: No such group: `%s'", pl->group);
+        return -1;
+      }
+      return gr.gr_gid;
+    } else if (errno == ERANGE) {
+      grbuf_size += grbuf_size; // increment buffer size and try again
+    } else {
+      ERROR("exec plugin: getegr_id failed %s", STRERRNO);
+      sfree(grbuf);
+      return -2;
+    }
+  } while (grbuf_size <= MAX_GRBUF_SIZE);
+  ERROR("exec plugin: getegr_id Max grbuf size reached  for %s", pl->group);
+  sfree(grbuf);
+  return -2;
+}
+
+/*
  * Creates three pipes (one for reading, one for writing and one for errors),
  * forks a child, sets up the pipes so that fd_in is connected to STDIN of
  * the child and fd_out is connected to STDOUT and fd_err is connected to STDERR
@@ -397,36 +461,10 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
 
   /* The group configured in the configfile is set as effective group, because
    * this way the forked process can (re-)gain the user's primary group. */
-  egid = -1;
-  if (pl->group != NULL) {
-    if (*pl->group != '\0') {
-      struct group *gr_ptr = NULL;
-      struct group gr;
-
-      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",
-              pl->group, STRERROR(status));
-        goto failed;
-      }
-      if (gr_ptr == NULL) {
-        ERROR("exec plugin: No such group: `%s'", pl->group);
-        goto failed;
-      }
-
-      egid = gr.gr_gid;
-    } else {
-      egid = gid;
-    }
-  } /* if (pl->group == NULL) */
+  egid = getegr_id(pl, gid);
+  if (egid == -2) {
+    goto failed;
+  }
 
   pid = fork();
   if (pid < 0) {
index ef1a138..9091ff5 100644 (file)
@@ -154,26 +154,6 @@ static int fc_config_add_dir_instance(fc_directory_conf_t *dir,
   return fc_config_set_instance(dir, ci->values[0].value.string);
 } /* int fc_config_add_dir_instance */
 
-static int fc_config_add_dir_name(fc_directory_conf_t *dir,
-                                  oconfig_item_t *ci) {
-  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;
-  }
-
-  char *temp = strdup(ci->values[0].value.string);
-  if (temp == NULL) {
-    ERROR("filecount plugin: strdup failed.");
-    return -1;
-  }
-
-  sfree(dir->name);
-  dir->name = temp;
-
-  return 0;
-} /* int fc_config_add_dir_name */
-
 static int fc_config_add_dir_mtime(fc_directory_conf_t *dir,
                                    oconfig_item_t *ci) {
   if ((ci->values_num != 1) || ((ci->values[0].type != OCONFIG_TYPE_STRING) &&
@@ -369,7 +349,7 @@ static int fc_config_add_dir(oconfig_item_t *ci) {
     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);
+      status = cf_util_get_string(option, &dir->name);
     else if (strcasecmp("MTime", option->key) == 0)
       status = fc_config_add_dir_mtime(dir, option);
     else if (strcasecmp("Size", option->key) == 0)
index ca65419..2bca05a 100644 (file)
@@ -833,28 +833,6 @@ static int mc_receive_thread_stop(void) /* {{{ */
  *   </Metric>
  * </Plugin>
  */
-static int gmond_config_set_string(oconfig_item_t *ci, char **str) /* {{{ */
-{
-  char *tmp;
-
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    WARNING("gmond plugin: The `%s' option needs "
-            "exactly one string argument.",
-            ci->key);
-    return -1;
-  }
-
-  tmp = strdup(ci->values[0].value.string);
-  if (tmp == NULL) {
-    ERROR("gmond plugin: strdup failed.");
-    return -1;
-  }
-
-  sfree(*str);
-  *str = tmp;
-  return 0;
-} /* }}} int gmond_config_set_string */
-
 static int gmond_config_add_metric(oconfig_item_t *ci) /* {{{ */
 {
   metric_map_t *map;
@@ -889,11 +867,11 @@ static int gmond_config_add_metric(oconfig_item_t *ci) /* {{{ */
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
     if (strcasecmp("Type", child->key) == 0)
-      gmond_config_set_string(child, &map->type);
+      cf_util_get_string(child, &map->type);
     else if (strcasecmp("TypeInstance", child->key) == 0)
-      gmond_config_set_string(child, &map->type_instance);
+      cf_util_get_string(child, &map->type_instance);
     else if (strcasecmp("DataSource", child->key) == 0)
-      gmond_config_set_string(child, &map->ds_name);
+      cf_util_get_string(child, &map->ds_name);
     else {
       WARNING("gmond plugin: Unknown configuration option `%s' ignored.",
               child->key);
index 1d32d04..b22c3a2 100644 (file)
--- a/src/gps.c
+++ b/src/gps.c
@@ -141,7 +141,12 @@ static void *cgps_thread(void *pData) {
         continue;
       }
 
-      if (gps_read(&gpsd_conn) == -1) {
+#if GPSD_API_MAJOR_VERSION > 6
+      if (gps_read(&gpsd_conn, NULL, 0) == -1)
+#else
+      if (gps_read(&gpsd_conn) == -1)
+#endif
+      {
         WARNING("gps plugin: incorrect data! (err_count: %d)", err_count);
         err_count++;
 
index 667033c..62ce9b8 100644 (file)
@@ -25,9 +25,9 @@
  *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
  **/
 
+#include "collectd.h"
 #include "common.h"
 #include "utils_config_cores.h"
-#include "collectd.h"
 
 #include <pqos.h>
 
@@ -336,8 +336,8 @@ static int rdt_config(oconfig_item_t *ci) {
   return 0;
 }
 
-static void rdt_submit_derive(char *cgroup, char *type, char *type_instance,
-                              derive_t value) {
+static void rdt_submit_derive(const char *cgroup, const char *type,
+                              const char *type_instance, derive_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
   vl.values = &(value_t){.derive = value};
@@ -352,8 +352,8 @@ static void rdt_submit_derive(char *cgroup, char *type, char *type_instance,
   plugin_dispatch_values(&vl);
 }
 
-static void rdt_submit_gauge(char *cgroup, char *type, char *type_instance,
-                             gauge_t value) {
+static void rdt_submit_gauge(const char *cgroup, const char *type,
+                             const char *type_instance, gauge_t value) {
   value_list_t vl = VALUE_LIST_INIT;
 
   vl.values = &(value_t){.gauge = value};
index 8fad588..225ed2c 100644 (file)
@@ -103,9 +103,7 @@ static int iptables_config(const char *key, const char *value) {
   ip_chain_t temp = {0};
   ip_chain_t *final, **list;
   char *table;
-  int table_len;
   char *chain;
-  int chain_len;
 
   char *value_copy;
   char *fields[4];
@@ -136,16 +134,16 @@ static int iptables_config(const char *key, const char *value) {
   table = fields[0];
   chain = fields[1];
 
-  table_len = strlen(table) + 1;
-  if ((unsigned int)table_len > sizeof(temp.table)) {
+  size_t table_len = strlen(table) + 1;
+  if (table_len > sizeof(temp.table)) {
     ERROR("Table `%s' too long.", table);
     free(value_copy);
     return 1;
   }
   sstrncpy(temp.table, table, table_len);
 
-  chain_len = strlen(chain) + 1;
-  if ((unsigned int)chain_len > sizeof(temp.chain)) {
+  size_t chain_len = strlen(chain) + 1;
+  if (chain_len > sizeof(temp.chain)) {
     ERROR("Chain `%s' too long.", chain);
     free(value_copy);
     return 1;
index 449e816..3f4b3d1 100644 (file)
@@ -1009,9 +1009,8 @@ static int jtoc_values_array(JNIEnv *jvm_env, /* {{{ */
   jobjectArray o_number_array;
 
   value_t *values;
-  int values_num;
 
-  values_num = ds->ds_num;
+  size_t values_num = ds->ds_num;
 
   values = NULL;
   o_number_array = NULL;
@@ -1064,7 +1063,7 @@ static int jtoc_values_array(JNIEnv *jvm_env, /* {{{ */
     BAIL_OUT(-1);
   }
 
-  for (int i = 0; i < values_num; i++) {
+  for (size_t i = 0; i < values_num; i++) {
     jobject o_number;
     int status;
 
@@ -1072,7 +1071,7 @@ static int jtoc_values_array(JNIEnv *jvm_env, /* {{{ */
         (*jvm_env)->GetObjectArrayElement(jvm_env, o_number_array, (jsize)i);
     if (o_number == NULL) {
       ERROR("java plugin: jtoc_values_array: "
-            "GetObjectArrayElement (%i) failed.",
+            "GetObjectArrayElement (%zu) failed.",
             i);
       BAIL_OUT(-1);
     }
@@ -1080,7 +1079,7 @@ static int jtoc_values_array(JNIEnv *jvm_env, /* {{{ */
     status = jtoc_value(jvm_env, values + i, ds->ds[i].type, o_number);
     if (status != 0) {
       ERROR("java plugin: jtoc_values_array: "
-            "jtoc_value (%i) failed.",
+            "jtoc_value (%zu) failed.",
             i);
       BAIL_OUT(-1);
     }
index d470324..df6fd96 100644 (file)
  *   Florian octo Forster <octo at collectd.org>
  **/
 
+#ifdef WIN32
+#include "gnulib_config.h"
+#include <winsock2.h>
+#endif
+
 #include "config.h"
 
 #if !defined(__GNUC__) || !__GNUC__
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/socket.h>
 #include <sys/types.h>
-#include <sys/un.h>
 #include <unistd.h>
 
+#ifndef WIN32
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif
+
 #include "collectd/client.h"
 
 /* NI_MAXHOST has been obsoleted by RFC 3493 which is a reason for SunOS 5.11
 #endif
 #endif
 
+#ifdef WIN32
+#define AI_ADDRCONFIG 0
+#endif
+
 /* Secure/static macros. They work like `strcpy' and `strcat', but assure null
  * termination. They work for static buffers only, because they use `sizeof'.
  * The `SSTRCATF' combines the functionality of `snprintf' and `strcat' which
@@ -367,6 +379,10 @@ static int lcc_sendreceive(lcc_connection_t *c, /* {{{ */
 
 static int lcc_open_unixsocket(lcc_connection_t *c, const char *path) /* {{{ */
 {
+#ifdef WIN32
+  lcc_set_errno(c, ENOTSUP);
+  return -1;
+#else
   struct sockaddr_un sa = {0};
   int fd;
   int status;
@@ -401,6 +417,7 @@ static int lcc_open_unixsocket(lcc_connection_t *c, const char *path) /* {{{ */
   }
 
   return 0;
+#endif /* WIN32 */
 } /* }}} int lcc_open_unixsocket */
 
 static int lcc_open_netsocket(lcc_connection_t *c, /* {{{ */
index c8a5da5..0b97362 100644 (file)
 #include <inttypes.h>
 #include <stdint.h>
 
+#ifdef WIN32
+extern unsigned int if_nametoindex(const char *interface_name);
+#endif
+
 #define NET_DEFAULT_V4_ADDR "239.192.74.66"
 #define NET_DEFAULT_V6_ADDR "ff18::efc0:4a42"
 #define NET_DEFAULT_PORT "25826"
@@ -60,7 +64,7 @@ int lcc_server_destroy(lcc_network_t *net, lcc_server_t *srv);
 
 /* Configure servers */
 int lcc_server_set_ttl(lcc_server_t *srv, uint8_t ttl);
-int lcc_server_set_interface(lcc_server_t *srv, char const *interface);
+int lcc_server_set_interface(lcc_server_t *srv, char const *iface);
 int lcc_server_set_security_level(lcc_server_t *srv, lcc_security_level_t level,
                                   const char *username, const char *password);
 
index ef6b792..e50df17 100644 (file)
@@ -74,7 +74,7 @@ typedef struct {
 
   /* interface is the name of the interface to use when subscribing to a
    * multicast group. Has no effect when using unicast. */
-  char *interface;
+  char *iface;
 } lcc_listener_t;
 
 /* lcc_listen_and_write listens on the provided UDP socket (or opens one using
index 49257d4..2d6b3ed 100644 (file)
 #include <net/if.h>
 #endif
 
+#ifdef WIN32
+#define AI_ADDRCONFIG 0
+#endif
+
 #include "collectd/network.h"
 #include "collectd/network_buffer.h"
 
@@ -364,15 +368,15 @@ int lcc_server_set_ttl(lcc_server_t *srv, uint8_t ttl) /* {{{ */
   return 0;
 } /* }}} int lcc_server_set_ttl */
 
-int lcc_server_set_interface(lcc_server_t *srv, char const *interface) /* {{{ */
+int lcc_server_set_interface(lcc_server_t *srv, char const *iface) /* {{{ */
 {
   unsigned int if_index;
   int status;
 
-  if ((srv == NULL) || (interface == NULL))
+  if ((srv == NULL) || (iface == NULL))
     return EINVAL;
 
-  if_index = if_nametoindex(interface);
+  if_index = if_nametoindex(iface);
   if (if_index == 0)
     return ENOENT;
 
@@ -420,8 +424,8 @@ int lcc_server_set_interface(lcc_server_t *srv, char const *interface) /* {{{ */
 
 /* else: Not a multicast interface. */
 #if defined(SO_BINDTODEVICE)
-  status = setsockopt(srv->fd, SOL_SOCKET, SO_BINDTODEVICE, interface,
-                      (socklen_t)(strlen(interface) + 1));
+  status = setsockopt(srv->fd, SOL_SOCKET, SO_BINDTODEVICE, iface,
+                      (socklen_t)(strlen(iface) + 1));
   if (status != 0)
     return -1;
 #endif
index 5a1ee8d..44d93e0 100644 (file)
  *   Florian octo Forster <octo at collectd.org>
  **/
 
+#ifdef WIN32
+#include "gnulib_config.h"
+#endif
+
 #include "config.h"
 
 #include <arpa/inet.h> /* htons */
index 1095eba..629c367 100644 (file)
  *   Florian octo Forster <octo at collectd.org>
  **/
 
+#ifdef WIN32
+#include "gnulib_config.h"
+#endif
+
 #include "config.h"
 
 #if !defined(__GNUC__) || !__GNUC__
 #include <stdio.h>
 #define DEBUG(...) printf(__VA_ARGS__)
 
+#ifdef WIN32
+#include <ws2tcpip.h>
+#define AI_ADDRCONFIG 0
+#endif
+
 static bool is_multicast(struct addrinfo const *ai) {
   if (ai->ai_family == AF_INET) {
     struct sockaddr_in *addr = (struct sockaddr_in *)ai->ai_addr;
@@ -81,13 +90,20 @@ static int server_multicast_join(lcc_listener_t *srv,
     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),
+        .imr_ifindex = if_nametoindex(srv->iface),
     };
 #else
+#ifdef WIN32
     struct ip_mreq mreq = {
+        .imr_interface.s_addr = INADDR_ANY,
         .imr_multiaddr.s_addr = sa->sin_addr.s_addr,
     };
-#endif
+#else
+    struct ip_mreq mreq = {
+        .imr_multiaddr.s_addr = sa->sin_addr.s_addr,
+    };
+#endif /* WIN32 */
+#endif /* HAVE_STRUCT_IP_MREQN_IMR_IFINDEX */
     status = setsockopt(srv->conn, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
                         sizeof(mreq));
     if (status == -1)
@@ -106,7 +122,7 @@ static int server_multicast_join(lcc_listener_t *srv,
       return errno;
 
     struct ipv6_mreq mreq6 = {
-        .ipv6mr_interface = if_nametoindex(srv->interface),
+        .ipv6mr_interface = if_nametoindex(srv->iface),
     };
     memmove(&mreq6.ipv6mr_multiaddr, &sa->sin6_addr, sizeof(struct in6_addr));
 
index 90f51de..1b2d6cc 100644 (file)
@@ -265,21 +265,19 @@ static void yyerror(const char *s)
 static char *unquote (const char *orig)
 {
        char *ret = strdup (orig);
-       int len;
-
        if (ret == NULL)
-               return (NULL);
+               return NULL;
 
-       len = strlen (ret);
+       size_t len = strlen (ret);
 
        if ((len < 2) || (ret[0] != '"') || (ret[len - 1] != '"'))
-               return (ret);
+               return ret;
 
        len -= 2;
        memmove (ret, ret + 1, len);
        ret[len] = 0;
 
-       for (int i = 0; i < len; i++)
+       for (size_t i = 0; i < len; i++)
        {
                if (ret[i] == '\\')
                {
@@ -288,5 +286,5 @@ static char *unquote (const char *orig)
                }
        }
 
-       return (ret);
+       return ret;
 } /* char *unquote */
index cfd9a5c..7efa78a 100644 (file)
  */
 
 %{
+#ifdef WIN32
+#include "gnulib_config.h"
+#include "config.h"
+#endif
+
 #include <stdlib.h>
 #include <string.h>
 #include "oconfig.h"
index 8cfb704..f66d852 100644 (file)
--- a/src/lua.c
+++ b/src/lua.c
  *   Ruben Kerkhof <ruben at rubenkerkhof.com>
  **/
 
-/* <lua5.1/luaconf.h> defines a macro using "sprintf". Although not used here,
- * GCC will complain about the macro definition. */
-#define DONT_POISON_SPRINTF_YET
-
+#include "collectd.h"
 #include "common.h"
 #include "plugin.h"
-#include "collectd.h"
+#include "utils_lua.h"
 
 /* Include the Lua API header files. */
 #include <lauxlib.h>
 #include <lua.h>
 #include <lualib.h>
-#include "utils_lua.h"
 
 #include <pthread.h>
 
-#if COLLECT_DEBUG && __GNUC__
-#undef sprintf
-#pragma GCC poison sprintf
-#endif
-
 typedef struct lua_script_s {
   char *script_path;
   lua_State *lua_state;
index 13e388e..f293aa1 100644 (file)
@@ -123,21 +123,6 @@ static int cmc_page_init_memc(web_page_t *wp) /* {{{ */
   return 0;
 } /* }}} int cmc_page_init_memc */
 
-static int cmc_config_add_string(const char *name, char **dest, /* {{{ */
-                                 oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    WARNING("memcachec plugin: `%s' needs exactly one string argument.", name);
-    return -1;
-  }
-
-  sfree(*dest);
-  *dest = strdup(ci->values[0].value.string);
-  if (*dest == NULL)
-    return -1;
-
-  return 0;
-} /* }}} int cmc_config_add_string */
-
 static int cmc_config_add_match_dstype(int *dstype_ret, /* {{{ */
                                        oconfig_item_t *ci) {
   int dstype;
@@ -204,16 +189,15 @@ static int cmc_config_add_match(web_page_t *page, /* {{{ */
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("Regex", child->key) == 0)
-      status = cmc_config_add_string("Regex", &match->regex, child);
+      status = cf_util_get_string(child, &match->regex);
     else if (strcasecmp("ExcludeRegex", child->key) == 0)
-      status =
-          cmc_config_add_string("ExcludeRegex", &match->exclude_regex, child);
+      status = cf_util_get_string(child, &match->exclude_regex);
     else if (strcasecmp("DSType", child->key) == 0)
       status = cmc_config_add_match_dstype(&match->dstype, child);
     else if (strcasecmp("Type", child->key) == 0)
-      status = cmc_config_add_string("Type", &match->type, child);
+      status = cf_util_get_string(child, &match->type);
     else if (strcasecmp("Instance", child->key) == 0)
-      status = cmc_config_add_string("Instance", &match->instance, child);
+      status = cf_util_get_string(child, &match->instance);
     else {
       WARNING("memcachec plugin: Option `%s' not allowed here.", child->key);
       status = -1;
@@ -301,11 +285,11 @@ static int cmc_config_add_page(oconfig_item_t *ci) /* {{{ */
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("Server", child->key) == 0)
-      status = cmc_config_add_string("Server", &page->server, child);
+      status = cf_util_get_string(child, &page->server);
     else if (strcasecmp("Key", child->key) == 0)
-      status = cmc_config_add_string("Key", &page->key, child);
+      status = cf_util_get_string(child, &page->key);
     else if (strcasecmp("Plugin", child->key) == 0)
-      status = cmc_config_add_string("Plugin", &page->plugin_name, child);
+      status = cf_util_get_string(child, &page->plugin_name);
     else if (strcasecmp("Match", child->key) == 0)
       /* Be liberal with failing matches => don't set `status'. */
       cmc_config_add_match(page, child);
index 11df999..4ff70f7 100644 (file)
@@ -475,7 +475,7 @@ static int memcached_read(user_data_t *user_data) {
     if (strsplit(line, fields, 3) != 3)
       continue;
 
-    int name_len = strlen(fields[1]);
+    size_t name_len = strlen(fields[1]);
     if (name_len == 0)
       continue;
 
index bb9eaa0..0a4f40c 100644 (file)
@@ -129,7 +129,6 @@ struct mb_host_s /* {{{ */
   int port;     /* for Modbus/TCP */
   int baudrate; /* for Modbus/RTU */
   mb_conntype_t conntype;
-  cdtime_t interval;
 
   mb_slave_t *slaves;
   size_t slaves_num;
@@ -252,15 +251,11 @@ static int mb_submit(mb_host_t *host, mb_slave_t *slave, /* {{{ */
   if ((host == NULL) || (slave == NULL) || (data == NULL))
     return EINVAL;
 
-  if (host->interval == 0)
-    host->interval = plugin_get_interval();
-
   if (slave->instance[0] == 0)
     snprintf(slave->instance, sizeof(slave->instance), "slave_%i", slave->id);
 
   vl.values = &value;
   vl.values_len = 1;
-  vl.interval = host->interval;
   sstrncpy(vl.host, host->host, sizeof(vl.host));
   sstrncpy(vl.plugin, "modbus", sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, slave->instance, sizeof(vl.plugin_instance));
@@ -952,6 +947,7 @@ static int mb_config_add_slave(mb_host_t *host, oconfig_item_t *ci) /* {{{ */
 
 static int mb_config_add_host(oconfig_item_t *ci) /* {{{ */
 {
+  cdtime_t interval = 0;
   mb_host_t *host;
   int status;
 
@@ -992,7 +988,7 @@ static int mb_config_add_host(oconfig_item_t *ci) /* {{{ */
     } else if (strcasecmp("Baudrate", child->key) == 0)
       status = cf_util_get_int(child, &host->baudrate);
     else if (strcasecmp("Interval", child->key) == 0)
-      status = cf_util_get_cdtime(child, &host->interval);
+      status = cf_util_get_cdtime(child, &interval);
     else if (strcasecmp("Slave", child->key) == 0)
       /* Don't set status: Gracefully continue if a slave fails. */
       mb_config_add_slave(host, child);
@@ -1033,7 +1029,7 @@ static int mb_config_add_host(oconfig_item_t *ci) /* {{{ */
 
     plugin_register_complex_read(/* group = */ NULL, name,
                                  /* callback = */ mb_read,
-                                 /* interval = */ host->interval,
+                                 /* interval = */ interval,
                                  &(user_data_t){
                                      .data = host, .free_func = host_free,
                                  });
index 281764c..62600ca 100644 (file)
@@ -2229,42 +2229,6 @@ static int cna_query_system(host_config_t *host) /* {{{ */
 /*
  * Configuration handling
  */
-/* Sets a given flag if the boolean argument is true and unsets the flag if it
- * is false. On error, the flag-field is not changed. */
-static int cna_config_bool_to_flag(const oconfig_item_t *ci, /* {{{ */
-                                   uint32_t *flags, uint32_t flag) {
-  if ((ci == NULL) || (flags == NULL))
-    return EINVAL;
-
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) {
-    WARNING("netapp plugin: The %s option needs exactly one boolean argument.",
-            ci->key);
-    return -1;
-  }
-
-  if (ci->values[0].value.boolean)
-    *flags |= flag;
-  else
-    *flags &= ~flag;
-
-  return 0;
-} /* }}} int cna_config_bool_to_flag */
-
-/* Handling of the "Interval" option which is allowed in every block. */
-static int cna_config_get_interval(const oconfig_item_t *ci, /* {{{ */
-                                   cna_interval_t *out_interval) {
-  cdtime_t tmp = 0;
-  int status;
-
-  status = cf_util_get_cdtime(ci, &tmp);
-  if (status != 0)
-    return status;
-
-  out_interval->interval = tmp;
-  out_interval->last_read = 0;
-
-  return 0;
-} /* }}} int cna_config_get_interval */
 
 /* Handling of the "GetIO", "GetOps" and "GetLatency" options within a
  * <VolumePerf /> block. */
@@ -2385,7 +2349,7 @@ static int cna_config_volume_performance(host_config_t *host, /* {{{ */
 
     /* if (!item || !item->key || !*item->key) continue; */
     if (strcasecmp(item->key, "Interval") == 0)
-      cna_config_get_interval(item, &cfg_volume_perf->interval);
+      cf_util_get_cdtime(item, &cfg_volume_perf->interval.interval);
     else if (!strcasecmp(item->key, "GetIO"))
       cna_config_volume_perf_option(cfg_volume_perf, item);
     else if (!strcasecmp(item->key, "GetOps"))
@@ -2481,7 +2445,7 @@ static int cna_config_quota(host_config_t *host, oconfig_item_t *ci) /* {{{ */
     oconfig_item_t *item = ci->children + i;
 
     if (strcasecmp(item->key, "Interval") == 0)
-      cna_config_get_interval(item, &cfg_quota->interval);
+      cf_util_get_cdtime(item, &cfg_quota->interval.interval);
     else
       WARNING("netapp plugin: The option %s is not allowed within "
               "`Quota' blocks.",
@@ -2517,9 +2481,9 @@ static int cna_config_disk(host_config_t *host, oconfig_item_t *ci) { /* {{{ */
 
     /* if (!item || !item->key || !*item->key) continue; */
     if (strcasecmp(item->key, "Interval") == 0)
-      cna_config_get_interval(item, &cfg_disk->interval);
+      cf_util_get_cdtime(item, &cfg_disk->interval.interval);
     else if (strcasecmp(item->key, "GetBusy") == 0)
-      cna_config_bool_to_flag(item, &cfg_disk->flags, CFG_DISK_BUSIEST);
+      cf_util_get_flag(item, &cfg_disk->flags, CFG_DISK_BUSIEST);
   }
 
   if ((cfg_disk->flags & CFG_DISK_ALL) == 0) {
@@ -2556,15 +2520,15 @@ static int cna_config_wafl(host_config_t *host, oconfig_item_t *ci) /* {{{ */
     oconfig_item_t *item = ci->children + i;
 
     if (strcasecmp(item->key, "Interval") == 0)
-      cna_config_get_interval(item, &cfg_wafl->interval);
+      cf_util_get_cdtime(item, &cfg_wafl->interval.interval);
     else if (!strcasecmp(item->key, "GetNameCache"))
-      cna_config_bool_to_flag(item, &cfg_wafl->flags, CFG_WAFL_NAME_CACHE);
+      cf_util_get_flag(item, &cfg_wafl->flags, CFG_WAFL_NAME_CACHE);
     else if (!strcasecmp(item->key, "GetDirCache"))
-      cna_config_bool_to_flag(item, &cfg_wafl->flags, CFG_WAFL_DIR_CACHE);
+      cf_util_get_flag(item, &cfg_wafl->flags, CFG_WAFL_DIR_CACHE);
     else if (!strcasecmp(item->key, "GetBufferCache"))
-      cna_config_bool_to_flag(item, &cfg_wafl->flags, CFG_WAFL_BUF_CACHE);
+      cf_util_get_flag(item, &cfg_wafl->flags, CFG_WAFL_BUF_CACHE);
     else if (!strcasecmp(item->key, "GetInodeCache"))
-      cna_config_bool_to_flag(item, &cfg_wafl->flags, CFG_WAFL_INODE_CACHE);
+      cf_util_get_flag(item, &cfg_wafl->flags, CFG_WAFL_INODE_CACHE);
     else
       WARNING("netapp plugin: The %s config option is not allowed within "
               "`WAFL' blocks.",
@@ -2636,7 +2600,7 @@ static int cna_config_volume_usage(host_config_t *host, /* {{{ */
 
     /* if (!item || !item->key || !*item->key) continue; */
     if (strcasecmp(item->key, "Interval") == 0)
-      cna_config_get_interval(item, &cfg_volume_usage->interval);
+      cf_util_get_cdtime(item, &cfg_volume_usage->interval.interval);
     else if (!strcasecmp(item->key, "GetCapacity"))
       cna_config_volume_usage_option(cfg_volume_usage, item);
     else if (!strcasecmp(item->key, "GetSnapshot"))
@@ -2677,7 +2641,7 @@ static int cna_config_snapvault(host_config_t *host, /* {{{ */
     oconfig_item_t *item = ci->children + i;
 
     if (strcasecmp(item->key, "Interval") == 0)
-      cna_config_get_interval(item, &cfg_snapvault->interval);
+      cf_util_get_cdtime(item, &cfg_snapvault->interval.interval);
     else
       WARNING("netapp plugin: The option %s is not allowed within "
               "`SnapVault' blocks.",
@@ -2712,15 +2676,15 @@ static int cna_config_system(host_config_t *host, /* {{{ */
     oconfig_item_t *item = ci->children + i;
 
     if (strcasecmp(item->key, "Interval") == 0) {
-      cna_config_get_interval(item, &cfg_system->interval);
+      cf_util_get_cdtime(item, &cfg_system->interval.interval);
     } else if (!strcasecmp(item->key, "GetCPULoad")) {
-      cna_config_bool_to_flag(item, &cfg_system->flags, CFG_SYSTEM_CPU);
+      cf_util_get_flag(item, &cfg_system->flags, CFG_SYSTEM_CPU);
     } else if (!strcasecmp(item->key, "GetInterfaces")) {
-      cna_config_bool_to_flag(item, &cfg_system->flags, CFG_SYSTEM_NET);
+      cf_util_get_flag(item, &cfg_system->flags, CFG_SYSTEM_NET);
     } else if (!strcasecmp(item->key, "GetDiskOps")) {
-      cna_config_bool_to_flag(item, &cfg_system->flags, CFG_SYSTEM_OPS);
+      cf_util_get_flag(item, &cfg_system->flags, CFG_SYSTEM_OPS);
     } else if (!strcasecmp(item->key, "GetDiskIO")) {
-      cna_config_bool_to_flag(item, &cfg_system->flags, CFG_SYSTEM_DISK);
+      cf_util_get_flag(item, &cfg_system->flags, CFG_SYSTEM_DISK);
     } else {
       WARNING("netapp plugin: The %s config option is not allowed within "
               "`System' blocks.",
index 84ba00b..d602dfc 100644 (file)
@@ -113,6 +113,7 @@ struct sockent_client {
 #endif
   cdtime_t next_resolve_reconnect;
   cdtime_t resolve_interval;
+  struct sockaddr_storage *bind_addr;
 };
 
 struct sockent_server {
@@ -1501,6 +1502,7 @@ static void free_sockent_client(struct sockent_client *sec) /* {{{ */
     sec->fd = -1;
   }
   sfree(sec->addr);
+  sfree(sec->bind_addr);
 #if HAVE_GCRYPT_H
   sfree(sec->username);
   sfree(sec->password);
@@ -1683,6 +1685,42 @@ static int network_set_interface(const sockent_t *se,
   return 0;
 } /* }}} network_set_interface */
 
+static int network_bind_socket_to_addr(sockent_t *se,
+                                       const struct addrinfo *ai) {
+
+  if (se->data.client.bind_addr == NULL)
+    return 0;
+
+  DEBUG("network_plugin: fd %i: bind socket to address", se->data.client.fd);
+  char pbuffer[64];
+
+  if (ai->ai_family == AF_INET) {
+    struct sockaddr_in *addr =
+        (struct sockaddr_in *)(se->data.client.bind_addr);
+    inet_ntop(AF_INET, &(addr->sin_addr), pbuffer, 64);
+    DEBUG("network_plugin: binding client socket to ipv4 address: %s", pbuffer);
+    if (bind(se->data.client.fd, (struct sockaddr *)addr, sizeof(*addr)) ==
+        -1) {
+      ERROR("network plugin: failed to bind client socket (ipv4) to %s: %s",
+            pbuffer, STRERRNO);
+      return -1;
+    }
+  } else if (ai->ai_family == AF_INET6) {
+    struct sockaddr_in6 *addr =
+        (struct sockaddr_in6 *)(se->data.client.bind_addr);
+    inet_ntop(AF_INET6, &(addr->sin6_addr), pbuffer, 64);
+    DEBUG("network_plugin: binding client socket to ipv6 address: %s", pbuffer);
+    if (bind(se->data.client.fd, (struct sockaddr *)addr, sizeof(*addr)) ==
+        -1) {
+      ERROR("network plugin: failed to bind client socket (ipv6) to %s: %s",
+            pbuffer, STRERRNO);
+      return -1;
+    }
+  }
+
+  return 0;
+} /* int network_bind_socket_to_addr */
+
 static int network_bind_socket(int fd, const struct addrinfo *ai,
                                const int interface_idx) {
 #if KERNEL_SOLARIS
@@ -1834,6 +1872,7 @@ static sockent_t *sockent_create(int type) /* {{{ */
   } else {
     se->data.client.fd = -1;
     se->data.client.addr = NULL;
+    se->data.client.bind_addr = NULL;
     se->data.client.resolve_interval = 0;
     se->data.client.next_resolve_reconnect = 0;
 #if HAVE_GCRYPT_H
@@ -1989,6 +2028,7 @@ static int sockent_client_connect(sockent_t *se) /* {{{ */
 
     network_set_ttl(se, ai_ptr);
     network_set_interface(se, ai_ptr);
+    network_bind_socket_to_addr(se, ai_ptr);
 
     /* We don't open more than one write-socket per
      * node/service pair.. */
@@ -2684,6 +2724,57 @@ static int network_config_set_interface(const oconfig_item_t *ci, /* {{{ */
   return 0;
 } /* }}} int network_config_set_interface */
 
+static int
+network_config_set_bind_address(const oconfig_item_t *ci,
+                                struct sockaddr_storage **bind_address) {
+  if ((*bind_address) != NULL) {
+    ERROR("network_plugin: only a single bind address is allowed");
+    return -1;
+  }
+
+  char addr_text[256];
+
+  if (cf_util_get_string_buffer(ci, addr_text, sizeof(addr_text)) != 0)
+    return -1;
+
+  int ret;
+  struct addrinfo *res = NULL;
+  struct addrinfo ai_hints = {.ai_family = AF_UNSPEC,
+                              .ai_flags = AI_NUMERICHOST,
+                              .ai_protocol = IPPROTO_UDP,
+                              .ai_socktype = SOCK_DGRAM};
+
+  ret = getaddrinfo(addr_text, NULL, &ai_hints, &res);
+  if (ret) {
+    ERROR("network plugin: Bind address option has invalid address set: %s",
+          gai_strerror(ret));
+    return -1;
+  }
+
+  *bind_address = malloc(sizeof(**bind_address));
+  if (*bind_address == NULL) {
+    ERROR("network plugin: network_config_set_bind_address: malloc failed.");
+    return -1;
+  }
+  (*bind_address)->ss_family = res->ai_family;
+  if (res->ai_family == AF_INET) {
+    struct sockaddr_in *addr = (struct sockaddr_in *)(*bind_address);
+    inet_pton(AF_INET, addr_text, &(addr->sin_addr));
+  } else if (res->ai_family == AF_INET6) {
+    struct sockaddr_in6 *addr = (struct sockaddr_in6 *)(*bind_address);
+    inet_pton(AF_INET6, addr_text, &(addr->sin6_addr));
+  } else {
+    ERROR("network plugin: %s is an unknown address format %d\n", addr_text,
+          res->ai_family);
+    sfree(*bind_address);
+    freeaddrinfo(res);
+    return -1;
+  }
+
+  freeaddrinfo(res);
+  return 0;
+} /* int network_config_set_bind_address */
+
 static int network_config_set_buffer_size(const oconfig_item_t *ci) /* {{{ */
 {
   int tmp = 0;
@@ -2843,6 +2934,8 @@ static int network_config_add_server(const oconfig_item_t *ci) /* {{{ */
 #endif /* HAVE_GCRYPT_H */
         if (strcasecmp("Interface", child->key) == 0)
       network_config_set_interface(child, &se->interface);
+    else if (strcasecmp("BindAddress", child->key) == 0)
+      network_config_set_bind_address(child, &se->data.client.bind_addr);
     else if (strcasecmp("ResolveInterval", child->key) == 0)
       cf_util_get_cdtime(child, &se->data.client.resolve_interval);
     else {
index e1987f1..481aa79 100644 (file)
--- a/src/nfs.c
+++ b/src/nfs.c
@@ -519,7 +519,8 @@ static int nfs_submit_nfs4_client(const char *instance, char **fields,
 static void nfs_read_linux(FILE *fh, const char *inst) {
   char buffer[1024];
 
-  char *fields[64];
+  // The stats line is prefixed with type and number of fields, thus plus 2
+  char *fields[MAX(NFS4_SERVER_MAX_PROC, NFS4_CLIENT_MAX_PROC) + 2];
   int fields_num = 0;
 
   if (fh == NULL)
index 6b32ad9..bb36ff2 100644 (file)
@@ -211,8 +211,8 @@ static int notify_email_notification(const notification_t *n,
   char subject[MAXSTRING];
 
   char buf[4096] = "";
+  char *buf_ptr = buf;
   int buf_len = sizeof(buf);
-  int i;
 
   snprintf(severity, sizeof(severity), "%s",
            (n->severity == NOTIF_FAILURE)
@@ -231,15 +231,36 @@ static int notify_email_notification(const notification_t *n,
   timestamp_str[sizeof(timestamp_str) - 1] = '\0';
 
   /* Let's make RFC822 message text with \r\n EOLs */
-  snprintf(buf, buf_len, "MIME-Version: 1.0\r\n"
-                         "Content-Type: text/plain; charset=\"US-ASCII\"\r\n"
-                         "Content-Transfer-Encoding: 8bit\r\n"
-                         "Subject: %s\r\n"
-                         "\r\n"
-                         "%s - %s@%s\r\n"
-                         "\r\n"
-                         "Message: %s",
-           subject, timestamp_str, severity, n->host, n->message);
+  int status = snprintf(buf, buf_len,
+                        "MIME-Version: 1.0\r\n"
+                        "Content-Type: text/plain; charset=\"US-ASCII\"\r\n"
+                        "Content-Transfer-Encoding: 8bit\r\n"
+                        "Subject: %s\r\n"
+                        "\r\n"
+                        "%s - %s@%s\r\n"
+                        "\r\n",
+                        subject, timestamp_str, severity, n->host);
+
+  if (status > 0) {
+    buf_ptr += status;
+    buf_len -= status;
+  }
+
+#define APPEND(format, value)                                                  \
+  if ((buf_len > 0) && (strlen(value) > 0)) {                                  \
+    status = snprintf(buf_ptr, buf_len, format "\r\n", value);                 \
+    if (status > 0) {                                                          \
+      buf_ptr += status;                                                       \
+      buf_len -= status;                                                       \
+    }                                                                          \
+  }
+
+  APPEND("Host: %s", n->host);
+  APPEND("Plugin: %s", n->plugin);
+  APPEND("Plugin instance: %s", n->plugin_instance);
+  APPEND("Type: %s", n->type);
+  APPEND("Type instance: %s", n->type_instance);
+  APPEND("\r\nMessage: %s", n->message);
 
   pthread_mutex_lock(&session_lock);
 
@@ -258,7 +279,7 @@ static int notify_email_notification(const notification_t *n,
   smtp_set_header(message, "To", NULL, NULL);
   smtp_set_message_str(message, buf);
 
-  for (i = 0; i < recipients_len; i++)
+  for (int i = 0; i < recipients_len; i++)
     smtp_add_recipient(message, recipients[i]);
 
   /* Initiate a connection to the SMTP server and transfer the message. */
index baa1988..ef63498 100644 (file)
@@ -251,7 +251,7 @@ static const char *refclock_names[] = {
     "CHRONOLOG",  "DUMBCLOCK",    "ULINK_M320", "PCF",         /* 32-35 */
     "WWV_AUDIO",  "GPS_FG",       "HOPF_S",     "HOPF_P",      /* 36-39 */
     "JJY",        "TT_IRIG",      "GPS_ZYFER",  "GPS_RIPENCC", /* 40-43 */
-    "NEOCLK4X"                                                 /* 44    */
+    "NEOCLK4X",   "PCI_TSYNC",    "GPSD_JSON"                  /* 44-46 */
 };
 static size_t refclock_names_num = STATIC_ARRAY_SIZE(refclock_names);
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@@ -779,17 +779,6 @@ static int ntpd_get_name_refclock(char *buffer, size_t buffer_size,
   return 0;
 } /* int ntpd_get_name_refclock */
 
-static int ntpd_get_name(char *buffer, size_t buffer_size,
-                         struct info_peer_summary const *peer_info) {
-  uint32_t addr = ntohl(peer_info->srcadr);
-
-  if (!peer_info->v6_flag && ((addr & REFCLOCK_MASK) == REFCLOCK_ADDR))
-    return ntpd_get_name_refclock(buffer, buffer_size, peer_info);
-  else
-    return ntpd_get_name_from_address(buffer, buffer_size, peer_info,
-                                      do_reverse_lookups);
-} /* int ntpd_addr_to_name */
-
 static int ntpd_read(void) {
   struct info_kernel *ik;
   int ik_num;
@@ -877,7 +866,15 @@ static int ntpd_read(void) {
 
     ptr = ps + i;
 
-    status = ntpd_get_name(peername, sizeof(peername), ptr);
+    int is_refclock = !ptr->v6_flag &&
+                      ((ntohl(ptr->srcadr) & REFCLOCK_MASK) == REFCLOCK_ADDR);
+
+    if (is_refclock)
+      status = ntpd_get_name_refclock(peername, sizeof(peername), ptr);
+    else
+      status = ntpd_get_name_from_address(peername, sizeof(peername), ptr,
+                                          do_reverse_lookups);
+
     if (status != 0) {
       ERROR("ntpd plugin: Determining name of peer failed.");
       continue;
@@ -895,6 +892,8 @@ static int ntpd_read(void) {
     M_LFPTOD(ntohl(ptr->offset_int), ntohl(ptr->offset_frc), offset);
 
     DEBUG("peer %i:\n"
+          "  is_refclock= %d\n"
+          "  refclock_id= %d\n"
           "  peername   = %s\n"
           "  srcadr     = 0x%08x\n"
           "  reach      = 0%03o\n"
@@ -903,16 +902,19 @@ static int ntpd_read(void) {
           "  offset_frc = %i\n"
           "  offset     = %f\n"
           "  dispersion = %f\n",
-          i, peername, ntohl(ptr->srcadr), ptr->reach, ntpd_read_fp(ptr->delay),
+          i, is_refclock, (is_refclock > 0) ? refclock_id : 0, peername,
+          ntohl(ptr->srcadr), ptr->reach, ntpd_read_fp(ptr->delay),
           ntohl(ptr->offset_int), ntohl(ptr->offset_frc), offset,
           ntpd_read_fp(ptr->dispersion));
 
-    if (refclock_id !=
-        1) /* not the system clock (offset will always be zero.. */
-      ntpd_submit_reach("time_offset", peername, ptr->reach, offset);
     ntpd_submit_reach("time_dispersion", peername, ptr->reach,
                       ntpd_read_fp(ptr->dispersion));
-    if (refclock_id == 0) /* not a reference clock */
+
+    /* not the system clock (offset will always be zero) */
+    if (!(is_refclock && refclock_id == 1))
+      ntpd_submit_reach("time_offset", peername, ptr->reach, offset);
+
+    if (!is_refclock) /* not a reference clock */
       ntpd_submit_reach("delay", peername, ptr->reach,
                         ntpd_read_fp(ptr->delay));
   }
index 4bca838..1b17d9c 100644 (file)
@@ -550,8 +550,7 @@ 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 = */ (db->plugin_name != NULL) ? db->plugin_name : "oracle",
-      db->name, column_names, column_num,
-      /* interval = */ 0);
+      db->name, column_names, column_num);
   if (status != 0) {
     ERROR("oracle plugin: o_read_database_query (%s, %s): "
           "udb_query_prepare_result failed.",
diff --git a/src/pcie_errors.c b/src/pcie_errors.c
new file mode 100644 (file)
index 0000000..b239a8c
--- /dev/null
@@ -0,0 +1,795 @@
+/**
+ * collectd - src/pcie_errors.c
+ *
+ * Copyright(c) 2018 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:
+ *   Kamil Wiatrowski <kamilx.wiatrowski@intel.com>
+ **/
+
+#include "collectd.h"
+
+#include "common.h"
+#include "utils_llist.h"
+
+#include <linux/pci_regs.h>
+
+#define PCIE_ERRORS_PLUGIN "pcie_errors"
+#define PCIE_DEFAULT_PROCDIR "/proc/bus/pci"
+#define PCIE_DEFAULT_SYSFSDIR "/sys/bus/pci"
+#define PCIE_NAME_LEN 512
+#define PCIE_BUFF_SIZE 1024
+
+#define PCIE_ERROR "pcie_error"
+#define PCIE_SEV_CE "correctable"
+#define PCIE_SEV_FATAL "fatal"
+#define PCIE_SEV_NOFATAL "non_fatal"
+
+#define PCIE_DEV(x) (((x) >> 3) & 0x1f)
+#define PCIE_FN(x) ((x)&0x07)
+
+#define PCIE_ECAP_OFFSET 0x100 /* ECAP always begin at offset 0x100 */
+
+typedef struct pcie_config_s {
+  bool use_sysfs;
+  bool notif_masked;
+  bool persistent;
+  char access_dir[PATH_MAX];
+} pcie_config_t;
+
+typedef struct pcie_device_s {
+  int fd;
+  int domain;
+  uint8_t bus;
+  uint8_t device;
+  uint8_t function;
+  int cap_exp;
+  int ecap_aer;
+  uint16_t device_status;
+  uint32_t correctable_errors;
+  uint32_t uncorrectable_errors;
+} pcie_device_t;
+
+typedef struct pcie_fops_s {
+  int (*list_devices)(llist_t *dev_list);
+  int (*open)(pcie_device_t *dev);
+  void (*close)(pcie_device_t *dev);
+  int (*read)(pcie_device_t *dev, void *buff, int size, int pos);
+} pcie_fops_t;
+
+typedef struct pcie_error_s {
+  int mask;
+  const char *desc;
+} pcie_error_t;
+
+static llist_t *pcie_dev_list;
+static pcie_config_t pcie_config = {.access_dir = "", .use_sysfs = true};
+static pcie_fops_t pcie_fops;
+
+/* Device Error Status */
+static const pcie_error_t pcie_base_errors[] = {
+    {PCI_EXP_DEVSTA_CED, "Correctable Error"},
+    {PCI_EXP_DEVSTA_NFED, "Non-Fatal Error"},
+    {PCI_EXP_DEVSTA_FED, "Fatal Error"},
+    {PCI_EXP_DEVSTA_URD, "Unsupported Request"}};
+static const int pcie_base_errors_num = STATIC_ARRAY_SIZE(pcie_base_errors);
+
+/* Uncorrectable Error Status */
+static const pcie_error_t pcie_aer_ues[] = {
+#ifdef PCI_ERR_UNC_DLP
+    {PCI_ERR_UNC_DLP, "Data Link Protocol"},
+#endif
+#ifdef PCI_ERR_UNC_SURPDN
+    {PCI_ERR_UNC_SURPDN, "Surprise Down"},
+#endif
+#ifdef PCI_ERR_UNC_POISON_TLP
+    {PCI_ERR_UNC_POISON_TLP, "Poisoned TLP"},
+#endif
+#ifdef PCI_ERR_UNC_FCP
+    {PCI_ERR_UNC_FCP, "Flow Control Protocol"},
+#endif
+#ifdef PCI_ERR_UNC_COMP_TIME
+    {PCI_ERR_UNC_COMP_TIME, "Completion Timeout"},
+#endif
+#ifdef PCI_ERR_UNC_COMP_ABORT
+    {PCI_ERR_UNC_COMP_ABORT, "Completer Abort"},
+#endif
+#ifdef PCI_ERR_UNC_UNX_COMP
+    {PCI_ERR_UNC_UNX_COMP, "Unexpected Completion"},
+#endif
+#ifdef PCI_ERR_UNC_RX_OVER
+    {PCI_ERR_UNC_RX_OVER, "Receiver Overflow"},
+#endif
+#ifdef PCI_ERR_UNC_MALF_TLP
+    {PCI_ERR_UNC_MALF_TLP, "Malformed TLP"},
+#endif
+#ifdef PCI_ERR_UNC_ECRC
+    {PCI_ERR_UNC_ECRC, "ECRC Error Status"},
+#endif
+#ifdef PCI_ERR_UNC_UNSUP
+    {PCI_ERR_UNC_UNSUP, "Unsupported Request"},
+#endif
+#ifdef PCI_ERR_UNC_ACSV
+    {PCI_ERR_UNC_ACSV, "ACS Violation"},
+#endif
+#ifdef PCI_ERR_UNC_INTN
+    {PCI_ERR_UNC_INTN, "Internal"},
+#endif
+#ifdef PCI_ERR_UNC_MCBTLP
+    {PCI_ERR_UNC_MCBTLP, "MC blocked TLP"},
+#endif
+#ifdef PCI_ERR_UNC_ATOMEG
+    {PCI_ERR_UNC_ATOMEG, "Atomic egress blocked"},
+#endif
+#ifdef PCI_ERR_UNC_TLPPRE
+    {PCI_ERR_UNC_TLPPRE, "TLP prefix blocked"},
+#endif
+};
+static const int pcie_aer_ues_num = STATIC_ARRAY_SIZE(pcie_aer_ues);
+
+/* Correctable Error Status */
+static const pcie_error_t pcie_aer_ces[] = {
+#ifdef PCI_ERR_COR_RCVR
+    {PCI_ERR_COR_RCVR, "Receiver Error Status"},
+#endif
+#ifdef PCI_ERR_COR_BAD_TLP
+    {PCI_ERR_COR_BAD_TLP, "Bad TLP Status"},
+#endif
+#ifdef PCI_ERR_COR_BAD_DLLP
+    {PCI_ERR_COR_BAD_DLLP, "Bad DLLP Status"},
+#endif
+#ifdef PCI_ERR_COR_REP_ROLL
+    {PCI_ERR_COR_REP_ROLL, "REPLAY_NUM Rollover"},
+#endif
+#ifdef PCI_ERR_COR_REP_TIMER
+    {PCI_ERR_COR_REP_TIMER, "Replay Timer Timeout"},
+#endif
+#ifdef PCI_ERR_COR_ADV_NFAT
+    {PCI_ERR_COR_ADV_NFAT, "Advisory Non-Fatal"},
+#endif
+#ifdef PCI_ERR_COR_INTERNAL
+    {PCI_ERR_COR_INTERNAL, "Corrected Internal"},
+#endif
+#ifdef PCI_ERR_COR_LOG_OVER
+    {PCI_ERR_COR_LOG_OVER, "Header Log Overflow"},
+#endif
+};
+static const int pcie_aer_ces_num = STATIC_ARRAY_SIZE(pcie_aer_ces);
+
+static int pcie_add_device(llist_t *list, int domain, uint8_t bus,
+                           uint8_t device, uint8_t fn) {
+  llentry_t *entry;
+  pcie_device_t *dev = calloc(1, sizeof(*dev));
+  if (dev == NULL) {
+    ERROR(PCIE_ERRORS_PLUGIN ": Failed to allocate device");
+    return -ENOMEM;
+  }
+
+  dev->domain = domain;
+  dev->bus = bus;
+  dev->device = device;
+  dev->function = fn;
+  dev->cap_exp = -1;
+  dev->ecap_aer = -1;
+  entry = llentry_create(NULL, dev);
+  if (entry == NULL) {
+    ERROR(PCIE_ERRORS_PLUGIN ": Failed to create llentry");
+    sfree(dev);
+    return -ENOMEM;
+  }
+  llist_append(list, entry);
+
+  DEBUG(PCIE_ERRORS_PLUGIN ": pci device added to list: %04x:%02x:%02x.%d",
+        domain, bus, device, fn);
+  return 0;
+}
+
+static void pcie_clear_list(llist_t *list) {
+  if (list == NULL)
+    return;
+
+  for (llentry_t *e = llist_head(list); e != NULL; e = e->next)
+    sfree(e->value);
+
+  llist_destroy(list);
+}
+
+static int pcie_list_devices_proc(llist_t *dev_list) {
+  FILE *fd;
+  char file_name[PCIE_NAME_LEN];
+  char buf[PCIE_BUFF_SIZE];
+  unsigned int i = 0;
+  int ret = 0;
+
+  if (dev_list == NULL)
+    return -EINVAL;
+
+  ret = snprintf(file_name, sizeof(file_name), "%s/devices",
+                 pcie_config.access_dir);
+  if (ret < 1 || (size_t)ret >= sizeof(file_name)) {
+    ERROR(PCIE_ERRORS_PLUGIN ": Access dir `%s' is too long (%d)",
+          pcie_config.access_dir, ret);
+    return -EINVAL;
+  }
+  fd = fopen(file_name, "r");
+  if (!fd) {
+    char errbuf[PCIE_BUFF_SIZE];
+    ERROR(PCIE_ERRORS_PLUGIN ": Cannot open file %s to get devices list: %s",
+          file_name, sstrerror(errno, errbuf, sizeof(errbuf)));
+    return -ENOENT;
+  }
+
+  while (fgets(buf, sizeof(buf), fd)) {
+    unsigned int slot;
+
+    if (sscanf(buf, "%x", &slot) != 1) {
+      ERROR(PCIE_ERRORS_PLUGIN ": Failed to read line %u from %s", i + 1,
+            file_name);
+      continue;
+    }
+
+    uint8_t bus = slot >> 8U;
+    uint8_t dev = PCIE_DEV(slot);
+    uint8_t fn = PCIE_FN(slot);
+    ret = pcie_add_device(dev_list, 0, bus, dev, fn);
+    if (ret)
+      break;
+
+    ++i;
+  }
+
+  fclose(fd);
+  return ret;
+}
+
+static int pcie_list_devices_sysfs(llist_t *dev_list) {
+  DIR *dir;
+  struct dirent *item;
+  char dir_name[PCIE_NAME_LEN];
+  int ret = 0;
+
+  if (dev_list == NULL)
+    return -EINVAL;
+
+  ret = snprintf(dir_name, sizeof(dir_name), "%s/devices",
+                 pcie_config.access_dir);
+  if (ret < 1 || (size_t)ret >= sizeof(dir_name)) {
+    ERROR(PCIE_ERRORS_PLUGIN ": Access dir `%s' is too long (%d)",
+          pcie_config.access_dir, ret);
+    return -EINVAL;
+  }
+  dir = opendir(dir_name);
+  if (!dir) {
+    char errbuf[PCIE_BUFF_SIZE];
+    ERROR(PCIE_ERRORS_PLUGIN ": Cannot open dir %s to get devices list: %s",
+          dir_name, sstrerror(errno, errbuf, sizeof(errbuf)));
+    return -ENOENT;
+  }
+
+  while ((item = readdir(dir))) {
+    unsigned int dom, bus, dev;
+    int fn;
+
+    /* Omit special non-device entries */
+    if (item->d_name[0] == '.')
+      continue;
+
+    if (sscanf(item->d_name, "%x:%x:%x.%d", &dom, &bus, &dev, &fn) != 4) {
+      ERROR(PCIE_ERRORS_PLUGIN ": Failed to parse entry %s", item->d_name);
+      continue;
+    }
+
+    ret = pcie_add_device(dev_list, dom, bus, dev, fn);
+    if (ret)
+      break;
+  }
+
+  closedir(dir);
+  return ret;
+}
+
+static void pcie_close(pcie_device_t *dev) {
+  if (close(dev->fd) == -1) {
+    char errbuf[PCIE_BUFF_SIZE];
+    ERROR(PCIE_ERRORS_PLUGIN ": Failed to close %04x:%02x:%02x.%d, fd=%d: %s",
+          dev->domain, dev->bus, dev->device, dev->function, dev->fd,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+  }
+
+  dev->fd = -1;
+}
+
+static int pcie_open(pcie_device_t *dev, const char *name) {
+  dev->fd = open(name, O_RDONLY);
+  if (dev->fd == -1) {
+    char errbuf[PCIE_BUFF_SIZE];
+    ERROR(PCIE_ERRORS_PLUGIN ": Failed to open file %s: %s", name,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+    return -ENOENT;
+  }
+
+  return 0;
+}
+
+static int pcie_open_proc(pcie_device_t *dev) {
+  char file_name[PCIE_NAME_LEN];
+
+  int ret =
+      snprintf(file_name, sizeof(file_name), "%s/%02x/%02x.%d",
+               pcie_config.access_dir, dev->bus, dev->device, dev->function);
+  if (ret < 1 || (size_t)ret >= sizeof(file_name)) {
+    ERROR(PCIE_ERRORS_PLUGIN ": Access dir `%s' is too long (%d)",
+          pcie_config.access_dir, ret);
+    return -EINVAL;
+  }
+
+  return pcie_open(dev, file_name);
+}
+
+static int pcie_open_sysfs(pcie_device_t *dev) {
+  char file_name[PCIE_NAME_LEN];
+
+  int ret =
+      snprintf(file_name, sizeof(file_name),
+               "%s/devices/%04x:%02x:%02x.%d/config", pcie_config.access_dir,
+               dev->domain, dev->bus, dev->device, dev->function);
+  if (ret < 1 || (size_t)ret >= sizeof(file_name)) {
+    ERROR(PCIE_ERRORS_PLUGIN ": Access dir `%s' is too long (%d)",
+          pcie_config.access_dir, ret);
+    return -EINVAL;
+  }
+
+  return pcie_open(dev, file_name);
+}
+
+static int pcie_read(pcie_device_t *dev, void *buff, int size, int pos) {
+  int len = pread(dev->fd, buff, size, pos);
+  if (len == size)
+    return 0;
+
+  if (len == -1) {
+    char errbuf[PCIE_BUFF_SIZE];
+    ERROR(PCIE_ERRORS_PLUGIN ": Failed to read %04x:%02x:%02x.%d at pos %d: %s",
+          dev->domain, dev->bus, dev->device, dev->function, pos,
+          sstrerror(errno, errbuf, sizeof(errbuf)));
+  } else {
+    ERROR(PCIE_ERRORS_PLUGIN
+          ": %04x:%02x:%02x.%d Read only %d bytes, should be %d",
+          dev->domain, dev->bus, dev->device, dev->function, len, size);
+  }
+  return -1;
+}
+
+static uint8_t pcie_read8(pcie_device_t *dev, int pos) {
+  uint8_t value;
+  if (pcie_fops.read(dev, &value, 1, pos))
+    return 0;
+  return value;
+}
+
+static uint16_t pcie_read16(pcie_device_t *dev, int pos) {
+  uint16_t value;
+  if (pcie_fops.read(dev, &value, 2, pos))
+    return 0;
+  return value;
+}
+
+static uint32_t pcie_read32(pcie_device_t *dev, int pos) {
+  uint32_t value;
+  if (pcie_fops.read(dev, &value, 4, pos))
+    return 0;
+  return value;
+}
+
+static void pcie_dispatch_notification(pcie_device_t *dev, notification_t *n,
+                                       const char *type,
+                                       const char *type_instance) {
+  sstrncpy(n->host, hostname_g, sizeof(n->host));
+  snprintf(n->plugin_instance, sizeof(n->plugin_instance), "%04x:%02x:%02x.%d",
+           dev->domain, dev->bus, dev->device, dev->function);
+  sstrncpy(n->type, type, sizeof(n->type));
+  sstrncpy(n->type_instance, type_instance, sizeof(n->type_instance));
+
+  plugin_dispatch_notification(n);
+}
+
+/* Report errors found in AER Correctable Error Status register */
+static void pcie_dispatch_correctable_errors(pcie_device_t *dev,
+                                             uint32_t errors, uint32_t masked) {
+  for (int i = 0; i < pcie_aer_ces_num; i++) {
+    const pcie_error_t *err = pcie_aer_ces + i;
+    notification_t n = {.severity = NOTIF_WARNING,
+                        .time = cdtime(),
+                        .plugin = PCIE_ERRORS_PLUGIN,
+                        .meta = NULL};
+
+    /* If not specifically set by config option omit masked errors */
+    if (!pcie_config.notif_masked && (err->mask & masked))
+      continue;
+
+    if (err->mask & errors) {
+      /* Error already reported, notify only if persistent is set */
+      if (!pcie_config.persistent && (err->mask & dev->correctable_errors))
+        continue;
+
+      DEBUG(PCIE_ERRORS_PLUGIN ": %04x:%02x:%02x.%d: %s set", dev->domain,
+            dev->bus, dev->device, dev->function, err->desc);
+      snprintf(n.message, sizeof(n.message), "Correctable Error set: %s",
+               err->desc);
+      pcie_dispatch_notification(dev, &n, PCIE_ERROR, PCIE_SEV_CE);
+
+    } else if (err->mask & dev->correctable_errors) {
+      DEBUG(PCIE_ERRORS_PLUGIN ": %04x:%02x:%02x.%d: %s cleared", dev->domain,
+            dev->bus, dev->device, dev->function, err->desc);
+
+      n.severity = NOTIF_OKAY;
+      snprintf(n.message, sizeof(n.message), "Correctable Error cleared: %s",
+               err->desc);
+      pcie_dispatch_notification(dev, &n, PCIE_ERROR, PCIE_SEV_CE);
+    }
+  }
+}
+
+/* Report errors found in AER Uncorrectable Error Status register */
+static void pcie_dispatch_uncorrectable_errors(pcie_device_t *dev,
+                                               uint32_t errors, uint32_t masked,
+                                               uint32_t severity) {
+  for (int i = 0; i < pcie_aer_ues_num; i++) {
+    const pcie_error_t *err = pcie_aer_ues + i;
+    const char *type_instance =
+        (severity & err->mask) ? PCIE_SEV_FATAL : PCIE_SEV_NOFATAL;
+    notification_t n = {
+        .time = cdtime(), .plugin = PCIE_ERRORS_PLUGIN, .meta = NULL};
+
+    /* If not specifically set by config option omit masked errors */
+    if (!pcie_config.notif_masked && (err->mask & masked))
+      continue;
+
+    if (err->mask & errors) {
+      /* Error already reported, notify only if persistent is set */
+      if (!pcie_config.persistent && (err->mask & dev->uncorrectable_errors))
+        continue;
+
+      DEBUG(PCIE_ERRORS_PLUGIN ": %04x:%02x:%02x.%d: %s(%s) set", dev->domain,
+            dev->bus, dev->device, dev->function, err->desc, type_instance);
+
+      n.severity = (severity & err->mask) ? NOTIF_FAILURE : NOTIF_WARNING;
+      snprintf(n.message, sizeof(n.message), "Uncorrectable(%s) Error set: %s",
+               type_instance, err->desc);
+      pcie_dispatch_notification(dev, &n, PCIE_ERROR, type_instance);
+
+    } else if (err->mask & dev->uncorrectable_errors) {
+      DEBUG(PCIE_ERRORS_PLUGIN ": %04x:%02x:%02x.%d: %s(%s) cleared",
+            dev->domain, dev->bus, dev->device, dev->function, err->desc,
+            type_instance);
+
+      n.severity = NOTIF_OKAY;
+      snprintf(n.message, sizeof(n.message),
+               "Uncorrectable(%s) Error cleared: %s", type_instance, err->desc);
+      pcie_dispatch_notification(dev, &n, PCIE_ERROR, type_instance);
+    }
+  }
+}
+
+/* Find offset of PCI Express Capability Structure
+ * in PCI configuration space.
+ * Returns offset, -1 if not found.
+**/
+static int pcie_find_cap_exp(pcie_device_t *dev) {
+  int pos = pcie_read8(dev, PCI_CAPABILITY_LIST) & ~3;
+
+  while (pos) {
+    uint8_t id = pcie_read8(dev, pos + PCI_CAP_LIST_ID);
+
+    if (id == 0xff)
+      break;
+    if (id == PCI_CAP_ID_EXP)
+      return pos;
+
+    pos = pcie_read8(dev, pos + PCI_CAP_LIST_NEXT) & ~3;
+  }
+
+  DEBUG(PCIE_ERRORS_PLUGIN ": Cannot find CAP EXP for %04x:%02x:%02x.%d",
+        dev->domain, dev->bus, dev->device, dev->function);
+
+  return -1;
+}
+
+/* Find offset of Advanced Error Reporting Capability.
+ * Returns AER offset, -1 if not found.
+**/
+static int pcie_find_ecap_aer(pcie_device_t *dev) {
+  int pos = PCIE_ECAP_OFFSET;
+  uint32_t header = pcie_read32(dev, pos);
+  int id = PCI_EXT_CAP_ID(header);
+  int next = PCI_EXT_CAP_NEXT(header);
+
+  if (!id && !next)
+    return -1;
+
+  if (id == PCI_EXT_CAP_ID_ERR)
+    return pos;
+
+  while (next) {
+    if (next <= PCIE_ECAP_OFFSET)
+      break;
+
+    header = pcie_read32(dev, next);
+    id = PCI_EXT_CAP_ID(header);
+
+    if (id == PCI_EXT_CAP_ID_ERR)
+      return next;
+
+    next = PCI_EXT_CAP_NEXT(header);
+  }
+
+  return -1;
+}
+
+static void pcie_check_dev_status(pcie_device_t *dev, int pos) {
+  /* Read Device Status register with mask for errors only */
+  uint16_t new_status = pcie_read16(dev, pos + PCI_EXP_DEVSTA) & 0xf;
+
+  /* Check if anything new should be reported */
+  if (!(pcie_config.persistent && new_status) &&
+      (new_status == dev->device_status))
+    return;
+
+  /* Report errors found in Device Status register */
+  for (int i = 0; i < pcie_base_errors_num; i++) {
+    const pcie_error_t *err = pcie_base_errors + i;
+    const char *type_instance = (err->mask == PCI_EXP_DEVSTA_FED)
+                                    ? PCIE_SEV_FATAL
+                                    : (err->mask == PCI_EXP_DEVSTA_CED)
+                                          ? PCIE_SEV_CE
+                                          : PCIE_SEV_NOFATAL;
+    int severity =
+        (err->mask == PCI_EXP_DEVSTA_FED) ? NOTIF_FAILURE : NOTIF_WARNING;
+    notification_t n = {.severity = severity,
+                        .time = cdtime(),
+                        .plugin = PCIE_ERRORS_PLUGIN,
+                        .meta = NULL};
+
+    if (err->mask & new_status) {
+      /* Error already reported, notify only if persistent is set */
+      if (!pcie_config.persistent && (err->mask & dev->device_status))
+        continue;
+
+      DEBUG(PCIE_ERRORS_PLUGIN ": %04x:%02x:%02x.%d: %s set", dev->domain,
+            dev->bus, dev->device, dev->function, err->desc);
+      snprintf(n.message, sizeof(n.message), "Device Status Error set: %s",
+               err->desc);
+      pcie_dispatch_notification(dev, &n, PCIE_ERROR, type_instance);
+
+    } else if (err->mask & dev->device_status) {
+      DEBUG(PCIE_ERRORS_PLUGIN ": %04x:%02x:%02x.%d: %s cleared", dev->domain,
+            dev->bus, dev->device, dev->function, err->desc);
+      n.severity = NOTIF_OKAY;
+      snprintf(n.message, sizeof(n.message), "Device Status Error cleared: %s",
+               err->desc);
+      pcie_dispatch_notification(dev, &n, PCIE_ERROR, type_instance);
+    }
+  }
+
+  dev->device_status = new_status;
+}
+
+static void pcie_check_aer(pcie_device_t *dev, int pos) {
+  /* Check for AER uncorrectable errors */
+  uint32_t errors = pcie_read32(dev, pos + PCI_ERR_UNCOR_STATUS);
+
+  if ((pcie_config.persistent && errors) ||
+      (errors != dev->uncorrectable_errors)) {
+    uint32_t masked = pcie_read32(dev, pos + PCI_ERR_UNCOR_MASK);
+    uint32_t severity = pcie_read32(dev, pos + PCI_ERR_UNCOR_SEVER);
+    pcie_dispatch_uncorrectable_errors(dev, errors, masked, severity);
+  }
+  dev->uncorrectable_errors = errors;
+
+  /* Check for AER correctable errors */
+  errors = pcie_read32(dev, pos + PCI_ERR_COR_STATUS);
+  if ((pcie_config.persistent && errors) ||
+      (errors != dev->correctable_errors)) {
+    uint32_t masked = pcie_read32(dev, pos + PCI_ERR_COR_MASK);
+    pcie_dispatch_correctable_errors(dev, errors, masked);
+  }
+  dev->correctable_errors = errors;
+}
+
+static int pcie_process_devices(llist_t *devs) {
+  int ret = 0;
+  if (devs == NULL)
+    return -1;
+
+  for (llentry_t *e = llist_head(devs); e != NULL; e = e->next) {
+    pcie_device_t *dev = e->value;
+
+    if (pcie_fops.open(dev) == 0) {
+      pcie_check_dev_status(dev, dev->cap_exp);
+      if (dev->ecap_aer != -1)
+        pcie_check_aer(dev, dev->ecap_aer);
+
+      pcie_fops.close(dev);
+    } else {
+      notification_t n = {.severity = NOTIF_FAILURE,
+                          .time = cdtime(),
+                          .message = "Failed to read device status",
+                          .plugin = PCIE_ERRORS_PLUGIN,
+                          .meta = NULL};
+      pcie_dispatch_notification(dev, &n, "", "");
+      ret = -1;
+    }
+  }
+
+  return ret;
+}
+
+/* This function is to be called during init to filter out no pcie devices */
+static void pcie_preprocess_devices(llist_t *devs) {
+  llentry_t *e_next;
+
+  if (devs == NULL)
+    return;
+
+  for (llentry_t *e = llist_head(devs); e != NULL; e = e_next) {
+    pcie_device_t *dev = e->value;
+    bool del = false;
+
+    if (pcie_fops.open(dev) == 0) {
+      uint16_t status = pcie_read16(dev, PCI_STATUS);
+      if (status & PCI_STATUS_CAP_LIST)
+        dev->cap_exp = pcie_find_cap_exp(dev);
+
+      /* Every PCIe device must have Capability Structure */
+      if (dev->cap_exp == -1) {
+        DEBUG(PCIE_ERRORS_PLUGIN ": Not PCI Express device: %04x:%02x:%02x.%d",
+              dev->domain, dev->bus, dev->device, dev->function);
+        del = true;
+      } else {
+        dev->ecap_aer = pcie_find_ecap_aer(dev);
+        if (dev->ecap_aer == -1)
+          INFO(PCIE_ERRORS_PLUGIN
+               ": Device is not AER capable: %04x:%02x:%02x.%d",
+               dev->domain, dev->bus, dev->device, dev->function);
+      }
+
+      pcie_fops.close(dev);
+    } else {
+      ERROR(PCIE_ERRORS_PLUGIN ": %04x:%02x:%02x.%d: failed to open",
+            dev->domain, dev->bus, dev->device, dev->function);
+      del = true;
+    }
+
+    e_next = e->next;
+    if (del) {
+      sfree(dev);
+      llist_remove(devs, e);
+      llentry_destroy(e);
+    }
+  }
+}
+
+static int pcie_plugin_read(__attribute__((unused)) user_data_t *ud) {
+
+  if (pcie_process_devices(pcie_dev_list) < 0) {
+    ERROR(PCIE_ERRORS_PLUGIN ": Failed to read devices state");
+    return -1;
+  }
+  return 0;
+}
+
+static void pcie_access_config(void) {
+  /* Set functions for register access to
+   * use proc or sysfs depending on config. */
+  if (pcie_config.use_sysfs) {
+    pcie_fops.list_devices = pcie_list_devices_sysfs;
+    pcie_fops.open = pcie_open_sysfs;
+    if (pcie_config.access_dir[0] == '\0')
+      sstrncpy(pcie_config.access_dir, PCIE_DEFAULT_SYSFSDIR,
+               sizeof(pcie_config.access_dir));
+  } else {
+    /* use proc */
+    pcie_fops.list_devices = pcie_list_devices_proc;
+    pcie_fops.open = pcie_open_proc;
+    if (pcie_config.access_dir[0] == '\0')
+      sstrncpy(pcie_config.access_dir, PCIE_DEFAULT_PROCDIR,
+               sizeof(pcie_config.access_dir));
+  }
+  /* Common functions */
+  pcie_fops.close = pcie_close;
+  pcie_fops.read = pcie_read;
+}
+
+static int pcie_plugin_config(oconfig_item_t *ci) {
+  int status = 0;
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Source", child->key) == 0) {
+      if ((child->values_num != 1) ||
+          (child->values[0].type != OCONFIG_TYPE_STRING)) {
+        status = -1;
+      } else if (strcasecmp("proc", child->values[0].value.string) == 0) {
+        pcie_config.use_sysfs = false;
+      } else if (strcasecmp("sysfs", child->values[0].value.string) != 0) {
+        ERROR(PCIE_ERRORS_PLUGIN ": Allowed sources are 'proc' or 'sysfs'.");
+        status = -1;
+      }
+    } else if (strcasecmp("AccessDir", child->key) == 0) {
+      status = cf_util_get_string_buffer(child, pcie_config.access_dir,
+                                         sizeof(pcie_config.access_dir));
+    } else if (strcasecmp("ReportMasked", child->key) == 0) {
+      status = cf_util_get_boolean(child, &pcie_config.notif_masked);
+    } else if (strcasecmp("PersistentNotifications", child->key) == 0) {
+      status = cf_util_get_boolean(child, &pcie_config.persistent);
+    } else {
+      ERROR(PCIE_ERRORS_PLUGIN ": Invalid configuration option \"%s\".",
+            child->key);
+      status = -1;
+      break;
+    }
+
+    if (status) {
+      ERROR(PCIE_ERRORS_PLUGIN ": Invalid configuration parameter \"%s\".",
+            child->key);
+      break;
+    }
+  }
+
+  return status;
+}
+
+static int pcie_shutdown(void) {
+  pcie_clear_list(pcie_dev_list);
+  pcie_dev_list = NULL;
+
+  return 0;
+}
+
+static int pcie_init(void) {
+
+  pcie_access_config();
+  pcie_dev_list = llist_create();
+  if (pcie_fops.list_devices(pcie_dev_list) != 0) {
+    ERROR(PCIE_ERRORS_PLUGIN ": Failed to find devices.");
+    pcie_shutdown();
+    return -1;
+  }
+  pcie_preprocess_devices(pcie_dev_list);
+  if (llist_size(pcie_dev_list) == 0) {
+    /* No any PCI Express devices were found on the system */
+    ERROR(PCIE_ERRORS_PLUGIN ": No PCIe devices found in %s",
+          pcie_config.access_dir);
+    pcie_shutdown();
+    return -1;
+  }
+
+  return 0;
+}
+
+void module_register(void) {
+  plugin_register_init(PCIE_ERRORS_PLUGIN, pcie_init);
+  plugin_register_complex_config(PCIE_ERRORS_PLUGIN, pcie_plugin_config);
+  plugin_register_complex_read(NULL, PCIE_ERRORS_PLUGIN, pcie_plugin_read, 0,
+                               NULL);
+  plugin_register_shutdown(PCIE_ERRORS_PLUGIN, pcie_shutdown);
+}
diff --git a/src/pcie_errors_test.c b/src/pcie_errors_test.c
new file mode 100644 (file)
index 0000000..5cb95fa
--- /dev/null
@@ -0,0 +1,570 @@
+/**
+ * collectd - src/pcie_errors.c
+ *
+ * Copyright(c) 2018 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:
+ *   Kamil Wiatrowski <kamilx.wiatrowski@intel.com>
+ **/
+
+#define plugin_dispatch_notification plugin_dispatch_notification_pcie_test
+
+#include "pcie_errors.c" /* sic */
+#include "testing.h"
+
+#define TEST_DOMAIN 1
+#define TEST_BUS 5
+#define TEST_DEVICE 0xc
+#define TEST_FUNCTION 2
+#define TEST_DEVICE_STR "0001:05:0c.2"
+
+#define G_BUFF_LEN 4
+
+static notification_t last_notif;
+static char g_buff[G_BUFF_LEN];
+
+/* mock functions */
+int plugin_dispatch_notification_pcie_test(const notification_t *notif) {
+  last_notif = *notif;
+  return ENOTSUP;
+}
+
+ssize_t pread(__attribute__((unused)) int fd, void *buf, size_t count,
+              __attribute__((unused)) off_t offset) {
+  if (count == 0 || count > G_BUFF_LEN)
+    return -1;
+
+  memcpy(buf, g_buff, count);
+  return count;
+}
+/* end mock functions */
+
+DEF_TEST(clear_dev_list) {
+  pcie_clear_list(NULL);
+
+  llist_t *test_list = llist_create();
+  CHECK_NOT_NULL(test_list);
+
+  pcie_device_t *dev = calloc(1, sizeof(*dev));
+  CHECK_NOT_NULL(dev);
+
+  llentry_t *entry = llentry_create(NULL, dev);
+  CHECK_NOT_NULL(entry);
+
+  llist_append(test_list, entry);
+
+  for (llentry_t *e = llist_head(test_list); e != NULL; e = e->next) {
+    EXPECT_EQ_PTR(dev, e->value);
+  }
+
+  pcie_clear_list(test_list);
+
+  return 0;
+}
+
+DEF_TEST(add_to_list) {
+  llist_t *test_list = llist_create();
+  CHECK_NOT_NULL(test_list);
+
+  int ret = pcie_add_device(test_list, TEST_DOMAIN, TEST_BUS, TEST_DEVICE,
+                            TEST_FUNCTION);
+  EXPECT_EQ_INT(0, ret);
+
+  llentry_t *e = llist_head(test_list);
+  CHECK_NOT_NULL(e);
+  OK(NULL == e->next);
+
+  pcie_device_t *dev = e->value;
+  CHECK_NOT_NULL(dev);
+  EXPECT_EQ_INT(TEST_DOMAIN, dev->domain);
+  EXPECT_EQ_INT(TEST_BUS, dev->bus);
+  EXPECT_EQ_INT(TEST_DEVICE, dev->device);
+  EXPECT_EQ_INT(TEST_FUNCTION, dev->function);
+  EXPECT_EQ_INT(-1, dev->cap_exp);
+  EXPECT_EQ_INT(-1, dev->ecap_aer);
+
+  pcie_clear_list(test_list);
+
+  return 0;
+}
+
+DEF_TEST(pcie_read) {
+  int ret;
+  pcie_device_t dev = {0};
+  uint32_t val = 0;
+  g_buff[0] = 4;
+  g_buff[1] = 3;
+  g_buff[2] = 2;
+  g_buff[3] = 1;
+
+  ret = pcie_read(&dev, &val, 1, 0);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(4, val);
+
+  ret = pcie_read(&dev, &val, 2, 0);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(0x304, val);
+
+  ret = pcie_read(&dev, &val, 3, 0);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(0x20304, val);
+
+  ret = pcie_read(&dev, &val, 4, 0);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(0x1020304, val);
+
+  ret = pcie_read(&dev, &val, G_BUFF_LEN + 1, 0);
+  EXPECT_EQ_INT(-1, ret);
+
+  pcie_fops.read = pcie_read;
+
+  uint8_t val8 = pcie_read8(&dev, 0);
+  EXPECT_EQ_INT(4, val8);
+
+  uint16_t val16 = pcie_read16(&dev, 0);
+  EXPECT_EQ_INT(0x304, val16);
+
+  uint32_t val32 = pcie_read32(&dev, 0);
+  EXPECT_EQ_INT(0x1020304, val32);
+
+  return 0;
+}
+
+DEF_TEST(dispatch_notification) {
+  pcie_device_t dev = {0, TEST_DOMAIN, TEST_BUS, TEST_DEVICE, TEST_FUNCTION,
+                       0, 0,           0,        0,           0};
+  cdtime_t t = cdtime();
+  notification_t n = {
+      .severity = 1, .time = t, .plugin = "pcie_errors_test", .meta = NULL};
+
+  pcie_dispatch_notification(&dev, &n, "test_type", "test_type_instance");
+  EXPECT_EQ_INT(1, last_notif.severity);
+  EXPECT_EQ_UINT64(t, last_notif.time);
+  EXPECT_EQ_STR("pcie_errors_test", last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(hostname_g, last_notif.host);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR("test_type", last_notif.type);
+  EXPECT_EQ_STR("test_type_instance", last_notif.type_instance);
+
+  return 0;
+}
+
+DEF_TEST(access_config) {
+  pcie_config.use_sysfs = 0;
+  pcie_access_config();
+  EXPECT_EQ_PTR(pcie_list_devices_proc, pcie_fops.list_devices);
+  EXPECT_EQ_PTR(pcie_open_proc, pcie_fops.open);
+  EXPECT_EQ_PTR(pcie_close, pcie_fops.close);
+  EXPECT_EQ_PTR(pcie_read, pcie_fops.read);
+  EXPECT_EQ_STR(PCIE_DEFAULT_PROCDIR, pcie_config.access_dir);
+
+  sstrncpy(pcie_config.access_dir, "Test", sizeof(pcie_config.access_dir));
+  pcie_access_config();
+  EXPECT_EQ_STR("Test", pcie_config.access_dir);
+
+  pcie_config.use_sysfs = 1;
+  pcie_access_config();
+  EXPECT_EQ_PTR(pcie_list_devices_sysfs, pcie_fops.list_devices);
+  EXPECT_EQ_PTR(pcie_open_sysfs, pcie_fops.open);
+  EXPECT_EQ_PTR(pcie_close, pcie_fops.close);
+  EXPECT_EQ_PTR(pcie_read, pcie_fops.read);
+  EXPECT_EQ_STR("Test", pcie_config.access_dir);
+
+  pcie_config.access_dir[0] = '\0';
+  pcie_access_config();
+  EXPECT_EQ_STR(PCIE_DEFAULT_SYSFSDIR, pcie_config.access_dir);
+
+  return 0;
+}
+
+DEF_TEST(plugin_config_fail) {
+  oconfig_item_t test_cfg_parent = {"pcie_errors", NULL, 0, NULL, NULL, 0};
+  char value_buff[256] = "procs";
+  char key_buff[256] = "Sources";
+  oconfig_value_t test_cfg_value = {{value_buff}, OCONFIG_TYPE_STRING};
+  oconfig_item_t test_cfg = {
+      key_buff, &test_cfg_value, 1, &test_cfg_parent, NULL, 0};
+
+  test_cfg_parent.children = &test_cfg;
+  test_cfg_parent.children_num = 1;
+
+  int ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(-1, ret);
+
+  sstrncpy(key_buff, "Source", sizeof(key_buff));
+  ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(-1, ret);
+
+  sstrncpy(value_buff, "proc", sizeof(value_buff));
+  test_cfg_value.type = OCONFIG_TYPE_NUMBER;
+  ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(-1, ret);
+
+  sstrncpy(key_buff, "AccessDir", sizeof(key_buff));
+  ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(-1, ret);
+
+  return 0;
+}
+
+DEF_TEST(plugin_config) {
+  oconfig_item_t test_cfg_parent = {"pcie_errors", NULL, 0, NULL, NULL, 0};
+  char value_buff[256] = "proc";
+  char key_buff[256] = "source";
+  oconfig_value_t test_cfg_value = {{value_buff}, OCONFIG_TYPE_STRING};
+  oconfig_item_t test_cfg = {
+      key_buff, &test_cfg_value, 1, &test_cfg_parent, NULL, 0};
+
+  test_cfg_parent.children = &test_cfg;
+  test_cfg_parent.children_num = 1;
+
+  pcie_config.use_sysfs = 1;
+  int ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(0, pcie_config.use_sysfs);
+
+  pcie_config.use_sysfs = 1;
+  sstrncpy(value_buff, "sysfs", sizeof(value_buff));
+  ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(1, pcie_config.use_sysfs);
+
+  sstrncpy(key_buff, "AccessDir", sizeof(key_buff));
+  sstrncpy(value_buff, "some/test/value", sizeof(value_buff));
+  ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("some/test/value", pcie_config.access_dir);
+
+  memset(&test_cfg_value.value, 0, sizeof(test_cfg_value.value));
+  test_cfg_value.value.boolean = 1;
+  test_cfg_value.type = OCONFIG_TYPE_BOOLEAN;
+  sstrncpy(key_buff, "ReportMasked", sizeof(key_buff));
+  ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(1, pcie_config.notif_masked);
+
+  sstrncpy(key_buff, "PersistentNotifications", sizeof(key_buff));
+  ret = pcie_plugin_config(&test_cfg_parent);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(1, pcie_config.persistent);
+
+  return 0;
+}
+
+#define BAD_TLP_SET_MSG "Correctable Error set: Bad TLP Status"
+#define BAD_TLP_CLEAR_MSG "Correctable Error cleared: Bad TLP Status"
+
+DEF_TEST(dispatch_correctable_errors) {
+  pcie_device_t dev = {0, TEST_DOMAIN, TEST_BUS, TEST_DEVICE, TEST_FUNCTION,
+                       0, 0,           0,        0,           0};
+  pcie_config.notif_masked = 0;
+  pcie_config.persistent = 0;
+
+  pcie_dispatch_correctable_errors(&dev, PCI_ERR_COR_BAD_TLP,
+                                   ~(PCI_ERR_COR_BAD_TLP));
+  EXPECT_EQ_INT(NOTIF_WARNING, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_CE, last_notif.type_instance);
+  EXPECT_EQ_STR(BAD_TLP_SET_MSG, last_notif.message);
+
+  memset(&last_notif, 0, sizeof(last_notif));
+  dev.correctable_errors = PCI_ERR_COR_BAD_TLP;
+  pcie_dispatch_correctable_errors(&dev, PCI_ERR_COR_BAD_TLP,
+                                   ~(PCI_ERR_COR_BAD_TLP));
+  EXPECT_EQ_STR("", last_notif.plugin_instance);
+
+  pcie_config.persistent = 1;
+  pcie_dispatch_correctable_errors(&dev, PCI_ERR_COR_BAD_TLP,
+                                   ~(PCI_ERR_COR_BAD_TLP));
+  EXPECT_EQ_INT(NOTIF_WARNING, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_CE, last_notif.type_instance);
+  EXPECT_EQ_STR(BAD_TLP_SET_MSG, last_notif.message);
+
+  memset(&last_notif, 0, sizeof(last_notif));
+  pcie_dispatch_correctable_errors(&dev, PCI_ERR_COR_BAD_TLP,
+                                   PCI_ERR_COR_BAD_TLP);
+  EXPECT_EQ_STR("", last_notif.plugin_instance);
+
+  pcie_config.notif_masked = 1;
+  pcie_dispatch_correctable_errors(&dev, PCI_ERR_COR_BAD_TLP,
+                                   PCI_ERR_COR_BAD_TLP);
+  EXPECT_EQ_INT(NOTIF_WARNING, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_CE, last_notif.type_instance);
+  EXPECT_EQ_STR(BAD_TLP_SET_MSG, last_notif.message);
+
+  pcie_config.persistent = 0;
+  memset(&last_notif, 0, sizeof(last_notif));
+  pcie_dispatch_correctable_errors(&dev, PCI_ERR_COR_BAD_TLP,
+                                   PCI_ERR_COR_BAD_TLP);
+  EXPECT_EQ_STR("", last_notif.plugin_instance);
+
+  dev.correctable_errors = 0;
+  pcie_dispatch_correctable_errors(&dev, PCI_ERR_COR_BAD_TLP,
+                                   PCI_ERR_COR_BAD_TLP);
+  EXPECT_EQ_INT(NOTIF_WARNING, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_CE, last_notif.type_instance);
+  EXPECT_EQ_STR(BAD_TLP_SET_MSG, last_notif.message);
+
+  pcie_dispatch_correctable_errors(&dev, PCI_ERR_COR_BAD_TLP,
+                                   ~(PCI_ERR_COR_BAD_TLP));
+  EXPECT_EQ_INT(NOTIF_WARNING, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_CE, last_notif.type_instance);
+  EXPECT_EQ_STR(BAD_TLP_SET_MSG, last_notif.message);
+
+  pcie_config.notif_masked = 0;
+  dev.correctable_errors = PCI_ERR_COR_BAD_TLP;
+  pcie_dispatch_correctable_errors(&dev, 0, ~(PCI_ERR_COR_BAD_TLP));
+  EXPECT_EQ_INT(NOTIF_OKAY, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_CE, last_notif.type_instance);
+  EXPECT_EQ_STR(BAD_TLP_CLEAR_MSG, last_notif.message);
+
+  return 0;
+}
+
+#define FCP_NF_SET_MSG                                                         \
+  "Uncorrectable(non_fatal) Error set: Flow Control Protocol"
+#define FCP_F_SET_MSG "Uncorrectable(fatal) Error set: Flow Control Protocol"
+#define FCP_NF_CLEAR_MSG                                                       \
+  "Uncorrectable(non_fatal) Error cleared: Flow Control Protocol"
+#define FCP_F_CLEAR_MSG                                                        \
+  "Uncorrectable(fatal) Error cleared: Flow Control Protocol"
+
+DEF_TEST(dispatch_uncorrectable_errors) {
+  pcie_device_t dev = {0, TEST_DOMAIN, TEST_BUS, TEST_DEVICE, TEST_FUNCTION,
+                       0, 0,           0,        0,           0};
+  pcie_config.notif_masked = 0;
+  pcie_config.persistent = 0;
+
+  pcie_dispatch_uncorrectable_errors(&dev, PCI_ERR_UNC_FCP, ~(PCI_ERR_UNC_FCP),
+                                     ~(PCI_ERR_UNC_FCP));
+  EXPECT_EQ_INT(NOTIF_WARNING, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_NOFATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FCP_NF_SET_MSG, last_notif.message);
+
+  pcie_dispatch_uncorrectable_errors(&dev, PCI_ERR_UNC_FCP, ~(PCI_ERR_UNC_FCP),
+                                     PCI_ERR_UNC_FCP);
+  EXPECT_EQ_INT(NOTIF_FAILURE, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_FATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FCP_F_SET_MSG, last_notif.message);
+
+  memset(&last_notif, 0, sizeof(last_notif));
+  dev.uncorrectable_errors = PCI_ERR_UNC_FCP;
+  pcie_dispatch_uncorrectable_errors(&dev, PCI_ERR_UNC_FCP, ~(PCI_ERR_UNC_FCP),
+                                     PCI_ERR_UNC_FCP);
+  EXPECT_EQ_STR("", last_notif.plugin_instance);
+
+  pcie_config.persistent = 1;
+  pcie_dispatch_uncorrectable_errors(&dev, PCI_ERR_UNC_FCP, ~(PCI_ERR_UNC_FCP),
+                                     PCI_ERR_UNC_FCP);
+  EXPECT_EQ_INT(NOTIF_FAILURE, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_FATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FCP_F_SET_MSG, last_notif.message);
+
+  memset(&last_notif, 0, sizeof(last_notif));
+  pcie_dispatch_uncorrectable_errors(&dev, PCI_ERR_UNC_FCP, PCI_ERR_UNC_FCP,
+                                     PCI_ERR_UNC_FCP);
+  EXPECT_EQ_STR("", last_notif.plugin_instance);
+
+  pcie_config.notif_masked = 1;
+  pcie_dispatch_uncorrectable_errors(&dev, PCI_ERR_UNC_FCP, PCI_ERR_UNC_FCP,
+                                     PCI_ERR_UNC_FCP);
+  EXPECT_EQ_INT(NOTIF_FAILURE, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_FATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FCP_F_SET_MSG, last_notif.message);
+
+  pcie_config.persistent = 0;
+  dev.uncorrectable_errors = 0;
+  memset(&last_notif, 0, sizeof(last_notif));
+  pcie_dispatch_uncorrectable_errors(&dev, PCI_ERR_UNC_FCP, ~(PCI_ERR_UNC_FCP),
+                                     PCI_ERR_UNC_FCP);
+  EXPECT_EQ_INT(NOTIF_FAILURE, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_FATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FCP_F_SET_MSG, last_notif.message);
+
+  pcie_config.notif_masked = 0;
+  dev.uncorrectable_errors = PCI_ERR_UNC_FCP;
+  pcie_dispatch_uncorrectable_errors(&dev, 0, ~(PCI_ERR_UNC_FCP),
+                                     ~(PCI_ERR_UNC_FCP));
+  EXPECT_EQ_INT(NOTIF_OKAY, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_NOFATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FCP_NF_CLEAR_MSG, last_notif.message);
+
+  memset(&last_notif, 0, sizeof(last_notif));
+  pcie_dispatch_uncorrectable_errors(&dev, 0, ~(PCI_ERR_UNC_FCP),
+                                     PCI_ERR_UNC_FCP);
+  EXPECT_EQ_INT(NOTIF_OKAY, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_FATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FCP_F_CLEAR_MSG, last_notif.message);
+
+  return 0;
+}
+
+#define UR_SET_MSG "Device Status Error set: Unsupported Request"
+#define UR_CLEAR_MSG "Device Status Error cleared: Unsupported Request"
+#define FE_SET_MSG "Device Status Error set: Fatal Error"
+#define FE_CLEAR_MSG "Device Status Error cleared: Fatal Error"
+
+DEF_TEST(device_status_errors) {
+  pcie_device_t dev = {0, TEST_DOMAIN, TEST_BUS, TEST_DEVICE, TEST_FUNCTION,
+                       0, 0,           0,        0,           0};
+  pcie_config.persistent = 0;
+  g_buff[0] = (PCI_EXP_DEVSTA_URD & 0xff);
+
+  memset(&last_notif, 0, sizeof(last_notif));
+  pcie_check_dev_status(&dev, 0);
+  EXPECT_EQ_INT(NOTIF_WARNING, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_NOFATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(UR_SET_MSG, last_notif.message);
+
+  memset(&last_notif, 0, sizeof(last_notif));
+  pcie_check_dev_status(&dev, 0);
+  EXPECT_EQ_STR("", last_notif.plugin_instance);
+
+  pcie_config.persistent = 1;
+  pcie_check_dev_status(&dev, 0);
+  EXPECT_EQ_INT(NOTIF_WARNING, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_NOFATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(UR_SET_MSG, last_notif.message);
+
+  g_buff[0] = 0;
+  pcie_check_dev_status(&dev, 0);
+  EXPECT_EQ_INT(NOTIF_OKAY, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_NOFATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(UR_CLEAR_MSG, last_notif.message);
+
+  pcie_config.persistent = 0;
+  dev.device_status = PCI_EXP_DEVSTA_URD;
+  pcie_check_dev_status(&dev, 0);
+  EXPECT_EQ_INT(NOTIF_OKAY, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_NOFATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(UR_CLEAR_MSG, last_notif.message);
+
+  memset(&last_notif, 0, sizeof(last_notif));
+  pcie_check_dev_status(&dev, 0);
+  EXPECT_EQ_STR("", last_notif.plugin_instance);
+
+  g_buff[0] = (PCI_EXP_DEVSTA_FED & 0xff);
+  pcie_check_dev_status(&dev, 0);
+  EXPECT_EQ_INT(NOTIF_FAILURE, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_FATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FE_SET_MSG, last_notif.message);
+
+  g_buff[0] = 0;
+  pcie_check_dev_status(&dev, 0);
+  EXPECT_EQ_INT(NOTIF_OKAY, last_notif.severity);
+  EXPECT_EQ_STR(PCIE_ERRORS_PLUGIN, last_notif.plugin);
+  OK(NULL == last_notif.meta);
+  EXPECT_EQ_STR(TEST_DEVICE_STR, last_notif.plugin_instance);
+  EXPECT_EQ_STR(PCIE_ERROR, last_notif.type);
+  EXPECT_EQ_STR(PCIE_SEV_FATAL, last_notif.type_instance);
+  EXPECT_EQ_STR(FE_CLEAR_MSG, last_notif.message);
+
+  return 0;
+}
+
+int main(void) {
+  RUN_TEST(clear_dev_list);
+  RUN_TEST(add_to_list);
+  RUN_TEST(pcie_read);
+  RUN_TEST(dispatch_notification);
+
+  RUN_TEST(access_config);
+  RUN_TEST(plugin_config_fail);
+  RUN_TEST(plugin_config);
+
+  RUN_TEST(dispatch_correctable_errors);
+  RUN_TEST(dispatch_uncorrectable_errors);
+  RUN_TEST(device_status_errors);
+
+  END_TEST;
+}
index 306d413..fffbc21 100644 (file)
 /* do not automatically get the thread specific Perl interpreter */
 #define PERL_NO_GET_CONTEXT
 
-#define DONT_POISON_SPRINTF_YET 1
 #include "collectd.h"
-
-#undef DONT_POISON_SPRINTF_YET
-
 #include <stdbool.h>
 
 #include <EXTERN.h>
 #include <perl.h>
 
-#if defined(COLLECT_DEBUG) && COLLECT_DEBUG && defined(__GNUC__) && __GNUC__
-#undef sprintf
-#pragma GCC poison sprintf
-#endif
-
 #include <XSUB.h>
 
 /* Some versions of Perl define their own version of DEBUG... :-/ */
index 7c140e0..ae887a1 100644 (file)
@@ -125,8 +125,6 @@ typedef struct {
   /* make sure we don't access the database object in parallel */
   pthread_mutex_t db_lock;
 
-  cdtime_t interval;
-
   /* writer "caching" settings */
   cdtime_t commit_interval;
   cdtime_t next_commit;
@@ -237,8 +235,6 @@ static c_psql_database_t *c_psql_database_new(const char *name) {
 
   pthread_mutex_init(&db->db_lock, /* attrs = */ NULL);
 
-  db->interval = 0;
-
   db->commit_interval = 0;
   db->next_commit = 0;
   db->expire_delay = 0;
@@ -431,8 +427,7 @@ static PGresult *c_psql_exec_query_params(c_psql_database_t *db, udb_query_t *q,
       break;
     case C_PSQL_PARAM_INTERVAL:
       snprintf(interval, sizeof(interval), "%.3f",
-               (db->interval > 0) ? CDTIME_T_TO_DOUBLE(db->interval)
-                                  : plugin_get_interval());
+               CDTIME_T_TO_DOUBLE(plugin_get_interval()));
       params[i] = interval;
       break;
     case C_PSQL_PARAM_INSTANCE:
@@ -548,7 +543,7 @@ static int c_psql_exec_query(c_psql_database_t *db, udb_query_t *q,
   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);
+      column_names, (size_t)column_num);
 
   if (0 != status) {
     log_err("udb_query_prepare_result failed with status %i.", status);
@@ -1123,6 +1118,7 @@ static int c_psql_config_writer(oconfig_item_t *ci) {
 static int c_psql_config_database(oconfig_item_t *ci) {
   c_psql_database_t *db;
 
+  cdtime_t interval = 0;
   char cb_name[DATA_MAX_NAME_LEN];
   static bool have_flush;
 
@@ -1163,7 +1159,7 @@ static int c_psql_config_database(oconfig_item_t *ci) {
       config_add_writer(c, writers, writers_num, &db->writers,
                         &db->writers_num);
     else if (0 == strcasecmp(c->key, "Interval"))
-      cf_util_get_cdtime(c, &db->interval);
+      cf_util_get_cdtime(c, &interval);
     else if (strcasecmp("CommitInterval", c->key) == 0)
       cf_util_get_cdtime(c, &db->commit_interval);
     else if (strcasecmp("ExpireDelay", c->key) == 0)
@@ -1211,8 +1207,8 @@ static int c_psql_config_database(oconfig_item_t *ci) {
 
   if (db->queries_num > 0) {
     ++db->ref_cnt;
-    plugin_register_complex_read("postgresql", cb_name, c_psql_read,
-                                 /* interval = */ db->interval, &ud);
+    plugin_register_complex_read("postgresql", cb_name, c_psql_read, interval,
+                                 &ud);
   }
   if (db->writers_num > 0) {
     ++db->ref_cnt;
index eb3ec53..7a2fbfd 100644 (file)
@@ -359,9 +359,6 @@ static void submit(const char *plugin_instance, /* {{{ */
   }
 
   if (0 != parse_value(value_str, &value, ds->ds[0].type)) {
-    ERROR("powerdns plugin: Cannot convert `%s' "
-          "to a number.",
-          value_str);
     return;
   }
 
index ffe6c5a..aa7cfa3 100644 (file)
@@ -909,7 +909,7 @@ static void ps_submit_proc_list(procstat_t *ps) {
   gauge_t const delay_factor = 1000000000.0;
 
   struct {
-    char *type_instance;
+    const char *type_instance;
     gauge_t rate_ns;
   } delay_metrics[] = {
       {"delay-cpu", ps->delay_cpu},
@@ -924,7 +924,7 @@ static void ps_submit_proc_list(procstat_t *ps) {
     sstrncpy(vl.type, "delay_rate", sizeof(vl.type));
     sstrncpy(vl.type_instance, delay_metrics[i].type_instance,
              sizeof(vl.type_instance));
-    vl.values[0].gauge = delay_metrics[i].rate_ns * delay_factor;
+    vl.values[0].gauge = delay_metrics[i].rate_ns / delay_factor;
     vl.values_len = 1;
     plugin_dispatch_values(&vl);
   }
index 59eb49a..36b1d83 100644 (file)
@@ -58,7 +58,6 @@ static void submit(const char *protocol_name, const char *str_key,
 
   status = parse_value(str_value, &value, DS_TYPE_DERIVE);
   if (status != 0) {
-    ERROR("protocols plugin: Parsing string as integer failed: %s", str_value);
     return;
   }
 
index 5734098..e24abd5 100644 (file)
 #include <hiredis/hiredis.h>
 #include <sys/time.h>
 
-#ifndef HOST_NAME_MAX
-#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
-#endif
-
 #define REDIS_DEF_HOST "localhost"
 #define REDIS_DEF_PASSWD ""
 #define REDIS_DEF_PORT 6379
-#define REDIS_DEF_TIMEOUT 2000
+#define REDIS_DEF_TIMEOUT_SEC 2
 #define REDIS_DEF_DB_COUNT 256
-#define MAX_REDIS_NODE_NAME 64
-#define MAX_REDIS_PASSWD_LENGTH 512
 #define MAX_REDIS_VAL_SIZE 256
 #define MAX_REDIS_QUERY 2048
 
@@ -65,57 +59,72 @@ struct redis_query_s {
   redis_query_t *next;
 };
 
+struct prev_s {
+  derive_t keyspace_hits;
+  derive_t keyspace_misses;
+};
+typedef struct prev_s prev_t;
+
 struct redis_node_s;
 typedef struct redis_node_s redis_node_t;
 struct redis_node_s {
-  char name[MAX_REDIS_NODE_NAME];
-  char host[HOST_NAME_MAX];
-  char passwd[MAX_REDIS_PASSWD_LENGTH];
+  char *name;
+  char *host;
+  char *socket;
+  char *passwd;
   int port;
   struct timeval timeout;
+  bool report_command_stats;
+  bool report_cpu_usage;
+  redisContext *redisContext;
   redis_query_t *queries;
+  prev_t prev;
 
   redis_node_t *next;
 };
 
-static redis_node_t *nodes_head;
+static bool redis_have_instances;
+static int redis_read(user_data_t *user_data);
 
-static int redis_node_add(const redis_node_t *rn) /* {{{ */
-{
-  redis_node_t *rn_copy;
-  redis_node_t *rn_ptr;
+static void redis_node_free(void *arg) {
+  redis_node_t *rn = arg;
+  if (rn == NULL)
+    return;
 
-  /* Check for duplicates first */
-  for (rn_ptr = nodes_head; rn_ptr != NULL; rn_ptr = rn_ptr->next)
-    if (strcmp(rn->name, rn_ptr->name) == 0)
-      break;
-
-  if (rn_ptr != NULL) {
-    ERROR("redis plugin: A node with the name `%s' already exists.", rn->name);
-    return -1;
-  }
-
-  rn_copy = malloc(sizeof(*rn_copy));
-  if (rn_copy == NULL) {
-    ERROR("redis plugin: malloc failed adding redis_node to the tree.");
-    return -1;
+  redis_query_t *rq = rn->queries;
+  while (rq != NULL) {
+    redis_query_t *next = rq->next;
+    sfree(rq);
+    rq = next;
   }
 
-  memcpy(rn_copy, rn, sizeof(*rn_copy));
-  rn_copy->next = NULL;
+  if (rn->redisContext)
+    redisFree(rn->redisContext);
+  sfree(rn->name);
+  sfree(rn->host);
+  sfree(rn->socket);
+  sfree(rn->passwd);
+  sfree(rn);
+} /* void redis_node_free */
 
+static int redis_node_add(redis_node_t *rn) /* {{{ */
+{
   DEBUG("redis plugin: Adding node \"%s\".", rn->name);
 
-  if (nodes_head == NULL)
-    nodes_head = rn_copy;
-  else {
-    rn_ptr = nodes_head;
-    while (rn_ptr->next != NULL)
-      rn_ptr = rn_ptr->next;
-    rn_ptr->next = rn_copy;
-  }
+  /* Disable automatic generation of default instance in the init callback. */
+  redis_have_instances = true;
 
-  return 0;
+  char cb_name[sizeof("redis/") + DATA_MAX_NAME_LEN];
+  snprintf(cb_name, sizeof(cb_name), "redis/%s", rn->name);
+
+  return plugin_register_complex_read(
+      /* group = */ "redis",
+      /* name      = */ cb_name,
+      /* callback  = */ redis_read,
+      /* interval  = */ 0,
+      &(user_data_t){
+          .data = rn, .free_func = redis_node_free,
+      });
 } /* }}} */
 
 static redis_query_t *redis_config_query(oconfig_item_t *ci) /* {{{ */
@@ -171,44 +180,65 @@ err:
 
 static int redis_config_node(oconfig_item_t *ci) /* {{{ */
 {
-  redis_query_t *rq;
-  int status;
-  int timeout;
+  redis_node_t *rn = calloc(1, sizeof(*rn));
+  if (rn == NULL) {
+    ERROR("redis plugin: calloc failed adding node.");
+    return ENOMEM;
+  }
 
-  redis_node_t rn = {.port = REDIS_DEF_PORT,
-                     .timeout.tv_usec = REDIS_DEF_TIMEOUT};
+  rn->port = REDIS_DEF_PORT;
+  rn->timeout.tv_sec = REDIS_DEF_TIMEOUT_SEC;
+  rn->report_cpu_usage = true;
 
-  sstrncpy(rn.host, REDIS_DEF_HOST, sizeof(rn.host));
+  rn->host = strdup(REDIS_DEF_HOST);
+  if (rn->host == NULL) {
+    ERROR("redis plugin: strdup failed adding node.");
+    sfree(rn);
+    return ENOMEM;
+  }
 
-  status = cf_util_get_string_buffer(ci, rn.name, sizeof(rn.name));
-  if (status != 0)
+  int status = cf_util_get_string(ci, &rn->name);
+  if (status != 0) {
+    sfree(rn->host);
+    sfree(rn);
     return status;
+  }
 
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *option = ci->children + i;
 
     if (strcasecmp("Host", option->key) == 0)
-      status = cf_util_get_string_buffer(option, rn.host, sizeof(rn.host));
+      status = cf_util_get_string(option, &rn->host);
     else if (strcasecmp("Port", option->key) == 0) {
       status = cf_util_get_port_number(option);
       if (status > 0) {
-        rn.port = status;
+        rn->port = status;
         status = 0;
       }
+    } else if (strcasecmp("Socket", option->key) == 0) {
+      status = cf_util_get_string(option, &rn->socket);
     } else if (strcasecmp("Query", option->key) == 0) {
-      rq = redis_config_query(option);
+      redis_query_t *rq = redis_config_query(option);
       if (rq == NULL) {
         status = 1;
       } else {
-        rq->next = rn.queries;
-        rn.queries = rq;
+        rq->next = rn->queries;
+        rn->queries = rq;
       }
     } else if (strcasecmp("Timeout", option->key) == 0) {
+      int timeout;
       status = cf_util_get_int(option, &timeout);
-      if (status == 0)
-        rn.timeout.tv_usec = timeout;
+      if (status == 0) {
+        rn->timeout.tv_usec = timeout * 1000;
+        rn->timeout.tv_sec = rn->timeout.tv_usec / 1000000L;
+        rn->timeout.tv_usec %= 1000000L;
+      }
     } else if (strcasecmp("Password", option->key) == 0)
-      status = cf_util_get_string_buffer(option, rn.passwd, sizeof(rn.passwd));
+      status = cf_util_get_string(option, &rn->passwd);
+    else if (strcasecmp("ReportCommandStats", option->key) == 0)
+      status = cf_util_get_boolean(option, &rn->report_command_stats);
+    else if (strcasecmp("ReportCpuUsage", option->key) == 0)
+      status = cf_util_get_boolean(option, &rn->report_cpu_usage);
     else
       WARNING("redis plugin: Option `%s' not allowed inside a `Node' "
               "block. I'll ignore this option.",
@@ -218,10 +248,12 @@ static int redis_config_node(oconfig_item_t *ci) /* {{{ */
       break;
   }
 
-  if (status != 0)
+  if (status != 0) {
+    redis_node_free(rn);
     return status;
+  }
 
-  return redis_node_add(&rn);
+  return redis_node_add(rn);
 } /* }}} int redis_config_node */
 
 static int redis_config(oconfig_item_t *ci) /* {{{ */
@@ -237,17 +269,12 @@ static int redis_config(oconfig_item_t *ci) /* {{{ */
               option->key);
   }
 
-  if (nodes_head == NULL) {
-    ERROR("redis plugin: No valid node configuration could be found.");
-    return ENOENT;
-  }
-
   return 0;
 } /* }}} */
 
 __attribute__((nonnull(2))) static void
-redis_submit(char *plugin_instance, const char *type, const char *type_instance,
-             value_t value) /* {{{ */
+redis_submit(const char *plugin_instance, const char *type,
+             const char *type_instance, value_t value) /* {{{ */
 {
   value_list_t vl = VALUE_LIST_INIT;
 
@@ -263,28 +290,78 @@ redis_submit(char *plugin_instance, const char *type, const char *type_instance,
   plugin_dispatch_values(&vl);
 } /* }}} */
 
+__attribute__((nonnull(2))) static void
+redis_submit2(const char *plugin_instance, const char *type,
+              const char *type_instance, value_t value0,
+              value_t value1) /* {{{ */
+{
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t values[] = {value0, value1};
+
+  vl.values = values;
+  vl.values_len = STATIC_ARRAY_SIZE(values);
+
+  sstrncpy(vl.plugin, "redis", sizeof(vl.plugin));
+  sstrncpy(vl.type, type, sizeof(vl.type));
+
+  if (plugin_instance != NULL)
+    sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
+
+  if (type_instance != NULL)
+    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+  plugin_dispatch_values(&vl);
+} /* }}} */
+
 static int redis_init(void) /* {{{ */
 {
-  redis_node_t rn = {.name = "default",
-                     .host = REDIS_DEF_HOST,
-                     .port = REDIS_DEF_PORT,
-                     .timeout.tv_sec = 0,
-                     .timeout.tv_usec = REDIS_DEF_TIMEOUT,
-                     .next = NULL};
+  if (redis_have_instances)
+    return 0;
 
-  if (nodes_head == NULL)
-    redis_node_add(&rn);
+  redis_node_t *rn = calloc(1, sizeof(*rn));
+  if (rn == NULL)
+    return ENOMEM;
 
-  return 0;
+  rn->port = REDIS_DEF_PORT;
+  rn->timeout.tv_sec = REDIS_DEF_TIMEOUT_SEC;
+
+  rn->name = strdup("default");
+  rn->host = strdup(REDIS_DEF_HOST);
+
+  if (rn->name == NULL || rn->host == NULL) {
+    sfree(rn->name);
+    sfree(rn->host);
+    sfree(rn);
+    return ENOMEM;
+  }
+
+  return redis_node_add(rn);
 } /* }}} int redis_init */
 
-static int redis_handle_info(char *node, char const *info_line,
-                             char const *type, char const *type_instance,
-                             char const *field_name, int ds_type) /* {{{ */
-{
+static void *c_redisCommand(redis_node_t *rn, const char *format, ...) {
+  redisContext *c = rn->redisContext;
+
+  if (c == NULL)
+    return NULL;
+
+  va_list ap;
+  va_start(ap, format);
+  void *reply = redisvCommand(c, format, ap);
+  va_end(ap);
+
+  if (reply == NULL) {
+    ERROR("redis plugin: Connection error: %s", c->errstr);
+    redisFree(rn->redisContext);
+    rn->redisContext = NULL;
+  }
+
+  return reply;
+} /* void c_redisCommand */
+
+static int redis_get_info_value(char const *info_line, char const *field_name,
+                                int ds_type, value_t *val) {
   char *str = strstr(info_line, field_name);
   static char buf[MAX_REDIS_VAL_SIZE];
-  value_t val;
   if (str) {
     int i;
 
@@ -294,20 +371,29 @@ static int redis_handle_info(char *node, char const *info_line,
       buf[i] = *str;
     buf[i] = '\0';
 
-    if (parse_value(buf, &val, ds_type) == -1) {
+    if (parse_value(buf, val, ds_type) == -1) {
       WARNING("redis plugin: Unable to parse field `%s'.", field_name);
       return -1;
     }
 
-    redis_submit(node, type, type_instance, val);
     return 0;
   }
   return -1;
+} /* int redis_get_info_value */
 
+static int redis_handle_info(char *node, char const *info_line,
+                             char const *type, char const *type_instance,
+                             char const *field_name, int ds_type) /* {{{ */
+{
+  value_t val;
+  if (redis_get_info_value(info_line, field_name, ds_type, &val) != 0)
+    return -1;
+
+  redis_submit(node, type, type_instance, val);
+  return 0;
 } /* }}} int redis_handle_info */
 
-static int redis_handle_query(redisContext *rh, redis_node_t *rn,
-                              redis_query_t *rq) /* {{{ */
+static int redis_handle_query(redis_node_t *rn, redis_query_t *rq) /* {{{ */
 {
   redisReply *rr;
   const data_set_t *ds;
@@ -315,22 +401,24 @@ static int redis_handle_query(redisContext *rh, redis_node_t *rn,
 
   ds = plugin_get_ds(rq->type);
   if (!ds) {
-    ERROR("redis plugin: DataSet `%s' not defined.", rq->type);
+    ERROR("redis plugin: DS type `%s' not defined.", rq->type);
     return -1;
   }
 
   if (ds->ds_num != 1) {
-    ERROR("redis plugin: DS `%s' has too many types.", rq->type);
+    ERROR("redis plugin: DS type `%s' has too many datasources. This is not "
+          "supported currently.",
+          rq->type);
     return -1;
   }
 
-  if ((rr = redisCommand(rh, "SELECT %d", rq->database)) == NULL) {
+  if ((rr = c_redisCommand(rn, "SELECT %d", rq->database)) == NULL) {
     WARNING("redis plugin: unable to switch to database `%d' on node `%s'.",
             rq->database, rn->name);
     return -1;
   }
 
-  if ((rr = redisCommand(rh, rq->query)) == NULL) {
+  if ((rr = c_redisCommand(rn, rq->query)) == NULL) {
     WARNING("redis plugin: unable to carry out query `%s'.", rq->query);
     return -1;
   }
@@ -354,13 +442,24 @@ static int redis_handle_query(redisContext *rh, redis_node_t *rn,
     break;
   case REDIS_REPLY_STRING:
     if (parse_value(rr->str, &val, ds->ds[0].type) == -1) {
-      WARNING("redis plugin: Unable to parse field `%s'.", rq->type);
+      WARNING("redis plugin: Query `%s': Unable to parse value.", rq->query);
       freeReplyObject(rr);
       return -1;
     }
     break;
+  case REDIS_REPLY_ERROR:
+    WARNING("redis plugin: Query `%s' failed: %s.", rq->query, rr->str);
+    freeReplyObject(rr);
+    return -1;
+  case REDIS_REPLY_ARRAY:
+    WARNING("redis plugin: Query `%s' should return string or integer. Arrays "
+            "are not supported.",
+            rq->query);
+    freeReplyObject(rr);
+    return -1;
   default:
-    WARNING("redis plugin: Cannot coerce redis type.");
+    WARNING("redis plugin: Query `%s': Cannot coerce redis type (%i).",
+            rq->query, rr->type);
     freeReplyObject(rr);
     return -1;
   }
@@ -371,7 +470,7 @@ static int redis_handle_query(redisContext *rh, redis_node_t *rn,
   return 0;
 } /* }}} int redis_handle_query */
 
-static int redis_db_stats(char *node, char const *info_line) /* {{{ */
+static int redis_db_stats(const char *node, char const *info_line) /* {{{ */
 {
   /* redis_db_stats parses and dispatches Redis database statistics,
    * currently the number of keys for each database.
@@ -410,91 +509,299 @@ static int redis_db_stats(char *node, char const *info_line) /* {{{ */
 
 } /* }}} int redis_db_stats */
 
-static int redis_read(void) /* {{{ */
-{
-  for (redis_node_t *rn = nodes_head; rn != NULL; rn = rn->next) {
-    redisContext *rh;
+static void redis_cpu_usage(const char *node, char const *info_line) {
+  while (42) {
+    value_t rusage_user;
+    value_t rusage_syst;
+
+    if (redis_get_info_value(info_line, "used_cpu_user", DS_TYPE_GAUGE,
+                             &rusage_user) != 0)
+      break;
+
+    if (redis_get_info_value(info_line, "used_cpu_sys", DS_TYPE_GAUGE,
+                             &rusage_syst) != 0)
+      break;
+
+    redis_submit2(node, "ps_cputime", "daemon",
+                  (value_t){.derive = rusage_user.gauge * 1000000},
+                  (value_t){.derive = rusage_syst.gauge * 1000000});
+    break;
+  }
+
+  while (42) {
+    value_t rusage_user;
+    value_t rusage_syst;
+
+    if (redis_get_info_value(info_line, "used_cpu_user_children", DS_TYPE_GAUGE,
+                             &rusage_user) != 0)
+      break;
+
+    if (redis_get_info_value(info_line, "used_cpu_sys_children", DS_TYPE_GAUGE,
+                             &rusage_syst) != 0)
+      break;
+
+    redis_submit2(node, "ps_cputime", "children",
+                  (value_t){.derive = rusage_user.gauge * 1000000},
+                  (value_t){.derive = rusage_syst.gauge * 1000000});
+    break;
+  }
+} /* void redis_cpu_usage */
+
+static gauge_t calculate_ratio_percent(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;
+} /* gauge_t calculate_ratio_percent */
+
+static void redis_keyspace_usage(redis_node_t *rn, char const *info_line) {
+  value_t hits, misses;
+
+  if (redis_get_info_value(info_line, "keyspace_hits", DS_TYPE_DERIVE, &hits) !=
+      0)
+    return;
+
+  if (redis_get_info_value(info_line, "keyspace_misses", DS_TYPE_DERIVE,
+                           &misses) != 0)
+    return;
+
+  redis_submit(rn->name, "cache_result", "hits", hits);
+  redis_submit(rn->name, "cache_result", "misses", misses);
+
+  prev_t *prev = &rn->prev;
+  gauge_t ratio = calculate_ratio_percent(
+      hits.derive, misses.derive, &prev->keyspace_hits, &prev->keyspace_misses);
+  redis_submit(rn->name, "percent", "hitratio", (value_t){.gauge = ratio});
+
+} /* void redis_keyspace_usage */
+
+static void redis_check_connection(redis_node_t *rn) {
+  if (rn->redisContext)
+    return;
+
+  redisContext *rh;
+  if (rn->socket != NULL)
+    rh = redisConnectUnixWithTimeout(rn->socket, rn->timeout);
+  else
+    rh = redisConnectWithTimeout(rn->host, rn->port, rn->timeout);
+
+  if (rh == NULL) {
+    ERROR("redis plugin: can't allocate redis context");
+    return;
+  }
+  if (rh->err) {
+    if (rn->socket)
+      ERROR("redis plugin: unable to connect to node `%s' (%s): %s.", rn->name,
+            rn->socket, rh->errstr);
+    else
+      ERROR("redis plugin: unable to connect to node `%s' (%s:%d): %s.",
+            rn->name, rn->host, rn->port, rh->errstr);
+    redisFree(rh);
+    return;
+  }
+
+  rn->redisContext = rh;
+
+  if (rn->passwd) {
     redisReply *rr;
 
-    DEBUG("redis plugin: querying info from node `%s' (%s:%d).", rn->name,
-          rn->host, rn->port);
+    DEBUG("redis plugin: authenticating node `%s' passwd(%s).", rn->name,
+          rn->passwd);
 
-    rh = redisConnectWithTimeout((char *)rn->host, rn->port, rn->timeout);
-    if (rh == NULL) {
-      ERROR("redis plugin: unable to connect to node `%s' (%s:%d).", rn->name,
-            rn->host, rn->port);
-      continue;
+    if ((rr = c_redisCommand(rn, "AUTH %s", rn->passwd)) == NULL) {
+      WARNING("redis plugin: unable to authenticate on node `%s'.", rn->name);
+      return;
     }
 
-    if (strlen(rn->passwd) > 0) {
-      DEBUG("redis plugin: authenticating node `%s' passwd(%s).", rn->name,
-            rn->passwd);
+    if (rr->type != REDIS_REPLY_STATUS) {
+      WARNING("redis plugin: invalid authentication on node `%s'.", rn->name);
+      freeReplyObject(rr);
+      redisFree(rn->redisContext);
+      rn->redisContext = NULL;
+      return;
+    }
 
-      if ((rr = redisCommand(rh, "AUTH %s", rn->passwd)) == NULL) {
-        WARNING("redis plugin: unable to authenticate on node `%s'.", rn->name);
-        goto redis_fail;
-      }
+    freeReplyObject(rr);
+  }
+  return;
+} /* void redis_check_connection */
 
-      if (rr->type != REDIS_REPLY_STATUS) {
-        WARNING("redis plugin: invalid authentication on node `%s'.", rn->name);
-        goto redis_fail;
-      }
+static void redis_read_server_info(redis_node_t *rn) {
+  redisReply *rr;
 
-      freeReplyObject(rr);
+  if ((rr = c_redisCommand(rn, "INFO")) == NULL) {
+    WARNING("redis plugin: unable to get INFO from node `%s'.", rn->name);
+    return;
+  }
+
+  redis_handle_info(rn->name, rr->str, "uptime", NULL, "uptime_in_seconds",
+                    DS_TYPE_GAUGE);
+  redis_handle_info(rn->name, rr->str, "current_connections", "clients",
+                    "connected_clients", DS_TYPE_GAUGE);
+  redis_handle_info(rn->name, rr->str, "blocked_clients", NULL,
+                    "blocked_clients", DS_TYPE_GAUGE);
+  redis_handle_info(rn->name, rr->str, "memory", NULL, "used_memory",
+                    DS_TYPE_GAUGE);
+  redis_handle_info(rn->name, rr->str, "memory_lua", NULL, "used_memory_lua",
+                    DS_TYPE_GAUGE);
+  /* changes_since_last_save: Deprecated in redis version 2.6 and above */
+  redis_handle_info(rn->name, rr->str, "volatile_changes", NULL,
+                    "changes_since_last_save", DS_TYPE_GAUGE);
+  redis_handle_info(rn->name, rr->str, "total_connections", NULL,
+                    "total_connections_received", DS_TYPE_DERIVE);
+  redis_handle_info(rn->name, rr->str, "total_operations", NULL,
+                    "total_commands_processed", DS_TYPE_DERIVE);
+  redis_handle_info(rn->name, rr->str, "expired_keys", NULL, "expired_keys",
+                    DS_TYPE_DERIVE);
+  redis_handle_info(rn->name, rr->str, "evicted_keys", NULL, "evicted_keys",
+                    DS_TYPE_DERIVE);
+  redis_handle_info(rn->name, rr->str, "pubsub", "channels", "pubsub_channels",
+                    DS_TYPE_GAUGE);
+  redis_handle_info(rn->name, rr->str, "pubsub", "patterns", "pubsub_patterns",
+                    DS_TYPE_GAUGE);
+  redis_handle_info(rn->name, rr->str, "current_connections", "slaves",
+                    "connected_slaves", DS_TYPE_GAUGE);
+  redis_handle_info(rn->name, rr->str, "total_bytes", "input",
+                    "total_net_input_bytes", DS_TYPE_DERIVE);
+  redis_handle_info(rn->name, rr->str, "total_bytes", "output",
+                    "total_net_output_bytes", DS_TYPE_DERIVE);
+
+  redis_keyspace_usage(rn, rr->str);
+
+  redis_db_stats(rn->name, rr->str);
+
+  if (rn->report_cpu_usage)
+    redis_cpu_usage(rn->name, rr->str);
+
+  freeReplyObject(rr);
+} /* void redis_read_server_info */
+
+static void redis_read_command_stats(redis_node_t *rn) {
+  redisReply *rr;
+
+  if ((rr = c_redisCommand(rn, "INFO commandstats")) == NULL) {
+    WARNING("redis plugin: node `%s': unable to get `INFO commandstats'.",
+            rn->name);
+    return;
+  }
+
+  if (rr->type != REDIS_REPLY_STRING) {
+    WARNING("redis plugin: node `%s' `INFO commandstats' returned unsupported "
+            "redis type %i.",
+            rn->name, rr->type);
+    freeReplyObject(rr);
+    return;
+  }
+
+  char *command;
+  char *line;
+  char *ptr = rr->str;
+  char *saveptr = NULL;
+  while ((line = strtok_r(ptr, "\n\r", &saveptr)) != NULL) {
+    ptr = NULL;
+
+    if (line[0] == '#')
+      continue;
+
+    /* command name */
+    if (strstr(line, "cmdstat_") != line) {
+      ERROR("redis plugin: not found 'cmdstat_' prefix in line '%s'", line);
+      continue;
     }
 
-    if ((rr = redisCommand(rh, "INFO")) == NULL) {
-      WARNING("redis plugin: unable to get info from node `%s'.", rn->name);
-      goto redis_fail;
+    char *values = strstr(line, ":");
+    if (values == NULL) {
+      ERROR("redis plugin: not found ':' separator in line '%s'", line);
+      continue;
     }
 
-    redis_handle_info(rn->name, rr->str, "uptime", NULL, "uptime_in_seconds",
-                      DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "current_connections", "clients",
-                      "connected_clients", DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "blocked_clients", NULL,
-                      "blocked_clients", DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "memory", NULL, "used_memory",
-                      DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "memory_lua", NULL, "used_memory_lua",
-                      DS_TYPE_GAUGE);
-    /* changes_since_last_save: Deprecated in redis version 2.6 and above */
-    redis_handle_info(rn->name, rr->str, "volatile_changes", NULL,
-                      "changes_since_last_save", DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "total_connections", NULL,
-                      "total_connections_received", DS_TYPE_DERIVE);
-    redis_handle_info(rn->name, rr->str, "total_operations", NULL,
-                      "total_commands_processed", DS_TYPE_DERIVE);
-    redis_handle_info(rn->name, rr->str, "operations_per_second", NULL,
-                      "instantaneous_ops_per_sec", DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "expired_keys", NULL, "expired_keys",
-                      DS_TYPE_DERIVE);
-    redis_handle_info(rn->name, rr->str, "evicted_keys", NULL, "evicted_keys",
-                      DS_TYPE_DERIVE);
-    redis_handle_info(rn->name, rr->str, "pubsub", "channels",
-                      "pubsub_channels", DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "pubsub", "patterns",
-                      "pubsub_patterns", DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "current_connections", "slaves",
-                      "connected_slaves", DS_TYPE_GAUGE);
-    redis_handle_info(rn->name, rr->str, "cache_result", "hits",
-                      "keyspace_hits", DS_TYPE_DERIVE);
-    redis_handle_info(rn->name, rr->str, "cache_result", "misses",
-                      "keyspace_misses", DS_TYPE_DERIVE);
-    redis_handle_info(rn->name, rr->str, "total_bytes", "input",
-                      "total_net_input_bytes", DS_TYPE_DERIVE);
-    redis_handle_info(rn->name, rr->str, "total_bytes", "output",
-                      "total_net_output_bytes", DS_TYPE_DERIVE);
-
-    redis_db_stats(rn->name, rr->str);
-
-    for (redis_query_t *rq = rn->queries; rq != NULL; rq = rq->next)
-      redis_handle_query(rh, rn, rq);
-
-  redis_fail:
-    if (rr != NULL)
-      freeReplyObject(rr);
-    redisFree(rh);
+    /* Null-terminate command token */
+    values[0] = '\0';
+    command = line + strlen("cmdstat_");
+    values++;
+
+    /* parse values */
+    /* cmdstat_publish:calls=20795774,usec=111039258,usec_per_call=5.34 */
+    char *field;
+    char *saveptr_field = NULL;
+    while ((field = strtok_r(values, "=", &saveptr_field)) != NULL) {
+      values = NULL;
+
+      const char *type;
+      /* only these are supported */
+      if (strcmp(field, "calls") == 0)
+        type = "commands";
+      else if (strcmp(field, "usec") == 0)
+        type = "redis_command_cputime";
+      else
+        continue;
+
+      if ((field = strtok_r(NULL, ",", &saveptr_field)) == NULL)
+        continue;
+
+      char *endptr = NULL;
+      errno = 0;
+      derive_t value = strtoll(field, &endptr, 0);
+
+      if ((endptr == field) || (errno != 0))
+        continue;
+
+      redis_submit(rn->name, type, command, (value_t){.derive = value});
+    }
+  }
+  freeReplyObject(rr);
+} /* void redis_read_command_stats */
+
+static int redis_read(user_data_t *user_data) /* {{{ */
+{
+  redis_node_t *rn = user_data->data;
+
+#if COLLECT_DEBUG
+  if (rn->socket)
+    DEBUG("redis plugin: querying info from node `%s' (%s).", rn->name,
+          rn->socket);
+  else
+    DEBUG("redis plugin: querying info from node `%s' (%s:%d).", rn->name,
+          rn->host, rn->port);
+#endif
+
+  redis_check_connection(rn);
+
+  if (!rn->redisContext) /* no connection */
+    return -1;
+
+  redis_read_server_info(rn);
+
+  if (!rn->redisContext) /* connection lost */
+    return -1;
+
+  if (rn->report_command_stats) {
+    redis_read_command_stats(rn);
+
+    if (!rn->redisContext) /* connection lost */
+      return -1;
+  }
+
+  for (redis_query_t *rq = rn->queries; rq != NULL; rq = rq->next) {
+    redis_handle_query(rn, rq);
+    if (!rn->redisContext) /* connection lost */
+      return -1;
   }
 
   return 0;
@@ -505,8 +812,5 @@ void module_register(void) /* {{{ */
 {
   plugin_register_complex_config("redis", redis_config);
   plugin_register_init("redis", redis_init);
-  plugin_register_read("redis", redis_read);
-  /* TODO: plugin_register_write: one redis list per value id with
-   * X elements */
 }
 /* }}} */
index c0d5ef7..1286805 100644 (file)
@@ -45,6 +45,7 @@ struct cr_data_s {
   bool collect_memory;
   bool collect_df;
   bool collect_disk;
+  bool collect_health;
 };
 typedef struct cr_data_s cr_data_t;
 
@@ -140,9 +141,17 @@ static void submit_regtable(cr_data_t *rd, /* {{{ */
   if (r == NULL)
     return;
 
+  const char *name = r->radio_name;
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 3)
+  if (name == NULL)
+    name = r->mac_address;
+#endif
+  if (name == NULL)
+    name = "default";
+
   /*** RX ***/
   snprintf(type_instance, sizeof(type_instance), "%s-%s-rx", r->interface,
-           r->radio_name ? r->radio_name : "default");
+           name);
   cr_submit_gauge(rd, "bitrate", type_instance,
                   (gauge_t)(1000000.0 * r->rx_rate));
   cr_submit_gauge(rd, "signal_power", type_instance,
@@ -151,7 +160,7 @@ static void submit_regtable(cr_data_t *rd, /* {{{ */
 
   /*** TX ***/
   snprintf(type_instance, sizeof(type_instance), "%s-%s-tx", r->interface,
-           r->radio_name ? r->radio_name : "default");
+           name);
   cr_submit_gauge(rd, "bitrate", type_instance,
                   (gauge_t)(1000000.0 * r->tx_rate));
   cr_submit_gauge(rd, "signal_power", type_instance,
@@ -159,8 +168,7 @@ static void submit_regtable(cr_data_t *rd, /* {{{ */
   cr_submit_gauge(rd, "signal_quality", type_instance, (gauge_t)r->tx_ccq);
 
   /*** RX / TX ***/
-  snprintf(type_instance, sizeof(type_instance), "%s-%s", r->interface,
-           r->radio_name ? r->radio_name : "default");
+  snprintf(type_instance, sizeof(type_instance), "%s-%s", r->interface, name);
   cr_submit_io(rd, "if_octets", type_instance, (derive_t)r->rx_bytes,
                (derive_t)r->tx_bytes);
   cr_submit_gauge(rd, "snr", type_instance, (gauge_t)r->signal_to_noise);
@@ -205,13 +213,31 @@ static int handle_system_resource(__attribute__((unused))
   }
 
   if (rd->collect_disk) {
-    cr_submit_counter(rd, "counter", "secors_written",
+    cr_submit_counter(rd, "counter", "sectors_written",
                       (derive_t)r->write_sect_total);
     cr_submit_gauge(rd, "gauge", "bad_blocks", (gauge_t)r->bad_blocks);
   }
 
   return 0;
 } /* }}} int handle_system_resource */
+
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 3)
+static int handle_system_health(__attribute__((unused))
+                                ros_connection_t *c, /* {{{ */
+                                const ros_system_health_t *r,
+                                __attribute__((unused)) void *user_data) {
+
+  if ((r == NULL) || (user_data == NULL))
+    return EINVAL;
+
+  cr_data_t *rd = user_data;
+
+  cr_submit_gauge(rd, "voltage", "system", (gauge_t)r->voltage);
+  cr_submit_gauge(rd, "temperature", "system", (gauge_t)r->temperature);
+
+  return 0;
+} /* }}} int handle_system_health */
+#endif
 #endif
 
 static int cr_read(user_data_t *user_data) /* {{{ */
@@ -272,6 +298,19 @@ static int cr_read(user_data_t *user_data) /* {{{ */
       return -1;
     }
   }
+
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 3)
+  if (rd->collect_health) {
+    status = ros_system_health(rd->connection, handle_system_health,
+                               /* user data = */ rd);
+    if (status != 0) {
+      ERROR("routeros plugin: ros_system_health failed: %s", STRERROR(status));
+      ros_disconnect(rd->connection);
+      rd->connection = NULL;
+      return -1;
+    }
+  }
+#endif
 #endif
 
   return 0;
@@ -333,6 +372,10 @@ static int cr_config_router(oconfig_item_t *ci) /* {{{ */
       cf_util_get_boolean(child, &router_data->collect_df);
     else if (strcasecmp("CollectDisk", child->key) == 0)
       cf_util_get_boolean(child, &router_data->collect_disk);
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 3)
+    else if (strcasecmp("CollectHealth", child->key) == 0)
+      cf_util_get_boolean(child, &router_data->collect_health);
+#endif
 #endif
     else {
       WARNING("routeros plugin: Unknown config option `%s'.", child->key);
@@ -355,7 +398,27 @@ static int cr_config_router(oconfig_item_t *ci) /* {{{ */
       status = -1;
     }
 
-    if (!router_data->collect_interface && !router_data->collect_regtable) {
+    int report = 0;
+    if (router_data->collect_interface)
+      report++;
+    if (router_data->collect_regtable)
+      report++;
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 0)
+    if (router_data->collect_cpu_load)
+      report++;
+    if (router_data->collect_memory)
+      report++;
+    if (router_data->collect_df)
+      report++;
+    if (router_data->collect_disk)
+      report++;
+#if ROS_VERSION >= ROS_VERSION_ENCODE(1, 1, 3)
+    if (router_data->collect_health)
+      report++;
+#endif
+#endif
+
+    if (!report) {
       ERROR("routeros plugin: No `Collect*' option within a `Router' block. "
             "What statistics should I collect?");
       status = -1;
index 5c87a43..f6290d7 100644 (file)
@@ -624,6 +624,7 @@ static int rrd_cache_insert(const char *filename, const char *value,
   if ((status != 0) || (rc == NULL)) {
     rc = malloc(sizeof(*rc));
     if (rc == NULL) {
+      ERROR("rrdtool plugin: malloc failed: %s", STRERRNO);
       pthread_mutex_unlock(&cache_lock);
       return -1;
     }
@@ -790,17 +791,22 @@ static int rrd_write(const data_set_t *ds, const value_list_t *vl,
   }
 
   char filename[PATH_MAX];
-  if (value_list_to_filename(filename, sizeof(filename), vl) != 0)
+  if (value_list_to_filename(filename, sizeof(filename), vl) != 0) {
+    ERROR("rrdtool plugin: failed to build filename");
     return -1;
+  }
 
   char values[32 * (ds->ds_num + 1)];
-  if (value_list_to_string(values, sizeof(values), ds, vl) != 0)
+  if (value_list_to_string(values, sizeof(values), ds, vl) != 0) {
+    ERROR("rrdtool plugin: failed to build values string");
     return -1;
+  }
 
   struct stat statbuf = {0};
   if (stat(filename, &statbuf) == -1) {
     if (errno == ENOENT) {
       if (cu_rrd_create_file(filename, ds, vl, &rrdcreate_config) != 0) {
+        ERROR("rrdtool plugin: cu_rrd_create_file (%s) failed.", filename);
         return -1;
       } else if (rrdcreate_config.async) {
         return 0;
index 41cccf1..6106df3 100644 (file)
@@ -368,6 +368,9 @@ static int sensors_load_conf(void) {
 #if SENSORS_API_VERSION >= 0x402
           (feature->type != SENSORS_FEATURE_CURR) &&
 #endif
+#if SENSORS_API_VERSION >= 0x431
+          (feature->type != SENSORS_FEATURE_HUMIDITY) &&
+#endif
           (feature->type != SENSORS_FEATURE_POWER)) {
         DEBUG("sensors plugin: sensors_load_conf: "
               "Ignoring feature `%s', "
@@ -387,6 +390,9 @@ static int sensors_load_conf(void) {
 #if SENSORS_API_VERSION >= 0x402
             (subfeature->type != SENSORS_SUBFEATURE_CURR_INPUT) &&
 #endif
+#if SENSORS_API_VERSION >= 0x431
+            (subfeature->type != SENSORS_SUBFEATURE_HUMIDITY_INPUT) &&
+#endif
             (subfeature->type != SENSORS_SUBFEATURE_POWER_INPUT))
           continue;
 
@@ -521,6 +527,10 @@ static int sensors_read(void) {
     else if (fl->feature->type == SENSORS_FEATURE_CURR)
       type = "current";
 #endif
+#if SENSORS_API_VERSION >= 0x431
+    else if (fl->feature->type == SENSORS_FEATURE_HUMIDITY)
+      type = "humidity";
+#endif
     else
       continue;
 
index 7c3ebc8..af26fbd 100644 (file)
@@ -29,6 +29,7 @@
 #include "common.h"
 #include "plugin.h"
 #include "utils_complain.h"
+#include "utils_ignorelist.h"
 
 #include <net-snmp/net-snmp-config.h>
 #include <net-snmp/net-snmp-includes.h>
@@ -44,18 +45,24 @@ struct oid_s {
 };
 typedef struct oid_s oid_t;
 
-union instance_u {
-  char string[DATA_MAX_NAME_LEN];
+struct instance_s {
+  bool configured;
   oid_t oid;
+  char *prefix;
+  char *value;
 };
-typedef union instance_u instance_t;
+typedef struct instance_s instance_t;
 
 struct data_definition_s {
   char *name; /* used to reference this from the `Collect' option */
   char *type; /* used to find the data_set */
   bool is_table;
-  instance_t instance;
-  char *instance_prefix;
+  instance_t type_instance;
+  instance_t plugin_instance;
+  instance_t host;
+  oid_t filter_oid;
+  ignorelist_t *ignorelist;
+  char *plugin_name;
   oid_t *values;
   size_t values_len;
   double scale;
@@ -90,7 +97,6 @@ struct host_definition_s {
 
   void *sess_handle;
   c_complain_t complaint;
-  cdtime_t interval;
   data_definition_t **data_list;
   int data_list_len;
 };
@@ -98,19 +104,28 @@ typedef struct host_definition_s host_definition_t;
 
 /* These two types are used to cache values in `csnmp_read_table' to handle
  * gaps in tables. */
-struct csnmp_list_instances_s {
+struct csnmp_cell_char_s {
   oid_t suffix;
-  char instance[DATA_MAX_NAME_LEN];
-  struct csnmp_list_instances_s *next;
+  char value[DATA_MAX_NAME_LEN];
+  struct csnmp_cell_char_s *next;
 };
-typedef struct csnmp_list_instances_s csnmp_list_instances_t;
+typedef struct csnmp_cell_char_s csnmp_cell_char_t;
 
-struct csnmp_table_values_s {
+struct csnmp_cell_value_s {
   oid_t suffix;
   value_t value;
-  struct csnmp_table_values_s *next;
+  struct csnmp_cell_value_s *next;
 };
-typedef struct csnmp_table_values_s csnmp_table_values_t;
+typedef struct csnmp_cell_value_s csnmp_cell_value_t;
+
+typedef enum {
+  OID_TYPE_SKIP = 0,
+  OID_TYPE_VARIABLE,
+  OID_TYPE_TYPEINSTANCE,
+  OID_TYPE_PLUGININSTANCE,
+  OID_TYPE_HOST,
+  OID_TYPE_FILTER,
+} csnmp_oid_type_t;
 
 /*
  * Private variables
@@ -199,6 +214,22 @@ static void csnmp_host_definition_destroy(void *arg) /* {{{ */
   sfree(hd);
 } /* }}} void csnmp_host_definition_destroy */
 
+static void csnmp_data_definition_destroy(data_definition_t *dd) {
+  sfree(dd->name);
+  sfree(dd->type);
+  sfree(dd->plugin_name);
+  sfree(dd->plugin_instance.prefix);
+  sfree(dd->plugin_instance.value);
+  sfree(dd->type_instance.prefix);
+  sfree(dd->type_instance.value);
+  sfree(dd->host.prefix);
+  sfree(dd->host.value);
+  sfree(dd->values);
+  sfree(dd->ignores);
+  ignorelist_free(dd->ignorelist);
+  sfree(dd);
+} /* void csnmp_data_definition_destroy */
+
 /* Many functions to handle the configuration. {{{ */
 /* First there are many functions which do configuration stuff. It's a big
  * bloated and messy, I'm afraid. */
@@ -208,8 +239,7 @@ static void csnmp_host_definition_destroy(void *arg) /* {{{ */
  *  csnmp_config
  *  +-> call_snmp_init_once
  *  +-> csnmp_config_add_data
- *  !   +-> csnmp_config_add_data_instance
- *  !   +-> csnmp_config_add_data_instance_prefix
+ *  !   +-> csnmp_config_configure_data_instance
  *  !   +-> csnmp_config_add_data_values
  *  +-> csnmp_config_add_host
  *      +-> csnmp_config_add_host_version
@@ -226,45 +256,29 @@ static void call_snmp_init_once(void) {
   have_init = 1;
 } /* void call_snmp_init_once */
 
-static int csnmp_config_add_data_instance(data_definition_t *dd,
-                                          oconfig_item_t *ci) {
+static int csnmp_config_configure_data_instance(instance_t *instance,
+                                                oconfig_item_t *ci) {
   char buffer[DATA_MAX_NAME_LEN];
-  int status;
 
-  status = cf_util_get_string_buffer(ci, buffer, sizeof(buffer));
+  int status = cf_util_get_string_buffer(ci, buffer, sizeof(buffer));
   if (status != 0)
     return status;
 
-  if (dd->is_table) {
-    /* Instance is an OID */
-    dd->instance.oid.oid_len = MAX_OID_LEN;
+  instance->configured = true;
 
-    if (!read_objid(buffer, dd->instance.oid.oid, &dd->instance.oid.oid_len)) {
-      ERROR("snmp plugin: read_objid (%s) failed.", buffer);
-      return -1;
-    }
-  } else {
-    /* Instance is a simple string */
-    sstrncpy(dd->instance.string, buffer, sizeof(dd->instance.string));
+  if (strlen(buffer) == 0) {
+    return 0;
   }
 
-  return 0;
-} /* int csnmp_config_add_data_instance */
+  instance->oid.oid_len = MAX_OID_LEN;
 
-static int csnmp_config_add_data_instance_prefix(data_definition_t *dd,
-                                                 oconfig_item_t *ci) {
-  int status;
-
-  if (!dd->is_table) {
-    WARNING("snmp plugin: data %s: InstancePrefix is ignored when `Table' "
-            "is set to `false'.",
-            dd->name);
+  if (!read_objid(buffer, instance->oid.oid, &instance->oid.oid_len)) {
+    ERROR("snmp plugin: read_objid (%s) failed.", buffer);
     return -1;
   }
 
-  status = cf_util_get_string(ci, &dd->instance_prefix);
-  return status;
-} /* int csnmp_config_add_data_instance_prefix */
+  return 0;
+} /* int csnmp_config_configure_data_instance */
 
 static int csnmp_config_add_data_values(data_definition_t *dd,
                                         oconfig_item_t *ci) {
@@ -301,7 +315,7 @@ static int csnmp_config_add_data_values(data_definition_t *dd,
   }
 
   return 0;
-} /* int csnmp_config_add_data_instance */
+} /* int csnmp_config_configure_data_instance */
 
 static int csnmp_config_add_data_blacklist(data_definition_t *dd,
                                            oconfig_item_t *ci) {
@@ -315,9 +329,6 @@ static int csnmp_config_add_data_blacklist(data_definition_t *dd,
     }
   }
 
-  dd->ignores_len = 0;
-  dd->ignores = NULL;
-
   for (int i = 0; i < ci->values_num; ++i) {
     if (strarray_add(&(dd->ignores), &(dd->ignores_len),
                      ci->values[i].value.string) != 0) {
@@ -329,6 +340,41 @@ 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_filter_values(data_definition_t *data,
+                                               oconfig_item_t *ci) {
+  if (ci->values_num < 1) {
+    WARNING("snmp plugin: `FilterValues' needs at least one argument.");
+    return -1;
+  }
+
+  for (int i = 0; i < ci->values_num; i++) {
+    if (ci->values[i].type != OCONFIG_TYPE_STRING) {
+      WARNING("snmp plugin: All arguments to `FilterValues' must be strings.");
+      return -1;
+    }
+    ignorelist_add(data->ignorelist, ci->values[i].value.string);
+  }
+
+  return 0;
+} /* int csnmp_config_add_data_filter_values */
+
+static int csnmp_config_add_data_filter_oid(data_definition_t *data,
+                                            oconfig_item_t *ci) {
+
+  char buffer[DATA_MAX_NAME_LEN];
+  int status = cf_util_get_string_buffer(ci, buffer, sizeof(buffer));
+  if (status != 0)
+    return status;
+
+  data->filter_oid.oid_len = MAX_OID_LEN;
+
+  if (!read_objid(buffer, data->filter_oid.oid, &data->filter_oid.oid_len)) {
+    ERROR("snmp plugin: read_objid (%s) failed.", buffer);
+    return -1;
+  }
+  return 0;
+} /* int csnmp_config_add_data_filter_oid */
+
 static int csnmp_config_add_data(oconfig_item_t *ci) {
   data_definition_t *dd = calloc(1, sizeof(*dd));
   if (dd == NULL)
@@ -342,6 +388,22 @@ static int csnmp_config_add_data(oconfig_item_t *ci) {
 
   dd->scale = 1.0;
   dd->shift = 0.0;
+  dd->ignores_len = 0;
+  dd->ignores = NULL;
+
+  dd->ignorelist = ignorelist_create(/* invert = */ 1);
+  if (dd->ignorelist == NULL) {
+    sfree(dd->name);
+    sfree(dd);
+    ERROR("snmp plugin: ignorelist_create() failed.");
+    return ENOMEM;
+  }
+
+  dd->plugin_name = strdup("snmp");
+  if (dd->plugin_name == NULL) {
+    ERROR("snmp plugin: Can't allocate memory");
+    return ENOMEM;
+  }
 
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *option = ci->children + i;
@@ -350,10 +412,47 @@ static int csnmp_config_add_data(oconfig_item_t *ci) {
       status = cf_util_get_string(option, &dd->type);
     else if (strcasecmp("Table", option->key) == 0)
       status = cf_util_get_boolean(option, &dd->is_table);
-    else if (strcasecmp("Instance", option->key) == 0)
-      status = csnmp_config_add_data_instance(dd, option);
-    else if (strcasecmp("InstancePrefix", option->key) == 0)
-      status = csnmp_config_add_data_instance_prefix(dd, option);
+    else if (strcasecmp("Plugin", option->key) == 0)
+      status = cf_util_get_string(option, &dd->plugin_name);
+    else if (strcasecmp("Instance", option->key) == 0) {
+      if (dd->is_table) {
+        /* Instance is OID */
+        WARNING(
+            "snmp plugin: data %s: Option `Instance' is deprecated, please use "
+            "option `TypeInstanceOID'.",
+            dd->name);
+        status =
+            csnmp_config_configure_data_instance(&dd->type_instance, option);
+      } else {
+        /* Instance is a simple string */
+        WARNING(
+            "snmp plugin: data %s: Option `Instance' is deprecated, please use "
+            "option `TypeInstance'.",
+            dd->name);
+        status = cf_util_get_string(option, &dd->type_instance.value);
+      }
+    } else if (strcasecmp("InstancePrefix", option->key) == 0) {
+      WARNING("snmp plugin: data %s: Option `InstancePrefix' is deprecated, "
+              "please use option `TypeInstancePrefix'.",
+              dd->name);
+      status = cf_util_get_string(option, &dd->type_instance.prefix);
+    } else if (strcasecmp("PluginInstance", option->key) == 0)
+      status = cf_util_get_string(option, &dd->plugin_instance.value);
+    else if (strcasecmp("TypeInstance", option->key) == 0)
+      status = cf_util_get_string(option, &dd->type_instance.value);
+    else if (strcasecmp("PluginInstanceOID", option->key) == 0)
+      status =
+          csnmp_config_configure_data_instance(&dd->plugin_instance, option);
+    else if (strcasecmp("PluginInstancePrefix", option->key) == 0)
+      status = cf_util_get_string(option, &dd->plugin_instance.prefix);
+    else if (strcasecmp("TypeInstanceOID", option->key) == 0)
+      status = csnmp_config_configure_data_instance(&dd->type_instance, option);
+    else if (strcasecmp("TypeInstancePrefix", option->key) == 0)
+      status = cf_util_get_string(option, &dd->type_instance.prefix);
+    else if (strcasecmp("HostOID", option->key) == 0)
+      status = csnmp_config_configure_data_instance(&dd->host, option);
+    else if (strcasecmp("HostPrefix", option->key) == 0)
+      status = cf_util_get_string(option, &dd->host.prefix);
     else if (strcasecmp("Values", option->key) == 0)
       status = csnmp_config_add_data_values(dd, option);
     else if (strcasecmp("Shift", option->key) == 0)
@@ -364,8 +463,18 @@ static int csnmp_config_add_data(oconfig_item_t *ci) {
       status = csnmp_config_add_data_blacklist(dd, option);
     else if (strcasecmp("InvertMatch", option->key) == 0)
       status = cf_util_get_boolean(option, &dd->invert_match);
-    else {
-      WARNING("snmp plugin: Option `%s' not allowed here.", option->key);
+    else if (strcasecmp("FilterOID", option->key) == 0) {
+      status = csnmp_config_add_data_filter_oid(dd, option);
+    } else if (strcasecmp("FilterValues", option->key) == 0) {
+      status = csnmp_config_add_data_filter_values(dd, option);
+    } else if (strcasecmp("FilterIgnoreSelected", option->key) == 0) {
+      bool t;
+      status = cf_util_get_boolean(option, &t);
+      if (status == 0)
+        ignorelist_set_invert(dd->ignorelist, /* invert = */ !t);
+    } else {
+      WARNING("snmp plugin: data %s: Option `%s' not allowed here.", dd->name,
+              option->key);
       status = -1;
     }
 
@@ -374,6 +483,65 @@ static int csnmp_config_add_data(oconfig_item_t *ci) {
   } /* for (ci->children) */
 
   while (status == 0) {
+    if (dd->is_table) {
+      /* Set type_instance to SUBID by default */
+      if (!dd->plugin_instance.configured && !dd->host.configured)
+        dd->type_instance.configured = true;
+
+      if (dd->plugin_instance.value && dd->plugin_instance.configured) {
+        WARNING(
+            "snmp plugin: data %s: Option `PluginInstance' will be ignored.",
+            dd->name);
+      }
+      if (dd->type_instance.value && dd->type_instance.configured) {
+        WARNING("snmp plugin: data %s: Option `TypeInstance' will be ignored.",
+                dd->name);
+      }
+      if (dd->type_instance.prefix && !dd->type_instance.configured) {
+        WARNING("snmp plugin: data %s: Option `TypeInstancePrefix' will be "
+                "ignored.",
+                dd->name);
+      }
+      if (dd->plugin_instance.prefix && !dd->plugin_instance.configured) {
+        WARNING("snmp plugin: data %s: Option `PluginInstancePrefix' will be "
+                "ignored.",
+                dd->name);
+      }
+      if (dd->host.prefix && !dd->host.configured) {
+        WARNING("snmp plugin: data %s: Option `HostPrefix' will be ignored.",
+                dd->name);
+      }
+    } else {
+      if (dd->plugin_instance.oid.oid_len > 0) {
+        WARNING("snmp plugin: data %s: Option `PluginInstanceOID' will be "
+                "ignored.",
+                dd->name);
+      }
+      if (dd->type_instance.oid.oid_len > 0) {
+        WARNING(
+            "snmp plugin: data %s: Option `TypeInstanceOID' will be ignored.",
+            dd->name);
+      }
+      if (dd->type_instance.prefix) {
+        WARNING("snmp plugin: data %s: Option `TypeInstancePrefix' is ignored "
+                "when `Table' "
+                "set to `false'.",
+                dd->name);
+      }
+      if (dd->plugin_instance.prefix) {
+        WARNING("snmp plugin: data %s: Option `PluginInstancePrefix' is "
+                "ignored when "
+                "`Table' set to `false'.",
+                dd->name);
+      }
+      if (dd->host.prefix) {
+        WARNING(
+            "snmp plugin: data %s: Option `HostPrefix' is ignored when `Table' "
+            "set to `false'.",
+            dd->name);
+      }
+    }
+
     if (dd->type == NULL) {
       WARNING("snmp plugin: `Type' not given for data `%s'", dd->name);
       status = -1;
@@ -389,18 +557,26 @@ static int csnmp_config_add_data(oconfig_item_t *ci) {
   } /* while (status == 0) */
 
   if (status != 0) {
-    sfree(dd->name);
-    sfree(dd->instance_prefix);
-    sfree(dd->values);
-    sfree(dd->ignores);
-    sfree(dd);
+    csnmp_data_definition_destroy(dd);
     return -1;
   }
 
   DEBUG("snmp plugin: dd = { name = %s, type = %s, is_table = %s, values_len = "
-        "%" PRIsz " }",
+        "%" PRIsz ",",
         dd->name, dd->type, (dd->is_table) ? "true" : "false", dd->values_len);
 
+  DEBUG("snmp plugin:        plugin_instance = %s, type_instance = %s,",
+        dd->plugin_instance.value, dd->type_instance.value);
+
+  DEBUG("snmp plugin:        type_instance_by_oid = %s, plugin_instance_by_oid "
+        "= %s }",
+        (dd->type_instance.oid.oid_len > 0)
+            ? "true"
+            : ((dd->type_instance.configured) ? "SUBID" : "false"),
+        (dd->plugin_instance.oid.oid_len > 0)
+            ? "true"
+            : ((dd->plugin_instance.configured) ? "SUBID" : "false"));
+
   if (data_head == NULL)
     data_head = dd;
   else {
@@ -433,7 +609,7 @@ static int csnmp_config_add_host_version(host_definition_t *hd,
   hd->version = version;
 
   return 0;
-} /* int csnmp_config_add_host_address */
+} /* int csnmp_config_add_host_version */
 
 static int csnmp_config_add_host_collect(host_definition_t *host,
                                          oconfig_item_t *ci) {
@@ -566,6 +742,7 @@ static int csnmp_config_add_host(oconfig_item_t *ci) {
   int status = 0;
 
   /* Registration stuff. */
+  cdtime_t interval = 0;
   char cb_name[DATA_MAX_NAME_LEN];
 
   hd = calloc(1, sizeof(*hd));
@@ -581,7 +758,6 @@ 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;
@@ -603,7 +779,7 @@ static int csnmp_config_add_host(oconfig_item_t *ci) {
     else if (strcasecmp("Collect", option->key) == 0)
       status = csnmp_config_add_host_collect(hd, option);
     else if (strcasecmp("Interval", option->key) == 0)
-      status = cf_util_get_cdtime(option, &hd->interval);
+      status = cf_util_get_cdtime(option, &interval);
     else if (strcasecmp("Username", option->key) == 0)
       status = cf_util_get_string(option, &hd->username);
     else if (strcasecmp("AuthProtocol", option->key) == 0)
@@ -698,7 +874,7 @@ static int csnmp_config_add_host(oconfig_item_t *ci) {
   snprintf(cb_name, sizeof(cb_name), "snmp-%s", hd->name);
 
   status = plugin_register_complex_read(
-      /* group = */ NULL, cb_name, csnmp_read_host, hd->interval,
+      /* group = */ NULL, cb_name, csnmp_read_host, interval,
       &(user_data_t){
           .data = hd, .free_func = csnmp_host_definition_destroy,
       });
@@ -821,8 +997,8 @@ static void csnmp_host_open_session(host_definition_t *host) {
 
 /* TODO: Check if negative values wrap around. Problem: negative temperatures.
  */
-static value_t csnmp_value_list_to_value(struct variable_list *vl, int type,
-                                         double scale, double shift,
+static value_t csnmp_value_list_to_value(const struct variable_list *vl,
+                                         int type, double scale, double shift,
                                          const char *host_name,
                                          const char *data_name) {
   value_t ret;
@@ -1025,96 +1201,99 @@ static int csnmp_strvbcopy(char *dst, /* {{{ */
   return 0;
 } /* }}} int csnmp_strvbcopy */
 
-static int csnmp_instance_list_add(csnmp_list_instances_t **head,
-                                   csnmp_list_instances_t **tail,
-                                   const struct snmp_pdu *res,
-                                   const host_definition_t *hd,
-                                   const data_definition_t *dd) {
-  csnmp_list_instances_t *il;
-  struct variable_list *vb;
-  oid_t vb_name;
-  int status;
+static csnmp_cell_char_t *csnmp_get_char_cell(const struct variable_list *vb,
+                                              const oid_t *root_oid,
+                                              const host_definition_t *hd,
+                                              const data_definition_t *dd) {
 
-  /* Set vb on the last variable */
-  for (vb = res->variables; (vb != NULL) && (vb->next_variable != NULL);
-       vb = vb->next_variable)
-    /* do nothing */;
   if (vb == NULL)
-    return -1;
+    return NULL;
 
-  csnmp_oid_init(&vb_name, vb->name, vb->name_length);
-
-  il = calloc(1, sizeof(*il));
+  csnmp_cell_char_t *il = calloc(1, sizeof(*il));
   if (il == NULL) {
     ERROR("snmp plugin: calloc failed.");
-    return -1;
+    return NULL;
   }
   il->next = NULL;
 
-  status = csnmp_oid_suffix(&il->suffix, &vb_name, &dd->instance.oid);
-  if (status != 0) {
+  oid_t vb_name;
+  csnmp_oid_init(&vb_name, vb->name, vb->name_length);
+
+  if (csnmp_oid_suffix(&il->suffix, &vb_name, root_oid) != 0) {
     sfree(il);
-    return status;
+    return NULL;
   }
 
-  /* Get instance name */
+  /* Get value */
   if ((vb->type == ASN_OCTET_STR) || (vb->type == ASN_BIT_STR) ||
       (vb->type == ASN_IPADDRESS)) {
-    char *ptr;
-
-    csnmp_strvbcopy(il->instance, vb, sizeof(il->instance));
-    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) {
-          sfree(il);
-          return 0;
-        } else {
-          is_matched = 1;
-          break;
-        }
-      }
-    }
-    if (dd->invert_match && !is_matched) {
-      sfree(il);
-      return 0;
-    }
-    for (ptr = il->instance; *ptr != '\0'; ptr++) {
-      if ((*ptr > 0) && (*ptr < 32))
-        *ptr = ' ';
-      else if (*ptr == '/')
-        *ptr = '_';
-    }
-    DEBUG("snmp plugin: il->instance = `%s';", il->instance);
+
+    csnmp_strvbcopy(il->value, vb, sizeof(il->value));
+
   } else {
     value_t val = csnmp_value_list_to_value(
         vb, DS_TYPE_COUNTER,
         /* scale = */ 1.0, /* shift = */ 0.0, hd->name, dd->name);
-    snprintf(il->instance, sizeof(il->instance), "%" PRIu64,
-             (uint64_t)val.counter);
+    snprintf(il->value, sizeof(il->value), "%" PRIu64, (uint64_t)val.counter);
   }
 
-  /* TODO: Debugging output */
+  return il;
+} /* csnmp_cell_char_t csnmp_get_char_cell */
 
+static void csnmp_cells_append(csnmp_cell_char_t **head,
+                               csnmp_cell_char_t **tail,
+                               csnmp_cell_char_t *il) {
   if (*head == NULL)
     *head = il;
   else
     (*tail)->next = il;
   *tail = il;
-
+} /* void csnmp_cells_append */
+
+static bool csnmp_ignore_instance(csnmp_cell_char_t *cell,
+                                  const data_definition_t *dd) {
+  bool is_matched = 0;
+  for (uint32_t i = 0; i < dd->ignores_len; i++) {
+    int status = fnmatch(dd->ignores[i], cell->value, 0);
+    if (status == 0) {
+      if (!dd->invert_match) {
+        return 1;
+      } else {
+        is_matched = 1;
+        break;
+      }
+    }
+  }
+  if (dd->invert_match && !is_matched) {
+    return 1;
+  }
   return 0;
-} /* int csnmp_instance_list_add */
+} /* bool csnmp_ignore_instance */
+
+static void csnmp_cell_replace_reserved_chars(csnmp_cell_char_t *cell) {
+  for (char *ptr = cell->value; *ptr != '\0'; ptr++) {
+    if ((*ptr > 0) && (*ptr < 32))
+      *ptr = ' ';
+    else if (*ptr == '/')
+      *ptr = '_';
+  }
+} /* void csnmp_cell_replace_reserved_chars */
 
 static int csnmp_dispatch_table(host_definition_t *host,
                                 data_definition_t *data,
-                                csnmp_list_instances_t *instance_list,
-                                csnmp_table_values_t **value_table) {
+                                csnmp_cell_char_t *type_instance_cells,
+                                csnmp_cell_char_t *plugin_instance_cells,
+                                csnmp_cell_char_t *hostname_cells,
+                                csnmp_cell_char_t *filter_cells,
+                                csnmp_cell_value_t **value_cells) {
   const data_set_t *ds;
   value_list_t vl = VALUE_LIST_INIT;
 
-  csnmp_list_instances_t *instance_list_ptr;
-  csnmp_table_values_t *value_table_ptr[data->values_len];
+  csnmp_cell_char_t *type_instance_cell_ptr = type_instance_cells;
+  csnmp_cell_char_t *plugin_instance_cell_ptr = plugin_instance_cells;
+  csnmp_cell_char_t *hostname_cell_ptr = hostname_cells;
+  csnmp_cell_char_t *filter_cell_ptr = filter_cells;
+  csnmp_cell_value_t *value_cell_ptr[data->values_len];
 
   size_t i;
   bool have_more;
@@ -1128,32 +1307,28 @@ static int csnmp_dispatch_table(host_definition_t *host,
   assert(ds->ds_num == data->values_len);
   assert(data->values_len > 0);
 
-  instance_list_ptr = instance_list;
-
   for (i = 0; i < data->values_len; i++)
-    value_table_ptr[i] = value_table[i];
+    value_cell_ptr[i] = value_cells[i];
 
-  sstrncpy(vl.host, host->name, sizeof(vl.host));
-  sstrncpy(vl.plugin, "snmp", sizeof(vl.plugin));
-
-  vl.interval = host->interval;
+  sstrncpy(vl.plugin, data->plugin_name, sizeof(vl.plugin));
+  sstrncpy(vl.type, data->type, sizeof(vl.type));
 
   have_more = 1;
   while (have_more) {
     bool suffix_skipped = 0;
 
     /* Determine next suffix to handle. */
-    if (instance_list != NULL) {
-      if (instance_list_ptr == NULL) {
+    if (type_instance_cells != NULL) {
+      if (type_instance_cell_ptr == NULL) {
         have_more = 0;
         continue;
       }
 
-      memcpy(&current_suffix, &instance_list_ptr->suffix,
+      memcpy(&current_suffix, &type_instance_cell_ptr->suffix,
              sizeof(current_suffix));
     } else {
       /* no instance configured */
-      csnmp_table_values_t *ptr = value_table_ptr[0];
+      csnmp_cell_value_t *ptr = value_cell_ptr[0];
       if (ptr == NULL) {
         have_more = 0;
         continue;
@@ -1162,18 +1337,82 @@ static int csnmp_dispatch_table(host_definition_t *host,
       memcpy(&current_suffix, &ptr->suffix, sizeof(current_suffix));
     }
 
-    /* Update all the value_table_ptr to point at the entry with the same
+    /*
+    char oid_buffer[1024] = {0};
+    snprint_objid(oid_buffer, sizeof(oid_buffer) - 1, current_suffix.oid,
+                          current_suffix.oid_len);
+    DEBUG("SNMP PLUGIN: SUFFIX %s", oid_buffer);
+    */
+
+    /* Update plugin_instance_cell_ptr to point expected suffix */
+    if (plugin_instance_cells != NULL) {
+      while ((plugin_instance_cell_ptr != NULL) &&
+             (csnmp_oid_compare(&plugin_instance_cell_ptr->suffix,
+                                &current_suffix) < 0))
+        plugin_instance_cell_ptr = plugin_instance_cell_ptr->next;
+
+      if (plugin_instance_cell_ptr == NULL) {
+        have_more = 0;
+        continue;
+      }
+
+      if (csnmp_oid_compare(&plugin_instance_cell_ptr->suffix,
+                            &current_suffix) > 0) {
+        /* This suffix is missing in the subtree. Indicate this with the
+         * "suffix_skipped" flag and try the next instance / suffix. */
+        suffix_skipped = 1;
+      }
+    }
+
+    /* Update hostname_cell_ptr to point expected suffix */
+    if (hostname_cells != NULL) {
+      while (
+          (hostname_cell_ptr != NULL) &&
+          (csnmp_oid_compare(&hostname_cell_ptr->suffix, &current_suffix) < 0))
+        hostname_cell_ptr = hostname_cell_ptr->next;
+
+      if (hostname_cell_ptr == NULL) {
+        have_more = 0;
+        continue;
+      }
+
+      if (csnmp_oid_compare(&hostname_cell_ptr->suffix, &current_suffix) > 0) {
+        /* This suffix is missing in the subtree. Indicate this with the
+         * "suffix_skipped" flag and try the next instance / suffix. */
+        suffix_skipped = 1;
+      }
+    }
+
+    /* Update filter_cell_ptr to point expected suffix */
+    if (filter_cells != NULL) {
+      while ((filter_cell_ptr != NULL) &&
+             (csnmp_oid_compare(&filter_cell_ptr->suffix, &current_suffix) < 0))
+        filter_cell_ptr = filter_cell_ptr->next;
+
+      if (filter_cell_ptr == NULL) {
+        have_more = 0;
+        continue;
+      }
+
+      if (csnmp_oid_compare(&filter_cell_ptr->suffix, &current_suffix) > 0) {
+        /* This suffix is missing in the subtree. Indicate this with the
+         * "suffix_skipped" flag and try the next instance / suffix. */
+        suffix_skipped = 1;
+      }
+    }
+
+    /* Update all the value_cell_ptr to point at the entry with the same
      * trailing partial OID */
     for (i = 0; i < data->values_len; i++) {
       while (
-          (value_table_ptr[i] != NULL) &&
-          (csnmp_oid_compare(&value_table_ptr[i]->suffix, &current_suffix) < 0))
-        value_table_ptr[i] = value_table_ptr[i]->next;
+          (value_cell_ptr[i] != NULL) &&
+          (csnmp_oid_compare(&value_cell_ptr[i]->suffix, &current_suffix) < 0))
+        value_cell_ptr[i] = value_cell_ptr[i]->next;
 
-      if (value_table_ptr[i] == NULL) {
+      if (value_cell_ptr[i] == NULL) {
         have_more = 0;
         break;
-      } else if (csnmp_oid_compare(&value_table_ptr[i]->suffix,
+      } else if (csnmp_oid_compare(&value_cell_ptr[i]->suffix,
                                    &current_suffix) > 0) {
         /* This suffix is missing in the subtree. Indicate this with the
          * "suffix_skipped" flag and try the next instance / suffix. */
@@ -1187,43 +1426,98 @@ static int csnmp_dispatch_table(host_definition_t *host,
 
     /* Matching the values failed. Start from the beginning again. */
     if (suffix_skipped) {
-      if (instance_list != NULL)
-        instance_list_ptr = instance_list_ptr->next;
+      if (type_instance_cells != NULL)
+        type_instance_cell_ptr = type_instance_cell_ptr->next;
       else
-        value_table_ptr[0] = value_table_ptr[0]->next;
+        value_cell_ptr[0] = value_cell_ptr[0]->next;
 
       continue;
     }
 
-/* if we reach this line, all value_table_ptr[i] are non-NULL and are set
- * to the same subid. instance_list_ptr is either NULL or points to the
+/* if we reach this line, all value_cell_ptr[i] are non-NULL and are set
+ * to the same subid. type_instance_cell_ptr is either NULL or points to the
  * same subid, too. */
 #if COLLECT_DEBUG
     for (i = 1; i < data->values_len; i++) {
-      assert(value_table_ptr[i] != NULL);
-      assert(csnmp_oid_compare(&value_table_ptr[i - 1]->suffix,
-                               &value_table_ptr[i]->suffix) == 0);
+      assert(value_cell_ptr[i] != NULL);
+      assert(csnmp_oid_compare(&value_cell_ptr[i - 1]->suffix,
+                               &value_cell_ptr[i]->suffix) == 0);
     }
-    assert((instance_list_ptr == NULL) ||
-           (csnmp_oid_compare(&instance_list_ptr->suffix,
-                              &value_table_ptr[0]->suffix) == 0));
+    assert((type_instance_cell_ptr == NULL) ||
+           (csnmp_oid_compare(&type_instance_cell_ptr->suffix,
+                              &value_cell_ptr[0]->suffix) == 0));
+    assert((plugin_instance_cell_ptr == NULL) ||
+           (csnmp_oid_compare(&plugin_instance_cell_ptr->suffix,
+                              &value_cell_ptr[0]->suffix) == 0));
+    assert((hostname_cell_ptr == NULL) ||
+           (csnmp_oid_compare(&hostname_cell_ptr->suffix,
+                              &value_cell_ptr[0]->suffix) == 0));
+    assert((filter_cell_ptr == NULL) ||
+           (csnmp_oid_compare(&filter_cell_ptr->suffix,
+                              &value_cell_ptr[0]->suffix) == 0));
 #endif
 
-    sstrncpy(vl.type, data->type, sizeof(vl.type));
+    /* Check the value in filter column */
+    if (filter_cell_ptr &&
+        ignorelist_match(data->ignorelist, filter_cell_ptr->value) != 0) {
+      if (type_instance_cells != NULL)
+        type_instance_cell_ptr = type_instance_cell_ptr->next;
+      else
+        value_cell_ptr[0] = value_cell_ptr[0]->next;
 
-    {
+      continue;
+    }
+
+    /* set vl.host */
+    if (data->host.configured) {
       char temp[DATA_MAX_NAME_LEN];
+      if (hostname_cell_ptr == NULL)
+        csnmp_oid_to_string(temp, sizeof(temp), &current_suffix);
+      else
+        sstrncpy(temp, hostname_cell_ptr->value, sizeof(temp));
+
+      if (data->host.prefix == NULL)
+        sstrncpy(vl.host, temp, sizeof(vl.host));
+      else
+        snprintf(vl.host, sizeof(vl.host), "%s%s", data->host.prefix, temp);
+    } else {
+      sstrncpy(vl.host, host->name, sizeof(vl.host));
+    }
 
-      if (instance_list_ptr == NULL)
+    /* set vl.type_instance */
+    if (data->type_instance.configured) {
+      char temp[DATA_MAX_NAME_LEN];
+      if (type_instance_cell_ptr == NULL)
         csnmp_oid_to_string(temp, sizeof(temp), &current_suffix);
       else
-        sstrncpy(temp, instance_list_ptr->instance, sizeof(temp));
+        sstrncpy(temp, type_instance_cell_ptr->value, sizeof(temp));
 
-      if (data->instance_prefix == NULL)
+      if (data->type_instance.prefix == NULL)
         sstrncpy(vl.type_instance, temp, sizeof(vl.type_instance));
       else
         snprintf(vl.type_instance, sizeof(vl.type_instance), "%s%s",
-                 data->instance_prefix, temp);
+                 data->type_instance.prefix, temp);
+    } else if (data->type_instance.value) {
+      sstrncpy(vl.type_instance, data->type_instance.value,
+               sizeof(vl.type_instance));
+    }
+
+    /* set vl.plugin_instance */
+    if (data->plugin_instance.configured) {
+      char temp[DATA_MAX_NAME_LEN];
+      if (plugin_instance_cell_ptr == NULL)
+        csnmp_oid_to_string(temp, sizeof(temp), &current_suffix);
+      else
+        sstrncpy(temp, plugin_instance_cell_ptr->value, sizeof(temp));
+
+      if (data->plugin_instance.prefix == NULL)
+        sstrncpy(vl.plugin_instance, temp, sizeof(vl.plugin_instance));
+      else
+        snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s%s",
+                 data->plugin_instance.prefix, temp);
+    } else if (data->plugin_instance.value) {
+      sstrncpy(vl.plugin_instance, data->plugin_instance.value,
+               sizeof(vl.plugin_instance));
     }
 
     vl.values_len = data->values_len;
@@ -1231,26 +1525,21 @@ static int csnmp_dispatch_table(host_definition_t *host,
     vl.values = values;
 
     for (i = 0; i < data->values_len; i++)
-      vl.values[i] = value_table_ptr[i]->value;
+      vl.values[i] = value_cell_ptr[i]->value;
 
-    /* If we get here `vl.type_instance' and all `vl.values' have been set
-     * vl.type_instance can be empty, i.e. a blank port description on a
-     * switch if you're using IF-MIB::ifDescr as Instance.
-     */
-    if (vl.type_instance[0] != '\0')
-      plugin_dispatch_values(&vl);
+    plugin_dispatch_values(&vl);
 
     /* prevent leakage of pointer to local variable. */
     vl.values_len = 0;
     vl.values = NULL;
 
-    if (instance_list != NULL)
-      instance_list_ptr = instance_list_ptr->next;
+    if (type_instance_cells != NULL)
+      type_instance_cell_ptr = type_instance_cell_ptr->next;
     else
-      value_table_ptr[0] = value_table_ptr[0]->next;
+      value_cell_ptr[0] = value_cell_ptr[0]->next;
   } /* while (have_more) */
 
-  return (0);
+  return 0;
 } /* int csnmp_dispatch_table */
 
 static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
@@ -1260,25 +1549,43 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
 
   const data_set_t *ds;
 
-  size_t oid_list_len = data->values_len + 1;
+  size_t oid_list_len = data->values_len;
+
+  if (data->type_instance.oid.oid_len > 0)
+    oid_list_len++;
+
+  if (data->plugin_instance.oid.oid_len > 0)
+    oid_list_len++;
+
+  if (data->host.oid.oid_len > 0)
+    oid_list_len++;
+
+  if (data->filter_oid.oid_len > 0)
+    oid_list_len++;
+
   /* Holds the last OID returned by the device. We use this in the GETNEXT
    * request to proceed. */
   oid_t oid_list[oid_list_len];
   /* Set to false when an OID has left its subtree so we don't re-request it
    * again. */
-  bool oid_list_todo[oid_list_len];
+  csnmp_oid_type_t oid_list_todo[oid_list_len];
 
   int status;
   size_t i;
 
-  /* `value_list_head' and `value_list_tail' implement a linked list for each
-   * value. `instance_list_head' and `instance_list_tail' implement a linked
-   * list of
-   * instance names. This is used to jump gaps in the table. */
-  csnmp_list_instances_t *instance_list_head;
-  csnmp_list_instances_t *instance_list_tail;
-  csnmp_table_values_t **value_list_head;
-  csnmp_table_values_t **value_list_tail;
+  /* `value_list_head' and `value_cells_tail' implement a linked list for each
+   * value. `instance_cells_head' and `instance_cells_tail' implement a linked
+   * list of instance names. This is used to jump gaps in the table. */
+  csnmp_cell_char_t *type_instance_cells_head = NULL;
+  csnmp_cell_char_t *type_instance_cells_tail = NULL;
+  csnmp_cell_char_t *plugin_instance_cells_head = NULL;
+  csnmp_cell_char_t *plugin_instance_cells_tail = NULL;
+  csnmp_cell_char_t *hostname_cells_head = NULL;
+  csnmp_cell_char_t *hostname_cells_tail = NULL;
+  csnmp_cell_char_t *filter_cells_head = NULL;
+  csnmp_cell_char_t *filter_cells_tail = NULL;
+  csnmp_cell_value_t **value_cells_head;
+  csnmp_cell_value_t **value_cells_tail;
 
   DEBUG("snmp plugin: csnmp_read_table (host = %s, data = %s)", host->name,
         data->name);
@@ -1303,31 +1610,48 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
   }
   assert(data->values_len > 0);
 
+  for (i = 0; i < data->values_len; i++)
+    oid_list_todo[i] = OID_TYPE_VARIABLE;
+
   /* We need a copy of all the OIDs, because GETNEXT will destroy them. */
   memcpy(oid_list, data->values, data->values_len * sizeof(oid_t));
-  if (data->instance.oid.oid_len > 0)
-    memcpy(oid_list + data->values_len, &data->instance.oid, sizeof(oid_t));
-  else /* no InstanceFrom option specified. */
-    oid_list_len--;
 
-  for (i = 0; i < oid_list_len; i++)
-    oid_list_todo[i] = 1;
+  if (data->type_instance.oid.oid_len > 0) {
+    memcpy(oid_list + i, &data->type_instance.oid, sizeof(oid_t));
+    oid_list_todo[i] = OID_TYPE_TYPEINSTANCE;
+    i++;
+  }
+
+  if (data->plugin_instance.oid.oid_len > 0) {
+    memcpy(oid_list + i, &data->plugin_instance.oid, sizeof(oid_t));
+    oid_list_todo[i] = OID_TYPE_PLUGININSTANCE;
+    i++;
+  }
+
+  if (data->host.oid.oid_len > 0) {
+    memcpy(oid_list + i, &data->host.oid, sizeof(oid_t));
+    oid_list_todo[i] = OID_TYPE_HOST;
+    i++;
+  }
+
+  if (data->filter_oid.oid_len > 0) {
+    memcpy(oid_list + i, &data->filter_oid, sizeof(oid_t));
+    oid_list_todo[i] = OID_TYPE_FILTER;
+    i++;
+  }
 
   /* We're going to construct n linked lists, one for each "value".
-   * value_list_head will contain pointers to the heads of these linked lists,
-   * value_list_tail will contain pointers to the tail of the lists. */
-  value_list_head = calloc(data->values_len, sizeof(*value_list_head));
-  value_list_tail = calloc(data->values_len, sizeof(*value_list_tail));
-  if ((value_list_head == NULL) || (value_list_tail == NULL)) {
+   * value_cells_head will contain pointers to the heads of these linked lists,
+   * value_cells_tail will contain pointers to the tail of the lists. */
+  value_cells_head = calloc(data->values_len, sizeof(*value_cells_head));
+  value_cells_tail = calloc(data->values_len, sizeof(*value_cells_tail));
+  if ((value_cells_head == NULL) || (value_cells_tail == NULL)) {
     ERROR("snmp plugin: csnmp_read_table: calloc failed.");
-    sfree(value_list_head);
-    sfree(value_list_tail);
+    sfree(value_cells_head);
+    sfree(value_cells_tail);
     return -1;
   }
 
-  instance_list_head = NULL;
-  instance_list_tail = NULL;
-
   status = 0;
   while (status == 0) {
     req = snmp_pdu_create(SNMP_MSG_GETNEXT);
@@ -1440,30 +1764,126 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
       }
 
       /* An instance is configured and the res variable we process is the
-       * instance value (last index) */
-      if ((data->instance.oid.oid_len > 0) && (i == data->values_len)) {
+       * instance value */
+      if (oid_list_todo[i] == OID_TYPE_TYPEINSTANCE) {
         if ((vb->type == SNMP_ENDOFMIBVIEW) ||
-            (snmp_oid_ncompare(
-                 data->instance.oid.oid, data->instance.oid.oid_len, vb->name,
-                 vb->name_length, data->instance.oid.oid_len) != 0)) {
-          DEBUG("snmp plugin: host = %s; data = %s; Instance left its subtree.",
+            (snmp_oid_ncompare(data->type_instance.oid.oid,
+                               data->type_instance.oid.oid_len, vb->name,
+                               vb->name_length,
+                               data->type_instance.oid.oid_len) != 0)) {
+          DEBUG("snmp plugin: host = %s; data = %s; TypeInstance left its "
+                "subtree.",
                 host->name, data->name);
           oid_list_todo[i] = 0;
           continue;
         }
 
-        /* Allocate a new `csnmp_list_instances_t', insert the instance name and
+        /* Allocate a new `csnmp_cell_char_t', insert the instance name and
          * add it to the list */
-        if (csnmp_instance_list_add(&instance_list_head, &instance_list_tail,
-                                    res, host, data) != 0) {
-          ERROR("snmp plugin: host %s: csnmp_instance_list_add failed.",
+        csnmp_cell_char_t *cell =
+            csnmp_get_char_cell(vb, &data->type_instance.oid, host, data);
+        if (cell == NULL) {
+          ERROR("snmp plugin: host %s: csnmp_get_char_cell() failed.",
                 host->name);
           status = -1;
           break;
         }
+
+        if (csnmp_ignore_instance(cell, data)) {
+          sfree(cell);
+        } else {
+          csnmp_cell_replace_reserved_chars(cell);
+
+          DEBUG("snmp plugin: il->type_instance = `%s';", cell->value);
+          csnmp_cells_append(&type_instance_cells_head,
+                             &type_instance_cells_tail, cell);
+        }
+      } else if (oid_list_todo[i] == OID_TYPE_PLUGININSTANCE) {
+        if ((vb->type == SNMP_ENDOFMIBVIEW) ||
+            (snmp_oid_ncompare(data->plugin_instance.oid.oid,
+                               data->plugin_instance.oid.oid_len, vb->name,
+                               vb->name_length,
+                               data->plugin_instance.oid.oid_len) != 0)) {
+          DEBUG("snmp plugin: host = %s; data = %s; TypeInstance left its "
+                "subtree.",
+                host->name, data->name);
+          oid_list_todo[i] = 0;
+          continue;
+        }
+
+        /* Allocate a new `csnmp_cell_char_t', insert the instance name and
+         * add it to the list */
+        csnmp_cell_char_t *cell =
+            csnmp_get_char_cell(vb, &data->plugin_instance.oid, host, data);
+        if (cell == NULL) {
+          ERROR("snmp plugin: host %s: csnmp_get_char_cell() failed.",
+                host->name);
+          status = -1;
+          break;
+        }
+
+        csnmp_cell_replace_reserved_chars(cell);
+
+        DEBUG("snmp plugin: il->plugin_instance = `%s';", cell->value);
+        csnmp_cells_append(&plugin_instance_cells_head,
+                           &plugin_instance_cells_tail, cell);
+      } else if (oid_list_todo[i] == OID_TYPE_HOST) {
+        if ((vb->type == SNMP_ENDOFMIBVIEW) ||
+            (snmp_oid_ncompare(data->host.oid.oid, data->host.oid.oid_len,
+                               vb->name, vb->name_length,
+                               data->host.oid.oid_len) != 0)) {
+          DEBUG("snmp plugin: host = %s; data = %s; Host left its subtree.",
+                host->name, data->name);
+          oid_list_todo[i] = 0;
+          continue;
+        }
+
+        /* Allocate a new `csnmp_cell_char_t', insert the instance name and
+         * add it to the list */
+        csnmp_cell_char_t *cell =
+            csnmp_get_char_cell(vb, &data->host.oid, host, data);
+        if (cell == NULL) {
+          ERROR("snmp plugin: host %s: csnmp_get_char_cell() failed.",
+                host->name);
+          status = -1;
+          break;
+        }
+
+        csnmp_cell_replace_reserved_chars(cell);
+
+        DEBUG("snmp plugin: il->hostname = `%s';", cell->value);
+        csnmp_cells_append(&hostname_cells_head, &hostname_cells_tail, cell);
+      } else if (oid_list_todo[i] == OID_TYPE_FILTER) {
+        if ((vb->type == SNMP_ENDOFMIBVIEW) ||
+            (snmp_oid_ncompare(data->filter_oid.oid, data->filter_oid.oid_len,
+                               vb->name, vb->name_length,
+                               data->filter_oid.oid_len) != 0)) {
+          DEBUG("snmp plugin: host = %s; data = %s; Host left its subtree.",
+                host->name, data->name);
+          oid_list_todo[i] = 0;
+          continue;
+        }
+
+        /* Allocate a new `csnmp_cell_char_t', insert the instance name and
+         * add it to the list */
+        csnmp_cell_char_t *cell =
+            csnmp_get_char_cell(vb, &data->filter_oid, host, data);
+        if (cell == NULL) {
+          ERROR("snmp plugin: host %s: csnmp_get_char_cell() failed.",
+                host->name);
+          status = -1;
+          break;
+        }
+
+        csnmp_cell_replace_reserved_chars(cell);
+
+        DEBUG("snmp plugin: il->filter = `%s';", cell->value);
+        csnmp_cells_append(&filter_cells_head, &filter_cells_tail, cell);
       } else /* The variable we are processing is a normal value */
       {
-        csnmp_table_values_t *vt;
+        assert(oid_list_todo[i] == OID_TYPE_VARIABLE);
+
+        csnmp_cell_value_t *vt;
         oid_t vb_name;
         oid_t suffix;
         int ret;
@@ -1482,10 +1902,9 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
         }
 
         /* Make sure the OIDs returned by the agent are increasing. Otherwise
-         * our
-         * table matching algorithm will get confused. */
-        if ((value_list_tail[i] != NULL) &&
-            (csnmp_oid_compare(&suffix, &value_list_tail[i]->suffix) <= 0)) {
+         * our table matching algorithm will get confused. */
+        if ((value_cells_tail[i] != NULL) &&
+            (csnmp_oid_compare(&suffix, &value_cells_tail[i]->suffix) <= 0)) {
           DEBUG("snmp plugin: host = %s; data = %s; i = %" PRIsz "; "
                 "Suffix is not increasing.",
                 host->name, data->name, i);
@@ -1506,11 +1925,11 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
         memcpy(&vt->suffix, &suffix, sizeof(vt->suffix));
         vt->next = NULL;
 
-        if (value_list_tail[i] == NULL)
-          value_list_head[i] = vt;
+        if (value_cells_tail[i] == NULL)
+          value_cells_head[i] = vt;
         else
-          value_list_tail[i]->next = vt;
-        value_list_tail[i] = vt;
+          value_cells_tail[i]->next = vt;
+        value_cells_tail[i] = vt;
       }
 
       /* Copy OID to oid_list[i] */
@@ -1529,25 +1948,45 @@ static int csnmp_read_table(host_definition_t *host, data_definition_t *data) {
   res = NULL;
 
   if (status == 0)
-    csnmp_dispatch_table(host, data, instance_list_head, value_list_head);
+    csnmp_dispatch_table(host, data, type_instance_cells_head,
+                         plugin_instance_cells_head, hostname_cells_head,
+                         filter_cells_head, value_cells_head);
 
   /* Free all allocated variables here */
-  while (instance_list_head != NULL) {
-    csnmp_list_instances_t *next = instance_list_head->next;
-    sfree(instance_list_head);
-    instance_list_head = next;
+  while (type_instance_cells_head != NULL) {
+    csnmp_cell_char_t *next = type_instance_cells_head->next;
+    sfree(type_instance_cells_head);
+    type_instance_cells_head = next;
+  }
+
+  while (plugin_instance_cells_head != NULL) {
+    csnmp_cell_char_t *next = plugin_instance_cells_head->next;
+    sfree(plugin_instance_cells_head);
+    plugin_instance_cells_head = next;
+  }
+
+  while (hostname_cells_head != NULL) {
+    csnmp_cell_char_t *next = hostname_cells_head->next;
+    sfree(hostname_cells_head);
+    hostname_cells_head = next;
+  }
+
+  while (filter_cells_head != NULL) {
+    csnmp_cell_char_t *next = filter_cells_head->next;
+    sfree(filter_cells_head);
+    filter_cells_head = next;
   }
 
   for (i = 0; i < data->values_len; i++) {
-    while (value_list_head[i] != NULL) {
-      csnmp_table_values_t *next = value_list_head[i]->next;
-      sfree(value_list_head[i]);
-      value_list_head[i] = next;
+    while (value_cells_head[i] != NULL) {
+      csnmp_cell_value_t *next = value_cells_head[i]->next;
+      sfree(value_cells_head[i]);
+      value_cells_head[i] = next;
     }
   }
 
-  sfree(value_list_head);
-  sfree(value_list_tail);
+  sfree(value_cells_head);
+  sfree(value_cells_tail);
 
   return 0;
 } /* int csnmp_read_table */
@@ -1597,11 +2036,14 @@ static int csnmp_read_value(host_definition_t *host, data_definition_t *data) {
   }
 
   sstrncpy(vl.host, host->name, sizeof(vl.host));
-  sstrncpy(vl.plugin, "snmp", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, data->plugin_name, sizeof(vl.plugin));
   sstrncpy(vl.type, data->type, sizeof(vl.type));
-  sstrncpy(vl.type_instance, data->instance.string, sizeof(vl.type_instance));
-
-  vl.interval = host->interval;
+  if (data->type_instance.value)
+    sstrncpy(vl.type_instance, data->type_instance.value,
+             sizeof(vl.type_instance));
+  if (data->plugin_instance.value)
+    sstrncpy(vl.plugin_instance, data->plugin_instance.value,
+             sizeof(vl.plugin_instance));
 
   req = snmp_pdu_create(SNMP_MSG_GET);
   if (req == NULL) {
@@ -1664,9 +2106,6 @@ static int csnmp_read_host(user_data_t *ud) {
 
   host = ud->data;
 
-  if (host->interval == 0)
-    host->interval = plugin_get_interval();
-
   if (host->sess_handle == NULL)
     csnmp_host_open_session(host);
 
@@ -1711,11 +2150,7 @@ static int csnmp_shutdown(void) {
   while (data_this != NULL) {
     data_next = data_this->next;
 
-    sfree(data_this->name);
-    sfree(data_this->type);
-    sfree(data_this->values);
-    sfree(data_this->ignores);
-    sfree(data_this);
+    csnmp_data_definition_destroy(data_this);
 
     data_this = data_next;
   }
index 48d9f86..1c7191f 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * collectd - src/snmp_agent.c
  *
- * Copyright(c) 2017 Intel Corporation. All rights reserved.
+ * Copyright(c) 2017-2018 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
@@ -24,6 +24,7 @@
  * Authors:
  *   Roman Korynkevych <romanx.korynkevych@intel.com>
  *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
+ *   Marcin Mozejko <marcinx.mozejko@intel.com>
  **/
 
 #include "collectd.h"
@@ -32,6 +33,7 @@
 #include "utils_avltree.h"
 #include "utils_cache.h"
 #include "utils_llist.h"
+#include <regex.h>
 
 #include <net-snmp/net-snmp-config.h>
 
 #include <net-snmp/agent/net-snmp-agent-includes.h>
 
 #define PLUGIN_NAME "snmp_agent"
-#define ERR_BUF_SIZE 1024
 #define TYPE_STRING -1
+#define GROUP_UNUSED -1
+#define OID_EXISTS 1
+#define MAX_KEY_SOURCES 5
+#define MAX_INDEX_KEYS 5
+#define MAX_MATCHES 5
+
+/* Identifies index key source */
+enum index_key_src_e {
+  INDEX_HOST = 0,
+  INDEX_PLUGIN,
+  INDEX_PLUGIN_INSTANCE,
+  INDEX_TYPE,
+  INDEX_TYPE_INSTANCE
+};
+typedef enum index_key_src_e index_key_src_t;
 
-#ifndef MIN
-#define MIN(x, y) ((x) < (y) ? (x) : (y))
-#endif
+struct index_key_s {
+  index_key_src_t source;
+  u_char type;
+  char *regex; /* Pattern used to parse index key source string */
+  int group;   /* If pattern gives more than one group we can specify which one
+                  we want to take */
+  regex_t regex_info;
+};
+typedef struct index_key_s index_key_t;
 
 struct oid_s {
   oid oid[MAX_OID_LEN];
@@ -54,6 +76,12 @@ struct oid_s {
 };
 typedef struct oid_s oid_t;
 
+struct token_s {
+  char *str;
+  netsnmp_variable_list *key; /* Points to succeeding key */
+};
+typedef struct token_s token_t;
+
 struct table_definition_s {
   char *name;
   oid_t index_oid;
@@ -61,6 +89,19 @@ struct table_definition_s {
   llist_t *columns;
   c_avl_tree_t *instance_index;
   c_avl_tree_t *index_instance;
+  c_avl_tree_t *instance_oids; /* Tells us how many OIDs registered for every
+                                  instance; */
+  index_key_t index_keys[MAX_INDEX_KEYS]; /* Stores information about what each
+                                             index key represents */
+  int index_keys_len;
+  netsnmp_variable_list *index_list_cont; /* Index key container used for
+                                             generating as well as parsing
+                                             OIDs, not thread-safe */
+  c_avl_tree_t *tokens[MAX_KEY_SOURCES];  /* Input string after regex execution
+                                             will be split into sepearate
+                                             tokens */
+
+  bool tokens_done; /* Set to true when all tokens are generated */
 };
 typedef struct table_definition_s table_definition_t;
 
@@ -71,7 +112,8 @@ struct data_definition_s {
   char *type;
   char *type_instance;
   const table_definition_t *table;
-  bool is_instance;
+  bool is_index_key; /* indicates if table column is an index key */
+  int index_key_pos; /* position in indexes list */
   oid_t *oids;
   size_t oids_len;
   double scale;
@@ -87,10 +129,13 @@ struct snmp_agent_ctx_s {
 
   llist_t *tables;
   llist_t *scalars;
+  c_avl_tree_t *registered_oids; /* AVL tree containing all registered OIDs */
 };
 typedef struct snmp_agent_ctx_s snmp_agent_ctx_t;
 
 static snmp_agent_ctx_t *g_agent;
+static const char *index_opts[MAX_KEY_SOURCES] = {
+    "Hostname", "Plugin", "PluginInstance", "Type", "TypeInstance"};
 
 #define CHECK_DD_TYPE(_dd, _p, _pi, _t, _ti)                                   \
   (_dd->plugin ? !strcmp(_dd->plugin, _p) : 0) &&                              \
@@ -105,6 +150,9 @@ static int snmp_agent_set_vardata(void *dst_buf, size_t *dst_buf_len,
                                   u_char asn_type, double scale, double shift,
                                   const void *value, size_t len, int type);
 static int snmp_agent_unregister_oid_index(oid_t *oid, int index);
+static int snmp_agent_update_instance_oids(c_avl_tree_t *tree, oid_t *index_oid,
+                                           int value);
+static int num_compare(const int *a, const int *b);
 
 static u_char snmp_agent_get_asn_type(oid *oid, size_t oid_len) {
   struct tree *node = get_tree(oid, oid_len, g_agent->tp);
@@ -131,10 +179,55 @@ static int snmp_agent_oid_to_string(char *buf, size_t buf_size,
   return strjoin(buf, buf_size, oid_str_ptr, o->oid_len, ".");
 }
 
-static void snmp_agent_dump_data(void) {
+/* Prints a configuration storing list. It handles both table columns list
+   and scalars list */
 #if COLLECT_DEBUG
+static void snmp_agent_dump_data(llist_t *list) {
   char oid_str[DATA_MAX_NAME_LEN];
+  for (llentry_t *de = llist_head(list); de != NULL; de = de->next) {
+    data_definition_t *dd = de->value;
+    table_definition_t const *td = dd->table;
 
+    if (dd->table != NULL)
+      DEBUG(PLUGIN_NAME ":   Column:");
+    else
+      DEBUG(PLUGIN_NAME ": Scalar:");
+
+    DEBUG(PLUGIN_NAME ":     Name: %s", dd->name);
+    if (dd->plugin)
+      DEBUG(PLUGIN_NAME ":     Plugin: %s", dd->plugin);
+    if (dd->plugin_instance)
+      DEBUG(PLUGIN_NAME ":     PluginInstance: %s", dd->plugin_instance);
+    if (dd->is_index_key) {
+      index_key_t const *index_key = &td->index_keys[dd->index_key_pos];
+
+      DEBUG(PLUGIN_NAME ":     IndexKey:");
+      DEBUG(PLUGIN_NAME ":       Source: %s", index_opts[index_key->source]);
+      DEBUG(PLUGIN_NAME ":       Type: %s",
+            (index_key->type == ASN_INTEGER) ? "Integer" : "String");
+      if (index_key->regex)
+        DEBUG(PLUGIN_NAME ":       Regex: %s", index_key->regex);
+      if (index_key->group != GROUP_UNUSED)
+        DEBUG(PLUGIN_NAME ":       Group: %d", index_key->group);
+    }
+    if (dd->type)
+      DEBUG(PLUGIN_NAME ":     Type: %s", dd->type);
+    if (dd->type_instance)
+      DEBUG(PLUGIN_NAME ":     TypeInstance: %s", dd->type_instance);
+    for (size_t i = 0; i < dd->oids_len; i++) {
+      snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &dd->oids[i]);
+      DEBUG(PLUGIN_NAME ":     OID[%" PRIsz "]: %s", i, oid_str);
+    }
+    DEBUG(PLUGIN_NAME ":   Scale: %g", dd->scale);
+    DEBUG(PLUGIN_NAME ":   Shift: %g", dd->shift);
+  }
+}
+
+/* Prints parsed configuration */
+static void snmp_agent_dump_config(void) {
+  char oid_str[DATA_MAX_NAME_LEN];
+
+  /* Printing tables */
   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
     table_definition_t *td = te->value;
 
@@ -149,62 +242,28 @@ static void snmp_agent_dump_data(void) {
       DEBUG(PLUGIN_NAME ":   SizeOID: %s", oid_str);
     }
 
-    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
-      data_definition_t *dd = de->value;
-
-      DEBUG(PLUGIN_NAME ":   Column:");
-      DEBUG(PLUGIN_NAME ":     Name: %s", dd->name);
-      if (dd->plugin)
-        DEBUG(PLUGIN_NAME ":     Plugin: %s", dd->plugin);
-      if (dd->plugin_instance)
-        DEBUG(PLUGIN_NAME ":     PluginInstance: %s", dd->plugin_instance);
-      if (dd->is_instance)
-        DEBUG(PLUGIN_NAME ":     Instance: true");
-      if (dd->type)
-        DEBUG(PLUGIN_NAME ":     Type: %s", dd->type);
-      if (dd->type_instance)
-        DEBUG(PLUGIN_NAME ":     TypeInstance: %s", dd->type_instance);
-      for (size_t i = 0; i < dd->oids_len; i++) {
-        snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &dd->oids[i]);
-        DEBUG(PLUGIN_NAME ":     OID[%" PRIsz "]: %s", i, oid_str);
-      }
-      DEBUG(PLUGIN_NAME ":   Scale: %g", dd->scale);
-      DEBUG(PLUGIN_NAME ":   Shift: %g", dd->shift);
-    }
+    snmp_agent_dump_data(td->columns);
   }
 
-  for (llentry_t *e = llist_head(g_agent->scalars); e != NULL; e = e->next) {
-    data_definition_t *dd = e->value;
-
-    DEBUG(PLUGIN_NAME ": Scalar:");
-    DEBUG(PLUGIN_NAME ":   Name: %s", dd->name);
-    if (dd->plugin)
-      DEBUG(PLUGIN_NAME ":   Plugin: %s", dd->plugin);
-    if (dd->plugin_instance)
-      DEBUG(PLUGIN_NAME ":   PluginInstance: %s", dd->plugin_instance);
-    if (dd->is_instance)
-      DEBUG(PLUGIN_NAME ":   Instance: true");
-    if (dd->type)
-      DEBUG(PLUGIN_NAME ":   Type: %s", dd->type);
-    if (dd->type_instance)
-      DEBUG(PLUGIN_NAME ":   TypeInstance: %s", dd->type_instance);
-    for (size_t i = 0; i < dd->oids_len; i++) {
-      snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &dd->oids[i]);
-      DEBUG(PLUGIN_NAME ":   OID[%" PRIsz "]: %s", i, oid_str);
-    }
-    DEBUG(PLUGIN_NAME ":   Scale: %g", dd->scale);
-    DEBUG(PLUGIN_NAME ":   Shift: %g", dd->shift);
-  }
-#endif /* COLLECT_DEBUG */
+  /* Printing scalars */
+  snmp_agent_dump_data(g_agent->scalars);
 }
+#endif /* COLLECT_DEBUG */
 
-static int snmp_agent_validate_data(void) {
+static int snmp_agent_validate_config(void) {
 
-  snmp_agent_dump_data();
+#if COLLECT_DEBUG
+  snmp_agent_dump_config();
+#endif
 
   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
     table_definition_t *td = te->value;
 
+    if (!td->index_keys_len) {
+      ERROR(PLUGIN_NAME ": Index keys not defined for '%s'", td->name);
+      return -EINVAL;
+    }
+
     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
       data_definition_t *dd = de->value;
 
@@ -227,11 +286,10 @@ static int snmp_agent_validate_data(void) {
         return -EINVAL;
       }
 
-      if (dd->is_instance) {
-
+      if (dd->is_index_key) {
         if (dd->type || dd->type_instance) {
           ERROR(PLUGIN_NAME ": Type and TypeInstance are not valid for "
-                            "instance data '%s'.'%s'",
+                            "index data '%s'.'%s'",
                 td->name, dd->name);
           return -EINVAL;
         }
@@ -267,9 +325,8 @@ static int snmp_agent_validate_data(void) {
       return -EINVAL;
     }
 
-    if (dd->is_instance) {
-      ERROR(PLUGIN_NAME
-            ": Instance flag can't be specified for scalar data '%s'",
+    if (dd->is_index_key) {
+      ERROR(PLUGIN_NAME ": Index field can't be specified for scalar data '%s'",
             dd->name);
       return -EINVAL;
     }
@@ -283,109 +340,429 @@ static int snmp_agent_validate_data(void) {
   return 0;
 }
 
-static void snmp_agent_generate_oid2string(oid_t *oid, size_t offset,
-                                           char *key) {
-  int key_len = oid->oid[offset];
-  int i;
+static int snmp_agent_parse_index_key(const char *input, regex_t *regex_info,
+                                      int gi, regmatch_t *m) {
+  regmatch_t matches[MAX_MATCHES];
 
-  for (i = 0; i < key_len && offset < oid->oid_len; i++)
-    key[i] = oid->oid[++offset];
+  int ret = regexec(regex_info, input, MAX_MATCHES, matches, 0);
+  if (!ret) {
+    if (gi > regex_info->re_nsub) {
+      ERROR(PLUGIN_NAME ": Group index %d not found. Check regex config", gi);
+      return -1;
+    }
+    *m = matches[gi];
+  } else if (ret == REG_NOMATCH) {
+    ERROR(PLUGIN_NAME ": No match found");
+    return -1;
+  } else {
+    char msgbuf[100];
 
-  key[i] = '\0';
+    regerror(ret, regex_info, msgbuf, sizeof(msgbuf));
+    ERROR(PLUGIN_NAME ": Regex match failed: %s", msgbuf);
+    return -1;
+  }
+
+  return 0;
 }
 
-static int snmp_agent_generate_string2oid(oid_t *oid, const char *key) {
-  int key_len = strlen(key);
+static int snmp_agent_create_token(char const *input, int t_off, int n,
+                                   c_avl_tree_t *tree,
+                                   netsnmp_variable_list *index_key) {
+  assert(tree != NULL);
+
+  token_t *token = malloc(sizeof(*token));
+
+  if (token == NULL)
+    goto error;
+
+  int *offset = malloc(sizeof(*offset));
+
+  if (offset == NULL)
+    goto free_token_error;
+
+  int ret = 0;
+
+  token->key = index_key;
+  input += t_off;
+  size_t len = strlen(input);
+
+  if (n < len)
+    len = n;
+
+  token->str = malloc(len + 1);
+
+  if (token->str == NULL)
+    goto free_offset_error;
+
+  /* copy at most n bytes from input with offset t_off into token->str */
+  sstrncpy(token->str, input, len + 1);
+  *offset = t_off;
+  ret = c_avl_insert(tree, (void *)offset, (void *)token);
+
+  if (ret == 0)
+    return 0;
+
+  sfree(token->str);
+
+free_offset_error:
+  sfree(offset);
+
+free_token_error:
+  sfree(token);
+
+error:
+  ERROR(PLUGIN_NAME ": Could not allocate memory to create token");
+
+  return -1;
+}
+
+static int snmp_agent_delete_token(int t_off, c_avl_tree_t *tree) {
+  token_t *token = NULL;
+  int *offset = NULL;
+
+  int ret = c_avl_remove(tree, &t_off, (void **)&offset, (void **)&token);
+
+  if (ret != 0) {
+    ERROR(PLUGIN_NAME ": Could not delete token");
+    return -1;
+  }
+
+  sfree(token->str);
+  sfree(token);
+  sfree(offset);
+  return 0;
+}
+
+static int snmp_agent_get_token(c_avl_tree_t *tree, int mpos) {
+
+  int *pos;
+  char *token;
+  int prev_pos = 0;
+
+  c_avl_iterator_t *it = c_avl_get_iterator(tree);
+  while (c_avl_iterator_next(it, (void **)&pos, (void **)&token) == 0) {
+    if (*pos >= mpos)
+      break;
+    else
+      prev_pos = *pos;
+  }
+
+  c_avl_iterator_destroy(it);
+  return prev_pos;
+}
+
+static int snmp_agent_tokenize(const char *input, c_avl_tree_t *tokens,
+                               const regmatch_t *m,
+                               netsnmp_variable_list *key) {
+  assert(tokens != NULL);
+
+  int ret = 0;
+  int len = strlen(input);
+
+  /* Creating first token that is going to be split later */
+  if (c_avl_size(tokens) == 0) {
+    ret = snmp_agent_create_token(input, 0, len, tokens, NULL);
+    if (ret != 0)
+      return ret;
+  }
+
+  /* Divide token that contains current match into two */
+  int t_pos = snmp_agent_get_token(tokens, m->rm_so);
+  ret = snmp_agent_delete_token(t_pos, tokens);
+
+  if (ret != 0)
+    return -1;
+
+  ret = snmp_agent_create_token(input, t_pos, m->rm_so - t_pos, tokens, key);
+
+  if (ret != 0)
+    return -1;
+
+  if (len - m->rm_eo > 1) {
+    ret = snmp_agent_create_token(input, m->rm_eo, len - m->rm_eo + 1, tokens,
+                                  NULL);
+    if (ret != 0) {
+      snmp_agent_delete_token(t_pos, tokens);
+      return -1;
+    }
+  }
 
-  oid->oid[oid->oid_len++] = key_len;
-  for (int i = 0; i < key_len; i++) {
-    oid->oid[oid->oid_len++] = key[i];
-    if (oid->oid_len >= MAX_OID_LEN) {
-      ERROR(PLUGIN_NAME ": Conversion key string %s to OID failed", key);
+  return 0;
+}
+
+static int snmp_agent_fill_index_list(table_definition_t *td,
+                                      value_list_t const *vl) {
+  int ret;
+  int i;
+  netsnmp_variable_list *key = td->index_list_cont;
+  char const *ptr;
+
+  for (i = 0; i < td->index_keys_len; i++) {
+    /* var should never be NULL */
+    assert(key != NULL);
+    ptr = NULL;
+    const index_key_src_t source = td->index_keys[i].source;
+    c_avl_tree_t *const tokens = td->tokens[source];
+    /* Generating list filled with all data necessary to generate an OID */
+    switch (source) {
+    case INDEX_HOST:
+      ptr = vl->host;
+      break;
+    case INDEX_PLUGIN:
+      ptr = vl->plugin;
+      break;
+    case INDEX_PLUGIN_INSTANCE:
+      ptr = vl->plugin_instance;
+      break;
+    case INDEX_TYPE:
+      ptr = vl->type;
+      break;
+    case INDEX_TYPE_INSTANCE:
+      ptr = vl->type_instance;
+      break;
+    default:
+      ERROR(PLUGIN_NAME ": Unknown index key source provided");
       return -EINVAL;
     }
+
+    /* Parsing input string if necessary */
+    if (td->index_keys[i].regex) {
+      regmatch_t m;
+
+      /* Parsing input string */
+      ret = snmp_agent_parse_index_key(ptr, &td->index_keys[i].regex_info,
+                                       td->index_keys[i].group, &m);
+      if (ret != 0) {
+        ERROR(PLUGIN_NAME ": Error executing regex");
+        return ret;
+      }
+
+      /* Tokenizing input string if not done yet */
+      if (td->tokens_done == false)
+        ret = snmp_agent_tokenize(ptr, tokens, &m, key);
+
+      if (ret != 0)
+        return -1;
+
+      if (td->index_keys[i].type == ASN_INTEGER) {
+        int val = strtol(ptr + m.rm_so, NULL, 0);
+
+#ifdef HAVE_NETSNMP_OLD_API
+        ret = snmp_set_var_value(key, (const u_char *)&val, sizeof(val));
+#else
+        ret = snmp_set_var_value(key, &val, sizeof(val));
+#endif
+      } else
+#ifdef HAVE_NETSNMP_OLD_API
+        ret = snmp_set_var_value(key, (const u_char *)(ptr + m.rm_so),
+                                 m.rm_eo - m.rm_so);
+#else
+        ret = snmp_set_var_value(key, ptr + m.rm_so, m.rm_eo - m.rm_so);
+#endif
+    } else
+#ifdef HAVE_NETSNMP_OLD_API
+      ret = snmp_set_var_value(key, (const u_char *)ptr, strlen(ptr));
+#else
+      ret = snmp_set_var_value(key, ptr, strlen(ptr));
+#endif
+
+    if (ret != 0)
+      return -1;
+
+    key = key->next_variable;
   }
 
+  /* Tokens for all source strings are generated */
+  for (i = 0; i < MAX_KEY_SOURCES; i++)
+    td->tokens_done = true;
+
+  return 0;
+}
+
+static int snmp_agent_prep_index_list(table_definition_t const *td,
+                                      netsnmp_variable_list **index_list) {
+  /* Generating list having only the structure (with no values) letting us
+   * know how to parse an OID*/
+  for (int i = 0; i < td->index_keys_len; i++) {
+    switch (td->index_keys[i].source) {
+    case INDEX_HOST:
+    case INDEX_PLUGIN:
+    case INDEX_PLUGIN_INSTANCE:
+    case INDEX_TYPE:
+    case INDEX_TYPE_INSTANCE:
+      snmp_varlist_add_variable(index_list, NULL, 0, td->index_keys[i].type,
+                                NULL, 0);
+      break;
+    default:
+      ERROR(PLUGIN_NAME ": Unknown index key source provided");
+      return -EINVAL;
+    }
+  }
   return 0;
 }
 
-static int snmp_agent_register_oid_string(oid_t *oid, const char *key,
+static int snmp_agent_generate_index(table_definition_t *td,
+                                     value_list_t const *vl, oid_t *index_oid) {
+
+  /* According to given information by index_keys list
+   * index OID is going to be built
+   */
+  int ret = snmp_agent_fill_index_list(td, vl);
+  if (ret != 0)
+    return -EINVAL;
+
+  /* Building only index part OID (without table prefix OID) */
+  ret = build_oid_noalloc(index_oid->oid, sizeof(index_oid->oid),
+                          &index_oid->oid_len, NULL, 0, td->index_list_cont);
+  if (ret != SNMPERR_SUCCESS) {
+    ERROR(PLUGIN_NAME ": Error building index OID");
+    return -EINVAL;
+  }
+
+  return 0;
+}
+
+/* It appends one OID to the end of another */
+static int snmp_agent_append_oid(oid_t *out, const oid_t *in) {
+
+  if (out->oid_len + in->oid_len > MAX_OID_LEN) {
+    ERROR(PLUGIN_NAME ": Cannot create OID. Output length is too long!");
+    return -EINVAL;
+  }
+  memcpy(&out->oid[out->oid_len], in->oid, in->oid_len * sizeof(oid));
+  out->oid_len += in->oid_len;
+
+  return 0;
+}
+
+static int snmp_agent_register_oid_string(const oid_t *oid,
+                                          const oid_t *index_oid,
                                           Netsnmp_Node_Handler *handler) {
   oid_t new_oid;
 
   memcpy(&new_oid, oid, sizeof(*oid));
-  int ret = snmp_agent_generate_string2oid(&new_oid, key);
+  /* Concatenating two string oids */
+  int ret = snmp_agent_append_oid(&new_oid, index_oid);
   if (ret != 0)
     return ret;
 
   return snmp_agent_register_oid(&new_oid, handler);
 }
 
-static int snmp_agent_unregister_oid_string(oid_t *oid, const char *key) {
+static int snmp_agent_unregister_oid(oid_t *oid) {
+  int ret = c_avl_remove(g_agent->registered_oids, (void *)oid, NULL, NULL);
+
+  if (ret != 0)
+    ERROR(PLUGIN_NAME ": Could not delete registration info");
+
+  return unregister_mib(oid->oid, oid->oid_len);
+}
+
+static int snmp_agent_unregister_oid_string(oid_t *oid,
+                                            const oid_t *index_oid) {
   oid_t new_oid;
+  char oid_str[DATA_MAX_NAME_LEN];
 
   memcpy(&new_oid, oid, sizeof(*oid));
-  int ret = snmp_agent_generate_string2oid(&new_oid, key);
+  /* Concatenating two string oids */
+  int ret = snmp_agent_append_oid(&new_oid, index_oid);
   if (ret != 0)
     return ret;
 
-  return unregister_mib(new_oid.oid, new_oid.oid_len);
+  snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &new_oid);
+  DEBUG(PLUGIN_NAME ": Unregistered handler for OID (%s)", oid_str);
+
+  return snmp_agent_unregister_oid(&new_oid);
 }
 
-static int snmp_agent_table_row_remove(table_definition_t *td,
-                                       const char *instance) {
+static void snmp_agent_table_data_remove(data_definition_t *dd,
+                                         table_definition_t *td,
+                                         oid_t *index_oid) {
   int *index = NULL;
-  char *ins = NULL;
+  oid_t *ind_oid = NULL;
 
   if (td->index_oid.oid_len) {
-    if ((c_avl_get(td->instance_index, instance, (void **)&index) != 0) ||
-        (c_avl_get(td->index_instance, index, (void **)&ins) != 0))
-      return 0;
+    if ((c_avl_get(td->instance_index, index_oid, (void **)&index) != 0) ||
+        (c_avl_get(td->index_instance, index, NULL) != 0))
+      return;
   } else {
-    if (c_avl_get(td->instance_index, instance, (void **)&ins) != 0)
-      return 0;
+    if (c_avl_get(td->instance_index, index_oid, NULL) != 0)
+      return;
   }
 
   pthread_mutex_lock(&g_agent->agentx_lock);
 
-  if (td->index_oid.oid_len)
-    snmp_agent_unregister_oid_index(&td->index_oid, *index);
+  int reg_oids = -1; /* Number of registered oids for given instance */
+
+  for (size_t i = 0; i < dd->oids_len; i++) {
+    if (td->index_oid.oid_len)
+      snmp_agent_unregister_oid_index(&dd->oids[i], *index);
+    else
+      snmp_agent_unregister_oid_string(&dd->oids[i], index_oid);
 
+    reg_oids =
+        snmp_agent_update_instance_oids(td->instance_oids, index_oid, -1);
+  }
+
+  /* Checking if any metrics are left registered */
+  if (reg_oids != 0) {
+    pthread_mutex_unlock(&g_agent->agentx_lock);
+    return;
+  }
+
+  /* All metrics have been unregistered. Unregistering index key OIDs */
+  int keys_processed = 0;
   for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
-    data_definition_t *dd = de->value;
+    data_definition_t *idd = de->value;
+
+    if (!idd->is_index_key)
+      continue;
 
-    for (size_t i = 0; i < dd->oids_len; i++)
+    for (size_t i = 0; i < idd->oids_len; i++)
       if (td->index_oid.oid_len)
-        snmp_agent_unregister_oid_index(&dd->oids[i], *index);
+        snmp_agent_unregister_oid_index(&idd->oids[i], *index);
       else
-        snmp_agent_unregister_oid_string(&dd->oids[i], ins);
-  }
+        snmp_agent_unregister_oid_string(&idd->oids[i], index_oid);
 
+    if (++keys_processed >= td->index_keys_len)
+      break;
+  }
   pthread_mutex_unlock(&g_agent->agentx_lock);
 
-  DEBUG(PLUGIN_NAME ": Removed row for '%s' table [%d, %s]", td->name,
-        (index != NULL) ? *index : -1, ins);
+  /* All OIDs have been unregistered so we dont need this instance registered
+   * as well */
+  char index_str[DATA_MAX_NAME_LEN];
+
+  if (index == NULL)
+    snmp_agent_oid_to_string(index_str, sizeof(index_str), index_oid);
+  else
+    snprintf(index_str, sizeof(index_str), "%d", *index);
 
   notification_t n = {
       .severity = NOTIF_WARNING, .time = cdtime(), .plugin = PLUGIN_NAME};
   sstrncpy(n.host, hostname_g, sizeof(n.host));
-  sstrncpy(n.plugin_instance, ins, sizeof(n.plugin_instance));
   snprintf(n.message, sizeof(n.message),
-           "Removed data row from table %s instance %s index %d", td->name, ins,
-           (index != NULL) ? *index : -1);
+           "Removed data row from table %s with index %s", td->name, index_str);
+  DEBUG(PLUGIN_NAME ": %s", n.message);
   plugin_dispatch_notification(&n);
 
-  if (td->index_oid.oid_len) {
-    c_avl_remove(td->index_instance, index, NULL, (void **)&ins);
-    c_avl_remove(td->instance_index, instance, NULL, (void **)&index);
+  int *val = NULL;
+
+  c_avl_remove(td->instance_oids, index_oid, NULL, (void **)&val);
+  sfree(val);
+
+  if (index != NULL) {
+    pthread_mutex_lock(&g_agent->agentx_lock);
+    snmp_agent_unregister_oid_index(&td->index_oid, *index);
+    pthread_mutex_unlock(&g_agent->agentx_lock);
+
+    c_avl_remove(td->index_instance, index, NULL, (void **)&ind_oid);
+    c_avl_remove(td->instance_index, index_oid, NULL, (void **)&index);
     sfree(index);
-    sfree(ins);
+    sfree(ind_oid);
   } else {
-    c_avl_remove(td->instance_index, instance, NULL, (void **)&ins);
-    sfree(ins);
+    c_avl_remove(td->instance_index, index_oid, NULL, NULL);
   }
-
-  return 0;
 }
 
 static int snmp_agent_clear_missing(const value_list_t *vl,
@@ -399,11 +776,23 @@ static int snmp_agent_clear_missing(const value_list_t *vl,
     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
       data_definition_t *dd = de->value;
 
-      if (!dd->is_instance) {
+      if (!dd->is_index_key) {
         if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type,
                           vl->type_instance)) {
-          snmp_agent_table_row_remove(td, vl->plugin_instance);
-          return 0;
+          oid_t *index_oid = calloc(1, sizeof(*index_oid));
+
+          if (index_oid == NULL) {
+            ERROR(PLUGIN_NAME ": Could not allocate memory for index_oid");
+            return -ENOMEM;
+          }
+
+          int ret = snmp_agent_generate_index(td, vl, index_oid);
+
+          if (ret == 0)
+            snmp_agent_table_data_remove(dd, td, index_oid);
+          sfree(index_oid);
+
+          return ret;
         }
       }
     }
@@ -444,23 +833,22 @@ static void snmp_agent_free_table_columns(table_definition_t *td) {
 
     if (td->index_oid.oid_len) {
       int *index;
-      char *instance;
+      oid_t *index_oid;
 
       c_avl_iterator_t *iter = c_avl_get_iterator(td->index_instance);
-      while (c_avl_iterator_next(iter, (void *)&index, (void *)&instance) ==
+      while (c_avl_iterator_next(iter, (void *)&index, (void *)&index_oid) ==
              0) {
         for (size_t i = 0; i < dd->oids_len; i++)
           snmp_agent_unregister_oid_index(&dd->oids[i], *index);
       }
       c_avl_iterator_destroy(iter);
     } else {
-      char *instance;
+      oid_t *index_oid;
 
       c_avl_iterator_t *iter = c_avl_get_iterator(dd->table->instance_index);
-      while (c_avl_iterator_next(iter, (void *)&instance, (void *)&instance) ==
-             0) {
+      while (c_avl_iterator_next(iter, (void *)&index_oid, NULL) == 0) {
         for (size_t i = 0; i < dd->oids_len; i++)
-          snmp_agent_unregister_oid_string(&dd->oids[i], instance);
+          snmp_agent_unregister_oid_string(&dd->oids[i], index_oid);
       }
       c_avl_iterator_destroy(iter);
     }
@@ -480,13 +868,14 @@ static void snmp_agent_free_table(table_definition_t **td) {
   if ((*td)->size_oid.oid_len)
     unregister_mib((*td)->size_oid.oid, (*td)->size_oid.oid_len);
 
+  oid_t *index_oid;
+
   /* Unregister Index OIDs */
   if ((*td)->index_oid.oid_len) {
     int *index;
-    char *instance;
 
     c_avl_iterator_t *iter = c_avl_get_iterator((*td)->index_instance);
-    while (c_avl_iterator_next(iter, (void *)&index, (void *)&instance) == 0)
+    while (c_avl_iterator_next(iter, (void **)&index, (void **)&index_oid) == 0)
       snmp_agent_unregister_oid_index(&(*td)->index_oid, *index);
 
     c_avl_iterator_destroy(iter);
@@ -497,6 +886,15 @@ static void snmp_agent_free_table(table_definition_t **td) {
 
   void *key = NULL;
   void *value = NULL;
+  int *num = NULL;
+
+  /* Removing data from instance_oids, leaving key pointers since they are still
+   * used in other AVL trees */
+  c_avl_iterator_t *iter = c_avl_get_iterator((*td)->instance_oids);
+  while (c_avl_iterator_next(iter, (void **)&index_oid, (void **)&num) == 0)
+    sfree(num);
+  c_avl_iterator_destroy(iter);
+  c_avl_destroy((*td)->instance_oids);
 
   /* index_instance and instance_index contain the same pointers */
   c_avl_destroy((*td)->index_instance);
@@ -511,20 +909,189 @@ static void snmp_agent_free_table(table_definition_t **td) {
     c_avl_destroy((*td)->instance_index);
     (*td)->instance_index = NULL;
   }
+  snmp_free_varbind((*td)->index_list_cont);
 
+  int i;
+  token_t *tok = NULL;
+
+  for (i = 0; i < (*td)->index_keys_len; i++) {
+    sfree((*td)->index_keys[i].regex);
+    regfree(&(*td)->index_keys[i].regex_info);
+  }
+  for (i = 0; i < MAX_KEY_SOURCES; i++)
+    if ((*td)->tokens[i] != NULL) {
+      while (c_avl_pick((*td)->tokens[i], &key, (void **)&tok) == 0) {
+        sfree(key);
+        sfree(tok->str);
+        sfree(tok);
+      }
+      c_avl_destroy((*td)->tokens[i]);
+      (*td)->tokens[i] = NULL;
+    }
   sfree((*td)->name);
   sfree(*td);
 
   return;
 }
 
+static int snmp_agent_parse_oid_index_keys(const table_definition_t *td,
+                                           oid_t *index_oid) {
+  assert(index_oid != NULL);
+  int ret = parse_oid_indexes(index_oid->oid, index_oid->oid_len,
+                              td->index_list_cont);
+  if (ret != SNMPERR_SUCCESS)
+    ERROR(PLUGIN_NAME ": index OID parse error!");
+  return ret;
+}
+
+static int snmp_agent_build_name(char **name, c_avl_tree_t *tokens) {
+  int *pos;
+  token_t *tok;
+  char str[DATA_MAX_NAME_LEN];
+  char out[DATA_MAX_NAME_LEN] = {0};
+  c_avl_iterator_t *it = c_avl_get_iterator(tokens);
+
+  if (it == NULL) {
+    ERROR(PLUGIN_NAME ": Error getting tokens list iterator");
+    return -1;
+  }
+
+  while (c_avl_iterator_next(it, (void **)&pos, (void **)&tok) == 0) {
+    strncat(out, tok->str, DATA_MAX_NAME_LEN - strlen(out) - 1);
+    if (tok->key != NULL) {
+      if (tok->key->type == ASN_INTEGER) {
+        snprintf(str, sizeof(str), "%ld", *tok->key->val.integer);
+        strncat(out, str, DATA_MAX_NAME_LEN - strlen(out) - 1);
+      } else /* OCTET_STR */
+        strncat(out, (char *)tok->key->val.string,
+                DATA_MAX_NAME_LEN - strlen(out) - 1);
+    }
+  }
+
+  c_avl_iterator_destroy(it);
+  *name = strdup(out);
+
+  if (*name == NULL) {
+    ERROR(PLUGIN_NAME ": Could not allocate memory");
+    return -ENOMEM;
+  }
+
+  return 0;
+}
+
+static int snmp_agent_format_name(char *name, int name_len,
+                                  data_definition_t *dd, oid_t *index_oid) {
+
+  int ret = 0;
+
+  if (index_oid == NULL) {
+    /* It's a scalar */
+    format_name(name, name_len, hostname_g, dd->plugin, dd->plugin_instance,
+                dd->type, dd->type_instance);
+  } else {
+    /* Need to parse string index OID */
+    const table_definition_t *td = dd->table;
+    ret = snmp_agent_parse_oid_index_keys(td, index_oid);
+    if (ret != 0)
+      return ret;
+
+    int i = 0;
+    netsnmp_variable_list *key = td->index_list_cont;
+    char str[DATA_MAX_NAME_LEN];
+    char *fields[MAX_KEY_SOURCES] = {hostname_g, dd->plugin,
+                                     dd->plugin_instance, dd->type,
+                                     dd->type_instance};
+
+    /* Looking for simple keys only */
+    while (key != NULL) {
+      if (!td->index_keys[i].regex) {
+        index_key_src_t source = td->index_keys[i].source;
+
+        if (source < INDEX_HOST || source > INDEX_TYPE_INSTANCE) {
+          ERROR(PLUGIN_NAME ": Unkown index key source!");
+          return -EINVAL;
+        }
+
+        if (td->index_keys[i].type == ASN_INTEGER) {
+          snprintf(str, sizeof(str), "%ld", *key->val.integer);
+          fields[source] = str;
+        } else /* OCTET_STR */
+          fields[source] = (char *)key->val.string;
+      }
+      key = key->next_variable;
+      i++;
+    }
+
+    /* Keys with regexes */
+    for (i = 0; i < MAX_KEY_SOURCES; i++) {
+      if (td->tokens[i] == NULL)
+        continue;
+      ret = snmp_agent_build_name(&fields[i], td->tokens[i]);
+      if (ret != 0)
+        return ret;
+    }
+    format_name(name, name_len, fields[INDEX_HOST], fields[INDEX_PLUGIN],
+                fields[INDEX_PLUGIN_INSTANCE], fields[INDEX_TYPE],
+                fields[INDEX_TYPE_INSTANCE]);
+    for (i = 0; i < MAX_KEY_SOURCES; i++) {
+      if (td->tokens[i])
+        sfree(fields[i]);
+    }
+  }
+
+  return 0;
+}
+
 static int snmp_agent_form_reply(struct netsnmp_request_info_s *requests,
-                                 data_definition_t *dd, char *instance,
+                                 data_definition_t *dd, oid_t *index_oid,
                                  int oid_index) {
+  int ret;
+
+  if (dd->is_index_key) {
+    const table_definition_t *td = dd->table;
+    int ret = snmp_agent_parse_oid_index_keys(td, index_oid);
+
+    if (ret != 0)
+      return ret;
+
+    netsnmp_variable_list *key = td->index_list_cont;
+    /* Searching index key */
+    for (int pos = 0; pos < dd->index_key_pos; pos++)
+      key = key->next_variable;
+
+    requests->requestvb->type = td->index_keys[dd->index_key_pos].type;
+
+    if (requests->requestvb->type == ASN_INTEGER)
+#ifdef HAVE_NETSNMP_OLD_API
+      snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
+                               (const u_char *)key->val.integer,
+                               sizeof(*key->val.integer));
+#else
+      snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
+                               key->val.integer, sizeof(*key->val.integer));
+#endif
+    else /* OCTET_STR */
+#ifdef HAVE_NETSNMP_OLD_API
+      snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
+                               (const u_char *)key->val.string,
+                               strlen((const char *)key->val.string));
+#else
+      snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
+                               key->val.string,
+                               strlen((const char *)key->val.string));
+#endif
+
+    pthread_mutex_unlock(&g_agent->lock);
+
+    return SNMP_ERR_NOERROR;
+  }
+
   char name[DATA_MAX_NAME_LEN];
-  format_name(name, sizeof(name), hostname_g, dd->plugin,
-              instance ? instance : dd->plugin_instance, dd->type,
-              dd->type_instance);
+
+  ret = snmp_agent_format_name(name, sizeof(name), dd, index_oid);
+  if (ret != 0)
+    return ret;
+
   DEBUG(PLUGIN_NAME ": Identifier '%s'", name);
 
   value_t *values;
@@ -535,7 +1102,7 @@ static int snmp_agent_form_reply(struct netsnmp_request_info_s *requests,
     return SNMP_NOSUCHINSTANCE;
   }
 
-  int ret = uc_get_value_by_name(name, &values, &values_num);
+  ret = uc_get_value_by_name(name, &values, &values_num);
 
   if (ret != 0) {
     ERROR(PLUGIN_NAME ": Failed to get value for '%s'", name);
@@ -571,14 +1138,14 @@ snmp_agent_table_oid_handler(struct netsnmp_mib_handler_s *handler,
                              struct netsnmp_agent_request_info_s *reqinfo,
                              struct netsnmp_request_info_s *requests) {
 
-  if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
+  if (reqinfo->mode != MODE_GET) {
     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
     return SNMP_ERR_NOERROR;
   }
 
   pthread_mutex_lock(&g_agent->lock);
 
-  oid_t oid;
+  oid_t oid; /* Requested OID */
   memcpy(oid.oid, requests->requestvb->name,
          sizeof(oid.oid[0]) * requests->requestvb->name_length);
   oid.oid_len = requests->requestvb->name_length;
@@ -588,6 +1155,7 @@ snmp_agent_table_oid_handler(struct netsnmp_mib_handler_s *handler,
   snmp_agent_oid_to_string(oid_str, sizeof(oid_str), &oid);
   DEBUG(PLUGIN_NAME ": Get request received for table OID '%s'", oid_str);
 #endif
+  oid_t index_oid; /* Index part of requested OID */
 
   for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) {
     table_definition_t *td = te->value;
@@ -598,49 +1166,37 @@ snmp_agent_table_oid_handler(struct netsnmp_mib_handler_s *handler,
       for (size_t i = 0; i < dd->oids_len; i++) {
         int ret = snmp_oid_ncompare(oid.oid, oid.oid_len, dd->oids[i].oid,
                                     dd->oids[i].oid_len,
-                                    MIN(oid.oid_len, dd->oids[i].oid_len));
+                                    SNMP_MIN(oid.oid_len, dd->oids[i].oid_len));
         if (ret != 0)
           continue;
 
-        char *instance;
+        /* Calculating OID length for index part */
+        index_oid.oid_len = oid.oid_len - dd->oids[i].oid_len;
+        /* Fetching index part of the OID */
+        memcpy(index_oid.oid, &oid.oid[dd->oids[i].oid_len],
+               index_oid.oid_len * sizeof(*oid.oid));
 
-        if (!td->index_oid.oid_len) {
-          char key[MAX_OID_LEN];
-
-          memset(key, 0, sizeof(key));
-          snmp_agent_generate_oid2string(
-              &oid, MIN(oid.oid_len, dd->oids[i].oid_len), key);
+        char index_str[DATA_MAX_NAME_LEN];
+        snmp_agent_oid_to_string(index_str, sizeof(index_str), &index_oid);
 
-          ret = c_avl_get(td->instance_index, key, (void **)&instance);
-          if (ret != 0) {
-            DEBUG(PLUGIN_NAME ": Nonexisting index string '%s' requested", key);
-            pthread_mutex_unlock(&g_agent->lock);
-            return SNMP_NOSUCHINSTANCE;
-          }
+        if (!td->index_oid.oid_len) {
+          ret = c_avl_get(td->instance_index, &index_oid, NULL);
         } else {
-          int index = oid.oid[oid.oid_len - 1];
+          oid_t *temp_oid;
 
-          ret = c_avl_get(td->index_instance, &index, (void **)&instance);
-          if (ret != 0) {
-            DEBUG(PLUGIN_NAME ": Nonexisting index '%d' requested", index);
-            pthread_mutex_unlock(&g_agent->lock);
-            return SNMP_NOSUCHINSTANCE;
-          }
+          assert(index_oid.oid_len == 1);
+          ret = c_avl_get(td->index_instance, (int *)&index_oid.oid[0],
+                          (void **)&temp_oid);
+          memcpy(&index_oid, temp_oid, sizeof(index_oid));
         }
 
-        if (dd->is_instance) {
-          requests->requestvb->type = ASN_OCTET_STR;
-          snmp_set_var_typed_value(
-              requests->requestvb, requests->requestvb->type,
-              (const u_char *)instance, strlen((instance)));
-
+        if (ret != 0) {
+          INFO(PLUGIN_NAME ": Non-existing index (%s) requested", index_str);
           pthread_mutex_unlock(&g_agent->lock);
-
-          return SNMP_ERR_NOERROR;
+          return SNMP_NOSUCHINSTANCE;
         }
 
-        ret = snmp_agent_form_reply(requests, dd, instance, i);
-
+        ret = snmp_agent_form_reply(requests, dd, &index_oid, i);
         pthread_mutex_unlock(&g_agent->lock);
 
         return ret;
@@ -659,7 +1215,7 @@ static int snmp_agent_table_index_oid_handler(
     struct netsnmp_agent_request_info_s *reqinfo,
     struct netsnmp_request_info_s *requests) {
 
-  if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
+  if (reqinfo->mode != MODE_GET) {
     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
     return SNMP_ERR_NOERROR;
   }
@@ -675,15 +1231,15 @@ static int snmp_agent_table_index_oid_handler(
     table_definition_t *td = te->value;
 
     if (td->index_oid.oid_len &&
-        (snmp_oid_ncompare(oid.oid, oid.oid_len, td->index_oid.oid,
-                           td->index_oid.oid_len,
-                           MIN(oid.oid_len, td->index_oid.oid_len)) == 0)) {
+        (snmp_oid_ncompare(
+             oid.oid, oid.oid_len, td->index_oid.oid, td->index_oid.oid_len,
+             SNMP_MIN(oid.oid_len, td->index_oid.oid_len)) == 0)) {
 
       DEBUG(PLUGIN_NAME ": Handle '%s' table index OID", td->name);
 
       int index = oid.oid[oid.oid_len - 1];
 
-      int ret = c_avl_get(td->index_instance, &index, &(void *){NULL});
+      int ret = c_avl_get(td->index_instance, &index, NULL);
       if (ret != 0) {
         /* nonexisting index requested */
         pthread_mutex_unlock(&g_agent->lock);
@@ -711,7 +1267,7 @@ static int snmp_agent_table_size_oid_handler(
     struct netsnmp_agent_request_info_s *reqinfo,
     struct netsnmp_request_info_s *requests) {
 
-  if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
+  if (reqinfo->mode != MODE_GET) {
     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
     return SNMP_ERR_NOERROR;
   }
@@ -731,12 +1287,16 @@ static int snmp_agent_table_size_oid_handler(
     if (td->size_oid.oid_len &&
         (snmp_oid_ncompare(oid.oid, oid.oid_len, td->size_oid.oid,
                            td->size_oid.oid_len,
-                           MIN(oid.oid_len, td->size_oid.oid_len)) == 0)) {
+                           SNMP_MIN(oid.oid_len, td->size_oid.oid_len)) == 0)) {
       DEBUG(PLUGIN_NAME ": Handle '%s' table size OID", td->name);
 
-      long size = c_avl_size(td->index_instance);
+      long size;
+      if (td->index_oid.oid_len)
+        size = c_avl_size(td->index_instance);
+      else
+        size = c_avl_size(td->instance_index);
 
-      requests->requestvb->type = td->size_oid.type;
+      requests->requestvb->type = ASN_INTEGER;
       snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type,
                                (const u_char *)&size, sizeof(size));
 
@@ -757,7 +1317,7 @@ snmp_agent_scalar_oid_handler(struct netsnmp_mib_handler_s *handler,
                               struct netsnmp_agent_request_info_s *reqinfo,
                               struct netsnmp_request_info_s *requests) {
 
-  if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT) {
+  if (reqinfo->mode != MODE_GET) {
     DEBUG(PLUGIN_NAME ": Not supported request mode (%d)", reqinfo->mode);
     return SNMP_ERR_NOERROR;
   }
@@ -860,10 +1420,14 @@ static int snmp_agent_config_data_oids(data_definition_t *dd,
       return -EINVAL;
     }
 
-  if (dd->oids != NULL)
-    sfree(dd->oids);
+  if (dd->oids != NULL) {
+    WARNING(PLUGIN_NAME ": OIDs can be configured only once for each data");
+    return -EINVAL;
+  }
+
   dd->oids_len = 0;
   dd->oids = calloc(ci->values_num, sizeof(*dd->oids));
+
   if (dd->oids == NULL)
     return -ENOMEM;
   dd->oids_len = (size_t)ci->values_num;
@@ -935,98 +1499,125 @@ static int snmp_agent_config_table_index_oid(table_definition_t *td,
   return 0;
 }
 
-static int snmp_agent_config_table_data(table_definition_t *td,
-                                        oconfig_item_t *ci) {
-  data_definition_t *dd;
-  int ret = 0;
+/* Getting index key source that will represent table row */
+static int snmp_agent_config_index_key_source(table_definition_t *td,
+                                              data_definition_t *dd,
+                                              oconfig_item_t *ci) {
+  char *val = NULL;
 
-  assert(ci != NULL);
+  int ret = cf_util_get_string(ci, &val);
+  if (ret != 0)
+    return -1;
 
-  dd = calloc(1, sizeof(*dd));
-  if (dd == NULL) {
-    ERROR(PLUGIN_NAME ": Failed to allocate memory for table data definition");
-    return -ENOMEM;
+  bool match = false;
+
+  for (int i = 0; i < MAX_KEY_SOURCES; i++) {
+    if (strcasecmp(index_opts[i], (const char *)val) == 0) {
+      td->index_keys[td->index_keys_len].source = i;
+      td->index_keys[td->index_keys_len].group = GROUP_UNUSED;
+      td->index_keys[td->index_keys_len].regex = NULL;
+      match = 1;
+      break;
+    }
   }
 
-  ret = cf_util_get_string(ci, &dd->name);
-  if (ret != 0) {
-    sfree(dd);
-    return -1;
+  if (!match) {
+    ERROR(PLUGIN_NAME ": Failed to parse index key source: '%s'", val);
+    sfree(val);
+    return -EINVAL;
   }
 
-  dd->scale = 1.0;
-  dd->shift = 0.0;
+  sfree(val);
+  dd->index_key_pos = td->index_keys_len++;
+  dd->is_index_key = true;
 
-  dd->table = td;
+  return 0;
+}
 
-  for (int i = 0; i < ci->children_num; i++) {
-    oconfig_item_t *option = ci->children + i;
+/* Getting format string used to parse values from index key source */
+static int snmp_agent_config_index_key_regex(table_definition_t *td,
+                                             data_definition_t *dd,
+                                             oconfig_item_t *ci) {
+  index_key_t *index_key = &td->index_keys[dd->index_key_pos];
 
-    if (strcasecmp("Instance", option->key) == 0)
-      ret = cf_util_get_boolean(option, &dd->is_instance);
-    else if (strcasecmp("Plugin", option->key) == 0)
-      ret = cf_util_get_string(option, &dd->plugin);
-    else if (strcasecmp("PluginInstance", option->key) == 0)
-      ret = cf_util_get_string(option, &dd->plugin_instance);
-    else if (strcasecmp("Type", option->key) == 0)
-      ret = cf_util_get_string(option, &dd->type);
-    else if (strcasecmp("TypeInstance", option->key) == 0)
-      ret = cf_util_get_string(option, &dd->type_instance);
-    else if (strcasecmp("Shift", option->key) == 0)
-      ret = cf_util_get_double(option, &dd->shift);
-    else if (strcasecmp("Scale", option->key) == 0)
-      ret = cf_util_get_double(option, &dd->scale);
-    else if (strcasecmp("OIDs", option->key) == 0)
-      ret = snmp_agent_config_data_oids(dd, option);
-    else {
-      WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key);
-      ret = -1;
-    }
+  int ret = cf_util_get_string(ci, &index_key->regex);
+  if (ret != 0)
+    return -1;
 
-    if (ret != 0) {
-      snmp_agent_free_data(&dd);
-      return -1;
-    }
+  ret = regcomp(&index_key->regex_info, index_key->regex, REG_EXTENDED);
+  if (ret) {
+    ERROR(PLUGIN_NAME ": Could not compile regex for %s", dd->name);
+    return -1;
   }
 
-  llentry_t *entry = llentry_create(dd->name, dd);
-  if (entry == NULL) {
-    snmp_agent_free_data(&dd);
-    return -ENOMEM;
+  index_key_src_t source = index_key->source;
+  if (td->tokens[source] == NULL) {
+    td->tokens[source] =
+        c_avl_create((int (*)(const void *, const void *))num_compare);
+    if (td->tokens[source] == NULL) {
+      ERROR(PLUGIN_NAME ": Could not allocate memory for AVL tree");
+      return -ENOMEM;
+    }
   }
 
-  llist_append(td->columns, entry);
-
   return 0;
 }
 
-static int snmp_agent_config_data(oconfig_item_t *ci) {
+static int snmp_agent_config_index_key(table_definition_t *td,
+                                       data_definition_t *dd,
+                                       oconfig_item_t *ci) {
+  int ret = 0;
+
+  for (int i = 0; (i < ci->children_num && ret == 0); i++) {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp("Source", option->key) == 0)
+      ret = snmp_agent_config_index_key_source(td, dd, option);
+    else if (strcasecmp("Regex", option->key) == 0)
+      ret = snmp_agent_config_index_key_regex(td, dd, option);
+    else if (strcasecmp("Group", option->key) == 0)
+      ret = cf_util_get_int(option, &td->index_keys[dd->index_key_pos].group);
+  }
+
+  return ret;
+}
+
+/* This function parses configuration of both scalar and table column
+ * because they have nearly the same structure */
+static int snmp_agent_config_table_column(table_definition_t *td,
+                                          oconfig_item_t *ci) {
   data_definition_t *dd;
   int ret = 0;
+  oconfig_item_t *option_tmp = NULL;
 
   assert(ci != NULL);
 
   dd = calloc(1, sizeof(*dd));
   if (dd == NULL) {
-    ERROR(PLUGIN_NAME ": Failed to allocate memory for data definition");
+    ERROR(PLUGIN_NAME ": Failed to allocate memory for table data definition");
     return -ENOMEM;
   }
 
   ret = cf_util_get_string(ci, &dd->name);
   if (ret != 0) {
-    free(dd);
+    sfree(dd);
     return -1;
   }
 
   dd->scale = 1.0;
   dd->shift = 0.0;
+  /* NULL if it's a scalar */
+  dd->table = td;
+  dd->is_index_key = false;
 
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *option = ci->children + i;
 
-    if (strcasecmp("Instance", option->key) == 0)
-      ret = cf_util_get_boolean(option, &dd->is_instance);
-    else if (strcasecmp("Plugin", option->key) == 0)
+    /* First 3 options are reserved for table entry only */
+    if (td != NULL && strcasecmp("IndexKey", option->key) == 0) {
+      dd->is_index_key = true;
+      option_tmp = option;
+    } else if (strcasecmp("Plugin", option->key) == 0)
       ret = cf_util_get_string(option, &dd->plugin);
     else if (strcasecmp("PluginInstance", option->key) == 0)
       ret = cf_util_get_string(option, &dd->plugin_instance);
@@ -1051,17 +1642,37 @@ static int snmp_agent_config_data(oconfig_item_t *ci) {
     }
   }
 
+  if (dd->is_index_key) {
+    ret = snmp_agent_config_index_key(td, dd, option_tmp);
+    td->index_keys[dd->index_key_pos].type =
+        snmp_agent_get_asn_type(dd->oids[0].oid, dd->oids[0].oid_len);
+
+    if (ret != 0) {
+      snmp_agent_free_data(&dd);
+      return -1;
+    }
+  }
+
   llentry_t *entry = llentry_create(dd->name, dd);
   if (entry == NULL) {
     snmp_agent_free_data(&dd);
     return -ENOMEM;
   }
 
-  llist_append(g_agent->scalars, entry);
+  /* Append to column list in parent table */
+  if (td != NULL)
+    llist_append(td->columns, entry);
+  else
+    llentry_destroy(entry);
 
   return 0;
 }
 
+/* Parses scalar configuration entry */
+static int snmp_agent_config_scalar(oconfig_item_t *ci) {
+  return snmp_agent_config_table_column(NULL, ci);
+}
+
 static int num_compare(const int *a, const int *b) {
   assert((a != NULL) && (b != NULL));
   if (*a < *b)
@@ -1072,6 +1683,10 @@ static int num_compare(const int *a, const int *b) {
     return 0;
 }
 
+static int oid_compare(const oid_t *a, const oid_t *b) {
+  return snmp_oid_compare(a->oid, a->oid_len, b->oid, b->oid_len);
+}
+
 static int snmp_agent_config_table(oconfig_item_t *ci) {
   table_definition_t *td;
   int ret = 0;
@@ -1097,6 +1712,10 @@ static int snmp_agent_config_table(oconfig_item_t *ci) {
     return -ENOMEM;
   }
 
+  for (int i = 0; i < MAX_KEY_SOURCES; i++)
+    td->tokens[i] = NULL;
+  td->tokens_done = false;
+
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *option = ci->children + i;
 
@@ -1105,7 +1724,7 @@ static int snmp_agent_config_table(oconfig_item_t *ci) {
     else if (strcasecmp("SizeOID", option->key) == 0)
       ret = snmp_agent_config_table_size_oid(td, option);
     else if (strcasecmp("Data", option->key) == 0)
-      ret = snmp_agent_config_table_data(td, option);
+      ret = snmp_agent_config_table_column(td, option);
     else {
       WARNING(PLUGIN_NAME ": Option `%s' not allowed here", option->key);
       ret = -1;
@@ -1117,8 +1736,13 @@ static int snmp_agent_config_table(oconfig_item_t *ci) {
     }
   }
 
+  /* Preparing index list container */
+  ret = snmp_agent_prep_index_list(td, &td->index_list_cont);
+  if (ret != 0)
+    return -EINVAL;
+
   td->instance_index =
-      c_avl_create((int (*)(const void *, const void *))strcmp);
+      c_avl_create((int (*)(const void *, const void *))oid_compare);
   if (td->instance_index == NULL) {
     snmp_agent_free_table(&td);
     return -ENOMEM;
@@ -1131,11 +1755,19 @@ static int snmp_agent_config_table(oconfig_item_t *ci) {
     return -ENOMEM;
   }
 
+  td->instance_oids =
+      c_avl_create((int (*)(const void *, const void *))oid_compare);
+  if (td->instance_oids == NULL) {
+    snmp_agent_free_table(&td);
+    return -ENOMEM;
+  }
+
   llentry_t *entry = llentry_create(td->name, td);
   if (entry == NULL) {
     snmp_agent_free_table(&td);
     return -ENOMEM;
   }
+
   llist_append(g_agent->tables, entry);
 
   return 0;
@@ -1236,98 +1868,167 @@ static int snmp_agent_unregister_oid_index(oid_t *oid, int index) {
   oid_t new_oid;
   memcpy(&new_oid, oid, sizeof(*oid));
   new_oid.oid[new_oid.oid_len++] = index;
-  return unregister_mib(new_oid.oid, new_oid.oid_len);
+  return snmp_agent_unregister_oid(&new_oid);
 }
 
-static int snmp_agent_update_index(table_definition_t *td,
-                                   const char *instance) {
+static int snmp_agent_update_instance_oids(c_avl_tree_t *tree, oid_t *index_oid,
+                                           int value) {
+  int *oids_num; /* number of oids registered for instance */
 
-  if (c_avl_get(td->instance_index, instance, NULL) == 0)
-    return 0;
+  if (c_avl_get(tree, index_oid, (void **)&oids_num) == 0) {
+    *oids_num += value;
+    return *oids_num;
+  } else {
+    ERROR(PLUGIN_NAME ": Error updating index data");
+    return -1;
+  }
+}
 
+static int snmp_agent_update_index(data_definition_t *dd,
+                                   table_definition_t *td, oid_t *index_oid,
+                                   bool *free_index_oid) {
   int ret;
   int *index = NULL;
-  char *ins;
+  int *value = NULL;
 
-  ins = strdup(instance);
-  if (ins == NULL)
-    return -ENOMEM;
+  if (c_avl_get(td->instance_index, (void *)index_oid, (void **)&index) != 0) {
+    /* We'll keep index_oid stored in AVL tree */
+    *free_index_oid = false;
 
-  /* need to generate index for the table */
-  if (td->index_oid.oid_len) {
-    index = calloc(1, sizeof(*index));
-    if (index == NULL) {
-      sfree(ins);
-      return -ENOMEM;
+    /* need to generate index for the table */
+    if (td->index_oid.oid_len) {
+      index = calloc(1, sizeof(*index));
+      if (index == NULL) {
+        ret = -ENOMEM;
+        goto error;
+      }
+
+      *index = c_avl_size(td->instance_index) + 1;
+
+      ret = c_avl_insert(td->instance_index, index_oid, index);
+      if (ret != 0)
+        goto free_index;
+
+      ret = c_avl_insert(td->index_instance, index, index_oid);
+      if (ret < 0) {
+        DEBUG(PLUGIN_NAME ": Failed to update index_instance for '%s' table",
+              td->name);
+        goto remove_avl_index_oid;
+      }
+
+      ret = snmp_agent_register_oid_index(&td->index_oid, *index,
+                                          snmp_agent_table_index_oid_handler);
+      if (ret != 0)
+        goto remove_avl_index;
+    } else {
+      /* instance as a key is required for any table */
+      ret = c_avl_insert(td->instance_index, index_oid, NULL);
+      if (ret != 0)
+        goto error;
     }
 
-    *index = c_avl_size(td->instance_index) + 1;
+    value = calloc(1, sizeof(*value));
 
-    ret = c_avl_insert(td->instance_index, ins, index);
-    if (ret != 0) {
-      sfree(ins);
-      sfree(index);
-      return ret;
+    if (value == NULL) {
+      ERROR(PLUGIN_NAME ": Failed to allocate memory");
+      ret = -ENOMEM;
+      goto unregister_index;
     }
 
-    ret = c_avl_insert(td->index_instance, index, ins);
+    ret = c_avl_insert(td->instance_oids, index_oid, value);
+
     if (ret < 0) {
-      DEBUG(PLUGIN_NAME ": Failed to update index_instance for '%s' table",
+      DEBUG(PLUGIN_NAME ": Failed to update instance_oids for '%s' table",
             td->name);
-      c_avl_remove(td->instance_index, ins, NULL, (void **)&index);
-      sfree(ins);
-      sfree(index);
-      return ret;
+      goto free_value;
     }
 
-    ret = snmp_agent_register_oid_index(&td->index_oid, *index,
-                                        snmp_agent_table_index_oid_handler);
-    if (ret != 0)
-      return ret;
-  } else {
-    /* instance as a key is required for any table */
-    ret = c_avl_insert(td->instance_index, ins, ins);
-    if (ret != 0) {
-      sfree(ins);
-      return ret;
-    }
-  }
+    int keys_processed = 0;
 
-  /* register new oids for all columns */
-  for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
-    data_definition_t *dd = de->value;
+    /* Registering index keys OIDs */
+    for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
+      data_definition_t *idd = de->value;
+      if (!idd->is_index_key)
+        continue;
 
-    for (size_t i = 0; i < dd->oids_len; i++) {
-      if (td->index_oid.oid_len) {
-        ret = snmp_agent_register_oid_index(&dd->oids[i], *index,
-                                            snmp_agent_table_oid_handler);
-      } else {
-        ret = snmp_agent_register_oid_string(&dd->oids[i], ins,
-                                             snmp_agent_table_oid_handler);
+      for (size_t i = 0; i < idd->oids_len; i++) {
+        if (td->index_oid.oid_len)
+          ret = snmp_agent_register_oid_index(&idd->oids[i], *index,
+                                              snmp_agent_table_oid_handler);
+        else
+          ret = snmp_agent_register_oid_string(&idd->oids[i], index_oid,
+                                               snmp_agent_table_oid_handler);
+
+        if (ret != 0) {
+          ERROR(PLUGIN_NAME ": Could not register OID");
+          goto free_index;
+        }
       }
 
-      if (ret != 0)
-        return ret;
+      if (++keys_processed >= td->index_keys_len)
+        break;
     }
   }
 
-  DEBUG(PLUGIN_NAME ": Updated index for '%s' table [%d, %s]", td->name,
-        (index != NULL) ? *index : -1, ins);
+  ret = 0;
 
-  notification_t n = {
-      .severity = NOTIF_OKAY, .time = cdtime(), .plugin = PLUGIN_NAME};
-  sstrncpy(n.host, hostname_g, sizeof(n.host));
-  sstrncpy(n.plugin_instance, ins, sizeof(n.plugin_instance));
-  snprintf(n.message, sizeof(n.message),
-           "Data row added to table %s instance %s index %d", td->name, ins,
-           (index != NULL) ? *index : -1);
-  plugin_dispatch_notification(&n);
+  for (size_t i = 0; i < dd->oids_len; i++) {
+    if (td->index_oid.oid_len)
+      ret = snmp_agent_register_oid_index(&dd->oids[i], *index,
+                                          snmp_agent_table_oid_handler);
+    else
+      ret = snmp_agent_register_oid_string(&dd->oids[i], index_oid,
+                                           snmp_agent_table_oid_handler);
+
+    if (ret < 0)
+      goto free_index;
+    else if (ret == OID_EXISTS)
+      break;
+    else if (snmp_agent_update_instance_oids(td->instance_oids, index_oid, 1) <
+             0)
+      goto free_index;
+  }
+
+  if (ret != OID_EXISTS) {
+    char index_str[DATA_MAX_NAME_LEN];
+
+    if (index == NULL)
+      snmp_agent_oid_to_string(index_str, sizeof(index_str), index_oid);
+    else
+      snprintf(index_str, sizeof(index_str), "%d", *index);
+
+    notification_t n = {
+        .severity = NOTIF_OKAY, .time = cdtime(), .plugin = PLUGIN_NAME};
+    sstrncpy(n.host, hostname_g, sizeof(n.host));
+    snprintf(n.message, sizeof(n.message),
+             "Data added to table %s with index %s", td->name, index_str);
+    DEBUG(PLUGIN_NAME ": %s", n.message);
+
+    plugin_dispatch_notification(&n);
+  }
 
   return 0;
+
+free_value:
+  sfree(value);
+unregister_index:
+  if (td->index_oid.oid_len)
+    snmp_agent_unregister_oid_index(index_oid, *index);
+remove_avl_index:
+  if (td->index_oid.oid_len)
+    c_avl_remove(td->index_instance, index, NULL, NULL);
+remove_avl_index_oid:
+  c_avl_remove(td->instance_index, index_oid, NULL, NULL);
+free_index:
+  if (index != NULL)
+    sfree(index);
+error:
+  *free_index_oid = true;
+
+  return ret;
 }
 
 static int snmp_agent_write(value_list_t const *vl) {
-
   if (vl == NULL)
     return -EINVAL;
 
@@ -1337,11 +2038,27 @@ static int snmp_agent_write(value_list_t const *vl) {
     for (llentry_t *de = llist_head(td->columns); de != NULL; de = de->next) {
       data_definition_t *dd = de->value;
 
-      if (!dd->is_instance) {
+      if (!dd->is_index_key) {
         if (CHECK_DD_TYPE(dd, vl->plugin, vl->plugin_instance, vl->type,
                           vl->type_instance)) {
-          snmp_agent_update_index(td, vl->plugin_instance);
-          return 0;
+          oid_t *index_oid = calloc(1, sizeof(*index_oid));
+          bool free_index_oid = true;
+
+          if (index_oid == NULL) {
+            ERROR(PLUGIN_NAME ": Could not allocate memory for index_oid");
+            return -ENOMEM;
+          }
+
+          int ret = snmp_agent_generate_index(td, vl, index_oid);
+
+          if (ret == 0)
+            ret = snmp_agent_update_index(dd, td, index_oid, &free_index_oid);
+
+          /* Index exists or update failed */
+          if (free_index_oid)
+            sfree(index_oid);
+
+          return ret;
         }
       }
     }
@@ -1363,10 +2080,6 @@ static int snmp_agent_collect(const data_set_t *ds, const value_list_t *vl,
 }
 
 static int snmp_agent_preinit(void) {
-  if (g_agent != NULL) {
-    /* already initialized if config callback was called before init callback */
-    return 0;
-  }
 
   g_agent = calloc(1, sizeof(*g_agent));
   if (g_agent == NULL) {
@@ -1376,22 +2089,26 @@ static int snmp_agent_preinit(void) {
 
   g_agent->tables = llist_create();
   g_agent->scalars = llist_create();
+  g_agent->registered_oids =
+      c_avl_create((int (*)(const void *, const void *))oid_compare);
 
   if (g_agent->tables == NULL || g_agent->scalars == NULL) {
     ERROR(PLUGIN_NAME ": llist_create() failed");
     llist_destroy(g_agent->scalars);
     llist_destroy(g_agent->tables);
+    c_avl_destroy(g_agent->registered_oids);
     return -ENOMEM;
   }
 
   int err;
-  /* make us a agentx client. */
+  /* make us an agentx client. */
   err = netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE,
                                1);
   if (err != 0) {
     ERROR(PLUGIN_NAME ": Failed to set agent role (%d)", err);
     llist_destroy(g_agent->scalars);
     llist_destroy(g_agent->tables);
+    c_avl_destroy(g_agent->registered_oids);
     return -1;
   }
 
@@ -1405,6 +2122,7 @@ static int snmp_agent_preinit(void) {
     ERROR(PLUGIN_NAME ": Failed to initialize the agent library (%d)", err);
     llist_destroy(g_agent->scalars);
     llist_destroy(g_agent->tables);
+    c_avl_destroy(g_agent->registered_oids);
     return -1;
   }
 
@@ -1480,6 +2198,27 @@ static void *snmp_agent_thread_run(void __attribute__((unused)) * arg) {
 
 static int snmp_agent_register_oid(oid_t *oid, Netsnmp_Node_Handler *handler) {
   netsnmp_handler_registration *reg;
+
+  if (c_avl_get(g_agent->registered_oids, (void *)oid, NULL) == 0)
+    return OID_EXISTS;
+  else {
+    oid_t *new_oid = calloc(1, sizeof(*oid));
+
+    if (new_oid == NULL) {
+      ERROR(PLUGIN_NAME ": Could not allocate memory to register new OID");
+      return -ENOMEM;
+    }
+
+    memcpy(new_oid, oid, sizeof(*oid));
+
+    int ret = c_avl_insert(g_agent->registered_oids, (void *)new_oid, NULL);
+    if (ret != 0) {
+      ERROR(PLUGIN_NAME ": Could not allocate memory to register new OID");
+      sfree(new_oid);
+      return -ENOMEM;
+    }
+  }
+
   char *oid_name = snmp_agent_get_oid_name(oid->oid, oid->oid_len - 1);
   char oid_str[DATA_MAX_NAME_LEN];
 
@@ -1554,13 +2293,22 @@ static int snmp_agent_shutdown(void) {
   pthread_mutex_destroy(&g_agent->lock);
   pthread_mutex_destroy(&g_agent->agentx_lock);
 
+  /* Freeing registered OIDs list */
+  void *oid;
+
+  if (g_agent->registered_oids != NULL) {
+    while (c_avl_pick(g_agent->registered_oids, &oid, NULL) == 0) {
+      sfree(oid);
+    }
+    c_avl_destroy(g_agent->registered_oids);
+  }
+
   sfree(g_agent);
 
   return ret;
 }
 
 static int snmp_agent_config(oconfig_item_t *ci) {
-
   int ret = snmp_agent_preinit();
 
   if (ret != 0) {
@@ -1571,7 +2319,7 @@ static int snmp_agent_config(oconfig_item_t *ci) {
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
     if (strcasecmp("Data", child->key) == 0) {
-      ret = snmp_agent_config_data(child);
+      ret = snmp_agent_config_scalar(child);
     } else if (strcasecmp("Table", child->key) == 0) {
       ret = snmp_agent_config_table(child);
     } else {
@@ -1588,7 +2336,7 @@ static int snmp_agent_config(oconfig_item_t *ci) {
     }
   }
 
-  ret = snmp_agent_validate_data();
+  ret = snmp_agent_validate_config();
   if (ret != 0) {
     ERROR(PLUGIN_NAME ": Invalid configuration provided");
     snmp_agent_free_config();
diff --git a/src/snmp_agent_test.c b/src/snmp_agent_test.c
new file mode 100644 (file)
index 0000000..581f33d
--- /dev/null
@@ -0,0 +1,831 @@
+/**
+ * collectd - src/snmp_agent_test.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:
+ *   Marcin Mozejko <marcinx.mozejko@intel.com>
+ **/
+
+#include "snmp_agent.c"
+#include "testing.h"
+
+#define TEST_HOSTNAME "test_hostname"
+#define TEST_PLUGIN "test_plugin"
+#define TEST_PLUGIN_INST "test_plugin_inst"
+#define TEST_TYPE "test_type"
+#define TEST_TYPE_INST "test_type_inst"
+
+DEF_TEST(oid_to_string) {
+  oid_t o = {.oid = {1, 2, 3, 4, 5, 6, 7, 8, 9}, .oid_len = 9};
+  char oid_str[DATA_MAX_NAME_LEN];
+
+  int ret = snmp_agent_oid_to_string(oid_str, DATA_MAX_NAME_LEN, &o);
+  EXPECT_EQ_INT(o.oid_len * 2 - 1, ret);
+  EXPECT_EQ_STR("1.2.3.4.5.6.7.8.9", oid_str);
+
+  return 0;
+}
+
+/* Testing formatting metric name for simple scalar */
+DEF_TEST(format_name_scalar) {
+  data_definition_t *dd = calloc(1, sizeof(*dd));
+
+  dd->plugin = TEST_PLUGIN;
+  dd->plugin_instance = TEST_PLUGIN_INST;
+  dd->type = TEST_TYPE;
+  dd->type_instance = TEST_TYPE_INST;
+
+  char name[DATA_MAX_NAME_LEN];
+  int ret = snmp_agent_format_name(name, sizeof(name), dd, NULL);
+
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR(
+      "example.com/test_plugin-test_plugin_inst/test_type-test_type_inst",
+      name);
+
+  sfree(dd);
+
+  return 0;
+}
+
+DEF_TEST(format_name_simple_index) {
+  netsnmp_variable_list *index_list_tmp = NULL;
+  oid_t index_oid;
+  data_definition_t *dd = calloc(1, sizeof(*dd));
+  table_definition_t *td = calloc(1, sizeof(*td));
+
+  td->index_list_cont = NULL;
+  td->index_keys[0].source = INDEX_PLUGIN_INSTANCE;
+  td->index_keys[0].type = ASN_OCTET_STR;
+  td->index_keys[1].source = INDEX_TYPE_INSTANCE;
+  td->index_keys[1].type = ASN_OCTET_STR;
+  dd->table = td;
+  dd->plugin = TEST_PLUGIN;
+  dd->type = TEST_TYPE;
+
+  const char plugin_inst[] = TEST_PLUGIN_INST;
+  const char type_inst[] = TEST_TYPE_INST;
+
+  snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_OCTET_STR,
+                            (const u_char *)plugin_inst, strlen(plugin_inst));
+  snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_OCTET_STR,
+                            (const u_char *)type_inst, strlen(type_inst));
+
+  build_oid_noalloc(index_oid.oid, sizeof(index_oid.oid), &index_oid.oid_len,
+                    NULL, 0, index_list_tmp);
+
+  snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, NULL,
+                            0);
+  snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, NULL,
+                            0);
+
+  char name[DATA_MAX_NAME_LEN];
+
+  int ret = snmp_agent_format_name(name, DATA_MAX_NAME_LEN, dd, &index_oid);
+
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR(
+      "example.com/test_plugin-test_plugin_inst/test_type-test_type_inst",
+      name);
+
+  snmp_free_varbind(index_list_tmp);
+  snmp_free_varbind(td->index_list_cont);
+  sfree(dd);
+  sfree(td);
+
+  return 0;
+}
+
+DEF_TEST(format_name_regex_index) {
+  netsnmp_variable_list *index_list_tmp = NULL;
+  oid_t index_oid;
+  data_definition_t *dd = calloc(1, sizeof(*dd));
+  table_definition_t *td = calloc(1, sizeof(*td));
+
+  td->index_keys_len = 3;
+  td->index_list_cont = NULL;
+  td->index_keys[0].source = INDEX_PLUGIN_INSTANCE;
+  td->index_keys[0].type = ASN_OCTET_STR;
+  td->index_keys[1].source = INDEX_TYPE_INSTANCE;
+  td->index_keys[1].type = ASN_INTEGER;
+  td->index_keys[1].regex = "^vcpu_([0-9]{1,3})-cpu_[0-9]{1,3}$";
+  td->index_keys[1].group = 1;
+  td->index_keys[2].source = INDEX_TYPE_INSTANCE;
+  td->index_keys[2].type = ASN_INTEGER;
+  td->index_keys[2].regex = "^vcpu_[0-9]{1,3}-cpu_([0-9]{1,3})$";
+  td->index_keys[2].group = 1;
+
+  dd->table = td;
+  dd->plugin = TEST_PLUGIN;
+  dd->type = TEST_TYPE;
+
+  const char plugin_inst[] = TEST_PLUGIN_INST;
+  int vcpu = 1;
+  int cpu = 10;
+
+  snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_OCTET_STR,
+                            (const u_char *)plugin_inst, strlen(plugin_inst));
+  snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_INTEGER,
+                            (const u_char *)&vcpu, sizeof(vcpu));
+  snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_INTEGER,
+                            (const u_char *)&cpu, sizeof(cpu));
+
+  build_oid_noalloc(index_oid.oid, sizeof(index_oid.oid), &index_oid.oid_len,
+                    NULL, 0, index_list_tmp);
+
+  token_t *token;
+  int *offset;
+
+  td->tokens[INDEX_TYPE_INSTANCE] =
+      c_avl_create((int (*)(const void *, const void *))num_compare);
+  snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, NULL,
+                            0);
+
+  token = malloc(sizeof(*token));
+  offset = malloc(sizeof(*offset));
+  token->key = snmp_varlist_add_variable(&td->index_list_cont, NULL, 0,
+                                         ASN_INTEGER, NULL, 0);
+  token->str = strdup("vcpu_");
+  *offset = 0;
+  int ret = c_avl_insert(td->tokens[INDEX_TYPE_INSTANCE], (void *)offset,
+                         (void *)token);
+
+  token = malloc(sizeof(*token));
+  offset = malloc(sizeof(*offset));
+  token->key = snmp_varlist_add_variable(&td->index_list_cont, NULL, 0,
+                                         ASN_INTEGER, NULL, 0);
+  token->str = strdup("-cpu_");
+  *offset = 6;
+  ret += c_avl_insert(td->tokens[INDEX_TYPE_INSTANCE], (void *)offset,
+                      (void *)token);
+  char name[DATA_MAX_NAME_LEN];
+
+  ret += snmp_agent_format_name(name, DATA_MAX_NAME_LEN, dd, &index_oid);
+
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR(
+      "example.com/test_plugin-test_plugin_inst/test_type-vcpu_1-cpu_10", name);
+  while (c_avl_pick(td->tokens[INDEX_TYPE_INSTANCE], (void **)&offset,
+                    (void **)&token) == 0) {
+    sfree(offset);
+    sfree(token->str);
+    sfree(token);
+  }
+  c_avl_destroy(td->tokens[INDEX_TYPE_INSTANCE]);
+  snmp_free_varbind(index_list_tmp);
+  snmp_free_varbind(td->index_list_cont);
+  sfree(dd);
+  sfree(td);
+
+  return 0;
+}
+
+DEF_TEST(prep_index_list) {
+  table_definition_t *td = calloc(1, sizeof(*td));
+
+  assert(td != NULL);
+  td->index_keys_len = 5;
+  td->index_keys[0].source = INDEX_HOST;
+  td->index_keys[0].type = ASN_OCTET_STR;
+  td->index_keys[1].source = INDEX_PLUGIN;
+  td->index_keys[1].type = ASN_OCTET_STR;
+  td->index_keys[2].source = INDEX_PLUGIN_INSTANCE;
+  td->index_keys[2].type = ASN_INTEGER;
+  td->index_keys[3].source = INDEX_TYPE;
+  td->index_keys[3].type = ASN_INTEGER;
+  td->index_keys[4].source = INDEX_TYPE_INSTANCE;
+  td->index_keys[4].type = ASN_OCTET_STR;
+  td->index_list_cont = NULL;
+
+  int ret = snmp_agent_prep_index_list(td, &td->index_list_cont);
+  EXPECT_EQ_INT(0, ret);
+
+  netsnmp_variable_list *key = td->index_list_cont;
+
+  OK(key != NULL);
+  EXPECT_EQ_INT(ASN_OCTET_STR, key->type);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_INT(ASN_OCTET_STR, key->type);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_INT(ASN_INTEGER, key->type);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_INT(ASN_INTEGER, key->type);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_INT(ASN_OCTET_STR, key->type);
+  key = key->next_variable;
+  OK(key == NULL);
+
+  snmp_free_varbind(td->index_list_cont);
+  sfree(td);
+
+  return 0;
+}
+
+DEF_TEST(fill_index_list_simple) {
+  table_definition_t *td = calloc(1, sizeof(*td));
+  assert(td != NULL);
+
+  /* Preparing value list */
+  value_list_t *vl = calloc(1, sizeof(*vl));
+  assert(vl != NULL);
+  strncpy(vl->host, TEST_HOSTNAME, DATA_MAX_NAME_LEN);
+  strncpy(vl->plugin, TEST_PLUGIN, DATA_MAX_NAME_LEN);
+  strncpy(vl->plugin_instance, TEST_PLUGIN_INST, DATA_MAX_NAME_LEN);
+  strncpy(vl->type, TEST_TYPE, DATA_MAX_NAME_LEN);
+  strncpy(vl->type_instance, TEST_TYPE_INST, DATA_MAX_NAME_LEN);
+
+  td->index_keys_len = 5;
+  td->index_keys[0].source = INDEX_HOST;
+  td->index_keys[0].type = ASN_OCTET_STR;
+  td->index_keys[1].source = INDEX_PLUGIN;
+  td->index_keys[1].type = ASN_OCTET_STR;
+  td->index_keys[2].source = INDEX_PLUGIN_INSTANCE;
+  td->index_keys[2].type = ASN_OCTET_STR;
+  td->index_keys[3].source = INDEX_TYPE;
+  td->index_keys[3].type = ASN_OCTET_STR;
+  td->index_keys[4].source = INDEX_TYPE_INSTANCE;
+  td->index_keys[4].type = ASN_OCTET_STR;
+
+  td->index_list_cont = NULL;
+  for (int i = 0; i < td->index_keys_len; i++)
+    snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR,
+                              NULL, 0);
+
+  int ret = snmp_agent_fill_index_list(td, vl);
+  EXPECT_EQ_INT(0, ret);
+
+  netsnmp_variable_list *key = td->index_list_cont;
+
+  ret = 0;
+
+  OK(key != NULL);
+  EXPECT_EQ_STR(vl->host, (char *)key->val.string);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_STR(vl->plugin, (char *)key->val.string);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_STR(vl->plugin_instance, (char *)key->val.string);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_STR(vl->type, (char *)key->val.string);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_STR(vl->type_instance, (char *)key->val.string);
+  key = key->next_variable;
+  OK(key == NULL);
+
+  snmp_free_varbind(td->index_list_cont);
+  sfree(vl);
+  sfree(td);
+
+  return 0;
+}
+
+DEF_TEST(fill_index_list_regex) {
+  table_definition_t *td = calloc(1, sizeof(*td));
+  int ret = 0;
+
+  assert(td != NULL);
+
+  /* Preparing value list */
+  value_list_t *vl = calloc(1, sizeof(*vl));
+  strncpy(vl->plugin_instance, TEST_PLUGIN_INST, DATA_MAX_NAME_LEN);
+  strncpy(vl->type_instance, "1test2test3", DATA_MAX_NAME_LEN);
+
+  td->index_keys_len = 4;
+  td->index_keys[0].source = INDEX_PLUGIN_INSTANCE;
+  td->index_keys[0].type = ASN_OCTET_STR;
+  td->index_keys[1].source = INDEX_TYPE_INSTANCE;
+  td->index_keys[1].type = ASN_INTEGER;
+  td->index_keys[1].regex = "^([0-9])test[0-9]test[0-9]$";
+  td->index_keys[1].group = 1;
+  td->index_keys[2].source = INDEX_TYPE_INSTANCE;
+  td->index_keys[2].type = ASN_INTEGER;
+  td->index_keys[2].regex = "^[0-9]test([0-9])test[0-9]$";
+  td->index_keys[2].group = 1;
+  td->index_keys[3].source = INDEX_TYPE_INSTANCE;
+  td->index_keys[3].type = ASN_INTEGER;
+  td->index_keys[3].regex = "^[0-9]test[0-9]test([0-9])$";
+  td->index_keys[3].group = 1;
+
+  td->index_list_cont = NULL;
+  snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, NULL,
+                            0);
+  for (int i = 1; i < td->index_keys_len; i++) {
+    snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_INTEGER, NULL,
+                              0);
+    ret = regcomp(&td->index_keys[i].regex_info, td->index_keys[i].regex,
+                  REG_EXTENDED);
+    EXPECT_EQ_INT(0, ret);
+  }
+  td->tokens[INDEX_TYPE_INSTANCE] =
+      c_avl_create((int (*)(const void *, const void *))num_compare);
+  assert(td->tokens[INDEX_TYPE_INSTANCE] != NULL);
+
+  ret = snmp_agent_fill_index_list(td, vl);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(1, td->tokens_done);
+
+  netsnmp_variable_list *key = td->index_list_cont;
+
+  OK(key != NULL);
+  EXPECT_EQ_STR(vl->plugin_instance, (char *)key->val.string);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_INT(1, *key->val.integer);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_INT(2, *key->val.integer);
+  key = key->next_variable;
+  OK(key != NULL);
+  EXPECT_EQ_INT(3, *key->val.integer);
+  key = key->next_variable;
+  OK(key == NULL);
+
+  token_t *token;
+  int *offset;
+
+  while (c_avl_pick(td->tokens[INDEX_TYPE_INSTANCE], (void **)&offset,
+                    (void **)&token) == 0) {
+    sfree(offset);
+    sfree(token->str);
+    sfree(token);
+  }
+
+  c_avl_destroy(td->tokens[INDEX_TYPE_INSTANCE]);
+  snmp_free_varbind(td->index_list_cont);
+  sfree(vl);
+
+  for (int i = 0; i < td->index_keys_len; i++) {
+    regfree(&td->index_keys[i].regex_info);
+  }
+  sfree(td);
+
+  return 0;
+}
+
+DEF_TEST(config_index_key_source) {
+  oconfig_item_t *ci = calloc(1, sizeof(*ci));
+  table_definition_t *td = calloc(1, sizeof(*td));
+  data_definition_t *dd = calloc(1, sizeof(*dd));
+
+  assert(ci != NULL);
+  assert(td != NULL);
+  assert(dd != NULL);
+
+  ci->values = calloc(1, sizeof(*ci->values));
+  assert(ci->values != NULL);
+  ci->values_num = 1;
+  ci->values->value.string = "PluginInstance";
+  ci->values->type = OCONFIG_TYPE_STRING;
+
+  int ret = snmp_agent_config_index_key_source(td, dd, ci);
+
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(1, td->index_keys_len);
+  EXPECT_EQ_INT(0, dd->index_key_pos);
+  EXPECT_EQ_INT(INDEX_PLUGIN_INSTANCE, td->index_keys[0].source);
+  EXPECT_EQ_INT(GROUP_UNUSED, td->index_keys[0].group);
+  OK(td->index_keys[0].regex == NULL);
+
+  sfree(ci->values);
+  sfree(ci);
+  sfree(td);
+  sfree(dd);
+
+  return 0;
+}
+
+DEF_TEST(config_index_key_regex) {
+  oconfig_item_t *ci = calloc(1, sizeof(*ci));
+  table_definition_t *td = calloc(1, sizeof(*td));
+  data_definition_t *dd = calloc(1, sizeof(*dd));
+
+  assert(ci != NULL);
+  assert(td != NULL);
+  assert(dd != NULL);
+
+  dd->index_key_pos = 0;
+  td->index_keys_len = 1;
+  td->index_keys[0].source = INDEX_PLUGIN_INSTANCE;
+  td->index_keys[0].group = 1;
+  ci->values = calloc(1, sizeof(*ci->values));
+  assert(ci->values != NULL);
+  ci->values_num = 1;
+  ci->values->value.string = "^([0-9])test[0-9]test[0-9]$";
+  ci->values->type = OCONFIG_TYPE_STRING;
+
+  int ret = snmp_agent_config_index_key_regex(td, dd, ci);
+
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR(td->index_keys[0].regex, "^([0-9])test[0-9]test[0-9]$");
+  OK(td->tokens[INDEX_PLUGIN_INSTANCE] != NULL);
+
+  c_avl_destroy(td->tokens[INDEX_PLUGIN_INSTANCE]);
+  sfree(ci->values);
+  sfree(ci);
+  sfree(td->index_keys[0].regex);
+  regfree(&td->index_keys[0].regex_info);
+  sfree(td);
+  sfree(dd);
+
+  return 0;
+}
+
+DEF_TEST(config_index_key) {
+  oconfig_item_t *ci = calloc(1, sizeof(*ci));
+  table_definition_t *td = calloc(1, sizeof(*td));
+  data_definition_t *dd = calloc(1, sizeof(*dd));
+
+  assert(ci != NULL);
+  assert(td != NULL);
+  assert(dd != NULL);
+
+  ci->children_num = 3;
+  ci->children = calloc(1, sizeof(*ci->children) * ci->children_num);
+
+  ci->children[0].key = "Source";
+  ci->children[0].parent = ci;
+  ci->children[0].values_num = 1;
+  ci->children[0].values = calloc(1, sizeof(*ci->children[0].values));
+  assert(ci->children[0].values != NULL);
+  ci->children[0].values->value.string = "PluginInstance";
+  ci->children[0].values->type = OCONFIG_TYPE_STRING;
+
+  ci->children[1].key = "Regex";
+  ci->children[1].parent = ci;
+  ci->children[1].values_num = 1;
+  ci->children[1].values = calloc(1, sizeof(*ci->children[0].values));
+  assert(ci->children[1].values != NULL);
+  ci->children[1].values->value.string = "^([0-9])test[0-9]test[0-9]$";
+  ci->children[1].values->type = OCONFIG_TYPE_STRING;
+
+  ci->children[2].key = "Group";
+  ci->children[2].parent = ci;
+  ci->children[2].values_num = 1;
+  ci->children[2].values = calloc(1, sizeof(*ci->children[0].values));
+  assert(ci->children[2].values != NULL);
+  ci->children[2].values->value.number = 1;
+  ci->children[2].values->type = OCONFIG_TYPE_NUMBER;
+
+  int ret = snmp_agent_config_index_key(td, dd, ci);
+
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(1, td->index_keys_len);
+  EXPECT_EQ_INT(0, dd->index_key_pos);
+  EXPECT_EQ_INT(INDEX_PLUGIN_INSTANCE, td->index_keys[0].source);
+  EXPECT_EQ_INT(1, td->index_keys[0].group);
+  EXPECT_EQ_STR("^([0-9])test[0-9]test[0-9]$", td->index_keys[0].regex);
+  OK(td->tokens[INDEX_PLUGIN_INSTANCE] != NULL);
+
+  sfree(ci->children[0].values);
+  sfree(ci->children[1].values);
+  sfree(ci->children[2].values);
+
+  sfree(ci->children);
+  sfree(ci);
+
+  c_avl_destroy(td->tokens[INDEX_PLUGIN_INSTANCE]);
+  sfree(dd);
+  sfree(td->index_keys[0].regex);
+  regfree(&td->index_keys[0].regex_info);
+  sfree(td);
+
+  return 0;
+}
+
+DEF_TEST(parse_index_key) {
+  const char regex[] = "test-([0-9])-([0-9])";
+  const char input[] = "snmp-test-5-6";
+  regex_t regex_info;
+  regmatch_t match;
+
+  int ret = regcomp(&regex_info, regex, REG_EXTENDED);
+  EXPECT_EQ_INT(0, ret);
+
+  ret = snmp_agent_parse_index_key(input, &regex_info, 0, &match);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(5, match.rm_so);
+  EXPECT_EQ_INT(13, match.rm_eo);
+
+  ret = snmp_agent_parse_index_key(input, &regex_info, 1, &match);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(10, match.rm_so);
+  EXPECT_EQ_INT(11, match.rm_eo);
+
+  ret = snmp_agent_parse_index_key(input, &regex_info, 2, &match);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(12, match.rm_so);
+  EXPECT_EQ_INT(13, match.rm_eo);
+
+  regfree(&regex_info);
+
+  return 0;
+}
+
+DEF_TEST(create_token) {
+  c_avl_tree_t *tokens =
+      c_avl_create((int (*)(const void *, const void *))num_compare);
+  const char input[] = "testA1-testB2";
+
+  assert(tokens != NULL);
+
+  int ret = snmp_agent_create_token(input, 0, 5, tokens, NULL);
+  EXPECT_EQ_INT(0, ret);
+  ret = snmp_agent_create_token(input, 6, 6, tokens, NULL);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(2, c_avl_size(tokens));
+
+  token_t *token;
+  int *offset;
+
+  ret = c_avl_pick(tokens, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(6, *offset);
+  EXPECT_EQ_STR("-testB", token->str);
+  sfree(offset);
+  sfree(token->str);
+  sfree(token);
+
+  ret = c_avl_pick(tokens, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(0, *offset);
+  EXPECT_EQ_STR("testA", token->str);
+  sfree(offset);
+  sfree(token->str);
+  sfree(token);
+
+  ret = c_avl_pick(tokens, (void **)&offset, (void **)&token);
+  OK(ret != 0);
+
+  c_avl_destroy(tokens);
+
+  return 0;
+}
+
+DEF_TEST(delete_token) {
+  c_avl_tree_t *tokens =
+      c_avl_create((int (*)(const void *, const void *))num_compare);
+  const char input[] = "testA1-testB2-testC3";
+
+  assert(tokens != NULL);
+
+  int ret = snmp_agent_create_token(input, 0, 5, tokens, NULL);
+  EXPECT_EQ_INT(0, ret);
+  ret = snmp_agent_create_token(input, 6, 6, tokens, NULL);
+  EXPECT_EQ_INT(0, ret);
+  ret = snmp_agent_create_token(input, 13, 6, tokens, NULL);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(3, c_avl_size(tokens));
+  ret = snmp_agent_delete_token(6, tokens);
+  EXPECT_EQ_INT(0, ret);
+
+  token_t *token;
+  int *offset;
+
+  ret = c_avl_pick(tokens, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(0, *offset);
+  EXPECT_EQ_STR("testA", token->str);
+  sfree(offset);
+  sfree(token->str);
+  sfree(token);
+
+  ret = c_avl_pick(tokens, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(13, *offset);
+  EXPECT_EQ_STR("-testC", token->str);
+  sfree(offset);
+  sfree(token->str);
+  sfree(token);
+
+  ret = c_avl_pick(tokens, (void **)&offset, (void **)&token);
+  OK(ret != 0);
+
+  c_avl_destroy(tokens);
+
+  return 0;
+}
+
+DEF_TEST(get_token) {
+  c_avl_tree_t *tokens =
+      c_avl_create((int (*)(const void *, const void *))num_compare);
+  const char input[] = "testA1-testB2-testC3";
+
+  assert(tokens != NULL);
+
+  int ret = snmp_agent_create_token(input, 0, 5, tokens, NULL);
+  EXPECT_EQ_INT(0, ret);
+  ret = snmp_agent_create_token(input, 6, 6, tokens, NULL);
+  EXPECT_EQ_INT(0, ret);
+  ret = snmp_agent_create_token(input, 13, 6, tokens, NULL);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_INT(3, c_avl_size(tokens));
+  ret = snmp_agent_get_token(tokens, 12);
+  EXPECT_EQ_INT(6, ret);
+
+  token_t *token;
+  int *offset;
+
+  while (c_avl_pick(tokens, (void **)&offset, (void **)&token) == 0) {
+    sfree(offset);
+    sfree(token->str);
+    sfree(token);
+  }
+
+  c_avl_destroy(tokens);
+
+  return 0;
+}
+
+DEF_TEST(tokenize) {
+  regmatch_t m[3];
+
+  m[0].rm_so = 5;
+  m[0].rm_eo = 6;
+  m[1].rm_so = 12;
+  m[1].rm_eo = 13;
+  m[2].rm_so = 19;
+  m[2].rm_eo = 20;
+
+  c_avl_tree_t *tokens =
+      c_avl_create((int (*)(const void *, const void *))num_compare);
+  const char input[] = "testA1-testB2-testC3";
+  token_t *token;
+  int *offset;
+  c_avl_iterator_t *it;
+  int ret;
+
+  assert(tokens != NULL);
+
+  /* First pass */
+  ret = snmp_agent_tokenize(input, tokens, &m[0], NULL);
+  EXPECT_EQ_INT(0, ret);
+  it = c_avl_get_iterator(tokens);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("testA", token->str);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("-testB2-testC3", token->str);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  OK(ret != 0);
+  c_avl_iterator_destroy(it);
+
+  /* Second pass */
+  ret = snmp_agent_tokenize(input, tokens, &m[1], NULL);
+  EXPECT_EQ_INT(0, ret);
+  it = c_avl_get_iterator(tokens);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("testA", token->str);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("-testB", token->str);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("-testC3", token->str);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  OK(ret != 0);
+  c_avl_iterator_destroy(it);
+
+  /* Third pass */
+  ret = snmp_agent_tokenize(input, tokens, &m[2], NULL);
+  EXPECT_EQ_INT(0, ret);
+  it = c_avl_get_iterator(tokens);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("testA", token->str);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("-testB", token->str);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("-testC", token->str);
+  ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token);
+  OK(ret != 0);
+  c_avl_iterator_destroy(it);
+
+  while (c_avl_pick(tokens, (void **)&offset, (void **)&token) == 0) {
+    sfree(offset);
+    sfree(token->str);
+    sfree(token);
+  }
+
+  c_avl_destroy(tokens);
+
+  return 0;
+}
+
+DEF_TEST(build_name) {
+  table_definition_t *td = calloc(1, sizeof(*td));
+  c_avl_tree_t *tokens =
+      c_avl_create((int (*)(const void *, const void *))num_compare);
+
+  assert(tokens != NULL);
+  assert(td != NULL);
+
+  int n[3] = {1, 2, 3};
+  char *t[3] = {"testA", "-testB", "-testC"};
+  int off[3] = {0, 6, 13};
+  token_t *token;
+  int *offset;
+  int ret = 0;
+  char *name = NULL;
+
+  td->index_list_cont = NULL;
+  for (int i = 0; i < 3; i++) {
+    token = malloc(sizeof(*token));
+    token->str = t[i];
+    token->key =
+        snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_INTEGER,
+                                  (const u_char *)&n[i], sizeof(n[i]));
+    assert(token->key != NULL);
+    offset = &off[i];
+    ret = c_avl_insert(tokens, (void *)offset, (void *)token);
+    assert(ret == 0);
+  }
+
+  ret = snmp_agent_build_name(&name, tokens);
+  EXPECT_EQ_INT(0, ret);
+  EXPECT_EQ_STR("testA1-testB2-testC3", name);
+
+  while (c_avl_pick(tokens, (void **)&offset, (void **)&token) == 0)
+    sfree(token);
+
+  c_avl_destroy(tokens);
+  snmp_free_varbind(td->index_list_cont);
+  sfree(td);
+  sfree(name);
+  return 0;
+}
+
+int main(void) {
+  /* snmp_agent_oid_to_string */
+  RUN_TEST(oid_to_string);
+
+  /* snmp_agent_prep_index_list */
+  RUN_TEST(prep_index_list);
+
+  /* snmp_agent_fill_index_list */
+  RUN_TEST(fill_index_list_simple);
+  RUN_TEST(fill_index_list_regex);
+
+  /* snmp_agent_format_name */
+  RUN_TEST(format_name_scalar);
+  RUN_TEST(format_name_simple_index);
+  RUN_TEST(format_name_regex_index);
+
+  /* snmp_agent_config_index_key_source */
+  RUN_TEST(config_index_key_source);
+
+  /* snmp_agent_config_index_key_regex */
+  RUN_TEST(config_index_key_regex);
+
+  /* snmp_agent_config_index_key */
+  RUN_TEST(config_index_key);
+
+  /*snmp_agent_parse_index_key */
+  RUN_TEST(parse_index_key);
+
+  /* snmp_agent_create_token */
+  RUN_TEST(create_token);
+
+  /* snmp_agent_delete_token */
+  RUN_TEST(delete_token);
+
+  /* snmp_agent_get_token */
+  RUN_TEST(get_token);
+
+  /* snmp_agent_tokenize */
+  RUN_TEST(tokenize);
+
+  /* snmp_agent_build_name */
+  RUN_TEST(build_name);
+
+  END_TEST;
+}
index 444e8ea..1558ec8 100644 (file)
@@ -489,8 +489,8 @@ static int statsd_network_init(struct pollfd **ret_fds, /* {{{ */
     int fd;
     struct pollfd *tmp;
 
-    char dbg_node[NI_MAXHOST];
-    char dbg_service[NI_MAXSERV];
+    char str_node[NI_MAXHOST];
+    char str_service[NI_MAXSERV];
 
     fd = socket(ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
     if (fd < 0) {
@@ -498,15 +498,24 @@ static int statsd_network_init(struct pollfd **ret_fds, /* {{{ */
       continue;
     }
 
-    getnameinfo(ai_ptr->ai_addr, ai_ptr->ai_addrlen, dbg_node, sizeof(dbg_node),
-                dbg_service, sizeof(dbg_service),
+    /* allow multiple sockets to use the same PORT number */
+    int yes = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
+      ERROR("statsd plugin: setsockopt (reuseaddr): %s", STRERRNO);
+      close(fd);
+      continue;
+    }
+
+    getnameinfo(ai_ptr->ai_addr, ai_ptr->ai_addrlen, str_node, sizeof(str_node),
+                str_service, sizeof(str_service),
                 NI_DGRAM | NI_NUMERICHOST | NI_NUMERICSERV);
-    DEBUG("statsd plugin: Trying to bind to [%s]:%s ...", dbg_node,
-          dbg_service);
+    DEBUG("statsd plugin: Trying to bind to [%s]:%s ...", str_node,
+          str_service);
 
     status = bind(fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
     if (status != 0) {
-      ERROR("statsd plugin: bind(2) failed: %s", STRERRNO);
+      ERROR("statsd plugin: bind(2) to [%s]:%s failed: %s", str_node,
+            str_service, STRERRNO);
       close(fd);
       continue;
     }
@@ -524,6 +533,7 @@ static int statsd_network_init(struct pollfd **ret_fds, /* {{{ */
     memset(tmp, 0, sizeof(*tmp));
     tmp->fd = fd;
     tmp->events = POLLIN | POLLPRI;
+    INFO("statsd plugin: Listening on [%s]:%s.", str_node, str_service);
   }
 
   freeaddrinfo(ai_list);
index 189d605..492bea6 100644 (file)
@@ -132,18 +132,6 @@ static size_t tables_num;
 /*
  * configuration handling
  */
-
-static int tbl_config_set_s(char *name, char **var, oconfig_item_t *ci) {
-  if (ci->values_num != 1 || ci->values[0].type != OCONFIG_TYPE_STRING) {
-    log_err("\"%s\" expects a single string argument.", name);
-    return 1;
-  }
-
-  sfree(*var);
-  *var = sstrdup(ci->values[0].value.string);
-  return 0;
-} /* tbl_config_set_separator */
-
 static int tbl_config_append_array_i(char *name, size_t **var, size_t *len,
                                      oconfig_item_t *ci) {
   if (ci->values_num < 1) {
@@ -196,9 +184,9 @@ static int tbl_config_result(tbl_t *tbl, oconfig_item_t *ci) {
     oconfig_item_t *c = ci->children + i;
 
     if (strcasecmp(c->key, "Type") == 0)
-      tbl_config_set_s(c->key, &res->type, c);
+      cf_util_get_string(c, &res->type);
     else if (strcasecmp(c->key, "InstancePrefix") == 0)
-      tbl_config_set_s(c->key, &res->instance_prefix, c);
+      cf_util_get_string(c, &res->instance_prefix);
     else if (strcasecmp(c->key, "InstancesFrom") == 0)
       tbl_config_append_array_i(c->key, &res->instances, &res->instances_num,
                                 c);
@@ -253,11 +241,11 @@ static int tbl_config_table(oconfig_item_t *ci) {
     oconfig_item_t *c = ci->children + i;
 
     if (strcasecmp(c->key, "Separator") == 0)
-      tbl_config_set_s(c->key, &tbl->sep, c);
+      cf_util_get_string(c, &tbl->sep);
     else if (strcasecmp(c->key, "Plugin") == 0)
-      tbl_config_set_s(c->key, &tbl->plugin_name, c);
+      cf_util_get_string(c, &tbl->plugin_name);
     else if (strcasecmp(c->key, "Instance") == 0)
-      tbl_config_set_s(c->key, &tbl->instance, c);
+      cf_util_get_string(c, &tbl->instance);
     else if (strcasecmp(c->key, "Result") == 0)
       tbl_config_result(tbl, c);
     else
index a6471d8..df94580 100644 (file)
@@ -54,14 +54,17 @@ struct ctail_config_match_s {
   int flags;
   char *type;
   char *type_instance;
-  cdtime_t interval;
   latency_config_t latency;
 };
 typedef struct ctail_config_match_s ctail_config_match_t;
 
-static cu_tail_match_t **tail_match_list;
-static size_t tail_match_list_num;
-static cdtime_t tail_match_list_intervals[255];
+static size_t tail_file_num;
+
+static int ctail_read(user_data_t *ud);
+
+static void ctail_match_free(void *arg) {
+  tail_match_destroy((cu_tail_match_t *)arg);
+} /* void ctail_match_free */
 
 static int ctail_config_add_match_dstype(ctail_config_match_t *cm,
                                          oconfig_item_t *ci) {
@@ -92,7 +95,7 @@ static int ctail_config_add_match_dstype(ctail_config_match_t *cm,
   } else if (strcasecmp("Distribution", ds_type) == 0) {
     cm->flags = UTILS_MATCH_DS_TYPE_GAUGE | UTILS_MATCH_CF_GAUGE_DIST;
 
-    int status = latency_config(&cm->latency, ci, "tail");
+    int status = latency_config(&cm->latency, ci);
     if (status != 0)
       return status;
   } else if (strncasecmp("Counter", ds_type, strlen("Counter")) == 0) {
@@ -136,7 +139,7 @@ static int ctail_config_add_match_dstype(ctail_config_match_t *cm,
 
 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) {
+                                  oconfig_item_t *ci) {
   ctail_config_match_t cm = {0};
   int status;
 
@@ -194,7 +197,7 @@ static int ctail_config_add_match(cu_tail_match_t *tm, const char *plugin_name,
     status = tail_match_add_match_simple(
         tm, cm.regex, cm.excluderegex, cm.flags,
         (plugin_name != NULL) ? plugin_name : "tail", plugin_instance, cm.type,
-        cm.type_instance, cm.latency, interval);
+        cm.type_instance, cm.latency);
 
     if (status != 0)
       ERROR("tail plugin: tail_match_add_match_simple failed.");
@@ -239,8 +242,7 @@ static int ctail_config_add_file(oconfig_item_t *ci) {
     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_name, plugin_instance, option,
-                                      interval);
+      status = ctail_config_add_match(tm, plugin_name, plugin_instance, option);
       if (status == 0)
         num_matches++;
       /* Be mild with failed matches.. */
@@ -261,23 +263,15 @@ static int ctail_config_add_file(oconfig_item_t *ci) {
           ci->values[0].value.string);
     tail_match_destroy(tm);
     return -1;
-  } else {
-    cu_tail_match_t **temp;
-
-    temp = realloc(tail_match_list,
-                   sizeof(cu_tail_match_t *) * (tail_match_list_num + 1));
-    if (temp == NULL) {
-      ERROR("tail plugin: realloc failed.");
-      tail_match_destroy(tm);
-      return -1;
-    }
-
-    tail_match_list = temp;
-    tail_match_list[tail_match_list_num] = tm;
-    tail_match_list_intervals[tail_match_list_num] = interval;
-    tail_match_list_num++;
   }
 
+  char str[255];
+  snprintf(str, sizeof(str), "tail-%zu", tail_file_num++);
+
+  plugin_register_complex_read(
+      NULL, str, ctail_read, interval,
+      &(user_data_t){.data = tm, .free_func = ctail_match_free});
+
   return 0;
 } /* int ctail_config_add_file */
 
@@ -307,40 +301,6 @@ static int ctail_read(user_data_t *ud) {
   return 0;
 } /* int ctail_read */
 
-static int ctail_init(void) {
-  char str[255];
-
-  if (tail_match_list_num == 0) {
-    WARNING("tail plugin: File list is empty. Returning an error.");
-    return -1;
-  }
-
-  for (size_t i = 0; i < tail_match_list_num; i++) {
-    snprintf(str, sizeof(str), "tail-%zu", i);
-
-    plugin_register_complex_read(NULL, str, ctail_read,
-                                 tail_match_list_intervals[i],
-                                 &(user_data_t){
-                                     .data = tail_match_list[i],
-                                 });
-  }
-
-  return 0;
-} /* int ctail_init */
-
-static int ctail_shutdown(void) {
-  for (size_t i = 0; i < tail_match_list_num; i++) {
-    tail_match_destroy(tail_match_list[i]);
-    tail_match_list[i] = NULL;
-  }
-  sfree(tail_match_list);
-  tail_match_list_num = 0;
-
-  return 0;
-} /* int ctail_shutdown */
-
 void module_register(void) {
   plugin_register_complex_config("tail", ctail_config);
-  plugin_register_init("tail", ctail_init);
-  plugin_register_shutdown("tail", ctail_shutdown);
 } /* void module_register */
index be7cd40..48f3d74 100644 (file)
@@ -50,7 +50,6 @@ struct instance_definition_s {
   cu_tail_t *tail;
   metric_definition_t **metric_list;
   size_t metric_list_len;
-  cdtime_t interval;
   ssize_t time_from;
   struct instance_definition_s *next;
 };
@@ -77,7 +76,6 @@ static int tcsv_submit(instance_definition_t *id, metric_definition_t *md,
     sstrncpy(vl.type_instance, md->instance, sizeof(vl.type_instance));
 
   vl.time = t;
-  vl.interval = id->interval;
 
   return plugin_dispatch_values(&vl);
 }
@@ -418,6 +416,7 @@ static int tcsv_config_add_file(oconfig_item_t *ci) {
   int status = 0;
 
   /* Registration variables */
+  cdtime_t interval = 0;
   char cb_name[DATA_MAX_NAME_LEN];
 
   id = calloc(1, sizeof(*id));
@@ -436,9 +435,6 @@ static int tcsv_config_add_file(oconfig_item_t *ci) {
     return status;
   }
 
-  /* Use default interval. */
-  id->interval = plugin_get_interval();
-
   for (int i = 0; i < ci->children_num; ++i) {
     oconfig_item_t *option = ci->children + i;
     status = 0;
@@ -448,7 +444,7 @@ static int tcsv_config_add_file(oconfig_item_t *ci) {
     else if (strcasecmp("Collect", option->key) == 0)
       status = tcsv_config_add_instance_collect(id, option);
     else if (strcasecmp("Interval", option->key) == 0)
-      cf_util_get_cdtime(option, &id->interval);
+      cf_util_get_cdtime(option, &interval);
     else if (strcasecmp("TimeFrom", option->key) == 0)
       status = tcsv_config_get_index(option, &id->time_from);
     else if (strcasecmp("Plugin", option->key) == 0)
@@ -484,7 +480,7 @@ static int tcsv_config_add_file(oconfig_item_t *ci) {
   snprintf(cb_name, sizeof(cb_name), "tail_csv/%s", id->path);
 
   status = plugin_register_complex_read(
-      NULL, cb_name, tcsv_read, id->interval,
+      NULL, cb_name, tcsv_read, interval,
       &(user_data_t){
           .data = id, .free_func = tcsv_instance_definition_destroy,
       });
index 5cf6955..fd7e6c6 100644 (file)
@@ -100,6 +100,18 @@ static int check_count__;
     printf("ok %i - %s = %" PRIu64 "\n", ++check_count__, #actual, got__);     \
   } while (0)
 
+#define EXPECT_EQ_PTR(expect, actual)                                          \
+  do {                                                                         \
+    void *want__ = expect;                                                     \
+    void *got__ = actual;                                                      \
+    if (got__ != want__) {                                                     \
+      printf("not ok %i - %s = %p, want %p\n", ++check_count__, #actual,       \
+             got__, want__);                                                   \
+      return -1;                                                               \
+    }                                                                          \
+    printf("ok %i - %s = %p\n", ++check_count__, #actual, got__);              \
+  } while (0)
+
 #define EXPECT_EQ_DOUBLE(expect, actual)                                       \
   do {                                                                         \
     double want__ = (double)expect;                                            \
index 7900133..79300f1 100644 (file)
@@ -109,90 +109,6 @@ static int ut_threshold_add(const threshold_t *th) { /* {{{ */
  * The following approximately two hundred functions are used to handle the
  * configuration and fill the threshold list.
  * {{{ */
-static int ut_config_type_datasource(threshold_t *th, oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    WARNING("threshold values: The `DataSource' option needs exactly one "
-            "string argument.");
-    return -1;
-  }
-
-  sstrncpy(th->data_source, ci->values[0].value.string,
-           sizeof(th->data_source));
-
-  return 0;
-} /* int ut_config_type_datasource */
-
-static int ut_config_type_instance(threshold_t *th, oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    WARNING("threshold values: The `Instance' option needs exactly one "
-            "string argument.");
-    return -1;
-  }
-
-  sstrncpy(th->type_instance, ci->values[0].value.string,
-           sizeof(th->type_instance));
-
-  return 0;
-} /* int ut_config_type_instance */
-
-static int ut_config_type_max(threshold_t *th, oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
-    WARNING("threshold values: The `%s' option needs exactly one "
-            "number argument.",
-            ci->key);
-    return -1;
-  }
-
-  if (strcasecmp(ci->key, "WarningMax") == 0)
-    th->warning_max = ci->values[0].value.number;
-  else
-    th->failure_max = ci->values[0].value.number;
-
-  return 0;
-} /* int ut_config_type_max */
-
-static int ut_config_type_min(threshold_t *th, oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
-    WARNING("threshold values: The `%s' option needs exactly one "
-            "number argument.",
-            ci->key);
-    return -1;
-  }
-
-  if (strcasecmp(ci->key, "WarningMin") == 0)
-    th->warning_min = ci->values[0].value.number;
-  else
-    th->failure_min = ci->values[0].value.number;
-
-  return 0;
-} /* int ut_config_type_min */
-
-static int ut_config_type_hits(threshold_t *th, oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
-    WARNING("threshold values: The `%s' option needs exactly one "
-            "number argument.",
-            ci->key);
-    return -1;
-  }
-
-  th->hits = ci->values[0].value.number;
-
-  return 0;
-} /* int ut_config_type_hits */
-
-static int ut_config_type_hysteresis(threshold_t *th, oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
-    WARNING("threshold values: The `%s' option needs exactly one "
-            "number argument.",
-            ci->key);
-    return -1;
-  }
-
-  th->hysteresis = ci->values[0].value.number;
-
-  return 0;
-} /* int ut_config_type_hysteresis */
-
 static int ut_config_type(const threshold_t *th_orig, oconfig_item_t *ci) {
   threshold_t th;
   int status = 0;
@@ -223,15 +139,19 @@ static int ut_config_type(const threshold_t *th_orig, oconfig_item_t *ci) {
     oconfig_item_t *option = ci->children + i;
 
     if (strcasecmp("Instance", option->key) == 0)
-      status = ut_config_type_instance(&th, option);
+      status = cf_util_get_string_buffer(option, th.type_instance,
+                                         sizeof(th.type_instance));
     else if (strcasecmp("DataSource", option->key) == 0)
-      status = ut_config_type_datasource(&th, option);
-    else if ((strcasecmp("WarningMax", option->key) == 0) ||
-             (strcasecmp("FailureMax", option->key) == 0))
-      status = ut_config_type_max(&th, option);
-    else if ((strcasecmp("WarningMin", option->key) == 0) ||
-             (strcasecmp("FailureMin", option->key) == 0))
-      status = ut_config_type_min(&th, option);
+      status = cf_util_get_string_buffer(option, th.data_source,
+                                         sizeof(th.data_source));
+    else if (strcasecmp("WarningMax", option->key) == 0)
+      status = cf_util_get_double(option, &th.warning_max);
+    else if (strcasecmp("FailureMax", option->key) == 0)
+      status = cf_util_get_double(option, &th.failure_max);
+    else if (strcasecmp("WarningMin", option->key) == 0)
+      status = cf_util_get_double(option, &th.warning_min);
+    else if (strcasecmp("FailureMin", option->key) == 0)
+      status = cf_util_get_double(option, &th.failure_min);
     else if (strcasecmp("Interesting", option->key) == 0)
       status = cf_util_get_flag(option, &th.flags, UT_FLAG_INTERESTING);
     else if (strcasecmp("Invert", option->key) == 0)
@@ -243,9 +163,9 @@ static int ut_config_type(const threshold_t *th_orig, oconfig_item_t *ci) {
     else if (strcasecmp("Percentage", option->key) == 0)
       status = cf_util_get_flag(option, &th.flags, UT_FLAG_PERCENTAGE);
     else if (strcasecmp("Hits", option->key) == 0)
-      status = ut_config_type_hits(&th, option);
+      status = cf_util_get_int(option, &th.hits);
     else if (strcasecmp("Hysteresis", option->key) == 0)
-      status = ut_config_type_hysteresis(&th, option);
+      status = cf_util_get_double(option, &th.hysteresis);
     else {
       WARNING("threshold values: Option `%s' not allowed inside a `Type' "
               "block.",
@@ -264,19 +184,6 @@ static int ut_config_type(const threshold_t *th_orig, oconfig_item_t *ci) {
   return status;
 } /* int ut_config_type */
 
-static int ut_config_plugin_instance(threshold_t *th, oconfig_item_t *ci) {
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    WARNING("threshold values: The `Instance' option needs exactly one "
-            "string argument.");
-    return -1;
-  }
-
-  sstrncpy(th->plugin_instance, ci->values[0].value.string,
-           sizeof(th->plugin_instance));
-
-  return 0;
-} /* int ut_config_plugin_instance */
-
 static int ut_config_plugin(const threshold_t *th_orig, oconfig_item_t *ci) {
   threshold_t th;
   int status = 0;
@@ -302,7 +209,8 @@ static int ut_config_plugin(const threshold_t *th_orig, oconfig_item_t *ci) {
     if (strcasecmp("Type", option->key) == 0)
       status = ut_config_type(&th, option);
     else if (strcasecmp("Instance", option->key) == 0)
-      status = ut_config_plugin_instance(&th, option);
+      status = cf_util_get_string_buffer(option, th.plugin_instance,
+                                         sizeof(th.plugin_instance));
     else {
       WARNING("threshold values: Option `%s' not allowed inside a `Plugin' "
               "block.",
index ac8c9fb..49c3968 100644 (file)
@@ -1476,7 +1476,7 @@ static int turbostat_read(void) {
   /* Saving the scheduling affinity, as it will be modified by get_counters */
   if (sched_getaffinity(0, cpu_saved_affinity_setsize,
                         cpu_saved_affinity_set) != 0) {
-    ERROR("turbostat plugin: Unable to save the CPU affinity");
+    ERROR("turbostat plugin: Unable to save the CPU affinity: %s", STRERRNO);
     return -1;
   }
 
index 9975d32..0370b7f 100644 (file)
@@ -31,6 +31,7 @@ clock_state             value:GAUGE:0:U
 clock_stratum           value:GAUGE:0:U
 compression             uncompressed:DERIVE:0:U, compressed:DERIVE:0:U
 compression_ratio       value:GAUGE:0:2
+commands                value:DERIVE:0:U
 connections             value:DERIVE:0:U
 conntrack               value:GAUGE:0:4294967295
 contextswitch           value:DERIVE:0:U
@@ -212,6 +213,7 @@ pstates_enabled         value:GAUGE:0:1
 pubsub                  value:GAUGE:0:U
 queue_length            value:GAUGE:0:U
 records                 value:GAUGE:0:U
+redis_command_cputime   value:DERIVE:0:U
 requests                value:GAUGE:0:U
 response_code           value:GAUGE:0:U
 response_time           value:GAUGE:0:U
index 61fe2e4..0279a47 100644 (file)
@@ -84,55 +84,29 @@ struct udb_query_preparation_area_s /* {{{ */
   char *plugin;
   char *db_name;
 
-  cdtime_t interval;
-
   udb_result_preparation_area_t *result_prep_areas;
 }; /* }}} */
 
 /*
  * Config Private functions
  */
-static int udb_config_set_string(char **ret_string, /* {{{ */
-                                 oconfig_item_t *ci) {
-  char *string;
-
-  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    WARNING("db query utils: The `%s' config option "
-            "needs exactly one string argument.",
-            ci->key);
-    return -1;
-  }
-
-  string = strdup(ci->values[0].value.string);
-  if (string == NULL) {
-    ERROR("db query utils: strdup failed.");
-    return -1;
-  }
-
-  if (*ret_string != NULL)
-    free(*ret_string);
-  *ret_string = string;
-
-  return 0;
-} /* }}} int udb_config_set_string */
-
 static int udb_config_add_string(char ***ret_array, /* {{{ */
                                  size_t *ret_array_len, oconfig_item_t *ci) {
   char **array;
   size_t array_len;
 
   if (ci->values_num < 1) {
-    WARNING("db query utils: The `%s' config option "
-            "needs at least one argument.",
-            ci->key);
+    P_WARNING("The `%s' config option "
+              "needs at least one argument.",
+              ci->key);
     return -1;
   }
 
   for (int i = 0; i < ci->values_num; i++) {
     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
-      WARNING("db query utils: Argument %i to the `%s' option "
-              "is not a string.",
-              i + 1, ci->key);
+      P_WARNING("Argument %i to the `%s' option "
+                "is not a string.",
+                i + 1, ci->key);
       return -1;
     }
   }
@@ -140,7 +114,7 @@ static int udb_config_add_string(char ***ret_array, /* {{{ */
   array_len = *ret_array_len;
   array = realloc(*ret_array, sizeof(char *) * (array_len + ci->values_num));
   if (array == NULL) {
-    ERROR("db query utils: realloc failed.");
+    P_ERROR("udb_config_add_string: realloc failed.");
     return -1;
   }
   *ret_array = array;
@@ -148,7 +122,7 @@ static int udb_config_add_string(char ***ret_array, /* {{{ */
   for (int i = 0; i < ci->values_num; i++) {
     array[array_len] = strdup(ci->values[i].value.string);
     if (array[array_len] == NULL) {
-      ERROR("db query utils: strdup failed.");
+      P_ERROR("udb_config_add_string: strdup failed.");
       *ret_array_len = array_len;
       return -1;
     }
@@ -161,18 +135,19 @@ static int udb_config_add_string(char ***ret_array, /* {{{ */
 
 static int udb_config_set_uint(unsigned int *ret_value, /* {{{ */
                                oconfig_item_t *ci) {
-  double tmp;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
-    WARNING("db query utils: The `%s' config option "
-            "needs exactly one numeric argument.",
-            ci->key);
+    P_WARNING("The `%s' config option "
+              "needs exactly one numeric argument.",
+              ci->key);
     return -1;
   }
 
-  tmp = ci->values[0].value.number;
-  if ((tmp < 0.0) || (tmp > ((double)UINT_MAX)))
+  double tmp = ci->values[0].value.number;
+  if ((tmp < 0.0) || (tmp > ((double)UINT_MAX))) {
+    P_WARNING("The value given for the `%s` option is out of range.", ci->key);
     return -ERANGE;
+  }
 
   *ret_value = (unsigned int)(tmp + .5);
   return 0;
@@ -194,7 +169,7 @@ static int udb_result_submit(udb_result_t *r, /* {{{ */
 
   vl.values = calloc(r->values_num, sizeof(*vl.values));
   if (vl.values == NULL) {
-    ERROR("db query utils: calloc failed.");
+    P_ERROR("udb_result_submit: calloc failed.");
     return -1;
   }
   vl.values_len = r_area->ds->ds_num;
@@ -203,17 +178,14 @@ static int udb_result_submit(udb_result_t *r, /* {{{ */
     char *value_str = r_area->values_buffer[i];
 
     if (0 != parse_value(value_str, &vl.values[i], r_area->ds->ds[i].type)) {
-      ERROR("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
-            value_str, DS_TYPE_TO_STRING(r_area->ds->ds[i].type));
+      P_ERROR("udb_result_submit: Parsing `%s' as %s failed.", value_str,
+              DS_TYPE_TO_STRING(r_area->ds->ds[i].type));
       errno = EINVAL;
       free(vl.values);
       return -1;
     }
   }
 
-  if (q_area->interval > 0)
-    vl.interval = q_area->interval;
-
   sstrncpy(vl.host, q_area->host, sizeof(vl.host));
   sstrncpy(vl.plugin, q_area->plugin, sizeof(vl.plugin));
   sstrncpy(vl.type, r->type, sizeof(vl.type));
@@ -238,7 +210,7 @@ static int udb_result_submit(udb_result_t *r, /* {{{ */
       int status = strjoin(vl.type_instance, sizeof(vl.type_instance),
                            r_area->instances_buffer, r->instances_num, "-");
       if (status < 0) {
-        ERROR(
+        P_ERROR(
             "udb_result_submit: creating type_instance failed with status %d.",
             status);
         return status;
@@ -249,7 +221,7 @@ static int udb_result_submit(udb_result_t *r, /* {{{ */
       int status = strjoin(tmp, sizeof(tmp), r_area->instances_buffer,
                            r->instances_num, "-");
       if (status < 0) {
-        ERROR(
+        P_ERROR(
             "udb_result_submit: creating type_instance failed with status %d.",
             status);
         return status;
@@ -267,7 +239,7 @@ static int udb_result_submit(udb_result_t *r, /* {{{ */
   if (r->metadata_num > 0) {
     vl.meta = meta_data_create();
     if (vl.meta == NULL) {
-      ERROR("db query utils:: meta_data_create failed.");
+      P_ERROR("udb_result_submit: meta_data_create failed.");
       free(vl.values);
       return -ENOMEM;
     }
@@ -276,7 +248,7 @@ static int udb_result_submit(udb_result_t *r, /* {{{ */
       int status = meta_data_add_string(vl.meta, r->metadata[i],
                                         r_area->metadata_buffer[i]);
       if (status != 0) {
-        ERROR("db query utils:: meta_data_add_string failed.");
+        P_ERROR("udb_result_submit: meta_data_add_string failed.");
         meta_data_destroy(vl.meta);
         vl.meta = NULL;
         free(vl.values);
@@ -355,18 +327,18 @@ static int udb_result_prepare_result(udb_result_t const *r, /* {{{ */
   /* Read `ds' and check number of values {{{ */
   prep_area->ds = plugin_get_ds(r->type);
   if (prep_area->ds == NULL) {
-    ERROR("db query utils: udb_result_prepare_result: Type `%s' is not "
-          "known by the daemon. See types.db(5) for details.",
-          r->type);
+    P_ERROR("udb_result_prepare_result: Type `%s' is not "
+            "known by the daemon. See types.db(5) for details.",
+            r->type);
     BAIL_OUT(-1);
   }
 
   if (prep_area->ds->ds_num != r->values_num) {
-    ERROR("db query utils: udb_result_prepare_result: The type `%s' "
-          "requires exactly %" PRIsz
-          " value%s, but the configuration specifies %" PRIsz ".",
-          r->type, prep_area->ds->ds_num,
-          (prep_area->ds->ds_num == 1) ? "" : "s", r->values_num);
+    P_ERROR("udb_result_prepare_result: The type `%s' "
+            "requires exactly %" PRIsz
+            " value%s, but the configuration specifies %" PRIsz ".",
+            r->type, prep_area->ds->ds_num,
+            (prep_area->ds->ds_num == 1) ? "" : "s", r->values_num);
     BAIL_OUT(-1);
   }
   /* }}} */
@@ -377,39 +349,39 @@ static int udb_result_prepare_result(udb_result_t const *r, /* {{{ */
     prep_area->instances_pos =
         (size_t *)calloc(r->instances_num, sizeof(size_t));
     if (prep_area->instances_pos == NULL) {
-      ERROR("db query utils: udb_result_prepare_result: calloc failed.");
+      P_ERROR("udb_result_prepare_result: calloc failed.");
       BAIL_OUT(-ENOMEM);
     }
 
     prep_area->instances_buffer =
         (char **)calloc(r->instances_num, sizeof(char *));
     if (prep_area->instances_buffer == NULL) {
-      ERROR("db query utils: udb_result_prepare_result: calloc failed.");
+      P_ERROR("udb_result_prepare_result: calloc failed.");
       BAIL_OUT(-ENOMEM);
     }
   } /* if (r->instances_num > 0) */
 
   prep_area->values_pos = (size_t *)calloc(r->values_num, sizeof(size_t));
   if (prep_area->values_pos == NULL) {
-    ERROR("db query utils: udb_result_prepare_result: calloc failed.");
+    P_ERROR("udb_result_prepare_result: calloc failed.");
     BAIL_OUT(-ENOMEM);
   }
 
   prep_area->values_buffer = (char **)calloc(r->values_num, sizeof(char *));
   if (prep_area->values_buffer == NULL) {
-    ERROR("db query utils: udb_result_prepare_result: calloc failed.");
+    P_ERROR("udb_result_prepare_result: calloc failed.");
     BAIL_OUT(-ENOMEM);
   }
 
   prep_area->metadata_pos = (size_t *)calloc(r->metadata_num, sizeof(size_t));
   if (prep_area->metadata_pos == NULL) {
-    ERROR("db query utils: udb_result_prepare_result: calloc failed.");
+    P_ERROR("udb_result_prepare_result: calloc failed.");
     BAIL_OUT(-ENOMEM);
   }
 
   prep_area->metadata_buffer = (char **)calloc(r->metadata_num, sizeof(char *));
   if (prep_area->metadata_buffer == NULL) {
-    ERROR("db query utils: udb_result_prepare_result: calloc failed.");
+    P_ERROR("udb_result_prepare_result: calloc failed.");
     BAIL_OUT(-ENOMEM);
   }
 
@@ -427,9 +399,9 @@ static int udb_result_prepare_result(udb_result_t const *r, /* {{{ */
     }
 
     if (j >= column_num) {
-      ERROR("db query utils: udb_result_prepare_result: "
-            "Column `%s' could not be found.",
-            r->instances[i]);
+      P_ERROR("udb_result_prepare_result: "
+              "Column `%s' could not be found.",
+              r->instances[i]);
       BAIL_OUT(-ENOENT);
     }
   } /* }}} for (i = 0; i < r->instances_num; i++) */
@@ -446,9 +418,9 @@ static int udb_result_prepare_result(udb_result_t const *r, /* {{{ */
     }
 
     if (j >= column_num) {
-      ERROR("db query utils: udb_result_prepare_result: "
-            "Column `%s' could not be found.",
-            r->values[i]);
+      P_ERROR("udb_result_prepare_result: "
+              "Column `%s' could not be found.",
+              r->values[i]);
       BAIL_OUT(-ENOENT);
     }
   } /* }}} for (i = 0; i < r->values_num; i++) */
@@ -465,9 +437,9 @@ static int udb_result_prepare_result(udb_result_t const *r, /* {{{ */
     }
 
     if (j >= column_num) {
-      ERROR("db query utils: udb_result_prepare_result: "
-            "Metadata column `%s' could not be found.",
-            r->values[i]);
+      P_ERROR("udb_result_prepare_result: "
+              "Metadata column `%s' could not be found.",
+              r->values[i]);
       BAIL_OUT(-ENOENT);
     }
   } /* }}} for (i = 0; i < r->metadata_num; i++) */
@@ -507,14 +479,14 @@ static int udb_result_create(const char *query_name, /* {{{ */
   int status;
 
   if (ci->values_num != 0) {
-    WARNING("db query utils: The `Result' block doesn't accept "
-            "any arguments. Ignoring %i argument%s.",
-            ci->values_num, (ci->values_num == 1) ? "" : "s");
+    P_WARNING("The `Result' block doesn't accept "
+              "any arguments. Ignoring %i argument%s.",
+              ci->values_num, (ci->values_num == 1) ? "" : "s");
   }
 
   r = calloc(1, sizeof(*r));
   if (r == NULL) {
-    ERROR("db query utils: calloc failed.");
+    P_ERROR("udb_result_create: calloc failed.");
     return -1;
   }
   r->type = NULL;
@@ -530,9 +502,9 @@ static int udb_result_create(const char *query_name, /* {{{ */
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("Type", child->key) == 0)
-      status = udb_config_set_string(&r->type, child);
+      status = cf_util_get_string(child, &r->type);
     else if (strcasecmp("InstancePrefix", child->key) == 0)
-      status = udb_config_set_string(&r->instance_prefix, child);
+      status = cf_util_get_string(child, &r->instance_prefix);
     else if (strcasecmp("InstancesFrom", child->key) == 0)
       status = udb_config_add_string(&r->instances, &r->instances_num, child);
     else if (strcasecmp("ValuesFrom", child->key) == 0)
@@ -540,8 +512,8 @@ static int udb_result_create(const char *query_name, /* {{{ */
     else if (strcasecmp("MetadataFrom", child->key) == 0)
       status = udb_config_add_string(&r->metadata, &r->metadata_num, child);
     else {
-      WARNING("db query utils: Query `%s': Option `%s' not allowed here.",
-              query_name, child->key);
+      P_WARNING("Query `%s': Option `%s' not allowed here.", query_name,
+                child->key);
       status = -1;
     }
 
@@ -552,15 +524,15 @@ static int udb_result_create(const char *query_name, /* {{{ */
   /* Check that all necessary options have been given. */
   while (status == 0) {
     if (r->type == NULL) {
-      WARNING("db query utils: `Type' not given for "
-              "result in query `%s'",
-              query_name);
+      P_WARNING("udb_result_create: `Type' not given for "
+                "result in query `%s'",
+                query_name);
       status = -1;
     }
     if (r->values == NULL) {
-      WARNING("db query utils: `ValuesFrom' not given for "
-              "result in query `%s'",
-              query_name);
+      P_WARNING("udb_result_create: `ValuesFrom' not given for "
+                "result in query `%s'",
+                query_name);
       status = -1;
     }
 
@@ -623,14 +595,14 @@ int udb_query_create(udb_query_t ***ret_query_list, /* {{{ */
   query_list_len = *ret_query_list_len;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    WARNING("db query utils: The `Query' block "
-            "needs exactly one string argument.");
+    P_WARNING("udb_result_create: The `Query' block "
+              "needs exactly one string argument.");
     return -1;
   }
 
   q = calloc(1, sizeof(*q));
   if (q == NULL) {
-    ERROR("db query utils: calloc failed.");
+    P_ERROR("udb_query_create: calloc failed.");
     return -1;
   }
   q->min_version = 0;
@@ -639,7 +611,7 @@ int udb_query_create(udb_query_t ***ret_query_list, /* {{{ */
   q->results = NULL;
   q->plugin_instance_from = NULL;
 
-  status = udb_config_set_string(&q->name, ci);
+  status = cf_util_get_string(ci, &q->name);
   if (status != 0) {
     sfree(q);
     return status;
@@ -650,7 +622,7 @@ int udb_query_create(udb_query_t ***ret_query_list, /* {{{ */
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("Statement", child->key) == 0)
-      status = udb_config_set_string(&q->statement, child);
+      status = cf_util_get_string(child, &q->statement);
     else if (strcasecmp("Result", child->key) == 0)
       status = udb_result_create(q->name, &q->results, child);
     else if (strcasecmp("MinVersion", child->key) == 0)
@@ -658,19 +630,19 @@ int udb_query_create(udb_query_t ***ret_query_list, /* {{{ */
     else if (strcasecmp("MaxVersion", child->key) == 0)
       status = udb_config_set_uint(&q->max_version, child);
     else if (strcasecmp("PluginInstanceFrom", child->key) == 0)
-      status = udb_config_set_string(&q->plugin_instance_from, child);
+      status = cf_util_get_string(child, &q->plugin_instance_from);
 
     /* Call custom callbacks */
     else if (cb != NULL) {
       status = (*cb)(q, child);
       if (status != 0) {
-        WARNING("db query utils: The configuration callback failed "
-                "to handle `%s'.",
-                child->key);
+        P_WARNING("The configuration callback failed "
+                  "to handle `%s'.",
+                  child->key);
       }
     } else {
-      WARNING("db query utils: Query `%s': Option `%s' not allowed here.",
-              q->name, child->key);
+      P_WARNING("Query `%s': Option `%s' not allowed here.", q->name,
+                child->key);
       status = -1;
     }
 
@@ -681,12 +653,11 @@ int udb_query_create(udb_query_t ***ret_query_list, /* {{{ */
   /* Check that all necessary options have been given. */
   if (status == 0) {
     if (q->statement == NULL) {
-      WARNING("db query utils: Query `%s': No `Statement' given.", q->name);
+      P_WARNING("Query `%s': No `Statement' given.", q->name);
       status = -1;
     }
     if (q->results == NULL) {
-      WARNING("db query utils: Query `%s': No (valid) `Result' block given.",
-              q->name);
+      P_WARNING("Query `%s': No (valid) `Result' block given.", q->name);
       status = -1;
     }
   } /* if (status == 0) */
@@ -698,7 +669,7 @@ int udb_query_create(udb_query_t ***ret_query_list, /* {{{ */
 
     temp = realloc(query_list, sizeof(*query_list) * (query_list_len + 1));
     if (temp == NULL) {
-      ERROR("db query utils: realloc failed");
+      P_ERROR("udb_query_create: realloc failed");
       status = -1;
     } else {
       query_list = temp;
@@ -738,8 +709,8 @@ int udb_query_pick_from_list_by_name(const char *name, /* {{{ */
 
   if ((name == NULL) || (src_list == NULL) || (dst_list == NULL) ||
       (dst_list_len == NULL)) {
-    ERROR("db query utils: udb_query_pick_from_list_by_name: "
-          "Invalid argument.");
+    P_ERROR("udb_query_pick_from_list_by_name: "
+            "Invalid argument.");
     return -EINVAL;
   }
 
@@ -754,7 +725,7 @@ int udb_query_pick_from_list_by_name(const char *name, /* {{{ */
     tmp_list_len = *dst_list_len;
     tmp_list = realloc(*dst_list, (tmp_list_len + 1) * sizeof(udb_query_t *));
     if (tmp_list == NULL) {
-      ERROR("db query utils: realloc failed.");
+      P_ERROR("udb_query_pick_from_list_by_name: realloc failed.");
       return -ENOMEM;
     }
 
@@ -768,12 +739,12 @@ int udb_query_pick_from_list_by_name(const char *name, /* {{{ */
   } /* for (i = 0; i < src_list_len; i++) */
 
   if (num_added <= 0) {
-    ERROR("db query utils: Cannot find query `%s'. Make sure the <Query> "
-          "block is above the database definition!",
-          name);
+    P_ERROR("Cannot find query `%s'. Make sure the <Query> "
+            "block is above the database definition!",
+            name);
     return -ENOENT;
   } else {
-    DEBUG("db query utils: Added %i versions of query `%s'.", num_added, name);
+    DEBUG("Added %i versions of query `%s'.", num_added, name);
   }
 
   return 0;
@@ -786,15 +757,15 @@ int udb_query_pick_from_list(oconfig_item_t *ci, /* {{{ */
 
   if ((ci == NULL) || (src_list == NULL) || (dst_list == NULL) ||
       (dst_list_len == NULL)) {
-    ERROR("db query utils: udb_query_pick_from_list: "
-          "Invalid argument.");
+    P_ERROR("udb_query_pick_from_list: "
+            "Invalid argument.");
     return -EINVAL;
   }
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-    ERROR("db query utils: The `%s' config option "
-          "needs exactly one string argument.",
-          ci->key);
+    P_ERROR("The `%s' config option "
+            "needs exactly one string argument.",
+            ci->key);
     return -1;
   }
   name = ci->values[0].value.string;
@@ -859,8 +830,6 @@ void udb_query_finish_result(udb_query_t const *q, /* {{{ */
   sfree(prep_area->plugin);
   sfree(prep_area->db_name);
 
-  prep_area->interval = 0;
-
   for (r = q->results, r_area = prep_area->result_prep_areas; r != NULL;
        r = r->next, r_area = r_area->next) {
     /* this may happen during error conditions of the caller */
@@ -883,16 +852,16 @@ int udb_query_handle_result(udb_query_t const *q, /* {{{ */
 
   if ((prep_area->column_num < 1) || (prep_area->host == NULL) ||
       (prep_area->plugin == NULL) || (prep_area->db_name == NULL)) {
-    ERROR("db query utils: Query `%s': Query is not prepared; "
-          "can't handle result.",
-          q->name);
+    P_ERROR("Query `%s': Query is not prepared; "
+            "can't handle result.",
+            q->name);
     return -EINVAL;
   }
 
 #if defined(COLLECT_DEBUG) && COLLECT_DEBUG /* {{{ */
   do {
     for (size_t i = 0; i < prep_area->column_num; i++) {
-      DEBUG("db query utils: udb_query_handle_result (%s, %s): "
+      DEBUG("udb_query_handle_result (%s, %s): "
             "column[%" PRIsz "] = %s;",
             prep_area->db_name, q->name, i, column_values[i]);
     }
@@ -908,9 +877,9 @@ int udb_query_handle_result(udb_query_t const *q, /* {{{ */
   }
 
   if (success == 0) {
-    ERROR("db query utils: udb_query_handle_result (%s, %s): "
-          "All results failed.",
-          prep_area->db_name, q->name);
+    P_ERROR("udb_query_handle_result (%s, %s): "
+            "All results failed.",
+            prep_area->db_name, q->name);
     return -1;
   }
 
@@ -921,7 +890,7 @@ int udb_query_prepare_result(udb_query_t const *q, /* {{{ */
                              udb_query_preparation_area_t *prep_area,
                              const char *host, const char *plugin,
                              const char *db_name, char **column_names,
-                             size_t column_num, cdtime_t interval) {
+                             size_t column_num) {
   udb_result_preparation_area_t *r_area;
   udb_result_t *r;
   int status;
@@ -934,7 +903,6 @@ int udb_query_prepare_result(udb_query_t const *q, /* {{{ */
   assert(prep_area->host == NULL);
   assert(prep_area->plugin == NULL);
   assert(prep_area->db_name == NULL);
-  assert(prep_area->interval == 0);
 #endif
 
   prep_area->column_num = column_num;
@@ -942,12 +910,9 @@ int udb_query_prepare_result(udb_query_t const *q, /* {{{ */
   prep_area->plugin = strdup(plugin);
   prep_area->db_name = strdup(db_name);
 
-  prep_area->interval = interval;
-
   if ((prep_area->host == NULL) || (prep_area->plugin == NULL) ||
       (prep_area->db_name == NULL)) {
-    ERROR("db query utils: Query `%s': Prepare failed: Out of memory.",
-          q->name);
+    P_ERROR("Query `%s': Prepare failed: Out of memory.", q->name);
     udb_query_finish_result(q, prep_area);
     return -ENOMEM;
   }
@@ -955,7 +920,7 @@ int udb_query_prepare_result(udb_query_t const *q, /* {{{ */
 #if defined(COLLECT_DEBUG) && COLLECT_DEBUG
   do {
     for (size_t i = 0; i < column_num; i++) {
-      DEBUG("db query utils: udb_query_prepare_result: "
+      DEBUG("udb_query_prepare_result: "
             "query = %s; column[%" PRIsz "] = %s;",
             q->name, i, column_names[i]);
     }
@@ -974,9 +939,9 @@ int udb_query_prepare_result(udb_query_t const *q, /* {{{ */
     }
 
     if (i >= column_num) {
-      ERROR("db query utils: udb_query_prepare_result: "
-            "Column `%s' from `PluginInstanceFrom' could not be found.",
-            q->plugin_instance_from);
+      P_ERROR("udb_query_prepare_result: "
+              "Column `%s' from `PluginInstanceFrom' could not be found.",
+              q->plugin_instance_from);
       udb_query_finish_result(q, prep_area);
       return -ENOENT;
     }
@@ -986,9 +951,9 @@ int udb_query_prepare_result(udb_query_t const *q, /* {{{ */
   for (r = q->results, r_area = prep_area->result_prep_areas; r != NULL;
        r = r->next, r_area = r_area->next) {
     if (!r_area) {
-      ERROR("db query utils: Query `%s': Invalid number of result "
-            "preparation areas.",
-            q->name);
+      P_ERROR("Query `%s': Invalid number of result "
+              "preparation areas.",
+              q->name);
       udb_query_finish_result(q, prep_area);
       return -EINVAL;
     }
index 4d6129a..f173204 100644 (file)
@@ -71,7 +71,7 @@ int udb_query_prepare_result(udb_query_t const *q,
                              udb_query_preparation_area_t *prep_area,
                              const char *host, const char *plugin,
                              const char *db_name, char **column_names,
-                             size_t column_num, cdtime_t interval);
+                             size_t column_num);
 int udb_query_handle_result(udb_query_t const *q,
                             udb_query_preparation_area_t *prep_area,
                             char **column_values);
index 1d4668f..3591eae 100644 (file)
 #include "common.h"
 #include "utils_dpdk.h"
 
+#if RTE_VERSION <= RTE_VERSION_NUM(18, 5, 0, 0)
 #define DPDK_DEFAULT_RTE_CONFIG "/var/run/.rte_config"
+#else
+#define DPDK_DEFAULT_RTE_CONFIG "/var/run/dpdk/rte/config"
+#endif
 #define DPDK_EAL_ARGC 10
 // Complete trace should fit into 1024 chars. Trace contain some headers
 // and text together with traced data from pipe. This is the reason why
@@ -184,8 +188,13 @@ int dpdk_helper_eal_config_parse(dpdk_helper_ctx_t *phc, oconfig_item_t *ci) {
 
       status = cf_util_get_string_buffer(child, prefix, sizeof(prefix));
       if (status == 0) {
+#if RTE_VERSION <= RTE_VERSION_NUM(18, 5, 0, 0)
         snprintf(phc->eal_config.file_prefix, DATA_MAX_NAME_LEN,
                  "/var/run/.%s_config", prefix);
+#else
+        snprintf(phc->eal_config.file_prefix, DATA_MAX_NAME_LEN,
+                 "/var/run/dpdk/%s/config", prefix);
+#endif
         DEBUG("dpdk_common: EAL:File prefix %s", phc->eal_config.file_prefix);
       }
     } else if (strcasecmp("LogLevel", child->key) == 0) {
@@ -853,7 +862,11 @@ uint128_t str_to_uint128(const char *str, int len) {
 }
 
 uint8_t dpdk_helper_eth_dev_count(void) {
+#if RTE_VERSION < RTE_VERSION_NUM(18, 05, 0, 0)
   uint8_t ports = rte_eth_dev_count();
+#else
+  uint8_t ports = rte_eth_dev_count_avail();
+#endif
   if (ports == 0) {
     ERROR(
         "%s:%d: No DPDK ports available. Check bound devices to DPDK driver.\n",
index 0bc802b..de3f0c2 100644 (file)
@@ -66,8 +66,8 @@ static int gr_format_values(char *ret, size_t ret_len, int ds_num,
   else if (ds->ds[ds_num].type == DS_TYPE_ABSOLUTE)
     BUFFER_ADD("%" PRIu64, vl->values[ds_num].absolute);
   else {
-    ERROR("gr_format_values plugin: Unknown data source type: %i",
-          ds->ds[ds_num].type);
+    P_ERROR("gr_format_values: Unknown data source type: %i",
+            ds->ds[ds_num].type);
     return -1;
   }
 
@@ -97,6 +97,86 @@ static void gr_copy_escape_part(char *dst, const char *src, size_t dst_len,
   }
 }
 
+static int gr_format_name_tagged(char *ret, int ret_len, value_list_t const *vl,
+                                 char const *ds_name, char const *prefix,
+                                 char const *postfix, char const escape_char,
+                                 unsigned int flags) {
+  char n_host[DATA_MAX_NAME_LEN];
+  char n_plugin[DATA_MAX_NAME_LEN];
+  char n_plugin_instance[DATA_MAX_NAME_LEN];
+  char n_type[DATA_MAX_NAME_LEN];
+  char n_type_instance[DATA_MAX_NAME_LEN];
+
+  char tmp_plugin[DATA_MAX_NAME_LEN + 8];
+  char tmp_plugin_instance[DATA_MAX_NAME_LEN + 17];
+  char tmp_type[DATA_MAX_NAME_LEN + 6];
+  char tmp_type_instance[DATA_MAX_NAME_LEN + 15];
+  char tmp_metric[3 * DATA_MAX_NAME_LEN + 2];
+  char tmp_ds_name[DATA_MAX_NAME_LEN + 9];
+
+  if (prefix == NULL)
+    prefix = "";
+
+  if (postfix == NULL)
+    postfix = "";
+
+  gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char, 1);
+  gr_copy_escape_part(n_plugin, vl->plugin, sizeof(n_plugin), escape_char, 1);
+  gr_copy_escape_part(n_plugin_instance, vl->plugin_instance,
+                      sizeof(n_plugin_instance), escape_char, 1);
+  gr_copy_escape_part(n_type, vl->type, sizeof(n_type), escape_char, 1);
+  gr_copy_escape_part(n_type_instance, vl->type_instance,
+                      sizeof(n_type_instance), escape_char, 1);
+
+  snprintf(tmp_plugin, sizeof(tmp_plugin), ";plugin=%s", n_plugin);
+
+  if (n_plugin_instance[0] != '\0')
+    snprintf(tmp_plugin_instance, sizeof(tmp_plugin_instance),
+             ";plugin_instance=%s", n_plugin_instance);
+  else
+    tmp_plugin_instance[0] = '\0';
+
+  if (!(flags & GRAPHITE_DROP_DUPE_FIELDS) || strcmp(n_plugin, n_type) != 0)
+    snprintf(tmp_type, sizeof(tmp_type), ";type=%s", n_type);
+  else
+    tmp_type[0] = '\0';
+
+  if (n_type_instance[0] != '\0') {
+    if (!(flags & GRAPHITE_DROP_DUPE_FIELDS) ||
+        strcmp(n_plugin_instance, n_type_instance) != 0)
+      snprintf(tmp_type_instance, sizeof(tmp_type_instance),
+               ";type_instance=%s", n_type_instance);
+    else
+      tmp_type_instance[0] = '\0';
+  } else
+    tmp_type_instance[0] = '\0';
+
+  /* Assert always_append_ds -> ds_name */
+  assert(!(flags & GRAPHITE_ALWAYS_APPEND_DS) || (ds_name != NULL));
+  if (ds_name != NULL) {
+    snprintf(tmp_ds_name, sizeof(tmp_ds_name), ";ds_name=%s", ds_name);
+
+    if ((flags & GRAPHITE_DROP_DUPE_FIELDS) && strcmp(n_plugin, n_type) == 0)
+      snprintf(tmp_metric, sizeof(tmp_metric), "%s.%s", n_plugin, ds_name);
+    else
+      snprintf(tmp_metric, sizeof(tmp_metric), "%s.%s.%s", n_plugin, n_type,
+               ds_name);
+  } else {
+    tmp_ds_name[0] = '\0';
+
+    if ((flags & GRAPHITE_DROP_DUPE_FIELDS) && strcmp(n_plugin, n_type) == 0)
+      snprintf(tmp_metric, sizeof(tmp_metric), "%s", n_plugin);
+    else
+      snprintf(tmp_metric, sizeof(tmp_metric), "%s.%s", n_plugin, n_type);
+  }
+
+  snprintf(ret, ret_len, "%s%s%s;host=%s%s%s%s%s%s", prefix, tmp_metric,
+           postfix, n_host, tmp_plugin, tmp_plugin_instance, tmp_type,
+           tmp_type_instance, tmp_ds_name);
+
+  return 0;
+}
+
 static int gr_format_name(char *ret, int ret_len, value_list_t const *vl,
                           char const *ds_name, char const *prefix,
                           char const *postfix, char const escape_char,
@@ -183,7 +263,7 @@ int format_graphite(char *buffer, size_t buffer_size, data_set_t const *ds,
   if (flags & GRAPHITE_STORE_RATES) {
     rates = uc_get_rate(ds, vl);
     if (rates == NULL) {
-      ERROR("format_graphite: error with uc_get_rate");
+      P_ERROR("format_graphite: error with uc_get_rate");
       return -1;
     }
   }
@@ -199,20 +279,31 @@ int format_graphite(char *buffer, size_t buffer_size, data_set_t const *ds,
       ds_name = ds->ds[i].name;
 
     /* Copy the identifier to `key' and escape it. */
-    status = gr_format_name(key, sizeof(key), vl, ds_name, prefix, postfix,
-                            escape_char, flags);
-    if (status != 0) {
-      ERROR("format_graphite: error with gr_format_name");
-      sfree(rates);
-      return status;
+    if (flags & GRAPHITE_USE_TAGS) {
+      status = gr_format_name_tagged(key, sizeof(key), vl, ds_name, prefix,
+                                     postfix, escape_char, flags);
+      if (status != 0) {
+        P_ERROR("format_graphite: error with gr_format_name_tagged");
+        sfree(rates);
+        return status;
+      }
+    } else {
+      status = gr_format_name(key, sizeof(key), vl, ds_name, prefix, postfix,
+                              escape_char, flags);
+      if (status != 0) {
+        P_ERROR("format_graphite: error with gr_format_name");
+        sfree(rates);
+        return status;
+      }
     }
 
     escape_graphite_string(key, escape_char);
+
     /* Convert the values to an ASCII representation and put that into
      * `values'. */
     status = gr_format_values(values, sizeof(values), i, ds, vl, rates);
     if (status != 0) {
-      ERROR("format_graphite: error with gr_format_values");
+      P_ERROR("format_graphite: error with gr_format_values");
       sfree(rates);
       return status;
     }
@@ -222,16 +313,16 @@ int format_graphite(char *buffer, size_t buffer_size, data_set_t const *ds,
         (size_t)snprintf(message, sizeof(message), "%s %s %u\r\n", key, values,
                          (unsigned int)CDTIME_T_TO_TIME_T(vl->time));
     if (message_len >= sizeof(message)) {
-      ERROR("format_graphite: message buffer too small: "
-            "Need %" PRIsz " bytes.",
-            message_len + 1);
+      P_ERROR("format_graphite: message buffer too small: "
+              "Need %" PRIsz " bytes.",
+              message_len + 1);
       sfree(rates);
       return -ENOMEM;
     }
 
     /* Append it in case we got multiple data set */
     if ((buffer_pos + message_len) >= buffer_size) {
-      ERROR("format_graphite: target buffer too small");
+      P_ERROR("format_graphite: target buffer too small");
       sfree(rates);
       return -ENOMEM;
     }
index de90c44..60b89ae 100644 (file)
@@ -31,6 +31,7 @@
 #define GRAPHITE_ALWAYS_APPEND_DS 0x04
 #define GRAPHITE_DROP_DUPE_FIELDS 0x08
 #define GRAPHITE_PRESERVE_SEPARATOR 0x10
+#define GRAPHITE_USE_TAGS 0x20
 
 int format_graphite(char *buffer, size_t buffer_size, const data_set_t *ds,
                     const value_list_t *vl, const char *prefix,
index a82142f..42efa68 100644 (file)
@@ -124,6 +124,22 @@ DEF_TEST(metric_name) {
           .suffix = NULL,
           .want_name = "foo.example@com.test.single",
       },
+      /* flag GRAPHITE_USE_TAGS */
+      {.flags = GRAPHITE_USE_TAGS,
+       .want_name = "test.single;host=example.com;plugin=test;type=single"},
+      {.plugin_instance = "f.o.o",
+       .type_instance = "b.a.r",
+       .flags = GRAPHITE_USE_TAGS,
+       .want_name = "test.single;host=example.com;plugin=test;plugin_instance="
+                    "f.o.o;type=single;type_instance=b.a.r"},
+      {.flags = GRAPHITE_USE_TAGS ^ GRAPHITE_ALWAYS_APPEND_DS,
+       .want_name = "test.single.value;host=example.com;plugin=test;type="
+                    "single;ds_name=value"},
+      {.plugin_instance = "foo",
+       .type_instance = "foo",
+       .flags = GRAPHITE_USE_TAGS ^ GRAPHITE_DROP_DUPE_FIELDS,
+       .want_name = "test.single;host=example.com;plugin=test;plugin_instance="
+                    "foo;type=single"},
   };
 
   for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
index eae0b18..49aa229 100644 (file)
@@ -145,7 +145,6 @@ static int values_to_json(char *buffer, size_t buffer_size, /* {{{ */
 
 #undef BUFFER_ADD
 
-  DEBUG("format_json: values_to_json: buffer = %s;", buffer);
   sfree(rates);
   return 0;
 } /* }}} int values_to_json */
@@ -179,8 +178,6 @@ static int dstypes_to_json(char *buffer, size_t buffer_size, /* {{{ */
 
 #undef BUFFER_ADD
 
-  DEBUG("format_json: dstypes_to_json: buffer = %s;", buffer);
-
   return 0;
 } /* }}} int dstypes_to_json */
 
@@ -213,8 +210,6 @@ static int dsnames_to_json(char *buffer, size_t buffer_size, /* {{{ */
 
 #undef BUFFER_ADD
 
-  DEBUG("format_json: dsnames_to_json: buffer = %s;", buffer);
-
   return 0;
 } /* }}} int dsnames_to_json */
 
@@ -378,8 +373,6 @@ static int value_list_to_json(char *buffer, size_t buffer_size, /* {{{ */
 #undef BUFFER_ADD_KEYVAL
 #undef BUFFER_ADD
 
-  DEBUG("format_json: value_list_to_json: buffer = %s;", buffer);
-
   return 0;
 } /* }}} int value_list_to_json */
 
index 4003243..4998906 100644 (file)
@@ -356,5 +356,3 @@ int format_kairosdb_value_list(char *buffer, /* {{{ */
       (*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 : */
diff --git a/src/utils_format_stackdriver.c b/src/utils_format_stackdriver.c
new file mode 100644 (file)
index 0000000..afaa8ed
--- /dev/null
@@ -0,0 +1,766 @@
+/**
+ * collectd - src/utils_format_stackdriver.c
+ * ISC license
+ *
+ * Copyright (C) 2017  Florian Forster
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+
+#include "utils_format_stackdriver.h"
+
+#include "common.h"
+#include "plugin.h"
+#include "utils_avltree.h"
+#include "utils_cache.h"
+#include "utils_time.h"
+
+#include <yajl/yajl_gen.h>
+#include <yajl/yajl_parse.h>
+#if HAVE_YAJL_YAJL_VERSION_H
+#include <yajl/yajl_version.h>
+#endif
+
+struct sd_output_s {
+  sd_resource_t *res;
+  yajl_gen gen;
+  c_avl_tree_t *staged;
+  c_avl_tree_t *metric_descriptors;
+};
+
+struct sd_label_s {
+  char *key;
+  char *value;
+};
+typedef struct sd_label_s sd_label_t;
+
+struct sd_resource_s {
+  char *type;
+
+  sd_label_t *labels;
+  size_t labels_num;
+};
+
+static int json_string(yajl_gen gen, char const *s) /* {{{ */
+{
+  yajl_gen_status status =
+      yajl_gen_string(gen, (unsigned char const *)s, strlen(s));
+  if (status != yajl_gen_status_ok)
+    return (int)status;
+
+  return 0;
+} /* }}} int json_string */
+
+static int json_time(yajl_gen gen, cdtime_t t) {
+  char buffer[64];
+
+  size_t status = rfc3339(buffer, sizeof(buffer), t);
+  if (status != 0) {
+    return status;
+  }
+
+  return json_string(gen, buffer);
+} /* }}} int json_time */
+
+/* MonitoredResource
+ *
+ * {
+ *   "type": "library.googleapis.com/book",
+ *   "labels": {
+ *     "/genre": "fiction",
+ *     "/media": "paper"
+ *     "/title": "The Old Man and the Sea"
+ *   }
+ * }
+ */
+static int format_gcm_resource(yajl_gen gen, sd_resource_t *res) /* {{{ */
+{
+  yajl_gen_map_open(gen);
+
+  int status = json_string(gen, "type") || json_string(gen, res->type);
+  if (status != 0)
+    return status;
+
+  if (res->labels_num != 0) {
+    status = json_string(gen, "labels");
+    if (status != 0)
+      return status;
+
+    yajl_gen_map_open(gen);
+    for (size_t i = 0; i < res->labels_num; i++) {
+      status = json_string(gen, res->labels[i].key) ||
+               json_string(gen, res->labels[i].value);
+      if (status != 0)
+        return status;
+    }
+    yajl_gen_map_close(gen);
+  }
+
+  yajl_gen_map_close(gen);
+  return 0;
+} /* }}} int format_gcm_resource */
+
+/* TypedValue
+ *
+ * {
+ *   // Union field, only one of the following:
+ *   "int64Value": string,
+ *   "doubleValue": number,
+ * }
+ */
+static int format_typed_value(yajl_gen gen, int ds_type, value_t v,
+                              int64_t start_value) {
+  char integer[32];
+
+  yajl_gen_map_open(gen);
+
+  switch (ds_type) {
+  case DS_TYPE_GAUGE: {
+    int status = json_string(gen, "doubleValue");
+    if (status != 0)
+      return status;
+
+    status = (int)yajl_gen_double(gen, (double)v.gauge);
+    if (status != yajl_gen_status_ok)
+      return status;
+
+    yajl_gen_map_close(gen);
+    return 0;
+  }
+  case DS_TYPE_DERIVE: {
+    derive_t diff = v.derive - (derive_t)start_value;
+    snprintf(integer, sizeof(integer), "%" PRIi64, diff);
+    break;
+  }
+  case DS_TYPE_COUNTER: {
+    counter_t diff = counter_diff((counter_t)start_value, v.counter);
+    snprintf(integer, sizeof(integer), "%llu", diff);
+    break;
+  }
+  case DS_TYPE_ABSOLUTE: {
+    snprintf(integer, sizeof(integer), "%" PRIu64, v.absolute);
+    break;
+  }
+  default: {
+    ERROR("format_typed_value: unknown value type %d.", ds_type);
+    return EINVAL;
+  }
+  }
+
+  int status = json_string(gen, "int64Value") || json_string(gen, integer);
+  if (status != 0) {
+    return status;
+  }
+
+  yajl_gen_map_close(gen);
+  return 0;
+} /* }}} int format_typed_value */
+
+/* MetricKind
+ *
+ * enum(
+ *   "CUMULATIVE",
+ *   "GAUGE"
+ * )
+*/
+static int format_metric_kind(yajl_gen gen, int ds_type) {
+  switch (ds_type) {
+  case DS_TYPE_GAUGE:
+  case DS_TYPE_ABSOLUTE:
+    return json_string(gen, "GAUGE");
+  case DS_TYPE_COUNTER:
+  case DS_TYPE_DERIVE:
+    return json_string(gen, "CUMULATIVE");
+  default:
+    ERROR("format_metric_kind: unknown value type %d.", ds_type);
+    return EINVAL;
+  }
+}
+
+/* ValueType
+ *
+ * enum(
+ *   "DOUBLE",
+ *   "INT64"
+ * )
+*/
+static int format_value_type(yajl_gen gen, int ds_type) {
+  return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "DOUBLE" : "INT64");
+}
+
+static int metric_type(char *buffer, size_t buffer_size, data_set_t const *ds,
+                       value_list_t const *vl, int ds_index) {
+  /* {{{ */
+  char const *ds_name = ds->ds[ds_index].name;
+
+#define GCM_PREFIX "custom.googleapis.com/collectd/"
+  if ((ds_index != 0) || strcmp("value", ds_name) != 0) {
+    snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s_%s", vl->plugin, vl->type,
+             ds_name);
+  } else {
+    snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s", vl->plugin, vl->type);
+  }
+
+  char const *whitelist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                          "abcdefghijklmnopqrstuvwxyz"
+                          "0123456789_/";
+  char *ptr = buffer + strlen(GCM_PREFIX);
+  size_t ok_len;
+  while ((ok_len = strspn(ptr, whitelist)) != strlen(ptr)) {
+    ptr[ok_len] = '_';
+    ptr += ok_len;
+  }
+
+  return 0;
+} /* }}} int metric_type */
+
+/* The metric type, including its DNS name prefix. The type is not URL-encoded.
+ * All user-defined custom metric types have the DNS name custom.googleapis.com.
+ * Metric types should use a natural hierarchical grouping. */
+static int format_metric_type(yajl_gen gen, data_set_t const *ds,
+                              value_list_t const *vl, int ds_index) {
+  /* {{{ */
+  char buffer[4 * DATA_MAX_NAME_LEN];
+  metric_type(buffer, sizeof(buffer), ds, vl, ds_index);
+
+  return json_string(gen, buffer);
+} /* }}} int format_metric_type */
+
+/* TimeInterval
+ *
+ * {
+ *   "endTime": string,
+ *   "startTime": string,
+ * }
+ */
+static int format_time_interval(yajl_gen gen, int ds_type,
+                                value_list_t const *vl, cdtime_t start_time) {
+  /* {{{ */
+  yajl_gen_map_open(gen);
+
+  int status = json_string(gen, "endTime") || json_time(gen, vl->time);
+  if (status != 0)
+    return status;
+
+  if ((ds_type == DS_TYPE_DERIVE) || (ds_type == DS_TYPE_COUNTER)) {
+    int status = json_string(gen, "startTime") || json_time(gen, start_time);
+    if (status != 0)
+      return status;
+  }
+
+  yajl_gen_map_close(gen);
+  return 0;
+} /* }}} int format_time_interval */
+
+/* read_cumulative_state reads the start time and start value of cumulative
+ * (i.e. DERIVE or COUNTER) metrics from the cache. If a metric is seen for the
+ * first time, or when a DERIVE metric is reset, the start time is (re)set to
+ * vl->time. */
+static int read_cumulative_state(data_set_t const *ds, value_list_t const *vl,
+                                 int ds_index, cdtime_t *ret_start_time,
+                                 int64_t *ret_start_value) {
+  int ds_type = ds->ds[ds_index].type;
+  if ((ds_type != DS_TYPE_DERIVE) && (ds_type != DS_TYPE_COUNTER)) {
+    return 0;
+  }
+
+  char start_value_key[DATA_MAX_NAME_LEN];
+  snprintf(start_value_key, sizeof(start_value_key),
+           "stackdriver:start_value[%d]", ds_index);
+
+  int status =
+      uc_meta_data_get_signed_int(vl, start_value_key, ret_start_value);
+  if ((status == 0) && ((ds_type != DS_TYPE_DERIVE) ||
+                        (*ret_start_value <= vl->values[ds_index].derive))) {
+    return uc_meta_data_get_unsigned_int(vl, "stackdriver:start_time",
+                                         ret_start_time);
+  }
+
+  if (ds_type == DS_TYPE_DERIVE) {
+    *ret_start_value = vl->values[ds_index].derive;
+  } else {
+    *ret_start_value = (int64_t)vl->values[ds_index].counter;
+  }
+  *ret_start_time = vl->time;
+
+  status = uc_meta_data_add_signed_int(vl, start_value_key, *ret_start_value);
+  if (status != 0) {
+    return status;
+  }
+  return uc_meta_data_add_unsigned_int(vl, "stackdriver:start_time",
+                                       *ret_start_time);
+} /* int read_cumulative_state */
+
+/* Point
+ *
+ * {
+ *   "interval": {
+ *     object(TimeInterval)
+ *   },
+ *   "value": {
+ *     object(TypedValue)
+ *   },
+ * }
+ */
+static int format_point(yajl_gen gen, data_set_t const *ds,
+                        value_list_t const *vl, int ds_index,
+                        cdtime_t start_time, int64_t start_value) {
+  /* {{{ */
+  yajl_gen_map_open(gen);
+
+  int ds_type = ds->ds[ds_index].type;
+
+  int status =
+      json_string(gen, "interval") ||
+      format_time_interval(gen, ds_type, vl, start_time) ||
+      json_string(gen, "value") ||
+      format_typed_value(gen, ds_type, vl->values[ds_index], start_value);
+  if (status != 0)
+    return status;
+
+  yajl_gen_map_close(gen);
+  return 0;
+} /* }}} int format_point */
+
+/* Metric
+ *
+ * {
+ *   "type": string,
+ *   "labels": {
+ *     string: string,
+ *     ...
+ *   },
+ * }
+ */
+static int format_metric(yajl_gen gen, data_set_t const *ds,
+                         value_list_t const *vl, int ds_index) {
+  /* {{{ */
+  yajl_gen_map_open(gen);
+
+  int status = json_string(gen, "type") ||
+               format_metric_type(gen, ds, vl, ds_index) ||
+               json_string(gen, "labels");
+  if (status != 0) {
+    return status;
+  }
+
+  yajl_gen_map_open(gen);
+  status = json_string(gen, "host") || json_string(gen, vl->host) ||
+           json_string(gen, "plugin_instance") ||
+           json_string(gen, vl->plugin_instance) ||
+           json_string(gen, "type_instance") ||
+           json_string(gen, vl->type_instance);
+  if (status != 0) {
+    return status;
+  }
+  yajl_gen_map_close(gen);
+
+  yajl_gen_map_close(gen);
+  return 0;
+} /* }}} int format_metric */
+
+/* TimeSeries
+ *
+ * {
+ *   "metric": {
+ *     object(Metric)
+ *   },
+ *   "resource": {
+ *     object(MonitoredResource)
+ *   },
+ *   "metricKind": enum(MetricKind),
+ *   "valueType": enum(ValueType),
+ *   "points": [
+ *     {
+ *       object(Point)
+ *     }
+ *   ],
+ * }
+ */
+/* format_time_series formats a TimeSeries object. Returns EAGAIN when a
+ * cumulative metric is seen for the first time and cannot be sent to
+ * Stackdriver due to lack of state. */
+static int format_time_series(yajl_gen gen, data_set_t const *ds,
+                              value_list_t const *vl, int ds_index,
+                              sd_resource_t *res) {
+  int ds_type = ds->ds[ds_index].type;
+
+  cdtime_t start_time = 0;
+  int64_t start_value = 0;
+  int status =
+      read_cumulative_state(ds, vl, ds_index, &start_time, &start_value);
+  if (status != 0) {
+    return status;
+  }
+  if (start_time == vl->time) {
+    /* for cumulative metrics, the interval must not be zero. */
+    return EAGAIN;
+  }
+
+  yajl_gen_map_open(gen);
+
+  status = json_string(gen, "metric") || format_metric(gen, ds, vl, ds_index) ||
+           json_string(gen, "resource") || format_gcm_resource(gen, res) ||
+           json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) ||
+           json_string(gen, "valueType") || format_value_type(gen, ds_type) ||
+           json_string(gen, "points");
+  if (status != 0)
+    return status;
+
+  yajl_gen_array_open(gen);
+
+  status = format_point(gen, ds, vl, ds_index, start_time, start_value);
+  if (status != 0)
+    return status;
+
+  yajl_gen_array_close(gen);
+  yajl_gen_map_close(gen);
+  return 0;
+} /* }}} int format_time_series */
+
+/* Request body
+ *
+ * {
+ *   "timeSeries": [
+ *     {
+ *       object(TimeSeries)
+ *     }
+ *   ],
+ * }
+ */
+static int sd_output_initialize(sd_output_t *out) /* {{{ */
+{
+  yajl_gen_map_open(out->gen);
+
+  int status = json_string(out->gen, "timeSeries");
+  if (status != 0) {
+    return status;
+  }
+
+  yajl_gen_array_open(out->gen);
+  return 0;
+} /* }}} int sd_output_initialize */
+
+static int sd_output_finalize(sd_output_t *out) /* {{{ */
+{
+  yajl_gen_array_close(out->gen);
+  yajl_gen_map_close(out->gen);
+
+  return 0;
+} /* }}} int sd_output_finalize */
+
+static void sd_output_reset_staged(sd_output_t *out) /* {{{ */
+{
+  void *key = NULL;
+
+  while (c_avl_pick(out->staged, &key, &(void *){NULL}) == 0)
+    sfree(key);
+} /* }}} void sd_output_reset_staged */
+
+sd_output_t *sd_output_create(sd_resource_t *res) /* {{{ */
+{
+  sd_output_t *out = calloc(1, sizeof(*out));
+  if (out == NULL)
+    return NULL;
+
+  out->res = res;
+
+  out->gen = yajl_gen_alloc(/* funcs = */ NULL);
+  if (out->gen == NULL) {
+    sd_output_destroy(out);
+    return NULL;
+  }
+
+  out->staged = c_avl_create((void *)strcmp);
+  if (out->staged == NULL) {
+    sd_output_destroy(out);
+    return NULL;
+  }
+
+  out->metric_descriptors = c_avl_create((void *)strcmp);
+  if (out->metric_descriptors == NULL) {
+    sd_output_destroy(out);
+    return NULL;
+  }
+
+  sd_output_initialize(out);
+
+  return out;
+} /* }}} sd_output_t *sd_output_create */
+
+void sd_output_destroy(sd_output_t *out) /* {{{ */
+{
+  if (out == NULL)
+    return;
+
+  if (out->metric_descriptors != NULL) {
+    void *key = NULL;
+    while (c_avl_pick(out->metric_descriptors, &key, &(void *){NULL}) == 0) {
+      sfree(key);
+    }
+    c_avl_destroy(out->metric_descriptors);
+    out->metric_descriptors = NULL;
+  }
+
+  if (out->staged != NULL) {
+    sd_output_reset_staged(out);
+    c_avl_destroy(out->staged);
+    out->staged = NULL;
+  }
+
+  if (out->gen != NULL) {
+    yajl_gen_free(out->gen);
+    out->gen = NULL;
+  }
+
+  if (out->res != NULL) {
+    sd_resource_destroy(out->res);
+    out->res = NULL;
+  }
+
+  sfree(out);
+} /* }}} void sd_output_destroy */
+
+int sd_output_add(sd_output_t *out, data_set_t const *ds,
+                  value_list_t const *vl) /* {{{ */
+{
+  /* first, check that we have all appropriate metric descriptors. */
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    char buffer[4 * DATA_MAX_NAME_LEN];
+    metric_type(buffer, sizeof(buffer), ds, vl, i);
+
+    if (c_avl_get(out->metric_descriptors, buffer, NULL) != 0) {
+      return ENOENT;
+    }
+  }
+
+  char key[6 * DATA_MAX_NAME_LEN];
+  int status = FORMAT_VL(key, sizeof(key), vl);
+  if (status != 0) {
+    ERROR("sd_output_add: FORMAT_VL failed with status %d.", status);
+    return status;
+  }
+
+  if (c_avl_get(out->staged, key, NULL) == 0) {
+    return EEXIST;
+  }
+
+  _Bool staged = 0;
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    int status = format_time_series(out->gen, ds, vl, i, out->res);
+    if (status == EAGAIN) {
+      /* first instance of a cumulative metric */
+      continue;
+    }
+    if (status != 0) {
+      ERROR("sd_output_add: format_time_series failed with status %d.", status);
+      return status;
+    }
+    staged = 1;
+  }
+
+  if (staged) {
+    c_avl_insert(out->staged, strdup(key), NULL);
+  }
+
+  size_t json_buffer_size = 0;
+  yajl_gen_get_buf(out->gen, &(unsigned char const *){NULL}, &json_buffer_size);
+  if (json_buffer_size > 65535)
+    return ENOBUFS;
+
+  return 0;
+} /* }}} int sd_output_add */
+
+int sd_output_register_metric(sd_output_t *out, data_set_t const *ds,
+                              value_list_t const *vl) {
+  /* {{{ */
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    char buffer[4 * DATA_MAX_NAME_LEN];
+    metric_type(buffer, sizeof(buffer), ds, vl, i);
+
+    char *key = strdup(buffer);
+    int status = c_avl_insert(out->metric_descriptors, key, NULL);
+    if (status != 0) {
+      sfree(key);
+      return status;
+    }
+  }
+
+  return 0;
+} /* }}} int sd_output_register_metric */
+
+char *sd_output_reset(sd_output_t *out) /* {{{ */
+{
+  sd_output_finalize(out);
+
+  unsigned char const *json_buffer = NULL;
+  yajl_gen_get_buf(out->gen, &json_buffer, &(size_t){0});
+  char *ret = strdup((void const *)json_buffer);
+
+  sd_output_reset_staged(out);
+
+  yajl_gen_free(out->gen);
+  out->gen = yajl_gen_alloc(/* funcs = */ NULL);
+
+  sd_output_initialize(out);
+
+  return ret;
+} /* }}} char *sd_output_reset */
+
+sd_resource_t *sd_resource_create(char const *type) /* {{{ */
+{
+  sd_resource_t *res = malloc(sizeof(*res));
+  if (res == NULL)
+    return NULL;
+  memset(res, 0, sizeof(*res));
+
+  res->type = strdup(type);
+  if (res->type == NULL) {
+    sfree(res);
+    return NULL;
+  }
+
+  res->labels = NULL;
+  res->labels_num = 0;
+
+  return res;
+} /* }}} sd_resource_t *sd_resource_create */
+
+void sd_resource_destroy(sd_resource_t *res) /* {{{ */
+{
+  if (res == NULL)
+    return;
+
+  for (size_t i = 0; i < res->labels_num; i++) {
+    sfree(res->labels[i].key);
+    sfree(res->labels[i].value);
+  }
+  sfree(res->labels);
+  sfree(res->type);
+  sfree(res);
+} /* }}} void sd_resource_destroy */
+
+int sd_resource_add_label(sd_resource_t *res, char const *key,
+                          char const *value) /* {{{ */
+{
+  if ((res == NULL) || (key == NULL) || (value == NULL))
+    return EINVAL;
+
+  sd_label_t *l =
+      realloc(res->labels, sizeof(*res->labels) * (res->labels_num + 1));
+  if (l == NULL)
+    return ENOMEM;
+
+  res->labels = l;
+  l = res->labels + res->labels_num;
+
+  l->key = strdup(key);
+  l->value = strdup(value);
+  if ((l->key == NULL) || (l->value == NULL)) {
+    sfree(l->key);
+    sfree(l->value);
+    return ENOMEM;
+  }
+
+  res->labels_num++;
+  return 0;
+} /* }}} int sd_resource_add_label */
+
+/* LabelDescriptor
+ *
+ * {
+ *   "key": string,
+ *   "valueType": enum(ValueType),
+ *   "description": string,
+ * }
+ */
+static int format_label_descriptor(yajl_gen gen, char const *key) {
+  /* {{{ */
+  yajl_gen_map_open(gen);
+
+  int status = json_string(gen, "key") || json_string(gen, key) ||
+               json_string(gen, "valueType") || json_string(gen, "STRING");
+  if (status != 0) {
+    return status;
+  }
+
+  yajl_gen_map_close(gen);
+  return 0;
+} /* }}} int format_label_descriptor */
+
+/* MetricDescriptor
+ *
+ * {
+ *   "name": string,
+ *   "type": string,
+ *   "labels": [
+ *     {
+ *       object(LabelDescriptor)
+ *     }
+ *   ],
+ *   "metricKind": enum(MetricKind),
+ *   "valueType": enum(ValueType),
+ *   "unit": string,
+ *   "description": string,
+ *   "displayName": string,
+ * }
+ */
+int sd_format_metric_descriptor(char *buffer, size_t buffer_size,
+                                data_set_t const *ds, value_list_t const *vl,
+                                int ds_index) {
+  /* {{{ */
+  yajl_gen gen = yajl_gen_alloc(/* funcs = */ NULL);
+  if (gen == NULL) {
+    return ENOMEM;
+  }
+
+  int ds_type = ds->ds[ds_index].type;
+
+  yajl_gen_map_open(gen);
+
+  int status =
+      json_string(gen, "type") || format_metric_type(gen, ds, vl, ds_index) ||
+      json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) ||
+      json_string(gen, "valueType") || format_value_type(gen, ds_type) ||
+      json_string(gen, "labels");
+  if (status != 0) {
+    yajl_gen_free(gen);
+    return status;
+  }
+
+  char const *labels[] = {"host", "plugin_instance", "type_instance"};
+  yajl_gen_array_open(gen);
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(labels); i++) {
+    int status = format_label_descriptor(gen, labels[i]);
+    if (status != 0) {
+      yajl_gen_free(gen);
+      return status;
+    }
+  }
+
+  yajl_gen_array_close(gen);
+  yajl_gen_map_close(gen);
+
+  unsigned char const *tmp = NULL;
+  yajl_gen_get_buf(gen, &tmp, &(size_t){0});
+  sstrncpy(buffer, (void const *)tmp, buffer_size);
+
+  yajl_gen_free(gen);
+  return 0;
+} /* }}} int sd_format_metric_descriptor */
diff --git a/src/utils_format_stackdriver.h b/src/utils_format_stackdriver.h
new file mode 100644 (file)
index 0000000..fee260e
--- /dev/null
@@ -0,0 +1,79 @@
+/**
+ * collectd - src/utils_format_stackdriver.h
+ * ISC license
+ *
+ * Copyright (C) 2017  Florian Forster
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#ifndef UTILS_FORMAT_STACKDRIVER_H
+#define UTILS_FORMAT_STACKDRIVER_H 1
+
+#include "collectd.h"
+#include "plugin.h"
+
+/* sd_output_t is a buffer to which value_list_t* can be added and from which
+ * an appropriately formatted char* can be read. */
+struct sd_output_s;
+typedef struct sd_output_s sd_output_t;
+
+/* sd_resource_t represents a MonitoredResource. */
+struct sd_resource_s;
+typedef struct sd_resource_s sd_resource_t;
+
+sd_output_t *sd_output_create(sd_resource_t *res);
+
+/* sd_output_destroy frees all memory used by out, including the
+ * sd_resource_t* passed to sd_output_create. */
+void sd_output_destroy(sd_output_t *out);
+
+/* sd_output_add adds a value_list_t* to "out".
+ *
+ * Return values:
+ *   - 0        Success
+ *   - ENOBUFS  Success, but the buffer should be flushed soon.
+ *   - EEXIST   The value list is already encoded in the buffer.
+ *              Flush the buffer, then call sd_output_add again.
+ *   - ENOENT   First time we encounter this metric. Create a metric descriptor
+ *              using the Stackdriver API and then call
+ *              sd_output_register_metric.
+ */
+int sd_output_add(sd_output_t *out, data_set_t const *ds,
+                  value_list_t const *vl);
+
+/* sd_output_register_metric adds the metric descriptor which vl maps to, to
+ * the list of known metric descriptors. */
+int sd_output_register_metric(sd_output_t *out, data_set_t const *ds,
+                              value_list_t const *vl);
+
+/* sd_output_reset resets the output and returns the previous content of the
+ * buffer. It is the caller's responsibility to call free() with the returned
+ * pointer. */
+char *sd_output_reset(sd_output_t *out);
+
+sd_resource_t *sd_resource_create(char const *type);
+void sd_resource_destroy(sd_resource_t *res);
+int sd_resource_add_label(sd_resource_t *res, char const *key,
+                          char const *value);
+
+/* sd_format_metric_descriptor creates the payload for a
+ * projects.metricDescriptors.create() request. */
+int sd_format_metric_descriptor(char *buffer, size_t buffer_size,
+                                data_set_t const *ds, value_list_t const *vl,
+                                int ds_index);
+
+#endif /* UTILS_FORMAT_STACKDRIVER_H */
diff --git a/src/utils_format_stackdriver_test.c b/src/utils_format_stackdriver_test.c
new file mode 100644 (file)
index 0000000..fa43866
--- /dev/null
@@ -0,0 +1,75 @@
+/**
+ * collectd - src/utils_format_stackdriver_test.c
+ * ISC license
+ *
+ * Copyright (C) 2017  Florian Forster
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "testing.h"
+#include "utils_format_stackdriver.h"
+
+DEF_TEST(sd_format_metric_descriptor) {
+  value_list_t vl = {
+      .host = "example.com", .plugin = "unit-test", .type = "example",
+  };
+  char got[1024];
+
+  data_set_t ds_single = {
+      .type = "example",
+      .ds_num = 1,
+      .ds =
+          &(data_source_t){
+              .name = "value", .type = DS_TYPE_GAUGE, .min = NAN, .max = NAN,
+          },
+  };
+  EXPECT_EQ_INT(
+      0, sd_format_metric_descriptor(got, sizeof(got), &ds_single, &vl, 0));
+  char const *want_single =
+      "{\"type\":\"custom.googleapis.com/collectd/unit_test/"
+      "example\",\"metricKind\":\"GAUGE\",\"valueType\":\"DOUBLE\",\"labels\":["
+      "{\"key\":\"host\",\"valueType\":\"STRING\"},{\"key\":\"plugin_"
+      "instance\",\"valueType\":\"STRING\"},{\"key\":\"type_instance\","
+      "\"valueType\":\"STRING\"}]}";
+  EXPECT_EQ_STR(want_single, got);
+
+  data_set_t ds_double = {
+      .type = "example",
+      .ds_num = 2,
+      .ds =
+          (data_source_t[]){
+              {.name = "one", .type = DS_TYPE_DERIVE, .min = 0, .max = NAN},
+              {.name = "two", .type = DS_TYPE_DERIVE, .min = 0, .max = NAN},
+          },
+  };
+  EXPECT_EQ_INT(
+      0, sd_format_metric_descriptor(got, sizeof(got), &ds_double, &vl, 0));
+  char const *want_double =
+      "{\"type\":\"custom.googleapis.com/collectd/unit_test/"
+      "example_one\",\"metricKind\":\"CUMULATIVE\",\"valueType\":\"INT64\","
+      "\"labels\":[{\"key\":\"host\",\"valueType\":\"STRING\"},{\"key\":"
+      "\"plugin_instance\",\"valueType\":\"STRING\"},{\"key\":\"type_"
+      "instance\",\"valueType\":\"STRING\"}]}";
+  EXPECT_EQ_STR(want_double, got);
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  RUN_TEST(sd_format_metric_descriptor);
+
+  END_TEST;
+}
diff --git a/src/utils_gce.c b/src/utils_gce.c
new file mode 100644 (file)
index 0000000..d43d1de
--- /dev/null
@@ -0,0 +1,284 @@
+/**
+ * collectd - src/utils_gce.c
+ * ISC license
+ *
+ * Copyright (C) 2017  Florian Forster
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+
+#include "common.h"
+#include "plugin.h"
+#include "utils_gce.h"
+#include "utils_oauth.h"
+#include "utils_time.h"
+
+#include <curl/curl.h>
+
+#ifndef GCP_METADATA_PREFIX
+#define GCP_METADATA_PREFIX "http://metadata.google.internal/computeMetadata/v1"
+#endif
+#ifndef GCE_METADATA_HEADER
+#define GCE_METADATA_HEADER "Metadata-Flavor: Google"
+#endif
+
+#ifndef GCE_INSTANCE_ID_URL
+#define GCE_INSTANCE_ID_URL GCP_METADATA_PREFIX "/instance/id"
+#endif
+#ifndef GCE_PROJECT_NUM_URL
+#define GCE_PROJECT_NUM_URL GCP_METADATA_PREFIX "/project/numeric-project-id"
+#endif
+#ifndef GCE_PROJECT_ID_URL
+#define GCE_PROJECT_ID_URL GCP_METADATA_PREFIX "/project/project-id"
+#endif
+#ifndef GCE_ZONE_URL
+#define GCE_ZONE_URL GCP_METADATA_PREFIX "/instance/zone"
+#endif
+
+#ifndef GCE_DEFAULT_SERVICE_ACCOUNT
+#define GCE_DEFAULT_SERVICE_ACCOUNT "default"
+#endif
+
+#ifndef GCE_SCOPE_URL
+#define GCE_SCOPE_URL_FORMAT                                                   \
+  GCP_METADATA_PREFIX "/instance/service-accounts/%s/scopes"
+#endif
+#ifndef GCE_TOKEN_URL
+#define GCE_TOKEN_URL_FORMAT                                                   \
+  GCP_METADATA_PREFIX "/instance/service-accounts/%s/token"
+#endif
+
+struct blob_s {
+  char *data;
+  size_t size;
+};
+typedef struct blob_s blob_t;
+
+static int on_gce = -1;
+
+static char *token = NULL;
+static char *token_email = NULL;
+static cdtime_t token_valid_until = 0;
+static pthread_mutex_t token_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static size_t write_callback(void *contents, size_t size, size_t nmemb,
+                             void *ud) /* {{{ */
+{
+  size_t realsize = size * nmemb;
+  blob_t *blob = ud;
+
+  if ((0x7FFFFFF0 < blob->size) || (0x7FFFFFF0 - blob->size < realsize)) {
+    ERROR("utils_gce: write_callback: integer overflow");
+    return 0;
+  }
+
+  blob->data = realloc(blob->data, blob->size + realsize + 1);
+  if (blob->data == NULL) {
+    /* out of memory! */
+    ERROR(
+        "utils_gce: write_callback: not enough memory (realloc returned NULL)");
+    return 0;
+  }
+
+  memcpy(blob->data + blob->size, contents, realsize);
+  blob->size += realsize;
+  blob->data[blob->size] = 0;
+
+  return realsize;
+} /* }}} size_t write_callback */
+
+/* read_url will issue a GET request for the given URL, setting the magic GCE
+ * metadata header in the process. On success, the response body is returned
+ * and it's the caller's responsibility to free it. On failure, an error is
+ * logged and NULL is returned. */
+static char *read_url(char const *url) /* {{{ */
+{
+  CURL *curl = curl_easy_init();
+  if (!curl) {
+    ERROR("utils_gce: curl_easy_init failed.");
+    return NULL;
+  }
+
+  struct curl_slist *headers = curl_slist_append(NULL, GCE_METADATA_HEADER);
+
+  char curl_errbuf[CURL_ERROR_SIZE];
+  blob_t blob = {0};
+  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
+  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
+  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &blob);
+  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+  curl_easy_setopt(curl, CURLOPT_URL, url);
+
+  int status = curl_easy_perform(curl);
+  if (status != CURLE_OK) {
+    ERROR("utils_gce: fetching %s failed: %s", url, curl_errbuf);
+    sfree(blob.data);
+    curl_easy_cleanup(curl);
+    curl_slist_free_all(headers);
+    return NULL;
+  }
+
+  long http_code = 0;
+  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+  if ((http_code < 200) || (http_code >= 300)) {
+    ERROR("write_gcm plugin: fetching %s failed: HTTP error %ld", url,
+          http_code);
+    sfree(blob.data);
+    curl_easy_cleanup(curl);
+    curl_slist_free_all(headers);
+    return NULL;
+  }
+
+  curl_easy_cleanup(curl);
+  curl_slist_free_all(headers);
+  return blob.data;
+} /* }}} char *read_url */
+
+_Bool gce_check(void) /* {{{ */
+{
+  if (on_gce != -1)
+    return on_gce == 1;
+
+  DEBUG("utils_gce: Checking whether I'm running on GCE ...");
+
+  CURL *curl = curl_easy_init();
+  if (!curl) {
+    ERROR("utils_gce: curl_easy_init failed.");
+    return 0;
+  }
+
+  struct curl_slist *headers = curl_slist_append(NULL, GCE_METADATA_HEADER);
+
+  char curl_errbuf[CURL_ERROR_SIZE];
+  blob_t blob = {NULL, 0};
+  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
+  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
+  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, write_callback);
+  curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &blob);
+  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+  curl_easy_setopt(curl, CURLOPT_URL, GCP_METADATA_PREFIX "/");
+
+  int status = curl_easy_perform(curl);
+  if ((status != CURLE_OK) || (blob.data == NULL) ||
+      (strstr(blob.data, "Metadata-Flavor: Google") == NULL)) {
+    DEBUG("utils_gce: ... no (%s)",
+          (status != CURLE_OK)
+              ? "curl_easy_perform failed"
+              : (blob.data == NULL) ? "blob.data == NULL"
+                                    : "Metadata-Flavor header not found");
+    sfree(blob.data);
+    curl_easy_cleanup(curl);
+    curl_slist_free_all(headers);
+    on_gce = 0;
+    return 0;
+  }
+  sfree(blob.data);
+
+  long http_code = 0;
+  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+  if ((http_code < 200) || (http_code >= 300)) {
+    DEBUG("utils_gce: ... no (HTTP status %ld)", http_code);
+    curl_easy_cleanup(curl);
+    curl_slist_free_all(headers);
+    on_gce = 0;
+    return 0;
+  }
+
+  DEBUG("utils_gce: ... yes");
+  curl_easy_cleanup(curl);
+  curl_slist_free_all(headers);
+  on_gce = 1;
+  return 1;
+} /* }}} _Bool gce_check */
+
+char *gce_project_id(void) /* {{{ */
+{
+  return read_url(GCE_PROJECT_ID_URL);
+} /* }}} char *gce_project_id */
+
+char *gce_instance_id(void) /* {{{ */
+{
+  return read_url(GCE_INSTANCE_ID_URL);
+} /* }}} char *gce_instance_id */
+
+char *gce_zone(void) /* {{{ */
+{
+  return read_url(GCE_ZONE_URL);
+} /* }}} char *gce_instance_id */
+
+char *gce_scope(char const *email) /* {{{ */
+{
+  char url[1024];
+
+  snprintf(url, sizeof(url), GCE_SCOPE_URL_FORMAT,
+           (email != NULL) ? email : GCE_DEFAULT_SERVICE_ACCOUNT);
+
+  return read_url(url);
+} /* }}} char *gce_scope */
+
+int gce_access_token(char const *email, char *buffer,
+                     size_t buffer_size) /* {{{ */
+{
+  char url[1024];
+  char *json;
+  cdtime_t now = cdtime();
+
+  pthread_mutex_lock(&token_lock);
+
+  if (email == NULL)
+    email = GCE_DEFAULT_SERVICE_ACCOUNT;
+
+  if ((token_email != NULL) && (strcmp(email, token_email) == 0) &&
+      (token_valid_until > now)) {
+    sstrncpy(buffer, token, buffer_size);
+    pthread_mutex_unlock(&token_lock);
+    return 0;
+  }
+
+  snprintf(url, sizeof(url), GCE_TOKEN_URL_FORMAT, email);
+  json = read_url(url);
+  if (json == NULL) {
+    pthread_mutex_unlock(&token_lock);
+    return -1;
+  }
+
+  char tmp[256];
+  cdtime_t expires_in = 0;
+  int status = oauth_parse_json_token(json, tmp, sizeof(tmp), &expires_in);
+  sfree(json);
+  if (status != 0) {
+    pthread_mutex_unlock(&token_lock);
+    return status;
+  }
+
+  sfree(token);
+  token = strdup(tmp);
+
+  sfree(token_email);
+  token_email = strdup(email);
+
+  /* let tokens expire a bit early */
+  expires_in = (expires_in * 95) / 100;
+  token_valid_until = now + expires_in;
+
+  sstrncpy(buffer, token, buffer_size);
+  pthread_mutex_unlock(&token_lock);
+  return 0;
+} /* }}} char *gce_token */
diff --git a/src/utils_gce.h b/src/utils_gce.h
new file mode 100644 (file)
index 0000000..2ee3f6e
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * collectd - src/utils_gce.h
+ * ISC license
+ *
+ * Copyright (C) 2017  Florian Forster
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#ifndef UTILS_GCE_H
+#define UTILS_GCE_H 1
+
+/* gce_check returns 1 when running on Google Compute Engine (GCE) and 0
+ * otherwise. */
+_Bool gce_check(void);
+
+/* gce_project_id returns the project ID of the instance, as configured when
+ * creating the project.
+ * For example "example-project-a". */
+char *gce_project_id(void);
+
+/* gce_instance_id returns the unique ID of the GCE instance. */
+char *gce_instance_id(void);
+
+/* gce_zone returns the zone in which the GCE instance runs. */
+char *gce_zone(void);
+
+/* gce_scope returns the list of scopes for the given service account (or the
+ * default service account when NULL is passed). */
+char *gce_scope(char const *email);
+
+/* gce_access_token acquires an OAuth access token for the given service account
+ * (or
+ * the default service account when NULL is passed) and stores it in buffer.
+ * Access tokens are automatically cached and renewed when they expire. Returns
+ * zero on success, non-zero otherwise. */
+int gce_access_token(char const *email, char *buffer, size_t buffer_size);
+
+#endif
index 1d3bf2e..6e4f873 100644 (file)
@@ -156,7 +156,7 @@ void latency_counter_add(latency_counter_t *lc, cdtime_t latency) /* {{{ */
     change_bin_width(lc, latency);
     bin = (latency - 1) / lc->bin_width;
     if (bin >= HISTOGRAM_NUM_BINS) {
-      ERROR("utils_latency: latency_counter_add: Invalid bin: %" PRIu64, bin);
+      P_ERROR("latency_counter_add: Invalid bin: %" PRIu64, bin);
       return;
     }
   }
index 5eb5b6d..9a91f11 100644 (file)
  *   Pavel Rochnyack <pavel2000 at ngs.ru>
  */
 
-#include "utils_latency_config.h"
-#include "common.h"
 #include "collectd.h"
+#include "common.h"
+#include "utils_latency_config.h"
 
 static int latency_config_add_percentile(latency_config_t *conf,
-                                         oconfig_item_t *ci,
-                                         const char *plugin) {
+                                         oconfig_item_t *ci) {
   double percent;
   int status = cf_util_get_double(ci, &percent);
   if (status != 0)
     return status;
 
   if ((percent <= 0.0) || (percent >= 100)) {
-    ERROR("%s plugin: The value for \"%s\" must be between 0 and 100, "
-          "exclusively.",
-          plugin, ci->key);
+    P_ERROR("The value for \"%s\" must be between 0 and 100, "
+            "exclusively.",
+            ci->key);
     return ERANGE;
   }
 
   double *tmp = realloc(conf->percentile,
                         sizeof(*conf->percentile) * (conf->percentile_num + 1));
   if (tmp == NULL) {
-    ERROR("%s plugin: realloc failed.", plugin);
+    P_ERROR("realloc failed.");
     return ENOMEM;
   }
   conf->percentile = tmp;
@@ -57,31 +56,29 @@ static int latency_config_add_percentile(latency_config_t *conf,
   return 0;
 } /* int latency_config_add_percentile */
 
-static int latency_config_add_bucket(latency_config_t *conf, oconfig_item_t *ci,
-                                     const char *plugin) {
+static int latency_config_add_bucket(latency_config_t *conf,
+                                     oconfig_item_t *ci) {
   if ((ci->values_num != 2) || (ci->values[0].type != OCONFIG_TYPE_NUMBER) ||
       (ci->values[1].type != OCONFIG_TYPE_NUMBER)) {
-    ERROR("%s plugin: \"%s\" requires exactly two numeric arguments.", plugin,
-          ci->key);
+    P_ERROR("\"%s\" requires exactly two numeric arguments.", ci->key);
     return EINVAL;
   }
 
   if (ci->values[1].value.number &&
       ci->values[1].value.number <= ci->values[0].value.number) {
-    ERROR("%s plugin: MIN must be less than MAX in \"%s\".", plugin, ci->key);
+    P_ERROR("MIN must be less than MAX in \"%s\".", ci->key);
     return ERANGE;
   }
 
   if (ci->values[0].value.number < 0) {
-    ERROR("%s plugin: MIN must be greater then or equal to zero in \"%s\".",
-          plugin, ci->key);
+    P_ERROR("MIN must be greater then or equal to zero in \"%s\".", ci->key);
     return ERANGE;
   }
 
   latency_bucket_t *tmp =
       realloc(conf->buckets, sizeof(*conf->buckets) * (conf->buckets_num + 1));
   if (tmp == NULL) {
-    ERROR("%s plugin: realloc failed.", plugin);
+    P_ERROR("realloc failed.");
     return ENOMEM;
   }
   conf->buckets = tmp;
@@ -94,22 +91,21 @@ static int latency_config_add_bucket(latency_config_t *conf, oconfig_item_t *ci,
   return 0;
 } /* int latency_config_add_bucket */
 
-int latency_config(latency_config_t *conf, oconfig_item_t *ci,
-                   char const *plugin) {
+int latency_config(latency_config_t *conf, oconfig_item_t *ci) {
   int status = 0;
 
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
 
     if (strcasecmp("Percentile", child->key) == 0)
-      status = latency_config_add_percentile(conf, child, plugin);
+      status = latency_config_add_percentile(conf, child);
     else if (strcasecmp("Bucket", child->key) == 0)
-      status = latency_config_add_bucket(conf, child, plugin);
+      status = latency_config_add_bucket(conf, child);
     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);
+      P_WARNING("\"%s\" is not a valid option within a \"%s\" block.",
+                child->key, ci->key);
 
     if (status != 0)
       return status;
@@ -117,9 +113,9 @@ int latency_config(latency_config_t *conf, oconfig_item_t *ci,
 
   if ((status == 0) && (conf->percentile_num == 0) &&
       (conf->buckets_num == 0)) {
-    ERROR("%s plugin: The \"%s\" block must contain at least one "
-          "\"Percentile\" or \"Bucket\" option.",
-          plugin, ci->key);
+    P_ERROR("The \"%s\" block must contain at least one "
+            "\"Percentile\" or \"Bucket\" option.",
+            ci->key);
     return EINVAL;
   }
 
index 2572fa0..3d2691a 100644 (file)
@@ -53,8 +53,7 @@ typedef struct {
   */
 } latency_config_t;
 
-int latency_config(latency_config_t *conf, oconfig_item_t *ci,
-                   char const *plugin);
+int latency_config(latency_config_t *conf, oconfig_item_t *ci);
 
 int latency_config_copy(latency_config_t *dst, const latency_config_t src);
 
index 1f060f8..11ac001 100644 (file)
  *   Florian Forster <octo at collectd.org>
  **/
 
-/* <lua5.1/luaconf.h> defines a macro using "sprintf". Although not used here,
- * GCC will complain about the macro definition. */
-#define DONT_POISON_SPRINTF_YET
-
 #include "common.h"
 #include "utils_lua.h"
 
index 61d9070..e5a3d74 100644 (file)
 #ifndef UTILS_LUA_H
 #define UTILS_LUA_H 1
 
-#include "plugin.h"
 #include "collectd.h"
+#include "plugin.h"
 
-#ifndef DONT_POISON_SPRINTF_YET
-#error "Files including utils_lua.h need to define DONT_POISON_SPRINTF_YET."
-#endif
 #include <lua.h>
 
 /*
diff --git a/src/utils_oauth.c b/src/utils_oauth.c
new file mode 100644 (file)
index 0000000..35533be
--- /dev/null
@@ -0,0 +1,636 @@
+/**
+ * collectd - src/utils_oauth.c
+ * ISC license
+ *
+ * Copyright (C) 2017  Florian Forster
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+
+#include "common.h"
+#include "plugin.h"
+#include "utils_oauth.h"
+
+#include <curl/curl.h>
+
+#include <yajl/yajl_tree.h>
+#include <yajl/yajl_version.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/sha.h>
+
+/*
+ * Private variables
+ */
+#define GOOGLE_TOKEN_URL "https://accounts.google.com/o/oauth2/token"
+
+/* Max send buffer size, since there will be only one writer thread and
+ * monitoring api supports up to 100K bytes in one request, 64K is reasonable
+ */
+#define MAX_BUFFER_SIZE 65536
+#define MAX_ENCODE_SIZE 2048
+
+struct oauth_s {
+  char *url;
+  char *iss;
+  char *aud;
+  char *scope;
+
+  EVP_PKEY *key;
+
+  char *token;
+  cdtime_t valid_until;
+};
+
+struct memory_s {
+  char *memory;
+  size_t size;
+};
+typedef struct memory_s memory_t;
+
+#define OAUTH_GRANT_TYPE "urn:ietf:params:oauth:grant-type:jwt-bearer"
+#define OAUTH_EXPIRATION_TIME TIME_T_TO_CDTIME_T(3600)
+#define OAUTH_HEADER "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"
+
+static const char OAUTH_CLAIM_FORMAT[] = "{"
+                                         "\"iss\":\"%s\","
+                                         "\"scope\":\"%s\","
+                                         "\"aud\":\"%s\","
+                                         "\"exp\":%lu,"
+                                         "\"iat\":%lu"
+                                         "}";
+
+static size_t write_memory(void *contents, size_t size, size_t nmemb, /* {{{ */
+                           void *userp) {
+  size_t realsize = size * nmemb;
+  memory_t *mem = (memory_t *)userp;
+  char *tmp;
+
+  if (0x7FFFFFF0 < mem->size || 0x7FFFFFF0 - mem->size < realsize) {
+    ERROR("integer overflow");
+    return 0;
+  }
+
+  tmp = (char *)realloc((void *)mem->memory, mem->size + realsize + 1);
+  if (tmp == NULL) {
+    /* out of memory! */
+    ERROR("write_memory: not enough memory (realloc returned NULL)");
+    return 0;
+  }
+  mem->memory = tmp;
+
+  memcpy(&(mem->memory[mem->size]), contents, realsize);
+  mem->size += realsize;
+  mem->memory[mem->size] = 0;
+
+  return realsize;
+} /* }}} size_t write_memory */
+
+/* Base64-encodes "s" and stores the result in buffer.
+ * Returns zero on success, non-zero otherwise. */
+static int base64_encode_n(char const *s, size_t s_size, /* {{{ */
+                           char *buffer, size_t buffer_size) {
+  BIO *b64;
+  BUF_MEM *bptr;
+  int status;
+  size_t i;
+
+  /* Set up the memory-base64 chain */
+  b64 = BIO_new(BIO_f_base64());
+  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+  b64 = BIO_push(b64, BIO_new(BIO_s_mem()));
+
+  /* Write data to the chain */
+  BIO_write(b64, (void const *)s, s_size);
+  status = BIO_flush(b64);
+  if (status != 1) {
+    ERROR("utils_oauth: base64_encode: BIO_flush() failed.");
+    BIO_free_all(b64);
+    return -1;
+  }
+
+  /* Never fails */
+  BIO_get_mem_ptr(b64, &bptr);
+
+  if (buffer_size <= bptr->length) {
+    ERROR("utils_oauth: base64_encode: Buffer too small.");
+    BIO_free_all(b64);
+    return -1;
+  }
+
+  /* Copy data to buffer. */
+  memcpy(buffer, bptr->data, bptr->length);
+  buffer[bptr->length] = 0;
+
+  /* replace + with -, / with _ and remove padding = at the end */
+  for (i = 0; i < bptr->length; i++) {
+    if (buffer[i] == '+') {
+      buffer[i] = '-';
+    } else if (buffer[i] == '/') {
+      buffer[i] = '_';
+    } else if (buffer[i] == '=') {
+      buffer[i] = 0;
+    }
+  }
+
+  BIO_free_all(b64);
+  return 0;
+} /* }}} int base64_encode_n */
+
+/* Base64-encodes "s" and stores the result in buffer.
+ * Returns zero on success, non-zero otherwise. */
+static int base64_encode(char const *s, /* {{{ */
+                         char *buffer, size_t buffer_size) {
+  return base64_encode_n(s, strlen(s), buffer, buffer_size);
+} /* }}} int base64_encode */
+
+/* get_header returns the base64 encoded OAuth header. */
+static int get_header(char *buffer, size_t buffer_size) /* {{{ */
+{
+  char header[] = OAUTH_HEADER;
+
+  return base64_encode(header, buffer, buffer_size);
+} /* }}} int get_header */
+
+/* get_claim constructs an OAuth claim and returns it as base64 encoded string.
+ */
+static int get_claim(oauth_t *auth, char *buffer, size_t buffer_size) /* {{{ */
+{
+  char claim[buffer_size];
+  cdtime_t exp;
+  cdtime_t iat;
+  int status;
+
+  iat = cdtime();
+  exp = iat + OAUTH_EXPIRATION_TIME;
+
+  /* create the claim set */
+  status =
+      snprintf(claim, sizeof(claim), OAUTH_CLAIM_FORMAT, auth->iss, auth->scope,
+               auth->aud, (unsigned long)CDTIME_T_TO_TIME_T(exp),
+               (unsigned long)CDTIME_T_TO_TIME_T(iat));
+  if (status < 1)
+    return -1;
+  else if ((size_t)status >= sizeof(claim))
+    return ENOMEM;
+
+  DEBUG("utils_oauth: get_claim() = %s", claim);
+
+  return base64_encode(claim, buffer, buffer_size);
+} /* }}} int get_claim */
+
+/* get_signature signs header and claim with pkey and returns the signature in
+ * buffer. */
+static int get_signature(char *buffer, size_t buffer_size, /* {{{ */
+                         char const *header, char const *claim,
+                         EVP_PKEY *pkey) {
+  char payload[buffer_size];
+  size_t payload_len;
+  char signature[buffer_size];
+  unsigned int signature_size;
+  int status;
+
+  /* Make the string to sign */
+  payload_len = snprintf(payload, sizeof(payload), "%s.%s", header, claim);
+  if (payload_len < 1) {
+    return -1;
+  } else if (payload_len >= sizeof(payload)) {
+    return ENOMEM;
+  }
+
+  /* Create the signature */
+  signature_size = EVP_PKEY_size(pkey);
+  if (signature_size > sizeof(signature)) {
+    ERROR("utils_oauth: Signature is too large (%u bytes).", signature_size);
+    return -1;
+  }
+
+  EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+
+  /* EVP_SignInit(3SSL) claims this is a void function, but in fact it returns
+   * an int. We're not going to rely on this, though. */
+  EVP_SignInit(ctx, EVP_sha256());
+
+  status = EVP_SignUpdate(ctx, payload, payload_len);
+  if (status != 1) {
+    char errbuf[1024];
+    ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
+    ERROR("utils_oauth: EVP_SignUpdate failed: %s", errbuf);
+
+    EVP_MD_CTX_free(ctx);
+    return -1;
+  }
+
+  status =
+      EVP_SignFinal(ctx, (unsigned char *)signature, &signature_size, pkey);
+  if (status != 1) {
+    char errbuf[1024];
+    ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
+    ERROR("utils_oauth: EVP_SignFinal failed: %s", errbuf);
+
+    EVP_MD_CTX_free(ctx);
+    return -1;
+  }
+
+  EVP_MD_CTX_free(ctx);
+
+  return base64_encode_n(signature, (size_t)signature_size, buffer,
+                         buffer_size);
+} /* }}} int get_signature */
+
+static int get_assertion(oauth_t *auth, char *buffer,
+                         size_t buffer_size) /* {{{ */
+{
+  char header[buffer_size];
+  char claim[buffer_size];
+  char signature[buffer_size];
+  int status;
+
+  status = get_header(header, sizeof(header));
+  if (status != 0)
+    return -1;
+
+  status = get_claim(auth, claim, sizeof(claim));
+  if (status != 0)
+    return -1;
+
+  status =
+      get_signature(signature, sizeof(signature), header, claim, auth->key);
+  if (status != 0)
+    return -1;
+
+  status = snprintf(buffer, buffer_size, "%s.%s.%s", header, claim, signature);
+  if (status < 1)
+    return -1;
+  else if (status >= buffer_size)
+    return ENOMEM;
+
+  return 0;
+} /* }}} int get_assertion */
+
+int oauth_parse_json_token(char const *json, /* {{{ */
+                           char *out_access_token, size_t access_token_size,
+                           cdtime_t *expires_in) {
+  time_t expire_in_seconds = 0;
+  yajl_val root;
+  yajl_val token_val;
+  yajl_val expire_val;
+  char errbuf[1024];
+  const char *token_path[] = {"access_token", NULL};
+  const char *expire_path[] = {"expires_in", NULL};
+
+  root = yajl_tree_parse(json, errbuf, sizeof(errbuf));
+  if (root == NULL) {
+    ERROR("utils_oauth: oauth_parse_json_token: parse error %s", errbuf);
+    return -1;
+  }
+
+  token_val = yajl_tree_get(root, token_path, yajl_t_string);
+  if (token_val == NULL) {
+    ERROR("utils_oauth: oauth_parse_json_token: access token field not found");
+    yajl_tree_free(root);
+    return -1;
+  }
+  sstrncpy(out_access_token, YAJL_GET_STRING(token_val), access_token_size);
+
+  expire_val = yajl_tree_get(root, expire_path, yajl_t_number);
+  if (expire_val == NULL) {
+    ERROR("utils_oauth: oauth_parse_json_token: expire field found");
+    yajl_tree_free(root);
+    return -1;
+  }
+  expire_in_seconds = (time_t)YAJL_GET_INTEGER(expire_val);
+  DEBUG("oauth_parse_json_token: expires_in %lu",
+        (unsigned long)expire_in_seconds);
+
+  *expires_in = TIME_T_TO_CDTIME_T(expire_in_seconds);
+  yajl_tree_free(root);
+  return 0;
+} /* }}} int oauth_parse_json_token */
+
+static int new_token(oauth_t *auth) /* {{{ */
+{
+  CURL *curl;
+  char assertion[1024];
+  char post_data[1024];
+  memory_t data;
+  char access_token[256];
+  cdtime_t expires_in;
+  cdtime_t now;
+  char curl_errbuf[CURL_ERROR_SIZE];
+  int status = 0;
+
+  data.size = 0;
+  data.memory = NULL;
+
+  now = cdtime();
+
+  status = get_assertion(auth, assertion, sizeof(assertion));
+  if (status != 0) {
+    ERROR("utils_oauth: Failed to get token using service account %s.",
+          auth->iss);
+    return -1;
+  }
+
+  snprintf(post_data, sizeof(post_data), "grant_type=%s&assertion=%s",
+           OAUTH_GRANT_TYPE, assertion);
+
+  curl = curl_easy_init();
+  if (curl == NULL) {
+    ERROR("utils_oauth: curl_easy_init failed.");
+    return -1;
+  }
+
+  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
+  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
+  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_memory);
+  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
+  curl_easy_setopt(curl, CURLOPT_POST, 1L);
+  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
+  curl_easy_setopt(curl, CURLOPT_URL, auth->url);
+
+  status = curl_easy_perform(curl);
+  if (status != CURLE_OK) {
+    ERROR("utils_oauth: curl_easy_perform failed with status %i: %s", status,
+          curl_errbuf);
+
+    sfree(data.memory);
+    curl_easy_cleanup(curl);
+
+    return -1;
+  } else {
+    long http_code = 0;
+
+    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+    if ((http_code < 200) || (http_code >= 300)) {
+      ERROR("utils_oauth: POST request to %s failed: HTTP error %ld", auth->url,
+            http_code);
+      if (data.memory != NULL)
+        INFO("utils_oauth: Server replied: %s", data.memory);
+
+      sfree(data.memory);
+      curl_easy_cleanup(curl);
+
+      return -1;
+    }
+  }
+
+  status = oauth_parse_json_token(data.memory, access_token,
+                                  sizeof(access_token), &expires_in);
+  if (status != 0) {
+    sfree(data.memory);
+    curl_easy_cleanup(curl);
+
+    return -1;
+  }
+
+  sfree(auth->token);
+  auth->token = strdup(access_token);
+  if (auth->token == NULL) {
+    ERROR("utils_oauth: strdup failed");
+    auth->valid_until = 0;
+
+    sfree(data.memory);
+    curl_easy_cleanup(curl);
+    return -1;
+  }
+
+  INFO("utils_oauth: OAuth2 access token is valid for %.3fs",
+       CDTIME_T_TO_DOUBLE(expires_in));
+  auth->valid_until = now + expires_in;
+
+  sfree(data.memory);
+  curl_easy_cleanup(curl);
+
+  return 0;
+} /* }}} int new_token */
+
+static int renew_token(oauth_t *auth) /* {{{ */
+{
+  /* TODO(octo): Make sure that we get a new token 60 seconds or so before the
+   * old one expires. */
+  if (auth->valid_until > cdtime())
+    return 0;
+
+  return new_token(auth);
+} /* }}} int renew_token */
+
+static oauth_t *oauth_create(char const *url, char const *iss,
+                             char const *scope, char const *aud,
+                             EVP_PKEY *key) /* {{{ */
+{
+  oauth_t *auth;
+
+  if ((url == NULL) || (iss == NULL) || (scope == NULL) || (aud == NULL) ||
+      (key == NULL))
+    return NULL;
+
+  auth = malloc(sizeof(*auth));
+  if (auth == NULL)
+    return NULL;
+  memset(auth, 0, sizeof(*auth));
+
+  auth->url = strdup(url);
+  auth->iss = strdup(iss);
+  auth->scope = strdup(scope);
+  auth->aud = strdup(aud);
+
+  if ((auth->url == NULL) || (auth->iss == NULL) || (auth->scope == NULL) ||
+      (auth->aud == NULL)) {
+    oauth_destroy(auth);
+    return NULL;
+  }
+
+  auth->key = key;
+
+  return auth;
+} /* }}} oauth_t *oauth_create */
+
+/*
+ * Public
+ */
+oauth_google_t oauth_create_google_json(char const *buffer, char const *scope) {
+  char errbuf[1024];
+  yajl_val root = yajl_tree_parse(buffer, errbuf, sizeof(errbuf));
+  if (root == NULL) {
+    ERROR("utils_oauth: oauth_create_google_json: parse error %s", errbuf);
+    return (oauth_google_t){NULL};
+  }
+
+  yajl_val field_project =
+      yajl_tree_get(root, (char const *[]){"project_id", NULL}, yajl_t_string);
+  if (field_project == NULL) {
+    ERROR("utils_oauth: oauth_create_google_json: project_id field not found");
+    yajl_tree_free(root);
+    return (oauth_google_t){NULL};
+  }
+  char const *project_id = YAJL_GET_STRING(field_project);
+
+  yajl_val field_iss = yajl_tree_get(
+      root, (char const *[]){"client_email", NULL}, yajl_t_string);
+  if (field_iss == NULL) {
+    ERROR(
+        "utils_oauth: oauth_create_google_json: client_email field not found");
+    yajl_tree_free(root);
+    return (oauth_google_t){NULL};
+  }
+
+  yajl_val field_token_uri =
+      yajl_tree_get(root, (char const *[]){"token_uri", NULL}, yajl_t_string);
+  char const *token_uri = (field_token_uri != NULL)
+                              ? YAJL_GET_STRING(field_token_uri)
+                              : GOOGLE_TOKEN_URL;
+
+  yajl_val field_priv_key =
+      yajl_tree_get(root, (char const *[]){"private_key", NULL}, yajl_t_string);
+  if (field_priv_key == NULL) {
+    ERROR("utils_oauth: oauth_create_google_json: private_key field not found");
+    yajl_tree_free(root);
+    return (oauth_google_t){NULL};
+  }
+
+  BIO *bp = BIO_new_mem_buf(YAJL_GET_STRING(field_priv_key), -1);
+  EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL);
+  if (pkey == NULL) {
+    char errbuf[1024];
+    ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
+    ERROR(
+        "utils_oauth: oauth_create_google_json: parsing private key failed: %s",
+        errbuf);
+    BIO_free(bp);
+    yajl_tree_free(root);
+    return (oauth_google_t){NULL};
+  }
+
+  BIO_free(bp);
+
+  oauth_t *oauth = oauth_create(token_uri, YAJL_GET_STRING(field_iss), scope,
+                                token_uri, pkey);
+  if (oauth == NULL) {
+    yajl_tree_free(root);
+    return (oauth_google_t){NULL};
+  }
+
+  oauth_google_t ret = {
+      .project_id = strdup(project_id), .oauth = oauth,
+  };
+
+  yajl_tree_free(root);
+  return ret;
+} /* oauth_google_t oauth_create_google_json */
+
+oauth_google_t oauth_create_google_file(char const *path,
+                                        char const *scope) { /* {{{ */
+  int fd = open(path, O_RDONLY);
+  if (fd == -1)
+    return (oauth_google_t){NULL};
+
+  struct stat st = {0};
+  if (fstat(fd, &st) != 0) {
+    close(fd);
+    return (oauth_google_t){NULL};
+  }
+
+  size_t buf_size = (size_t)st.st_size;
+  char *buf = calloc(1, buf_size + 1);
+  if (buf == NULL) {
+    close(fd);
+    return (oauth_google_t){NULL};
+  }
+
+  if (sread(fd, buf, buf_size) != 0) {
+    free(buf);
+    close(fd);
+    return (oauth_google_t){NULL};
+  }
+  close(fd);
+  buf[buf_size] = 0;
+
+  oauth_google_t ret = oauth_create_google_json(buf, scope);
+
+  free(buf);
+  return ret;
+} /* }}} oauth_google_t oauth_create_google_file */
+
+/* oauth_create_google_default checks for JSON credentials in well-known
+ * positions, similar to gcloud and other tools. */
+oauth_google_t oauth_create_google_default(char const *scope) {
+  char const *app_creds;
+  if ((app_creds = getenv("GOOGLE_APPLICATION_CREDENTIALS")) != NULL) {
+    oauth_google_t ret = oauth_create_google_file(app_creds, scope);
+    if (ret.oauth == NULL) {
+      ERROR("The environment variable GOOGLE_APPLICATION_CREDENTIALS is set to "
+            "\"%s\" but that file could not be read.",
+            app_creds);
+    } else {
+      return ret;
+    }
+  }
+
+  char const *home;
+  if ((home = getenv("HOME")) != NULL) {
+    char path[PATH_MAX];
+    snprintf(path, sizeof(path),
+             "%s/.config/gcloud/application_default_credentials.json", home);
+
+    oauth_google_t ret = oauth_create_google_file(path, scope);
+    if (ret.oauth != NULL) {
+      return ret;
+    }
+  }
+
+  return (oauth_google_t){NULL};
+} /* }}} oauth_google_t oauth_create_google_default */
+
+void oauth_destroy(oauth_t *auth) /* {{{ */
+{
+  if (auth == NULL)
+    return;
+
+  sfree(auth->url);
+  sfree(auth->iss);
+  sfree(auth->scope);
+  sfree(auth->aud);
+
+  if (auth->key != NULL) {
+    EVP_PKEY_free(auth->key);
+    auth->key = NULL;
+  }
+
+  sfree(auth);
+} /* }}} void oauth_destroy */
+
+int oauth_access_token(oauth_t *auth, char *buffer,
+                       size_t buffer_size) /* {{{ */
+{
+  int status;
+
+  if (auth == NULL)
+    return EINVAL;
+
+  status = renew_token(auth);
+  if (status != 0)
+    return status;
+  assert(auth->token != NULL);
+
+  sstrncpy(buffer, auth->token, buffer_size);
+  return 0;
+} /* }}} int oauth_access_token */
diff --git a/src/utils_oauth.h b/src/utils_oauth.h
new file mode 100644 (file)
index 0000000..b93c87b
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * collectd - src/utils_oauth.h
+ * ISC license
+ *
+ * Copyright (C) 2017  Florian Forster
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#ifndef UTILS_OAUTH_H
+#define UTILS_OAUTH_H
+
+#include "collectd.h"
+#include "utils_time.h"
+
+#ifndef GOOGLE_OAUTH_URL
+#define GOOGLE_OAUTH_URL "https://www.googleapis.com/oauth2/v3/token"
+#endif
+
+struct oauth_s;
+typedef struct oauth_s oauth_t;
+
+int oauth_parse_json_token(char const *json, char *out_access_token,
+                           size_t access_token_size, cdtime_t *expires_in);
+
+typedef struct {
+  char *project_id;
+  oauth_t *oauth;
+} oauth_google_t;
+
+/* oauth_create_google_json creates an OAuth object from JSON encoded
+ * credentials. */
+oauth_google_t oauth_create_google_json(char const *json, char const *scope);
+
+/* oauth_create_google_file reads path, which contains JSON encoded service
+ * account credentials, and returns an OAuth object. */
+oauth_google_t oauth_create_google_file(char const *path, char const *scope);
+
+/* oauth_create_google_default looks for service account credentials in a couple
+ * of well-known places and returns an OAuth object if found. The well known
+ * locations are:
+ *
+ *   - ${GOOGLE_APPLICATION_CREDENTIALS}
+ *   - ${HOME}/.config/gcloud/application_default_credentials.json
+ */
+oauth_google_t oauth_create_google_default(char const *scope);
+
+/* oauth_destroy frees all resources associated with an OAuth object. */
+void oauth_destroy(oauth_t *auth);
+
+int oauth_access_token(oauth_t *auth, char *buffer, size_t buffer_size);
+
+#endif
diff --git a/src/utils_oauth_test.c b/src/utils_oauth_test.c
new file mode 100644 (file)
index 0000000..791564f
--- /dev/null
@@ -0,0 +1,149 @@
+/**
+ * collectd - src/tests/utils_oauth_test.c
+ * Copyright (C) 2015  Google Inc.
+ *
+ * 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 Forster <octo at google.com>
+ **/
+
+#include "testing.h"
+#include "utils_oauth.h"
+
+struct {
+  char *json;
+  int status;
+  char *access_token;
+  cdtime_t expires_in;
+} cases[] = {
+    {
+        "{\"access_token\":\"MaeC6kaePhie1ree\",\"expires_in\":3600}",
+        /* status = */ 0, "MaeC6kaePhie1ree", TIME_T_TO_CDTIME_T_STATIC(3600),
+    },
+    {
+        "{\"token_type\":\"Bearer\",\"expires_in\":1800,\"access_token\":"
+        "\"aeThiebee2gushuY\"}",
+        /* status = */ 0, "aeThiebee2gushuY", TIME_T_TO_CDTIME_T_STATIC(1800),
+    },
+    {
+        "{\"ignored_key\":\"uaph5aewaeghi1Ge\",\"expires_in\":3600}",
+        /* status = */ -1, NULL, 0,
+    },
+    {
+        /* expires_in missing */
+        "{\"access_token\":\"shaephohbie9Ahch\"}",
+        /* status = */ -1, NULL, 0,
+    },
+};
+
+DEF_TEST(simple) /* {{{ */
+{
+  size_t i;
+  _Bool success = 1;
+
+  for (i = 0; i < (sizeof(cases) / sizeof(cases[0])); i++) {
+    char buffer[1024];
+    cdtime_t expires_in;
+
+    EXPECT_EQ_INT(cases[i].status,
+                  oauth_parse_json_token(cases[i].json, buffer, sizeof(buffer),
+                                         &expires_in));
+    if (cases[i].status != 0)
+      continue;
+
+    EXPECT_EQ_STR(cases[i].access_token, buffer);
+    EXPECT_EQ_UINT64(cases[i].expires_in, expires_in);
+  }
+
+  return success ? 0 : -1;
+} /* }}} simple */
+
+DEF_TEST(oauth_create_google_json) {
+  char const *in =
+      "{\"type\": \"service_account\","
+      "\"project_id\":\"collectd.org:unit-test\","
+      "\"private_key_id\": \"ed7b4eb6c1b61a7bedab5bcafff374f7fc820698\","
+      "\"private_key\":\"-----BEGIN PRIVATE KEY-----\\n"
+      "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDNvS71Lr2WIEqx\\n"
+      "U766iJGORVVib0FnHhOf/0FEI4Hw+tF11vP3LZj0AyQFIi/h2l2EDXOr43C6Gt+K\\n"
+      "0stsyaWvRNzeQa+dUFY5A/ZEtdvYVPq7KudML5Hs9DNmWFlM/iIfQyIUJ+vHv7fe\\n"
+      "pJGgu4ZgSkNWehmWj3qiRzIvYxKvDIQizqPZNlTh+33KQcT2x+ErkuB3snQu8hSK\\n"
+      "HAg2sCvORqKGOvN9F4bAqXt5T0NVjGy4YXeuif1p/Np/GH6Ys1p+etgGwvIimXIv\\n"
+      "jFL9K/ZtrTOcFdy4R5bwrj2piCZa2T5H6fupVp2tVgIuS53r2fEaBMLD97oAvwZ3\\n"
+      "9XPxG1NLAgMBAAECggEACgHroKcrN1FkdgyzSIKFG1evCBCOV17kqHyI5wYXzNTT\\n"
+      "zyNrZDjBFGQkt+U0/AucTznnnahSCZNuD+QiBgLRqYgJevwp99Z6YzVDS438Xsuq\\n"
+      "Ezmf3O+sGEu78Pys11cTP38LT3yuS4iSqo9Jus5JrTG05dDJoYO4J4rxW3xlDRj8\\n"
+      "lQUimXI+S9skaSusf0oErDrjuQG9dxmhnGcSEX+rIe9G0UygTNuI0KKGJ8jmnPz5\\n"
+      "OS+sM8qrKcnjrvENFWKLb11HlliHkh6dILoO5rvf5DR+XGKM7BFAsdWg6oI7SFGh\\n"
+      "S6zGZ0jUR7QAugrjbTlDOCnAuZ+Mbc/4yHZ3u5PlcQKBgQDuvH1ds1YmmbOllOK5\\n"
+      "JtkdjCUUyH1bgkMrmcg/KkRARPRHQvfAioZsC6d0fa6jq0kTW/3Zu14IsVXgM8xK\\n"
+      "fuNSp8LdY+NCtJnfvdLaChgAwZaQLX4qgV0qYw8iLv5ifa4ZY0qaZioJCzkv57y1\\n"
+      "KkavYvITboO7aUSa441Zko9c+wKBgQDcndg0QpWH6JMz/FkCf/KDyW/cUODfKXhP\\n"
+      "5p9eTcVlfDL2sAb2RzVhvKZcuWXVwnfaDP0oBj2/SBLGx0idUb+VHdM/IGiLroyK\\n"
+      "pAHpNM//dowiGL1qPPOLXrzF/vn+w4t2Dqggfcqu52SzRiyaxUtSMnNyyyU19cO+\\n"
+      "pb7wAS5x8QKBgCW7WL0UeQtEw6Xp8CN/RlVrLvkn7tglsGQVvBZvobXesBULOokN\\n"
+      "28z70o2Qx6dKjRQoN+jPuj75eC8lQKaNg3Qu25eOD/8c+CzqnYakjcKg1iEXb5dc\\n"
+      "NtNaMKwgbUg3wOp2TPY2K3KeeX1ezO59LgrOQqBbmSpnqtYoHNEJXus9AoGAWl/y\\n"
+      "9J2eIdm9i5tBX0vIrgHz5/3d0K1tUtX3zSrwxT0Wp4W+pF7RWGNuhyePtvx+Gn4d\\n"
+      "qqq72sMMpg93CLM3Vz+rjP2atjXf7t92xPDUkCMhDsqxtXaYkixSCo4EHUA/vjIM\\n"
+      "35qIUBQMZYBGv3Q5AcgXERx09uDhuhSt3iWtwBECgYAHFnCh8fKsJbQrVN10tU/h\\n"
+      "ofVx0KZkUpBz8eNQPuxt4aY+LyWsKVKtnduw2WdumuOY66cUN1lsi8Bz/cq1dhPt\\n"
+      "Oc2S7pqjbu2Q1Oqx+/yr6jqsvKaSxHmcpbWQBsGn6UaWZgYZcAtQBcqDAp7pylwj\\n"
+      "tejRh0NB8d81H5Dli1Qfzw==\\n"
+      "-----END PRIVATE KEY-----\\n\","
+      "\"client_email\":\"example-sacct@unit-test.iam.gserviceaccount.com\", "
+      "\"client_id\": \"109958449193027604084\","
+      "\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\","
+      "\"token_uri\":\"https://accounts.google.com/o/oauth2/token\","
+      "\"auth_provider_x509_cert_url\":"
+      "\"https://www.googleapis.com/oauth2/v1/certs\","
+      "\"client_x509_cert_url\":\"https://www.googleapis.com/robot/v1/"
+      "metadata/x509/example-sacct%40ssc-serv-dev.iam.gserviceaccount.com\"}";
+
+  oauth_google_t ret =
+      oauth_create_google_json(in, "https://collectd.org/example.scope");
+
+  EXPECT_EQ_STR("collectd.org:unit-test", ret.project_id);
+
+  CHECK_NOT_NULL(ret.oauth);
+  struct {
+    char *url;
+    char *iss;
+    char *aud;
+    char *scope;
+  } *obj = (void *)ret.oauth;
+
+  EXPECT_EQ_STR("https://accounts.google.com/o/oauth2/token", obj->url);
+  EXPECT_EQ_STR("example-sacct@unit-test.iam.gserviceaccount.com", obj->iss);
+  EXPECT_EQ_STR("https://collectd.org/example.scope", obj->scope);
+
+  free(ret.project_id);
+  oauth_destroy(ret.oauth);
+
+  return 0;
+}
+
+int main(int argc, char **argv) /* {{{ */
+{
+  RUN_TEST(simple);
+  RUN_TEST(oauth_create_google_json);
+
+  END_TEST;
+} /* }}} int main */
index ce7838d..8f92cfd 100644 (file)
@@ -96,7 +96,7 @@ static srrd_create_args_t *srrd_create_args_create(const char *filename,
 
   args = calloc(1, sizeof(*args));
   if (args == NULL) {
-    ERROR("srrd_create_args_create: calloc failed.");
+    P_ERROR("srrd_create_args_create: calloc failed.");
     return NULL;
   }
   args->filename = NULL;
@@ -106,14 +106,14 @@ static srrd_create_args_t *srrd_create_args_create(const char *filename,
 
   args->filename = strdup(filename);
   if (args->filename == NULL) {
-    ERROR("srrd_create_args_create: strdup failed.");
+    P_ERROR("srrd_create_args_create: strdup failed.");
     srrd_create_args_destroy(args);
     return NULL;
   }
 
   args->argv = calloc((size_t)(argc + 1), sizeof(*args->argv));
   if (args->argv == NULL) {
-    ERROR("srrd_create_args_create: calloc failed.");
+    P_ERROR("srrd_create_args_create: calloc failed.");
     srrd_create_args_destroy(args);
     return NULL;
   }
@@ -121,7 +121,7 @@ static srrd_create_args_t *srrd_create_args_create(const char *filename,
   for (args->argc = 0; args->argc < argc; args->argc++) {
     args->argv[args->argc] = strdup(argv[args->argc]);
     if (args->argv[args->argc] == NULL) {
-      ERROR("srrd_create_args_create: strdup failed.");
+      P_ERROR("srrd_create_args_create: strdup failed.");
       srrd_create_args_destroy(args);
       return NULL;
     }
@@ -212,7 +212,7 @@ static int rra_get(char ***ret, const value_list_t *vl, /* {{{ */
                         rra_types[j], cfg->xff, cdp_len, cdp_num);
 
       if ((status < 0) || ((size_t)status >= sizeof(buffer))) {
-        ERROR("rra_get: Buffer would have been truncated.");
+        P_ERROR("rra_get: Buffer would have been truncated.");
         continue;
       }
 
@@ -251,7 +251,7 @@ static int ds_get(char ***ret, /* {{{ */
 
   ds_def = calloc(ds->ds_num, sizeof(*ds_def));
   if (ds_def == NULL) {
-    ERROR("rrdtool plugin: calloc failed: %s", STRERRNO);
+    P_ERROR("ds_get: calloc failed: %s", STRERRNO);
     return -1;
   }
 
@@ -271,7 +271,7 @@ static int ds_get(char ***ret, /* {{{ */
     else if (d->type == DS_TYPE_ABSOLUTE)
       type = "ABSOLUTE";
     else {
-      ERROR("rrdtool plugin: Unknown DS type: %i", d->type);
+      P_ERROR("ds_get: Unknown DS type: %i", d->type);
       break;
     }
 
@@ -335,8 +335,8 @@ static int srrd_create(const char *filename, /* {{{ */
   status = rrd_create_r(filename_copy, pdp_step, last_up, argc, (void *)argv);
 
   if (status != 0) {
-    WARNING("rrdtool plugin: rrd_create_r (%s) failed: %s", filename,
-            rrd_get_error());
+    P_WARNING("srrd_create: rrd_create_r (%s) failed: %s", filename,
+              rrd_get_error());
   }
 
   sfree(filename_copy);
@@ -360,7 +360,7 @@ static int srrd_create(const char *filename, /* {{{ */
   new_argc = 6 + argc;
   new_argv = malloc((new_argc + 1) * sizeof(*new_argv));
   if (new_argv == NULL) {
-    ERROR("rrdtool plugin: malloc failed.");
+    P_ERROR("srrd_create: malloc failed.");
     return -1;
   }
 
@@ -388,8 +388,8 @@ static int srrd_create(const char *filename, /* {{{ */
   pthread_mutex_unlock(&librrd_lock);
 
   if (status != 0) {
-    WARNING("rrdtool plugin: rrd_create (%s) failed: %s", filename,
-            rrd_get_error());
+    P_WARNING("srrd_create: rrd_create (%s) failed: %s", filename,
+              rrd_get_error());
   }
 
   sfree(new_argv);
@@ -487,10 +487,11 @@ static void *srrd_create_thread(void *targs) /* {{{ */
   status = lock_file(args->filename);
   if (status != 0) {
     if (status == EEXIST)
-      NOTICE("srrd_create_thread: File \"%s\" is already being created.",
-             args->filename);
+      P_NOTICE("srrd_create_thread: File \"%s\" is already being created.",
+               args->filename);
     else
-      ERROR("srrd_create_thread: Unable to lock file \"%s\".", args->filename);
+      P_ERROR("srrd_create_thread: Unable to lock file \"%s\".",
+              args->filename);
     srrd_create_args_destroy(args);
     return 0;
   }
@@ -500,8 +501,8 @@ static void *srrd_create_thread(void *targs) /* {{{ */
   status = srrd_create(tmpfile, args->pdp_step, args->last_up, args->argc,
                        (void *)args->argv);
   if (status != 0) {
-    WARNING("srrd_create_thread: srrd_create (%s) returned status %i.",
-            args->filename, status);
+    P_WARNING("srrd_create_thread: srrd_create (%s) returned status %i.",
+              args->filename, status);
     unlink(tmpfile);
     unlock_file(args->filename);
     srrd_create_args_destroy(args);
@@ -510,8 +511,8 @@ static void *srrd_create_thread(void *targs) /* {{{ */
 
   status = rename(tmpfile, args->filename);
   if (status != 0) {
-    ERROR("srrd_create_thread: rename (\"%s\", \"%s\") failed: %s", tmpfile,
-          args->filename, STRERRNO);
+    P_ERROR("srrd_create_thread: rename (\"%s\", \"%s\") failed: %s", tmpfile,
+            args->filename, STRERRNO);
     unlink(tmpfile);
     unlock_file(args->filename);
     srrd_create_args_destroy(args);
@@ -556,7 +557,7 @@ static int srrd_create_async(const char *filename, /* {{{ */
 
   status = pthread_create(&thread, &attr, srrd_create_thread, args);
   if (status != 0) {
-    ERROR("srrd_create_async: pthread_create failed: %s", STRERROR(status));
+    P_ERROR("srrd_create_async: pthread_create failed: %s", STRERROR(status));
     pthread_attr_destroy(&attr);
     srrd_create_args_destroy(args);
     return status;
@@ -587,12 +588,12 @@ int cu_rrd_create_file(const char *filename, /* {{{ */
     return -1;
 
   if ((rra_num = rra_get(&rra_def, vl, cfg)) < 1) {
-    ERROR("cu_rrd_create_file failed: Could not calculate RRAs");
+    P_ERROR("cu_rrd_create_file failed: Could not calculate RRAs");
     return -1;
   }
 
   if ((ds_num = ds_get(&ds_def, ds, vl, cfg)) < 1) {
-    ERROR("cu_rrd_create_file failed: Could not calculate DSes");
+    P_ERROR("cu_rrd_create_file failed: Could not calculate DSes");
     rra_free(rra_num, rra_def);
     return -1;
   }
@@ -600,7 +601,7 @@ int cu_rrd_create_file(const char *filename, /* {{{ */
   argc = ds_num + rra_num;
 
   if ((argv = malloc(sizeof(*argv) * (argc + 1))) == NULL) {
-    ERROR("cu_rrd_create_file failed: %s", STRERRNO);
+    P_ERROR("cu_rrd_create_file failed: %s", STRERRNO);
     rra_free(rra_num, rra_def);
     ds_free(ds_num, ds_def);
     return -1;
@@ -624,25 +625,25 @@ int cu_rrd_create_file(const char *filename, /* {{{ */
     status = srrd_create_async(filename, stepsize, last_up, argc,
                                (const char **)argv);
     if (status != 0)
-      WARNING("cu_rrd_create_file: srrd_create_async (%s) "
-              "returned status %i.",
-              filename, status);
+      P_WARNING("cu_rrd_create_file: srrd_create_async (%s) "
+                "returned status %i.",
+                filename, status);
   } else /* synchronous */
   {
     status = lock_file(filename);
     if (status != 0) {
       if (status == EEXIST)
-        NOTICE("cu_rrd_create_file: File \"%s\" is already being created.",
-               filename);
+        P_NOTICE("cu_rrd_create_file: File \"%s\" is already being created.",
+                 filename);
       else
-        ERROR("cu_rrd_create_file: Unable to lock file \"%s\".", filename);
+        P_ERROR("cu_rrd_create_file: Unable to lock file \"%s\".", filename);
     } else {
       status =
           srrd_create(filename, stepsize, last_up, argc, (const char **)argv);
 
       if (status != 0) {
-        WARNING("cu_rrd_create_file: srrd_create (%s) returned status %i.",
-                filename, status);
+        P_WARNING("cu_rrd_create_file: srrd_create (%s) returned status %i.",
+                  filename, status);
       } else {
         DEBUG("cu_rrd_create_file: Successfully created RRD file \"%s\".",
               filename);
index b5dc5af..55a3287 100644 (file)
@@ -43,13 +43,11 @@ struct cu_tail_s {
 
 static int cu_tail_reopen(cu_tail_t *obj) {
   int seek_end = 0;
-  FILE *fh;
   struct stat stat_buf = {0};
-  int status;
 
-  status = stat(obj->file, &stat_buf);
+  int status = stat(obj->file, &stat_buf);
   if (status != 0) {
-    ERROR("utils_tail: stat (%s) failed: %s", obj->file, STRERRNO);
+    P_ERROR("utils_tail: stat (%s) failed: %s", obj->file, STRERRNO);
     return -1;
   }
 
@@ -57,10 +55,10 @@ static int cu_tail_reopen(cu_tail_t *obj) {
   if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino)) {
     /* Seek to the beginning if file was truncated */
     if (stat_buf.st_size < obj->stat.st_size) {
-      INFO("utils_tail: File `%s' was truncated.", obj->file);
+      P_INFO("utils_tail: File `%s' was truncated.", obj->file);
       status = fseek(obj->fh, 0, SEEK_SET);
       if (status != 0) {
-        ERROR("utils_tail: fseek (%s) failed: %s", obj->file, STRERRNO);
+        P_ERROR("utils_tail: fseek (%s) failed: %s", obj->file, STRERRNO);
         fclose(obj->fh);
         obj->fh = NULL;
         return -1;
@@ -75,16 +73,16 @@ static int cu_tail_reopen(cu_tail_t *obj) {
   if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
     seek_end = 1;
 
-  fh = fopen(obj->file, "r");
+  FILE *fh = fopen(obj->file, "r");
   if (fh == NULL) {
-    ERROR("utils_tail: fopen (%s) failed: %s", obj->file, STRERRNO);
+    P_ERROR("utils_tail: fopen (%s) failed: %s", obj->file, STRERRNO);
     return -1;
   }
 
   if (seek_end != 0) {
     status = fseek(fh, 0, SEEK_END);
     if (status != 0) {
-      ERROR("utils_tail: fseek (%s) failed: %s", obj->file, STRERRNO);
+      P_ERROR("utils_tail: fseek (%s) failed: %s", obj->file, STRERRNO);
       fclose(fh);
       return -1;
     }
index 5134a6e..ccab5ac 100644 (file)
@@ -43,7 +43,6 @@ struct cu_tail_match_simple_s {
   char plugin_instance[DATA_MAX_NAME_LEN];
   char type[DATA_MAX_NAME_LEN];
   char type_instance[DATA_MAX_NAME_LEN];
-  cdtime_t interval;
   latency_config_t latency_config;
 };
 typedef struct cu_tail_match_simple_s cu_tail_match_simple_t;
@@ -57,10 +56,7 @@ struct cu_tail_match_match_s {
 typedef struct cu_tail_match_match_s cu_tail_match_match_t;
 
 struct cu_tail_match_s {
-  int flags;
   cu_tail_t *tail;
-
-  cdtime_t interval;
   cu_tail_match_match_t *matches;
   size_t matches_num;
 };
@@ -92,7 +88,6 @@ static int simple_submit_match(cu_match_t *match, void *user_data) {
   sstrncpy(vl.type, data->type, sizeof(vl.type));
   sstrncpy(vl.type_instance, data->type_instance, sizeof(vl.type_instance));
 
-  vl.interval = data->interval;
   plugin_dispatch_values(&vl);
 
   match_value_reset(match_value);
@@ -111,7 +106,6 @@ static int latency_submit_match(cu_match_t *match, void *user_data) {
   sstrncpy(vl.plugin, data->plugin, sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, data->plugin_instance,
            sizeof(vl.plugin_instance));
-  vl.interval = data->interval;
   vl.time = cdtime();
 
   /* Submit percentiles */
@@ -248,8 +242,6 @@ int tail_match_add_match(cu_tail_match_t *obj, cu_match_t *match,
   obj->matches = temp;
   obj->matches_num++;
 
-  DEBUG("tail_match_add_match interval %lf",
-        CDTIME_T_TO_DOUBLE(((cu_tail_match_simple_t *)user_data)->interval));
   temp = obj->matches + (obj->matches_num - 1);
 
   temp->match = match;
@@ -264,8 +256,7 @@ int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
                                 const char *excluderegex, int ds_type,
                                 const char *plugin, const char *plugin_instance,
                                 const char *type, const char *type_instance,
-                                const latency_config_t latency_cfg,
-                                const cdtime_t interval) {
+                                const latency_config_t latency_cfg) {
   cu_match_t *match;
   cu_tail_match_simple_t *user_data;
   int status;
@@ -290,8 +281,6 @@ int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
     sstrncpy(user_data->type_instance, type_instance,
              sizeof(user_data->type_instance));
 
-  user_data->interval = interval;
-
   if ((ds_type & UTILS_MATCH_DS_TYPE_GAUGE) &&
       (ds_type & UTILS_MATCH_CF_GAUGE_DIST)) {
     status = latency_config_copy(&user_data->latency_config, latency_cfg);
index 03b70e9..2d4c253 100644 (file)
@@ -80,9 +80,8 @@ void tail_match_destroy(cu_tail_match_t *obj);
  *   When `tail_match_destroy' is called the `user_data' pointer is freed using
  *   the `free_user_data' callback - if it is not NULL.
  *   When using this interface the `tail_match' module doesn't dispatch any
- * values
- *   itself - all that has to happen in either the match-callbacks or the
- *   submit_match callback.
+ *   values itself - all that has to happen in either the match-callbacks or
+ *   the submit_match callback.
  *
  * RETURN VALUE
  *   Zero upon success, non-zero otherwise.
@@ -98,14 +97,13 @@ int tail_match_add_match(cu_tail_match_t *obj, cu_match_t *match,
  *  tail_match_add_match_simple
  *
  * DESCRIPTION
- *  A simplified version of `tail_match_add_match'. The regular expressen
- * `regex'
- *  must match a number, which is then dispatched according to `ds_type'. See
- *  the `match_create_simple' function in utils_match.h for a description how
- *  this flag effects calculation of a new value.
+ *  A simplified version of `tail_match_add_match'. The regular expression
+ *  `regex' must match a number, which is then dispatched according to
+ * `ds_type'.
+ *  See the `match_create_simple' function in utils_match.h for a description
+ *  how this flag effects calculation of a new value.
  *  The values gathered are dispatched by the tail_match module in this case.
- * The
- *  passed `plugin', `plugin_instance', `type', and `type_instance' are
+ *  The passed `plugin', `plugin_instance', `type', and `type_instance' are
  *  directly used when submitting these values.
  *  With excluderegex it is possible to exlude lines from the match.
  *  The `latency_cfg' specifies configuration for submitting latency.
@@ -117,8 +115,7 @@ int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
                                 const char *excluderegex, int ds_type,
                                 const char *plugin, const char *plugin_instance,
                                 const char *type, const char *type_instance,
-                                const latency_config_t latency_cfg,
-                                const cdtime_t interval);
+                                const latency_config_t latency_cfg);
 
 /*
  * NAME
@@ -130,8 +127,7 @@ int tail_match_add_match_simple(cu_tail_match_t *obj, const char *regex,
  *   added `utils_match' objects.
  *   After all lines have been read and processed, the submit_match callback is
  *   called or, in case of tail_match_add_match_simple, the data is dispatched
- * to
- *   the daemon directly.
+ *   to the daemon directly.
  *
  * RETURN VALUE
  *   Zero on success, nonzero on failure.
index 1ce376c..c52a8a7 100644 (file)
@@ -1024,7 +1024,8 @@ static void domain_state_submit_notif(virDomainPtr dom, int state, int reason) {
   char msg[DATA_MAX_NAME_LEN];
   const char *state_str = domain_states[state];
 #ifdef HAVE_DOM_REASON
-  if ((reason < 0) || ((size_t)reason >= STATIC_ARRAY_SIZE(domain_reasons[0]))) {
+  if ((reason < 0) ||
+      ((size_t)reason >= STATIC_ARRAY_SIZE(domain_reasons[0]))) {
     ERROR(PLUGIN_NAME ": Array index out of bounds: reason=%d", reason);
     return;
   }
@@ -1584,7 +1585,7 @@ static int get_block_stats(struct block_device *block_dev) {
 
 #define NM_ADD_STR_ITEMS(_items, _size)                                        \
   do {                                                                         \
-    for (size_t _i = 0; _i < _size; ++_i) {                                       \
+    for (size_t _i = 0; _i < _size; ++_i) {                                    \
       DEBUG(PLUGIN_NAME                                                        \
             " plugin: Adding notification metadata name=%s value=%s",          \
             _items[_i].name, _items[_i].value);                                \
@@ -1824,7 +1825,7 @@ static int get_if_dev_stats(struct interface_device *if_dev) {
   return 0;
 }
 
-static int domain_lifecycle_event_cb(__attribute__((unused)) virConnectPtr conn,
+static int domain_lifecycle_event_cb(__attribute__((unused)) virConnectPtr con_,
                                      virDomainPtr dom, int event, int detail,
                                      __attribute__((unused)) void *opaque) {
   int domain_state = map_domain_event_to_state(event);
@@ -2100,7 +2101,7 @@ static int lv_read(user_data_t *ud) {
       ERROR(PLUGIN_NAME
             " failed to get stats for block device (%s) in domain %s",
             state->block_devices[i].path,
-            virDomainGetName(state->domains[i].ptr));
+            virDomainGetName(state->block_devices[i].dom));
   }
 
   /* Get interface stats for each domain. */
index 2e597f3..4208d36 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/wireless.c
- * Copyright (C) 2006,2007  Florian octo Forster
+ * Copyright (C) 2006-2018  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"),
 #include "common.h"
 #include "plugin.h"
 
-#if !KERNEL_LINUX
+#if KERNEL_LINUX
+#include <linux/if.h>
+#include <linux/wireless.h>
+#include <sys/ioctl.h>
+#else
 #error "No applicable input method."
 #endif
 
@@ -90,7 +94,14 @@ static int wireless_read(void) {
 
   /* there are a variety of names for the wireless device */
   if ((fh = fopen(WIRELESS_PROC_FILE, "r")) == NULL) {
-    WARNING("wireless: fopen: %s", STRERRNO);
+    ERROR("wireless plugin: fopen: %s", STRERRNO);
+    return -1;
+  }
+
+  int sock = socket(AF_INET, SOCK_DGRAM, 0);
+  if (sock == -1) {
+    ERROR("wireless plugin: socket: %s", STRERRNO);
+    fclose(fh);
     return -1;
   }
 
@@ -142,9 +153,20 @@ static int wireless_read(void) {
     wireless_submit(device, "signal_power", power);
     wireless_submit(device, "signal_noise", noise);
 
+    struct iwreq req = {
+        .ifr_ifrn.ifrn_name = {0},
+    };
+    sstrncpy(req.ifr_ifrn.ifrn_name, device, sizeof(req.ifr_ifrn.ifrn_name));
+    if (ioctl(sock, SIOCGIWRATE, &req) == -1) {
+      WARNING("wireless plugin: ioctl(SIOCGIWRATE): %s", STRERRNO);
+    } else {
+      wireless_submit(device, "bitrate", (double)req.u.bitrate.value);
+    }
+
     devices_found++;
   }
 
+  close(sock);
   fclose(fh);
 
   /* If no wireless devices are present return an error, so the plugin
index 099c62b..7624e24 100644 (file)
@@ -38,6 +38,7 @@
  *     Protocol "udp"
  *     LogSendErrors true
  *     Prefix "collectd"
+ *     UseTags true
  *   </Carbon>
  * </Plugin>
  */
@@ -518,6 +519,8 @@ static int wg_config_node(oconfig_item_t *ci) {
       cf_util_get_flag(child, &cb->format_flags, GRAPHITE_PRESERVE_SEPARATOR);
     else if (strcasecmp("DropDuplicateFields", child->key) == 0)
       cf_util_get_flag(child, &cb->format_flags, GRAPHITE_DROP_DUPE_FIELDS);
+    else if (strcasecmp("UseTags", child->key) == 0)
+      cf_util_get_flag(child, &cb->format_flags, GRAPHITE_USE_TAGS);
     else if (strcasecmp("EscapeCharacter", child->key) == 0)
       config_set_char(&cb->escape_char, child);
     else {
index c120d15..04e67b9 100644 (file)
@@ -383,6 +383,10 @@ static void kafka_config_topic(rd_kafka_conf_t *conf,
       status = cf_util_get_flag(child, &tctx->graphite_flags,
                                 GRAPHITE_PRESERVE_SEPARATOR);
 
+    } else if (strcasecmp("GraphiteUseTags", child->key) == 0) {
+      status =
+          cf_util_get_flag(child, &tctx->graphite_flags, GRAPHITE_USE_TAGS);
+
     } else if (strcasecmp("GraphitePrefix", child->key) == 0) {
       status = cf_util_get_string(child, &tctx->prefix);
     } else if (strcasecmp("GraphitePostfix", child->key) == 0) {
index 3b22922..6f9b049 100644 (file)
@@ -802,8 +802,13 @@ static struct MHD_Daemon *prom_start_daemon() {
     return NULL;
   }
 
+  unsigned int flags = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG;
+#if MHD_VERSION >= 0x00095300
+  flags |= MHD_USE_INTERNAL_POLLING_THREAD;
+#endif
+
   struct MHD_Daemon *d = MHD_start_daemon(
-      MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, httpd_port,
+      flags, httpd_port,
       /* MHD_AcceptPolicyCallback = */ NULL,
       /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler, NULL,
       MHD_OPTION_LISTEN_SOCKET, fd, MHD_OPTION_EXTERNAL_LOGGER, prom_logger,
index c17654b..72cb594 100644 (file)
@@ -184,8 +184,8 @@ static int wr_config_node(oconfig_item_t *ci) /* {{{ */
     return ENOMEM;
   node->host = NULL;
   node->port = 0;
-  node->timeout.tv_sec = 0;
-  node->timeout.tv_usec = 1000;
+  node->timeout.tv_sec = 1;
+  node->timeout.tv_usec = 0;
   node->conn = NULL;
   node->prefix = NULL;
   node->database = 0;
@@ -213,8 +213,11 @@ static int wr_config_node(oconfig_item_t *ci) /* {{{ */
       }
     } else if (strcasecmp("Timeout", child->key) == 0) {
       status = cf_util_get_int(child, &timeout);
-      if (status == 0)
-        node->timeout.tv_usec = timeout;
+      if (status == 0) {
+        node->timeout.tv_usec = timeout * 1000;
+        node->timeout.tv_sec = node->timeout.tv_usec / 1000000L;
+        node->timeout.tv_usec %= 1000000L;
+      }
     } else if (strcasecmp("Prefix", child->key) == 0) {
       status = cf_util_get_string(child, &node->prefix);
     } else if (strcasecmp("Database", child->key) == 0) {
index 1578e1c..b35d10e 100644 (file)
@@ -699,21 +699,13 @@ static int wrr_config_node(oconfig_item_t *ci) /* {{{ */
     } else if (strcasecmp("Port", child->key) == 0) {
       host->port = cf_util_get_port_number(child);
       if (host->port == -1) {
-        ERROR("write_riemann plugin: Invalid argument "
-              "configured for the \"Port\" "
-              "option.");
         break;
       }
     } else if (strcasecmp("Protocol", child->key) == 0) {
       char tmp[16];
       status = cf_util_get_string_buffer(child, tmp, sizeof(tmp));
-      if (status != 0) {
-        ERROR("write_riemann plugin: cf_util_get_"
-              "string_buffer failed with "
-              "status %i.",
-              status);
+      if (status != 0)
         break;
-      }
 
       if (strcasecmp("UDP", tmp) == 0)
         host->client_type = RIEMANN_CLIENT_UDP;
@@ -729,31 +721,16 @@ static int wrr_config_node(oconfig_item_t *ci) /* {{{ */
                 tmp);
     } else if (strcasecmp("TLSCAFile", child->key) == 0) {
       status = cf_util_get_string(child, &host->tls_ca_file);
-      if (status != 0) {
-        ERROR("write_riemann plugin: cf_util_get_"
-              "string_buffer failed with "
-              "status %i.",
-              status);
+      if (status != 0)
         break;
-      }
     } else if (strcasecmp("TLSCertFile", child->key) == 0) {
       status = cf_util_get_string(child, &host->tls_cert_file);
-      if (status != 0) {
-        ERROR("write_riemann plugin: cf_util_get_"
-              "string_buffer failed with "
-              "status %i.",
-              status);
+      if (status != 0)
         break;
-      }
     } else if (strcasecmp("TLSKeyFile", child->key) == 0) {
       status = cf_util_get_string(child, &host->tls_key_file);
-      if (status != 0) {
-        ERROR("write_riemann plugin: cf_util_get_"
-              "string_buffer failed with "
-              "status %i.",
-              status);
+      if (status != 0)
         break;
-      }
     } else if (strcasecmp("StoreRates", child->key) == 0) {
       status = cf_util_get_boolean(child, &host->store_rates);
       if (status != 0)
index 4c9f42b..6ea8106 100644 (file)
@@ -1084,12 +1084,8 @@ static int sensu_config_node(oconfig_item_t *ci) /* {{{ */
         break;
     } else if (strcasecmp("Port", child->key) == 0) {
       status = cf_util_get_service(child, &host->service);
-      if (status != 0) {
-        ERROR("write_sensu plugin: Invalid argument "
-              "configured for the \"Port\" "
-              "option.");
+      if (status != 0)
         break;
-      }
     } else if (strcasecmp("StoreRates", child->key) == 0) {
       status = cf_util_get_boolean(child, &host->store_rates);
       if (status != 0)
diff --git a/src/write_stackdriver.c b/src/write_stackdriver.c
new file mode 100644 (file)
index 0000000..a1341d9
--- /dev/null
@@ -0,0 +1,689 @@
+/**
+ * collectd - src/write_stackdriver.c
+ * ISC license
+ *
+ * Copyright (C) 2017  Florian Forster
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+
+#include "common.h"
+#include "configfile.h"
+#include "plugin.h"
+#include "utils_format_stackdriver.h"
+#include "utils_gce.h"
+#include "utils_oauth.h"
+
+#include <curl/curl.h>
+#include <pthread.h>
+#include <yajl/yajl_tree.h>
+
+/*
+ * Private variables
+ */
+#ifndef GCM_API_URL
+#define GCM_API_URL "https://monitoring.googleapis.com/v3"
+#endif
+
+#ifndef MONITORING_SCOPE
+#define MONITORING_SCOPE "https://www.googleapis.com/auth/monitoring"
+#endif
+
+struct wg_callback_s {
+  /* config */
+  char *email;
+  char *project;
+  char *url;
+  sd_resource_t *resource;
+
+  /* runtime */
+  oauth_t *auth;
+  sd_output_t *formatter;
+  CURL *curl;
+  char curl_errbuf[CURL_ERROR_SIZE];
+  /* used by flush */
+  size_t timeseries_count;
+  cdtime_t send_buffer_init_time;
+
+  pthread_mutex_t lock;
+};
+typedef struct wg_callback_s wg_callback_t;
+
+struct wg_memory_s {
+  char *memory;
+  size_t size;
+};
+typedef struct wg_memory_s wg_memory_t;
+
+static size_t wg_write_memory_cb(void *contents, size_t size,
+                                 size_t nmemb, /* {{{ */
+                                 void *userp) {
+  size_t realsize = size * nmemb;
+  wg_memory_t *mem = (wg_memory_t *)userp;
+
+  if (0x7FFFFFF0 < mem->size || 0x7FFFFFF0 - mem->size < realsize) {
+    ERROR("integer overflow");
+    return 0;
+  }
+
+  mem->memory = (char *)realloc((void *)mem->memory, mem->size + realsize + 1);
+  if (mem->memory == NULL) {
+    /* out of memory! */
+    ERROR("wg_write_memory_cb: not enough memory (realloc returned NULL)");
+    return 0;
+  }
+
+  memcpy(&(mem->memory[mem->size]), contents, realsize);
+  mem->size += realsize;
+  mem->memory[mem->size] = 0;
+  return realsize;
+} /* }}} size_t wg_write_memory_cb */
+
+static char *wg_get_authorization_header(wg_callback_t *cb) { /* {{{ */
+  int status = 0;
+  char access_token[256];
+  char authorization_header[256];
+
+  assert((cb->auth != NULL) || gce_check());
+  if (cb->auth != NULL)
+    status = oauth_access_token(cb->auth, access_token, sizeof(access_token));
+  else
+    status = gce_access_token(cb->email, access_token, sizeof(access_token));
+  if (status != 0) {
+    ERROR("write_stackdriver plugin: Failed to get access token");
+    return NULL;
+  }
+
+  status = snprintf(authorization_header, sizeof(authorization_header),
+                    "Authorization: Bearer %s", access_token);
+  if ((status < 1) || ((size_t)status >= sizeof(authorization_header)))
+    return NULL;
+
+  return strdup(authorization_header);
+} /* }}} char *wg_get_authorization_header */
+
+typedef struct {
+  int code;
+  char *message;
+} api_error_t;
+
+static api_error_t *parse_api_error(char const *body) {
+  char errbuf[1024];
+  yajl_val root = yajl_tree_parse(body, errbuf, sizeof(errbuf));
+  if (root == NULL) {
+    ERROR("write_stackdriver plugin: yajl_tree_parse failed: %s", errbuf);
+    return NULL;
+  }
+
+  api_error_t *err = calloc(1, sizeof(*err));
+  if (err == NULL) {
+    ERROR("write_stackdriver plugin: calloc failed");
+    yajl_tree_free(root);
+    return NULL;
+  }
+
+  yajl_val code = yajl_tree_get(root, (char const *[]){"error", "code", NULL},
+                                yajl_t_number);
+  if (YAJL_IS_INTEGER(code)) {
+    err->code = YAJL_GET_INTEGER(code);
+  }
+
+  yajl_val message = yajl_tree_get(
+      root, (char const *[]){"error", "message", NULL}, yajl_t_string);
+  if (YAJL_IS_STRING(message)) {
+    char const *m = YAJL_GET_STRING(message);
+    if (m != NULL) {
+      err->message = strdup(m);
+    }
+  }
+
+  return err;
+}
+
+static char *api_error_string(api_error_t *err, char *buffer,
+                              size_t buffer_size) {
+  if (err == NULL) {
+    strncpy(buffer, "Unknown error (API error is NULL)", buffer_size);
+  } else if (err->message == NULL) {
+    snprintf(buffer, buffer_size, "API error %d", err->code);
+  } else {
+    snprintf(buffer, buffer_size, "API error %d: %s", err->code, err->message);
+  }
+
+  return buffer;
+}
+#define API_ERROR_STRING(err) api_error_string(err, (char[1024]){""}, 1024)
+
+// do_post does a HTTP POST request, assuming a JSON payload and using OAuth
+// authentication. Returns -1 on error and the HTTP status code otherwise.
+// ret_content, if not NULL, will contain the server's response.
+// If ret_content is provided and the server responds with a 4xx or 5xx error,
+// an appropriate message will be logged.
+static int do_post(wg_callback_t *cb, char const *url, void const *payload,
+                   wg_memory_t *ret_content) {
+  if (cb->curl == NULL) {
+    cb->curl = curl_easy_init();
+    if (cb->curl == NULL) {
+      ERROR("write_stackdriver plugin: curl_easy_init() failed");
+      return -1;
+    }
+
+    curl_easy_setopt(cb->curl, CURLOPT_ERRORBUFFER, cb->curl_errbuf);
+    curl_easy_setopt(cb->curl, CURLOPT_NOSIGNAL, 1L);
+  }
+
+  curl_easy_setopt(cb->curl, CURLOPT_POST, 1L);
+  curl_easy_setopt(cb->curl, CURLOPT_URL, url);
+
+  long timeout_ms = 2 * CDTIME_T_TO_MS(plugin_get_interval());
+  if (timeout_ms < 10000) {
+    timeout_ms = 10000;
+  }
+  curl_easy_setopt(cb->curl, CURLOPT_TIMEOUT_MS, timeout_ms);
+
+  /* header */
+  char *auth_header = wg_get_authorization_header(cb);
+  if (auth_header == NULL) {
+    ERROR("write_stackdriver plugin: getting access token failed with");
+    return -1;
+  }
+
+  struct curl_slist *headers =
+      curl_slist_append(NULL, "Content-Type: application/json");
+  headers = curl_slist_append(headers, auth_header);
+  curl_easy_setopt(cb->curl, CURLOPT_HTTPHEADER, headers);
+
+  curl_easy_setopt(cb->curl, CURLOPT_POSTFIELDS, payload);
+
+  curl_easy_setopt(cb->curl, CURLOPT_WRITEFUNCTION,
+                   ret_content ? wg_write_memory_cb : NULL);
+  curl_easy_setopt(cb->curl, CURLOPT_WRITEDATA, ret_content);
+
+  int status = curl_easy_perform(cb->curl);
+
+  /* clean up that has to happen in any case */
+  curl_slist_free_all(headers);
+  sfree(auth_header);
+  curl_easy_setopt(cb->curl, CURLOPT_HTTPHEADER, NULL);
+  curl_easy_setopt(cb->curl, CURLOPT_WRITEFUNCTION, NULL);
+  curl_easy_setopt(cb->curl, CURLOPT_WRITEDATA, NULL);
+
+  if (status != CURLE_OK) {
+    ERROR("write_stackdriver plugin: POST %s failed: %s", url, cb->curl_errbuf);
+    if (ret_content != NULL) {
+      sfree(ret_content->memory);
+      ret_content->size = 0;
+    }
+    return -1;
+  }
+
+  long http_code = 0;
+  curl_easy_getinfo(cb->curl, CURLINFO_RESPONSE_CODE, &http_code);
+
+  if (ret_content != NULL) {
+    if ((http_code >= 400) && (http_code < 500)) {
+      ERROR("write_stackdriver plugin: POST %s: %s", url,
+            API_ERROR_STRING(parse_api_error(ret_content->memory)));
+    } else if (http_code >= 500) {
+      WARNING("write_stackdriver plugin: POST %s: %s", url,
+              ret_content->memory);
+    }
+  }
+
+  return (int)http_code;
+} /* int do_post */
+
+static int wg_call_metricdescriptor_create(wg_callback_t *cb,
+                                           char const *payload) {
+  char url[1024];
+  snprintf(url, sizeof(url), "%s/projects/%s/metricDescriptors", cb->url,
+           cb->project);
+  wg_memory_t response = {0};
+
+  int status = do_post(cb, url, payload, &response);
+  if (status == -1) {
+    ERROR("write_stackdriver plugin: POST %s failed", url);
+    return -1;
+  }
+  sfree(response.memory);
+
+  if (status != 200) {
+    ERROR("write_stackdriver plugin: POST %s: unexpected response code: got "
+          "%d, want 200",
+          url, status);
+    return -1;
+  }
+  return 0;
+} /* int wg_call_metricdescriptor_create */
+
+static int wg_call_timeseries_write(wg_callback_t *cb, char const *payload) {
+  char url[1024];
+  snprintf(url, sizeof(url), "%s/projects/%s/timeSeries", cb->url, cb->project);
+  wg_memory_t response = {0};
+
+  int status = do_post(cb, url, payload, &response);
+  if (status == -1) {
+    ERROR("write_stackdriver plugin: POST %s failed", url);
+    return -1;
+  }
+  sfree(response.memory);
+
+  if (status != 200) {
+    ERROR("write_stackdriver plugin: POST %s: unexpected response code: got "
+          "%d, want 200",
+          url, status);
+    return -1;
+  }
+  return 0;
+} /* int wg_call_timeseries_write */
+
+static void wg_reset_buffer(wg_callback_t *cb) /* {{{ */
+{
+  cb->timeseries_count = 0;
+  cb->send_buffer_init_time = cdtime();
+} /* }}} wg_reset_buffer */
+
+static int wg_callback_init(wg_callback_t *cb) /* {{{ */
+{
+  if (cb->curl != NULL)
+    return 0;
+
+  cb->formatter = sd_output_create(cb->resource);
+  if (cb->formatter == NULL) {
+    ERROR("write_stackdriver plugin: sd_output_create failed.");
+    return -1;
+  }
+
+  cb->curl = curl_easy_init();
+  if (cb->curl == NULL) {
+    ERROR("write_stackdriver plugin: curl_easy_init failed.");
+    return -1;
+  }
+
+  curl_easy_setopt(cb->curl, CURLOPT_NOSIGNAL, 1L);
+  curl_easy_setopt(cb->curl, CURLOPT_USERAGENT,
+                   PACKAGE_NAME "/" PACKAGE_VERSION);
+  curl_easy_setopt(cb->curl, CURLOPT_ERRORBUFFER, cb->curl_errbuf);
+  wg_reset_buffer(cb);
+
+  return 0;
+} /* }}} int wg_callback_init */
+
+static int wg_flush_nolock(cdtime_t timeout, wg_callback_t *cb) /* {{{ */
+{
+  if (cb->timeseries_count == 0) {
+    cb->send_buffer_init_time = cdtime();
+    return 0;
+  }
+
+  /* timeout == 0  => flush unconditionally */
+  if (timeout > 0) {
+    cdtime_t now = cdtime();
+
+    if ((cb->send_buffer_init_time + timeout) > now)
+      return 0;
+  }
+
+  char *payload = sd_output_reset(cb->formatter);
+  int status = wg_call_timeseries_write(cb, payload);
+  wg_reset_buffer(cb);
+  return status;
+} /* }}} wg_flush_nolock */
+
+static int wg_flush(cdtime_t timeout, /* {{{ */
+                    const char *identifier __attribute__((unused)),
+                    user_data_t *user_data) {
+  wg_callback_t *cb;
+  int status;
+
+  if (user_data == NULL)
+    return -EINVAL;
+
+  cb = user_data->data;
+
+  pthread_mutex_lock(&cb->lock);
+
+  if (cb->curl == NULL) {
+    status = wg_callback_init(cb);
+    if (status != 0) {
+      ERROR("write_stackdriver plugin: wg_callback_init failed.");
+      pthread_mutex_unlock(&cb->lock);
+      return -1;
+    }
+  }
+
+  status = wg_flush_nolock(timeout, cb);
+  pthread_mutex_unlock(&cb->lock);
+
+  return status;
+} /* }}} int wg_flush */
+
+static void wg_callback_free(void *data) /* {{{ */
+{
+  wg_callback_t *cb = data;
+  if (cb == NULL)
+    return;
+
+  sd_output_destroy(cb->formatter);
+  cb->formatter = NULL;
+
+  sfree(cb->email);
+  sfree(cb->project);
+  sfree(cb->url);
+
+  oauth_destroy(cb->auth);
+  if (cb->curl) {
+    curl_easy_cleanup(cb->curl);
+  }
+
+  sfree(cb);
+} /* }}} void wg_callback_free */
+
+static int wg_metric_descriptors_create(wg_callback_t *cb, const data_set_t *ds,
+                                        const value_list_t *vl) {
+  /* {{{ */
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    char buffer[4096];
+
+    int status = sd_format_metric_descriptor(buffer, sizeof(buffer), ds, vl, i);
+    if (status != 0) {
+      ERROR("write_stackdriver plugin: sd_format_metric_descriptor failed "
+            "with status "
+            "%d",
+            status);
+      return status;
+    }
+
+    status = wg_call_metricdescriptor_create(cb, buffer);
+    if (status != 0) {
+      ERROR("write_stackdriver plugin: wg_call_metricdescriptor_create failed "
+            "with "
+            "status %d",
+            status);
+      return status;
+    }
+  }
+
+  return sd_output_register_metric(cb->formatter, ds, vl);
+} /* }}} int wg_metric_descriptors_create */
+
+static int wg_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
+                    user_data_t *user_data) {
+  wg_callback_t *cb = user_data->data;
+  if (cb == NULL)
+    return EINVAL;
+
+  pthread_mutex_lock(&cb->lock);
+
+  if (cb->curl == NULL) {
+    int status = wg_callback_init(cb);
+    if (status != 0) {
+      ERROR("write_stackdriver plugin: wg_callback_init failed.");
+      pthread_mutex_unlock(&cb->lock);
+      return status;
+    }
+  }
+
+  int status;
+  while (42) {
+    status = sd_output_add(cb->formatter, ds, vl);
+    if (status == 0) { /* success */
+      break;
+    } else if (status == ENOBUFS) { /* success, flush */
+      wg_flush_nolock(0, cb);
+      status = 0;
+      break;
+    } else if (status == EEXIST) {
+      /* metric already in the buffer; flush and retry */
+      wg_flush_nolock(0, cb);
+      continue;
+    } else if (status == ENOENT) {
+      /* new metric, create metric descriptor first */
+      status = wg_metric_descriptors_create(cb, ds, vl);
+      if (status != 0) {
+        break;
+      }
+      continue;
+    } else {
+      break;
+    }
+  }
+
+  if (status == 0) {
+    cb->timeseries_count++;
+  }
+
+  pthread_mutex_unlock(&cb->lock);
+  return status;
+} /* }}} int wg_write */
+
+static void wg_check_scope(char const *email) /* {{{ */
+{
+  char *scope = gce_scope(email);
+  if (scope == NULL) {
+    WARNING("write_stackdriver plugin: Unable to determine scope of this "
+            "instance.");
+    return;
+  }
+
+  if (strstr(scope, MONITORING_SCOPE) == NULL) {
+    size_t scope_len;
+
+    /* Strip trailing newline characers for printing. */
+    scope_len = strlen(scope);
+    while ((scope_len > 0) && (iscntrl((int)scope[scope_len - 1])))
+      scope[--scope_len] = 0;
+
+    WARNING("write_stackdriver plugin: The determined scope of this instance "
+            "(\"%s\") does not contain the monitoring scope (\"%s\"). You need "
+            "to add this scope to the list of scopes passed to gcutil with "
+            "--service_account_scopes when creating the instance. "
+            "Alternatively, to use this plugin on an instance which does not "
+            "have this scope, use a Service Account.",
+            scope, MONITORING_SCOPE);
+  }
+
+  sfree(scope);
+} /* }}} void wg_check_scope */
+
+static int wg_config_resource(oconfig_item_t *ci, wg_callback_t *cb) /* {{{ */
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
+    ERROR("write_stackdriver plugin: The \"%s\" option requires exactly one "
+          "string "
+          "argument.",
+          ci->key);
+    return EINVAL;
+  }
+  char *resource_type = ci->values[0].value.string;
+
+  if (cb->resource != NULL) {
+    sd_resource_destroy(cb->resource);
+  }
+
+  cb->resource = sd_resource_create(resource_type);
+  if (cb->resource == NULL) {
+    ERROR("write_stackdriver plugin: sd_resource_create(\"%s\") failed.",
+          resource_type);
+    return ENOMEM;
+  }
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Label", child->key) == 0) {
+      if ((child->values_num != 2) ||
+          (child->values[0].type != OCONFIG_TYPE_STRING) ||
+          (child->values[1].type != OCONFIG_TYPE_STRING)) {
+        ERROR("write_stackdriver plugin: The \"Label\" option needs exactly "
+              "two string arguments.");
+        continue;
+      }
+
+      sd_resource_add_label(cb->resource, child->values[0].value.string,
+                            child->values[1].value.string);
+    }
+  }
+
+  return 0;
+} /* }}} int wg_config_resource */
+
+static int wg_config(oconfig_item_t *ci) /* {{{ */
+{
+  if (ci == NULL) {
+    return EINVAL;
+  }
+
+  wg_callback_t *cb = calloc(1, sizeof(*cb));
+  if (cb == NULL) {
+    ERROR("write_stackdriver plugin: calloc failed.");
+    return ENOMEM;
+  }
+  cb->url = strdup(GCM_API_URL);
+  pthread_mutex_init(&cb->lock, /* attr = */ NULL);
+
+  char *credential_file = NULL;
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp("Project", child->key) == 0)
+      cf_util_get_string(child, &cb->project);
+    else if (strcasecmp("Email", child->key) == 0)
+      cf_util_get_string(child, &cb->email);
+    else if (strcasecmp("Url", child->key) == 0)
+      cf_util_get_string(child, &cb->url);
+    else if (strcasecmp("CredentialFile", child->key) == 0)
+      cf_util_get_string(child, &credential_file);
+    else if (strcasecmp("Resource", child->key) == 0)
+      wg_config_resource(child, cb);
+    else {
+      ERROR("write_stackdriver plugin: Invalid configuration option: %s.",
+            child->key);
+      wg_callback_free(cb);
+      return EINVAL;
+    }
+  }
+
+  /* Set up authentication */
+  /* Option 1: Credentials file given => use service account */
+  if (credential_file != NULL) {
+    oauth_google_t cfg =
+        oauth_create_google_file(credential_file, MONITORING_SCOPE);
+    if (cfg.oauth == NULL) {
+      ERROR("write_stackdriver plugin: oauth_create_google_file failed");
+      wg_callback_free(cb);
+      return EINVAL;
+    }
+    cb->auth = cfg.oauth;
+
+    if (cb->project == NULL) {
+      cb->project = cfg.project_id;
+      INFO("write_stackdriver plugin: Automatically detected project ID: "
+           "\"%s\"",
+           cb->project);
+    } else {
+      sfree(cfg.project_id);
+    }
+  }
+  /* Option 2: Look for credentials in well-known places */
+  if (cb->auth == NULL) {
+    oauth_google_t cfg = oauth_create_google_default(MONITORING_SCOPE);
+    cb->auth = cfg.oauth;
+
+    if (cb->project == NULL) {
+      cb->project = cfg.project_id;
+      INFO("write_stackdriver plugin: Automatically detected project ID: "
+           "\"%s\"",
+           cb->project);
+    } else {
+      sfree(cfg.project_id);
+    }
+  }
+
+  if ((cb->auth != NULL) && (cb->email != NULL)) {
+    NOTICE("write_stackdriver plugin: A service account email was configured "
+           "but is "
+           "not used for authentication because %s used instead.",
+           (credential_file != NULL) ? "a credential file was"
+                                     : "application default credentials were");
+  }
+
+  /* Option 3: Running on GCE => use metadata service */
+  if ((cb->auth == NULL) && gce_check()) {
+    wg_check_scope(cb->email);
+  } else if (cb->auth == NULL) {
+    ERROR("write_stackdriver plugin: Unable to determine credentials. Please "
+          "either "
+          "specify the \"Credentials\" option or set up Application Default "
+          "Credentials.");
+    wg_callback_free(cb);
+    return EINVAL;
+  }
+
+  if ((cb->project == NULL) && gce_check()) {
+    cb->project = gce_project_id();
+  }
+  if (cb->project == NULL) {
+    ERROR("write_stackdriver plugin: Unable to determine the project number. "
+          "Please specify the \"Project\" option manually.");
+    wg_callback_free(cb);
+    return EINVAL;
+  }
+
+  if ((cb->resource == NULL) && gce_check()) {
+    /* TODO(octo): add error handling */
+    cb->resource = sd_resource_create("gce_instance");
+    sd_resource_add_label(cb->resource, "project_id", gce_project_id());
+    sd_resource_add_label(cb->resource, "instance_id", gce_instance_id());
+    sd_resource_add_label(cb->resource, "zone", gce_zone());
+  }
+  if (cb->resource == NULL) {
+    /* TODO(octo): add error handling */
+    cb->resource = sd_resource_create("global");
+    sd_resource_add_label(cb->resource, "project_id", cb->project);
+  }
+
+  DEBUG("write_stackdriver plugin: Registering write callback with URL %s",
+        cb->url);
+  assert((cb->auth != NULL) || gce_check());
+
+  user_data_t user_data = {
+      .data = cb,
+  };
+  plugin_register_flush("write_stackdriver", wg_flush, &user_data);
+
+  user_data.free_func = wg_callback_free;
+  plugin_register_write("write_stackdriver", wg_write, &user_data);
+
+  return 0;
+} /* }}} int wg_config */
+
+static int wg_init(void) {
+  /* {{{ */
+  /* Call this while collectd is still single-threaded to avoid
+   * initialization issues in libgcrypt. */
+  curl_global_init(CURL_GLOBAL_SSL);
+
+  return 0;
+} /* }}} int wg_init */
+
+void module_register(void) /* {{{ */
+{
+  plugin_register_complex_config("write_stackdriver", wg_config);
+  plugin_register_init("write_stackdriver", wg_init);
+} /* }}} void module_register */