Merge pull request #3072 from octo/ff/network_test
authorrjablonx <36538481+rjablonx@users.noreply.github.com>
Fri, 12 Jul 2019 11:09:00 +0000 (13:09 +0200)
committerGitHub <noreply@github.com>
Fri, 12 Jul 2019 11:09:00 +0000 (13:09 +0200)
Add a unit test for the network plugin.

59 files changed:
.cirrus.yml [new file with mode: 0644]
.travis.yml
AUTHORS
ChangeLog
Makefile.am
README
configure.ac
contrib/docker/50docker-apt-conf
contrib/docker/Dockerfile
contrib/redhat/collectd.spec
contrib/systemd.collectd.service
src/barometer.c
src/collectd-lua.pod
src/collectd-snmp.pod
src/collectd.conf.in
src/collectd.conf.pod
src/cpufreq.c
src/curl.c
src/curl_json.c
src/curl_xml.c
src/daemon/collectd.c
src/daemon/configfile.c
src/daemon/globals.c
src/daemon/types_list.c
src/daemon/utils_cache.c
src/intel_rdt.c
src/intel_rdt_test.c [new file with mode: 0644]
src/load.c
src/logfile.c
src/lua.c
src/match_regex.c
src/memcached.c
src/modbus.c
src/mysql.c
src/openvpn.c
src/ovs_stats.c
src/powerdns.c
src/rrdcached.c
src/syslog.c
src/ted.c
src/turbostat.c
src/types.db
src/utils/cmds/cmds_test.c
src/utils/common/common_test.c
src/utils/dns/dns.c
src/utils/dns/dns.h
src/utils/format_graphite/format_graphite.c
src/utils/format_graphite/format_graphite.h
src/utils/mount/mount.h
src/utils/oauth/oauth.c
src/utils/proc_pids/proc_pids.c [new file with mode: 0644]
src/utils/proc_pids/proc_pids.h [new file with mode: 0644]
src/utils/proc_pids/proc_pids_test.c [new file with mode: 0644]
src/virt.c
src/write_graphite.c
src/write_prometheus.c
src/write_riemann.c
src/write_sensu.c
src/write_syslog.c [new file with mode: 0644]

diff --git a/.cirrus.yml b/.cirrus.yml
new file mode 100644 (file)
index 0000000..ab88daf
--- /dev/null
@@ -0,0 +1,225 @@
+env:
+  LANG: C
+  CIRRUS_CLONE_DEPTH: 1
+  DEFAULT_CONFIG_OPTS: --enable-debug --without-libstatgrab --disable-dependency-tracking
+
+
+###
+# make distcheck and other sanity checks
+#
+release_ready_task:
+  container:
+    image: collectd/ci:stretch_amd64
+  lint_script:
+    - /checks/check-bashisms.sh
+    - /checks/check-pod.sh
+  configure_script:
+    - ./build.sh
+    - ./configure $DEFAULT_CONFIG_OPTS
+  checks_script:
+    - make -j2 -s distcheck DISTCHECK_CONFIGURE_FLAGS="${DEFAULT_CONFIG_OPTS}"
+
+###
+# Default toolchain and build flags used in deb packages, on a range of Debian
+# and Ubuntu releases (+ Debian/unstable)
+# Most should succeed, and PRs shouldn't break them.
+#
+debian_default_toolchain_task:
+  matrix:
+    - allow_failures: false
+      container:
+        image: collectd/ci:jessie_amd64
+    - allow_failures: false
+      container:
+        image: collectd/ci:stretch_amd64
+    - allow_failures: false
+      container:
+        image: collectd/ci:stretch_i386
+    - allow_failures: false
+      container:
+        image: collectd/ci:trusty_amd64
+    - allow_failures: false
+      container:
+        image: collectd/ci:xenial_amd64
+    # debian/unstable is expected to fail
+    - allow_failures: true
+      skip_notifications: true
+      only_if: $CIRRUS_BRANCH == 'master'
+      container:
+        image: collectd/ci:sid_amd64
+  configure_script:
+    - ./build.sh
+    - gcc --version
+    - >
+      ./configure CC=gcc $DEFAULT_CONFIG_OPTS
+      CFLAGS="$(dpkg-buildflags --get CFLAGS)"
+      CPPLAGS="$(dpkg-buildflags --get CPPFLAGS)"
+      LDFLAGS="$(dpkg-buildflags --get LDFLAGS)"
+  build_script:
+    - make -j2 -sk
+  tests_script:
+    - make -j2 -sk check
+    - /checks/check-built-plugins.sh
+
+###
+# Default toolchain and build flags used in RPM packages, on a range of RedHat
+# and Fedora releases (+ Fedora/rawhide)
+# Most should succeed, and PRs shouldn't break them.
+#
+redhat_default_toolchain_task:
+  matrix:
+    - allow_failures: false
+      skip_notifications: false
+      container:
+        image: collectd/ci:el6_x86_64
+    - allow_failures: true
+      skip_notifications: true
+      container:
+        image: collectd/ci:el7_x86_64 # TODO: fix this platform
+    - allow_failures: true
+      skip_notifications: true
+      container:
+        image: collectd/ci:fedora28_x86_64
+    # fedora/rawhide is expected to fail
+    - allow_failures: true
+      skip_notifications: true
+      only_if: $CIRRUS_BRANCH == 'master'
+      container:
+        image: collectd/ci:fedora_rawhide_x86_64
+  configure_script:
+    - ./build.sh
+    - gcc --version
+    - ./configure CC=gcc $DEFAULT_CONFIG_OPTS CFLAGS="$(rpm --eval '%optflags')"
+  build_script:
+    - make -j2 -sk
+  tests_script:
+    - make -j2 -sk check
+    - /checks/check-built-plugins.sh
+
+
+###
+# Misc non-standard build environment & options on most recent released debian
+# version.
+# Some are expected to fail, others should always pass
+non_standard_toolchains_task:
+  container:
+    image: collectd/ci:stretch_amd64
+  only_if: $CIRRUS_PR == ''
+
+  matrix:
+
+    # build using clang with default build flags, should always pass
+    - env:
+        LABEL: clang
+      allow_failures: true # TODO: fix this platform
+      skip_notifications: true
+      configure_script:
+        - ./build.sh
+        - clang --version
+        - >
+          ./configure CC=clang CXX=clang++
+          $DEFAULT_CONFIG_OPTS
+          CFLAGS="$(dpkg-buildflags --get CFLAGS)"
+          CPPLAGS="$(dpkg-buildflags --get CPPFLAGS)"
+          LDFLAGS="$(dpkg-buildflags --get LDFLAGS)"
+      build_script:
+        - make -j2 -sk
+      tests_script:
+        - make -j2 -sk check
+
+    # build against libstatgrab, should always pass
+    - env:
+        LABEL: statgrab
+      allow_failures: false
+      skip_notifications: false
+      configure_script:
+        - ./build.sh
+        - gcc --version
+        - >
+          ./configure --with-libstatgrab --enable-debug
+          CFLAGS="$(dpkg-buildflags --get CFLAGS)"
+          CPPLAGS="$(dpkg-buildflags --get CPPFLAGS)"
+          LDFLAGS="$(dpkg-buildflags --get LDFLAGS)"
+      build_script:
+        - make -j2 -sk
+      tests_script:
+        - >
+          for i in cpu disk interface load memory swap users; do
+            if ! $(ldd ".libs/${i}.so" 2>/dev/null | grep -q 'libstatgrab.so'); then
+              echo "plugin $i NOT linked against libstatgrab"
+              exit 1
+            fi
+          done
+
+    # build using clang with a collection of strict build flags, will most
+    # probably always fail
+    - env:
+        LABEL: clang strict
+      allow_failures: true
+      skip_notifications: true
+      configure_script:
+        - ./build.sh
+        - clang --version
+        - >
+          ./configure CC=clang CXX=clang++
+          $DEFAULT_CONFIG_OPTS
+          CFLAGS='-Wall
+          -Wno-error
+          -Wextra
+          -Wformat=2
+          -Wformat-security
+          -Wformat-nonliteral
+          -Wmissing-include-dirs
+          -Wold-style-definition
+          -Wpointer-arith
+          -Winit-self
+          -Wmissing-prototypes
+          -Wimplicit-function-declaration
+          -Wmissing-declarations
+          -Wstrict-prototypes
+          -Wmissing-noreturn
+          -Wshadow
+          -Wendif-labels
+          -Wwrite-strings
+          -Wno-unused-parameter
+          -Wno-missing-field-initializers
+          -Wdate-time
+          -Wnested-externs
+          -Wno-typedef-redefinition
+          -Wno-gnu-variable-sized-type-not-at-end'
+      build_script:
+        - make -j2 -sk
+      tests_script:
+        - make -j2 -sk check
+
+###
+# Build using a range of compilers, available in debian/unstable. NB: might
+# fail because of changes to the distro, not the compiler used.
+#
+bleeding_edge_compilers_task:
+  container:
+    image: collectd/ci:sid_amd64
+  only_if: $CIRRUS_BRANCH == 'master'
+  allow_failures: true
+  skip_notifications: true
+  env:
+    matrix:
+      CC: gcc-7
+      CC: gcc-8
+      CC: clang-6.0
+      CC: clang-7
+      CC: clang-8
+      CC: clang-9
+  configure_script:
+    - ./build.sh
+    - $CC --version
+    - >
+      ./configure CC=$CC
+      $DEFAULT_CONFIG_OPTS
+      CFLAGS="$(dpkg-buildflags --get CFLAGS)"
+      CPPLAGS="$(dpkg-buildflags --get CPPFLAGS)"
+      LDFLAGS="$(dpkg-buildflags --get LDFLAGS)"
+  build_script:
+    - make -j2 -sk
+  tests_script:
+    - make -j2 -sk check
index 39aaf10..4ba4e2a 100644 (file)
@@ -1,29 +1,39 @@
 # Travis CI configuration file
 # https://travis-ci.org/collectd/collectd
+language: c
+
 env:
   global:
-   # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
-   # via the "travis encrypt" command using the project repo's public key
-   - secure: "ZdWWp0XX3C4sLIp4lqeQTWC7vt+GsWjmyRiD17T9833NBAW4dddz310I6iyeXe6oX09ZFFiVIN4ogx9ANcNBx9jriGXI2/82nBhpxOJBebet8JCNS5VeTr4rDSfQOKP+Oc+ko5KbbghTuAtO2CFYiH3jZUcn4TdsYbVanf+TwUs="
+    - MAKEFLAGS="-j 2"
+    # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
+    # via the "travis encrypt" command using the project repo's public key
+    - secure: "ZdWWp0XX3C4sLIp4lqeQTWC7vt+GsWjmyRiD17T9833NBAW4dddz310I6iyeXe6oX09ZFFiVIN4ogx9ANcNBx9jriGXI2/82nBhpxOJBebet8JCNS5VeTr4rDSfQOKP+Oc+ko5KbbghTuAtO2CFYiH3jZUcn4TdsYbVanf+TwUs="
+
+matrix:
+  include:
+    - os: osx
+      osx_image: xcode10.1
+      compiler: clang
+      env:
+        - CXX=clang++
+        - PATH="/usr/local/opt/mysql-client/bin:$PATH"
+    - os: linux
+      dist: xenial
+      compiler: clang
+    - os: linux
+      dist: xenial
+      compiler: gcc
 
-sudo: required
-dist: trusty
-os:
-  - linux
-  - osx
-compiler:
-  - gcc
-  - clang
-language: c
 before_install:
   # When building the coverity_scan branch, allow only the first job to continue to avoid travis-ci/travis-ci#1975.
   - if [[ "${TRAVIS_BRANCH}" == "coverity_scan" && ! "${TRAVIS_JOB_NUMBER}" =~ \.1$ ]]; then exit 0; fi
-before_script: autoreconf -fi
+
+before_script: autoreconf -vif
+
 script:
   - if [[ "${TRAVIS_BRANCH}" == "coverity_scan" ]]; then exit 0; fi
   - ./configure
-  - make
-  - make check
+  - make distcheck DISTCHECK_CONFIGURE_FLAGS="--disable-dependency-tracking --enable-debug"
 
 addons:
   apt:
@@ -51,7 +61,7 @@ addons:
     - libmicrohttpd-dev
     - libmnl-dev
     - libmodbus-dev
-    - libmosquitto0-dev
+    - libmosquitto-dev
     - libmysqlclient-dev
     - libnotify-dev
     - libopenipmi-dev
@@ -116,9 +126,15 @@ addons:
     - protobuf
     - protobuf-c
     - python
+    - qpid-proton
     - rabbitmq-c
     - riemann-client
     - rrdtool
     - tokyo-cabinet
     - varnish
     - yajl
+
+git:
+  quiet: true
+  submodules: false
+  depth: 1
diff --git a/AUTHORS b/AUTHORS
index 409655a..d05b86f 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -172,6 +172,9 @@ Jiri Tyr <jiri.tyr at gmail.com>
 Julien Ammous <j.ammous at gmail.com>
  - Lua plugin.
 
+Shirly Radco <sradco at redhat.com>
+ - write_syslog plugin.
+
 Kevin Bowling <kbowling at llnw.com>
  - write_tsdb plugin for http://opentsdb.net/
 
index e9a8415..7ea5a07 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -72,6 +72,8 @@
          newer libmicrohttpd. Thanks to Pavel Rochnyak. #2849
        * Write Prometheus plugin: set "SO_REUSEADDRESS" on listening socket.
          Thanks to Pavel Rochnyak. #2570, #2673
+       * Write Syslog plugin: The new "write_syslog" plugin writes value
+         lists as syslog messages. Thanks to Shirly Radco. #3019
 
 2017-11-17, Version 5.8.0
        * collectd: The core daemon is now completely licensed under the MIT
index 5121176..2e16e5c 100644 (file)
@@ -276,7 +276,7 @@ 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
@@ -1065,11 +1065,32 @@ if BUILD_PLUGIN_INTEL_RDT
 pkglib_LTLIBRARIES += intel_rdt.la
 intel_rdt_la_SOURCES = \
        src/intel_rdt.c \
+       src/utils/proc_pids/proc_pids.c \
+       src/utils/proc_pids/proc_pids.h \
        src/utils/config_cores/config_cores.h \
        src/utils/config_cores/config_cores.c
 intel_rdt_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBPQOS_CPPFLAGS)
 intel_rdt_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBPQOS_LDFLAGS)
 intel_rdt_la_LIBADD = $(BUILD_WITH_LIBPQOS_LIBS)
+
+test_plugin_intel_rdt_SOURCES = \
+       src/intel_rdt_test.c \
+       src/utils/config_cores/config_cores.c \
+       src/utils/proc_pids/proc_pids.c \
+       src/daemon/configfile.c \
+       src/daemon/types_list.c
+test_plugin_intel_rdt_CPPFLAGS = $(AM_CPPFLAGS)
+test_plugin_intel_rdt_LDFLAGS = $(PLUGIN_LDFLAGS)
+test_plugin_intel_rdt_LDADD = liboconfig.la libplugin_mock.la
+check_PROGRAMS += test_plugin_intel_rdt
+TESTS += test_plugin_intel_rdt
+
+test_utils_proc_pids_SOURCES = \
+       src/utils/proc_pids/proc_pids_test.c \
+       src/testing.h
+test_utils_proc_pids_LDADD = libplugin_mock.la
+check_PROGRAMS += test_utils_proc_pids
+TESTS += test_utils_proc_pids
 endif
 
 if BUILD_PLUGIN_INTERFACE
@@ -1954,12 +1975,13 @@ virt_la_CFLAGS = $(AM_CFLAGS) \
 virt_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 virt_la_LIBADD = libignorelist.la $(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
 
-test_plugin_virt_SOURCES = src/virt_test.c
+test_plugin_virt_SOURCES = src/virt_test.c src/daemon/configfile.c \
+       src/daemon/types_list.c
 test_plugin_virt_CPPFLAGS = $(AM_CPPFLAGS) \
        $(BUILD_WITH_LIBVIRT_CPPFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
 test_plugin_virt_LDFLAGS = $(PLUGIN_LDFLAGS) \
        $(BUILD_WITH_LIBVIRT_LDFLAGS) $(BUILD_WITH_LIBXML2_LDFLAGS)
-test_plugin_virt_LDADD = libplugin_mock.la \
+test_plugin_virt_LDADD = liboconfig.la libplugin_mock.la \
        $(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
 check_PROGRAMS += test_plugin_virt
 TESTS += test_plugin_virt
@@ -2072,6 +2094,12 @@ write_stackdriver_la_LIBADD = libformat_stackdriver.la libgce.la liboauth.la \
                      $(BUILD_WITH_LIBCURL_LIBS)
 endif
 
+if BUILD_PLUGIN_WRITE_SYSLOG
+pkglib_LTLIBRARIES += write_syslog.la
+write_syslog_la_SOURCES = src/write_syslog.c
+write_syslog_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+endif
+
 if BUILD_PLUGIN_WRITE_TSDB
 pkglib_LTLIBRARIES += write_tsdb.la
 write_tsdb_la_SOURCES = src/write_tsdb.c
@@ -2124,20 +2152,10 @@ am__v_POD2MAN_C_0 = @echo "  POD2MAN " $@;
 am__v_POD2MAN_C_1 =
 
 .pod.1:
-       $(AM_V_POD2MAN_C)pod2man --release=$(VERSION) --center=$(PACKAGE) $< \
-               >.pod2man.tmp.$$$$ 2>/dev/null && mv -f .pod2man.tmp.$$$$ $@ || true
-       @if grep '\<POD ERRORS\>' $@ >/dev/null 2>&1; \
-       then \
-               echo "$@ has some POD errors!"; false; \
-       fi
+       $(AM_V_POD2MAN_C)pod2man --release=$(VERSION) --center=$(PACKAGE) $< $@
 
 .pod.5:
-       $(AM_V_POD2MAN_C)pod2man --section=5 --release=$(VERSION) --center=$(PACKAGE) $< \
-               >.pod2man.tmp.$$$$ 2>/dev/null && mv -f .pod2man.tmp.$$$$ $@ || true
-       @if grep '\<POD ERRORS\>' $@ >/dev/null 2>&1; \
-       then \
-               echo "$@ has some POD errors!"; false; \
-       fi
+       $(AM_V_POD2MAN_C)pod2man --section=5 --release=$(VERSION) --center=$(PACKAGE) $< $@
 
 V_PROTOC = $(v_protoc_@AM_V@)
 v_protoc_ = $(v_protoc_@AM_DEFAULT_V@)
diff --git a/README b/README
index 8776054..f28d499 100644 (file)
--- a/README
+++ b/README
@@ -563,6 +563,10 @@ Features
       Sends data to Sensu, a stream processing and monitoring system, via the
       Sensu client local TCP socket.
 
+    - write_syslog
+      Sends data in syslog format, using TCP, where the message
+      contains the metric in human or JSON format.
+
     - write_tsdb
       Sends data OpenTSDB, a scalable no master, no shared state time series
       database.
index cadbb24..2289aa4 100644 (file)
@@ -635,16 +635,6 @@ fi
 
 # }}}
 
-# For the dns plugin
-AC_CHECK_HEADERS([arpa/nameser.h])
-AC_CHECK_HEADERS([arpa/nameser_compat.h], [], [],
-  [[
-    #if HAVE_ARPA_NAMESER_H
-    # include <arpa/nameser.h>
-    #endif
-  ]]
-)
-
 AC_CHECK_HEADERS([net/if_arp.h], [], [],
   [[
     #if HAVE_SYS_SOCKET_H
@@ -777,8 +767,10 @@ AC_CHECK_FUNCS_ONCE([ \
 
 AC_FUNC_STRERROR_R
 
-SAVE_CFLAGS="$CFLAGS"
-CFLAGS="-Wall -Werror"
+if test "x$GCC" = "xyes"; then
+  SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$CFLAGS -Wall -Werror"
+fi
 SAVE_LDFLAGS="$LDFLAGS"
 LDFLAGS=""
 if test "x$ac_system" = "xWindows"; then
@@ -1583,7 +1575,7 @@ if test "x$have_getmntent" = "xlibc"; then
       AC_COMPILE_IFELSE(
         [
           AC_LANG_PROGRAM(
-            [[#include "$srcdir/src/utils_mount.h"]],
+            [[#include "$srcdir/src/utils/mount/mount.h"]],
             [[
               FILE *fh;
               struct mntent *me;
@@ -1605,7 +1597,7 @@ if test "x$have_getmntent" = "xlibc"; then
       AC_COMPILE_IFELSE(
         [
           AC_LANG_PROGRAM(
-            [[#include "$srcdir/src/utils_mount.h"]],
+            [[#include "$srcdir/src/utils/mount/mount.h"]],
             [[
               FILE *fh;
               struct mnttab mt;
@@ -2136,9 +2128,6 @@ CPPFLAGS="$CPPFLAGS $LIBAQUAERO5_CFLAGS"
 LDFLAGS="$LDFLAGS $LIBAQUAERO5_LDFLAGS"
 
 if test "x$with_libaquaero5" = "xyes"; then
-  if test "x$LIBAQUAERO5_CFLAGS" != "x"; then
-    AC_MSG_NOTICE([libaquaero5 CPPFLAGS: $LIBAQUAERO5_CFLAGS])
-  fi
   AC_CHECK_HEADERS([libaquaero5.h],
     [with_libaquaero5="yes"],
     [with_libaquaero5="no (libaquaero5.h not found)"]
@@ -2146,9 +2135,6 @@ if test "x$with_libaquaero5" = "xyes"; then
 fi
 
 if test "x$with_libaquaero5" = "xyes"; then
-  if test "x$LIBAQUAERO5_LDFLAGS" != "x"; then
-    AC_MSG_NOTICE([libaquaero5 LDFLAGS: $LIBAQUAERO5_LDFLAGS])
-  fi
   AC_CHECK_LIB([aquaero5], libaquaero5_poll,
     [with_libaquaero5="yes"],
     [with_libaquaero5="no (symbol 'libaquaero5_poll' not found)"]
@@ -2189,9 +2175,6 @@ CPPFLAGS="$CPPFLAGS $LIBHIREDIS_CPPFLAGS"
 LDFLAGS="$LDFLAGS $LIBHIREDIS_LDFLAGS"
 
 if test "x$with_libhiredis" = "xyes"; then
-  if test "x$LIBHIREDIS_CPPFLAGS" != "x"; then
-    AC_MSG_NOTICE([libhiredis CPPFLAGS: $LIBHIREDIS_CPPFLAGS])
-  fi
   AC_CHECK_HEADERS([hiredis/hiredis.h],
     [with_libhiredis="yes"],
     [with_libhiredis="no (hiredis.h not found)"]
@@ -2199,9 +2182,6 @@ if test "x$with_libhiredis" = "xyes"; then
 fi
 
 if test "x$with_libhiredis" = "xyes"; then
-  if test "x$LIBHIREDIS_LDFLAGS" != "x"; then
-    AC_MSG_NOTICE([libhiredis LDFLAGS: $LIBHIREDIS_LDFLAGS])
-  fi
   AC_CHECK_LIB([hiredis], [redisCommand],
     [with_libhiredis="yes"],
     [with_libhiredis="no (symbol 'redisCommand' not found)"]
@@ -2639,9 +2619,6 @@ LDFLAGS="$LDFLAGS $GCRYPT_LDFLAGS"
 LIBS="$LIBS $GCRYPT_LIBS"
 
 if test "x$with_libgcrypt" = "xyes"; then
-  if test "x$GCRYPT_CPPFLAGS" != "x"; then
-    AC_MSG_NOTICE([gcrypt CPPFLAGS: $GCRYPT_CPPFLAGS])
-  fi
   AC_CHECK_HEADERS([gcrypt.h],
     [with_libgcrypt="yes"],
     [with_libgcrypt="no (gcrypt.h not found)"]
@@ -2978,18 +2955,6 @@ if test "x$with_java" = "xyes"; then
   fi; fi
 fi
 
-if test "x$JAVA_CPPFLAGS" != "x"; then
-  AC_MSG_NOTICE([Building with JAVA_CPPFLAGS set to: $JAVA_CPPFLAGS])
-fi
-if test "x$JAVA_CFLAGS" != "x"; then
-  AC_MSG_NOTICE([Building with JAVA_CFLAGS set to: $JAVA_CFLAGS])
-fi
-if test "x$JAVA_LDFLAGS" != "x"; then
-  AC_MSG_NOTICE([Building with JAVA_LDFLAGS set to: $JAVA_LDFLAGS])
-fi
-if test "x$JAVA_LIBS" != "x"; then
-  AC_MSG_NOTICE([Building with JAVA_LIBS set to: $JAVA_LIBS])
-fi
 if test "x$JAVAC" = "x"; then
   with_javac_path="$PATH"
   if test "x$with_java_home" != "x"; then
@@ -3047,7 +3012,6 @@ fi
 
 if test "x$with_java" = "xyes"; then
   JAVA_LIBS="$JAVA_LIBS -ljvm"
-  AC_MSG_NOTICE([Building with JAVA_LIBS set to: $JAVA_LIBS])
 fi
 
 CPPFLAGS="$SAVE_CPPFLAGS"
@@ -3087,10 +3051,6 @@ CPPFLAGS="$CPPFLAGS $LIBLDAP_CPPFLAGS"
 LDFLAGS="$LDFLAGS $LIBLDAP_LDFLAGS"
 
 if test "x$with_libldap" = "xyes"; then
-  if test "x$LIBLDAP_CPPFLAGS" != "x"; then
-    AC_MSG_NOTICE([libldap CPPFLAGS: $LIBLDAP_CPPFLAGS])
-  fi
-
   AC_CHECK_HEADERS([ldap.h],
     [with_libldap="yes"],
     [with_libldap="no ('ldap.h' not found)"]
@@ -3098,10 +3058,6 @@ if test "x$with_libldap" = "xyes"; then
 fi
 
 if test "x$with_libldap" = "xyes"; then
-  if test "x$LIBLDAP_LDFLAGS" != "x"; then
-    AC_MSG_NOTICE([libldap LDFLAGS: $LIBLDAP_LDFLAGS])
-  fi
-
   AC_CHECK_LIB([ldap], [ldap_initialize],
     [with_libldap="yes"],
     [with_libldap="no (symbol 'ldap_initialize' not found)"]
@@ -3409,7 +3365,6 @@ AC_ARG_WITH([libmodbus],
 
 # configure using pkg-config
 if test "x$with_libmodbus" = "xuse_pkgconfig"; then
-  AC_MSG_NOTICE([Checking for libmodbus using $PKG_CONFIG])
   $PKG_CONFIG --exists 'libmodbus' 2>/dev/null
   if test $? -ne 0; then
     with_libmodbus="no (pkg-config doesn't know libmodbus)"
@@ -3492,10 +3447,6 @@ if test "x$with_libmongoc" = "xyes"; then
 
   CPPFLAGS="$CPPFLAGS $LIBMONGOC_CFLAGS"
 
-  if test "x$CPPFLAGS" != "x"; then
-    AC_MSG_NOTICE([libmongoc CPPFLAGS: $LIBMONGOC_CFLAGS])
-  fi
-
   AC_CHECK_HEADERS([mongoc.h],
     [with_libmongoc="yes"],
     [with_libmongoc="no ('mongoc.h' not found)"]
@@ -3511,10 +3462,6 @@ if test "x$with_libmongoc" = "xyes"; then
   CPPFLAGS="$CPPFLAGS $LIBMONGOC_CFLAGS"
   LDFLAGS="$LDFLAGS $LIBMONGOC_LDFLAGS"
 
-  if test "x$LIBMONGOC_LDFLAGS" != "x"; then
-    AC_MSG_NOTICE([libmongoc LDFLAGS: $LIBMONGOC_LDFLAGS])
-  fi
-
   AC_CHECK_LIB([mongoc-1.0], [mongoc_init],
     [with_libmongoc="yes"],
     [with_libmongoc="no (symbol 'mongoc_init' not found)"]
@@ -3818,9 +3765,6 @@ CPPFLAGS="$CPPFLAGS $LIBNETAPP_CPPFLAGS"
 LDFLAGS="$LDFLAGS $LIBNETAPP_LDFLAGS"
 
 if test "x$with_libnetapp" = "xyes"; then
-  if test "x$LIBNETAPP_CPPFLAGS" != "x"; then
-    AC_MSG_NOTICE([netapp CPPFLAGS: $LIBNETAPP_CPPFLAGS])
-  fi
   AC_CHECK_HEADERS([netapp_api.h],
     [with_libnetapp="yes"],
     [with_libnetapp="no (netapp_api.h not found)"]
@@ -3828,16 +3772,10 @@ if test "x$with_libnetapp" = "xyes"; then
 fi
 
 if test "x$with_libnetapp" = "xyes"; then
-  if test "x$LIBNETAPP_LDFLAGS" != "x"; then
-    AC_MSG_NOTICE([netapp LDFLAGS: $LIBNETAPP_LDFLAGS])
-  fi
-
   if test "x$LIBNETAPP_LIBS" = "x"; then
     LIBNETAPP_LIBS="$PTHREAD_LIBS -lxml -ladt -lssl -lm -lcrypto -lz"
   fi
 
-  AC_MSG_NOTICE([netapp LIBS: $LIBNETAPP_LIBS])
-
   AC_CHECK_LIB([netapp], [na_server_invoke_elem],
     [with_libnetapp="yes"],
     [with_libnetapp="no (symbol na_server_invoke_elem not found)"],
@@ -3922,7 +3860,10 @@ if test "x$with_libnetsnmp" = "xyes"; then
   SAVE_CPPFLAGS="$CPPFLAGS"
   SAVE_LDFLAGS="$LDFLAGS"
   SAVE_LIBS="$LIBS"
-  CPPFLAGS="$CPPFLAGS $with_libnetsnmp_cppflags -Wall -Werror"
+  CPPFLAGS="$CPPFLAGS $with_libnetsnmp_cppflags"
+  if test "x$GCC" = "xyes"; then
+    CPPFLAGS="$CPPFLAGS -Wall -Werror"
+  fi
   LDFLAGS="$LDFLAGS $with_libnetsnmp_ldflags"
   LIBS="$LIBS -lnetsnmp"
 
@@ -4031,6 +3972,8 @@ if test "x$with_libnetsnmpagent" = "xyes"; then
 fi
 
 if test "x$with_libnetsnmpagent" = "xyes"; then
+  BUILD_WITH_LIBNETSNMPAGENT_CPPFLAGS="$with_libnetsnmpagent_cppflags"
+  BUILD_WITH_LIBNETSNMPAGENT_LDFLAGS="$with_libnetsnmpagent_ldflags"
   BUILD_WITH_LIBNETSNMPAGENT_LIBS="-lnetsnmpagent $libnetsnmphelpers"
 fi
 
@@ -4385,7 +4328,10 @@ if test "x$with_libperl" = "xyes"; then
   # (see issues #41 and #42)
   SAVE_CFLAGS="$CFLAGS"
   SAVE_LIBS="$LIBS"
-  CFLAGS="$CFLAGS $PERL_CFLAGS -Wall -Werror"
+  CFLAGS="$CFLAGS $PERL_CFLAGS"
+  if test "x$GCC" = "xyes"; then
+    CFLAGS="$CFLAGS -Wall -Werror"
+  fi
   LIBS="$LIBS $PERL_LIBS"
 
   AC_CACHE_CHECK([for broken Perl_load_module()],
@@ -4825,7 +4771,7 @@ if test "$PYTHON_CONFIG" != ""; then
   if test $? -ne 0; then
     with_libpython="no"
   fi
-  LIBPYTHON_LIBS="`${PYTHON_CONFIG} --libs`"
+  LIBPYTHON_LIBS="`${PYTHON_CONFIG} --libs --embed`" || LIBPYTHON_LIBS="`${PYTHON_CONFIG} --libs`"
   if test $? -ne 0; then
     with_libpython="no"
   fi
@@ -5663,7 +5609,6 @@ fi
 
 # configure using pkg-config
 if test "x$with_libupsclient" = "xuse_pkgconfig"; then
-  AC_MSG_NOTICE([Checking for libupsclient using $PKG_CONFIG])
   $PKG_CONFIG --exists 'libupsclient' 2>/dev/null
   if test $? -ne 0; then
     with_libupsclient="no (pkg-config doesn't know libupsclient)"
@@ -6573,6 +6518,7 @@ fi
 # FreeBSD
 
 if test "x$ac_system" = "xFreeBSD"; then
+  plugin_cpufreq="yes"
   plugin_disk="yes"
   plugin_zfs_arc="yes"
 fi
@@ -6685,7 +6631,7 @@ if test "x$c_cv_have_one_getmntent" = "xyes"; then
   plugin_df="yes"
 fi
 
-if test "x$c_cv_have_getmntent_r" = "xyes"; then
+if test "x$have_getmntent_r" = "xyes"; then
   plugin_df="yes"
 fi
 
@@ -6978,7 +6924,6 @@ AC_PLUGIN([vserver],             [$plugin_vserver],           [Linux VServer sta
 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])
@@ -6986,6 +6931,8 @@ AC_PLUGIN([write_prometheus],    [$plugin_write_prometheus],  [Prometheus write
 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_stackdriver],   [$plugin_write_stackdriver], [Google Stackdriver Monitoring output plugin])
+AC_PLUGIN([write_syslog],        [yes],                       [Syslog 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])
@@ -7129,11 +7076,13 @@ AC_SUBST([LCC_VERSION_STRING])
 
 AC_CONFIG_FILES([src/libcollectdclient/collectd/lcc_features.h])
 
-AM_CFLAGS="-Wall"
-AM_CXXFLAGS="-Wall"
-if test "x$enable_werror" != "xno"; then
-  AM_CFLAGS="$AM_CFLAGS -Werror"
-  AM_CXXFLAGS="$AM_CXXFLAGS -Werror"
+if test "x$GCC" = "xyes"; then
+  AM_CFLAGS="-Wall"
+  AM_CXXFLAGS="-Wall"
+  if test "x$enable_werror" != "xno"; then
+    AM_CFLAGS="$AM_CFLAGS -Werror"
+    AM_CXXFLAGS="$AM_CXXFLAGS -Werror"
+  fi
 fi
 
 AC_SUBST([AM_CFLAGS])
@@ -7411,6 +7360,7 @@ 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_syslog . .  . . $enable_write_syslog])
 AC_MSG_RESULT([    write_tsdb  . . . . . $enable_write_tsdb])
 AC_MSG_RESULT([    xencpu  . . . . . . . $enable_xencpu])
 AC_MSG_RESULT([    xmms  . . . . . . . . $enable_xmms])
index 1b71704..43b0ec9 100644 (file)
@@ -1,4 +1,4 @@
 APT::Install-Recommends "1";
-APT::Install-Suggests "0";
+APT::Install-Suggests "1";
 APT::Get::Assume-Yes "1";
 APT::Get::AutomaticRemove "1";
index a691f2e..fcb46b1 100644 (file)
@@ -1,4 +1,4 @@
-FROM debian:stable
+FROM debian:stable-slim
 
 ENV DEBIAN_FRONTEND noninteractive
 COPY 50docker-apt-conf /etc/apt/apt.conf.d/
@@ -21,4 +21,5 @@ COPY collectd.conf.d /etc/collectd/collectd.conf.d
 
 ENV LD_PRELOAD /usr/src/rootfs_prefix/rootfs_prefix.so
 
-CMD [ "/usr/sbin/collectd", "-f"]
+ENTRYPOINT ["/usr/sbin/collectd"]
+CMD ["-f"]
index 6f86b7e..246fcb5 100644 (file)
 %define with_openvpn 0%{!?_without_openvpn:1}
 %define with_ovs_events 0%{!?_without_ovs_events:1}
 %define with_ovs_stats 0%{!?_without_ovs_stats:1}
+%define with_pcie_errors 0%{!?_without_pcie_errors:1}
 %define with_perl 0%{!?_without_perl:1}
 %define with_pinba 0%{!?_without_pinba:1}
 %define with_ping 0%{!?_without_ping:1}
 %define with_write_prometheus 0%{!?_without_write_prometheus:1}
 %define with_write_redis 0%{!?_without_write_redis:1}
 %define with_write_riemann 0%{!?_without_write_riemann:1}
+%define with_write_stackdriver 0%{!?_without_write_stackdriver:1}
 %define with_write_sensu 0%{!?_without_write_sensu:1}
+%define with_write_syslog 0%{!?_without_write_syslog:1}
 %define with_write_tsdb 0%{!?_without_write_tsdb:1}
 %define with_xmms 0%{!?_without_xmms:0%{?_has_xmms}}
 %define with_zfs_arc 0%{!?_without_zfs_arc:1}
 %define with_xencpu 0%{!?_without_xencpu:0}
 # plugin zone disabled, requires Solaris
 %define with_zone 0%{!?_without_zone:0}
+# plugin gpu_nvidia requires cuda-nvml-dev
+# get it from https://developer.nvidia.com/cuda-downloads
+# then install cuda-nvml-dev-10-1 or other version
+%define with_gpu_nvidia 0%{!?_without_gpu_nvidia:0}
+# not sure why this one's failing
+%define with_write_stackdriver 0%{!?_without_write_stackdriver:0}
 
 # Plugins not buildable on RHEL < 6
 %if 0%{?rhel} && 0%{?rhel} < 6
 
 Summary:       Statistics collection and monitoring daemon
 Name:          collectd
-Version:       5.7.1
-Release:       9%{?dist}
+Version:       5.9.0
+Release:       1%{?dist}
 URL:           https://collectd.org
 Source:                https://collectd.org/files/%{name}-%{version}.tar.bz2
 License:       GPLv2
@@ -740,6 +749,16 @@ The Perl plugin embeds a Perl interpreter into collectd and exposes the
 application programming interface (API) to Perl-scripts.
 %endif
 
+%if %{with_pcie_errors}
+%package pcie_errors
+Summary:       PCI Express errors plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+%description pcie_errors
+The pcie_errors plugin collects PCI Express errors from Device Status in Capability
+structure and from Advanced Error Reporting Extended Capability.
+%endif
+
 %if %{with_pinba}
 %package pinba
 Summary:       Pinba plugin for collectd
@@ -944,6 +963,27 @@ BuildRequires:     riemann-c-client-devel >= 1.6
 The riemann plugin submits values to Riemann, an event stream processor.
 %endif
 
+%if %{with_write_stackdriver}
+%package write_stackdriver
+Summary:       stackdriver plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: curl-devel, yajl-devel, openssl-devel
+%description write_stackdriver
+The write_stackdriver collectd plugin writes metrics to the
+Google Stackdriver Monitoring service.
+%endif
+
+%if %{with_gpu_nvidia}
+%package gpu_nvidia
+Summary:       stackdriver plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: cuda-nvml-dev-10-1
+%description gpu_nvidia
+The gpu_nvidia collectd plugin collects NVidia GPU metrics.
+%endif
+
 %if %{with_xencpu}
 %package xencpu
 Summary:       xencpu plugin for collectd
@@ -1552,6 +1592,12 @@ Collectd utilities
 %define _with_perl --disable-perl
 %endif
 
+%if %{with_pcie_errors}
+%define _with_pcie_errors --enable-pcie_errors
+%else
+%define _with_pcie_errors --disable-pcie_errors
+%endif
+
 %if %{with_pf}
 %define _with_pf --enable-pf
 %else
@@ -1851,12 +1897,30 @@ Collectd utilities
 %define _with_write_riemann --disable-write_riemann
 %endif
 
+%if %{with_write_stackdriver}
+%define _with_write_stackdriver --enable-write_stackdriver
+%else
+%define _with_write_stackdriver --disable-write_stackdriver
+%endif
+
+%if %{with_gpu_nvidia}
+%define _with_gpu_nvidia --enable-gpu_nvidia
+%else
+%define _with_gpu_nvidia --disable-gpu_nvidia
+%endif
+
 %if %{with_write_sensu}
 %define _with_write_sensu --enable-write_sensu
 %else
 %define _with_write_sensu --disable-write_sensu
 %endif
 
+%if %{with_write_syslog}
+%define _with_write_syslog --enable-write_syslog
+%else
+%define _with_write_syslog --disable-write_syslog
+%endif
+
 %if %{with_write_tsdb}
 %define _with_write_tsdb --enable-write_tsdb
 %else
@@ -2002,6 +2066,7 @@ Collectd utilities
        %{?_with_ovs_events} \
        %{?_with_ovs_stats} \
        %{?_with_perl} \
+       %{?_with_pcie_errors} \
        %{?_with_pf} \
        %{?_with_pinba} \
        %{?_with_ping} \
@@ -2053,7 +2118,10 @@ Collectd utilities
        %{?_with_write_prometheus} \
        %{?_with_write_redis} \
        %{?_with_write_riemann} \
+       %{?_with_write_stackdriver} \
+       %{?_with_gpu_nvidia} \
        %{?_with_write_sensu} \
+       %{?_with_write_syslog} \
        %{?_with_write_tsdb} \
        %{?_with_xencpu} \
        %{?_with_xmms} \
@@ -2388,6 +2456,9 @@ fi
 %if %{with_write_log}
 %{_libdir}/%{name}/write_log.so
 %endif
+%if %{with_write_syslog}
+%{_libdir}/%{name}/write_syslog.so
+%endif
 %if %{with_write_sensu}
 %{_libdir}/%{name}/write_sensu.so
 %endif
@@ -2656,6 +2727,11 @@ fi
 %{_libdir}/%{name}/perl.so
 %endif
 
+%if %{with_pcie_errors}
+%files pcie_errors
+%{_libdir}/%{name}/pcie_errors.so
+%endif
+
 %if %{with_pinba}
 %files pinba
 %{_libdir}/%{name}/pinba.so
@@ -2749,6 +2825,16 @@ fi
 %{_libdir}/%{name}/write_riemann.so
 %endif
 
+%if %{with_write_stackdriver}
+%files write_stackdriver
+%{_libdir}/%{name}/write_stackdriver.so
+%endif
+
+%if %{with_gpu_nvidia}
+%files write_gpu_nvidia
+%{_libdir}/%{name}/write_gpu_nvidia.so
+%endif
+
 %if %{with_xencpu}
 %files xencpu
 %{_libdir}/%{name}/xencpu.so
@@ -2771,6 +2857,11 @@ fi
 %doc contrib/
 
 %changelog
+* Fri Jun 14 2019 Fabien Wernli <rpmbuild@faxmodem.org> - 5.9.0-1
+- add code for write_stackdriver (disabled for now)
+- add code for gpu_nvidia (disabled for now)
+- add pcie_errors
+
 * Thu Sep 28 2017 Jakub Jankowski <shasta@toxcorp.com> - 5.7.1-9
 - Fix mbmon/mcelog build options
 
index c5b1142..fe535bf 100644 (file)
@@ -8,8 +8,6 @@ Requires=local-fs.target network-online.target
 ExecStart=/usr/sbin/collectd
 EnvironmentFile=-/etc/sysconfig/collectd
 EnvironmentFile=-/etc/default/collectd
-ProtectSystem=full
-ProtectHome=true
 
 # A few plugins won't work without some privileges, which you'll have to
 # specify using the CapabilityBoundingSet directive below.
index 468a237..fd733b4 100644 (file)
@@ -1309,8 +1309,8 @@ static int collectd_barometer_config(const char *key, const char *value) {
   } else if (strcasecmp(key, "Normalization") == 0) {
     int normalize_tmp = atoi(value);
     if (normalize_tmp < 0 || normalize_tmp > 2) {
-      WARNING("barometer: collectd_barometer_config: invalid normalization: %d",
-              normalize_tmp);
+      ERROR("barometer: collectd_barometer_config: invalid normalization: %d",
+            normalize_tmp);
       return 1;
     }
     config_normalize = normalize_tmp;
index 7a25655..eccd71f 100644 (file)
@@ -72,8 +72,8 @@ These are used to collect the actual data. It is called once
 per interval (see the B<Interval> configuration option of collectd). Usually
 it will call B<collectd.dispatch_values> to dispatch the values to collectd
 which will pass them on to all registered B<write functions>. If this function
-does not return 0 the plugin will be skipped for an increasing
-amount of time until it returns normally again.
+does not return 0, interval between its calls will grow until function returns
+0 again. See the B<MaxReadInterval> configuration option of collectd.
 
 =item write functions
 
@@ -90,12 +90,14 @@ The following functions are provided to Lua modules:
 
 =item register_read(callback)
 
+Function to register read callbacks.
 The callback will be called without arguments.
 If this callback function does not return 0 the next call will be delayed by
 an increasing interval.
 
-=item register_write
+=item register_write(callback)
 
+Function to register write callbacks.
 The callback function will be called with one argument passed, which will be a
 table of values.
 If this callback function does not return 0 next call will be delayed by
@@ -136,8 +138,8 @@ A very simple write function might look like:
 
 To register those functions with collectd:
 
-  collectd.register_read(read)
-  collectd.register_write(write)
+  collectd.register_read(read)     -- pass function as variable
+  collectd.register_write("write") -- pass by global-scope function name
 
 =back
 
@@ -150,12 +152,12 @@ L<lua(1)>,
 =head1 AUTHOR
 
 The C<Lua plugin> has been written by
-Julien Ammous E<lt>j.ammous<nbsp>atE<nbsp>gmail.comE<gt>,
+Julien Ammous E<lt>j.ammousE<nbsp>atE<nbsp>gmail.comE<gt>,
 Florian Forster E<lt>octoE<nbsp>atE<nbsp>collectd.orgE<gt> and
-Ruben Kerkhof E<lt>ruben<nbsp>atE<nbsp>rubenkerkhof.com<gt> and
+Ruben Kerkhof E<lt>rubenE<nbsp>atE<nbsp>rubenkerkhof.comE<gt>.
 
 This manpage has been written by Ruben Kerkhof
-E<lt>ruben<nbsp>atE<nbsp>rubenkerkhof.com<gt>.
+E<lt>rubenE<nbsp>atE<nbsp>rubenkerkhof.comE<gt>.
 It is based on the L<collectd-perl(5)> manual page by
 Florian Forster E<lt>octoE<nbsp>atE<nbsp>collectd.orgE<gt> and
 Sebastian Harl E<lt>shE<nbsp>atE<nbsp>tokkee.orgE<gt>.
index 9d508d1..493f5ec 100644 (file)
@@ -76,9 +76,11 @@ internal format and dispatches them. Depending on the write plugins you have
 loaded they may be written to disk or submitted to another instance or
 whatever you configured.
 
-Because querying a host via SNMP may produce a timeout multiple threads are
-used to query hosts in parallel. Depending on the number of hosts between one
-and ten threads are used.
+Because querying a host via SNMP may produce a timeout the "complex reads"
+polling method is used. The ReadThreads parameter in the main configuration
+influences the number of parallel polling jobs which can be undertaken. If
+you expect timeouts or some polling to take a long time, you should increase
+this parameter. Note that other plugins also use the same threads.
 
 =head1 CONFIGURATION
 
@@ -338,7 +340,7 @@ Selects the authentication protocol for SNMPv3 security.
 
 =item B<AuthPassphrase> I<Passphrase>
 
-Sets the authentication passphrase for SNMPv3 security. 
+Sets the authentication passphrase for SNMPv3 security.
 
 =item B<PrivacyProtocol> I<AES>|I<DES>
 
@@ -346,7 +348,7 @@ Selects the privacy (encryption) protocol for SNMPv3 security.
 
 =item B<PrivacyPassphrase> I<Passphrase>
 
-Sets the privacy (encryption) passphrase for SNMPv3 security. 
+Sets the privacy (encryption) passphrase for SNMPv3 security.
 
 =item B<Collect> I<Data> [I<Data> ...]
 
index f58d3b4..f09f373 100644 (file)
 #@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_SYSLOG_TRUE@LoadPlugin write_syslog
 #@BUILD_PLUGIN_WRITE_TSDB_TRUE@LoadPlugin write_tsdb
 #@BUILD_PLUGIN_XENCPU_TRUE@LoadPlugin xencpu
 #@BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
 #<Plugin curl>
 #  <Page "stock_quotes">
 #    URL "http://finance.google.com/finance?q=NYSE%3AAMD"
+#    AddressFamily "any"
 #    User "foo"
 #    Password "bar"
 #    Digest false
 
 #<Plugin curl_json>
 #  <URL "http://localhost:80/test.json">
+#    AddressFamily "any"
 #    Instance "test_http_json"
 #    <Key "testArray/0">
 #      Type "gauge"
 # }
 ## See: http://wiki.apache.org/couchdb/Runtime_Statistics
 #  <URL "http://localhost:5984/_stats">
+#    AddressFamily "ipv4"
 #    Instance "httpd"
 #    <Key "httpd/requests/count">
 #      Type "http_requests"
 #  </URL>
 ## Database status metrics:
 #  <URL "http://localhost:5984/_all_dbs">
+#    AddressFamily "ipv6"
 #    Instance "dbs"
 #    <Key "*/doc_count">
 #      Type "gauge"
 
 #<Plugin curl_xml>
 #  <URL "http://localhost/stats.xml">
+#    AddressFamily "any"
 #    Host "my_host"
 #    #Plugin "stats"
 #    Instance "some_instance"
 
 #<Plugin "intel_rdt">
 #  Cores "0-2"
+#  Processes "sshd"
 #</Plugin>
 
 #<Plugin interface>
 #      InterfaceFormat name
 #      PluginInstanceFormat name
 #      Instances 1
-#      ExtraStats "cpu_util disk disk_err domain_state fs_info job_stats_background pcpu perf vcpupin"
+#      ExtraStats "cpu_util disk disk_err domain_state fs_info job_stats_background pcpu perf vcpu vcpupin disk_physical disk_allocation disk_capacity memory"
 #      PersistentNotification false
 #</Plugin>
 
 #    SeparateInstances false
 #    PreserveSeparator false
 #    DropDuplicateFields false
+#    ReverseHost false
 #  </Node>
 #</Plugin>
 
 #  Url "https://monitoring.googleapis.com/v3"
 #</Plugin>
 
+#<Plugin write_syslog>
+#      <Node>
+#              Host "localhost"
+#              Port "44514"
+#              Prefix "collectd"
+#              MessageFormat "human"
+#              HostTags ""
+#              StoreRates false
+#              AlwaysAppendDS false
+#      </Node>
+#</Plugin>
+
 #<Plugin write_tsdb>
 #      <Node>
 #              Host "localhost"
index a38a998..ed49195 100644 (file)
@@ -1656,15 +1656,24 @@ Defaults to B<true>.
 
 =head2 Plugin C<cpufreq>
 
-This plugin doesn't have any options. It reads
+This plugin is available on Linux and FreeBSD only.  It doesn't have any
+options.  On Linux it reads
 F</sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq> (for the first CPU
 installed) to get the current CPU frequency. If this file does not exist make
 sure B<cpufreqd> (L<http://cpufreqd.sourceforge.net/>) or a similar tool is
 installed and an "cpu governor" (that's a kernel module) is loaded.
 
-If the system has the I<cpufreq-stats> kernel module loaded, this plugin reports
-the rate of p-state (cpu frequency) transitions and the percentage of time spent
-in each p-state.
+On Linux, if the system has the I<cpufreq-stats> kernel module loaded, this
+plugin reports the rate of p-state (cpu frequency) transitions and the
+percentage of time spent in each p-state.
+
+On FreeBSD it does a sysctl dev.cpu.0.freq and submits this as instance 0.
+At this time FreeBSD only has one frequency setting for all cores.
+See the BUGS section in the FreeBSD man page for cpufreq(4) for more details.
+
+On FreeBSD the plugin checks the success of sysctl dev.cpu.0.freq and
+unregisters the plugin when this fails.  A message will be logged to indicate
+this.
 
 =head2 Plugin C<cpusleep>
 
@@ -1795,6 +1804,7 @@ finance page and dispatch the value to collectd.
     <Page "stock_quotes">
       Plugin "quotes"
       URL "http://finance.google.com/finance?q=NYSE%3AAMD"
+      AddressFamily "any"
       User "foo"
       Password "bar"
       Digest false
@@ -1835,6 +1845,18 @@ Defaults to C<curl>.
 URL of the web site to retrieve. Since a regular expression will be used to
 extract information from this data, non-binary data is a big plus here ;)
 
+=item B<AddressFamily> I<Type>
+
+IP version to resolve URL to. Useful in cases when hostname in URL resolves
+to both IPv4 and IPv6 addresses, and you are interested in using one of them
+specifically.
+Use C<ipv4> to enforce IPv4, C<ipv6> to enforce IPv6, or C<any> to keep the
+default behavior of resolving addresses to all IP versions your system allows.
+If C<libcurl> is compiled without IPv6 support, using C<ipv6> will result in
+a warning and fallback to C<any>.
+If C<Type> cannot be parsed, a warning will be printed and the whole B<Page>
+block will be ignored.
+
 =item B<User> I<Name>
 
 Username to use if authorization is required to read the page.
@@ -1946,6 +1968,7 @@ C<_stats> runtime statistics module of I<CouchDB>
 
   <Plugin curl_json>
     <URL "http://localhost:5984/_stats">
+      AddressFamily "any"
       Instance "httpd"
       <Key "httpd/requests/count">
         Type "http_requests"
@@ -1990,6 +2013,18 @@ The following options are valid within B<URL> blocks:
 
 =over 4
 
+=item B<AddressFamily> I<Type>
+
+IP version to resolve URL to. Useful in cases when hostname in URL resolves
+to both IPv4 and IPv6 addresses, and you are interested in using one of them
+specifically.
+Use C<ipv4> to enforce IPv4, C<ipv6> to enforce IPv6, or C<any> to keep the
+default behavior of resolving addresses to all IP versions your system allows.
+If C<libcurl> is compiled without IPv6 support, using C<ipv6> will result in
+a warning and fallback to C<any>.
+If C<Type> cannot be parsed, a warning will be printed and the whole B<URL>
+block will be ignored.
+
 =item B<Host> I<Name>
 
 Use I<Name> as the host name when submitting values. Defaults to the global
@@ -2061,6 +2096,7 @@ The B<curl_xml plugin> uses B<libcurl> (L<http://curl.haxx.se/>) and B<libxml2>
 
  <Plugin "curl_xml">
    <URL "http://localhost/stats.xml">
+     AddressFamily "any"
      Host "my_host"
      #Plugin "curl_xml"
      Instance "some_instance"
@@ -2097,6 +2133,18 @@ Within the B<URL> block the following options are accepted:
 
 =over 4
 
+=item B<AddressFamily> I<Type>
+
+IP version to resolve URL to. Useful in cases when hostname in URL resolves
+to both IPv4 and IPv6 addresses, and you are interested in using one of them
+specifically.
+Use C<ipv4> to enforce IPv4, C<ipv6> to enforce IPv6, or C<any> to keep the
+default behavior of resolving addresses to all IP versions your system allows.
+If C<libcurl> is compiled without IPv6 support, using C<ipv6> will result in
+a warning and fallback to C<any>.
+If C<Type> cannot be parsed, a warning will be printed and the whole B<URL>
+block will be ignored.
+
 =item B<Host> I<Name>
 
 Use I<Name> as the host name when submitting values. Defaults to the global
@@ -3498,6 +3546,7 @@ B<Synopsis:>
 
   <Plugin "intel_rdt">
     Cores "0-2" "3,4,6" "8-10,15"
+    Processes "sshd,qemu-system-x86" "bash"
   </Plugin>
 
 B<Options:>
@@ -3513,11 +3562,10 @@ recommended to set interval higher than 1 sec.
 
 =item B<Cores> I<cores groups>
 
-All events are reported on a per core basis. Monitoring of the events can be
-configured for group of cores (aggregated statistics). This field defines groups
-of cores on which to monitor supported events. The field is represented as list
-of strings with core group values. Each string represents a list of cores in a
-group. Allowed formats are:
+Monitoring of the events can be configured for group of cores
+(aggregated statistics). This field defines groups of cores on which to monitor
+supported events. The field is represented as list of strings with core group
+values. Each string represents a list of cores in a group. Allowed formats are:
     0,1,2,3
     0-10,20-18
     1,3,5-8,10,0x10-12
@@ -3525,6 +3573,15 @@ group. Allowed formats are:
 If an empty string is provided as value for this field default cores
 configuration is applied - a separate group is created for each core.
 
+=item B<Processes> I<process names groups>
+
+Monitoring of the events can be configured for group of processes
+(aggregated statistics). This field defines groups of processes on which to
+monitor supported events. The field is represented as list of strings with
+process names group values. Each string represents a list of processes in a
+group. Allowed format is:
+    sshd,bash,qemu
+
 =back
 
 B<Note:> By default global interval is used to retrieve statistics on monitored
@@ -4444,6 +4501,12 @@ For Modbus/RTU, specifies the path to the serial device being used.
 For Modbus/RTU, specifies the baud rate of the serial device.
 Note, connections currently support only 8/N/1.
 
+=item B<UARTType> I<UARTType>
+
+For Modbus/RTU, specifies the type of the serial device.
+RS232, RS422 and RS485 are supported. Defaults to RS232.
+Available only on Linux systems with libmodbus>=2.9.4.
+
 =item B<Interval> I<Interval>
 
 Sets the interval (in seconds) in which the values will be collected from this
@@ -9252,13 +9315,51 @@ surrounded by I</.../> and collectd was compiled with support for regexps.
 
 The default is to collect statistics for all domains and all their devices.
 
-Example:
+B<Note:> B<BlockDevice> and B<InterfaceDevice> options are related to
+corresponding B<*Format> options. Specifically, B<BlockDevice> filtering depends
+on B<BlockDeviceFormat> setting - if user wants to filter block devices by
+'target' name then B<BlockDeviceFormat> option has to be set to 'target' and
+B<BlockDevice> option must be set to a valid block device target
+name("/:hdb/"). Mixing formats and filter values from different worlds (i.e.,
+using 'target' name as B<BlockDevice> value with B<BlockDeviceFormat> set to
+'source') may lead to unexpected results (all devices filtered out or all
+visible, depending on the value of B<IgnoreSelected> option).
+Similarly, option B<InterfaceDevice> is related to B<InterfaceFormat> setting
+(i.e., when user wants to use MAC address as a filter then B<InterfaceFormat>
+has to be set to 'address' - using wrong type here may filter out all of the
+interfaces).
+
+B<Example 1:>
+
+Ignore all I<hdb> devices on any domain, but other block devices (eg. I<hda>)
+will be collected:
 
  BlockDevice "/:hdb/"
  IgnoreSelected "true"
+ BlockDeviceFormat "target"
 
-Ignore all I<hdb> devices on any domain, but other block devices (eg. I<hda>)
-will be collected.
+B<Example 2:>
+
+Collect metrics only for block device on 'baremetal0' domain when its
+'source' matches given path:
+
+ BlockDevice "baremetal0:/var/lib/libvirt/images/baremetal0.qcow2"
+ BlockDeviceFormat source
+
+As you can see it is possible to filter devices/interfaces using
+various formats - for block devices 'target' or 'source' name can be
+used.  Interfaces can be filtered using 'name', 'address' or 'number'.
+
+B<Example 3:>
+
+Collect metrics only for domains 'baremetal0' and 'baremetal1' and
+ignore any other domain:
+
+ Domain "baremetal0"
+ Domain "baremetal1"
+
+It is possible to filter multiple block devices/domains/interfaces by
+adding multiple filtering entries in separate lines.
 
 =item B<BlockDeviceFormat> B<target>|B<source>
 
@@ -9289,6 +9390,11 @@ to C<sda>.
 Setting C<BlockDeviceFormat source> will cause the I<type instance> to be set
 to C<var_lib_libvirt_images_image1.qcow2>.
 
+B<Note:> this option determines also what field will be used for
+filtering over block devices (filter value in B<BlockDevice>
+will be applied to target or source). More info about filtering
+block devices can be found in the description of B<BlockDevice>.
+
 =item B<BlockDeviceFormatBasename> B<false>|B<true>
 
 The B<BlockDeviceFormatBasename> controls whether the full path or the
@@ -9339,6 +9445,11 @@ interface path might change between reboots of a guest or across migrations.
 
 B<number> means use the interface's number in guest.
 
+B<Note:> this option determines also what field will be used for
+filtering over interface device (filter value in B<InterfaceDevice>
+will be applied to name, address or number).  More info about filtering
+interfaces can be found in the description of B<InterfaceDevice>.
+
 =item B<PluginInstanceFormat> B<name|uuid|metadata|none>
 
 When the virt plugin logs data, it sets the plugin_instance of the collected
@@ -9394,9 +9505,7 @@ I<0.9.5> or later.
 =item B<disk_err>: report disk errors if any occured. Requires libvirt API version
 I<0.9.10> or later.
 
-=item B<domain_state>: report domain state and reason in human-readable format as
-a notification. If libvirt API version I<0.9.2> or later is available, domain
-reason will be included in notification.
+=item B<domain_state>: report domain state and reason as 'domain_state' metric.
 
 =item B<fs_info>: report file system information as a notification. Requires
 libvirt API version I<1.2.11> or later. Can be collected only if I<Guest Agent>
@@ -9411,6 +9520,9 @@ Requires libvirt API version I<1.2.9> or later.
 a domain. Only one type of job statistics can be collected at the same time.
 Requires libvirt API version I<1.2.9> or later.
 
+=item B<memory>: report statistics about memory usage details, provided
+by libvirt virDomainMemoryStats() function.
+
 =item B<pcpu>: report the physical user/system cpu time consumed by the hypervisor, per-vm.
 Requires libvirt API version I<0.9.11> or later.
 
@@ -9419,8 +9531,22 @@ metrics they must be enabled for domain and supported by the platform. Requires
 libvirt API version I<1.3.3> or later.
 B<Note>: I<perf> metrics can't be collected if I<intel_rdt> plugin is enabled.
 
+=item B<vcpu>: report domain virtual CPUs utilisation.
+
 =item B<vcpupin>: report pinning of domain VCPUs to host physical CPUs.
 
+=item B<disk_physical>: report 'disk_physical' statistic for disk device.
+B<Note>: This statistic is only reported for disk devices with 'source'
+property available.
+
+=item B<disk_allocation>: report 'disk_allocation' statistic for disk device.
+B<Note>: This statistic is only reported for disk devices with 'source'
+property available.
+
+=item B<disk_capacity>: report 'disk_capacity' statistic for disk device.
+B<Note>: This statistic is only reported for disk devices with 'source'
+property available.
+
 =back
 
 =item B<PersistentNotification> B<true>|B<false>
@@ -9510,6 +9636,7 @@ Synopsis:
      LogSendErrors true
      Prefix "collectd"
      UseTags false
+     ReverseHost false
    </Node>
  </Plugin>
 
@@ -9621,6 +9748,30 @@ are not used.
 
 Default value: B<false>.
 
+=item B<ReverseHost> B<false>|B<true>
+
+If set to B<true>, the (dot separated) parts of the B<host> field of the
+I<value list> will be rewritten in reverse order. The rewrite happens I<before>
+special characters are replaced with the B<EscapeCharacter>.
+
+This option might be convenient if the metrics are presented with Graphite in a
+DNS like tree structure (probably without replacing dots in hostnames).
+
+Example:
+ Hostname "node3.cluster1.example.com"
+ LoadPlugin "cpu"
+ LoadPlugin "write_graphite"
+ <Plugin "write_graphite">
+  <Node "graphite.example.com">
+   EscapeCharacter "."
+   ReverseHost true
+  </Node>
+ </Plugin>
+
+ result on the wire: com.example.cluster1.node3.cpu-0.cpu-idle 99.900993 1543010932
+
+Default value: B<false>.
+
 =back
 
 =head2 Plugin C<write_log>
@@ -10596,6 +10747,141 @@ C<https://monitoring.googleapis.com/v3>.
 
 =back
 
+=head2 Plugin C<write_syslog>
+
+The C<write_syslog> plugin writes data in I<syslog> format log messages.
+It implements the basic syslog protocol, RFC 5424, extends it with
+content-based filtering, rich filtering capabilities,
+flexible configuration options and adds features such as using TCP for transport.
+The plugin can connect to a I<Syslog> daemon, like syslog-ng and rsyslog, that will
+ingest metrics, transform and ship them to the specified output.
+The plugin uses I<TCP> over the "line based" protocol with a default port 44514.
+The data will be sent in blocks of at most 1428 bytes to minimize the number of
+network packets.
+
+Synopsis:
+
+ <Plugin write_syslog>
+   ResolveInterval 60
+   ResolveJitter 60
+   <Node "example">
+     Host "syslog-1.my.domain"
+     Port "44514"
+     Prefix "collectd"
+     MessageFormat "human"
+     HostTags ""
+   </Node>
+ </Plugin>
+
+The configuration consists of one or more E<lt>B<Node>E<nbsp>I<Name>E<gt>
+blocks and global directives.
+
+Global directives are:
+
+=over 4
+
+=item B<ResolveInterval> I<seconds>
+
+=item B<ResolveJitter> I<seconds>
+
+When I<collectd> connects to a syslog node, it will request the hostname from
+DNS. This can become a problem if the syslog node is unavailable or badly
+configured because collectd will request DNS in order to reconnect for every
+metric, which can flood your DNS. So you can cache the last value for
+I<ResolveInterval> seconds.
+Defaults to the I<Interval> of the I<write_syslog plugin>, e.g. 10E<nbsp>seconds.
+
+You can also define a jitter, a random interval to wait in addition to
+I<ResolveInterval>. This prevents all your collectd servers to resolve the
+hostname at the same time when the connection fails.
+Defaults to the I<Interval> of the I<write_syslog plugin>, e.g. 10E<nbsp>seconds.
+
+B<Note:> If the DNS resolution has already been successful when the socket
+closes, the plugin will try to reconnect immediately with the cached
+information. DNS is queried only when the socket is closed for a longer than
+I<ResolveInterval> + I<ResolveJitter> seconds.
+
+=back
+
+Inside the B<Node> blocks, the following options are recognized:
+
+=over 4
+
+=item B<Host> I<Address>
+
+Hostname or address to connect to. Defaults to C<localhost>.
+
+=item B<Port> I<Service>
+
+Service name or port number to connect to. Defaults to C<44514>.
+
+
+=item B<HostTags> I<String>
+
+When set, I<HostTags> is added to the end of the metric.
+It is intended to be used for adding additional metadata to tag the metric with.
+Dots and whitespace are I<not> escaped in this string.
+
+Examples:
+
+When MessageFormat is set to "human".
+
+  ["prefix1" "example1"="example1_v"]["prefix2" "example2"="example2_v"]"
+
+When MessageFormat is set to "JSON", text should be in JSON format.
+Escaping the quotation marks is required.
+
+  HostTags "\"prefix1\": {\"example1\":\"example1_v\",\"example2\":\"example2_v\"}"
+
+=item B<MessageFormat> I<String>
+
+I<MessageFormat> selects the format in which messages are sent to the
+syslog deamon, human or JSON. Defaults to human.
+
+Syslog message format:
+
+<priority>VERSION ISOTIMESTAMP HOSTNAME APPLICATION PID MESSAGEID STRUCTURED-DATA MSG
+
+The difference between the message formats are in the STRUCTURED-DATA and MSG parts.
+
+Human format:
+
+  <166>1 ISOTIMESTAMP HOSTNAME collectd PID MESSAGEID
+  ["collectd" "value": "v1" "plugin"="plugin_v" "plugin_instance"="plugin_instance_v"
+  "type_instance"="type_instance_v" "type"="type_v" "ds_name"="ds_name_v" "interval"="interval_v" ]
+  "host_tag_example"="host_tag_example_v" plugin_v.type_v.ds_name_v="v1"
+
+JSON format:
+
+  <166>1 ISOTIMESTAMP HOSTNAME collectd PID MESSAGEID STRUCTURED-DATA
+  {
+    "collectd": {
+    "time": time_as_epoch, "interval": interval_v, "plugin": "plugin_v",
+    "plugin_instance": "plugin_instance_v", "type":"type_v",
+    "type_instance": "type_instance_v", "plugin_v": {"type_v": v1}
+    } , "host":"host_v", "host_tag_example": "host_tag_example_v"
+  }
+
+=item B<StoreRates> B<false>|B<true>
+
+If set to B<true>, convert counter values to rates. If set to B<false>
+(the default) counter values are stored as is, as an increasing
+integer number.
+
+=item B<AlwaysAppendDS> B<false>|B<true>
+
+If set to B<true>, append the name of the I<Data Source> (DS) to the "metric"
+identifier. If set to B<false> (the default), this is only done when there is
+more than one DS.
+
+=item B<Prefix> I<String>
+
+When set, I<Prefix> is added to all metrics names as a prefix. It is intended in
+case you want to be able to define the source of the specific metric. Dots and
+whitespace are I<not> escaped in this string.
+
+=back
+
 =head2 Plugin C<xencpu>
 
 This plugin collects metrics of hardware CPU load for machine running Xen
@@ -11584,7 +11870,7 @@ be an FQDN.
 =head1 IGNORELISTS
 
 B<Ignorelists> are a generic framework to either ignore some metrics or report
-specific metircs only. Plugins usually provide one or more options to specify
+specific metrics only. Plugins usually provide one or more options to specify
 the items (mounts points, devices, ...) and the boolean option
 C<IgnoreSelected>.
 
index 35ec07f..f95b282 100644 (file)
 #include "plugin.h"
 #include "utils/common/common.h"
 
+#if KERNEL_FREEBSD
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#endif
+
+#if KERNEL_LINUX
 #define MAX_AVAIL_FREQS 20
 
 static int num_cpu;
@@ -71,8 +77,10 @@ static void cpufreq_stats_init(void) {
   }
   return;
 }
+#endif /* KERNEL_LINUX */
 
 static int cpufreq_init(void) {
+#if KERNEL_LINUX
   char filename[PATH_MAX];
 
   num_cpu = 0;
@@ -96,6 +104,16 @@ static int cpufreq_init(void) {
 
   if (num_cpu == 0)
     plugin_unregister_read("cpufreq");
+#elif KERNEL_FREEBSD
+  char mib[] = "dev.cpu.0.freq";
+  int cpufreq;
+  size_t cf_len = sizeof(cpufreq);
+
+  if (sysctlbyname(mib, &cpufreq, &cf_len, NULL, 0) != 0) {
+    WARNING("cpufreq plugin: sysctl \"%s\" failed.", mib);
+    plugin_unregister_read("cpufreq");
+  }
+#endif
 
   return 0;
 } /* int cpufreq_init */
@@ -116,6 +134,7 @@ static void cpufreq_submit(int cpu_num, const char *type,
   plugin_dispatch_values(&vl);
 }
 
+#if KERNEL_LINUX
 static void cpufreq_read_stats(int cpu) {
   char filename[PATH_MAX];
   /* Read total transitions for cpu frequency */
@@ -184,8 +203,10 @@ static void cpufreq_read_stats(int cpu) {
   }
   fclose(fh);
 }
+#endif /* KERNEL_LINUX */
 
 static int cpufreq_read(void) {
+#if KERNEL_LINUX
   for (int cpu = 0; cpu < num_cpu; cpu++) {
     char filename[PATH_MAX];
     /* Read cpu frequency */
@@ -206,6 +227,23 @@ static int cpufreq_read(void) {
     if (report_p_stats)
       cpufreq_read_stats(cpu);
   }
+#elif KERNEL_FREEBSD
+  /* FreeBSD currently only has 1 freq setting.  See BUGS in cpufreq(4) */
+  char mib[] = "dev.cpu.0.freq";
+  int cpufreq;
+  size_t cf_len = sizeof(cpufreq);
+
+  if (sysctlbyname(mib, &cpufreq, &cf_len, NULL, 0) != 0) {
+    WARNING("cpufreq plugin: sysctl \"%s\" failed.", mib);
+    return 0;
+  }
+
+  value_t v;
+  /* convert Mhz to Hz */
+  v.gauge = cpufreq * 1000000.0;
+
+  cpufreq_submit(0, "cpufreq", NULL, &v);
+#endif
   return 0;
 } /* int cpufreq_read */
 
index 9ad3dc8..e3e3c17 100644 (file)
@@ -57,6 +57,7 @@ struct web_page_s /* {{{ */
   char *instance;
 
   char *url;
+  int address_family;
   char *user;
   char *pass;
   char *credentials;
@@ -345,6 +346,7 @@ static int cc_page_init_curl(web_page_t *wp) /* {{{ */
   curl_easy_setopt(wp->curl, CURLOPT_ERRORBUFFER, wp->curl_errbuf);
   curl_easy_setopt(wp->curl, CURLOPT_FOLLOWLOCATION, 1L);
   curl_easy_setopt(wp->curl, CURLOPT_MAXREDIRS, 50L);
+  curl_easy_setopt(wp->curl, CURLOPT_IPRESOLVE, wp->address_family);
 
   if (wp->user != NULL) {
 #ifdef HAVE_CURLOPT_USERNAME
@@ -411,6 +413,7 @@ static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */
   }
   page->plugin_name = NULL;
   page->url = NULL;
+  page->address_family = CURL_IPRESOLVE_WHATEVER;
   page->user = NULL;
   page->pass = NULL;
   page->digest = false;
@@ -437,7 +440,34 @@ static int cc_config_add_page(oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string(child, &page->plugin_name);
     else if (strcasecmp("URL", child->key) == 0)
       status = cf_util_get_string(child, &page->url);
-    else if (strcasecmp("User", child->key) == 0)
+    else if (strcasecmp("AddressFamily", child->key) == 0) {
+      char *af = NULL;
+      status = cf_util_get_string(child, &af);
+      if (status != 0 || af == NULL) {
+        WARNING("curl plugin: Cannot parse value of `%s' "
+                "for instance `%s'.",
+                child->key, page->instance);
+      } else if (strcasecmp("any", af) == 0) {
+        page->address_family = CURL_IPRESOLVE_WHATEVER;
+      } else if (strcasecmp("ipv4", af) == 0) {
+        page->address_family = CURL_IPRESOLVE_V4;
+      } else if (strcasecmp("ipv6", af) == 0) {
+        /* If curl supports ipv6, use it. If not, log a warning and
+         * fall back to default - don't set status to non-zero.
+         */
+        curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
+        if (curl_info->features & CURL_VERSION_IPV6)
+          page->address_family = CURL_IPRESOLVE_V6;
+        else
+          WARNING("curl plugin: IPv6 not supported by this libCURL. "
+                  "Using fallback `any'.");
+      } else {
+        WARNING("curl plugin: Unsupported value of `%s' "
+                "for instance `%s'.",
+                child->key, page->instance);
+        status = -1;
+      }
+    } else if (strcasecmp("User", child->key) == 0)
       status = cf_util_get_string(child, &page->user);
     else if (strcasecmp("Password", child->key) == 0)
       status = cf_util_get_string(child, &page->pass);
index a26664f..9d01e6e 100644 (file)
@@ -88,6 +88,7 @@ struct cj_s /* {{{ */
   char *sock;
 
   char *url;
+  int address_family;
   char *user;
   char *pass;
   char *credentials;
@@ -582,6 +583,7 @@ static int cj_init_curl(cj_t *db) /* {{{ */
   curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
   curl_easy_setopt(db->curl, CURLOPT_FOLLOWLOCATION, 1L);
   curl_easy_setopt(db->curl, CURLOPT_MAXREDIRS, 50L);
+  curl_easy_setopt(db->curl, CURLOPT_IPRESOLVE, db->address_family);
 
   if (db->user != NULL) {
 #ifdef HAVE_CURLOPT_USERNAME
@@ -649,6 +651,7 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
   }
 
   db->timeout = -1;
+  db->address_family = CURL_IPRESOLVE_WHATEVER;
 
   if (strcasecmp("URL", ci->key) == 0)
     status = cf_util_get_string(ci, &db->url);
@@ -702,6 +705,31 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
       db->stats = curl_stats_from_config(child);
       if (db->stats == NULL)
         status = -1;
+    } else if (db->url && strcasecmp("AddressFamily", child->key) == 0) {
+      char *af = NULL;
+      status = cf_util_get_string(child, &af);
+      if (status != 0 || af == NULL) {
+        WARNING("curl_json plugin: Cannot parse value of `%s' for URL `%s'.",
+                child->key, db->url);
+      } else if (strcasecmp("any", af) == 0) {
+        db->address_family = CURL_IPRESOLVE_WHATEVER;
+      } else if (strcasecmp("ipv4", af) == 0) {
+        db->address_family = CURL_IPRESOLVE_V4;
+      } else if (strcasecmp("ipv6", af) == 0) {
+        /* If curl supports ipv6, use it. If not, log a warning and
+         * fall back to default - don't set status to non-zero.
+         */
+        curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
+        if (curl_info->features & CURL_VERSION_IPV6)
+          db->address_family = CURL_IPRESOLVE_V6;
+        else
+          WARNING("curl_json plugin: IPv6 not supported by this libCURL. "
+                  "Using fallback `any'.");
+      } else {
+        WARNING("curl_json plugin: Unsupported value of `%s' for URL `%s'.",
+                child->key, db->url);
+        status = -1;
+      }
     } else {
       WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
       status = -1;
index ed70f69..ce6bd46 100644 (file)
@@ -76,6 +76,7 @@ struct cx_s /* {{{ */
   char *host;
 
   char *url;
+  int address_family;
   char *user;
   char *pass;
   char *credentials;
@@ -736,6 +737,7 @@ static int cx_init_curl(cx_t *db) /* {{{ */
   curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
   curl_easy_setopt(db->curl, CURLOPT_FOLLOWLOCATION, 1L);
   curl_easy_setopt(db->curl, CURLOPT_MAXREDIRS, 50L);
+  curl_easy_setopt(db->curl, CURLOPT_IPRESOLVE, db->address_family);
 
   if (db->user != NULL) {
 #ifdef HAVE_CURLOPT_USERNAME
@@ -814,6 +816,7 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
   }
 
   db->timeout = -1;
+  db->address_family = CURL_IPRESOLVE_WHATEVER;
 
   int status = cf_util_get_string(ci, &db->url);
   if (status != 0) {
@@ -863,6 +866,31 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
       db->stats = curl_stats_from_config(child);
       if (db->stats == NULL)
         status = -1;
+    } else if (strcasecmp("AddressFamily", child->key) == 0) {
+      char *af = NULL;
+      status = cf_util_get_string(child, &af);
+      if (status != 0 || af == NULL) {
+        WARNING("curl_xml plugin: Cannot parse value of `%s' for URL `%s'.",
+                child->key, db->url);
+      } else if (strcasecmp("any", af) == 0) {
+        db->address_family = CURL_IPRESOLVE_WHATEVER;
+      } else if (strcasecmp("ipv4", af) == 0) {
+        db->address_family = CURL_IPRESOLVE_V4;
+      } else if (strcasecmp("ipv6", af) == 0) {
+        /* If curl supports ipv6, use it. If not, log a warning and
+         * fall back to default - don't set status to non-zero.
+         */
+        curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
+        if (curl_info->features & CURL_VERSION_IPV6)
+          db->address_family = CURL_IPRESOLVE_V6;
+        else
+          WARNING("curl_xml plugin: IPv6 not supported by this libCURL. "
+                  "Using fallback `any'.");
+      } else {
+        WARNING("curl_xml plugin: Unsupported value of `%s' for URL `%s'.",
+                child->key, db->url);
+        status = -1;
+      }
     } else {
       WARNING("curl_xml plugin: Option `%s' not allowed here.", child->key);
       status = -1;
index 28fa715..ae2d327 100644 (file)
@@ -357,8 +357,7 @@ static int configure_collectd(struct cmdline_config *config) {
    * Also, this will automatically load modules.
    */
   if (cf_read(config->configfile)) {
-    fprintf(stderr, "Error: Reading the config file failed!\n"
-                    "Read the logs for details.\n");
+    fprintf(stderr, "Error: Parsing the config file failed!\n");
     return 1;
   }
 
index 8bf6b8d..5cf8ac1 100644 (file)
@@ -267,7 +267,8 @@ static int dispatch_loadplugin(oconfig_item_t *ci) {
 
   /* default to the global interval set before loading this plugin */
   plugin_ctx_t ctx = {
-      .interval = cf_get_default_interval(), .name = strdup(name),
+      .interval = cf_get_default_interval(),
+      .name = strdup(name),
   };
   if (ctx.name == NULL)
     return ENOMEM;
@@ -411,10 +412,16 @@ static int dispatch_block_plugin(oconfig_item_t *ci) {
   }
 
   /* Hm, no complex plugin found. Dispatch the values one by one */
-  for (int i = 0; i < ci->children_num; i++) {
-    if (ci->children[i].children == NULL)
-      dispatch_value_plugin(name, ci->children + i);
-    else {
+  for (int i = 0, ret = 0; i < ci->children_num; i++) {
+    if (ci->children[i].children == NULL) {
+      oconfig_item_t *child = ci->children + i;
+      ret = dispatch_value_plugin(name, child);
+      if (ret != 0) {
+        ERROR("Plugin %s failed to handle option %s, return code: %i", name,
+              child->key, ret);
+        return ret;
+      }
+    } else {
       WARNING("There is a `%s' block within the "
               "configuration for the %s plugin. "
               "The plugin either only expects "
@@ -473,9 +480,9 @@ static int cf_ci_replace_child(oconfig_item_t *dst, oconfig_item_t *src,
     return 0;
   }
 
-  temp = realloc(dst->children,
-                 sizeof(oconfig_item_t) *
-                     (dst->children_num + src->children_num - 1));
+  temp =
+      realloc(dst->children, sizeof(oconfig_item_t) *
+                                 (dst->children_num + src->children_num - 1));
   if (temp == NULL) {
     ERROR("configfile: realloc failed.");
     return -1;
@@ -514,9 +521,8 @@ static int cf_ci_append_children(oconfig_item_t *dst, oconfig_item_t *src) {
   if ((src == NULL) || (src->children_num == 0))
     return 0;
 
-  temp =
-      realloc(dst->children,
-              sizeof(oconfig_item_t) * (dst->children_num + src->children_num));
+  temp = realloc(dst->children, sizeof(oconfig_item_t) *
+                                    (dst->children_num + src->children_num));
   if (temp == NULL) {
     ERROR("configfile: realloc failed.");
     return -1;
@@ -804,7 +810,7 @@ static oconfig_item_t *cf_read_generic(const char *path, const char *pattern,
 
   return root;
 } /* oconfig_item_t *cf_read_generic */
-/* #endif HAVE_WORDEXP_H */
+  /* #endif HAVE_WORDEXP_H */
 
 #else  /* if !HAVE_WORDEXP_H */
 static oconfig_item_t *cf_read_generic(const char *path, const char *pattern,
index a76a44c..85f1b11 100644 (file)
  * DEALINGS IN THE SOFTWARE.
  **/
 
-#include "globals.h"
+// clang-format off
+/*
+ * Explicit order is required or _FILE_OFFSET_BITS will have definition mismatches on Solaris
+ * See Github Issue #3193 for details
+ */
 #include "utils/common/common.h"
+#include "globals.h"
+// clang-format on
 
 #if HAVE_KSTAT_H
 #include <kstat.h>
index 9c61040..7eb3cb2 100644 (file)
@@ -96,8 +96,6 @@ static int parse_ds(data_source_t *dsrc, char *buf, size_t buf_len) {
 static void parse_line(char *buf) {
   char *fields[64];
   size_t fields_num;
-  data_set_t *ds;
-
   fields_num = strsplit(buf, fields, 64);
   if (fields_num < 2)
     return;
@@ -106,33 +104,27 @@ static void parse_line(char *buf) {
   if (fields[0][0] == '#')
     return;
 
-  ds = calloc(1, sizeof(*ds));
-  if (ds == NULL)
-    return;
+  data_set_t ds = {{0}};
 
-  sstrncpy(ds->type, fields[0], sizeof(ds->type));
+  sstrncpy(ds.type, fields[0], sizeof(ds.type));
 
-  ds->ds_num = fields_num - 1;
-  ds->ds = calloc(ds->ds_num, sizeof(*ds->ds));
-  if (ds->ds == NULL) {
-    sfree(ds);
+  ds.ds_num = fields_num - 1;
+  ds.ds = calloc(ds.ds_num, sizeof(*ds.ds));
+  if (ds.ds == NULL)
     return;
-  }
 
-  for (size_t i = 0; i < ds->ds_num; i++)
-    if (parse_ds(ds->ds + i, fields[i + 1], strlen(fields[i + 1])) != 0) {
+  for (size_t i = 0; i < ds.ds_num; i++)
+    if (parse_ds(ds.ds + i, fields[i + 1], strlen(fields[i + 1])) != 0) {
       ERROR("types_list: parse_line: Cannot parse data source #%" PRIsz
             " of data set %s",
-            i, ds->type);
-      sfree(ds->ds);
-      sfree(ds);
+            i, ds.type);
+      sfree(ds.ds);
       return;
     }
 
-  plugin_register_data_set(ds);
+  plugin_register_data_set(&ds);
 
-  sfree(ds->ds);
-  sfree(ds);
+  sfree(ds.ds);
 } /* void parse_line */
 
 static void parse_file(FILE *fh) {
index c53e5d1..e63a20e 100644 (file)
@@ -203,6 +203,10 @@ static int uc_insert(const data_set_t *ds, const value_list_t *vl,
   ce->interval = vl->interval;
   ce->state = STATE_UNKNOWN;
 
+  if (vl->meta != NULL) {
+    ce->meta = meta_data_clone(vl->meta);
+  }
+
   if (c_avl_insert(cache_tree, key_copy, ce) != 0) {
     sfree(key_copy);
     ERROR("uc_insert: c_avl_insert failed.");
@@ -889,13 +893,12 @@ int uc_iterator_get_values(uc_iter_t *iter, value_t **ret_values,
   if ((iter == NULL) || (iter->entry == NULL) || (ret_values == NULL) ||
       (ret_num == NULL))
     return -1;
-
   *ret_values =
       calloc(iter->entry->values_num, sizeof(*iter->entry->values_raw));
   if (*ret_values == NULL)
     return -1;
   for (size_t i = 0; i < iter->entry->values_num; ++i)
-    *ret_values[i] = iter->entry->values_raw[i];
+    (*ret_values)[i] = iter->entry->values_raw[i];
 
   *ret_num = iter->entry->values_num;
 
index d949114..a68620e 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * collectd - src/intel_rdt.c
  *
- * Copyright(c) 2016-2018 Intel Corporation. All rights reserved.
+ * Copyright(c) 2016-2019 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
  *
  * Authors:
  *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
+ *   Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ *   Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ *   MichaÅ‚ AleksiÅ„ski <michalx.aleksinski@intel.com>
  **/
 
 #include "collectd.h"
 #include "utils/common/common.h"
 #include "utils/config_cores/config_cores.h"
-
+#include "utils/proc_pids/proc_pids.h"
 #include <pqos.h>
 
 #define RDT_PLUGIN "intel_rdt"
 
+/* libpqos v2.0 or newer is required for process monitoring*/
+#undef LIBPQOS2
+#if defined(PQOS_VERSION) && PQOS_VERSION >= 20000
+#define LIBPQOS2
+#endif
+
+#define RDT_PLUGIN "intel_rdt"
+
 #define RDT_MAX_SOCKETS 8
 #define RDT_MAX_SOCKET_CORES 64
 #define RDT_MAX_CORES (RDT_MAX_SOCKET_CORES * RDT_MAX_SOCKETS)
 
+#ifdef LIBPQOS2
+/*
+ * Process name inside comm file is limited to 16 chars.
+ * More info here: http://man7.org/linux/man-pages/man5/proc.5.html
+ */
+#define RDT_MAX_NAMES_GROUPS 64
+#define RDT_PROC_PATH "/proc"
+#endif /* LIBPQOS2 */
+
 typedef enum {
   UNKNOWN = 0,
   CONFIGURATION_ERROR,
 } rdt_config_status;
 
+#ifdef LIBPQOS2
+struct rdt_name_group_s {
+  char *desc;
+  size_t num_names;
+  char **names;
+  proc_pids_t **proc_pids;
+  size_t monitored_pids_count;
+  enum pqos_mon_event events;
+};
+typedef struct rdt_name_group_s rdt_name_group_t;
+#endif /* LIBPQOS2 */
+
 struct rdt_ctx_s {
   core_groups_list_t cores;
   enum pqos_mon_event events[RDT_MAX_CORES];
-  struct pqos_mon_data *pgroups[RDT_MAX_CORES];
-  size_t num_groups;
+  struct pqos_mon_data *pcgroups[RDT_MAX_CORES];
+#ifdef LIBPQOS2
+  rdt_name_group_t ngroups[RDT_MAX_NAMES_GROUPS];
+  struct pqos_mon_data *pngroups[RDT_MAX_NAMES_GROUPS];
+  size_t num_ngroups;
+  proc_pids_t **proc_pids;
+  size_t num_proc_pids;
+#endif /* LIBPQOS2 */
   const struct pqos_cpuinfo *pqos_cpu;
   const struct pqos_cap *pqos_cap;
   const struct pqos_capability *cap_mon;
@@ -57,6 +95,40 @@ static rdt_ctx_t *g_rdt;
 
 static rdt_config_status g_state = UNKNOWN;
 
+static int g_interface = -1;
+
+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};
+  vl.values_len = 1;
+
+  sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
+  snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
+  sstrncpy(vl.type, type, sizeof(vl.type));
+  if (type_instance)
+    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+  plugin_dispatch_values(&vl);
+}
+
+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};
+  vl.values_len = 1;
+
+  sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
+  snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
+  sstrncpy(vl.type, type, sizeof(vl.type));
+  if (type_instance)
+    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
+
+  plugin_dispatch_values(&vl);
+}
+
 #if COLLECT_DEBUG
 static void rdt_dump_cgroups(void) {
   char cores[RDT_MAX_CORES * 4];
@@ -65,13 +137,13 @@ static void rdt_dump_cgroups(void) {
     return;
 
   DEBUG(RDT_PLUGIN ": Core Groups Dump");
-  DEBUG(RDT_PLUGIN ":  groups count: %" PRIsz, g_rdt->num_groups);
+  DEBUG(RDT_PLUGIN ":  groups count: %" PRIsz, g_rdt->cores.num_cgroups);
 
-  for (size_t i = 0; i < g_rdt->num_groups; i++) {
+  for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
     core_group_t *cgroup = g_rdt->cores.cgroups + i;
 
     memset(cores, 0, sizeof(cores));
-    for (int j = 0; j < cgroup->num_cores; j++) {
+    for (size_t j = 0; j < cgroup->num_cores; j++) {
       snprintf(cores + strlen(cores), sizeof(cores) - strlen(cores) - 1, " %d",
                cgroup->cores[j]);
     }
@@ -85,46 +157,750 @@ static void rdt_dump_cgroups(void) {
   return;
 }
 
+#ifdef LIBPQOS2
+static void rdt_dump_ngroups(void) {
+
+  char names[DATA_MAX_NAME_LEN];
+
+  if (g_rdt == NULL)
+    return;
+
+  DEBUG(RDT_PLUGIN ": Process Names Groups Dump");
+  DEBUG(RDT_PLUGIN ":  groups count: %" PRIsz, g_rdt->num_ngroups);
+
+  for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+    memset(names, 0, sizeof(names));
+    for (size_t j = 0; j < g_rdt->ngroups[i].num_names; j++)
+      snprintf(names + strlen(names), sizeof(names) - strlen(names) - 1, " %s",
+               g_rdt->ngroups[i].names[j]);
+
+    DEBUG(RDT_PLUGIN ":  group[%d]:", (int)i);
+    DEBUG(RDT_PLUGIN ":    description: %s", g_rdt->ngroups[i].desc);
+    DEBUG(RDT_PLUGIN ":    process names:%s", names);
+    DEBUG(RDT_PLUGIN ":    events: 0x%X", g_rdt->ngroups[i].events);
+  }
+
+  return;
+}
+#endif /* LIBPQOS2 */
+
 static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
 
 static inline double bytes_to_mb(const double bytes) {
   return bytes / (1024.0 * 1024.0);
 }
 
-static void rdt_dump_data(void) {
+static void rdt_dump_cores_data(void) {
+/*
+ * CORE - monitored group of cores
+ * RMID - Resource Monitoring ID associated with the monitored group
+ *        This is not available for monitoring with resource control
+ * LLC - last level cache occupancy
+ * MBL - local memory bandwidth
+ * MBR - remote memory bandwidth
+ */
+#ifdef LIBPQOS2
+  if (g_interface == PQOS_INTER_OS_RESCTRL_MON) {
+    DEBUG(RDT_PLUGIN ":  CORE     LLC[KB]   MBL[MB]    MBR[MB]");
+  } else {
+    DEBUG(RDT_PLUGIN ":  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
+  }
+#else
+  DEBUG(RDT_PLUGIN ":  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
+#endif /* LIBPQOS2 */
+
+  for (int i = 0; i < g_rdt->cores.num_cgroups; i++) {
+    const struct pqos_event_values *pv = &g_rdt->pcgroups[i]->values;
+
+    double llc = bytes_to_kb(pv->llc);
+    double mbr = bytes_to_mb(pv->mbm_remote_delta);
+    double mbl = bytes_to_mb(pv->mbm_local_delta);
+#ifdef LIBPQOS2
+    if (g_interface == PQOS_INTER_OS_RESCTRL_MON) {
+      DEBUG(RDT_PLUGIN ": [%s] %10.1f %10.1f %10.1f",
+            g_rdt->cores.cgroups[i].desc, llc, mbl, mbr);
+    } else {
+      DEBUG(RDT_PLUGIN ": [%s] %8u %10.1f %10.1f %10.1f",
+            g_rdt->cores.cgroups[i].desc, g_rdt->pcgroups[i]->poll_ctx[0].rmid,
+            llc, mbl, mbr);
+    }
+#else
+    DEBUG(RDT_PLUGIN ": [%s] %8u %10.1f %10.1f %10.1f",
+          g_rdt->cores.cgroups[i].desc, g_rdt->pcgroups[i]->poll_ctx[0].rmid,
+          llc, mbl, mbr);
+#endif /* LIBPQOS2 */
+  }
+}
+
+#ifdef LIBPQOS2
+static void rdt_dump_pids_data(void) {
   /*
-   * CORE - monitored group of cores
-   * RMID - Resource Monitoring ID associated with the monitored group
+   * NAME - monitored group of processes
+   * PIDs - list of PID numbers in the NAME group
    * LLC - last level cache occupancy
    * MBL - local memory bandwidth
    * MBR - remote memory bandwidth
    */
-  DEBUG("  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
-  for (int i = 0; i < g_rdt->num_groups; i++) {
 
-    const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
+  DEBUG(RDT_PLUGIN ":  NAME     PIDs");
+  char pids[DATA_MAX_NAME_LEN];
+  for (size_t i = 0; i < g_rdt->num_ngroups; ++i) {
+    memset(pids, 0, sizeof(pids));
+    for (size_t j = 0; j < g_rdt->ngroups[i].num_names; ++j) {
+      pids_list_t *list = g_rdt->ngroups[i].proc_pids[j]->curr;
+      for (size_t k = 0; k < list->size; k++)
+        snprintf(pids + strlen(pids), sizeof(pids) - strlen(pids) - 1, " %u",
+                 list->pids[k]);
+    }
+    DEBUG(RDT_PLUGIN ":  [%s] %s", g_rdt->ngroups[i].desc, pids);
+  }
+
+  DEBUG(RDT_PLUGIN ":  NAME    LLC[KB]   MBL[MB]    MBR[MB]");
+  for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+
+    const struct pqos_event_values *pv = &g_rdt->pngroups[i]->values;
 
     double llc = bytes_to_kb(pv->llc);
     double mbr = bytes_to_mb(pv->mbm_remote_delta);
     double mbl = bytes_to_mb(pv->mbm_local_delta);
 
-    DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cores.cgroups[i].desc,
-          g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
+    DEBUG(RDT_PLUGIN ":  [%s] %10.1f %10.1f %10.1f", g_rdt->ngroups[i].desc,
+          llc, mbl, mbr);
   }
 }
+#endif /* LIBPQOS2 */
 #endif /* COLLECT_DEBUG */
 
+#ifdef LIBPQOS2
+static int isdupstr(const char *names[], const size_t size, const char *name) {
+  for (size_t i = 0; i < size; i++)
+    if (strncmp(names[i], name, (size_t)MAX_PROC_NAME_LEN) == 0)
+      return 1;
+
+  return 0;
+}
+
+/*
+ * NAME
+ *   strlisttoarray
+ *
+ * DESCRIPTION
+ *   Converts string representing list of strings into array of strings.
+ *   Allowed format is:
+ *     name,name1,name2,name3
+ *
+ * PARAMETERS
+ *   `str_list'  String representing list of strings.
+ *   `names'     Array to put extracted strings into.
+ *   `names_num' Variable to put number of extracted strings.
+ *
+ * RETURN VALUE
+ *    Number of elements placed into names.
+ */
+static int strlisttoarray(char *str_list, char ***names, size_t *names_num) {
+  char *saveptr = NULL;
+
+  if (str_list == NULL || names == NULL)
+    return -EINVAL;
+
+  if (strstr(str_list, ",,")) {
+    /* strtok ignores empty words between separators.
+     * This condition handles that by rejecting strings
+     * with consecutive seprators */
+    ERROR(RDT_PLUGIN ": Empty process name");
+    return -EINVAL;
+  }
+
+  for (;;) {
+    char *token = strtok_r(str_list, ",", &saveptr);
+    if (token == NULL)
+      break;
+
+    str_list = NULL;
+
+    while (isspace(*token))
+      token++;
+
+    if (*token == '\0')
+      continue;
+
+    if ((isdupstr((const char **)*names, *names_num, token))) {
+      ERROR(RDT_PLUGIN ": Duplicated process name \'%s\' in group \'%s\'",
+            token, str_list);
+      return -EINVAL;
+    } else {
+      if (0 != strarray_add(names, names_num, token)) {
+        ERROR(RDT_PLUGIN ": Error allocating process name string");
+        return -ENOMEM;
+      }
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * NAME
+ *   ngroup_cmp
+ *
+ * DESCRIPTION
+ *   Function to compare names in two name groups.
+ *
+ * PARAMETERS
+ *   `ng_a'      Pointer to name group a.
+ *   `ng_b'      Pointer to name group b.
+ *
+ * RETURN VALUE
+ *    1 if both groups contain the same names
+ *    0 if none of their names match
+ *    -1 if some but not all names match
+ */
+static int ngroup_cmp(const rdt_name_group_t *ng_a,
+                      const rdt_name_group_t *ng_b) {
+  unsigned found = 0;
+
+  assert(ng_a != NULL);
+  assert(ng_b != NULL);
+
+  const size_t sz_a = (unsigned)ng_a->num_names;
+  const size_t sz_b = (unsigned)ng_b->num_names;
+  const char **tab_a = (const char **)ng_a->names;
+  const char **tab_b = (const char **)ng_b->names;
+
+  for (size_t i = 0; i < sz_a; i++) {
+    for (size_t j = 0; j < sz_b; j++)
+      if (strncmp(tab_a[i], tab_b[j], (size_t)MAX_PROC_NAME_LEN) == 0)
+        found++;
+  }
+  /* if no names are the same */
+  if (!found)
+    return 0;
+  /* if group contains same names */
+  if (sz_a == sz_b && sz_b == (size_t)found)
+    return 1;
+  /* if not all names are the same */
+  return -1;
+}
+
+/*
+ * NAME
+ *   oconfig_to_ngroups
+ *
+ * DESCRIPTION
+ *   Function to set the descriptions and names for each process names group.
+ *   Takes a config option containing list of strings that are used to set
+ *   process group values.
+ *
+ * PARAMETERS
+ *   `item'        Config option containing process names groups.
+ *   `groups'      Table of process name groups to set values in.
+ *   `max_groups'  Maximum number of process name groups allowed.
+ *
+ * RETURN VALUE
+ *   On success, the number of name groups set up. On error, appropriate
+ *   negative error value.
+ */
+static int oconfig_to_ngroups(const oconfig_item_t *item,
+                              rdt_name_group_t *groups,
+                              const size_t max_groups) {
+  int index = 0;
+
+  assert(groups != NULL);
+  assert(max_groups > 0);
+  assert(item != NULL);
+
+  for (int j = 0; j < item->values_num; j++) {
+    int ret;
+    char value[DATA_MAX_NAME_LEN];
+
+    if ((item->values[j].value.string == NULL) ||
+        (strlen(item->values[j].value.string) == 0)) {
+      ERROR(RDT_PLUGIN ": Error - empty group");
+      return -EINVAL;
+    }
+
+    sstrncpy(value, item->values[j].value.string, sizeof(value));
+
+    ret = strlisttoarray(value, &groups[index].names, &groups[index].num_names);
+    if (ret != 0 || groups[index].num_names == 0) {
+      ERROR(RDT_PLUGIN ": Error parsing process names group (%s)",
+            item->values[j].value.string);
+      return -EINVAL;
+    }
+
+    /* set group description info */
+    groups[index].desc = sstrdup(item->values[j].value.string);
+    if (groups[index].desc == NULL) {
+      ERROR(RDT_PLUGIN ": Error allocating name group description");
+      return -ENOMEM;
+    }
+
+    groups[index].proc_pids = NULL;
+    groups[index].monitored_pids_count = 0;
+
+    index++;
+
+    if (index >= (const int)max_groups) {
+      WARNING(RDT_PLUGIN ": Too many process names groups configured");
+      return index;
+    }
+  }
+
+  return index;
+}
+
+/*
+ * NAME
+ *   rdt_free_ngroups
+ *
+ * DESCRIPTION
+ *   Function to deallocate memory allocated for name groups.
+ *
+ * PARAMETERS
+ *   `rdt'       Pointer to rdt context
+ */
+static void rdt_free_ngroups(rdt_ctx_t *rdt) {
+  for (int i = 0; i < RDT_MAX_NAMES_GROUPS; i++) {
+    if (rdt->ngroups[i].desc)
+      DEBUG(RDT_PLUGIN ": Freeing pids \'%s\' group\'s data...",
+            rdt->ngroups[i].desc);
+    sfree(rdt->ngroups[i].desc);
+    strarray_free(rdt->ngroups[i].names, rdt->ngroups[i].num_names);
+
+    if (rdt->ngroups[i].proc_pids)
+      proc_pids_free(rdt->ngroups[i].proc_pids, rdt->ngroups[i].num_names);
+
+    rdt->ngroups[i].num_names = 0;
+    sfree(rdt->pngroups[i]);
+  }
+  if (rdt->proc_pids)
+    sfree(rdt->proc_pids);
+
+  rdt->num_ngroups = 0;
+}
+
+/*
+ * NAME
+ *   rdt_config_ngroups
+ *
+ * DESCRIPTION
+ *   Reads name groups configuration.
+ *
+ * PARAMETERS
+ *   `rdt`       Pointer to rdt context
+ *   `item'      Config option containing process names groups.
+ *
+ * RETURN VALUE
+ *  0 on success. Negative number on error.
+ */
+static int rdt_config_ngroups(rdt_ctx_t *rdt, const oconfig_item_t *item) {
+  int n = 0;
+  enum pqos_mon_event events = 0;
+
+  if (item == NULL) {
+    DEBUG(RDT_PLUGIN ": ngroups_config: Invalid argument.");
+    return -EINVAL;
+  }
+
+  DEBUG(RDT_PLUGIN ": Process names groups [%d]:", item->values_num);
+  for (int j = 0; j < item->values_num; j++) {
+    if (item->values[j].type != OCONFIG_TYPE_STRING) {
+      ERROR(RDT_PLUGIN
+            ": given process names group value is not a string [idx=%d]",
+            j);
+      return -EINVAL;
+    }
+    DEBUG(RDT_PLUGIN ":  [%d]: %s", j, item->values[j].value.string);
+  }
+
+  n = oconfig_to_ngroups(item, rdt->ngroups, RDT_MAX_NAMES_GROUPS);
+  if (n < 0) {
+    rdt_free_ngroups(rdt);
+    ERROR(RDT_PLUGIN ": Error parsing process name groups configuration.");
+    return -EINVAL;
+  }
+
+  /* validate configured process name values */
+  for (int group_idx = 0; group_idx < n; group_idx++) {
+    DEBUG(RDT_PLUGIN ":  checking group [%d]: %s", group_idx,
+          rdt->ngroups[group_idx].desc);
+    for (size_t name_idx = 0; name_idx < rdt->ngroups[group_idx].num_names;
+         name_idx++) {
+      DEBUG(RDT_PLUGIN ":    checking process name [%zu]: %s", name_idx,
+            rdt->ngroups[group_idx].names[name_idx]);
+      if (!proc_pids_is_name_valid(rdt->ngroups[group_idx].names[name_idx])) {
+        ERROR(RDT_PLUGIN ": Process name group '%s' contains invalid name '%s'",
+              rdt->ngroups[group_idx].desc,
+              rdt->ngroups[group_idx].names[name_idx]);
+        rdt_free_ngroups(rdt);
+        return -EINVAL;
+      }
+    }
+  }
+
+  if (n == 0) {
+    ERROR(RDT_PLUGIN ": Empty process name groups configured.");
+    return -EINVAL;
+  }
+
+  /* Get all available events on this platform */
+  for (unsigned i = 0; i < rdt->cap_mon->u.mon->num_events; i++)
+    events |= rdt->cap_mon->u.mon->events[i].type;
+
+  events &= ~(PQOS_PERF_EVENT_LLC_MISS);
+
+  DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
+
+  rdt->num_ngroups = n;
+  for (int i = 0; i < n; i++) {
+    for (int j = 0; j < i; j++) {
+      int found = ngroup_cmp(&rdt->ngroups[j], &rdt->ngroups[i]);
+      if (found != 0) {
+        rdt_free_ngroups(rdt);
+        ERROR(RDT_PLUGIN
+              ": Cannot monitor same process name in different groups.");
+        return -EINVAL;
+      }
+    }
+
+    rdt->ngroups[i].events = events;
+    rdt->pngroups[i] = calloc(1, sizeof(*rdt->pngroups[i]));
+    if (rdt->pngroups[i] == NULL) {
+      rdt_free_ngroups(rdt);
+      ERROR(RDT_PLUGIN
+            ": Failed to allocate memory for process name monitoring data.");
+      return -ENOMEM;
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * NAME
+ *   rdt_refresh_ngroup
+ *
+ * DESCRIPTION
+ *   Refresh pids monitored by name group.
+ *
+ * PARAMETERS
+ *   `ngroup`         Pointer to name group.
+ *   `group_mon_data' PQoS monitoring context.
+ *
+ * RETURN VALUE
+ *  0 on success. Negative number on error.
+ */
+static int rdt_refresh_ngroup(rdt_name_group_t *ngroup,
+                              struct pqos_mon_data *group_mon_data) {
+
+  int result = 0;
+
+  if (NULL == ngroup)
+    return -1;
+
+  if (NULL == ngroup->proc_pids) {
+    ERROR(RDT_PLUGIN
+          ": rdt_refresh_ngroup: \'%s\' uninitialized process pids array.",
+          ngroup->desc);
+
+    return -1;
+  }
+
+  DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' process names group.",
+        ngroup->desc);
+
+  proc_pids_t **proc_pids = ngroup->proc_pids;
+  pids_list_t added_pids;
+  pids_list_t removed_pids;
+
+  memset(&added_pids, 0, sizeof(added_pids));
+  memset(&removed_pids, 0, sizeof(removed_pids));
+
+  for (size_t i = 0; i < ngroup->num_names; ++i) {
+    int diff_result = pids_list_diff(proc_pids[i], &added_pids, &removed_pids);
+    if (0 != diff_result) {
+      ERROR(RDT_PLUGIN
+            ": rdt_refresh_ngroup: \'%s\'. Error [%d] during PID diff.",
+            ngroup->desc, diff_result);
+      result = -1;
+      goto cleanup;
+    }
+  }
+
+  DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' process names group, added: "
+                   "%u, removed: %u.",
+        ngroup->desc, (unsigned)added_pids.size, (unsigned)removed_pids.size);
+
+  if (added_pids.size > 0) {
+
+    /* no pids are monitored for this group yet: start monitoring */
+    if (0 == ngroup->monitored_pids_count) {
+
+      int start_result =
+          pqos_mon_start_pids(added_pids.size, added_pids.pids, ngroup->events,
+                              (void *)ngroup->desc, group_mon_data);
+      if (PQOS_RETVAL_OK == start_result) {
+        ngroup->monitored_pids_count = added_pids.size;
+      } else {
+        ERROR(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\'. Error [%d] while "
+                         "STARTING pids monitoring",
+              ngroup->desc, start_result);
+        result = -1;
+        goto pqos_error_recovery;
+      }
+
+    } else {
+
+      int add_result =
+          pqos_mon_add_pids(added_pids.size, added_pids.pids, group_mon_data);
+      if (PQOS_RETVAL_OK == add_result)
+        ngroup->monitored_pids_count += added_pids.size;
+      else {
+        ERROR(RDT_PLUGIN
+              ": rdt_refresh_ngroup: \'%s\'. Error [%d] while ADDING pids.",
+              ngroup->desc, add_result);
+        result = -1;
+        goto pqos_error_recovery;
+      }
+    }
+  }
+
+  if (removed_pids.size > 0) {
+
+    /* all pids are removed: stop monitoring */
+    if (removed_pids.size == ngroup->monitored_pids_count) {
+      /* all pids for this group are lost: stop monitoring */
+      int stop_result = pqos_mon_stop(group_mon_data);
+      if (PQOS_RETVAL_OK != stop_result) {
+        ERROR(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\'. Error [%d] while "
+                         "STOPPING monitoring",
+              ngroup->desc, stop_result);
+        result = -1;
+        goto pqos_error_recovery;
+      }
+      ngroup->monitored_pids_count = 0;
+    } else {
+      int remove_result = pqos_mon_remove_pids(
+          removed_pids.size, removed_pids.pids, group_mon_data);
+      if (PQOS_RETVAL_OK == remove_result) {
+        ngroup->monitored_pids_count -= removed_pids.size;
+      } else {
+        ERROR(RDT_PLUGIN
+              ": rdt_refresh_ngroup: \'%s\'. Error [%d] while REMOVING pids.",
+              ngroup->desc, remove_result);
+        result = -1;
+        goto pqos_error_recovery;
+      }
+    }
+  }
+
+  goto cleanup;
+
+pqos_error_recovery:
+  /* Why?
+   * Resources might be temporary unavailable.
+   *
+   * How?
+   * Collectd will halt the reading thread for this
+   * plugin if it returns an error.
+   * Consecutive errors will be increasing the read period
+   * up to 1 day interval.
+   * On pqos error stop monitoring current group
+   * and reset the proc_pids array
+   * monitoring will be restarted on next collectd read cycle
+   */
+  DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' group RESET after error.",
+        ngroup->desc);
+  pqos_mon_stop(group_mon_data);
+  for (size_t i = 0; i < ngroup->num_names; ++i)
+    if (ngroup->proc_pids[i]->curr)
+      ngroup->proc_pids[i]->curr->size = 0;
+
+  ngroup->monitored_pids_count = 0;
+
+cleanup:
+  pids_list_clear(&added_pids);
+  pids_list_clear(&removed_pids);
+
+  return result;
+}
+
+/*
+ * NAME
+ *   read_pids_data
+ *
+ * DESCRIPTION
+ *   Poll monitoring statistics for name groups
+ *
+ * RETURN VALUE
+ *  0 on success. Negative number on error.
+ */
+static int read_pids_data() {
+
+  if (0 == g_rdt->num_ngroups) {
+    DEBUG(RDT_PLUGIN ": read_pids_data: not configured - PIDs read skipped");
+    return 0;
+  }
+
+  DEBUG(RDT_PLUGIN ": read_pids_data: Scanning active groups");
+  struct pqos_mon_data *active_groups[RDT_MAX_NAMES_GROUPS] = {0};
+  size_t active_group_idx = 0;
+  for (size_t pngroups_idx = 0;
+       pngroups_idx < STATIC_ARRAY_SIZE(g_rdt->pngroups); ++pngroups_idx)
+    if (0 != g_rdt->ngroups[pngroups_idx].monitored_pids_count)
+      active_groups[active_group_idx++] = g_rdt->pngroups[pngroups_idx];
+
+  int ret = 0;
+
+  if (0 == active_group_idx) {
+    DEBUG(RDT_PLUGIN ": read_pids_data: no active groups - PIDs read skipped");
+    goto groups_refresh;
+  }
+
+  DEBUG(RDT_PLUGIN ": read_pids_data: PIDs data polling");
+
+  int poll_result = pqos_mon_poll(active_groups, active_group_idx);
+  if (poll_result != PQOS_RETVAL_OK) {
+    ERROR(RDT_PLUGIN ": read_pids_data: Failed to poll monitoring data for "
+                     "pids. Error [%d].",
+          poll_result);
+    ret = -poll_result;
+    goto groups_refresh;
+  }
+
+  for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+    enum pqos_mon_event mbm_events =
+        (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
+         PQOS_MON_EVENT_RMEM_BW);
+
+    if (g_rdt->pngroups[i] == NULL ||
+        g_rdt->ngroups[i].monitored_pids_count == 0)
+      continue;
+
+    const struct pqos_event_values *pv = &g_rdt->pngroups[i]->values;
+
+    /* Submit only monitored events data */
+
+    if (g_rdt->ngroups[i].events & PQOS_MON_EVENT_L3_OCCUP)
+      rdt_submit_gauge(g_rdt->ngroups[i].desc, "bytes", "llc", pv->llc);
+
+    if (g_rdt->ngroups[i].events & PQOS_PERF_EVENT_IPC)
+      rdt_submit_gauge(g_rdt->ngroups[i].desc, "ipc", NULL, pv->ipc);
+
+    if (g_rdt->ngroups[i].events & mbm_events) {
+      rdt_submit_derive(g_rdt->ngroups[i].desc, "memory_bandwidth", "local",
+                        pv->mbm_local_delta);
+      rdt_submit_derive(g_rdt->ngroups[i].desc, "memory_bandwidth", "remote",
+                        pv->mbm_remote_delta);
+    }
+  }
+
+#if COLLECT_DEBUG
+  rdt_dump_pids_data();
+#endif /* COLLECT_DEBUG */
+
+groups_refresh:
+  ret = proc_pids_update(RDT_PROC_PATH, g_rdt->proc_pids, g_rdt->num_proc_pids);
+  if (0 != ret) {
+    ERROR(RDT_PLUGIN ": Initial update of proc pids failed");
+    return ret;
+  }
+
+  for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
+    int refresh_result =
+        rdt_refresh_ngroup(&(g_rdt->ngroups[i]), g_rdt->pngroups[i]);
+
+    if (0 != refresh_result) {
+      ERROR(RDT_PLUGIN ": read_pids_data: NGroup %zu refresh failed. Error: %d",
+            i, refresh_result);
+      if (0 == ret) {
+        /* refresh error will be escalated only if there were no
+         * errors before.
+         */
+        ret = refresh_result;
+      }
+    }
+  }
+
+  assert(ret <= 0);
+  return ret;
+}
+
+/*
+ * NAME
+ *   rdt_init_pids_monitoring
+ *
+ * DESCRIPTION
+ *   Initialize pids monitoring for all name groups
+ */
+static void rdt_init_pids_monitoring() {
+  for (size_t group_idx = 0; group_idx < g_rdt->num_ngroups; group_idx++) {
+    /*
+     * Each group must have not-null proc_pids array.
+     * Initial refresh is not mandatory for proper
+     * PIDs statistics detection.
+     */
+    rdt_name_group_t *ng = &g_rdt->ngroups[group_idx];
+    int init_result =
+        proc_pids_init((const char **)ng->names, ng->num_names, &ng->proc_pids);
+    if (0 != init_result) {
+      ERROR(RDT_PLUGIN
+            ": Initialization of proc_pids for group %zu failed. Error: %d",
+            group_idx, init_result);
+      continue;
+    }
+
+    /* update global proc_pids table */
+    proc_pids_t **proc_pids = realloc(g_rdt->proc_pids,
+                                      (g_rdt->num_proc_pids + ng->num_names) *
+                                          sizeof(*g_rdt->proc_pids));
+    if (NULL == proc_pids) {
+      ERROR(RDT_PLUGIN ": Alloc error\n");
+      continue;
+    }
+
+    for (size_t i = 0; i < ng->num_names; i++)
+      proc_pids[g_rdt->num_proc_pids + i] = ng->proc_pids[i];
+
+    g_rdt->proc_pids = proc_pids;
+    g_rdt->num_proc_pids += ng->num_names;
+  }
+
+  if (g_rdt->num_ngroups > 0) {
+    int update_result =
+        proc_pids_update(RDT_PROC_PATH, g_rdt->proc_pids, g_rdt->num_proc_pids);
+    if (0 != update_result)
+      ERROR(RDT_PLUGIN ": Initial update of proc pids failed");
+  }
+
+  for (size_t group_idx = 0; group_idx < g_rdt->num_ngroups; group_idx++) {
+    int refresh_result = rdt_refresh_ngroup(&(g_rdt->ngroups[group_idx]),
+                                            g_rdt->pngroups[group_idx]);
+    if (0 != refresh_result)
+      ERROR(RDT_PLUGIN ": Initial refresh of group %zu failed. Error: %d",
+            group_idx, refresh_result);
+  }
+}
+#endif /* LIBPQOS2 */
+/*
+ * NAME
+ *   rdt_free_cgroups
+ *
+ * DESCRIPTION
+ *   Function to deallocate memory allocated for core groups.
+ */
 static void rdt_free_cgroups(void) {
   config_cores_cleanup(&g_rdt->cores);
   for (int i = 0; i < RDT_MAX_CORES; i++) {
-    sfree(g_rdt->pgroups[i]);
+    sfree(g_rdt->pcgroups[i]);
   }
+  g_rdt->cores.num_cgroups = 0;
 }
 
 static int rdt_default_cgroups(void) {
   unsigned num_cores = g_rdt->pqos_cpu->num_cores;
 
-  g_rdt->cores.cgroups = calloc(num_cores, sizeof(*g_rdt->cores.cgroups));
+  g_rdt->cores.cgroups = calloc(num_cores, sizeof(*(g_rdt->cores.cgroups)));
   if (g_rdt->cores.cgroups == NULL) {
     ERROR(RDT_PLUGIN ": Error allocating core groups array");
     return -ENOMEM;
@@ -214,9 +990,9 @@ static int rdt_config_cgroups(oconfig_item_t *item) {
         g_rdt->pqos_cpu->num_cores);
   DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
 
-  g_rdt->num_groups = n;
-  for (size_t i = 0; i < n; i++) {
-    for (size_t j = 0; j < i; j++) {
+  g_rdt->cores.num_cgroups = n;
+  for (int i = 0; i < n; i++) {
+    for (int j = 0; j < i; j++) {
       int found = 0;
       found = config_cores_cmp_cgroups(&g_rdt->cores.cgroups[j],
                                        &g_rdt->cores.cgroups[i]);
@@ -228,8 +1004,8 @@ static int rdt_config_cgroups(oconfig_item_t *item) {
     }
 
     g_rdt->events[i] = events;
-    g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i]));
-    if (g_rdt->pgroups[i] == NULL) {
+    g_rdt->pcgroups[i] = calloc(1, sizeof(*g_rdt->pcgroups[i]));
+    if (g_rdt->pcgroups[i] == NULL) {
       rdt_free_cgroups();
       ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
       return -ENOMEM;
@@ -260,14 +1036,34 @@ static int rdt_preinit(void) {
   struct pqos_config pqos = {.fd_log = -1,
                              .callback_log = rdt_pqos_log,
                              .context_log = NULL,
-                             .verbose = 0};
+                             .verbose = 0,
+#ifdef LIBPQOS2
+                             .interface = PQOS_INTER_OS_RESCTRL_MON};
+  DEBUG(RDT_PLUGIN ": Initializing PQoS with RESCTRL interface");
+#else
+                             .interface = PQOS_INTER_MSR};
+  DEBUG(RDT_PLUGIN ": Initializing PQoS with MSR interface");
+#endif
 
   ret = pqos_init(&pqos);
+  DEBUG(RDT_PLUGIN ": PQoS initialization result: [%d]", ret);
+
+#ifdef LIBPQOS2
+  if (ret == PQOS_RETVAL_INTER) {
+    pqos.interface = PQOS_INTER_MSR;
+    DEBUG(RDT_PLUGIN ": Initializing PQoS with MSR interface");
+    ret = pqos_init(&pqos);
+    DEBUG(RDT_PLUGIN ": PQoS initialization result: [%d]", ret);
+  }
+#endif
+
   if (ret != PQOS_RETVAL_OK) {
     ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
     goto rdt_preinit_error1;
   }
 
+  g_interface = pqos.interface;
+
   ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
   if (ret != PQOS_RETVAL_OK) {
     ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
@@ -296,7 +1092,6 @@ rdt_preinit_error2:
   pqos_fini();
 
 rdt_preinit_error1:
-
   sfree(g_rdt);
 
   return -1;
@@ -309,25 +1104,66 @@ static int rdt_config(oconfig_item_t *ci) {
       reports a failure in configuration and
       aborts
     */
-    return (0);
+    return 0;
   }
 
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
 
-    if (strcasecmp("Cores", child->key) == 0) {
-      if (rdt_config_cgroups(child) != 0) {
+    if (strncasecmp("Cores", child->key, (size_t)strlen("Cores")) == 0) {
+      if (g_rdt->cores.num_cgroups > 0) {
+        ERROR(RDT_PLUGIN
+              ": Configuration parameter \"%s\" can be used only once.",
+              child->key);
         g_state = CONFIGURATION_ERROR;
+      } else if (rdt_config_cgroups(child) != 0)
+        g_state = CONFIGURATION_ERROR;
+
+      if (g_state == CONFIGURATION_ERROR)
         /* if we return -1 at this point collectd
            reports a failure in configuration and
            aborts
          */
-        return (0);
-      }
+        return 0;
 
 #if COLLECT_DEBUG
       rdt_dump_cgroups();
 #endif /* COLLECT_DEBUG */
+    } else if (strncasecmp("Processes", child->key,
+                           (size_t)strlen("Processes")) == 0) {
+#ifdef LIBPQOS2
+      if (g_interface != PQOS_INTER_OS_RESCTRL_MON) {
+        ERROR(RDT_PLUGIN ": Configuration parameter \"%s\" not supported. "
+                         "Resctrl monitoring is needed for PIDs monitoring.",
+              child->key);
+        g_state = CONFIGURATION_ERROR;
+      }
+
+      else if (g_rdt->num_ngroups > 0) {
+        ERROR(RDT_PLUGIN
+              ": Configuration parameter \"%s\" can be used only once.",
+              child->key);
+        g_state = CONFIGURATION_ERROR;
+      }
+
+      else if (rdt_config_ngroups(g_rdt, child) != 0)
+        g_state = CONFIGURATION_ERROR;
+
+      if (g_state == CONFIGURATION_ERROR)
+        /* if we return -1 at this point collectd
+           reports a failure in configuration and
+           aborts
+         */
+        return 0;
+
+#if COLLECT_DEBUG
+      rdt_dump_ngroups();
+#endif /* COLLECT_DEBUG */
+#else  /* !LIBPQOS2 */
+      ERROR(RDT_PLUGIN ": Configuration parameter \"%s\" not supported, please "
+                       "recompile collectd with libpqos version 2.0 or newer.",
+            child->key);
+#endif /* LIBPQOS2 */
     } else {
       ERROR(RDT_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
     }
@@ -336,64 +1172,30 @@ static int rdt_config(oconfig_item_t *ci) {
   return 0;
 }
 
-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};
-  vl.values_len = 1;
-
-  sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
-  snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
-  sstrncpy(vl.type, type, sizeof(vl.type));
-  if (type_instance)
-    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
-
-  plugin_dispatch_values(&vl);
-}
-
-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};
-  vl.values_len = 1;
-
-  sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
-  snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
-  sstrncpy(vl.type, type, sizeof(vl.type));
-  if (type_instance)
-    sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
-
-  plugin_dispatch_values(&vl);
-}
-
-static int rdt_read(__attribute__((unused)) user_data_t *ud) {
-  int ret;
+static int read_cores_data() {
 
-  if (g_rdt == NULL) {
-    ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
-    return -EINVAL;
+  if (0 == g_rdt->cores.num_cgroups) {
+    DEBUG(RDT_PLUGIN ": read_cores_data: not configured - Cores read skipped");
+    return 0;
   }
+  DEBUG(RDT_PLUGIN ": read_cores_data: Cores data poll");
 
-  ret = pqos_mon_poll(&g_rdt->pgroups[0], (unsigned)g_rdt->num_groups);
+  int ret =
+      pqos_mon_poll(&g_rdt->pcgroups[0], (unsigned)g_rdt->cores.num_cgroups);
   if (ret != PQOS_RETVAL_OK) {
-    ERROR(RDT_PLUGIN ": Failed to poll monitoring data.");
+    ERROR(RDT_PLUGIN ": read_cores_data: Failed to poll monitoring data for "
+                     "cores. Error [%d].",
+          ret);
     return -1;
   }
 
-#if COLLECT_DEBUG
-  rdt_dump_data();
-#endif /* COLLECT_DEBUG */
-
-  for (size_t i = 0; i < g_rdt->num_groups; i++) {
+  for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
     core_group_t *cgroup = g_rdt->cores.cgroups + i;
-
     enum pqos_mon_event mbm_events =
         (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
          PQOS_MON_EVENT_RMEM_BW);
 
-    const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
+    const struct pqos_event_values *pv = &g_rdt->pcgroups[i]->values;
 
     /* Submit only monitored events data */
 
@@ -411,31 +1213,75 @@ static int rdt_read(__attribute__((unused)) user_data_t *ud) {
     }
   }
 
+#if COLLECT_DEBUG
+  rdt_dump_cores_data();
+#endif /* COLLECT_DEBUG */
+
   return 0;
 }
 
-static int rdt_init(void) {
-  int ret;
+static int rdt_read(__attribute__((unused)) user_data_t *ud) {
 
-  if (g_state == CONFIGURATION_ERROR)
-    return -1;
+  if (g_rdt == NULL) {
+    ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
+    return -EINVAL;
+  }
 
-  ret = rdt_preinit();
-  if (ret != 0)
-    return ret;
+  int cores_read_result = read_cores_data();
+
+#ifdef LIBPQOS2
+  int pids_read_result = read_pids_data();
+#endif /* LIBPQOS2 */
+
+  if (0 != cores_read_result)
+    return cores_read_result;
 
-  /* Start monitoring */
-  for (size_t i = 0; i < g_rdt->num_groups; i++) {
+#ifdef LIBPQOS2
+  if (0 != pids_read_result)
+    return pids_read_result;
+#endif /* LIBPQOS2 */
+
+  return 0;
+}
+
+static void rdt_init_cores_monitoring() {
+  for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
     core_group_t *cg = g_rdt->cores.cgroups + i;
 
-    ret = pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
-                         (void *)cg->desc, g_rdt->pgroups[i]);
+    int mon_start_result =
+        pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
+                       (void *)cg->desc, g_rdt->pcgroups[i]);
+
+    if (mon_start_result != PQOS_RETVAL_OK)
+      ERROR(RDT_PLUGIN
+            ": Error starting cores monitoring group %s (pqos status=%d)",
+            cg->desc, mon_start_result);
+  }
+}
+
+static int rdt_init(void) {
 
-    if (ret != PQOS_RETVAL_OK)
-      ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
-            cg->desc, ret);
+  if (g_state == CONFIGURATION_ERROR) {
+    if (g_rdt != NULL) {
+      if (g_rdt->cores.num_cgroups > 0)
+        rdt_free_cgroups();
+#ifdef LIBPQOS2
+      if (g_rdt->num_ngroups > 0)
+        rdt_free_ngroups(g_rdt);
+#endif
+    }
+    return -1;
   }
 
+  int rdt_preinint_result = rdt_preinit();
+  if (rdt_preinint_result != 0)
+    return rdt_preinint_result;
+
+  rdt_init_cores_monitoring();
+#ifdef LIBPQOS2
+  rdt_init_pids_monitoring();
+#endif /* LIBPQOS2 */
+
   return 0;
 }
 
@@ -447,16 +1293,24 @@ static int rdt_shutdown(void) {
   if (g_rdt == NULL)
     return 0;
 
-  /* Stop monitoring */
-  for (size_t i = 0; i < g_rdt->num_groups; i++) {
-    pqos_mon_stop(g_rdt->pgroups[i]);
+  /* Stop monitoring cores */
+  for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
+    pqos_mon_stop(g_rdt->pcgroups[i]);
   }
 
+/* Stop pids monitoring */
+#ifdef LIBPQOS2
+  for (size_t i = 0; i < g_rdt->num_ngroups; i++)
+    pqos_mon_stop(g_rdt->pngroups[i]);
+#endif
+
   ret = pqos_fini();
   if (ret != PQOS_RETVAL_OK)
     ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
-
   rdt_free_cgroups();
+#ifdef LIBPQOS2
+  rdt_free_ngroups(g_rdt);
+#endif /* LIBPQOS2 */
   sfree(g_rdt);
 
   return 0;
diff --git a/src/intel_rdt_test.c b/src/intel_rdt_test.c
new file mode 100644 (file)
index 0000000..af5672b
--- /dev/null
@@ -0,0 +1,302 @@
+#include "intel_rdt.c" /* sic */
+#include "testing.h"
+
+/***************************************************************************
+ * PQOS mocks
+ */
+int pqos_mon_reset(void) { return 0; }
+int pqos_mon_assoc_get(const unsigned lcore, pqos_rmid_t *rmid) { return 0; }
+int pqos_mon_start(const unsigned num_cores, const unsigned *cores,
+                   const enum pqos_mon_event event, void *context,
+                   struct pqos_mon_data *group) {
+  return 0;
+}
+#if PQOS_VERSION >= 30000
+int pqos_mon_start_pids(const unsigned num_pids, const pid_t *pids,
+                        const enum pqos_mon_event event, void *context,
+                        struct pqos_mon_data *group) {
+  return 0;
+}
+int pqos_mon_add_pids(const unsigned num_pids, const pid_t *pids,
+                      struct pqos_mon_data *group) {
+  return 0;
+}
+int pqos_mon_remove_pids(const unsigned num_pids, const pid_t *pids,
+                         struct pqos_mon_data *group) {
+  return 0;
+}
+
+#else
+int pqos_mon_start_pid(const pid_t pids, const enum pqos_mon_event event,
+                       void *context, struct pqos_mon_data *group) {
+  return 0;
+}
+#endif
+int pqos_mon_stop(struct pqos_mon_data *group) { return 0; }
+int pqos_mon_poll(struct pqos_mon_data **groups, const unsigned num_groups) {
+  return 0;
+}
+
+#if PQOS_VERSION >= 30000
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg,
+                     const enum pqos_cdp_config l2_cdp_cfg,
+                     const enum pqos_mba_config mba_cfg) {
+  return 0;
+}
+#elif PQOS_VERSION >= 20000
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg,
+                     const enum pqos_cdp_config l2_cdp_cfg) {
+  return 0;
+}
+#else
+int pqos_alloc_reset(const enum pqos_cdp_config l3_cdp_cfg) { return 0; }
+#endif
+int pqos_alloc_assoc_set(const unsigned lcore, const unsigned class_id) {
+  return 0;
+}
+int pqos_alloc_assoc_get(const unsigned lcore, unsigned *class_id) { return 0; }
+int pqos_alloc_assoc_set_pid(const pid_t task, const unsigned class_id) {
+  return 0;
+}
+int pqos_alloc_assoc_get_pid(const pid_t task, unsigned *class_id) { return 0; }
+int pqos_alloc_assign(const unsigned technology, const unsigned *core_array,
+                      const unsigned core_num, unsigned *class_id) {
+  return 0;
+}
+int pqos_alloc_release(const unsigned *core_array, const unsigned core_num) {
+  return 0;
+}
+int pqos_alloc_assign_pid(const unsigned technology, const pid_t *task_array,
+                          const unsigned task_num, unsigned *class_id) {
+  return 0;
+}
+int pqos_alloc_release_pid(const pid_t *task_array, const unsigned task_num) {
+  return 0;
+}
+int pqos_init(const struct pqos_config *config) { return 0; }
+int pqos_fini(void) { return 0; }
+int pqos_cap_get_type(const struct pqos_cap *cap, const enum pqos_cap_type type,
+                      const struct pqos_capability **cap_item) {
+  return 0;
+}
+int pqos_cap_get(const struct pqos_cap **cap, const struct pqos_cpuinfo **cpu) {
+  return 0;
+}
+
+#ifdef LIBPQOS2
+/***************************************************************************
+ * helper functions
+ */
+rdt_ctx_t *stub_rdt_setup() {
+
+  rdt_ctx_t *rdt = calloc(1, sizeof(*rdt));
+  struct pqos_cpuinfo *pqos_cpu = calloc(1, sizeof(*pqos_cpu));
+  struct pqos_cap *pqos_cap = calloc(1, sizeof(*pqos_cap));
+  struct pqos_cap_mon *mon = calloc(1, sizeof(*mon));
+  struct pqos_capability *cap_mon = calloc(1, sizeof(*cap_mon));
+
+  cap_mon->u.mon = mon;
+  rdt->pqos_cap = pqos_cap;
+  rdt->pqos_cpu = pqos_cpu;
+  rdt->cap_mon = cap_mon;
+
+  return rdt;
+}
+
+void stub_rdt_teardown(rdt_ctx_t *rdt) {
+  free(rdt->cap_mon->u.mon);
+  free((void *)rdt->cap_mon);
+  free((void *)rdt->pqos_cpu);
+  free((void *)rdt->pqos_cap);
+  free(rdt);
+}
+
+/***************************************************************************
+ * tests
+ */
+DEF_TEST(rdt_config_ngroups__one_process) {
+  /* setup */
+  rdt_ctx_t *rdt = stub_rdt_setup();
+
+  oconfig_value_t values[] = {
+      {.value.string = "proc1", .type = OCONFIG_TYPE_STRING},
+  };
+  oconfig_item_t config_item = {
+      .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+  };
+
+  /* check */
+  int result = rdt_config_ngroups(rdt, &config_item);
+  EXPECT_EQ_INT(0, result);
+  EXPECT_EQ_STR(values[0].value.string, rdt->ngroups[0].desc);
+  EXPECT_EQ_INT(1, rdt->num_ngroups);
+
+  /* cleanup */
+  rdt_free_ngroups(rdt);
+  stub_rdt_teardown(rdt);
+
+  return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__two_groups) {
+  /* setup */
+  rdt_ctx_t *rdt = stub_rdt_setup();
+
+  oconfig_value_t values[] = {
+      {.value.string = "proc11,proc12,proc13", .type = OCONFIG_TYPE_STRING},
+      {.value.string = "proc21,proc22,proc23", .type = OCONFIG_TYPE_STRING},
+  };
+  oconfig_item_t config_item = {
+      .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+  };
+
+  /* check */
+  int result = rdt_config_ngroups(rdt, &config_item);
+  EXPECT_EQ_INT(0, result);
+  EXPECT_EQ_INT(2, rdt->num_ngroups);
+  EXPECT_EQ_STR("proc11,proc12,proc13", rdt->ngroups[0].desc);
+  EXPECT_EQ_STR("proc21,proc22,proc23", rdt->ngroups[1].desc);
+  EXPECT_EQ_STR("proc11", rdt->ngroups[0].names[0]);
+  EXPECT_EQ_STR("proc12", rdt->ngroups[0].names[1]);
+  EXPECT_EQ_STR("proc13", rdt->ngroups[0].names[2]);
+  EXPECT_EQ_STR("proc21", rdt->ngroups[1].names[0]);
+  EXPECT_EQ_STR("proc22", rdt->ngroups[1].names[1]);
+  EXPECT_EQ_STR("proc23", rdt->ngroups[1].names[2]);
+
+  /* cleanup */
+  rdt_free_ngroups(rdt);
+  stub_rdt_teardown(rdt);
+
+  return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__too_long_proc_name) {
+  /* setup */
+  rdt_ctx_t *rdt = stub_rdt_setup();
+
+  oconfig_value_t values[] = {
+      {.value.string = "_seventeen_chars_", .type = OCONFIG_TYPE_STRING},
+  };
+  oconfig_item_t config_item = {
+      .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+  };
+
+  /* check */
+  int result = rdt_config_ngroups(rdt, &config_item);
+  EXPECT_EQ_INT(-EINVAL, result);
+
+  /* cleanup */
+  stub_rdt_teardown(rdt);
+
+  return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__duplicate_proc_name_between_groups) {
+  /* setup */
+  rdt_ctx_t *rdt = stub_rdt_setup();
+
+  oconfig_value_t values[] = {
+      {.value.string = "proc11,proc12,proc", .type = OCONFIG_TYPE_STRING},
+      {.value.string = "proc21,proc,proc23", .type = OCONFIG_TYPE_STRING},
+  };
+  oconfig_item_t config_item = {
+      .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+  };
+
+  /* check */
+  int result = rdt_config_ngroups(rdt, &config_item);
+  EXPECT_EQ_INT(-EINVAL, result);
+
+  /* cleanup */
+  stub_rdt_teardown(rdt);
+
+  return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__duplicate_proc_name_in_group) {
+  /* setup */
+  rdt_ctx_t *rdt = stub_rdt_setup();
+
+  oconfig_value_t values[] = {
+      {.value.string = "proc11,proc,proc,proc14", .type = OCONFIG_TYPE_STRING},
+  };
+  oconfig_item_t config_item = {
+      .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+  };
+
+  /* check */
+  int result = rdt_config_ngroups(rdt, &config_item);
+  EXPECT_EQ_INT(-EINVAL, result);
+
+  /* cleanup */
+  stub_rdt_teardown(rdt);
+
+  return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__empty_group) {
+  /* setup */
+  rdt_ctx_t *rdt = stub_rdt_setup();
+
+  oconfig_value_t values[] = {
+      {.value.string = "proc11,proc12,proc13", .type = OCONFIG_TYPE_STRING},
+      {.value.string = "", .type = OCONFIG_TYPE_STRING},
+
+  };
+  oconfig_item_t config_item = {
+      .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+  };
+
+  /* check */
+  int result = rdt_config_ngroups(rdt, &config_item);
+  EXPECT_EQ_INT(-EINVAL, result);
+
+  /* cleanup */
+  stub_rdt_teardown(rdt);
+
+  return 0;
+}
+
+DEF_TEST(rdt_config_ngroups__empty_proc_name) {
+  /* setup */
+  rdt_ctx_t *rdt = stub_rdt_setup();
+
+  oconfig_value_t values[] = {
+      {.value.string = "proc11,,proc13", .type = OCONFIG_TYPE_STRING},
+  };
+  oconfig_item_t config_item = {
+      .values = values, .values_num = STATIC_ARRAY_SIZE(values),
+  };
+
+  /* check */
+  int result = rdt_config_ngroups(rdt, &config_item);
+  EXPECT_EQ_INT(-EINVAL, result);
+
+  /* cleanup */
+  stub_rdt_teardown(rdt);
+
+  return 0;
+}
+
+int main(void) {
+  RUN_TEST(rdt_config_ngroups__one_process);
+  RUN_TEST(rdt_config_ngroups__two_groups);
+  RUN_TEST(rdt_config_ngroups__too_long_proc_name);
+  RUN_TEST(rdt_config_ngroups__duplicate_proc_name_between_groups);
+  RUN_TEST(rdt_config_ngroups__duplicate_proc_name_in_group);
+  RUN_TEST(rdt_config_ngroups__empty_group);
+  RUN_TEST(rdt_config_ngroups__empty_proc_name);
+  END_TEST;
+}
+
+#else
+DEF_TEST(pqos12_test_stub) {
+  EXPECT_EQ_INT(0, 0);
+  return 0;
+}
+
+int main(void) {
+  RUN_TEST(pqos12_test_stub);
+  END_TEST;
+}
+#endif
index da7fe58..a94ee96 100644 (file)
@@ -61,7 +61,7 @@ static const char *config_keys[] = {"ReportRelative"};
 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
 
 static int load_config(const char *key, const char *value) {
-  if (strcasecmp(key, "ReportRelative") == 0)
+  if (strcasecmp(key, "ReportRelative") == 0) {
 #ifdef _SC_NPROCESSORS_ONLN
     report_relative_load = IS_TRUE(value);
 #else
@@ -69,6 +69,8 @@ static int load_config(const char *key, const char *value) {
             "is not available, because I can't determine the "
             "number of CPUS on this system. Sorry.");
 #endif
+    return 0;
+  }
   return -1;
 }
 static void load_submit(gauge_t snum, gauge_t mnum, gauge_t lnum) {
@@ -89,7 +91,9 @@ static void load_submit(gauge_t snum, gauge_t mnum, gauge_t lnum) {
 
   value_list_t vl = VALUE_LIST_INIT;
   value_t values[] = {
-      {.gauge = snum}, {.gauge = mnum}, {.gauge = lnum},
+      {.gauge = snum},
+      {.gauge = mnum},
+      {.gauge = lnum},
   };
 
   vl.values = values;
@@ -114,7 +118,7 @@ static int load_read(void) {
   else {
     WARNING("load: getloadavg failed: %s", STRERRNO);
   }
-/* #endif HAVE_GETLOADAVG */
+    /* #endif HAVE_GETLOADAVG */
 
 #elif defined(KERNEL_LINUX)
   gauge_t snum, mnum, lnum;
@@ -149,7 +153,7 @@ static int load_read(void) {
   lnum = atof(fields[2]);
 
   load_submit(snum, mnum, lnum);
-/* #endif KERNEL_LINUX */
+  /* #endif KERNEL_LINUX */
 
 #elif HAVE_LIBSTATGRAB
   gauge_t snum, mnum, lnum;
@@ -162,7 +166,7 @@ static int load_read(void) {
   mnum = ls->min5;
   lnum = ls->min15;
   load_submit(snum, mnum, lnum);
-/* #endif HAVE_LIBSTATGRAB */
+  /* #endif HAVE_LIBSTATGRAB */
 
 #elif HAVE_PERFSTAT
   gauge_t snum, mnum, lnum;
@@ -178,7 +182,7 @@ static int load_read(void) {
   mnum = (float)cputotal.loadavg[1] / (float)(1 << SBITS);
   lnum = (float)cputotal.loadavg[2] / (float)(1 << SBITS);
   load_submit(snum, mnum, lnum);
-/* #endif HAVE_PERFSTAT */
+  /* #endif HAVE_PERFSTAT */
 
 #else
 #error "No applicable input method."
index 3a25319..ef75052 100644 (file)
@@ -52,8 +52,8 @@ static int logfile_config(const char *key, const char *value) {
     log_level = parse_log_severity(value);
     if (log_level < 0) {
       log_level = LOG_INFO;
-      ERROR("logfile: invalid loglevel [%s] defaulting to 'info'", value);
-      return 1;
+      WARNING("logfile: invalid loglevel [%s] defaulting to 'info'", value);
+      return 0;
     }
   } else if (0 == strcasecmp(key, "File")) {
     sfree(log_file);
index d91676c..9a1ceed 100644 (file)
--- a/src/lua.c
+++ b/src/lua.c
 
 #include <pthread.h>
 
+#define PLUGIN_READ 1
+#define PLUGIN_WRITE 2
+
 typedef struct lua_script_s {
-  char *script_path;
   lua_State *lua_state;
   struct lua_script_s *next;
 } lua_script_t;
 
 typedef struct {
   lua_State *lua_state;
-  const char *lua_function_name;
+  char *lua_function_name;
   pthread_mutex_t lock;
   int callback_id;
 } clua_callback_data_t;
@@ -80,18 +82,14 @@ static int clua_load_callback(lua_State *L, int callback_ref) /* {{{ */
  * garbage collector. */
 static int clua_store_thread(lua_State *L, int idx) /* {{{ */
 {
-  if (idx < 0)
-    idx += lua_gettop(L) + 1;
-
-  /* Copy the thread pointer */
-  lua_pushvalue(L, idx); /* +1 = 3 */
-  if (!lua_isthread(L, -1)) {
-    lua_pop(L, 3); /* -3 = 0 */
+  if (!lua_isthread(L, idx)) {
     return -1;
   }
 
+  /* Copy the thread pointer */
+  lua_pushvalue(L, idx);
+
   luaL_ref(L, LUA_REGISTRYINDEX);
-  lua_pop(L, 1); /* -1 = 0 */
   return 0;
 } /* }}} int clua_store_thread */
 
@@ -263,61 +261,46 @@ static int lua_cb_dispatch_values(lua_State *L) /* {{{ */
   return 0;
 } /* }}} lua_cb_dispatch_values */
 
-static int lua_cb_register_read(lua_State *L) /* {{{ */
+static void lua_cb_free(void *data) {
+  clua_callback_data_t *cb = data;
+  free(cb->lua_function_name);
+  pthread_mutex_destroy(&cb->lock);
+  free(cb);
+}
+
+static int lua_cb_register_generic(lua_State *L, int type) /* {{{ */
 {
   int nargs = lua_gettop(L);
 
   if (nargs != 1)
     return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
 
-  luaL_checktype(L, 1, LUA_TFUNCTION);
-
-  char function_name[DATA_MAX_NAME_LEN];
-  snprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
-
-  int callback_id = clua_store_callback(L, 1);
-  if (callback_id < 0)
-    return luaL_error(L, "%s", "Storing callback function failed");
-
-  lua_State *thread = lua_newthread(L);
-  if (thread == NULL)
-    return luaL_error(L, "%s", "lua_newthread failed");
-  clua_store_thread(L, -1);
-  lua_pop(L, 1);
-
-  clua_callback_data_t *cb = calloc(1, sizeof(*cb));
-  if (cb == NULL)
-    return luaL_error(L, "%s", "calloc failed");
-
-  cb->lua_state = thread;
-  cb->callback_id = callback_id;
-  cb->lua_function_name = strdup(function_name);
-  pthread_mutex_init(&cb->lock, NULL);
-
-  int status = plugin_register_complex_read(/* group = */ "lua",
-                                            /* name      = */ function_name,
-                                            /* callback  = */ clua_read,
-                                            /* interval  = */ 0,
-                                            &(user_data_t){
-                                                .data = cb,
-                                            });
+  char subname[DATA_MAX_NAME_LEN];
+  if (!lua_isfunction(L, 1) && lua_isstring(L, 1)) {
+    const char *fname = lua_tostring(L, 1);
+    snprintf(subname, sizeof(subname), "%s()", fname);
 
-  if (status != 0)
-    return luaL_error(L, "%s", "plugin_register_complex_read failed");
-  return 0;
-} /* }}} int lua_cb_register_read */
-
-static int lua_cb_register_write(lua_State *L) /* {{{ */
-{
-  int nargs = lua_gettop(L);
-
-  if (nargs != 1)
-    return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
+    lua_getglobal(L, fname); // Push function into stack
+    lua_remove(L, 1);        // Remove string from stack
+    if (!lua_isfunction(L, -1)) {
+      return luaL_error(L, "Unable to find function '%s'", fname);
+    }
+  } else {
+    lua_getfield(L, LUA_REGISTRYINDEX, "collectd:callback_num");
+    int tmp = lua_tointeger(L, -1);
+    snprintf(subname, sizeof(subname), "callback_%d", tmp);
+    lua_pop(L, 1); // Remove old value from stack
+    lua_pushinteger(L, tmp + 1);
+    lua_setfield(L, LUA_REGISTRYINDEX, "collectd:callback_num"); // pops value
+  }
 
   luaL_checktype(L, 1, LUA_TFUNCTION);
 
-  char function_name[DATA_MAX_NAME_LEN] = "";
-  snprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
+  lua_getfield(L, LUA_REGISTRYINDEX, "collectd:script_path");
+  char function_name[DATA_MAX_NAME_LEN];
+  snprintf(function_name, sizeof(function_name), "lua/%s/%s",
+           lua_tostring(L, -1), subname);
+  lua_pop(L, 1);
 
   int callback_id = clua_store_callback(L, 1);
   if (callback_id < 0)
@@ -338,16 +321,41 @@ static int lua_cb_register_write(lua_State *L) /* {{{ */
   cb->lua_function_name = strdup(function_name);
   pthread_mutex_init(&cb->lock, NULL);
 
-  int status = plugin_register_write(/* name = */ function_name,
-                                     /* callback  = */ clua_write,
+  if (PLUGIN_READ == type) {
+    int status =
+        plugin_register_complex_read(/* group = */ "lua",
+                                     /* name      = */ function_name,
+                                     /* callback  = */ clua_read,
+                                     /* interval  = */ 0,
                                      &(user_data_t){
-                                         .data = cb,
+                                         .data = cb, .free_func = lua_cb_free,
                                      });
 
-  if (status != 0)
-    return luaL_error(L, "%s", "plugin_register_write failed");
-  return 0;
-} /* }}} int lua_cb_register_write */
+    if (status != 0)
+      return luaL_error(L, "%s", "plugin_register_complex_read failed");
+    return 0;
+  } else if (PLUGIN_WRITE == type) {
+    int status = plugin_register_write(/* name = */ function_name,
+                                       /* callback  = */ clua_write,
+                                       &(user_data_t){
+                                           .data = cb, .free_func = lua_cb_free,
+                                       });
+
+    if (status != 0)
+      return luaL_error(L, "%s", "plugin_register_write failed");
+    return 0;
+  } else {
+    return luaL_error(L, "%s", "lua_cb_register_generic unsupported type");
+  }
+} /* }}} int lua_cb_register_generic */
+
+static int lua_cb_register_read(lua_State *L) {
+  return lua_cb_register_generic(L, PLUGIN_READ);
+}
+
+static int lua_cb_register_write(lua_State *L) {
+  return lua_cb_register_generic(L, PLUGIN_WRITE);
+}
 
 static const luaL_Reg collectdlib[] = {
     {"log_debug", lua_cb_log_debug},
@@ -382,7 +390,6 @@ static void lua_script_free(lua_script_t *script) /* {{{ */
     script->lua_state = NULL;
   }
 
-  sfree(script->script_path);
   sfree(script);
 
   lua_script_free(next);
@@ -446,14 +453,7 @@ static int lua_script_load(const char *script_path) /* {{{ */
     return status;
   }
 
-  script->script_path = strdup(script_path);
-  if (script->script_path == NULL) {
-    ERROR("Lua plugin: strdup failed.");
-    lua_script_free(script);
-    return -1;
-  }
-
-  status = luaL_loadfile(script->lua_state, script->script_path);
+  status = luaL_loadfile(script->lua_state, script_path);
   if (status != 0) {
     ERROR("Lua plugin: luaL_loadfile failed: %s",
           lua_tostring(script->lua_state, -1));
@@ -462,6 +462,11 @@ static int lua_script_load(const char *script_path) /* {{{ */
     return -1;
   }
 
+  lua_pushstring(script->lua_state, script_path);
+  lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:script_path");
+  lua_pushinteger(script->lua_state, 0);
+  lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:callback_num");
+
   status = lua_pcall(script->lua_state,
                      /* nargs = */ 0,
                      /* nresults = */ LUA_MULTRET,
@@ -474,11 +479,8 @@ static int lua_script_load(const char *script_path) /* {{{ */
             "In addition, no error message could be retrieved from the stack.",
             status);
     else
-      ERROR("Lua plugin: Executing script \"%s\" failed:\n%s",
-            script->script_path, errmsg);
-
-    lua_script_free(script);
-    return -1;
+      ERROR("Lua plugin: Executing script \"%s\" failed: %s", script_path,
+            errmsg);
   }
 
   /* Append this script to the global list of scripts. */
@@ -492,6 +494,9 @@ static int lua_script_load(const char *script_path) /* {{{ */
     scripts = script;
   }
 
+  if (status != 0)
+    return -1;
+
   return 0;
 } /* }}} int lua_script_load */
 
index 4052ad5..99f0709 100644 (file)
@@ -336,21 +336,22 @@ static int mr_match(const data_set_t __attribute__((unused)) * ds, /* {{{ */
   if (mr_match_regexen(m->type_instance, vl->type_instance) ==
       FC_MATCH_NO_MATCH)
     return nomatch_value;
-  if (vl->meta != NULL) {
-    for (llentry_t *e = llist_head(m->meta); e != NULL; e = e->next) {
-      mr_regex_t *meta_re = (mr_regex_t *)e->value;
-      char *value;
-      int status = meta_data_get_string(vl->meta, e->key, &value);
-      if (status == (-ENOENT)) /* key is not present */
-        return nomatch_value;
-      if (status != 0) /* some other problem */
-        continue;      /* error will have already been printed. */
-      if (mr_match_regexen(meta_re, value) == FC_MATCH_NO_MATCH) {
-        sfree(value);
-        return nomatch_value;
-      }
+  for (llentry_t *e = llist_head(m->meta); e != NULL; e = e->next) {
+    mr_regex_t *meta_re = (mr_regex_t *)e->value;
+    char *value;
+    int status;
+    if (vl->meta == NULL)
+      return nomatch_value;
+    status = meta_data_get_string(vl->meta, e->key, &value);
+    if (status == (-ENOENT)) /* key is not present */
+      return nomatch_value;
+    if (status != 0) /* some other problem */
+      continue;      /* error will have already been printed. */
+    if (mr_match_regexen(meta_re, value) == FC_MATCH_NO_MATCH) {
       sfree(value);
+      return nomatch_value;
     }
+    sfree(value);
   }
 
   return match_value;
index 0baf6c2..eb804d1 100644 (file)
@@ -509,6 +509,13 @@ static int memcached_read(user_data_t *user_data) {
     }
 
     /*
+     * Number of secs since the server started
+     */
+    else if (FIELD_IS("uptime")) {
+      submit_gauge("uptime", NULL, atof(fields[2]), st);
+    }
+
+    /*
      * Number of bytes used and available (total - used)
      */
     else if (FIELD_IS("bytes")) {
index ed53319..04232c3 100644 (file)
@@ -97,6 +97,12 @@ enum mb_conntype_e /* {{{ */
   MBCONN_RTU }; /* }}} */
 typedef enum mb_conntype_e mb_conntype_t;
 
+enum mb_uarttype_e /* {{{ */
+{ UARTTYPE_RS232,
+  UARTTYPE_RS422,
+  UARTTYPE_RS485 }; /* }}} */
+typedef enum mb_uarttype_e mb_uarttype_t;
+
 struct mb_data_s;
 typedef struct mb_data_s mb_data_t;
 struct mb_data_s /* {{{ */
@@ -126,8 +132,9 @@ struct mb_host_s /* {{{ */
   char host[DATA_MAX_NAME_LEN];
   char node[NI_MAXHOST]; /* TCP hostname or RTU serial device */
   /* char service[NI_MAXSERV]; */
-  int port;     /* for Modbus/TCP */
-  int baudrate; /* for Modbus/RTU */
+  int port;               /* for Modbus/TCP */
+  int baudrate;           /* for Modbus/RTU */
+  mb_uarttype_t uarttype; /* UART type for Modbus/RTU */
   mb_conntype_t conntype;
 
   mb_slave_t *slaves;
@@ -387,6 +394,22 @@ static int mb_init_connection(mb_host_t *host) /* {{{ */
     return status;
   }
 
+#if defined(linux) && LIBMODBUS_VERSION_CHECK(2, 9, 4)
+  switch (host->uarttype) {
+  case UARTTYPE_RS485:
+    if (modbus_rtu_set_serial_mode(host->connection, MODBUS_RTU_RS485))
+      DEBUG("Modbus plugin: Setting RS485 mode failed.");
+    break;
+  case UARTTYPE_RS422:
+    /* libmodbus doesn't say anything about full-duplex symmetric RS422 UART */
+    break;
+  case UARTTYPE_RS232:
+    break;
+  default:
+    DEBUG("Modbus plugin: Invalid UART type!.");
+  }
+#endif /* defined(linux) && LIBMODBUS_VERSION_CHECK(2, 9, 4) */
+
   return 0;
 } /* }}} int mb_init_connection */
 #endif /* !LEGACY_LIBMODBUS */
@@ -983,11 +1006,35 @@ static int mb_config_add_host(oconfig_item_t *ci) /* {{{ */
         status = -1;
     } else if (strcasecmp("Device", child->key) == 0) {
       status = cf_util_get_string_buffer(child, host->node, sizeof(host->node));
-      if (status == 0)
+      if (status == 0) {
         host->conntype = MBCONN_RTU;
+        host->uarttype = UARTTYPE_RS232;
+      }
     } else if (strcasecmp("Baudrate", child->key) == 0)
       status = cf_util_get_int(child, &host->baudrate);
-    else if (strcasecmp("Interval", child->key) == 0)
+    else if (strcasecmp("UARTType", child->key) == 0) {
+#if defined(linux) && !LEGACY_LIBMODBUS && LIBMODBUS_VERSION_CHECK(2, 9, 4)
+      char buffer[NI_MAXHOST];
+      status = cf_util_get_string_buffer(child, buffer, sizeof(buffer));
+      if (status != 0)
+        break;
+      if (strncmp(buffer, "RS485", 6) == 0)
+        host->uarttype = UARTTYPE_RS485;
+      else if (strncmp(buffer, "RS422", 6) == 0)
+        host->uarttype = UARTTYPE_RS422;
+      else if (strncmp(buffer, "RS232", 6) == 0)
+        host->uarttype = UARTTYPE_RS232;
+      else {
+        ERROR("Modbus plugin: The UARTType \"%s\" is unknown.", buffer);
+        status = -1;
+        break;
+      }
+#else
+      ERROR("Modbus plugin: Option `UARTType' not supported. Please "
+            "upgrade libmodbus to at least 2.9.4");
+      return -1;
+#endif
+    } else if (strcasecmp("Interval", child->key) == 0)
       status = cf_util_get_cdtime(child, &interval);
     else if (strcasecmp("Slave", child->key) == 0)
       /* Don't set status: Gracefully continue if a slave fails. */
index 7399fe2..0eba01f 100644 (file)
@@ -905,6 +905,8 @@ static int mysql_read(user_data_t *ud) {
 
     } else if (strncmp(key, "Slow_queries", strlen("Slow_queries")) == 0) {
       derive_submit("mysql_slow_queries", NULL, val, db);
+    } else if (strcmp(key, "Uptime") == 0) {
+      gauge_submit("uptime", NULL, val, db);
     }
   }
   mysql_free_result(res);
index 4d4a878..b0e4bf4 100644 (file)
@@ -139,7 +139,8 @@ static void iostats_submit(const char *pinst, const char *tinst, derive_t rx,
                            derive_t tx) {
   value_list_t vl = VALUE_LIST_INIT;
   value_t values[] = {
-      {.derive = rx}, {.derive = tx},
+      {.derive = rx},
+      {.derive = tx},
   };
 
   /* NOTE ON THE NEW NAMING SCHEMA:
@@ -165,7 +166,8 @@ static void compression_submit(const char *pinst, const char *tinst,
                                derive_t uncompressed, derive_t compressed) {
   value_list_t vl = VALUE_LIST_INIT;
   value_t values[] = {
-      {.derive = uncompressed}, {.derive = compressed},
+      {.derive = uncompressed},
+      {.derive = compressed},
   };
 
   vl.values = values;
@@ -502,14 +504,15 @@ static int openvpn_config(const char *key, const char *value) {
         /* callback  = */ openvpn_read,
         /* interval  = */ 0,
         &(user_data_t){
-            .data = instance, .free_func = openvpn_free,
+            .data = instance,
+            .free_func = openvpn_free,
         });
 
     if (status == EINVAL) {
-      WARNING("openvpn plugin: status filename \"%s\" "
-              "already used, please choose a "
-              "different one.",
-              status_name);
+      ERROR("openvpn plugin: status filename \"%s\" "
+            "already used, please choose a "
+            "different one.",
+            status_name);
       return -1;
     }
 
index fe1953f..da218b6 100644 (file)
@@ -860,7 +860,7 @@ static int ovs_stats_update_port(const char *uuid, yajl_val port) {
 
     // ifaces_list is [[ "uuid", "<some_uuid>" ], [ "uuid",
     // "<another_uuid>" ], ... ]]
-    for (int i = 0; i < YAJL_GET_ARRAY(ifaces_list)->len; i++) {
+    for (size_t i = 0; i < YAJL_GET_ARRAY(ifaces_list)->len; i++) {
       yajl_val iface_tuple = YAJL_GET_ARRAY(ifaces_list)->values[i];
 
       // iface_tuple is [ "uuid", "<some_uuid>" ]
index a5b45a1..9079719 100644 (file)
@@ -89,48 +89,48 @@ all-outqueries        counts the number of outgoing UDP queries since starting
 answers-slow          counts the number of queries answered after 1 second
 answers0-1            counts the number of queries answered within 1 millisecond
 answers1-10           counts the number of queries answered within 10
-milliseconds
+                      milliseconds
 answers10-100         counts the number of queries answered within 100
-milliseconds
+                      milliseconds
 answers100-1000       counts the number of queries answered within 1 second
 cache-bytes           size of the cache in bytes (since 3.3.1)
 cache-entries         shows the number of entries in the cache
 cache-hits            counts the number of cache hits since starting, this does
-not include hits that got answered from the packet-cache
+                      not include hits that got answered from the packet-cache
 cache-misses          counts the number of cache misses since starting
 case-mismatches       counts the number of mismatches in character case since
-starting
+                      starting
 chain-resends         number of queries chained to existing outstanding query
 client-parse-errors   counts number of client packets that could not be parsed
 concurrent-queries    shows the number of MThreads currently running
 dlg-only-drops        number of records dropped because of delegation only
-setting
+                      setting
 dont-outqueries       number of outgoing queries dropped because of 'dont-query'
-setting (since 3.3)
+                      setting (since 3.3)
 edns-ping-matches     number of servers that sent a valid EDNS PING respons
 edns-ping-mismatches  number of servers that sent an invalid EDNS PING response
 failed-host-entries   number of servers that failed to resolve
 ipv6-outqueries       number of outgoing queries over IPv6
 ipv6-questions        counts all End-user initiated queries with the RD bit set,
-received over IPv6 UDP
+                      received over IPv6 UDP
 malloc-bytes          returns the number of bytes allocated by the process
-(broken, always returns 0)
+                      (broken, always returns 0)
 max-mthread-stack     maximum amount of thread stack ever used
 negcache-entries      shows the number of entries in the Negative answer cache
 no-packet-error       number of errorneous received packets
 noedns-outqueries     number of queries sent out without EDNS
 noerror-answers       counts the number of times it answered NOERROR since
-starting
+                      starting
 noping-outqueries     number of queries sent out without ENDS PING
 nsset-invalidations   number of times an nsset was dropped because it no longer
-worked
+                      worked
 nsspeeds-entries      shows the number of entries in the NS speeds map
 nxdomain-answers      counts the number of times it answered NXDOMAIN since
-starting
+                      starting
 outgoing-timeouts     counts the number of timeouts on outgoing UDP queries
-since starting
+                      since starting
 over-capacity-drops   questions dropped because over maximum concurrent query
-limit (since 3.2)
+                      limit (since 3.2)
 packetcache-bytes     size of the packet cache in bytes (since 3.3.1)
 packetcache-entries   size of packet cache (since 3.2)
 packetcache-hits      packet cache hits (since 3.2)
@@ -139,32 +139,32 @@ policy-drops          packets dropped because of (Lua) policy decision
 qa-latency            shows the current latency average
 questions             counts all end-user initiated queries with the RD bit set
 resource-limits       counts number of queries that could not be performed
-because of resource limits
+                      because of resource limits
 security-status       security status based on security polling
 server-parse-errors   counts number of server replied packets that could not be
-parsed
+                      parsed
 servfail-answers      counts the number of times it answered SERVFAIL since
-starting
+                      starting
 spoof-prevents        number of times PowerDNS considered itself spoofed, and
-dropped the data
+                      dropped the data
 sys-msec              number of CPU milliseconds spent in 'system' mode
 tcp-client-overflow   number of times an IP address was denied TCP access
-because it already had too many connections
+                      because it already had too many connections
 tcp-clients           counts the number of currently active TCP/IP clients
 tcp-outqueries        counts the number of outgoing TCP queries since starting
 tcp-questions         counts all incoming TCP queries (since starting)
 throttle-entries      shows the number of entries in the throttle map
 throttled-out         counts the number of throttled outgoing UDP queries since
-starting
+                      starting
 throttled-outqueries  idem to throttled-out
 unauthorized-tcp      number of TCP questions denied because of allow-from
-restrictions
+                      restrictions
 unauthorized-udp      number of UDP questions denied because of allow-from
-restrictions
+                      restrictions
 unexpected-packets    number of answers from remote servers that were unexpected
-(might point to spoofing)
+                      (might point to spoofing)
 unreachables          number of times nameservers were unreachable since
-starting
+                      starting
 uptime                number of seconds process has been running (since 3.1.5)
 user-msec             number of CPU milliseconds spent in 'user' mode
 }}} */
@@ -301,6 +301,7 @@ static statname_lookup_t lookup_table[] = /* {{{ */
         {"unauthorized-tcp", "counter", "denied-unauthorized_tcp"},
         {"unauthorized-udp", "counter", "denied-unauthorized_udp"},
         {"unexpected-packets", "dns_answer", "unexpected"},
+        {"unreachables", "counter", "unreachables"},
         {"uptime", "uptime", NULL}}; /* }}} */
 static int lookup_table_length = STATIC_ARRAY_SIZE(lookup_table);
 
index b5f0970..9aeb71a 100644 (file)
@@ -41,44 +41,36 @@ static char *datadir;
 static char *daemon_address;
 static bool config_create_files = true;
 static bool config_collect_stats = true;
-static rrdcreate_config_t rrdcreate_config = {
-    /* stepsize = */ 0,
-    /* heartbeat = */ 0,
-    /* rrarows = */ 1200,
-    /* xff = */ 0.1,
-
-    /* timespans = */ NULL,
-    /* timespans_num = */ 0,
-
-    /* consolidation_functions = */ NULL,
-    /* consolidation_functions_num = */ 0,
-
-    /* async = */ 0};
+static rrdcreate_config_t rrdcreate_config = {.stepsize = 0,
+                                              .heartbeat = 0,
+                                              .rrarows = 1200,
+                                              .xff = 0.1,
+                                              .timespans = NULL,
+                                              .timespans_num = 0,
+                                              .consolidation_functions = NULL,
+                                              .consolidation_functions_num = 0,
+                                              .async = 0};
 
 /*
  * Prototypes.
  */
 static int rc_write(const data_set_t *ds, const value_list_t *vl,
-                    user_data_t __attribute__((unused)) * user_data);
+                    __attribute__((unused)) user_data_t *ud);
 static int rc_flush(__attribute__((unused)) cdtime_t timeout,
                     const char *identifier,
                     __attribute__((unused)) user_data_t *ud);
 
 static int value_list_to_string(char *buffer, int buffer_len,
                                 const data_set_t *ds, const value_list_t *vl) {
-  int offset;
-  int status;
-  time_t t;
-
-  assert(0 == strcmp(ds->type, vl->type));
+  assert(strcmp(ds->type, vl->type) == 0);
 
   memset(buffer, '\0', buffer_len);
 
-  t = CDTIME_T_TO_TIME_T(vl->time);
-  status = snprintf(buffer, buffer_len, "%lu", (unsigned long)t);
+  int status =
+      snprintf(buffer, buffer_len, "%.6f", CDTIME_T_TO_DOUBLE(vl->time));
   if ((status < 1) || (status >= buffer_len))
     return -1;
-  offset = status;
+  int offset = status;
 
   for (size_t i = 0; i < ds->ds_num; i++) {
     if ((ds->ds[i].type != DS_TYPE_COUNTER) &&
@@ -113,8 +105,6 @@ static int value_list_to_string(char *buffer, int buffer_len,
 static int value_list_to_filename(char *buffer, size_t buffer_size,
                                   value_list_t const *vl) {
   char const suffix[] = ".rrd";
-  int status;
-  size_t len;
 
   if (datadir != NULL) {
     size_t datadir_len = strlen(datadir) + 1;
@@ -124,17 +114,17 @@ static int value_list_to_filename(char *buffer, size_t buffer_size,
 
     sstrncpy(buffer, datadir, buffer_size);
     buffer[datadir_len - 1] = '/';
-    buffer[datadir_len] = 0;
+    buffer[datadir_len] = '\0';
 
     buffer += datadir_len;
     buffer_size -= datadir_len;
   }
 
-  status = FORMAT_VL(buffer, buffer_size, vl);
+  int status = FORMAT_VL(buffer, buffer_size, vl);
   if (status != 0)
     return status;
 
-  len = strlen(buffer);
+  size_t len = strlen(buffer);
   assert(len < buffer_size);
   buffer += len;
   buffer_size -= len;
@@ -147,10 +137,9 @@ static int value_list_to_filename(char *buffer, size_t buffer_size,
 } /* int value_list_to_filename */
 
 static int rc_config_get_int_positive(oconfig_item_t const *ci, int *ret) {
-  int status;
   int tmp = 0;
 
-  status = cf_util_get_int(ci, &tmp);
+  int status = cf_util_get_int(ci, &tmp);
   if (status != 0)
     return status;
   if (tmp < 0)
@@ -161,8 +150,6 @@ static int rc_config_get_int_positive(oconfig_item_t const *ci, int *ret) {
 } /* int rc_config_get_int_positive */
 
 static int rc_config_get_xff(oconfig_item_t const *ci, double *ret) {
-  double value;
-
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
     ERROR("rrdcached plugin: The \"%s\" needs exactly one numeric argument "
           "in the range [0.0, 1.0)",
@@ -170,7 +157,7 @@ static int rc_config_get_xff(oconfig_item_t const *ci, double *ret) {
     return EINVAL;
   }
 
-  value = ci->values[0].value.number;
+  double value = ci->values[0].value.number;
   if ((value >= 0.0) && (value < 1.0)) {
     *ret = value;
     return 0;
@@ -183,14 +170,12 @@ static int rc_config_get_xff(oconfig_item_t const *ci, double *ret) {
 } /* int rc_config_get_xff */
 
 static int rc_config_add_timespan(int timespan) {
-  int *tmp;
-
   if (timespan <= 0)
     return EINVAL;
 
-  tmp = realloc(rrdcreate_config.timespans,
-                sizeof(*rrdcreate_config.timespans) *
-                    (rrdcreate_config.timespans_num + 1));
+  int *tmp = realloc(rrdcreate_config.timespans,
+                     sizeof(*rrdcreate_config.timespans) *
+                         (rrdcreate_config.timespans_num + 1));
   if (tmp == NULL)
     return ENOMEM;
   rrdcreate_config.timespans = tmp;
@@ -262,12 +247,10 @@ static int rc_config(oconfig_item_t *ci) {
 } /* int rc_config */
 
 static int try_reconnect(void) {
-  int status;
-
   rrdc_disconnect();
 
   rrd_clear_error();
-  status = rrdc_connect(daemon_address);
+  int status = rrdc_connect(daemon_address);
   if (status != 0) {
     ERROR("rrdcached plugin: Failed to reconnect to RRDCacheD "
           "at %s: %s (status=%d)",
@@ -282,9 +265,6 @@ static int try_reconnect(void) {
 } /* int try_reconnect */
 
 static int rc_read(void) {
-  int status;
-  rrdc_stats_t *head;
-  bool retried = false;
 
   value_list_t vl = VALUE_LIST_INIT;
   vl.values = &(value_t){.gauge = NAN};
@@ -302,7 +282,7 @@ static int rc_read(void) {
   sstrncpy(vl.plugin, "rrdcached", sizeof(vl.plugin));
 
   rrd_clear_error();
-  status = rrdc_connect(daemon_address);
+  int status = rrdc_connect(daemon_address);
   if (status != 0) {
     ERROR("rrdcached plugin: Failed to connect to RRDCacheD "
           "at %s: %s (status=%d)",
@@ -310,6 +290,9 @@ static int rc_read(void) {
     return -1;
   }
 
+  rrdc_stats_t *head;
+  bool retried = false;
+
   while (42) {
     /* The RRD client lib does not provide any means for checking a
      * connection, hence we'll have to retry upon failed operations. */
@@ -390,7 +373,6 @@ static int rc_write(const data_set_t *ds, const value_list_t *vl,
                     user_data_t __attribute__((unused)) * user_data) {
   char filename[PATH_MAX];
   char values[512];
-  char *values_array[2];
   int status;
   bool retried = false;
 
@@ -415,9 +397,6 @@ static int rc_write(const data_set_t *ds, const value_list_t *vl,
     return -1;
   }
 
-  values_array[0] = values;
-  values_array[1] = NULL;
-
   if (config_create_files) {
     struct stat statbuf;
 
@@ -446,6 +425,10 @@ static int rc_write(const data_set_t *ds, const value_list_t *vl,
     return -1;
   }
 
+  char *values_array[2] = {
+          [0] = values, [1] = NULL,
+  };
+
   while (42) {
     /* The RRD client lib does not provide any means for checking a
      * connection, hence we'll have to retry upon failed operations. */
@@ -472,20 +455,18 @@ static int rc_write(const data_set_t *ds, const value_list_t *vl,
 static int rc_flush(__attribute__((unused)) cdtime_t timeout, /* {{{ */
                     const char *identifier,
                     __attribute__((unused)) user_data_t *ud) {
-  char filename[PATH_MAX + 1];
-  int status;
-  bool retried = false;
-
   if (identifier == NULL)
     return EINVAL;
 
+  char filename[PATH_MAX + 1];
+
   if (datadir != NULL)
     snprintf(filename, sizeof(filename), "%s/%s.rrd", datadir, identifier);
   else
     snprintf(filename, sizeof(filename), "%s.rrd", identifier);
 
   rrd_clear_error();
-  status = rrdc_connect(daemon_address);
+  int status = rrdc_connect(daemon_address);
   if (status != 0) {
     ERROR("rrdcached plugin: Failed to connect to RRDCacheD "
           "at %s: %s (status=%d)",
@@ -493,6 +474,8 @@ static int rc_flush(__attribute__((unused)) cdtime_t timeout, /* {{{ */
     return -1;
   }
 
+  bool retried = false;
+
   while (42) {
     /* The RRD client lib does not provide any means for checking a
      * connection, hence we'll have to retry upon failed operations. */
index a600f30..b4e2c0c 100644 (file)
@@ -41,7 +41,8 @@ static int log_level = LOG_INFO;
 static int notif_severity;
 
 static const char *config_keys[] = {
-    "LogLevel", "NotifyLevel",
+    "LogLevel",
+    "NotifyLevel",
 };
 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
 
@@ -55,8 +56,10 @@ static int sl_config(const char *key, const char *value) {
     }
   } else if (strcasecmp(key, "NotifyLevel") == 0) {
     notif_severity = parse_notif_severity(value);
-    if (notif_severity < 0)
+    if (notif_severity < 0) {
+      ERROR("syslog: invalid notification severity [%s]", value);
       return 1;
+    }
   }
 
   return 0;
index e1b48fd..ff650a8 100644 (file)
--- a/src/ted.c
+++ b/src/ted.c
@@ -243,7 +243,7 @@ static int ted_config(const char *key, const char *value) {
 
     tmp = atoi(value);
     if (tmp < 0) {
-      WARNING("ted plugin: Invalid retry count: %i", tmp);
+      ERROR("ted plugin: Invalid retry count: %i", tmp);
       return 1;
     }
     conf_retries = tmp;
index deb16e0..ce57942 100644 (file)
@@ -1463,7 +1463,7 @@ err:
   return ret;
 }
 
-int save_affinity(void) {
+static int save_affinity(void) {
   if (affinity_policy == policy_restore_affinity) {
     /* Try to save the scheduling affinity, as it will be modified by
      * get_counters().
@@ -1476,7 +1476,7 @@ int save_affinity(void) {
   return 0;
 }
 
-void restore_affinity(void) {
+static void restore_affinity(void) {
   /* Let's restore the affinity to the value saved in save_affinity */
   if (affinity_policy == policy_restore_affinity)
     (void)sched_setaffinity(0, cpu_saved_affinity_setsize,
index e9de64f..69f59b0 100644 (file)
@@ -51,6 +51,8 @@ df                      used:GAUGE:0:1125899906842623, free:GAUGE:0:112589990684
 df_complex              value:GAUGE:0:U
 df_inodes               value:GAUGE:0:U
 dilution_of_precision   value:GAUGE:0:U
+disk_allocation         value:GAUGE:0:U
+disk_capacity           value:GAUGE:0:U
 disk_error              value:GAUGE:0:U
 disk_io_time            io_time:DERIVE:0:U, weighted_io_time:DERIVE:0:U
 disk_latency            read:GAUGE:0:U, write:GAUGE:0:U
@@ -58,6 +60,7 @@ disk_merged             read:DERIVE:0:U, write:DERIVE:0:U
 disk_octets             read:DERIVE:0:U, write:DERIVE:0:U
 disk_ops                read:DERIVE:0:U, write:DERIVE:0:U
 disk_ops_complex        value:DERIVE:0:U
+disk_physical           value:GAUGE:0:U
 disk_time               read:DERIVE:0:U, write:DERIVE:0:U
 dns_answer              value:DERIVE:0:U
 dns_notify              value:DERIVE:0:U
index 713f032..4f449a3 100644 (file)
  *   Sebastian 'tokkee' Harl <sh at tokkee.org>
  **/
 
+// clang-format off
+/*
+ * Explicit order is required or _FILE_OFFSET_BITS will have definition mismatches on Solaris
+ * See Github Issue #3193 for details
+ */
+#include "utils/common/common.h"
 #include "testing.h"
 #include "utils/cmds/cmds.h"
-#include "utils/common/common.h"
+// clang-format on
 
 static void error_cb(void *ud, cmd_status_t status, const char *format,
                      va_list ap) {
@@ -51,114 +57,201 @@ static struct {
 } parse_data[] = {
     /* Valid FLUSH commands. */
     {
-        "FLUSH", NULL, CMD_OK, CMD_FLUSH,
+        "FLUSH",
+        NULL,
+        CMD_OK,
+        CMD_FLUSH,
     },
     {
-        "FLUSH identifier=myhost/magic/MAGIC", NULL, CMD_OK, CMD_FLUSH,
+        "FLUSH identifier=myhost/magic/MAGIC",
+        NULL,
+        CMD_OK,
+        CMD_FLUSH,
     },
     {
-        "FLUSH identifier=magic/MAGIC", &default_host_opts, CMD_OK, CMD_FLUSH,
+        "FLUSH identifier=magic/MAGIC",
+        &default_host_opts,
+        CMD_OK,
+        CMD_FLUSH,
     },
     {
-        "FLUSH timeout=123 plugin=\"A\"", NULL, CMD_OK, CMD_FLUSH,
+        "FLUSH timeout=123 plugin=\"A\"",
+        NULL,
+        CMD_OK,
+        CMD_FLUSH,
     },
     /* Invalid FLUSH commands. */
     {
         /* Missing hostname; no default. */
-        "FLUSH identifier=magic/MAGIC", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "FLUSH identifier=magic/MAGIC",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
         /* Missing 'identifier' key. */
-        "FLUSH myhost/magic/MAGIC", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "FLUSH myhost/magic/MAGIC",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
         /* Invalid timeout. */
-        "FLUSH timeout=A", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "FLUSH timeout=A",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
         /* Invalid identifier. */
-        "FLUSH identifier=invalid", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "FLUSH identifier=invalid",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
         /* Invalid option. */
-        "FLUSH invalid=option", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "FLUSH invalid=option",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
 
     /* Valid GETVAL commands. */
     {
-        "GETVAL myhost/magic/MAGIC", NULL, CMD_OK, CMD_GETVAL,
+        "GETVAL myhost/magic/MAGIC",
+        NULL,
+        CMD_OK,
+        CMD_GETVAL,
     },
     {
-        "GETVAL magic/MAGIC", &default_host_opts, CMD_OK, CMD_GETVAL,
+        "GETVAL magic/MAGIC",
+        &default_host_opts,
+        CMD_OK,
+        CMD_GETVAL,
     },
 
     /* Invalid GETVAL commands. */
     {
-        "GETVAL magic/MAGIC", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "GETVAL magic/MAGIC",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "GETVAL", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "GETVAL",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "GETVAL invalid", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "GETVAL invalid",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
 
     /* Valid LISTVAL commands. */
     {
-        "LISTVAL", NULL, CMD_OK, CMD_LISTVAL,
+        "LISTVAL",
+        NULL,
+        CMD_OK,
+        CMD_LISTVAL,
     },
 
     /* Invalid LISTVAL commands. */
     {
-        "LISTVAL invalid", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "LISTVAL invalid",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
 
     /* Valid PUTVAL commands. */
     {
-        "PUTVAL magic/MAGIC N:42", &default_host_opts, CMD_OK, CMD_PUTVAL,
+        "PUTVAL magic/MAGIC N:42",
+        &default_host_opts,
+        CMD_OK,
+        CMD_PUTVAL,
     },
     {
-        "PUTVAL myhost/magic/MAGIC N:42", NULL, CMD_OK, CMD_PUTVAL,
+        "PUTVAL myhost/magic/MAGIC N:42",
+        NULL,
+        CMD_OK,
+        CMD_PUTVAL,
     },
     {
-        "PUTVAL myhost/magic/MAGIC 1234:42", NULL, CMD_OK, CMD_PUTVAL,
+        "PUTVAL myhost/magic/MAGIC 1234:42",
+        NULL,
+        CMD_OK,
+        CMD_PUTVAL,
     },
     {
-        "PUTVAL myhost/magic/MAGIC 1234:42 2345:23", NULL, CMD_OK, CMD_PUTVAL,
+        "PUTVAL myhost/magic/MAGIC 1234:42 2345:23",
+        NULL,
+        CMD_OK,
+        CMD_PUTVAL,
     },
     {
-        "PUTVAL myhost/magic/MAGIC interval=2 1234:42", NULL, CMD_OK,
+        "PUTVAL myhost/magic/MAGIC interval=2 1234:42",
+        NULL,
+        CMD_OK,
         CMD_PUTVAL,
     },
     {
-        "PUTVAL myhost/magic/MAGIC interval=2 1234:42 interval=5 2345:23", NULL,
-        CMD_OK, CMD_PUTVAL,
+        "PUTVAL myhost/magic/MAGIC interval=2 1234:42 interval=5 2345:23",
+        NULL,
+        CMD_OK,
+        CMD_PUTVAL,
     },
 
     /* Invalid PUTVAL commands. */
     {
-        "PUTVAL magic/MAGIC N:42", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "PUTVAL magic/MAGIC N:42",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "PUTVAL", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "PUTVAL",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "PUTVAL invalid N:42", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "PUTVAL invalid N:42",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "PUTVAL myhost/magic/MAGIC A:42", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "PUTVAL myhost/magic/MAGIC A:42",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "PUTVAL myhost/magic/MAGIC 1234:A", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "PUTVAL myhost/magic/MAGIC 1234:A",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "PUTVAL myhost/magic/MAGIC", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "PUTVAL myhost/magic/MAGIC",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "PUTVAL 1234:A", NULL, CMD_PARSE_ERROR, CMD_UNKNOWN,
+        "PUTVAL 1234:A",
+        NULL,
+        CMD_PARSE_ERROR,
+        CMD_UNKNOWN,
     },
     {
-        "PUTVAL myhost/magic/UNKNOWN 1234:42", NULL, CMD_PARSE_ERROR,
+        "PUTVAL myhost/magic/UNKNOWN 1234:42",
+        NULL,
+        CMD_PARSE_ERROR,
         CMD_UNKNOWN,
     },
     /*
@@ -173,10 +266,16 @@ static struct {
 
     /* Invalid commands. */
     {
-        "INVALID", NULL, CMD_UNKNOWN_COMMAND, CMD_UNKNOWN,
+        "INVALID",
+        NULL,
+        CMD_UNKNOWN_COMMAND,
+        CMD_UNKNOWN,
     },
     {
-        "INVALID interval=2", NULL, CMD_UNKNOWN_COMMAND, CMD_UNKNOWN,
+        "INVALID interval=2",
+        NULL,
+        CMD_UNKNOWN_COMMAND,
+        CMD_UNKNOWN,
     },
 };
 
@@ -196,9 +295,10 @@ DEF_TEST(parse) {
     memset(&cmd, 0, sizeof(cmd));
 
     status = cmd_parse(input, &cmd, parse_data[i].opts, &err);
-    snprintf(description, sizeof(description), "cmd_parse (\"%s\", opts=%p) = "
-                                               "%d (type=%d [%s]); want %d "
-                                               "(type=%d [%s])",
+    snprintf(description, sizeof(description),
+             "cmd_parse (\"%s\", opts=%p) = "
+             "%d (type=%d [%s]); want %d "
+             "(type=%d [%s])",
              parse_data[i].input, parse_data[i].opts, status, cmd.type,
              CMD_TO_STRING(cmd.type), parse_data[i].expected_status,
              parse_data[i].expected_type,
index 426082f..4f15c16 100644 (file)
  *   Florian octo Forster <octo at collectd.org>
  */
 
-#include "testing.h"
+// clang-format off
+/*
+ * Explicit order is required or _FILE_OFFSET_BITS will have definition mismatches on Solaris
+ * See Github Issue #3193 for details
+ */
 #include "utils/common/common.h"
+#include "testing.h"
+// clang-format on
 
 #if HAVE_KSTAT_H
 #include <kstat.h>
@@ -280,10 +286,15 @@ DEF_TEST(parse_values) {
 
   for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
     data_source_t dsrc = {
-        .name = "value", .type = DS_TYPE_GAUGE, .min = 0.0, .max = NAN,
+        .name = "value",
+        .type = DS_TYPE_GAUGE,
+        .min = 0.0,
+        .max = NAN,
     };
     data_set_t ds = {
-        .type = "example", .ds_num = 1, .ds = &dsrc,
+        .type = "example",
+        .ds_num = 1,
+        .ds = &dsrc,
     };
 
     value_t v = {
@@ -344,7 +355,8 @@ DEF_TEST(value_to_rate) {
   for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
     cdtime_t t0 = TIME_T_TO_CDTIME_T(cases[i].t0);
     value_to_rate_state_t state = {
-        .last_value = cases[i].v0, .last_time = t0,
+        .last_value = cases[i].v0,
+        .last_time = t0,
     };
     gauge_t got;
 
index 2ea919b..fb5cb4f 100644 (file)
 #if HAVE_ARPA_INET_H
 #include <arpa/inet.h>
 #endif
-#if HAVE_ARPA_NAMESER_H
-#include <arpa/nameser.h>
-#endif
-#if HAVE_ARPA_NAMESER_COMPAT_H
-#include <arpa/nameser_compat.h>
-#endif
 
 #if HAVE_NETDB_H
 #include <netdb.h>
@@ -666,251 +660,182 @@ void handle_pcap(u_char *udata, const struct pcap_pkthdr *hdr,
 
 const char *qtype_str(int t) {
   static char buf[32];
+  // clang-format off
+  /*
+  Built (with minor cleanup) from glibc-2.29 by
+    cat resolv/arpa/nameser.h | grep "ns_t_" | \
+    perl -ne '/ns_t_(\S+)\ =\ (\d+)/; print "  case $2:\n    return \"".uc($1)."\";\n";'
+  */
+  // clang-format on
   switch (t) {
-#if (defined(__NAMESER)) && (__NAMESER >= 19991001)
-  case ns_t_a:
+  case 1:
     return "A";
-  case ns_t_ns:
+  case 2:
     return "NS";
-  case ns_t_md:
+  case 3:
     return "MD";
-  case ns_t_mf:
+  case 4:
     return "MF";
-  case ns_t_cname:
+  case 5:
     return "CNAME";
-  case ns_t_soa:
+  case 6:
     return "SOA";
-  case ns_t_mb:
+  case 7:
     return "MB";
-  case ns_t_mg:
+  case 8:
     return "MG";
-  case ns_t_mr:
+  case 9:
     return "MR";
-  case ns_t_null:
+  case 10:
     return "NULL";
-  case ns_t_wks:
+  case 11:
     return "WKS";
-  case ns_t_ptr:
+  case 12:
     return "PTR";
-  case ns_t_hinfo:
+  case 13:
     return "HINFO";
-  case ns_t_minfo:
+  case 14:
     return "MINFO";
-  case ns_t_mx:
+  case 15:
     return "MX";
-  case ns_t_txt:
+  case 16:
     return "TXT";
-  case ns_t_rp:
+  case 17:
     return "RP";
-  case ns_t_afsdb:
+  case 18:
     return "AFSDB";
-  case ns_t_x25:
+  case 19:
     return "X25";
-  case ns_t_isdn:
+  case 20:
     return "ISDN";
-  case ns_t_rt:
+  case 21:
     return "RT";
-  case ns_t_nsap:
+  case 22:
     return "NSAP";
-  case ns_t_nsap_ptr:
+  case 23:
     return "NSAP-PTR";
-  case ns_t_sig:
+  case 24:
     return "SIG";
-  case ns_t_key:
+  case 25:
     return "KEY";
-  case ns_t_px:
+  case 26:
     return "PX";
-  case ns_t_gpos:
+  case 27:
     return "GPOS";
-  case ns_t_aaaa:
+  case 28:
     return "AAAA";
-  case ns_t_loc:
+  case 29:
     return "LOC";
-  case ns_t_nxt:
+  case 30:
     return "NXT";
-  case ns_t_eid:
+  case 31:
     return "EID";
-  case ns_t_nimloc:
+  case 32:
     return "NIMLOC";
-  case ns_t_srv:
+  case 33:
     return "SRV";
-  case ns_t_atma:
+  case 34:
     return "ATMA";
-  case ns_t_naptr:
+  case 35:
     return "NAPTR";
-  case ns_t_opt:
-    return "OPT";
-#if __NAMESER >= 19991006
-  case ns_t_kx:
+  case 36:
     return "KX";
-  case ns_t_cert:
+  case 37:
     return "CERT";
-  case ns_t_a6:
+  case 38:
     return "A6";
-  case ns_t_dname:
+  case 39:
     return "DNAME";
-  case ns_t_sink:
+  case 40:
     return "SINK";
-  case ns_t_tsig:
-    return "TSIG";
-#endif
-#if __NAMESER >= 20090302
-  case ns_t_apl:
+  case 41:
+    return "OPT";
+  case 42:
     return "APL";
-  case ns_t_ds:
+  case 43:
     return "DS";
-  case ns_t_sshfp:
+  case 44:
     return "SSHFP";
-  case ns_t_ipseckey:
+  case 45:
     return "IPSECKEY";
-  case ns_t_rrsig:
+  case 46:
     return "RRSIG";
-  case ns_t_nsec:
+  case 47:
     return "NSEC";
-  case ns_t_dnskey:
+  case 48:
     return "DNSKEY";
-  case ns_t_dhcid:
+  case 49:
     return "DHCID";
-  case ns_t_nsec3:
+  case 50:
     return "NSEC3";
-  case ns_t_nsec3param:
+  case 51:
     return "NSEC3PARAM";
-  case ns_t_hip:
+  case 52:
+    return "TLSA";
+  case 53:
+    return "SMIMEA";
+  case 55:
     return "HIP";
-  case ns_t_spf:
+  case 56:
+    return "NINFO";
+  case 57:
+    return "RKEY";
+  case 58:
+    return "TALINK";
+  case 59:
+    return "CDS";
+  case 60:
+    return "CDNSKEY";
+  case 61:
+    return "OPENPGPKEY";
+  case 62:
+    return "CSYNC";
+  case 99:
     return "SPF";
-  case ns_t_ixfr:
+  case 100:
+    return "UINFO";
+  case 101:
+    return "UID";
+  case 102:
+    return "GID";
+  case 103:
+    return "UNSPEC";
+  case 104:
+    return "NID";
+  case 105:
+    return "L32";
+  case 106:
+    return "L64";
+  case 107:
+    return "LP";
+  case 108:
+    return "EUI48";
+  case 109:
+    return "EUI64";
+  case 249:
+    return "TKEY";
+  case 250:
+    return "TSIG";
+  case 251:
     return "IXFR";
-#endif
-  case ns_t_axfr:
+  case 252:
     return "AXFR";
-  case ns_t_mailb:
+  case 253:
     return "MAILB";
-  case ns_t_maila:
+  case 254:
     return "MAILA";
-  case ns_t_any:
+  case 255:
     return "ANY";
-#if __NAMESER >= 19991006
-  case ns_t_zxfr:
-    return "ZXFR";
-#endif
-#if __NAMESER >= 20090302
-  case ns_t_dlv:
+  case 256:
+    return "URI";
+  case 257:
+    return "CAA";
+  case 258:
+    return "AVC";
+  case 32768:
+    return "TA";
+  case 32769:
     return "DLV";
-#endif
-/* #endif __NAMESER >= 19991001 */
-#elif (defined(__BIND)) && (__BIND >= 19950621)
-  case T_A:
-    return "A"; /* 1 ... */
-  case T_NS:
-    return "NS";
-  case T_MD:
-    return "MD";
-  case T_MF:
-    return "MF";
-  case T_CNAME:
-    return "CNAME";
-  case T_SOA:
-    return "SOA";
-  case T_MB:
-    return "MB";
-  case T_MG:
-    return "MG";
-  case T_MR:
-    return "MR";
-  case T_NULL:
-    return "NULL";
-  case T_WKS:
-    return "WKS";
-  case T_PTR:
-    return "PTR";
-  case T_HINFO:
-    return "HINFO";
-  case T_MINFO:
-    return "MINFO";
-  case T_MX:
-    return "MX";
-  case T_TXT:
-    return "TXT";
-  case T_RP:
-    return "RP";
-  case T_AFSDB:
-    return "AFSDB";
-  case T_X25:
-    return "X25";
-  case T_ISDN:
-    return "ISDN";
-  case T_RT:
-    return "RT";
-  case T_NSAP:
-    return "NSAP";
-  case T_NSAP_PTR:
-    return "NSAP_PTR";
-  case T_SIG:
-    return "SIG";
-  case T_KEY:
-    return "KEY";
-  case T_PX:
-    return "PX";
-  case T_GPOS:
-    return "GPOS";
-  case T_AAAA:
-    return "AAAA";
-  case T_LOC:
-    return "LOC";
-  case T_NXT:
-    return "NXT";
-  case T_EID:
-    return "EID";
-  case T_NIMLOC:
-    return "NIMLOC";
-  case T_SRV:
-    return "SRV";
-  case T_ATMA:
-    return "ATMA";
-  case T_NAPTR:
-    return "NAPTR"; /* ... 35 */
-#if (__BIND >= 19960801)
-  case T_KX:
-    return "KX"; /* 36 ... */
-  case T_CERT:
-    return "CERT";
-  case T_A6:
-    return "A6";
-  case T_DNAME:
-    return "DNAME";
-  case T_SINK:
-    return "SINK";
-  case T_OPT:
-    return "OPT";
-  case T_APL:
-    return "APL";
-  case T_DS:
-    return "DS";
-  case T_SSHFP:
-    return "SSHFP";
-  case T_RRSIG:
-    return "RRSIG";
-  case T_NSEC:
-    return "NSEC";
-  case T_DNSKEY:
-    return "DNSKEY"; /* ... 48 */
-  case T_TKEY:
-    return "TKEY"; /* 249 */
-#endif /* __BIND >= 19960801 */
-  case T_TSIG:
-    return "TSIG"; /* 250 ... */
-  case T_IXFR:
-    return "IXFR";
-  case T_AXFR:
-    return "AXFR";
-  case T_MAILB:
-    return "MAILB";
-  case T_MAILA:
-    return "MAILA";
-  case T_ANY:
-    return "ANY"; /* ... 255 */
-#endif /* __BIND >= 19950621 */
   default:
     snprintf(buf, sizeof(buf), "#%i", t);
     return buf;
@@ -938,65 +863,46 @@ const char *opcode_str(int o) {
 
 const char *rcode_str(int rcode) {
   static char buf[32];
+  /* RFC2136 rcodes */
+  // clang-format off
+  /*
+  Built (with minor cleanup) from glibc-2.29 by
+    cat resolv/arpa/nameser.h | grep "ns_r_" | \
+    perl -ne '/ns_r_(\S+)\ =\ (\d+)/; print "  case $2:\n    return \"".uc($1)."\";\n";'
+
+  https://tools.ietf.org/html/rfc2671 assigns EDNS Extended RCODE "16" to "BADVERS"
+  https://tools.ietf.org/html/rfc2845 declares 0..15 as DNS RCODE and 16 is BADSIG.
+  */
+  // clang-format on
   switch (rcode) {
-#if (defined(__NAMESER)) && (__NAMESER >= 19991006)
-  case ns_r_noerror:
-    return "NOERROR";
-  case ns_r_formerr:
+  case 1:
     return "FORMERR";
-  case ns_r_servfail:
+  case 2:
     return "SERVFAIL";
-  case ns_r_nxdomain:
+  case 3:
     return "NXDOMAIN";
-  case ns_r_notimpl:
+  case 4:
     return "NOTIMPL";
-  case ns_r_refused:
+  case 5:
     return "REFUSED";
-  case ns_r_yxdomain:
+  case 6:
     return "YXDOMAIN";
-  case ns_r_yxrrset:
+  case 7:
     return "YXRRSET";
-  case ns_r_nxrrset:
+  case 8:
     return "NXRRSET";
-  case ns_r_notauth:
+  case 9:
     return "NOTAUTH";
-  case ns_r_notzone:
+  case 10:
     return "NOTZONE";
-  case ns_r_max:
+  case 11:
     return "MAX";
-  case ns_r_badsig:
+  case 16:
     return "BADSIG";
-  case ns_r_badkey:
+  case 17:
     return "BADKEY";
-  case ns_r_badtime:
+  case 18:
     return "BADTIME";
-/* #endif __NAMESER >= 19991006 */
-#elif (defined(__BIND)) && (__BIND >= 19950621)
-  case NOERROR:
-    return "NOERROR";
-  case FORMERR:
-    return "FORMERR";
-  case SERVFAIL:
-    return "SERVFAIL";
-  case NXDOMAIN:
-    return "NXDOMAIN";
-  case NOTIMP:
-    return "NOTIMP";
-  case REFUSED:
-    return "REFUSED";
-#if defined(YXDOMAIN) && defined(NXRRSET)
-  case YXDOMAIN:
-    return "YXDOMAIN";
-  case YXRRSET:
-    return "YXRRSET";
-  case NXRRSET:
-    return "NXRRSET";
-  case NOTAUTH:
-    return "NOTAUTH";
-  case NOTZONE:
-    return "NOTZONE";
-#endif /* RFC2136 rcodes */
-#endif /* __BIND >= 19950621 */
   default:
     snprintf(buf, sizeof(buf), "RCode%i", rcode);
     return buf;
index 9d9b75f..9cc49b6 100644 (file)
@@ -38,7 +38,6 @@
 
 #include "config.h"
 
-#include <arpa/nameser.h>
 #include <stdint.h>
 
 #if HAVE_PCAP_H
index d0e047f..ffef3e2 100644 (file)
 /* Utils functions to format data sets in graphite format.
  * Largely taken from write_graphite.c as it remains the same formatting */
 
+/* helper function for reverse_hostname */
+void reverse_string(char *r_host, int len) {
+  for (int i = 0, j = len - 1; i < j; i++, j--) {
+    char t = r_host[i];
+    r_host[i] = r_host[j];
+    r_host[j] = t;
+  }
+}
+
+void reverse_hostname(char *r_host, char const *orig_host) {
+  int len_host = strlen(orig_host);
+
+  /* put reversed hostname into working copy */
+  for (int i = 0; i < len_host; i++)
+    r_host[i] = orig_host[len_host - 1 - i];
+  r_host[len_host] = '\0';
+
+  /* reverse labels (except last) */
+  int p = 0;
+  for (int i = 0; i < len_host; i++)
+    if (r_host[i] == '.') {
+      reverse_string(&r_host[p], i - p);
+      p = i + 1;
+    }
+
+  /* reverse last label */
+  reverse_string(&r_host[p], len_host - p);
+}
+
 static int gr_format_values(char *ret, size_t ret_len, int ds_num,
                             const data_set_t *ds, const value_list_t *vl,
                             gauge_t const *rates) {
@@ -120,7 +149,13 @@ static int gr_format_name_tagged(char *ret, int ret_len, value_list_t const *vl,
   if (postfix == NULL)
     postfix = "";
 
-  gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char, 1);
+  if (flags & GRAPHITE_REVERSE_HOST) {
+    char r_host[DATA_MAX_NAME_LEN];
+    reverse_hostname(r_host, vl->host);
+    gr_copy_escape_part(n_host, r_host, sizeof(n_host), escape_char, 1);
+  } else {
+    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);
@@ -198,8 +233,15 @@ static int gr_format_name(char *ret, int ret_len, value_list_t const *vl,
 
   bool preserve_separator = (flags & GRAPHITE_PRESERVE_SEPARATOR);
 
-  gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char,
-                      preserve_separator);
+  if (flags & GRAPHITE_REVERSE_HOST) {
+    char r_host[DATA_MAX_NAME_LEN];
+    reverse_hostname(r_host, vl->host);
+    gr_copy_escape_part(n_host, r_host, sizeof(n_host), escape_char,
+                        preserve_separator);
+  } else {
+    gr_copy_escape_part(n_host, vl->host, sizeof(n_host), escape_char,
+                        preserve_separator);
+  }
   gr_copy_escape_part(n_plugin, vl->plugin, sizeof(n_plugin), escape_char,
                       preserve_separator);
   gr_copy_escape_part(n_plugin_instance, vl->plugin_instance,
index 60b89ae..4df7db3 100644 (file)
@@ -32,6 +32,7 @@
 #define GRAPHITE_DROP_DUPE_FIELDS 0x08
 #define GRAPHITE_PRESERVE_SEPARATOR 0x10
 #define GRAPHITE_USE_TAGS 0x20
+#define GRAPHITE_REVERSE_HOST 0x40
 
 int format_graphite(char *buffer, size_t buffer_size, const data_set_t *ds,
                     const value_list_t *vl, const char *prefix,
index 0ad7d02..5bae546 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * collectd - src/utils_mount.h
+ * collectd - src/utils/mount/mount.h
  * Copyright (C) 2005,2006  Niki W. Waibel
  *
  * This program is free software; you can redistribute it and/
index 671de84..4b31056 100644 (file)
@@ -280,7 +280,7 @@ static int get_assertion(oauth_t *auth, char *buffer,
   status = snprintf(buffer, buffer_size, "%s.%s.%s", header, claim, signature);
   if (status < 1)
     return -1;
-  else if (status >= buffer_size)
+  else if ((size_t)status >= buffer_size)
     return ENOMEM;
 
   return 0;
diff --git a/src/utils/proc_pids/proc_pids.c b/src/utils/proc_pids/proc_pids.c
new file mode 100644 (file)
index 0000000..336a996
--- /dev/null
@@ -0,0 +1,361 @@
+/**
+ * collectd - src/utils/proc_pids/proc_pids.c
+ *
+ * Copyright(c) 2018-2019 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:
+ *   Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ *   Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ *   MichaÅ‚ AleksiÅ„ski <michalx.aleksinski@intel.com>
+ **/
+
+#include "collectd.h"
+#include "utils/common/common.h"
+#include "utils/proc_pids/proc_pids.h"
+
+#define UTIL_NAME "utils_proc_pids"
+
+void pids_list_free(pids_list_t *list) {
+  assert(list);
+
+  sfree(list->pids);
+  sfree(list);
+}
+
+int proc_pids_is_name_valid(const char *name) {
+
+  if (name != NULL) {
+    unsigned len = strlen(name);
+    if (len > 0 && len <= MAX_PROC_NAME_LEN)
+      return 1;
+    else {
+      DEBUG(UTIL_NAME
+            ": Process name \'%s\' is too long. Max supported len is %d chars.",
+            name, MAX_PROC_NAME_LEN);
+    }
+  }
+
+  return 0;
+}
+
+int pids_list_add_pid(pids_list_t *list, const pid_t pid) {
+  assert(list);
+
+  if (list->allocated == list->size) {
+    size_t new_allocated = list->allocated + 1 + list->allocated / 10;
+    pid_t *new_pids = realloc(list->pids, sizeof(pid_t) * new_allocated);
+
+    if (NULL == new_pids) {
+      ERROR(UTIL_NAME ": Alloc error\n");
+      return -1;
+    }
+
+    list->pids = new_pids;
+    list->allocated = new_allocated;
+  }
+
+  list->pids[list->size] = pid;
+  list->size++;
+
+  return 0;
+}
+
+int pids_list_add_list(pids_list_t *dst, pids_list_t *src) {
+  assert(dst);
+  assert(src);
+
+  if (dst->allocated < dst->size + src->size) {
+    pid_t *new_pids =
+        realloc(dst->pids, sizeof(pid_t) * (dst->size + src->size));
+
+    if (NULL == new_pids) {
+      ERROR(UTIL_NAME ": Alloc error\n");
+      return -1;
+    }
+
+    dst->allocated = dst->size + src->size;
+    dst->pids = new_pids;
+  }
+
+  memcpy(dst->pids + dst->size, src->pids, src->size * sizeof(*(src->pids)));
+  dst->size += src->size;
+
+  return 0;
+}
+
+int pids_list_clear(pids_list_t *list) {
+  assert(list);
+
+  if (list->pids != NULL)
+    sfree(list->pids);
+
+  list->size = 0;
+  list->allocated = 0;
+
+  return 0;
+}
+
+int pids_list_contains_pid(pids_list_t *list, const pid_t pid) {
+  assert(list);
+
+  for (int i = 0; i < list->size; i++)
+    if (list->pids[i] == pid)
+      return 1;
+
+  return 0;
+}
+
+/*
+ * NAME
+ *   read_proc_name
+ *
+ * DESCRIPTION
+ *   Reads process name from given pid directory.
+ *   Strips new-line character (\n).
+ *
+ * PARAMETERS
+ *   `procfs_path' Path to systems proc directory (e.g. /proc)
+ *   `pid_entry'   Dirent for PID directory
+ *   `name'        Output buffer for process name, recommended proc_comm.
+ *   `out_size'    Output buffer size, recommended sizeof(proc_comm)
+ *
+ * RETURN VALUE
+ *   On success, the number of read bytes (includes stripped \n).
+ *   -1 on file open error
+*/
+static int read_proc_name(const char *procfs_path,
+                          const struct dirent *pid_entry, char *name,
+                          const size_t out_size) {
+  assert(pid_entry);
+  assert(name);
+  assert(out_size);
+  memset(name, 0, out_size);
+
+  const char *comm_file_name = "comm";
+
+  char *path = ssnprintf_alloc("%s/%s/%s", procfs_path, pid_entry->d_name,
+                               comm_file_name);
+  if (path == NULL)
+    return -1;
+  FILE *f = fopen(path, "r");
+  if (f == NULL) {
+    ERROR(UTIL_NAME ": Failed to open comm file, error: %d\n", errno);
+    sfree(path);
+    return -1;
+  }
+  size_t read_length = fread(name, sizeof(char), out_size, f);
+  name[out_size - 1] = '\0';
+  fclose(f);
+  sfree(path);
+  /* strip new line ending */
+  char *newline = strchr(name, '\n');
+  if (newline) {
+    *newline = '\0';
+  }
+
+  return read_length;
+}
+
+/*
+ * NAME
+ *   get_pid_number
+ *
+ * DESCRIPTION
+ *   Gets pid number for given /proc/pid directory entry or
+ *   returns error if input directory does not hold PID information.
+ *
+ * PARAMETERS
+ *   `entry'    Dirent for PID directory
+ *   `pid'      PID number to be filled
+ *
+ * RETURN VALUE
+ *   0 on success. -1 on error.
+ */
+static int get_pid_number(struct dirent *entry, pid_t *pid) {
+  char *tmp_end; /* used for strtoul error check*/
+
+  if (pid == NULL || entry == NULL)
+    return -1;
+
+  if (entry->d_type != DT_DIR)
+    return -1;
+
+  /* trying to get pid number from directory name*/
+  *pid = strtoul(entry->d_name, &tmp_end, 10);
+  if (*tmp_end != '\0') {
+    return -1; /* conversion failed, not proc-pid */
+  }
+  /* all checks passed, marking as success */
+  return 0;
+}
+
+int proc_pids_init(const char **procs_names_array,
+                   const size_t procs_names_array_size,
+                   proc_pids_t **proc_pids[]) {
+
+  proc_pids_t **proc_pids_array;
+  assert(proc_pids);
+  assert(NULL == *proc_pids);
+
+  /* Copy procs names to output array. Initialize pids list with NULL value. */
+  proc_pids_array = calloc(procs_names_array_size, sizeof(*proc_pids_array));
+
+  if (NULL == proc_pids_array)
+    return -1;
+
+  for (size_t i = 0; i < procs_names_array_size; ++i) {
+    proc_pids_array[i] = calloc(1, sizeof(**proc_pids_array));
+    if (NULL == proc_pids_array[i])
+      goto proc_pids_init_error;
+
+    sstrncpy(proc_pids_array[i]->process_name, procs_names_array[i],
+             STATIC_ARRAY_SIZE(proc_pids_array[i]->process_name));
+    proc_pids_array[i]->prev = NULL;
+    proc_pids_array[i]->curr = NULL;
+  }
+
+  *proc_pids = proc_pids_array;
+
+  return 0;
+proc_pids_init_error:
+  if (NULL != proc_pids_array) {
+    for (size_t i = 0; i < procs_names_array_size; ++i) {
+      free(proc_pids_array[i]);
+    }
+    free(proc_pids_array);
+  }
+  return -1;
+}
+
+static void swap_proc_pids(proc_pids_t **proc_pids, size_t proc_pids_num) {
+  for (size_t i = 0; i < proc_pids_num; i++) {
+    pids_list_t *swap = proc_pids[i]->prev;
+    proc_pids[i]->prev = proc_pids[i]->curr;
+    proc_pids[i]->curr = swap;
+  }
+}
+
+int proc_pids_update(const char *procfs_path, proc_pids_t **proc_pids,
+                     size_t proc_pids_num) {
+  assert(procfs_path);
+  assert(proc_pids);
+
+  DIR *proc_dir = opendir(procfs_path);
+  if (proc_dir == NULL) {
+    ERROR(UTIL_NAME ": Could not open %s directory, error: %d", procfs_path,
+          errno);
+    return -1;
+  }
+
+  swap_proc_pids(proc_pids, proc_pids_num);
+
+  for (size_t i = 0; i < proc_pids_num; i++) {
+    if (NULL == proc_pids[i]->curr)
+      proc_pids[i]->curr = calloc(1, sizeof(*(proc_pids[i]->curr)));
+
+    if (NULL == proc_pids[i]->curr) {
+      ERROR(UTIL_NAME ": Alloc error\n");
+      goto update_error;
+    }
+
+    proc_pids[i]->curr->size = 0;
+  }
+
+  /* Go through procfs and find PIDS and their comms */
+  struct dirent *entry;
+  while ((entry = readdir(proc_dir)) != NULL) {
+    pid_t pid;
+    int pid_conversion = get_pid_number(entry, &pid);
+    if (pid_conversion < 0)
+      continue;
+
+    proc_comm_t comm;
+    int read_result =
+        read_proc_name(procfs_path, entry, comm, sizeof(proc_comm_t));
+    if (read_result <= 0)
+      continue;
+
+    /* Try to find comm in input procs array */
+    for (size_t i = 0; i < proc_pids_num; ++i) {
+      if (0 ==
+          strncmp(comm, proc_pids[i]->process_name, STATIC_ARRAY_SIZE(comm)))
+        pids_list_add_pid(proc_pids[i]->curr, pid);
+    }
+  }
+
+  int close_result = closedir(proc_dir);
+  if (0 != close_result) {
+    ERROR(UTIL_NAME ": failed to close /proc directory, error: %d", errno);
+    goto update_error;
+  }
+  return 0;
+
+update_error:
+  swap_proc_pids(proc_pids, proc_pids_num);
+  return -1;
+}
+
+int pids_list_diff(proc_pids_t *proc, pids_list_t *added,
+                   pids_list_t *removed) {
+  assert(proc);
+  assert(added);
+  assert(removed);
+
+  added->size = 0;
+  removed->size = 0;
+
+  if (NULL == proc->prev || 0 == proc->prev->size) {
+    /* append all PIDs from curr to added*/
+    return pids_list_add_list(added, proc->curr);
+  } else if (NULL == proc->curr || 0 == proc->curr->size) {
+    /* append all PIDs from prev to removed*/
+    return pids_list_add_list(removed, proc->prev);
+  }
+
+  for (int i = 0; i < proc->prev->size; i++)
+    if (0 == pids_list_contains_pid(proc->curr, proc->prev->pids[i])) {
+      int add_result = pids_list_add_pid(removed, proc->prev->pids[i]);
+      if (add_result < 0)
+        return add_result;
+    }
+
+  for (int i = 0; i < proc->curr->size; i++)
+    if (0 == pids_list_contains_pid(proc->prev, proc->curr->pids[i])) {
+      int add_result = pids_list_add_pid(added, proc->curr->pids[i]);
+      if (add_result < 0)
+        return add_result;
+    }
+
+  return 0;
+}
+
+int proc_pids_free(proc_pids_t *proc_pids[], size_t proc_pids_num) {
+  for (size_t i = 0; i < proc_pids_num; i++) {
+    if (NULL != proc_pids[i]->curr)
+      pids_list_free(proc_pids[i]->curr);
+    if (NULL != proc_pids[i]->prev)
+      pids_list_free(proc_pids[i]->prev);
+    sfree(proc_pids[i]);
+  }
+  sfree(proc_pids);
+
+  return 0;
+}
diff --git a/src/utils/proc_pids/proc_pids.h b/src/utils/proc_pids/proc_pids.h
new file mode 100644 (file)
index 0000000..8b19497
--- /dev/null
@@ -0,0 +1,226 @@
+/**
+ * collectd - src/utils/proc_pids/proc_pids.h
+ *
+ * Copyright(c) 2018-2019 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:
+ *   Starzyk, Mateusz <mateuszx.starzyk@intel.com>
+ *   Wojciech Andralojc <wojciechx.andralojc@intel.com>
+ *   MichaÅ‚ AleksiÅ„ski <michalx.aleksinski@intel.com>
+ **/
+
+#include <dirent.h>
+#include <sys/types.h>
+
+/*
+ * Process name inside comm file is limited to 16 chars.
+ * More info here: http://man7.org/linux/man-pages/man5/proc.5.html
+ */
+#define MAX_PROC_NAME_LEN 16
+
+/* Helper typedef for process name array
+ * Extra 1 char is added for string null termination.
+ */
+typedef char proc_comm_t[MAX_PROC_NAME_LEN + 1];
+
+/* List of pids. */
+typedef struct pids_list_s {
+  pid_t *pids;
+  size_t size;
+  size_t allocated;
+} pids_list_t;
+
+/* Holds process name and list of pids assigned to that name */
+typedef struct proc_pids_s {
+  proc_comm_t process_name;
+  pids_list_t *prev;
+  pids_list_t *curr;
+} proc_pids_t;
+
+/*
+ * NAME
+ *   pids_list_free
+ *
+ * DESCRIPTION
+ *   Free all elements of given pids list
+ *
+ * PARAMETERS
+ *   `list'     Head of target pids_list.
+ */
+void pids_list_free(pids_list_t *list);
+
+/*
+ * NAME
+ *   pids_list_add_pid
+ *
+ * DESCRIPTION
+ *   Adds pid at the end of the pids array.
+ *   Reallocates memory for new pid element, it is up to user to free it.
+ *
+ * PARAMETERS
+ *   `list'     Target pids_list.
+ *   `pid'      Pid to be added.
+ *
+ * RETURN VALUE
+ *   On success, returns 0.
+ *   -1 on memory allocation error.
+ */
+int pids_list_add_pid(pids_list_t *list, const pid_t pid);
+
+/*
+ * NAME
+ *   pids_list_clear
+ *
+ * DESCRIPTION
+ *   Remove all pids from the list
+ *
+ * PARAMETERS
+ *   `list'     Target pids_list.
+ *
+ * RETURN VALUE
+ *   On success, return 0
+ */
+int pids_list_clear(pids_list_t *list);
+
+/*
+ * NAME
+ *   pids_list_add_list
+ *
+ * DESCRIPTION
+ *   Adds pids list at the end of the pids list.
+ *   Allocates memory for new pid elements, it is up to user to free it.
+ *
+ * PARAMETERS
+ *   `dst'      Target PIDs list.
+ *   `src'      Source PIDs list.
+ *
+ * RETURN VALUE
+ *   On success, returns 0.
+ *   -1 on memory allocation error.
+ */
+int pids_list_add_list(pids_list_t *dst, pids_list_t *src);
+
+/*
+ * NAME
+ *   pids_list_contains_pid
+ *
+ * DESCRIPTION
+ *   Tests if pids list contains specific pid.
+ *
+ * PARAMETERS
+ *   `list'     pids_list to check.
+ *   `pid'      Pid to be searched for.
+ *
+ * RETURN VALUE
+ *   If PID found in list, returns 1,
+ *   Otherwise returns 0.
+ */
+int pids_list_contains_pid(pids_list_t *list, const pid_t pid);
+
+/*
+ * NAME
+ *   pids_list_diff
+ *
+ * DESCRIPTION
+ *   Searches for differences in two given lists
+ *
+ * PARAMETERS
+ *   `proc'            List of pids
+ *   `added'           New pids which appeared
+ *   `removed'         Result array storing pids which disappeared
+ * RETURN VALUE
+ *   0 on success. Negative number on error.
+ */
+int pids_list_diff(proc_pids_t *proc, pids_list_t *added, pids_list_t *removed);
+
+/*
+ * NAME
+ *   proc_pids_is_name_valid
+ *
+ * DESCRIPTION
+ *   Checks if given string is valid process name.
+ *
+ * PARAMETERS
+ *   `name'     null-terminated char array
+ *
+ * RETURN VALUE
+ *   If given name is a valid process name, returns 1,
+ *   Otherwise returns 0.
+ */
+int proc_pids_is_name_valid(const char *name);
+
+/*
+ * NAME
+ *   proc_pids_init
+ *
+ * DESCRIPTION
+ *   Helper function to properly initialize array of proc_pids.
+ *   Allocates memory for proc_pids structs.
+ *
+ * PARAMETERS
+ *   `procs_names_array'      Array of null-terminated strings with
+ *                            process' names to be copied to new array
+ *   `procs_names_array_size' procs_names_array element count
+ *   `proc_pids'              Address of pointer, under which new
+ *                            array of proc_pids will be allocated.
+ *                            Must be NULL.
+ * RETURN VALUE
+ *   0 on success. Negative number on error:
+ *   -1: allocation error
+ */
+int proc_pids_init(const char **procs_names_array,
+                   const size_t procs_names_array_size,
+                   proc_pids_t **proc_pids[]);
+
+/*
+ * NAME
+ *   proc_pids_update
+ *
+ * DESCRIPTION
+ *   Updates PIDs matching processes's names.
+ *   Searches all PID directories in /proc fs and updates current pids_list.
+ *
+ * PARAMETERS
+ *   `procfs_path'     Path to systems proc directory (e.g. /proc)
+ *   `proc_pids'       Array of proc_pids pointers to be updated.
+ *   `proc_pids_num'   proc_pids element count
+ *
+ * RETURN VALUE
+ *   0 on success. -1 on error.
+ */
+int proc_pids_update(const char *procfs_path, proc_pids_t *proc_pids[],
+                     size_t proc_pids_num);
+
+/*
+ * NAME
+ *   proc_pids_free
+ *
+ * DESCRIPTION
+ *   Releses memory allocatd for proc_pids
+ *
+ * PARAMETERS
+ *   `proc_pids'       Array of proc_pids
+ *   `proc_pids_num'   proc_pids element count
+ *
+ * RETURN VALUE
+ *   0 on success. -1 on error.
+ */
+int proc_pids_free(proc_pids_t *proc_pids[], size_t proc_pids_num);
diff --git a/src/utils/proc_pids/proc_pids_test.c b/src/utils/proc_pids/proc_pids_test.c
new file mode 100644 (file)
index 0000000..42d17f4
--- /dev/null
@@ -0,0 +1,511 @@
+// clang-format off
+/*
+ * Explicit order is required or _FILE_OFFSET_BITS will have definition mismatches on Solaris
+ * See Github Issue #3193 for details
+ */
+#include "utils/proc_pids/proc_pids.c" /* sic */
+#include "testing.h"
+// clang-format on
+#include <sys/stat.h>
+
+/***************************************************************************
+ * helper functions
+ */
+
+typedef struct stub_proc_pid {
+  proc_comm_t comm;
+  pid_t pid;
+} stub_proc_pid_t;
+
+static const char *proc_fs = "/tmp/procfs_stub";
+
+/*
+ * NAME
+ *   stub_procfs_setup
+ *
+ * DESCRIPTION
+ *   Prepares testing environment by creating temporary
+ *   PID/comm file structure.
+ *
+ * PARAMETERS
+ *   `proc_pids_array'          Array of stub_proc_pid_t structs. Represents
+ *                              which PIDs should hold given process name.
+ *   `proc_pids_array_length'   Element count of input array.
+ *
+ * RETURN VALUE
+ *   0 on success.
+ *   -1 on base dir creation error.
+ *   -2 on comm file creation error.
+ *   -3 on comm file write error.
+ */
+int stub_procfs_setup(const stub_proc_pid_t *proc_pids_array,
+                      const size_t proc_pids_array_length) {
+  if (mkdir(proc_fs, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0)
+    return -1;
+  char path[256];
+
+  for (size_t i = 0; i < proc_pids_array_length; ++i) {
+    memset(path, 0, sizeof(path));
+    snprintf(path, STATIC_ARRAY_SIZE(path), "%s/%d", proc_fs,
+             proc_pids_array[i].pid);
+    mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+    strncat(path, "/comm", STATIC_ARRAY_SIZE(path) - strlen(path) - 1);
+
+    FILE *fp = fopen(path, "w");
+    if (!fp)
+      return -2;
+
+    size_t slen = strlen(proc_pids_array[i].comm);
+    size_t wlen = fwrite(proc_pids_array[i].comm, sizeof(char), slen, fp);
+    fclose(fp);
+
+    if (slen != wlen)
+      return -3;
+  }
+  return 0;
+}
+
+/*
+ * NAME
+ *   stub_procfs_teardown
+ *
+ * DESCRIPTION
+ *   Clears testing environment: removes stub proc files.
+ *   NOTE - This function could be implemented by usage of nftw, but this
+ *   would require #define _XOPEN_SOURCE 500, which
+ *   messes up intel_rdt includes.
+ *
+ * RETURN VALUE
+ *   system command result
+ */
+int stub_procfs_teardown() {
+  char cmd[256];
+  sstrncpy(cmd, "rm -rf ", STATIC_ARRAY_SIZE(cmd));
+  strncat(cmd, proc_fs, STATIC_ARRAY_SIZE(cmd) - strlen(cmd) - 1);
+  return system(cmd);
+}
+
+/* Max PID value. More info:
+ * http://web.archive.org/web/20111209081734/http://research.cs.wisc.edu/condor/condorg/linux_scalability.html
+ */
+#define MAX_PID 4194304
+#define MAX_PID_STR "4194304"
+
+/***************************************************************************
+ * tests
+ */
+DEF_TEST(proc_pids_init__on_nullptr) {
+  /* setup */
+  const char *procs_names_array[] = {"proc1", "proc2", "proc3"};
+  const size_t procs_names_array_size = STATIC_ARRAY_SIZE(procs_names_array);
+  proc_pids_t **proc_pids_array = NULL;
+
+  /* check */
+  int result = proc_pids_init(procs_names_array, procs_names_array_size,
+                              &proc_pids_array);
+  EXPECT_EQ_INT(0, result);
+  for (size_t i = 0; i < procs_names_array_size; ++i)
+    EXPECT_EQ_STR(procs_names_array[i], proc_pids_array[i]->process_name);
+
+  /* cleanup */
+  proc_pids_free(proc_pids_array, procs_names_array_size);
+  return 0;
+}
+
+DEF_TEST(pid_list_add_pid__empty_list) {
+  /* setup */
+  pids_list_t *proc_pids_instance = calloc(1, sizeof(*proc_pids_instance));
+  pid_t pid = 1234;
+
+  /* check */
+  pids_list_add_pid(proc_pids_instance, pid);
+  EXPECT_EQ_INT(pid, proc_pids_instance->pids[0]);
+
+  /* cleanup */
+  pids_list_free(proc_pids_instance);
+  return 0;
+}
+
+DEF_TEST(pid_list_add_pid__non_empty_list) {
+  /* setup */
+  pids_list_t *proc_pids_instance = calloc(1, sizeof(*proc_pids_instance));
+  pid_t pids[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+
+  /* check */
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids); ++i)
+    pids_list_add_pid(proc_pids_instance, pids[i]);
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids); ++i) {
+    EXPECT_EQ_INT(pids[i], proc_pids_instance->pids[i]);
+  }
+
+  /* cleanup */
+  pids_list_free(proc_pids_instance);
+  return 0;
+}
+
+DEF_TEST(pids_list_add_pids_list__non_empty_lists) {
+  /* setup */
+  pid_t pids_array_1[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+  pid_t pids_array_2[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+  pids_list_t *pids_list_1 = calloc(1, sizeof(*pids_list_1));
+  pids_list_t *pids_list_2 = calloc(1, sizeof(*pids_list_2));
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_1); ++i) {
+    pids_list_add_pid(pids_list_1, pids_array_1[i]);
+    pids_list_add_pid(pids_list_2, pids_array_2[i]);
+  }
+
+  /* check */
+  int result = pids_list_add_list(pids_list_1, pids_list_2);
+  EXPECT_EQ_INT(0, result);
+  EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_2) +
+                    STATIC_ARRAY_SIZE(pids_array_1),
+                pids_list_1->size);
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_1); ++i) {
+    EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array_1[i]));
+    EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array_2[i]));
+  }
+
+  /* setup */
+  pids_list_free(pids_list_1);
+  pids_list_free(pids_list_2);
+  return 0;
+}
+
+DEF_TEST(pids_list_add_pids_list__add_to_empty) {
+  /* setup */
+  pid_t pids_array[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+  pids_list_t *pids_list_1 = calloc(1, sizeof(*pids_list_1));
+  pids_list_t *pids_list_2 = calloc(1, sizeof(*pids_list_2));
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array); ++i)
+    pids_list_add_pid(pids_list_2, pids_array[i]);
+
+  /* check */
+  int result = pids_list_add_list(pids_list_1, pids_list_2);
+  EXPECT_EQ_INT(0, result);
+  EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array), pids_list_1->size);
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array); ++i)
+    EXPECT_EQ_INT(1, pids_list_contains_pid(pids_list_1, pids_array[i]));
+
+  /* setup */
+  pids_list_free(pids_list_1);
+  pids_list_free(pids_list_2);
+  return 0;
+}
+
+DEF_TEST(get_pid_number__valid_dir) {
+  /* setup */
+  struct dirent d;
+  sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+  d.d_type = DT_DIR;
+  pid_t pid = 0;
+
+  /* check */
+  int pid_conversion = get_pid_number(&d, &pid);
+
+  EXPECT_EQ_INT(0, pid_conversion);
+  EXPECT_EQ_INT(MAX_PID, pid);
+
+  /* cleanup */
+  return 0;
+}
+
+DEF_TEST(get_pid_number__invalid_dir_name) {
+  /* setup */
+  struct dirent d;
+  sstrncpy(d.d_name, "invalid", STATIC_ARRAY_SIZE(d.d_name));
+  d.d_type = DT_DIR;
+  pid_t pid = 0;
+
+  /* check */
+  int pid_conversion = get_pid_number(&d, &pid);
+
+  EXPECT_EQ_INT(-1, pid_conversion);
+  EXPECT_EQ_INT(0, pid);
+
+  /* cleanup */
+  return 0;
+}
+
+DEF_TEST(read_proc_name__valid_name) {
+  /* setup */
+  stub_proc_pid_t pp_stubs[] = {{"proc1", MAX_PID}};
+  stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+  struct dirent d;
+  sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+  d.d_type = DT_DIR;
+
+  /* check */
+  proc_comm_t comm;
+  int read_result = read_proc_name(proc_fs, &d, comm, STATIC_ARRAY_SIZE(comm));
+
+  EXPECT_EQ_INT(strlen(pp_stubs[0].comm), read_result);
+  EXPECT_EQ_STR(pp_stubs[0].comm, comm);
+
+  /* cleanup */
+  stub_procfs_teardown();
+  return 0;
+}
+
+DEF_TEST(read_proc_name__invalid_name) {
+  /* setup */
+  struct dirent d;
+  sstrncpy(d.d_name, MAX_PID_STR, STATIC_ARRAY_SIZE(d.d_name));
+  d.d_type = DT_DIR;
+
+  /* check */
+  proc_comm_t comm;
+  int read_result = read_proc_name(proc_fs, &d, comm, STATIC_ARRAY_SIZE(comm));
+
+  EXPECT_EQ_INT(-1, read_result);
+
+  /* cleanup */
+  return 0;
+}
+
+DEF_TEST(proc_pids_update__one_proc_many_pid) {
+  /* setup */
+  const char *proc_names[] = {"proc1"};
+  stub_proc_pid_t pp_stubs[] = {{"proc1", 1007},
+                                {"proc1", 1008},
+                                {"proc1", 1009},
+                                {"proc2", 1010},
+                                {"proc3", 1011}};
+  proc_pids_t **proc_pids = NULL;
+  int result;
+  stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+
+  result =
+      proc_pids_init(proc_names, STATIC_ARRAY_SIZE(proc_names), &proc_pids);
+  EXPECT_EQ_INT(0, result);
+
+  /* check */
+  result = proc_pids_update(proc_fs, proc_pids, STATIC_ARRAY_SIZE(proc_names));
+  EXPECT_EQ_INT(0, result);
+
+  /* proc name check */
+  EXPECT_EQ_STR(proc_names[0], proc_pids[0]->process_name);
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(pp_stubs); ++i) {
+    if (0 == strcmp(pp_stubs[i].comm, proc_names[0]))
+      /* check if proc struct has correct pids */
+      EXPECT_EQ_INT(pids_list_contains_pid(proc_pids[0]->curr, pp_stubs[i].pid),
+                    1);
+    else
+      /* check if proc struct has no incorrect pids */
+      EXPECT_EQ_INT(pids_list_contains_pid(proc_pids[0]->curr, pp_stubs[i].pid),
+                    0);
+  }
+
+  /* cleanup */
+  proc_pids_free(proc_pids, STATIC_ARRAY_SIZE(proc_names));
+  stub_procfs_teardown();
+  return 0;
+}
+
+DEF_TEST(proc_pids_update__many_proc_many_pid) {
+  /* setup */
+  const char *proc_names[] = {"proc1", "proc2", "proc3"};
+  stub_proc_pid_t pp_stubs[] = {
+      {"proc1", 1007}, {"proc1", 1008}, {"proc1", 1009}, {"proc2", 2007},
+      {"proc2", 2008}, {"proc2", 2009}, {"proc3", 3007}, {"proc3", 3008},
+      {"proc3", 3009}, {"proc4", 4007}, {"proc4", 4008}, {"proc4", 4009},
+      {"proc5", 5007}, {"proc5", 5008}, {"proc5", 5009}};
+  proc_pids_t **proc_pids = NULL;
+  int result;
+  stub_procfs_setup(pp_stubs, STATIC_ARRAY_SIZE(pp_stubs));
+
+  result =
+      proc_pids_init(proc_names, STATIC_ARRAY_SIZE(proc_names), &proc_pids);
+  EXPECT_EQ_INT(0, result);
+
+  /* check */
+  result = proc_pids_update(proc_fs, proc_pids, STATIC_ARRAY_SIZE(proc_names));
+  EXPECT_EQ_INT(0, result);
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(proc_names); ++i) {
+
+    /* proc name check */
+    EXPECT_EQ_STR(proc_names[i], proc_pids[i]->process_name);
+
+    for (size_t j = 0; j < STATIC_ARRAY_SIZE(pp_stubs); ++j) {
+      if (0 == strcmp(pp_stubs[j].comm, proc_names[i]))
+        /* check if proc struct has correct pids */
+        EXPECT_EQ_INT(
+            pids_list_contains_pid(proc_pids[i]->curr, pp_stubs[j].pid), 1);
+      else
+        /* check if proc struct has no incorrect pids */
+        EXPECT_EQ_INT(
+            pids_list_contains_pid(proc_pids[i]->curr, pp_stubs[j].pid), 0);
+    }
+  }
+
+  /* cleanup */
+  proc_pids_free(proc_pids, STATIC_ARRAY_SIZE(proc_names));
+  stub_procfs_teardown();
+  return 0;
+}
+
+DEF_TEST(pids_list_diff__all_changed) {
+  /* setup */
+  pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+  pid_t pids_array_after[] = {2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007};
+  proc_pids_t proc_pids;
+  pids_list_t curr;
+  pids_list_t prev;
+
+  prev.pids = pids_array_before;
+  prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+  prev.allocated = prev.size;
+  curr.pids = pids_array_after;
+  curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+  curr.allocated = curr.size;
+  proc_pids.curr = &curr;
+  proc_pids.prev = &prev;
+
+  pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+  pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+  /* check */
+  int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+  EXPECT_EQ_INT(0, result);
+  EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_before), lost_pids->size);
+  EXPECT_EQ_INT(STATIC_ARRAY_SIZE(pids_array_after), new_pids->size);
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(pids_array_before); ++i) {
+    EXPECT_EQ_INT(1, pids_list_contains_pid(new_pids, pids_array_after[i]));
+    EXPECT_EQ_INT(1, pids_list_contains_pid(lost_pids, pids_array_before[i]));
+  }
+
+  /* cleanup */
+  pids_list_free(new_pids);
+  pids_list_free(lost_pids);
+
+  return 0;
+}
+
+DEF_TEST(pids_list_diff__nothing_changed) {
+  /* setup */
+  pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+  proc_pids_t proc_pids;
+  pids_list_t curr;
+  pids_list_t prev;
+
+  prev.pids = pids_array_before;
+  prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+  prev.allocated = prev.size;
+  curr.pids = pids_array_before;
+  curr.size = STATIC_ARRAY_SIZE(pids_array_before);
+  curr.allocated = curr.size;
+  proc_pids.curr = &curr;
+  proc_pids.prev = &prev;
+
+  pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+  pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+  /* check */
+  int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+  EXPECT_EQ_INT(0, result);
+  EXPECT_EQ_INT(0, lost_pids->size);
+  EXPECT_EQ_INT(0, new_pids->size);
+
+  /* cleanup */
+  pids_list_free(lost_pids);
+  pids_list_free(new_pids);
+
+  return 0;
+}
+
+DEF_TEST(pids_list_diff__one_added) {
+  /* setup */
+  pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+  pid_t pids_array_after[] = {1000, 1001, 1002, 1003, 1004,
+                              1005, 1006, 1007, 1008};
+  proc_pids_t proc_pids;
+  pids_list_t curr;
+  pids_list_t prev;
+
+  prev.pids = pids_array_before;
+  prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+  prev.allocated = prev.size;
+  curr.pids = pids_array_after;
+  curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+  curr.allocated = curr.size;
+  proc_pids.curr = &curr;
+  proc_pids.prev = &prev;
+
+  pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+  pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+  /* check */
+  int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+  EXPECT_EQ_INT(0, result);
+  EXPECT_EQ_INT(0, lost_pids->size);
+  EXPECT_EQ_INT(1, new_pids->size);
+  EXPECT_EQ_INT(1008, new_pids->pids[0]);
+
+  /* cleanup */
+  pids_list_free(lost_pids);
+  pids_list_free(new_pids);
+
+  return 0;
+}
+
+DEF_TEST(pids_list_diff__one_removed) {
+  /* setup */
+  pid_t pids_array_before[] = {1000, 1001, 1002, 1003, 1004,
+                               1005, 1006, 1007, 1008};
+  pid_t pids_array_after[] = {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007};
+
+  proc_pids_t proc_pids;
+  pids_list_t curr;
+  pids_list_t prev;
+
+  prev.pids = pids_array_before;
+  prev.size = STATIC_ARRAY_SIZE(pids_array_before);
+  prev.allocated = prev.size;
+  curr.pids = pids_array_after;
+  curr.size = STATIC_ARRAY_SIZE(pids_array_after);
+  curr.allocated = curr.size;
+  proc_pids.curr = &curr;
+  proc_pids.prev = &prev;
+
+  pids_list_t *new_pids = calloc(1, sizeof(*new_pids));
+  pids_list_t *lost_pids = calloc(1, sizeof(*lost_pids));
+
+  /* check */
+  int result = pids_list_diff(&proc_pids, new_pids, lost_pids);
+  EXPECT_EQ_INT(0, result);
+  EXPECT_EQ_INT(0, new_pids->size);
+  EXPECT_EQ_INT(1, lost_pids->size);
+  EXPECT_EQ_INT(1008, lost_pids->pids[0]);
+
+  /* cleanup */
+  pids_list_free(lost_pids);
+  pids_list_free(new_pids);
+
+  return 0;
+}
+
+int main(void) {
+  stub_procfs_teardown();
+  RUN_TEST(proc_pids_init__on_nullptr);
+  RUN_TEST(pid_list_add_pid__empty_list);
+  RUN_TEST(pid_list_add_pid__non_empty_list);
+  RUN_TEST(pids_list_add_pids_list__non_empty_lists);
+  RUN_TEST(pids_list_add_pids_list__add_to_empty);
+  RUN_TEST(get_pid_number__valid_dir);
+  RUN_TEST(get_pid_number__invalid_dir_name);
+  RUN_TEST(read_proc_name__valid_name);
+  RUN_TEST(read_proc_name__invalid_name);
+  RUN_TEST(proc_pids_update__one_proc_many_pid);
+  RUN_TEST(proc_pids_update__many_proc_many_pid);
+  RUN_TEST(pids_list_diff__all_changed);
+  RUN_TEST(pids_list_diff__nothing_changed);
+  RUN_TEST(pids_list_diff__one_added);
+  RUN_TEST(pids_list_diff__one_removed);
+  stub_procfs_teardown();
+  END_TEST;
+}
index fd20c77..b9b224e 100644 (file)
 #endif
 
 /*
-  virConnectListAllDomains() appeared in 0.10.2
-  Note that LIBVIR_CHECK_VERSION appeared a year later, so
-  in some systems which actually have virConnectListAllDomains()
+  virConnectListAllDomains() appeared in 0.10.2 (Sep 2012)
+  Note that LIBVIR_CHECK_VERSION appeared a year later (Dec 2013,
+  libvirt-1.2.0),
+  so in some systems which actually have virConnectListAllDomains()
   we can't detect this.
  */
 #if LIBVIR_CHECK_VERSION(0, 10, 2)
 #define HAVE_DOM_REASON_POSTCOPY 1
 #endif
 
+#if LIBVIR_CHECK_VERSION(4, 10, 0)
+#define HAVE_DOM_REASON_SHUTOFF_DAEMON 1
+#endif
 #endif /* LIBVIR_CHECK_VERSION */
 
 /* structure used for aggregating notification-thread data*/
@@ -117,32 +121,6 @@ typedef struct virt_notif_thread_s {
   bool is_active;
 } virt_notif_thread_t;
 
-static const char *config_keys[] = {"Connection",
-
-                                    "RefreshInterval",
-
-                                    "Domain",
-                                    "BlockDevice",
-                                    "BlockDeviceFormat",
-                                    "BlockDeviceFormatBasename",
-                                    "InterfaceDevice",
-                                    "IgnoreSelected",
-
-                                    "HostnameFormat",
-                                    "HostnameMetadataNS",
-                                    "HostnameMetadataXPath",
-                                    "InterfaceFormat",
-
-                                    "PluginInstanceFormat",
-
-                                    "Instances",
-                                    "ExtraStats",
-                                    "PersistentNotification",
-
-                                    "ReportBlockDevices",
-                                    "ReportNetworkInterfaces",
-                                    NULL};
-
 /* PersistentNotification is false by default */
 static bool persistent_notification = false;
 
@@ -326,6 +304,16 @@ static int map_domain_event_detail_to_reason(int event, int detail) {
     switch (detail) {
     case VIR_DOMAIN_EVENT_SHUTDOWN_FINISHED: /* Guest finished shutdown
                                                 sequence */
+#ifdef LIBVIR_CHECK_VERSION
+#if LIBVIR_CHECK_VERSION(3, 4, 0)
+    case VIR_DOMAIN_EVENT_SHUTDOWN_GUEST: /* Domain finished shutting down after
+                                             request from the guest itself (e.g.
+                                             hardware-specific action) */
+    case VIR_DOMAIN_EVENT_SHUTDOWN_HOST:  /* Domain finished shutting down after
+                                             request from the host (e.g. killed
+                                             by a signal) */
+#endif
+#endif
       ret = VIR_DOMAIN_SHUTDOWN_USER;
       break;
     default:
@@ -451,6 +439,10 @@ const char *domain_reasons[][DOMAIN_STATE_REASON_MAX_SIZE] = {
             "domain failed to start",
         [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT] =
             "restored from a snapshot which was taken while domain was shutoff",
+#ifdef HAVE_DOM_REASON_SHUTOFF_DAEMON
+        [VIR_DOMAIN_SHUTOFF][VIR_DOMAIN_SHUTOFF_DAEMON] =
+            "daemon decides to kill domain during reconnection processing",
+#endif
 
         [VIR_DOMAIN_CRASHED][VIR_DOMAIN_CRASHED_UNKNOWN] =
             "the reason is unknown",
@@ -465,7 +457,6 @@ const char *domain_reasons[][DOMAIN_STATE_REASON_MAX_SIZE] = {
 };
 #endif /* HAVE_DOM_REASON */
 
-#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
 #define NANOSEC_IN_SEC 1e9
 
 #define GET_STATS(_f, _name, ...)                                              \
@@ -500,6 +491,7 @@ static int ignore_device_match(ignorelist_t *, const char *domname,
 struct block_device {
   virDomainPtr dom; /* domain */
   char *path;       /* name of block device */
+  bool has_source;  /* information whether source is defined or not */
 };
 
 /* Actual list of network interfaces found on last refresh. */
@@ -534,7 +526,7 @@ static int add_domain(struct lv_read_state *state, virDomainPtr dom,
 
 static void free_block_devices(struct lv_read_state *state);
 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
-                            const char *path);
+                            const char *path, bool has_source);
 
 static void free_interface_devices(struct lv_read_state *state);
 static int add_interface_device(struct lv_read_state *state, virDomainPtr dom,
@@ -616,6 +608,11 @@ enum ex_stats {
   ex_stats_job_stats_completed = 1 << 8,
   ex_stats_job_stats_background = 1 << 9,
 #endif
+  ex_stats_disk_allocation = 1 << 10,
+  ex_stats_disk_capacity = 1 << 11,
+  ex_stats_disk_physical = 1 << 12,
+  ex_stats_memory = 1 << 13,
+  ex_stats_vcpu = 1 << 14
 };
 
 static unsigned int extra_stats = ex_stats_none;
@@ -643,6 +640,11 @@ static const struct ex_stats_item ex_stats_table[] = {
     {"job_stats_completed", ex_stats_job_stats_completed},
     {"job_stats_background", ex_stats_job_stats_background},
 #endif
+    {"disk_allocation", ex_stats_disk_allocation},
+    {"disk_capacity", ex_stats_disk_capacity},
+    {"disk_physical", ex_stats_disk_physical},
+    {"memory", ex_stats_memory},
+    {"vcpu", ex_stats_vcpu},
     {NULL, ex_stats_none},
 };
 
@@ -655,8 +657,10 @@ static enum if_field interface_format = if_name;
 static time_t last_refresh = (time_t)0;
 
 static int refresh_lists(struct lv_read_instance *inst);
+static int register_event_impl(void);
+static int start_event_loop(virt_notif_thread_t *thread_data);
 
-struct lv_block_info {
+struct lv_block_stats {
   virDomainBlockStatsStruct bi;
 
   long long rd_total_times;
@@ -666,50 +670,56 @@ struct lv_block_info {
   long long fl_total_times;
 };
 
-static void init_block_info(struct lv_block_info *binfo) {
-  if (binfo == NULL)
+static void init_block_stats(struct lv_block_stats *bstats) {
+  if (bstats == NULL)
     return;
 
-  binfo->bi.rd_req = -1;
-  binfo->bi.wr_req = -1;
-  binfo->bi.rd_bytes = -1;
-  binfo->bi.wr_bytes = -1;
+  bstats->bi.rd_req = -1;
+  bstats->bi.wr_req = -1;
+  bstats->bi.rd_bytes = -1;
+  bstats->bi.wr_bytes = -1;
+
+  bstats->rd_total_times = -1;
+  bstats->wr_total_times = -1;
+  bstats->fl_req = -1;
+  bstats->fl_total_times = -1;
+}
 
-  binfo->rd_total_times = -1;
-  binfo->wr_total_times = -1;
-  binfo->fl_req = -1;
-  binfo->fl_total_times = -1;
+static void init_block_info(virDomainBlockInfoPtr binfo) {
+  binfo->allocation = -1;
+  binfo->capacity = -1;
+  binfo->physical = -1;
 }
 
 #ifdef HAVE_BLOCK_STATS_FLAGS
 
-#define GET_BLOCK_INFO_VALUE(NAME, FIELD)                                      \
+#define GET_BLOCK_STATS_VALUE(NAME, FIELD)                                     \
   if (!strcmp(param[i].field, NAME)) {                                         \
-    binfo->FIELD = param[i].value.l;                                           \
+    bstats->FIELD = param[i].value.l;                                          \
     continue;                                                                  \
   }
 
-static int get_block_info(struct lv_block_info *binfo,
-                          virTypedParameterPtr param, int nparams) {
-  if (binfo == NULL || param == NULL)
+static int get_block_stats(struct lv_block_stats *bstats,
+                           virTypedParameterPtr param, int nparams) {
+  if (bstats == NULL || param == NULL)
     return -1;
 
   for (int i = 0; i < nparams; ++i) {
     /* ignore type. Everything must be LLONG anyway. */
-    GET_BLOCK_INFO_VALUE("rd_operations", bi.rd_req);
-    GET_BLOCK_INFO_VALUE("wr_operations", bi.wr_req);
-    GET_BLOCK_INFO_VALUE("rd_bytes", bi.rd_bytes);
-    GET_BLOCK_INFO_VALUE("wr_bytes", bi.wr_bytes);
-    GET_BLOCK_INFO_VALUE("rd_total_times", rd_total_times);
-    GET_BLOCK_INFO_VALUE("wr_total_times", wr_total_times);
-    GET_BLOCK_INFO_VALUE("flush_operations", fl_req);
-    GET_BLOCK_INFO_VALUE("flush_total_times", fl_total_times);
+    GET_BLOCK_STATS_VALUE("rd_operations", bi.rd_req);
+    GET_BLOCK_STATS_VALUE("wr_operations", bi.wr_req);
+    GET_BLOCK_STATS_VALUE("rd_bytes", bi.rd_bytes);
+    GET_BLOCK_STATS_VALUE("wr_bytes", bi.wr_bytes);
+    GET_BLOCK_STATS_VALUE("rd_total_times", rd_total_times);
+    GET_BLOCK_STATS_VALUE("wr_total_times", wr_total_times);
+    GET_BLOCK_STATS_VALUE("flush_operations", fl_req);
+    GET_BLOCK_STATS_VALUE("flush_total_times", fl_total_times);
   }
 
   return 0;
 }
 
-#undef GET_BLOCK_INFO_VALUE
+#undef GET_BLOCK_STATS_VALUE
 
 #endif /* HAVE_BLOCK_STATS_FLAGS */
 
@@ -722,7 +732,7 @@ static int get_block_info(struct lv_block_info *binfo,
       ERROR(PLUGIN_NAME " plugin: %s failed: %s", (s), err->message);          \
   } while (0)
 
-char *metadata_get_hostname(virDomainPtr dom) {
+static char *metadata_get_hostname(virDomainPtr dom) {
   const char *xpath_str = NULL;
   if (hm_xpath == NULL)
     xpath_str = "/instance/name/text()";
@@ -930,13 +940,14 @@ static void memory_submit(virDomainPtr dom, gauge_t value) {
 
 static void memory_stats_submit(gauge_t value, virDomainPtr dom,
                                 int tag_index) {
-  static const char *tags[] = {"swap_in",        "swap_out", "major_fault",
-                               "minor_fault",    "unused",   "available",
-                               "actual_balloon", "rss",      "usable",
-                               "last_update"};
+  static const char *tags[] = {"swap_in",        "swap_out",   "major_fault",
+                               "minor_fault",    "unused",     "available",
+                               "actual_balloon", "rss",        "usable",
+                               "last_update",    "disk_caches"};
 
   if ((tag_index < 0) || (tag_index >= (int)STATIC_ARRAY_SIZE(tags))) {
-    ERROR("virt plugin: Array index out of bounds: tag_index = %d", tag_index);
+    ERROR(PLUGIN_NAME " plugin: Array index out of bounds: tag_index = %d",
+          tag_index);
     return;
   }
 
@@ -966,7 +977,7 @@ static double cpu_ns_to_percent(unsigned int node_cpus,
   }
 
   DEBUG(PLUGIN_NAME " plugin: node_cpus=%u cpu_time_old=%" PRIu64
-                    " cpu_time_new=%" PRIu64 "cpu_time_diff=%" PRIu64
+                    " cpu_time_new=%" PRIu64 " cpu_time_diff=%" PRIu64
                     " time_diff_sec=%f percent=%f",
         node_cpus, (uint64_t)cpu_time_old, (uint64_t)cpu_time_new,
         (uint64_t)cpu_time_diff, time_diff_sec, percent);
@@ -1002,8 +1013,9 @@ static void vcpu_submit(derive_t value, virDomainPtr dom, int vcpu_nr,
   submit(dom, type, type_instance, &(value_t){.derive = value}, 1);
 }
 
-static void disk_submit(struct lv_block_info *binfo, virDomainPtr dom,
-                        const char *dev) {
+static void disk_block_stats_submit(struct lv_block_stats *bstats,
+                                    virDomainPtr dom, const char *dev,
+                                    virDomainBlockInfoPtr binfo) {
   char *dev_copy = strdup(dev);
   const char *type_instance = dev_copy;
 
@@ -1022,34 +1034,58 @@ static void disk_submit(struct lv_block_info *binfo, virDomainPtr dom,
   snprintf(flush_type_instance, sizeof(flush_type_instance), "flush-%s",
            type_instance);
 
-  if ((binfo->bi.rd_req != -1) && (binfo->bi.wr_req != -1))
-    submit_derive2("disk_ops", (derive_t)binfo->bi.rd_req,
-                   (derive_t)binfo->bi.wr_req, dom, type_instance);
+  if ((bstats->bi.rd_req != -1) && (bstats->bi.wr_req != -1))
+    submit_derive2("disk_ops", (derive_t)bstats->bi.rd_req,
+                   (derive_t)bstats->bi.wr_req, dom, type_instance);
 
-  if ((binfo->bi.rd_bytes != -1) && (binfo->bi.wr_bytes != -1))
-    submit_derive2("disk_octets", (derive_t)binfo->bi.rd_bytes,
-                   (derive_t)binfo->bi.wr_bytes, dom, type_instance);
+  if ((bstats->bi.rd_bytes != -1) && (bstats->bi.wr_bytes != -1))
+    submit_derive2("disk_octets", (derive_t)bstats->bi.rd_bytes,
+                   (derive_t)bstats->bi.wr_bytes, dom, type_instance);
 
   if (extra_stats & ex_stats_disk) {
-    if ((binfo->rd_total_times != -1) && (binfo->wr_total_times != -1))
-      submit_derive2("disk_time", (derive_t)binfo->rd_total_times,
-                     (derive_t)binfo->wr_total_times, dom, type_instance);
+    if ((bstats->rd_total_times != -1) && (bstats->wr_total_times != -1))
+      submit_derive2("disk_time", (derive_t)bstats->rd_total_times,
+                     (derive_t)bstats->wr_total_times, dom, type_instance);
 
-    if (binfo->fl_req != -1)
+    if (bstats->fl_req != -1)
       submit(dom, "total_requests", flush_type_instance,
-             &(value_t){.derive = (derive_t)binfo->fl_req}, 1);
-    if (binfo->fl_total_times != -1) {
-      derive_t value = binfo->fl_total_times / 1000; // ns -> ms
+             &(value_t){.derive = (derive_t)bstats->fl_req}, 1);
+    if (bstats->fl_total_times != -1) {
+      derive_t value = bstats->fl_total_times / 1000; // ns -> ms
       submit(dom, "total_time_in_ms", flush_type_instance,
              &(value_t){.derive = value}, 1);
     }
   }
 
+  /* disk_allocation, disk_capacity and disk_physical are stored only
+   * if corresponding extrastats are set in collectd configuration file */
+  if ((extra_stats & ex_stats_disk_allocation) && binfo->allocation != -1)
+    submit(dom, "disk_allocation", type_instance,
+           &(value_t){.gauge = (gauge_t)binfo->allocation}, 1);
+
+  if ((extra_stats & ex_stats_disk_capacity) && binfo->capacity != -1)
+    submit(dom, "disk_capacity", type_instance,
+           &(value_t){.gauge = (gauge_t)binfo->capacity}, 1);
+
+  if ((extra_stats & ex_stats_disk_physical) && binfo->physical != -1)
+    submit(dom, "disk_physical", type_instance,
+           &(value_t){.gauge = (gauge_t)binfo->physical}, 1);
+
   sfree(dev_copy);
 }
 
-static unsigned int parse_ex_stats_flags(char **exstats, int numexstats) {
+/**
+ * Function for parsing ExtraStats configuration options.
+ * Result of parsing is stored under 'out_parsed_flags' pointer.
+ *
+ * Returns 0 in case of success and 1 in case of parsing error
+ */
+static int parse_ex_stats_flags(unsigned int *out_parsed_flags, char **exstats,
+                                int numexstats) {
   unsigned int ex_stats_flags = ex_stats_none;
+
+  assert(out_parsed_flags != NULL);
+
   for (int i = 0; i < numexstats; i++) {
     for (int j = 0; ex_stats_table[j].name != NULL; j++) {
       if (strcasecmp(exstats[i], ex_stats_table[j].name) == 0) {
@@ -1062,10 +1098,13 @@ static unsigned int parse_ex_stats_flags(char **exstats, int numexstats) {
       if (ex_stats_table[j + 1].name == NULL) {
         ERROR(PLUGIN_NAME " plugin: Unmatched ExtraStats option: %s",
               exstats[i]);
+        return 1;
       }
     }
   }
-  return ex_stats_flags;
+
+  *out_parsed_flags = ex_stats_flags;
+  return 0;
 }
 
 static void domain_state_submit_notif(virDomainPtr dom, int state, int reason) {
@@ -1138,224 +1177,255 @@ static int lv_init_ignorelists() {
   return 0;
 }
 
-static int lv_config(const char *key, const char *value) {
-  if (virInitialize() != 0)
-    return 1;
-
-  if (lv_init_ignorelists() != 0)
-    return 1;
-
-  if (strcasecmp(key, "Connection") == 0) {
-    char *tmp = strdup(value);
-    if (tmp == NULL) {
-      ERROR(PLUGIN_NAME " plugin: Connection strdup failed.");
-      return 1;
-    }
-    sfree(conn_string);
-    conn_string = tmp;
-    return 0;
-  }
-
-  if (strcasecmp(key, "RefreshInterval") == 0) {
-    char *eptr = NULL;
-    interval = strtol(value, &eptr, 10);
-    if (eptr == NULL || *eptr != '\0')
-      return 1;
-    return 0;
+/* Validates config option that may take multiple strings arguments.
+ * Returns 0 on success, -1 otherwise */
+static int check_config_multiple_string_entry(const oconfig_item_t *ci) {
+  if (ci == NULL) {
+    ERROR(PLUGIN_NAME " plugin: ci oconfig_item can't be NULL");
+    return -1;
   }
 
-  if (strcasecmp(key, "Domain") == 0) {
-    if (ignorelist_add(il_domains, value))
-      return 1;
-    return 0;
-  }
-  if (strcasecmp(key, "BlockDevice") == 0) {
-    if (ignorelist_add(il_block_devices, value))
-      return 1;
-    return 0;
+  if (ci->values_num < 1) {
+    ERROR(PLUGIN_NAME
+          " plugin: the '%s' option requires at least one string argument",
+          ci->key);
+    return -1;
   }
 
-  if (strcasecmp(key, "BlockDeviceFormat") == 0) {
-    if (strcasecmp(value, "target") == 0)
-      blockdevice_format = target;
-    else if (strcasecmp(value, "source") == 0)
-      blockdevice_format = source;
-    else {
-      ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s", value);
+  for (int i = 0; i < ci->values_num; ++i) {
+    if (ci->values[i].type != OCONFIG_TYPE_STRING) {
+      ERROR(PLUGIN_NAME
+            " plugin: one of the '%s' options is not a valid string",
+            ci->key);
       return -1;
     }
-    return 0;
-  }
-  if (strcasecmp(key, "BlockDeviceFormatBasename") == 0) {
-    blockdevice_format_basename = IS_TRUE(value) ? true : false;
-    return 0;
-  }
-  if (strcasecmp(key, "InterfaceDevice") == 0) {
-    if (ignorelist_add(il_interface_devices, value))
-      return 1;
-    return 0;
   }
 
-  if (strcasecmp(key, "IgnoreSelected") == 0) {
-    if (IS_TRUE(value)) {
-      ignorelist_set_invert(il_domains, 0);
-      ignorelist_set_invert(il_block_devices, 0);
-      ignorelist_set_invert(il_interface_devices, 0);
-    } else {
-      ignorelist_set_invert(il_domains, 1);
-      ignorelist_set_invert(il_block_devices, 1);
-      ignorelist_set_invert(il_interface_devices, 1);
-    }
-    return 0;
-  }
+  return 0;
+}
 
-  if (strcasecmp(key, "HostnameMetadataNS") == 0) {
-    char *tmp = strdup(value);
-    if (tmp == NULL) {
-      ERROR(PLUGIN_NAME " plugin: HostnameMetadataNS strdup failed.");
-      return 1;
-    }
-    sfree(hm_ns);
-    hm_ns = tmp;
-    return 0;
+static int lv_config(oconfig_item_t *ci) {
+  if (lv_init_ignorelists() != 0) {
+    ERROR(PLUGIN_NAME " plugin: lv_init_ignorelist failed.");
+    return -1;
   }
 
-  if (strcasecmp(key, "HostnameMetadataXPath") == 0) {
-    char *tmp = strdup(value);
-    if (tmp == NULL) {
-      ERROR(PLUGIN_NAME " plugin: HostnameMetadataXPath strdup failed.");
-      return 1;
-    }
-    sfree(hm_xpath);
-    hm_xpath = tmp;
-    return 0;
-  }
+  for (int i = 0; i < ci->children_num; ++i) {
+    oconfig_item_t *c = ci->children + i;
 
-  if (strcasecmp(key, "HostnameFormat") == 0) {
-    char *value_copy = strdup(value);
-    if (value_copy == NULL) {
-      ERROR(PLUGIN_NAME " plugin: strdup failed.");
-      return -1;
-    }
+    if (strcasecmp(c->key, "Connection") == 0) {
+      if (cf_util_get_string(c, &conn_string) != 0 || conn_string == NULL)
+        return -1;
 
-    char *fields[HF_MAX_FIELDS];
-    int n = strsplit(value_copy, fields, HF_MAX_FIELDS);
-    if (n < 1) {
-      sfree(value_copy);
-      ERROR(PLUGIN_NAME " plugin: HostnameFormat: no fields");
-      return -1;
-    }
+      continue;
+    } else if (strcasecmp(c->key, "RefreshInterval") == 0) {
+      if (cf_util_get_int(c, &interval) != 0)
+        return -1;
 
-    for (int i = 0; i < n; ++i) {
-      if (strcasecmp(fields[i], "hostname") == 0)
-        hostname_format[i] = hf_hostname;
-      else if (strcasecmp(fields[i], "name") == 0)
-        hostname_format[i] = hf_name;
-      else if (strcasecmp(fields[i], "uuid") == 0)
-        hostname_format[i] = hf_uuid;
-      else if (strcasecmp(fields[i], "metadata") == 0)
-        hostname_format[i] = hf_metadata;
+      continue;
+    } else if (strcasecmp(c->key, "Domain") == 0) {
+      char *domain_name = NULL;
+      if (cf_util_get_string(c, &domain_name) != 0)
+        return -1;
+
+      if (ignorelist_add(il_domains, domain_name)) {
+        ERROR(PLUGIN_NAME " plugin: Adding '%s' to domain-ignorelist failed",
+              domain_name);
+        sfree(domain_name);
+        return -1;
+      }
+
+      sfree(domain_name);
+      continue;
+    } else if (strcasecmp(c->key, "BlockDevice") == 0) {
+      char *device_name = NULL;
+      if (cf_util_get_string(c, &device_name) != 0)
+        return -1;
+
+      if (ignorelist_add(il_block_devices, device_name) != 0) {
+        ERROR(PLUGIN_NAME
+              " plugin: Adding '%s' to block-device-ignorelist failed",
+              device_name);
+        sfree(device_name);
+        return -1;
+      }
+
+      sfree(device_name);
+      continue;
+    } else if (strcasecmp(c->key, "BlockDeviceFormat") == 0) {
+      char *device_format = NULL;
+      if (cf_util_get_string(c, &device_format) != 0)
+        return -1;
+
+      if (strcasecmp(device_format, "target") == 0)
+        blockdevice_format = target;
+      else if (strcasecmp(device_format, "source") == 0)
+        blockdevice_format = source;
       else {
-        ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s",
-              fields[i]);
-        sfree(value_copy);
+        ERROR(PLUGIN_NAME " plugin: unknown BlockDeviceFormat: %s",
+              device_format);
+        sfree(device_format);
         return -1;
       }
-    }
-    sfree(value_copy);
 
-    for (int i = n; i < HF_MAX_FIELDS; ++i)
-      hostname_format[i] = hf_none;
+      sfree(device_format);
+      continue;
+    } else if (strcasecmp(c->key, "BlockDeviceFormatBasename") == 0) {
+      if (cf_util_get_boolean(c, &blockdevice_format_basename) != 0)
+        return -1;
 
-    return 0;
-  }
+      continue;
+    } else if (strcasecmp(c->key, "InterfaceDevice") == 0) {
+      char *interface_name = NULL;
+      if (cf_util_get_string(c, &interface_name) != 0)
+        return -1;
 
-  if (strcasecmp(key, "PluginInstanceFormat") == 0) {
-    char *value_copy = strdup(value);
-    if (value_copy == NULL) {
-      ERROR(PLUGIN_NAME " plugin: strdup failed.");
-      return -1;
-    }
+      if (ignorelist_add(il_interface_devices, interface_name)) {
+        ERROR(PLUGIN_NAME " plugin: Adding '%s' to interface-ignorelist failed",
+              interface_name);
+        sfree(interface_name);
+        return -1;
+      }
 
-    char *fields[PLGINST_MAX_FIELDS];
-    int n = strsplit(value_copy, fields, PLGINST_MAX_FIELDS);
-    if (n < 1) {
-      sfree(value_copy);
-      ERROR(PLUGIN_NAME " plugin: PluginInstanceFormat: no fields");
-      return -1;
-    }
+      sfree(interface_name);
+      continue;
+    } else if (strcasecmp(c->key, "IgnoreSelected") == 0) {
+      bool ignore_selected = false;
+      if (cf_util_get_boolean(c, &ignore_selected) != 0)
+        return -1;
 
-    for (int i = 0; i < n; ++i) {
-      if (strcasecmp(fields[i], "none") == 0) {
-        plugin_instance_format[i] = plginst_none;
-        break;
-      } else if (strcasecmp(fields[i], "name") == 0)
-        plugin_instance_format[i] = plginst_name;
-      else if (strcasecmp(fields[i], "uuid") == 0)
-        plugin_instance_format[i] = plginst_uuid;
-      else if (strcasecmp(fields[i], "metadata") == 0)
-        plugin_instance_format[i] = plginst_metadata;
-      else {
-        ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s",
-              fields[i]);
-        sfree(value_copy);
+      if (ignore_selected) {
+        ignorelist_set_invert(il_domains, 0);
+        ignorelist_set_invert(il_block_devices, 0);
+        ignorelist_set_invert(il_interface_devices, 0);
+      } else {
+        ignorelist_set_invert(il_domains, 1);
+        ignorelist_set_invert(il_block_devices, 1);
+        ignorelist_set_invert(il_interface_devices, 1);
+      }
+
+      continue;
+    } else if (strcasecmp(c->key, "HostnameMetadataNS") == 0) {
+      if (cf_util_get_string(c, &hm_ns) != 0)
+        return -1;
+
+      continue;
+    } else if (strcasecmp(c->key, "HostnameMetadataXPath") == 0) {
+      if (cf_util_get_string(c, &hm_xpath) != 0)
+        return -1;
+
+      continue;
+    } else if (strcasecmp(c->key, "HostnameFormat") == 0) {
+      /* this option can take multiple strings arguments in one config line*/
+      if (check_config_multiple_string_entry(c) != 0) {
+        ERROR(PLUGIN_NAME " plugin: Could not get 'HostnameFormat' parameter");
         return -1;
       }
-    }
-    sfree(value_copy);
 
-    for (int i = n; i < PLGINST_MAX_FIELDS; ++i)
-      plugin_instance_format[i] = plginst_none;
+      const int params_num = c->values_num;
+      for (int i = 0; i < params_num; ++i) {
+        const char *param_name = c->values[i].value.string;
+        if (strcasecmp(param_name, "hostname") == 0)
+          hostname_format[i] = hf_hostname;
+        else if (strcasecmp(param_name, "name") == 0)
+          hostname_format[i] = hf_name;
+        else if (strcasecmp(param_name, "uuid") == 0)
+          hostname_format[i] = hf_uuid;
+        else if (strcasecmp(param_name, "metadata") == 0)
+          hostname_format[i] = hf_metadata;
+        else {
+          ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s",
+                param_name);
+          return -1;
+        }
+      }
 
-    return 0;
-  }
+      for (int i = params_num; i < HF_MAX_FIELDS; ++i)
+        hostname_format[i] = hf_none;
 
-  if (strcasecmp(key, "InterfaceFormat") == 0) {
-    if (strcasecmp(value, "name") == 0)
-      interface_format = if_name;
-    else if (strcasecmp(value, "address") == 0)
-      interface_format = if_address;
-    else if (strcasecmp(value, "number") == 0)
-      interface_format = if_number;
-    else {
-      ERROR(PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", value);
-      return -1;
-    }
-    return 0;
-  }
+      continue;
+    } else if (strcasecmp(c->key, "PluginInstanceFormat") == 0) {
+      /* this option can handle list of string parameters in one line*/
+      if (check_config_multiple_string_entry(c) != 0) {
+        ERROR(PLUGIN_NAME
+              " plugin: Could not get 'PluginInstanceFormat' parameter");
+        return -1;
+      }
 
-  if (strcasecmp(key, "Instances") == 0) {
-    char *eptr = NULL;
-    double val = strtod(value, &eptr);
+      const int params_num = c->values_num;
+      for (int i = 0; i < params_num; ++i) {
+        const char *param_name = c->values[i].value.string;
+        if (strcasecmp(param_name, "none") == 0) {
+          plugin_instance_format[i] = plginst_none;
+          break;
+        } else if (strcasecmp(param_name, "name") == 0)
+          plugin_instance_format[i] = plginst_name;
+        else if (strcasecmp(param_name, "uuid") == 0)
+          plugin_instance_format[i] = plginst_uuid;
+        else if (strcasecmp(param_name, "metadata") == 0)
+          plugin_instance_format[i] = plginst_metadata;
+        else {
+          ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s",
+                param_name);
+
+          return -1;
+        }
+      }
 
-    if (*eptr != '\0') {
-      ERROR(PLUGIN_NAME " plugin: Invalid value for Instances = '%s'", value);
-      return 1;
-    }
-    if (val <= 0) {
-      ERROR(PLUGIN_NAME " plugin: Instances <= 0 makes no sense.");
-      return 1;
-    }
-    if (val > NR_INSTANCES_MAX) {
-      ERROR(PLUGIN_NAME " plugin: Instances=%f > NR_INSTANCES_MAX=%i"
-                        " use a lower setting or recompile the plugin.",
-            val, NR_INSTANCES_MAX);
-      return 1;
-    }
+      for (int i = params_num; i < PLGINST_MAX_FIELDS; ++i)
+        plugin_instance_format[i] = plginst_none;
 
-    nr_instances = (int)val;
-    DEBUG(PLUGIN_NAME " plugin: configured %i instances", nr_instances);
-    return 0;
-  }
+      continue;
+    } else if (strcasecmp(c->key, "InterfaceFormat") == 0) {
+      char *format = NULL;
+      if (cf_util_get_string(c, &format) != 0)
+        return -1;
+
+      if (strcasecmp(format, "name") == 0)
+        interface_format = if_name;
+      else if (strcasecmp(format, "address") == 0)
+        interface_format = if_address;
+      else if (strcasecmp(format, "number") == 0)
+        interface_format = if_number;
+      else {
+        ERROR(PLUGIN_NAME " plugin: unknown InterfaceFormat: %s", format);
+        sfree(format);
+        return -1;
+      }
+
+      sfree(format);
+      continue;
+    } else if (strcasecmp(c->key, "Instances") == 0) {
+      if (cf_util_get_int(c, &nr_instances) != 0)
+        return -1;
+
+      if (nr_instances <= 0) {
+        ERROR(PLUGIN_NAME " plugin: Instances <= 0 makes no sense.");
+        return -1;
+      }
+      if (nr_instances > NR_INSTANCES_MAX) {
+        ERROR(PLUGIN_NAME " plugin: Instances=%i > NR_INSTANCES_MAX=%i"
+                          " use a lower setting or recompile the plugin.",
+              nr_instances, NR_INSTANCES_MAX);
+        return -1;
+      }
+
+      DEBUG(PLUGIN_NAME " plugin: configured %i instances", nr_instances);
+      continue;
+    } else if (strcasecmp(c->key, "ExtraStats") == 0) {
+      char *ex_str = NULL;
+
+      if (cf_util_get_string(c, &ex_str) != 0)
+        return -1;
 
-  if (strcasecmp(key, "ExtraStats") == 0) {
-    char *localvalue = strdup(value);
-    if (localvalue != NULL) {
       char *exstats[EX_STATS_MAX_FIELDS];
-      int numexstats =
-          strsplit(localvalue, exstats, STATIC_ARRAY_SIZE(exstats));
-      extra_stats = parse_ex_stats_flags(exstats, numexstats);
-      sfree(localvalue);
+      int numexstats = strsplit(ex_str, exstats, STATIC_ARRAY_SIZE(exstats));
+      int status = parse_ex_stats_flags(&extra_stats, exstats, numexstats);
+      sfree(ex_str);
+      if (status != 0) {
+        ERROR(PLUGIN_NAME " plugin: parsing 'ExtraStats' option failed");
+        return status;
+      }
 
 #ifdef HAVE_JOB_STATS
       if ((extra_stats & ex_stats_job_stats_completed) &&
@@ -1363,33 +1433,44 @@ static int lv_config(const char *key, const char *value) {
         ERROR(PLUGIN_NAME " plugin: Invalid job stats configuration. Only one "
                           "type of job statistics can be collected at the same "
                           "time");
-        return 1;
+        return -1;
       }
 #endif
-    }
-  }
 
-  if (strcasecmp(key, "PersistentNotification") == 0) {
-    persistent_notification = IS_TRUE(value);
-    return 0;
-  }
+      /* ExtraStats parsed successfully */
+      continue;
+    } else if (strcasecmp(c->key, "PersistentNotification") == 0) {
+      if (cf_util_get_boolean(c, &persistent_notification) != 0)
+        return -1;
 
-  if (strcasecmp(key, "ReportBlockDevices") == 0) {
-    report_block_devices = IS_TRUE(value);
-    return 0;
-  }
+      continue;
+    } else if (strcasecmp(c->key, "ReportBlockDevices") == 0) {
+      if (cf_util_get_boolean(c, &report_block_devices) != 0)
+        return -1;
 
-  if (strcasecmp(key, "ReportNetworkInterfaces") == 0) {
-    report_network_interfaces = IS_TRUE(value);
-    return 0;
+      continue;
+    } else if (strcasecmp(c->key, "ReportNetworkInterfaces") == 0) {
+      if (cf_util_get_boolean(c, &report_network_interfaces) != 0)
+        return -1;
+
+      continue;
+    } else {
+      /* Unrecognised option. */
+      ERROR(PLUGIN_NAME " plugin: Unrecognized option: '%s'", c->key);
+      return -1;
+    }
   }
 
-  /* Unrecognised option. */
-  return -1;
+  return 0;
 }
 
 static int lv_connect(void) {
   if (conn == NULL) {
+    /* event implementation must be registered before connection is opened */
+    if (!persistent_notification)
+      if (register_event_impl() != 0)
+        return -1;
+
 /* `conn_string == NULL' is acceptable */
 #ifdef HAVE_FS_INFO
     /* virDomainGetFSInfo requires full read-write access connection */
@@ -1407,8 +1488,17 @@ static int lv_connect(void) {
     int status = virNodeGetInfo(conn, &nodeinfo);
     if (status != 0) {
       ERROR(PLUGIN_NAME " plugin: virNodeGetInfo failed");
+      virConnectClose(conn);
+      conn = NULL;
       return -1;
     }
+
+    if (!persistent_notification)
+      if (start_event_loop(&notif_thread) != 0) {
+        virConnectClose(conn);
+        conn = NULL;
+        return -1;
+      }
   }
   c_release(LOG_NOTICE, &conn_complain,
             PLUGIN_NAME " plugin: Connection established.");
@@ -1422,8 +1512,8 @@ static void lv_disconnect(void) {
   WARNING(PLUGIN_NAME " plugin: closed connection to libvirt");
 }
 
-static int lv_domain_block_info(virDomainPtr dom, const char *path,
-                                struct lv_block_info *binfo) {
+static int lv_domain_block_stats(virDomainPtr dom, const char *path,
+                                 struct lv_block_stats *bstats) {
 #ifdef HAVE_BLOCK_STATS_FLAGS
   int nparams = 0;
   if (virDomainBlockStatsFlags(dom, path, NULL, &nparams, 0) < 0 ||
@@ -1434,8 +1524,8 @@ static int lv_domain_block_info(virDomainPtr dom, const char *path,
 
   virTypedParameterPtr params = calloc(nparams, sizeof(*params));
   if (params == NULL) {
-    ERROR("virt plugin: alloc(%i) for block=%s parameters failed.", nparams,
-          path);
+    ERROR(PLUGIN_NAME " plugin: alloc(%i) for block=%s parameters failed.",
+          nparams, path);
     return -1;
   }
 
@@ -1443,14 +1533,14 @@ static int lv_domain_block_info(virDomainPtr dom, const char *path,
   if (virDomainBlockStatsFlags(dom, path, params, &nparams, 0) < 0) {
     VIRT_ERROR(conn, "getting the disk params values");
   } else {
-    rc = get_block_info(binfo, params, nparams);
+    rc = get_block_stats(bstats, params, nparams);
   }
 
   virTypedParamsClear(params, nparams);
   sfree(params);
   return rc;
 #else
-  return virDomainBlockStats(dom, path, &(binfo->bi), sizeof(binfo->bi));
+  return virDomainBlockStats(dom, path, &(bstats->bi), sizeof(bstats->bi));
 #endif /* HAVE_BLOCK_STATS_FLAGS */
 }
 
@@ -1475,8 +1565,17 @@ static int get_perf_events(virDomainPtr domain) {
   int status =
       virDomainListGetStats(domain_array, VIR_DOMAIN_STATS_PERF, &stats, 0);
   if (status == -1) {
-    ERROR("virt plugin: virDomainListGetStats failed with status %i.", status);
-    return status;
+    ERROR(PLUGIN_NAME " plugin: virDomainListGetStats failed with status %i.",
+          status);
+
+    virErrorPtr err = virGetLastError();
+    if (err->code == VIR_ERR_NO_SUPPORT) {
+      ERROR(PLUGIN_NAME
+            " plugin: Disabled unsupported ExtraStats selector: perf");
+      extra_stats &= ~(ex_stats_perf);
+    }
+
+    return -1;
   }
 
   for (int i = 0; i < status; ++i)
@@ -1500,7 +1599,6 @@ static void vcpu_pin_submit(virDomainPtr dom, int max_cpus, int vcpu,
 
 static int get_vcpu_stats(virDomainPtr domain, unsigned short nr_virt_cpu) {
   int max_cpus = VIR_NODEINFO_MAXCPUS(nodeinfo);
-  int cpu_map_len = VIR_CPU_MAPLEN(max_cpus);
 
   virVcpuInfoPtr vinfo = calloc(nr_virt_cpu, sizeof(*vinfo));
   if (vinfo == NULL) {
@@ -1508,11 +1606,17 @@ static int get_vcpu_stats(virDomainPtr domain, unsigned short nr_virt_cpu) {
     return -1;
   }
 
-  unsigned char *cpumaps = calloc(nr_virt_cpu, cpu_map_len);
-  if (cpumaps == NULL) {
-    ERROR(PLUGIN_NAME " plugin: calloc failed.");
-    sfree(vinfo);
-    return -1;
+  int cpu_map_len = 0;
+  unsigned char *cpumaps = NULL;
+  if (extra_stats & ex_stats_vcpupin) {
+    cpu_map_len = VIR_CPU_MAPLEN(max_cpus);
+    cpumaps = calloc(nr_virt_cpu, cpu_map_len);
+
+    if (cpumaps == NULL) {
+      ERROR(PLUGIN_NAME " plugin: calloc failed.");
+      sfree(vinfo);
+      return -1;
+    }
   }
 
   int status =
@@ -1520,13 +1624,26 @@ static int get_vcpu_stats(virDomainPtr domain, unsigned short nr_virt_cpu) {
   if (status < 0) {
     ERROR(PLUGIN_NAME " plugin: virDomainGetVcpus failed with status %i.",
           status);
+
+    virErrorPtr err = virGetLastError();
+    if (err->code == VIR_ERR_NO_SUPPORT) {
+      if (extra_stats & ex_stats_vcpu)
+        ERROR(PLUGIN_NAME
+              " plugin: Disabled unsupported ExtraStats selector: vcpu");
+      if (extra_stats & ex_stats_vcpupin)
+        ERROR(PLUGIN_NAME
+              " plugin: Disabled unsupported ExtraStats selector: vcpupin");
+      extra_stats &= ~(ex_stats_vcpu | ex_stats_vcpupin);
+    }
+
     sfree(cpumaps);
     sfree(vinfo);
-    return status;
+    return -1;
   }
 
   for (int i = 0; i < nr_virt_cpu; ++i) {
-    vcpu_submit(vinfo[i].cpuTime, domain, vinfo[i].number, "virt_vcpu");
+    if (extra_stats & ex_stats_vcpu)
+      vcpu_submit(vinfo[i].cpuTime, domain, vinfo[i].number, "virt_vcpu");
     if (extra_stats & ex_stats_vcpupin)
       vcpu_pin_submit(domain, max_cpus, i, cpumaps, cpu_map_len);
   }
@@ -1541,6 +1658,14 @@ static int get_pcpu_stats(virDomainPtr dom) {
   int nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0);
   if (nparams < 0) {
     VIRT_ERROR(conn, "getting the CPU params count");
+
+    virErrorPtr err = virGetLastError();
+    if (err->code == VIR_ERR_NO_SUPPORT) {
+      ERROR(PLUGIN_NAME
+            " plugin: Disabled unsupported ExtraStats selector: pcpu");
+      extra_stats &= ~(ex_stats_pcpu);
+    }
+
     return -1;
   }
 
@@ -1580,16 +1705,7 @@ static int get_pcpu_stats(virDomainPtr dom) {
 #endif /* HAVE_CPU_STATS */
 
 #ifdef HAVE_DOM_REASON
-
-static void domain_state_submit(virDomainPtr dom, int state, int reason) {
-  value_t values[] = {
-      {.gauge = (gauge_t)state}, {.gauge = (gauge_t)reason},
-  };
-
-  submit(dom, "domain_state", NULL, values, STATIC_ARRAY_SIZE(values));
-}
-
-static int get_domain_state(virDomainPtr domain) {
+static int submit_domain_state(virDomainPtr domain) {
   int domain_state = 0;
   int domain_reason = 0;
 
@@ -1600,9 +1716,13 @@ static int get_domain_state(virDomainPtr domain) {
     return status;
   }
 
-  domain_state_submit(domain, domain_state, domain_reason);
+  value_t values[] = {
+      {.gauge = (gauge_t)domain_state}, {.gauge = (gauge_t)domain_reason},
+  };
+
+  submit(domain, "domain_state", NULL, values, STATIC_ARRAY_SIZE(values));
 
-  return status;
+  return 0;
 }
 
 #ifdef HAVE_LIST_ALL_DOMAINS
@@ -1617,8 +1737,7 @@ static int get_domain_state_notify(virDomainPtr domain) {
     return status;
   }
 
-  if (persistent_notification)
-    domain_state_submit_notif(domain, domain_state, domain_reason);
+  domain_state_submit_notif(domain, domain_state, domain_reason);
 
   return status;
 }
@@ -1629,21 +1748,63 @@ static int get_memory_stats(virDomainPtr domain) {
   virDomainMemoryStatPtr minfo =
       calloc(VIR_DOMAIN_MEMORY_STAT_NR, sizeof(*minfo));
   if (minfo == NULL) {
-    ERROR("virt plugin: calloc failed.");
+    ERROR(PLUGIN_NAME " plugin: calloc failed.");
     return -1;
   }
 
   int mem_stats =
       virDomainMemoryStats(domain, minfo, VIR_DOMAIN_MEMORY_STAT_NR, 0);
   if (mem_stats < 0) {
-    ERROR("virt plugin: virDomainMemoryStats failed with mem_stats %i.",
+    ERROR(PLUGIN_NAME " plugin: virDomainMemoryStats failed with mem_stats %i.",
           mem_stats);
     sfree(minfo);
-    return mem_stats;
+
+    virErrorPtr err = virGetLastError();
+    if (err->code == VIR_ERR_NO_SUPPORT) {
+      ERROR(PLUGIN_NAME
+            " plugin: Disabled unsupported ExtraStats selector: memory");
+      extra_stats &= ~(ex_stats_memory);
+    }
+
+    return -1;
+  }
+
+  derive_t swap_in = -1;
+  derive_t swap_out = -1;
+  derive_t min_flt = -1;
+  derive_t maj_flt = -1;
+
+  for (int i = 0; i < mem_stats; i++) {
+    if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_SWAP_IN)
+      swap_in = minfo[i].val;
+    else if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_SWAP_OUT)
+      swap_out = minfo[i].val;
+    else if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_MINOR_FAULT)
+      min_flt = minfo[i].val;
+    else if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_MAJOR_FAULT)
+      maj_flt = minfo[i].val;
+#ifdef LIBVIR_CHECK_VERSION
+#if LIBVIR_CHECK_VERSION(2, 1, 0)
+    else if (minfo[i].tag == VIR_DOMAIN_MEMORY_STAT_LAST_UPDATE)
+      /* Skip 'last_update' reporting as that is not memory but timestamp */
+      continue;
+#endif
+#endif
+    else
+      memory_stats_submit((gauge_t)minfo[i].val * 1024, domain, minfo[i].tag);
   }
 
-  for (int i = 0; i < mem_stats; i++)
-    memory_stats_submit((gauge_t)minfo[i].val * 1024, domain, minfo[i].tag);
+  if (swap_in > 0 || swap_out > 0) {
+    submit(domain, "swap_io", "in", &(value_t){.gauge = swap_in}, 1);
+    submit(domain, "swap_io", "out", &(value_t){.gauge = swap_out}, 1);
+  }
+
+  if (min_flt > 0 || maj_flt > 0) {
+    value_t values[] = {
+        {.gauge = (gauge_t)min_flt}, {.gauge = (gauge_t)maj_flt},
+    };
+    submit(domain, "ps_pagefaults", NULL, values, STATIC_ARRAY_SIZE(values));
+  }
 
   sfree(minfo);
   return 0;
@@ -1662,6 +1823,15 @@ static int get_disk_err(virDomainPtr domain) {
   if (disk_err_count == -1) {
     ERROR(PLUGIN_NAME
           " plugin: failed to get preferred size of disk errors array");
+
+    virErrorPtr err = virGetLastError();
+
+    if (err->code == VIR_ERR_NO_SUPPORT) {
+      ERROR(PLUGIN_NAME
+            " plugin: Disabled unsupported ExtraStats selector: disk_err");
+      extra_stats &= ~(ex_stats_disk_err);
+    }
+
     return -1;
   }
 
@@ -1689,22 +1859,57 @@ static int get_disk_err(virDomainPtr domain) {
 }
 #endif /* HAVE_DISK_ERR */
 
-static int get_block_stats(struct block_device *block_dev) {
-
+static int get_block_device_stats(struct block_device *block_dev) {
   if (!block_dev) {
     ERROR(PLUGIN_NAME " plugin: get_block_stats NULL pointer");
     return -1;
   }
 
-  struct lv_block_info binfo;
+  virDomainBlockInfo binfo;
   init_block_info(&binfo);
 
-  if (lv_domain_block_info(block_dev->dom, block_dev->path, &binfo) < 0) {
-    ERROR(PLUGIN_NAME " plugin: lv_domain_block_info failed");
+  /* Fetching block info stats only if needed*/
+  if (extra_stats & (ex_stats_disk_allocation | ex_stats_disk_capacity |
+                     ex_stats_disk_physical)) {
+    /* Block info statistics can be only fetched from devices with 'source'
+     * defined */
+    if (block_dev->has_source) {
+      if (virDomainGetBlockInfo(block_dev->dom, block_dev->path, &binfo, 0) <
+          0) {
+        ERROR(PLUGIN_NAME " plugin: virDomainGetBlockInfo failed for path: %s",
+              block_dev->path);
+
+        virErrorPtr err = virGetLastError();
+        if (err->code == VIR_ERR_NO_SUPPORT) {
+
+          if (extra_stats & ex_stats_disk_allocation)
+            ERROR(PLUGIN_NAME " plugin: Disabled unsupported ExtraStats "
+                              "selector: disk_allocation");
+          if (extra_stats & ex_stats_disk_capacity)
+            ERROR(PLUGIN_NAME " plugin: Disabled unsupported ExtraStats "
+                              "selector: disk_capacity");
+          if (extra_stats & ex_stats_disk_physical)
+            ERROR(PLUGIN_NAME " plugin: Disabled unsupported ExtraStats "
+                              "selector: disk_physical");
+
+          extra_stats &= ~(ex_stats_disk_allocation | ex_stats_disk_capacity |
+                           ex_stats_disk_physical);
+        }
+
+        return -1;
+      }
+    }
+  }
+
+  struct lv_block_stats bstats;
+  init_block_stats(&bstats);
+
+  if (lv_domain_block_stats(block_dev->dom, block_dev->path, &bstats) < 0) {
+    ERROR(PLUGIN_NAME " plugin: lv_domain_block_stats failed");
     return -1;
   }
 
-  disk_submit(&binfo, block_dev->dom, block_dev->path);
+  disk_block_stats_submit(&bstats, block_dev->dom, block_dev->path, &binfo);
   return 0;
 }
 
@@ -1777,7 +1982,15 @@ static int get_fs_info(virDomainPtr domain) {
   if (mount_points_cnt == -1) {
     ERROR(PLUGIN_NAME " plugin: virDomainGetFSInfo failed: %d",
           mount_points_cnt);
-    return mount_points_cnt;
+
+    virErrorPtr err = virGetLastError();
+    if (err->code == VIR_ERR_NO_SUPPORT) {
+      ERROR(PLUGIN_NAME
+            " plugin: Disabled unsupported ExtraStats selector: fs_info");
+      extra_stats &= ~(ex_stats_fs_info);
+    }
+
+    return -1;
   }
 
   for (int i = 0; i < mount_points_cnt; ++i) {
@@ -1824,7 +2037,6 @@ static void job_stats_submit(virDomainPtr domain, virTypedParameterPtr param) {
 }
 
 static int get_job_stats(virDomainPtr domain) {
-  int ret = 0;
   int job_type = 0;
   int nparams = 0;
   virTypedParameterPtr params = NULL;
@@ -1832,10 +2044,24 @@ static int get_job_stats(virDomainPtr domain) {
                   ? VIR_DOMAIN_JOB_STATS_COMPLETED
                   : 0;
 
-  ret = virDomainGetJobStats(domain, &job_type, &params, &nparams, flags);
+  int ret = virDomainGetJobStats(domain, &job_type, &params, &nparams, flags);
   if (ret != 0) {
     ERROR(PLUGIN_NAME " plugin: virDomainGetJobStats failed: %d", ret);
-    return ret;
+
+    virErrorPtr err = virGetLastError();
+    // VIR_ERR_INVALID_ARG returned when VIR_DOMAIN_JOB_STATS_COMPLETED flag is
+    // not supported by driver
+    if (err->code == VIR_ERR_NO_SUPPORT || err->code == VIR_ERR_INVALID_ARG) {
+      if (extra_stats & ex_stats_job_stats_completed)
+        ERROR(PLUGIN_NAME " plugin: Disabled unsupported ExtraStats selector: "
+                          "job_stats_completed");
+      if (extra_stats & ex_stats_job_stats_background)
+        ERROR(PLUGIN_NAME " plugin: Disabled unsupported ExtraStats selector: "
+                          "job_stats_background");
+      extra_stats &=
+          ~(ex_stats_job_stats_completed | ex_stats_job_stats_background);
+    }
+    return -1;
   }
 
   DEBUG(PLUGIN_NAME " plugin: job_type=%d nparams=%d", job_type, nparams);
@@ -1847,7 +2073,7 @@ static int get_job_stats(virDomainPtr domain) {
   }
 
   virTypedParamsFree(params, nparams);
-  return ret;
+  return 0;
 }
 #endif /* HAVE_JOB_STATS */
 
@@ -1871,7 +2097,7 @@ static int get_domain_metrics(domain_t *domain) {
      * however it doesn't provide a reason for entering particular state.
      * We need to get it from virDomainGetState.
      */
-    GET_STATS(get_domain_state, "domain reason", domain->ptr);
+    GET_STATS(submit_domain_state, "domain reason", domain->ptr);
 #endif
   }
 
@@ -1888,8 +2114,10 @@ static int get_domain_metrics(domain_t *domain) {
 
   memory_submit(domain->ptr, (gauge_t)info.memory * 1024);
 
-  GET_STATS(get_vcpu_stats, "vcpu stats", domain->ptr, info.nrVirtCpu);
-  GET_STATS(get_memory_stats, "memory stats", domain->ptr);
+  if (extra_stats & (ex_stats_vcpu | ex_stats_vcpupin))
+    GET_STATS(get_vcpu_stats, "vcpu stats", domain->ptr, info.nrVirtCpu);
+  if (extra_stats & ex_stats_memory)
+    GET_STATS(get_memory_stats, "memory stats", domain->ptr);
 
 #ifdef HAVE_PERF_STATS
   if (extra_stats & ex_stats_perf)
@@ -1976,6 +2204,9 @@ static int domain_lifecycle_event_cb(__attribute__((unused)) virConnectPtr con_,
   return 0;
 }
 
+static void virt_eventloop_timeout_cb(int timer ATTRIBUTE_UNUSED,
+                                      void *timer_info) {}
+
 static int register_event_impl(void) {
   if (virEventRegisterDefaultImpl() < 0) {
     virErrorPtr err = virGetLastError();
@@ -1985,6 +2216,14 @@ static int register_event_impl(void) {
     return -1;
   }
 
+  if (virEventAddTimeout(CDTIME_T_TO_MS(plugin_get_interval()),
+                         virt_eventloop_timeout_cb, NULL, NULL) < 0) {
+    virErrorPtr err = virGetLastError();
+    ERROR(PLUGIN_NAME " plugin: virEventAddTimeout failed: %s",
+          err && err->message ? err->message : "Unknown error");
+    return -1;
+  }
+
   return 0;
 }
 
@@ -2023,10 +2262,9 @@ static void *event_loop_worker(void *arg) {
 }
 
 static int virt_notif_thread_init(virt_notif_thread_t *thread_data) {
-  int ret;
-
   assert(thread_data != NULL);
-  ret = pthread_mutex_init(&thread_data->active_mutex, NULL);
+
+  int ret = pthread_mutex_init(&thread_data->active_mutex, NULL);
   if (ret != 0) {
     ERROR(PLUGIN_NAME " plugin: Failed to initialize mutex, err %u", ret);
     return ret;
@@ -2055,11 +2293,15 @@ static int start_event_loop(virt_notif_thread_t *thread_data) {
     return -1;
   }
 
+  DEBUG(PLUGIN_NAME " plugin: starting event loop");
+
   virt_notif_thread_set_active(thread_data, 1);
   if (pthread_create(&thread_data->event_loop_tid, NULL, event_loop_worker,
                      thread_data)) {
     ERROR(PLUGIN_NAME " plugin: failed event loop thread creation");
+    virt_notif_thread_set_active(thread_data, 0);
     virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
+    thread_data->domain_event_cb_id = -1;
     return -1;
   }
 
@@ -2068,13 +2310,21 @@ static int start_event_loop(virt_notif_thread_t *thread_data) {
 
 /* stop event loop thread and deregister callback */
 static void stop_event_loop(virt_notif_thread_t *thread_data) {
-  /* stopping loop and de-registering event handler*/
-  virt_notif_thread_set_active(thread_data, 0);
-  if (conn != NULL && thread_data->domain_event_cb_id != -1)
-    virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
 
-  if (pthread_join(notif_thread.event_loop_tid, NULL) != 0)
-    ERROR(PLUGIN_NAME " plugin: stopping notification thread failed");
+  DEBUG(PLUGIN_NAME " plugin: stopping event loop");
+
+  /* Stopping loop */
+  if (virt_notif_thread_is_active(thread_data)) {
+    virt_notif_thread_set_active(thread_data, 0);
+    if (pthread_join(notif_thread.event_loop_tid, NULL) != 0)
+      ERROR(PLUGIN_NAME " plugin: stopping notification thread failed");
+  }
+
+  /* ... and de-registering event handler */
+  if (conn != NULL && thread_data->domain_event_cb_id != -1) {
+    virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
+    thread_data->domain_event_cb_id = -1;
+  }
 }
 
 static int persistent_domains_state_notification(void) {
@@ -2150,33 +2400,41 @@ static int persistent_domains_state_notification(void) {
 }
 
 static int lv_read(user_data_t *ud) {
-  time_t t;
-  struct lv_read_instance *inst = NULL;
-  struct lv_read_state *state = NULL;
-
   if (ud->data == NULL) {
     ERROR(PLUGIN_NAME " plugin: NULL userdata");
     return -1;
   }
 
-  inst = ud->data;
-  state = &inst->read_state;
-
-  bool reconnect = conn == NULL ? true : false;
-  /* event implementation must be registered before connection is opened */
-  if (inst->id == 0) {
-    if (!persistent_notification && reconnect)
-      if (register_event_impl() != 0)
-        return -1;
+  struct lv_read_instance *inst = ud->data;
+  struct lv_read_state *state = &inst->read_state;
 
+  if (inst->id == 0)
     if (lv_connect() < 0)
       return -1;
 
-    if (!persistent_notification && reconnect && conn != NULL)
-      if (start_event_loop(&notif_thread) != 0)
-        return -1;
+  /* Wait until inst#0 establish connection */
+  if (conn == NULL) {
+    DEBUG(PLUGIN_NAME " plugin#%s: Wait until inst#0 establish connection",
+          inst->tag);
+    return 0;
+  }
+
+  int ret = virConnectIsAlive(conn);
+  if (ret == 0) { /* Connection lost */
+    if (inst->id == 0) {
+      c_complain(LOG_ERR, &conn_complain,
+                 PLUGIN_NAME " plugin: Lost connection.");
+
+      if (!persistent_notification)
+        stop_event_loop(&notif_thread);
+
+      lv_disconnect();
+      last_refresh = 0;
+    }
+    return -1;
   }
 
+  time_t t;
   time(&t);
 
   /* Need to refresh domain or device lists? */
@@ -2223,8 +2481,8 @@ static int lv_read(user_data_t *ud) {
     if (dom->active)
       status = get_domain_metrics(dom);
 #ifdef HAVE_DOM_REASON
-    else
-      status = get_domain_state(dom->ptr);
+    else if (extra_stats & ex_stats_domain_state)
+      status = submit_domain_state(dom->ptr);
 #endif
 
     if (status != 0)
@@ -2234,7 +2492,7 @@ static int lv_read(user_data_t *ud) {
 
   /* Get block device stats for each domain. */
   for (int i = 0; i < state->nr_block_devices; ++i) {
-    int status = get_block_stats(&state->block_devices[i]);
+    int status = get_block_device_stats(&state->block_devices[i]);
     if (status != 0)
       ERROR(PLUGIN_NAME
             " plugin: failed to get stats for block device (%s) in domain %s",
@@ -2297,21 +2555,11 @@ static int lv_init(void) {
   if (lv_init_ignorelists() != 0)
     return -1;
 
-  /* event implementation must be registered before connection is opened */
   if (!persistent_notification)
-    if (register_event_impl() != 0)
+    if (virt_notif_thread_init(&notif_thread) != 0)
       return -1;
 
-  if (lv_connect() != 0)
-    return -1;
-
-  DEBUG(PLUGIN_NAME " plugin: starting event loop");
-
-  if (!persistent_notification) {
-    virt_notif_thread_init(&notif_thread);
-    if (start_event_loop(&notif_thread) != 0)
-      return -1;
-  }
+  lv_connect();
 
   DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
 
@@ -2419,35 +2667,76 @@ static int lv_instance_include_domain(struct lv_read_instance *inst,
 static void lv_add_block_devices(struct lv_read_state *state, virDomainPtr dom,
                                  const char *domname,
                                  xmlXPathContextPtr xpath_ctx) {
-  const char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
-  if (blockdevice_format == source)
-    bd_xmlpath = "/domain/devices/disk/source[@dev]";
-
   xmlXPathObjectPtr xpath_obj =
-      xmlXPathEval((const xmlChar *)bd_xmlpath, xpath_ctx);
+      xmlXPathEval((const xmlChar *)"/domain/devices/disk", xpath_ctx);
 
-  if (xpath_obj == NULL)
+  if (xpath_obj == NULL) {
+    DEBUG(PLUGIN_NAME " plugin: no disk xpath-object found for domain %s",
+          domname);
     return;
+  }
 
   if (xpath_obj->type != XPATH_NODESET || xpath_obj->nodesetval == NULL) {
-    xmlXPathFreeObject(xpath_obj);
-    return;
+    DEBUG(PLUGIN_NAME " plugin: no disk node found for domain %s", domname);
+    goto cleanup;
   }
 
-  for (int j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
-    xmlNodePtr node = xpath_obj->nodesetval->nodeTab[j];
-    if (!node)
-      continue;
+  xmlNodeSetPtr xml_block_devices = xpath_obj->nodesetval;
+  for (int i = 0; i < xml_block_devices->nodeNr; ++i) {
+    xmlNodePtr xml_device = xpath_obj->nodesetval->nodeTab[i];
+    char *path_str = NULL;
+    char *source_str = NULL;
 
-    char *path = (char *)xmlGetProp(node, (xmlChar *)"dev");
-    if (!path)
+    if (!xml_device)
       continue;
 
-    if (ignore_device_match(il_block_devices, domname, path) == 0)
-      add_block_device(state, dom, path);
+    /* Fetching path and source for block device */
+    for (xmlNodePtr child = xml_device->children; child; child = child->next) {
+      if (child->type != XML_ELEMENT_NODE)
+        continue;
+
+      /* we are interested only in either "target" or "source" elements */
+      if (xmlStrEqual(child->name, (const xmlChar *)"target"))
+        path_str = (char *)xmlGetProp(child, (const xmlChar *)"dev");
+      else if (xmlStrEqual(child->name, (const xmlChar *)"source")) {
+        /* name of the source is located in "dev" or "file" element (it depends
+         * on type of source). Trying "dev" at first*/
+        source_str = (char *)xmlGetProp(child, (const xmlChar *)"dev");
+        if (!source_str)
+          source_str = (char *)xmlGetProp(child, (const xmlChar *)"file");
+      }
+      /* ignoring any other element*/
+    }
+
+    /* source_str will be interpreted as a device path if blockdevice_format
+     *  param is set to 'source'. */
+    const char *device_path =
+        (blockdevice_format == source) ? source_str : path_str;
+
+    if (!device_path) {
+      /* no path found and we can't add block_device without it */
+      WARNING(PLUGIN_NAME " plugin: could not generate device path for disk in "
+                          "domain %s - disk device will be ignored in reports",
+              domname);
+      goto cont;
+    }
+
+    if (ignore_device_match(il_block_devices, domname, device_path) == 0) {
+      /* we only have to store information whether 'source' exists or not */
+      bool has_source = (source_str != NULL) ? true : false;
+
+      add_block_device(state, dom, device_path, has_source);
+    }
+
+  cont:
+    if (path_str)
+      xmlFree(path_str);
 
-    xmlFree(path);
+    if (source_str)
+      xmlFree(source_str);
   }
+
+cleanup:
   xmlXPathFreeObject(xpath_obj);
 }
 
@@ -2470,6 +2759,7 @@ static void lv_add_network_interfaces(struct lv_read_state *state,
   for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
     char *path = NULL;
     char *address = NULL;
+    const int itf_number = j + 1;
 
     xmlNodePtr xml_interface = xml_interfaces->nodeTab[j];
     if (!xml_interface)
@@ -2491,11 +2781,31 @@ static void lv_add_network_interfaces(struct lv_read_state *state,
       }
     }
 
-    if ((ignore_device_match(il_interface_devices, domname, path) == 0 &&
-         ignore_device_match(il_interface_devices, domname, address) == 0)) {
-      add_interface_device(state, dom, path, address, j + 1);
+    bool device_ignored = false;
+    switch (interface_format) {
+    case if_name:
+      if (ignore_device_match(il_interface_devices, domname, path) != 0)
+        device_ignored = true;
+      break;
+    case if_address:
+      if (ignore_device_match(il_interface_devices, domname, address) != 0)
+        device_ignored = true;
+      break;
+    case if_number: {
+      char number_string[4];
+      snprintf(number_string, sizeof(number_string), "%d", itf_number);
+      if (ignore_device_match(il_interface_devices, domname, number_string) !=
+          0)
+        device_ignored = true;
+    } break;
+    default:
+      ERROR(PLUGIN_NAME " plugin: Unknown interface_format option: %d",
+            interface_format);
     }
 
+    if (!device_ignored)
+      add_interface_device(state, dom, path, address, itf_number);
+
     if (path)
       xmlFree(path);
     if (address)
@@ -2504,6 +2814,24 @@ static void lv_add_network_interfaces(struct lv_read_state *state,
   xmlXPathFreeObject(xpath_obj);
 }
 
+static bool is_domain_ignored(virDomainPtr dom) {
+  const char *domname = virDomainGetName(dom);
+
+  if (domname == NULL) {
+    VIRT_ERROR(conn, "virDomainGetName failed, ignoring domain");
+    return true;
+  }
+
+  if (ignorelist_match(il_domains, domname) != 0) {
+    DEBUG(PLUGIN_NAME
+          " plugin: ignoring domain '%s' because of ignorelist option",
+          domname);
+    return true;
+  }
+
+  return false;
+}
+
 static int refresh_lists(struct lv_read_instance *inst) {
   struct lv_read_state *state = &inst->read_state;
   int n;
@@ -2553,8 +2881,9 @@ static int refresh_lists(struct lv_read_instance *inst) {
 
 #ifdef HAVE_LIST_ALL_DOMAINS
   for (int i = 0; i < m; ++i)
-    if (add_domain(state, domains_inactive[i], 0) < 0) {
-      ERROR(PLUGIN_NAME " plugin: malloc failed.");
+    if (is_domain_ignored(domains_inactive[i]) ||
+        add_domain(state, domains_inactive[i], 0) < 0) {
+      /* domain ignored or failed during adding to domains list*/
       virDomainFree(domains_inactive[i]);
       domains_inactive[i] = NULL;
       continue;
@@ -2575,8 +2904,10 @@ static int refresh_lists(struct lv_read_instance *inst) {
     }
 #endif
 
-    if (add_domain(state, dom, 1) < 0) {
+    if (is_domain_ignored(dom) || add_domain(state, dom, 1) < 0) {
       /*
+       * domain ignored or failed during adding to domains list
+       *
        * When domain is already tracked, then there is
        * no problem with memory handling (will be freed
        * with the rest of domains cached data)
@@ -2584,7 +2915,6 @@ static int refresh_lists(struct lv_read_instance *inst) {
        * before adding domain to track) we have to take
        * care it ourselves and call virDomainFree
        */
-      ERROR(PLUGIN_NAME " plugin: malloc failed.");
       virDomainFree(dom);
       continue;
     }
@@ -2608,9 +2938,6 @@ static int refresh_lists(struct lv_read_instance *inst) {
       continue;
     }
 
-    if (ignorelist_match(il_domains, domname) != 0)
-      continue;
-
     /* Get a list of devices for this domain. */
     xmlDocPtr xml_doc = NULL;
     xmlXPathContextPtr xpath_ctx = NULL;
@@ -2687,12 +3014,13 @@ static void free_domains(struct lv_read_state *state) {
 
 static int add_domain(struct lv_read_state *state, virDomainPtr dom,
                       bool active) {
-
   int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
 
   domain_t *new_ptr = realloc(state->domains, new_size);
-  if (new_ptr == NULL)
+  if (new_ptr == NULL) {
+    ERROR(PLUGIN_NAME " plugin: realloc failed in add_domain()");
     return -1;
+  }
 
   state->domains = new_ptr;
   state->domains[state->nr_domains].ptr = dom;
@@ -2714,7 +3042,7 @@ static void free_block_devices(struct lv_read_state *state) {
 }
 
 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
-                            const char *path) {
+                            const char *path, bool has_source) {
 
   char *path_copy = strdup(path);
   if (!path_copy)
@@ -2731,6 +3059,7 @@ static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
   state->block_devices = new_ptr;
   state->block_devices[state->nr_block_devices].dom = dom;
   state->block_devices[state->nr_block_devices].path = path_copy;
+  state->block_devices[state->nr_block_devices].has_source = has_source;
   return state->nr_block_devices++;
 }
 
@@ -2815,8 +3144,6 @@ static int lv_shutdown(void) {
     lv_fini_instance(i);
   }
 
-  DEBUG(PLUGIN_NAME " plugin: stopping event loop");
-
   if (!persistent_notification)
     stop_event_loop(&notif_thread);
 
@@ -2833,7 +3160,7 @@ static int lv_shutdown(void) {
 }
 
 void module_register(void) {
-  plugin_register_config(PLUGIN_NAME, lv_config, config_keys, NR_CONFIG_KEYS);
+  plugin_register_complex_config("virt", lv_config);
   plugin_register_init(PLUGIN_NAME, lv_init);
   plugin_register_shutdown(PLUGIN_NAME, lv_shutdown);
 }
index 000b62e..cb6793d 100644 (file)
@@ -29,7 +29,7 @@
  * Based on the write_http plugin.
  **/
 
-/* write_graphite plugin configuation example
+/* write_graphite plugin configuration example
  *
  * <Plugin write_graphite>
  *   <Carbon>
@@ -39,6 +39,7 @@
  *     LogSendErrors true
  *     Prefix "collectd"
  *     UseTags true
+ *     ReverseHost false
  *   </Carbon>
  * </Plugin>
  */
@@ -521,6 +522,8 @@ static int wg_config_node(oconfig_item_t *ci) {
       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("ReverseHost", child->key) == 0)
+      cf_util_get_flag(child, &cb->format_flags, GRAPHITE_REVERSE_HOST);
     else if (strcasecmp("EscapeCharacter", child->key) == 0)
       config_set_char(&cb->escape_char, child);
     else {
index b109d42..96e71ca 100644 (file)
@@ -722,7 +722,7 @@ metric_family_get(data_set_t const *ds, value_list_t const *vl, size_t ds_index,
 
   int status = c_avl_insert(metrics, fam->name, fam);
   if (status != 0) {
-    ERROR("write_prometheus plugin: Adding \"%s\" failed.", name);
+    ERROR("write_prometheus plugin: Adding \"%s\" failed.", fam->name);
     metric_family_destroy(fam);
     return NULL;
   }
index 62ddc67..8056993 100644 (file)
@@ -392,6 +392,23 @@ wrr_value_to_event(struct riemann_host const *host, /* {{{ */
                       RIEMANN_EVENT_FIELD_NONE);
   }
 
+  if (vl->meta) {
+    char **toc;
+    int n = meta_data_toc(vl->meta, &toc);
+
+    for (int i = 0; i < n; i++) {
+      char *key = toc[i];
+      char *value;
+
+      if (0 == meta_data_as_string(vl->meta, key, &value)) {
+        riemann_event_string_attribute_add(event, key, value);
+        free(value);
+      }
+    }
+
+    free(toc);
+  }
+
   DEBUG("write_riemann plugin: Successfully created message for metric: "
         "host = \"%s\", service = \"%s\"",
         event->host, event->service);
index 1bff27b..7d08fb5 100644 (file)
@@ -1221,10 +1221,10 @@ static int sensu_config(oconfig_item_t *ci) /* {{{ */
         continue;
 
       status = add_str_to_list(&sensu_tags_arr, tmp);
+      DEBUG("write_sensu plugin: Got tag: %s", tmp);
       sfree(tmp);
       if (status != 0)
         continue;
-      DEBUG("write_sensu plugin: Got tag: %s", tmp);
     } else {
       WARNING("write_sensu plugin: Ignoring unknown "
               "configuration option \"%s\" at top level.",
diff --git a/src/write_syslog.c b/src/write_syslog.c
new file mode 100644 (file)
index 0000000..92c5dde
--- /dev/null
@@ -0,0 +1,639 @@
+/**
+ * collectd - src/write_syslog.c
+ * Copyright (C) 2012       Pierre-Yves Ritschard
+ * Copyright (C) 2011       Scott Sanders
+ * Copyright (C) 2009       Paul Sadauskas
+ * Copyright (C) 2009       Doug MacEachern
+ * Copyright (C) 2007-2012  Florian octo Forster
+ * Copyright (C) 2013-2014  Limelight Networks, Inc.
+ * Copyright (C) 2019       Shirly Radco
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Based on the write_graphite plugin. Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Doug MacEachern <dougm at hyperic.com>
+ *   Paul Sadauskas <psadauskas at gmail.com>
+ *   Scott Sanders <scott at jssjr.com>
+ *   Pierre-Yves Ritschard <pyr at spootnik.org>
+ * Based on the write_tsdb plugin. Authors:
+ *   Brett Hawn <bhawn at llnw.com>
+ *   Kevin Bowling <kbowling@llnw.com>
+ * write_syslog. Authors:
+ *   Shirly Radco <sradco@redhat.com>
+ **/
+
+/* write_syslog plugin configuration example
+ *
+ * <Plugin write_syslog>
+ *   <Node>
+ *     Host "localhost"
+ *     Port "44514"
+ *     Prefix "collectd"
+ *     MessageFormat "human"
+ *     HostTags "["prefix1" "example1"="example1_v"]
+ *   </Node>
+ * </Plugin>
+ *
+ */
+
+#include "collectd.h"
+#include "utils/common/common.h"
+
+#include "plugin.h"
+#include "utils_cache.h"
+#include "utils_random.h"
+
+#include <netdb.h>
+
+#define WS_DEFAULT_NODE "localhost"
+
+#define WS_DEFAULT_SERVICE "44514"
+
+#define WS_DEFAULT_FORMAT "human"
+
+#define WS_DEFAULT_PREFIX "collectd"
+
+#define WS_DEFAULT_ESCAPE '.'
+
+/* Ethernet - (IPv6 + TCP) = 1500 - (40 + 32) = 1428 */
+#define WS_SEND_BUF_SIZE 1428
+
+/*
+ * Private variables
+ */
+struct ws_callback {
+  struct addrinfo *ai;
+  cdtime_t ai_last_update;
+  int sock_fd;
+
+  char *node;
+  char *service;
+  char *host_tags;
+  char *msg_format;
+  char *metrics_prefix;
+  bool store_rates;
+  bool always_append_ds;
+
+  char send_buf[WS_SEND_BUF_SIZE];
+  size_t send_buf_free;
+  size_t send_buf_fill;
+  cdtime_t send_buf_init_time;
+
+  pthread_mutex_t send_lock;
+
+  bool connect_failed_log_enabled;
+  int connect_dns_failed_attempts_remaining;
+  cdtime_t next_random_ttl;
+};
+
+static cdtime_t resolve_interval;
+static cdtime_t resolve_jitter;
+
+/*
+ * Functions
+ */
+static void ws_reset_buffer(struct ws_callback *cb) {
+  memset(cb->send_buf, 0, sizeof(cb->send_buf));
+  cb->send_buf_free = sizeof(cb->send_buf);
+  cb->send_buf_fill = 0;
+  cb->send_buf_init_time = cdtime();
+}
+
+static int ws_send_buffer(struct ws_callback *cb) {
+  ssize_t status = 0;
+
+  status = swrite(cb->sock_fd, cb->send_buf, strlen(cb->send_buf));
+  if (status != 0) {
+    ERROR("write_syslog plugin: send failed with status %zi (%s)", status,
+          STRERRNO);
+
+    if (cb->sock_fd > 0) {
+      close(cb->sock_fd);
+      cb->sock_fd = -1;
+    }
+
+    return -1;
+  }
+
+  return 0;
+}
+
+/* NOTE: You must hold cb->send_lock when calling this function! */
+static int ws_flush_nolock(cdtime_t timeout, struct ws_callback *cb) {
+  int status;
+
+  DEBUG("write_syslog plugin: ws_flush_nolock: timeout = %.3f; "
+        "send_buf_fill = %" PRIsz ";",
+        (double)timeout, cb->send_buf_fill);
+
+  /* timeout == 0  => flush unconditionally */
+  if (timeout > 0) {
+    cdtime_t now;
+
+    now = cdtime();
+    if ((cb->send_buf_init_time + timeout) > now)
+      return 0;
+  }
+
+  if (cb->send_buf_fill == 0) {
+    cb->send_buf_init_time = cdtime();
+    return 0;
+  }
+
+  status = ws_send_buffer(cb);
+  ws_reset_buffer(cb);
+
+  return status;
+}
+
+static cdtime_t new_random_ttl(void) {
+  if (resolve_jitter == 0)
+    return 0;
+
+  return (cdtime_t)cdrand_range(0, (long)resolve_jitter);
+}
+
+static int ws_callback_init(struct ws_callback *cb) {
+  int status;
+  cdtime_t now;
+
+  const char *node = cb->node ? cb->node : WS_DEFAULT_NODE;
+  const char *service = cb->service ? cb->service : WS_DEFAULT_SERVICE;
+
+  if (cb->sock_fd > 0)
+    return 0;
+
+  now = cdtime();
+  if (cb->ai) {
+    /* When we are here, we still have the IP in cache.
+     * If we have remaining attempts without calling the DNS, we update the
+     * last_update date so we keep the info until next time.
+     * If there is no more attempts, we need to flush the cache.
+     */
+
+    if ((cb->ai_last_update + resolve_interval + cb->next_random_ttl) < now) {
+      cb->next_random_ttl = new_random_ttl();
+      if (cb->connect_dns_failed_attempts_remaining > 0) {
+        /* Warning : this is run under send_lock mutex.
+         * This is why we do not use another mutex here.
+         * */
+        cb->ai_last_update = now;
+        cb->connect_dns_failed_attempts_remaining--;
+      } else {
+        freeaddrinfo(cb->ai);
+        cb->ai = NULL;
+      }
+    }
+  }
+
+  if (cb->ai == NULL) {
+    if ((cb->ai_last_update + resolve_interval + cb->next_random_ttl) >= now) {
+      DEBUG("write_syslog plugin: too many getaddrinfo(%s, %s) failures", node,
+            service);
+      return -1;
+    }
+    cb->ai_last_update = now;
+    cb->next_random_ttl = new_random_ttl();
+
+    struct addrinfo ai_hints = {
+        .ai_family = AF_UNSPEC,
+        .ai_flags = AI_ADDRCONFIG,
+        .ai_socktype = SOCK_STREAM,
+    };
+
+    status = getaddrinfo(node, service, &ai_hints, &cb->ai);
+    if (status != 0) {
+      if (cb->ai) {
+        freeaddrinfo(cb->ai);
+        cb->ai = NULL;
+      }
+      if (cb->connect_failed_log_enabled) {
+        ERROR("write_syslog plugin: getaddrinfo(%s, %s) failed: %s", node,
+              service, gai_strerror(status));
+        cb->connect_failed_log_enabled = 0;
+      }
+      return -1;
+    }
+  }
+
+  assert(cb->ai != NULL);
+  for (struct addrinfo *ai = cb->ai; ai != NULL; ai = ai->ai_next) {
+    cb->sock_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+    if (cb->sock_fd < 0)
+      continue;
+
+    set_sock_opts(cb->sock_fd);
+
+    status = connect(cb->sock_fd, ai->ai_addr, ai->ai_addrlen);
+    if (status != 0) {
+      close(cb->sock_fd);
+      cb->sock_fd = -1;
+      continue;
+    }
+
+    break;
+  }
+
+  if (cb->sock_fd < 0) {
+    ERROR("write_syslog plugin: Connecting to %s:%s failed. "
+          "The last error was: %s",
+          node, service, STRERRNO);
+    return -1;
+  }
+
+  if (cb->connect_failed_log_enabled == 0) {
+    INFO("write_syslog plugin: Connecting to %s:%s succeeded.", node, service);
+    cb->connect_failed_log_enabled = 1;
+  }
+  cb->connect_dns_failed_attempts_remaining = 1;
+
+  ws_reset_buffer(cb);
+
+  return 0;
+}
+
+static void ws_callback_free(void *data) {
+  struct ws_callback *cb;
+
+  if (data == NULL)
+    return;
+
+  cb = data;
+
+  pthread_mutex_lock(&cb->send_lock);
+
+  ws_flush_nolock(0, cb);
+
+  close(cb->sock_fd);
+  cb->sock_fd = -1;
+
+  sfree(cb->node);
+  sfree(cb->service);
+  sfree(cb->host_tags);
+  sfree(cb->msg_format);
+  sfree(cb->metrics_prefix);
+
+  pthread_mutex_unlock(&cb->send_lock);
+  pthread_mutex_destroy(&cb->send_lock);
+
+  sfree(cb);
+}
+
+static int ws_flush(cdtime_t timeout,
+                    const char *identifier __attribute__((unused)),
+                    user_data_t *user_data) {
+  struct ws_callback *cb;
+  int status;
+
+  if (user_data == NULL)
+    return -EINVAL;
+
+  cb = user_data->data;
+
+  pthread_mutex_lock(&cb->send_lock);
+
+  if (cb->sock_fd < 0) {
+    status = ws_callback_init(cb);
+    if (status != 0) {
+      ERROR("write_syslog plugin: ws_callback_init failed.");
+      pthread_mutex_unlock(&cb->send_lock);
+      return -1;
+    }
+  }
+
+  status = ws_flush_nolock(timeout, cb);
+  pthread_mutex_unlock(&cb->send_lock);
+
+  return status;
+}
+
+static int ws_format_values(char *ret, size_t ret_len, int ds_num,
+                            const data_set_t *ds, const value_list_t *vl,
+                            bool store_rates) {
+  size_t offset = 0;
+  int status;
+  gauge_t *rates = NULL;
+
+  assert(strcmp(ds->type, vl->type) == 0);
+
+  memset(ret, 0, ret_len);
+
+#define BUFFER_ADD(...)                                                        \
+  do {                                                                         \
+    status = snprintf(ret + offset, ret_len - offset, __VA_ARGS__);            \
+    if (status < 1) {                                                          \
+      sfree(rates);                                                            \
+      return -1;                                                               \
+    } else if (((size_t)status) >= (ret_len - offset)) {                       \
+      sfree(rates);                                                            \
+      return -1;                                                               \
+    } else                                                                     \
+      offset += ((size_t)status);                                              \
+  } while (0)
+
+  if (ds->ds[ds_num].type == DS_TYPE_GAUGE)
+    BUFFER_ADD(GAUGE_FORMAT, vl->values[ds_num].gauge);
+  else if (store_rates) {
+    if (rates == NULL)
+      rates = uc_get_rate(ds, vl);
+    if (rates == NULL) {
+      WARNING("format_values: "
+              "uc_get_rate failed.");
+      return -1;
+    }
+    BUFFER_ADD(GAUGE_FORMAT, rates[ds_num]);
+  } else if (ds->ds[ds_num].type == DS_TYPE_COUNTER)
+    BUFFER_ADD("%" PRIu64, (uint64_t)vl->values[ds_num].counter);
+  else if (ds->ds[ds_num].type == DS_TYPE_DERIVE)
+    BUFFER_ADD("%" PRIi64, vl->values[ds_num].derive);
+  else if (ds->ds[ds_num].type == DS_TYPE_ABSOLUTE)
+    BUFFER_ADD("%" PRIu64, vl->values[ds_num].absolute);
+  else {
+    ERROR("format_values plugin: Unknown data source type: %i",
+          ds->ds[ds_num].type);
+    sfree(rates);
+    return -1;
+  }
+
+#undef BUFFER_ADD
+
+  sfree(rates);
+  return 0;
+}
+
+static int ws_format_name(char *ret, int ret_len, const value_list_t *vl,
+                          const struct ws_callback *cb, const char *ds_name) {
+
+  if (ds_name != NULL) {
+    snprintf(ret, ret_len, "%s.%s", vl->type, ds_name);
+  } else { /* ds_name == NULL */
+    snprintf(ret, ret_len, "%s", vl->type);
+  }
+
+  return 0;
+}
+
+static int ws_send_message(const char *key, const char *value, cdtime_t time,
+                           struct ws_callback *cb, const char *plugin,
+                           const char *plugin_instance,
+                           const char *type_instance, const char *type,
+                           const char *ds_name, cdtime_t interval,
+                           const char *host) {
+  int status;
+  size_t message_len;
+  char message[1024];
+  char rfc3339_timestamp[64];
+  const char *host_tags = cb->host_tags ? cb->host_tags : "";
+  const char *host_tags_json_prefix = "";
+  const char *metrics_prefix =
+      cb->metrics_prefix ? cb->metrics_prefix : WS_DEFAULT_PREFIX;
+  const char *msg_format = cb->msg_format ? cb->msg_format : WS_DEFAULT_FORMAT;
+  int pid;
+
+  pid = getpid();
+
+  rfc3339_local(rfc3339_timestamp, sizeof(rfc3339_timestamp), time);
+
+  /* skip if value is NaN */
+  if (value[0] == 'n')
+    return 0;
+
+  if (strcasecmp("JSON", msg_format) == 0) {
+    if (cb->host_tags) {
+      host_tags_json_prefix = ",";
+    }
+    status = snprintf(
+        /* The metric key-values are are part of the syslog msg, in json
+           format */
+        message, sizeof(message),
+        "<166>1 %s %s collectd %d - - {\"time\":%.0f, \"%s\":{ \"%s\":{ "
+        "\"%s\":%s }, "
+        "\"plugin\":\"%s\", \"plugin_instance\":\"%s\", "
+        "\"type_instance\":\"%s\","
+        " \"type\":\"%s\", \"interval\":%.0f }, \"hostname\":\"%s\" %s "
+        "%s}\n",
+        rfc3339_timestamp, host, pid, CDTIME_T_TO_DOUBLE(time), metrics_prefix,
+        plugin, key, value, plugin, plugin_instance, type_instance, type,
+        CDTIME_T_TO_DOUBLE(interval), host, host_tags_json_prefix, host_tags);
+  } else {
+    status = snprintf(
+        /* The metric key-values are part of the syslog structrude data,
+         * MessageFormat = "human" */
+        message, sizeof(message),
+        "<166>1 %s %s collectd %d - [%s value=\"%s\""
+        " plugin=\"%s\" plugin_instance=\"%s\""
+        " type_instance=\"%s\" type=\"%s\""
+        " ds_name=\"%s\" interval=\"%.0f\"] %s %s.%s=\"%s\"\n",
+        rfc3339_timestamp, host, pid, metrics_prefix, value, plugin,
+        plugin_instance, type_instance, type, ds_name,
+        CDTIME_T_TO_DOUBLE(interval), host_tags, plugin, key, value);
+  }
+  if (status < 0)
+    return -1;
+  message_len = (size_t)status;
+
+  if (message_len >= sizeof(message)) {
+    ERROR("write_syslog plugin: message buffer too small: "
+          "Need %" PRIsz " bytes.",
+          message_len + 1);
+    return -1;
+  }
+
+  pthread_mutex_lock(&cb->send_lock);
+
+  if (cb->sock_fd < 0) {
+    status = ws_callback_init(cb);
+    if (status != 0) {
+      ERROR("write_syslog plugin: ws_callback_init failed.");
+      pthread_mutex_unlock(&cb->send_lock);
+      return -1;
+    }
+  }
+
+  if (message_len >= cb->send_buf_free) {
+    status = ws_flush_nolock(0, cb);
+    if (status != 0) {
+      pthread_mutex_unlock(&cb->send_lock);
+      return status;
+    }
+  }
+
+  /* Assert that we have enough space for this message. */
+  assert(message_len < cb->send_buf_free);
+
+  /* `message_len + 1' because `message_len' does not include the
+   * trailing null byte. Neither does `send_buffer_fill'. */
+  memcpy(cb->send_buf + cb->send_buf_fill, message, message_len + 1);
+  cb->send_buf_fill += message_len;
+  cb->send_buf_free -= message_len;
+
+  DEBUG("write_syslog plugin: [%s]:%s buf %" PRIsz "/%" PRIsz
+        " (%.1f %%) \"%s\"",
+        cb->node, cb->service, cb->send_buf_fill, sizeof(cb->send_buf),
+        100.0 * ((double)cb->send_buf_fill) / ((double)sizeof(cb->send_buf)),
+        message);
+
+  pthread_mutex_unlock(&cb->send_lock);
+
+  return 0;
+}
+
+static int ws_write_messages(const data_set_t *ds, const value_list_t *vl,
+                             struct ws_callback *cb) {
+  char key[10 * DATA_MAX_NAME_LEN];
+  char values[512];
+
+  int status;
+
+  if (0 != strcmp(ds->type, vl->type)) {
+    ERROR("write_syslog plugin: DS type does not match "
+          "value list type");
+    return -1;
+  }
+
+  for (size_t i = 0; i < ds->ds_num; i++) {
+    const char *ds_name = NULL;
+
+    if (cb->always_append_ds || (ds->ds_num > 1))
+      ds_name = ds->ds[i].name;
+
+    /* Copy the identifier to 'key' and escape it. */
+    status = ws_format_name(key, sizeof(key), vl, cb, ds_name);
+    if (status != 0) {
+      ERROR("write_syslog plugin: error with format_name");
+      return status;
+    }
+
+    escape_string(key, sizeof(key));
+    /* Convert the values to an ASCII representation and put that into
+     * 'values'. */
+    status =
+        ws_format_values(values, sizeof(values), i, ds, vl, cb->store_rates);
+    if (status != 0) {
+      ERROR("write_syslog plugin: error with "
+            "ws_format_values");
+      return status;
+    }
+
+    /* Send the message to tcp */
+    status = ws_send_message(key, values, vl->time, cb, vl->plugin,
+                             vl->plugin_instance, vl->type_instance, vl->type,
+                             ds_name, vl->interval, vl->host);
+    if (status != 0) {
+      ERROR("write_syslog plugin: error with "
+            "ws_send_message");
+      return status;
+    }
+  }
+
+  return 0;
+}
+
+static int ws_write(const data_set_t *ds, const value_list_t *vl,
+                    user_data_t *user_data) {
+  struct ws_callback *cb;
+  int status;
+
+  if (user_data == NULL)
+    return EINVAL;
+
+  cb = user_data->data;
+
+  status = ws_write_messages(ds, vl, cb);
+
+  return status;
+}
+
+static int ws_config_tsd(oconfig_item_t *ci) {
+  struct ws_callback *cb;
+  char callback_name[DATA_MAX_NAME_LEN];
+
+  cb = calloc(1, sizeof(*cb));
+  if (cb == NULL) {
+    ERROR("write_syslog plugin: calloc failed.");
+    return -1;
+  }
+  cb->sock_fd = -1;
+  cb->connect_failed_log_enabled = 1;
+  cb->next_random_ttl = new_random_ttl();
+
+  pthread_mutex_init(&cb->send_lock, NULL);
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Host", child->key) == 0)
+      cf_util_get_string(child, &cb->node);
+    else if (strcasecmp("Port", child->key) == 0)
+      cf_util_get_service(child, &cb->service);
+    else if (strcasecmp("MessageFormat", child->key) == 0)
+      cf_util_get_string(child, &cb->msg_format);
+    else if (strcasecmp("HostTags", child->key) == 0)
+      cf_util_get_string(child, &cb->host_tags);
+    else if (strcasecmp("StoreRates", child->key) == 0)
+      cf_util_get_boolean(child, &cb->store_rates);
+    else if (strcasecmp("AlwaysAppendDS", child->key) == 0)
+      cf_util_get_boolean(child, &cb->always_append_ds);
+    else if (strcasecmp("Prefix", child->key) == 0)
+      cf_util_get_string(child, &cb->metrics_prefix);
+    else {
+      ERROR("write_syslog plugin: Invalid configuration "
+            "option: %s.",
+            child->key);
+      return -1;
+    }
+  }
+
+  snprintf(callback_name, sizeof(callback_name), "write_syslog/%s/%s",
+           cb->node != NULL ? cb->node : WS_DEFAULT_NODE,
+           cb->service != NULL ? cb->service : WS_DEFAULT_SERVICE);
+
+  user_data_t user_data = {.data = cb, .free_func = ws_callback_free};
+
+  plugin_register_write(callback_name, ws_write, &user_data);
+
+  user_data.free_func = NULL;
+  plugin_register_flush(callback_name, ws_flush, &user_data);
+
+  return 0;
+}
+
+static int ws_config(oconfig_item_t *ci) {
+  if ((resolve_interval == 0) && (resolve_jitter == 0))
+    resolve_interval = resolve_jitter = plugin_get_interval();
+
+  for (int i = 0; i < ci->children_num; i++) {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp("Node", child->key) == 0) {
+      if (ws_config_tsd(child) < 0)
+        return -1;
+    } else if (strcasecmp("ResolveInterval", child->key) == 0)
+      cf_util_get_cdtime(child, &resolve_interval);
+    else if (strcasecmp("ResolveJitter", child->key) == 0)
+      cf_util_get_cdtime(child, &resolve_jitter);
+    else {
+      ERROR("write_syslog plugin: Invalid configuration "
+            "option: %s.",
+            child->key);
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+void module_register(void) {
+  plugin_register_complex_config("write_syslog", ws_config);
+}