Merge branch 'ad/aquaero'
authorFlorian Forster <octo@collectd.org>
Sun, 18 Aug 2013 09:13:32 +0000 (11:13 +0200)
committerFlorian Forster <octo@collectd.org>
Sun, 18 Aug 2013 09:13:32 +0000 (11:13 +0200)
17 files changed:
.gitignore
AUTHORS
README
clean.sh
configure.in
src/Makefile.am
src/collectd.conf.in
src/collectd.conf.pod
src/curl_json.c
src/netlink.c
src/rrdtool.c
src/sigrok.c [new file with mode: 0644]
src/statsd.c [new file with mode: 0644]
src/types.db
src/utils_latency.c [new file with mode: 0644]
src/utils_latency.h [new file with mode: 0644]
src/varnish.c

index 6e87aaf..7c7c848 100644 (file)
@@ -33,11 +33,12 @@ src/stamp-h1
 .libs/
 src/collectd
 src/collectd-nagios
+src/collectd-tg
 src/collectdctl
 src/collectdmon
 src/*.1
 src/*.5
-src/libcollectdclient/lcc_features.h
+src/libcollectdclient/collectd/lcc_features.h
 
 # patch stuff
 *.rej
diff --git a/AUTHORS b/AUTHORS
index 2e6511b..31d132f 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -31,6 +31,9 @@ Amit Gupta <amit.gupta221 at gmail.com>
  - Multiple servers in the apache plugin.
  - curl_xml plugin.
 
+Andreas Henriksson <andreas at fatal.se>
+ - libmnl support in the netlink plugin.
+
 Anthony Dewhurst <dewhurst at gmail.com>
  - zfs_arc plugin.
 
@@ -44,6 +47,9 @@ Aurélien Reynaud <collectd at wattapower.net>
  - LPAR plugin.
  - Various fixes for AIX, HP-UX and Solaris.
 
+Bert Vermeulen <bert at biot.com>
+ - sigrok plugin
+
 Bruno Prémont <bonbons at linux-vserver.org>
  - BIND plugin.
  - Many bugreports and -fixes in various plugins,
@@ -128,6 +134,11 @@ Manuel Sanmartin
    + swap
  - Various AIX-related fixes and hacks.
 
+Marc Fournier <marc.fournier at camptocamp.com>
+ - Various fixes to the varnish plugin.
+ - RPM specfile update.
+ - libmnl support in the netlink plugin.
+
 Marco Chiappero <marco at absence.it>
  - uptime plugin.
  - ip6tables support in the iptables plugin.
diff --git a/README b/README
index df50ed2..c0d7036 100644 (file)
--- a/README
+++ b/README
@@ -283,6 +283,11 @@ Features
     - serial
       RX and TX of serial interfaces. Linux only; needs root privileges.
 
+    - sigrok
+      Uses libsigrok as a backend, allowing any sigrok-supported device
+      to have its measurements fed to collectd. This includes multimeters,
+      sound level meters, thermometers, and much more.
+
     - snmp
       Read values from SNMP (Simple Network Management Protocol) enabled
       network devices such as switches, routers, thermometers, rack monitoring
@@ -629,6 +634,10 @@ Prerequisites
     Used by the `memcachec' plugin to connect to a memcache daemon.
     <http://tangent.org/552/libmemcached.html>
 
+  * libmnl (optional)
+    Used by the `netlink' plugin.
+    <http://www.netfilter.org/projects/libmnl/>
+
   * libmodbus (optional)
     Used by the “modbus” plugin to communicate with Modbus/TCP devices. The
     “modbus” plugin works with version 2.0.3 of the library – due to frequent
@@ -643,10 +652,6 @@ Prerequisites
     Required for the “netapp” plugin.
     This library is part of the “Manage ONTAP SDK” published by NetApp.
 
-  * libnetlink (optional)
-    Used, obviously, for the `netlink' plugin.
-    <http://www.linuxfoundation.org/en/Net:Iproute2>
-
   * libnetsnmp (optional)
     For the `snmp' plugin.
     <http://www.net-snmp.org/>
@@ -712,6 +717,11 @@ Prerequisites
     To read from `lm_sensors', see the `sensors' plugin.
     <http://www.lm-sensors.org/>
 
+  * libsigrok (optional)
+    Used by the sigrok plugin. In addition, libsigrok depends on glib,
+    libzip, and optionally (depending on which drivers are enabled) on
+    libusb, libftdi and libudev.
+
   * libstatgrab (optional)
     Used by various plugins to collect statistics on systems other than Linux
     and/or Solaris.
index 9defb05..46e8c29 100755 (executable)
--- a/clean.sh
+++ b/clean.sh
@@ -21,6 +21,7 @@ true \
 && rm -f Makefile \
 && rm -f Makefile.in \
 && rm -f missing \
+&& rm -f INSTALL \
 && rm -f -r src/.deps \
 && rm -f -r src/.libs \
 && rm -f src/*.o \
@@ -28,6 +29,11 @@ true \
 && rm -f src/*.lo \
 && rm -f src/collectd \
 && rm -f src/collectd.1 \
+&& rm -f src/collectd.conf \
+&& rm -f src/collectdctl \
+&& rm -f src/collectd-tg \
+&& rm -f src/collectd-nagios \
+&& rm -f src/collectdmon \
 && rm -f src/config.h \
 && rm -f src/config.h.in \
 && rm -f src/config.h.in~ \
@@ -35,6 +41,18 @@ true \
 && rm -f src/Makefile.in \
 && rm -f src/stamp-h1 \
 && rm -f src/stamp-h1.in \
+&& rm -f src/*.pb-c.c \
+&& rm -f src/*.pb-c.h \
+&& rm -f src/Makefile.in \
+&& rm -f src/liboconfig/*.o \
+&& rm -f src/liboconfig/*.la \
+&& rm -f src/liboconfig/*.lo \
+&& rm -f -r src/liboconfig/.libs \
+&& rm -f -r src/liboconfig/Makefile \
+&& rm -f -r src/liboconfig/Makefile.in \
+&& rm -f -r src/liboconfig/parser.c \
+&& rm -f -r src/liboconfig/parser.h \
+&& rm -f -r src/liboconfig/scanner.c \
 && rm -f -r src/libping/.libs \
 && rm -f src/libping/*.o \
 && rm -f src/libping/*.la \
@@ -48,5 +66,17 @@ true \
 && rm -f src/libcollectdclient/*.o \
 && rm -f src/libcollectdclient/*.la \
 && rm -f src/libcollectdclient/*.lo \
+&& rm -f src/libcollectdclient/Makefile \
+&& rm -f src/libcollectdclient/Makefile.in \
+&& rm -f src/libcollectdclient/collectd/lcc_features.h \
+&& rm -f src/libcollectdclient/libcollectdclient.pc \
+&& rm -f bindings/Makefile \
+&& rm -f bindings/Makefile.in \
+&& rm -f -r bindings/java/.libs \
+&& rm -f bindings/java/Makefile \
+&& rm -f bindings/java/Makefile.in \
+&& rm -f bindings/java/java-build-stamp \
+&& rm -f bindings/java/org/collectd/api/*.class \
+&& rm -f bindings/java/org/collectd/java/*.class \
 && rm -f bindings/.perl-directory-stamp \
 && rm -f -r bindings/buildperl
index b396db6..16bffba 100644 (file)
@@ -2558,24 +2558,24 @@ fi
 AM_CONDITIONAL(BUILD_WITH_LIBMYSQL, test "x$with_libmysql" = "xyes")
 # }}}
 
-# --with-libnetlink {{{
-with_libnetlink_cflags=""
-with_libnetlink_libs="-lnetlink"
-AC_ARG_WITH(libnetlink, [AS_HELP_STRING([--with-libnetlink@<:@=PREFIX@:>@], [Path to libnetlink.])],
+# --with-libmnl {{{
+with_libmnl_cflags=""
+with_libmnl_libs=""
+AC_ARG_WITH(libmnl, [AS_HELP_STRING([--with-libmnl@<:@=PREFIX@:>@], [Path to libmnl.])],
 [
- echo "libnetlink: withval = $withval"
+ echo "libmnl: withval = $withval"
  if test "x$withval" = "xyes"
  then
-        with_libnetlink="yes"
+        with_libmnl="yes"
  else if test "x$withval" = "xno"
  then
-        with_libnetlink="no"
+        with_libmnl="no"
  else
         if test -d "$withval/include"
         then
-                with_libnetlink_cflags="-I$withval/include"
-                with_libnetlink_libs="-L$withval/lib -lnetlink"
-                with_libnetlink="yes"
+                with_libmnl_cflags="-I$withval/include"
+                with_libmnl_libs="-L$withval/lib -lmnl"
+                with_libmnl="yes"
         else
                 AC_MSG_ERROR("no such directory: $withval/include")
         fi
@@ -2584,21 +2584,25 @@ AC_ARG_WITH(libnetlink, [AS_HELP_STRING([--with-libnetlink@<:@=PREFIX@:>@], [Pat
 [
  if test "x$ac_system" = "xLinux"
  then
-        with_libnetlink="yes"
+        with_libmnl="yes"
  else
-        with_libnetlink="no (Linux only library)"
+        with_libmnl="no (Linux only library)"
  fi
 ])
-if test "x$with_libnetlink" = "xyes"
+if test "x$PKG_CONFIG" = "x"
 then
-       SAVE_CFLAGS="$CFLAGS"
-       CFLAGS="$CFLAGS $with_libnetlink_cflags"
-
-       with_libnetlink="no (libnetlink.h not found)"
+       with_libmnl="no (Don't have pkg-config)"
+fi
+if test "x$with_libmnl" = "xyes"
+then
+       if $PKG_CONFIG --exists libmnl 2>/dev/null; then
+         with_libmnl_cflags="$with_libmnl_ldflags `$PKG_CONFIG --cflags libmnl`"
+         with_libmnl_libs="$with_libmnl_libs `$PKG_CONFIG --libs libmnl`"
+       fi
 
-       AC_CHECK_HEADERS(libnetlink.h iproute/libnetlink.h linux/libnetlink.h,
+       AC_CHECK_HEADERS(libmnl.h libmnl/libmnl.h,
        [
-        with_libnetlink="yes"
+        with_libmnl="yes"
         break
        ], [],
 [#include <stdio.h>
@@ -2614,126 +2618,53 @@ then
 #include <sys/socket.h>])
 
         AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
-[[[
+[[
 #include <stdio.h>
 #include <sys/types.h>
 #include <asm/types.h>
 #include <sys/socket.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
-]]],
-[[[
+]],
+[[
 int retval = TCA_STATS2;
 return (retval);
-]]]
+]]
        )],
        [AC_DEFINE([HAVE_TCA_STATS2], [1], [True if the enum-member TCA_STATS2 exists])])
 
        AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
-[[[
+[[
 #include <stdio.h>
 #include <sys/types.h>
 #include <asm/types.h>
 #include <sys/socket.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
-]]],
-[[[
+]],
+[[
 int retval = TCA_STATS;
 return (retval);
-]]]
+]]
        )],
        [AC_DEFINE([HAVE_TCA_STATS], 1, [True if the enum-member TCA_STATS exists])])
-
-       CFLAGS="$SAVE_CFLAGS"
 fi
-if test "x$with_libnetlink" = "xyes"
+if test "x$with_libmnl" = "xyes"
 then
-       AC_CHECK_LIB(netlink, rtnl_open,
-                    [with_libnetlink="yes"],
-                    [with_libnetlink="no (symbol 'rtnl_open' not found)"],
-                    [$with_libnetlink_libs])
+       AC_CHECK_LIB(mnl, mnl_nlmsg_get_payload,
+                    [with_libmnl="yes"],
+                    [with_libmnl="no (symbol 'mnl_nlmsg_get_payload' not found)"],
+                    [$with_libmnl_libs])
 fi
-if test "x$with_libnetlink" = "xyes"
+if test "x$with_libmnl" = "xyes"
 then
-       SAVE_CFLAGS="$CFLAGS"
-       CFLAGS="$CFLAGS $with_libnetlink_cflags"
-
-       AC_CACHE_CHECK(
-               [if function 'rtnl_dump_filter' expects five arguments],
-               [c_cv_rtnl_dump_filter_five_args],
-               AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
-[[[
-#include <stdio.h>
-#include <sys/types.h>
-#include <asm/types.h>
-#include <sys/socket.h>
-#if HAVE_LIBNETLINK_H
-# include <libnetlink.h>
-#elif HAVE_IPROUTE_LIBNETLINK_H
-# include <iproute/libnetlink.h>
-#elif HAVE_LINUX_LIBNETLINK_H
-# include <linux/libnetlink.h>
-#endif
-]]],
-[[[
-if (rtnl_dump_filter(NULL, NULL, NULL, NULL, NULL))
-       return 1;
-return 0;
-]]]
-       )],
-       [c_cv_rtnl_dump_filter_five_args="yes"],
-       [c_cv_rtnl_dump_filter_five_args="no"]
-       )
-)
-
-       AC_CACHE_CHECK(
-               [if function 'rtnl_dump_filter' expects three arguments],
-               [c_cv_rtnl_dump_filter_three_args],
-               AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
-[[[
-#include <stdio.h>
-#include <sys/types.h>
-#include <asm/types.h>
-#include <sys/socket.h>
-#if HAVE_LIBNETLINK_H
-# include <libnetlink.h>
-#elif HAVE_IPROUTE_LIBNETLINK_H
-# include <iproute/libnetlink.h>
-#elif HAVE_LINUX_LIBNETLINK_H
-# include <linux/libnetlink.h>
-#endif
-]]],
-[[[
-if (rtnl_dump_filter(NULL, NULL, NULL))
-       return 1;
-return 0;
-]]]
-       )],
-       [c_cv_rtnl_dump_filter_three_args="yes"],
-       [c_cv_rtnl_dump_filter_three_args="no"]
-       )
-)
-
-       CFLAGS="$SAVE_CFLAGS"
-
-       if test "x$c_cv_rtnl_dump_filter_five_args" = "xyes"
-       then
-               AC_DEFINE(RTNL_DUMP_FILTER_FIVE_ARGS, 1,
-                               [Define to 1 if function 'rtnl_dump_filter' expects five arguments.])
-       fi
-       if test "x$c_cv_rtnl_dump_filter_three_args" = "xyes"
-       then
-               AC_DEFINE(RTNL_DUMP_FILTER_THREE_ARGS, 1,
-                               [Define to 1 if function 'rtnl_dump_filter' expects three arguments.])
-       fi
-
-       BUILD_WITH_LIBNETLINK_CFLAGS="$with_libnetlink_cflags"
-       BUILD_WITH_LIBNETLINK_LIBS="$with_libnetlink_libs"
-       AC_SUBST(BUILD_WITH_LIBNETLINK_CFLAGS)
-       AC_SUBST(BUILD_WITH_LIBNETLINK_LIBS)
+       AC_DEFINE(HAVE_LIBMNL, 1, [Define if libmnl is present and usable.])
+       BUILD_WITH_LIBMNL_CFLAGS="$with_libmnl_cflags"
+       BUILD_WITH_LIBMNL_LIBS="$with_libmnl_libs"
+       AC_SUBST(BUILD_WITH_LIBMNL_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBMNL_LIBS)
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBNETLINK, test "x$with_libnetlink" = "xyes")
+AM_CONDITIONAL(BUILD_WITH_LIBMNL, test "x$with_libmnl" = "xyes")
 # }}}
 
 # --with-libnetapp {{{
@@ -3824,6 +3755,74 @@ fi
 AM_CONDITIONAL(BUILD_WITH_LM_SENSORS, test "x$with_libsensors" = "xyes")
 # }}}
 
+# --with-libsigrok {{{
+with_libsigrok_cflags=""
+with_libsigrok_ldflags=""
+AC_ARG_WITH(libsigrok, [AS_HELP_STRING([--with-libsigrok@<:@=PREFIX@:>@], [Path to libsigrok.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libsigrok="no"
+       else
+               with_libsigrok="yes"
+               if test "x$withval" != "xyes"
+               then
+                       with_libsigrok_cflags="-I$withval/include"
+                       with_libsigrok_ldflags="-L$withval/lib"
+               fi
+       fi
+],[])
+
+# libsigrok has a glib dependency
+if test "x$with_libsigrok" = "xyes"
+then
+       if test -z "m4_ifdef([AM_PATH_GLIB_2_0], [yes], [])"
+       then
+               with_libsigrok="no (glib not available)"
+       else
+               AM_PATH_GLIB_2_0([2.28.0],
+                       [with_libsigrok_cflags="$with_libsigrok_cflags $GLIB_CFLAGS"; with_libsigrok_ldflags="$with_libsigrok_ldflags $GLIB_LIBS"])
+       fi
+fi
+
+# libsigrok headers
+if test "x$with_libsigrok" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libsigrok_cflags"
+
+       AC_CHECK_HEADERS(libsigrok/libsigrok.h, [], [with_libsigrok="no (libsigrok/libsigrok.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+# libsigrok library
+if test "x$with_libsigrok" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libsigrok_cflags"
+       LDFLAGS="$LDFLAGS $with_libsigrok_ldflags"
+
+       AC_CHECK_LIB(sigrok, sr_init,
+       [
+               AC_DEFINE(HAVE_LIBSIGROK, 1, [Define to 1 if you have the sigrok library (-lsigrok).])
+       ],
+       [with_libsigrok="no (libsigrok not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libsigrok" = "xyes"
+then
+       BUILD_WITH_LIBSIGROK_CFLAGS="$with_libsigrok_cflags"
+       BUILD_WITH_LIBSIGROK_LDFLAGS="$with_libsigrok_ldflags"
+       AC_SUBST(BUILD_WITH_LIBSIGROK_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBSIGROK_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBSIGROK, test "x$with_libsigrok" = "xyes")
+# }}}
+
 # --with-libstatgrab {{{
 with_libstatgrab_cflags=""
 with_libstatgrab_ldflags=""
@@ -5127,7 +5126,7 @@ AC_PLUGIN([modbus],      [$with_libmodbus],    [Modbus plugin])
 AC_PLUGIN([multimeter],  [$plugin_multimeter], [Read multimeter values])
 AC_PLUGIN([mysql],       [$with_libmysql],     [MySQL statistics])
 AC_PLUGIN([netapp],      [$with_libnetapp],    [NetApp plugin])
-AC_PLUGIN([netlink],     [$with_libnetlink],   [Enhanced Linux network statistics])
+AC_PLUGIN([netlink],     [$with_libmnl],       [Enhanced Linux network statistics])
 AC_PLUGIN([network],     [yes],                [Network communication plugin])
 AC_PLUGIN([nfs],         [$plugin_nfs],        [NFS statistics])
 AC_PLUGIN([nginx],       [$with_libcurl],      [nginx statistics])
@@ -5156,7 +5155,9 @@ AC_PLUGIN([rrdcached],   [$librrd_rrdc_update], [RRDTool output plugin])
 AC_PLUGIN([rrdtool],     [$with_librrd],       [RRDTool output plugin])
 AC_PLUGIN([sensors],     [$with_libsensors],   [lm_sensors statistics])
 AC_PLUGIN([serial],      [$plugin_serial],     [serial port traffic])
+AC_PLUGIN([sigrok],      [$with_libsigrok],    [sigrok acquisition sources])
 AC_PLUGIN([snmp],        [$with_libnetsnmp],   [SNMP querying plugin])
+AC_PLUGIN([statsd],      [yes],                [StatsD plugin])
 AC_PLUGIN([swap],        [$plugin_swap],       [Swap usage statistics])
 AC_PLUGIN([syslog],      [$have_syslog],       [Syslog logging plugin])
 AC_PLUGIN([table],       [yes],                [Parsing of tabular data])
@@ -5372,10 +5373,10 @@ Configuration:
     libkstat  . . . . . . $with_kstat
     libkvm  . . . . . . . $with_libkvm
     libmemcached  . . . . $with_libmemcached
+    libmnl  . . . . . . . $with_libmnl
     libmodbus . . . . . . $with_libmodbus
     libmysql  . . . . . . $with_libmysql
     libnetapp . . . . . . $with_libnetapp
-    libnetlink  . . . . . $with_libnetlink
     libnetsnmp  . . . . . $with_libnetsnmp
     libnotify . . . . . . $with_libnotify
     liboconfig  . . . . . $with_liboconfig
@@ -5390,6 +5391,7 @@ Configuration:
     librouteros . . . . . $with_librouteros
     librrd  . . . . . . . $with_librrd
     libsensors  . . . . . $with_libsensors
+    libsigrok   . . . . . $with_libsigrok
     libstatgrab . . . . . $with_libstatgrab
     libtokyotyrant  . . . $with_libtokyotyrant
     libupsclient  . . . . $with_libupsclient
@@ -5495,7 +5497,9 @@ Configuration:
     rrdtool . . . . . . . $enable_rrdtool
     sensors . . . . . . . $enable_sensors
     serial  . . . . . . . $enable_serial
+    sigrok  . . . . . . . $enable_sigrok
     snmp  . . . . . . . . $enable_snmp
+    statsd  . . . . . . . $enable_statsd
     swap  . . . . . . . . $enable_swap
     syslog  . . . . . . . $enable_syslog
     table . . . . . . . . $enable_table
index 6da9d53..59454dc 100644 (file)
@@ -754,8 +754,8 @@ if BUILD_PLUGIN_NETLINK
 pkglib_LTLIBRARIES += netlink.la
 netlink_la_SOURCES = netlink.c
 netlink_la_LDFLAGS = -module -avoid-version
-netlink_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBNETLINK_CFLAGS)
-netlink_la_LIBADD = $(BUILD_WITH_LIBNETLINK_LIBS)
+netlink_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBMNL_CFLAGS)
+netlink_la_LIBADD = $(BUILD_WITH_LIBMNL_LIBS)
 collectd_LDADD += "-dlopen" netlink.la
 collectd_DEPENDENCIES += netlink.la
 endif
@@ -1062,6 +1062,16 @@ collectd_LDADD += "-dlopen" serial.la
 collectd_DEPENDENCIES += serial.la
 endif
 
+if BUILD_PLUGIN_SIGROK
+pkglib_LTLIBRARIES += sigrok.la
+sigrok_la_SOURCES = sigrok.c
+sigrok_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBSIGROK_CFLAGS)
+sigrok_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBSIGROK_LDFLAGS)
+sigrok_la_LIBADD = -lsigrok
+collectd_LDADD += "-dlopen" sigrok.la
+collectd_DEPENDENCIES += sigrok.la
+endif
+
 if BUILD_PLUGIN_SNMP
 pkglib_LTLIBRARIES += snmp.la
 snmp_la_SOURCES = snmp.c
@@ -1079,6 +1089,16 @@ collectd_LDADD += "-dlopen" snmp.la
 collectd_DEPENDENCIES += snmp.la
 endif
 
+if BUILD_PLUGIN_STATSD
+pkglib_LTLIBRARIES += statsd.la
+statsd_la_SOURCES = statsd.c \
+                    utils_latency.h utils_latency.c
+statsd_la_LDFLAGS = -module -avoid-version
+statsd_la_LIBADD = -lpthread
+collectd_LDADD += "-dlopen" statsd.la
+collectd_DEPENDENCIES += statsd.la
+endif
+
 if BUILD_PLUGIN_SWAP
 pkglib_LTLIBRARIES += swap.la
 swap_la_SOURCES = swap.c
index 040b930..d4d5a20 100644 (file)
 @LOAD_PLUGIN_RRDTOOL@LoadPlugin rrdtool
 #@BUILD_PLUGIN_SENSORS_TRUE@LoadPlugin sensors
 #@BUILD_PLUGIN_SERIAL_TRUE@LoadPlugin serial
+#@BUILD_PLUGIN_SIGROK_TRUE@LoadPlugin sigrok
 #@BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
+#@BUILD_PLUGIN_STATSD_TRUE@LoadPlugin statsd
 #@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
 #@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table
 #@BUILD_PLUGIN_TAIL_TRUE@LoadPlugin tail
 #      IgnoreSelected false
 #</Plugin>
 
+#<Plugin sigrok>
+#  LogLevel 3
+#  <Device "AC Voltage">
+#    Driver "fluke-dmm"
+#    MinimumInterval 10
+#    Conn "/dev/ttyUSB2"
+#  </Device>
+#  <Device "Sound Level">
+#    Driver "cem-dt-885x"
+#    Conn "/dev/ttyUSB1"
+#  </Device>
+#</Plugin>
+
 #<Plugin snmp>
 #   <Data "powerplus_voltge_input">
 #       Type "voltage"
 #   </Host>
 #</Plugin>
 
+#<Plugin statsd>
+#  Host "::"
+#  Port "8125"
+#  DeleteCounters false
+#  DeleteTimers   false
+#  DeleteGauges   false
+#  DeleteSets     false
+#  TimerPercentile 90.0
+#</Plugin>
+
 #<Plugin "swap">
 #      ReportByDevice false
 #      ReportBytes true
index 1add975..691a749 100644 (file)
@@ -1095,13 +1095,15 @@ is set to B<true>, B<Match> blocks are optional.
 
 =head2 Plugin C<curl_json>
 
-The B<curl_json plugin> uses B<libcurl> (L<http://curl.haxx.se/>) and
-B<libyajl> (L<http://www.lloydforge.org/projects/yajl/>) to retrieve JSON data
-via cURL. This can be used to collect values from CouchDB documents (which are
-stored JSON notation), for example.
-
-The following example will collect several values from the built-in `_stats'
-runtime statistics module of CouchDB
+The B<curl_json plugin> collects values from JSON data to be parsed by
+B<libyajl> (L<http://www.lloydforge.org/projects/yajl/>) retrieved via
+either B<libcurl> (L<http://curl.haxx.se/>) or read directly from a
+unix socket. The former can be used, for example, to collect values
+from CouchDB documents (which are stored JSON notation), and the
+latter to collect values from a uWSGI stats socket.
+
+The following example will collect several values from the built-in
+C<_stats> runtime statistics module of I<CouchDB>
 (L<http://wiki.apache.org/couchdb/Runtime_Statistics>).
 
   <Plugin curl_json>
@@ -1121,11 +1123,30 @@ runtime statistics module of CouchDB
     </URL>
   </Plugin>
 
-In the B<Plugin> block, there may be one or more B<URL> blocks, each defining
-a URL to be fetched via HTTP (using libcurl) and one or more B<Key> blocks.
-The B<Key> string argument must be in a path format, which is used to collect a
-value from a JSON map object. If a path element of B<Key> is the
-I<*>E<nbsp>wildcard, the values for all keys will be collectd.
+This example will collect data directly from a I<uWSGI> "Stats Server" socket.
+
+  <Plugin curl_json>
+    <Sock "/var/run/uwsgi.stats.sock">
+      Instance "uwsgi"
+      <Key "workers/*/requests">
+        Type "http_requests"
+      </Key>
+
+      <Key "workers/*/apps/*/requests">
+        Type "http_requests"
+      </Key>
+    </Sock>
+  </Plugin>
+
+In the B<Plugin> block, there may be one or more B<URL> blocks, each
+defining a URL to be fetched via HTTP (using libcurl) or B<Sock>
+blocks defining a unix socket to read JSON from directly.  Each of
+these blocks may have one or more B<Key> blocks.
+
+The B<Key> string argument must be in a path format. Each component is
+used to match the key from a JSON map or the index of an JSON
+array. If a path component of a B<Key> is a I<*>E<nbsp>wildcard, the
+values for all map keys or array indices will be collectd.
 
 The following options are valid within B<URL> blocks:
 
@@ -5171,12 +5192,129 @@ and all other sensors are collected.
 
 =back
 
+=head2 Plugin C<sigrok>
+
+The I<sigrok plugin> uses I<libsigrok> to retrieve measurements from any device
+supported by the L<sigrok|http://sigrok.org/> project.
+
+B<Synopsis>
+
+ <Plugin sigrok>
+   LogLevel 3
+   <Device "AC Voltage">
+      Driver "fluke-dmm"
+      MinimumInterval 10
+      Conn "/dev/ttyUSB2"
+   </Device>
+   <Device "Sound Level">
+      Driver "cem-dt-885x"
+      Conn "/dev/ttyUSB1"
+   </Device>
+ </Plugin>
+
+=over 4
+
+=item B<LogLevel> B<0-5>
+
+The I<sigrok> logging level to pass on to the I<collectd> log, as a number
+between B<0> and B<5> (inclusive). These levels correspond to C<None>,
+C<Errors>, C<Warnings>, C<Informational>, C<Debug >and C<Spew>, respectively.
+The default is B<2> (C<Warnings>). The I<sigrok> log messages, regardless of
+their level, are always submitted to I<collectd> at its INFO log level.
+
+=item E<lt>B<Device> I<Name>E<gt>
+
+A sigrok-supported device, uniquely identified by this section's options. The
+I<Name> is passed to I<collectd> as the I<plugin instance>.
+
+=item B<Driver> I<DriverName>
+
+The sigrok driver to use for this device.
+
+=item B<Conn> I<ConnectionSpec>
+
+If the device cannot be auto-discovered, or more than one might be discovered
+by the driver, I<ConnectionSpec> specifies the connection string to the device.
+It can be of the form of a device path (e.g.E<nbsp>C</dev/ttyUSB2>), or, in
+case of a non-serial USB-connected device, the USB I<VendorID>B<.>I<ProductID>
+separated by a period (e.g.E<nbsp>C<0403.6001>). A USB device can also be
+specified as I<Bus>B<.>I<Address> (e.g.E<nbsp>C<1.41>).
+
+=item B<SerialComm> I<SerialSpec>
+
+For serial devices with non-standard port settings, this option can be used
+to specify them in a form understood by I<sigrok>, e.g.E<nbsp>C<9600/8n1>.
+This should not be necessary; drivers know how to communicate with devices they
+support.
+
+=item B<MinimumInterval> I<Seconds>
+
+Specifies the minimum time between measurement dispatches to I<collectd>, in
+seconds. Since some I<sigrok> supported devices can acquire measurements many
+times per second, it may be necessary to throttle these. For example, the
+I<RRD plugin> cannot process writes more than once per second.
+
+The default B<MinimumInterval> is B<0>, meaning measurements received from the
+device are always dispatched to I<collectd>. When throttled, unused
+measurements are discarded.
+
+=back
+
 =head2 Plugin C<snmp>
 
 Since the configuration of the C<snmp plugin> is a little more complicated than
 other plugins, its documentation has been moved to an own manpage,
 L<collectd-snmp(5)>. Please see there for details.
 
+=head2 Plugin C<statsd>
+
+The I<statsd plugin> listens to a UDP socket, reads "events" in the statsd
+protocol and dispatches rates or other aggregates of these numbers
+periodically.
+
+The plugin implements the I<Counter>, I<Timer>, I<Gauge> and I<Set> types which
+are dispatched as the I<collectd> types C<derive>, C<latency>, C<gauge> and
+C<objects> respectively.
+
+The following configuration options are valid:
+
+=over 4
+
+=item B<Host> I<Host>
+
+Bind to the hostname / address I<Host>. By default, the plugin will bind to the
+"any" address, i.e. accept packets sent to any of the hosts addresses.
+
+=item B<Port> I<Port>
+
+UDP port to listen to. This can be either a service name or a port number.
+Defaults to C<8125>.
+
+=item B<DeleteCounters> B<false>|B<true>
+
+=item B<DeleteTimers> B<false>|B<true>
+
+=item B<DeleteGauges> B<false>|B<true>
+
+=item B<DeleteSets> B<false>|B<true>
+
+These options control what happens if metrics are not updated in an interval.
+If set to B<False>, the default, metrics are dispatched unchanged, i.e. the
+rate of counters and size of sets will be zero, timers report C<NaN> and gauges
+are unchanged. If set to B<True>, the such metrics are not dispatched and
+removed from the internal cache.
+
+=item B<TimerPercentile> I<Percent>
+
+Calculate and dispatch the configured percentile, i.e. compute the latency, so
+that I<Percent> of all reported timers are smaller than or equal to the
+computed latency. This is useful for cutting off the long tail latency, as it's
+often done in I<Service Level Agreements> (SLAs).
+
+If not specified, no percentile is calculated / dispatched.
+
+=back
+
 =head2 Plugin C<swap>
 
 The I<Swap plugin> collects information about used and available swap space. On
index deee460..511863e 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * collectd - src/curl_json.c
  * Copyright (C) 2009       Doug MacEachern
- * Copyright (C) 2006-2011  Florian octo Forster
+ * Copyright (C) 2006-2013  Florian octo Forster
  *
  * 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
 #include "utils_avltree.h"
 #include "utils_complain.h"
 
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
 #include <curl/curl.h>
+
 #include <yajl/yajl_parse.h>
 #if HAVE_YAJL_YAJL_VERSION_H
 # include <yajl/yajl_version.h>
@@ -60,6 +65,8 @@ struct cj_s /* {{{ */
   char *instance;
   char *host;
 
+  char *sock;
+
   char *url;
   char *user;
   char *pass;
@@ -82,6 +89,8 @@ struct cj_s /* {{{ */
       c_avl_tree_t *tree;
       cj_key_t *key;
     };
+    _Bool in_array;
+    int index;
     char name[DATA_MAX_NAME_LEN];
   } state[YAJL_MAX_DEPTH];
 };
@@ -94,7 +103,6 @@ typedef unsigned int yajl_len_t;
 #endif
 
 static int cj_read (user_data_t *ud);
-static int cj_curl_perform (cj_t *db, CURL *curl);
 static void cj_submit (cj_t *db, cj_key_t *key, value_t *value);
 
 static size_t cj_curl_callback (void *buf, /* {{{ */
@@ -167,12 +175,44 @@ static int cj_get_type (cj_key_t *key)
   return ds->ds[0].type;
 }
 
+static int cj_cb_map_key (void *ctx, const unsigned char *val,
+    yajl_len_t len);
+
+static void cj_cb_inc_array_index (void *ctx, _Bool update_key)
+{
+  cj_t *db = (cj_t *)ctx;
+
+  if (!db->state[db->depth].in_array)
+    return;
+
+  db->state[db->depth].index++;
+
+  if (update_key)
+  {
+    char name[DATA_MAX_NAME_LEN];
+
+    ssnprintf (name, sizeof (name), "%d", db->state[db->depth].index - 1);
+
+    cj_cb_map_key (ctx, (unsigned char *)name, (yajl_len_t) strlen (name));
+  }
+}
+
 /* yajl callbacks */
 #define CJ_CB_ABORT    0
 #define CJ_CB_CONTINUE 1
 
-/* "number" may not be null terminated, so copy it into a buffer before
- * parsing. */
+static int cj_cb_boolean (void * ctx, int boolVal)
+{
+  cj_cb_inc_array_index (ctx, /* update_key = */ 0);
+  return (CJ_CB_CONTINUE);
+}
+
+static int cj_cb_null (void * ctx)
+{
+  cj_cb_inc_array_index (ctx, /* update_key = */ 0);
+  return (CJ_CB_CONTINUE);
+}
+
 static int cj_cb_number (void *ctx,
     const char *number, yajl_len_t number_len)
 {
@@ -184,12 +224,20 @@ static int cj_cb_number (void *ctx,
   int type;
   int status;
 
-  if ((key == NULL) || !CJ_IS_KEY (key))
-    return (CJ_CB_CONTINUE);
-
+  /* Create a null-terminated version of the string. */
   memcpy (buffer, number, number_len);
   buffer[sizeof (buffer) - 1] = 0;
 
+  if ((key == NULL) || !CJ_IS_KEY (key)) {
+    if (key != NULL)
+      NOTICE ("curl_json plugin: Found \"%s\", but the configuration expects"
+              " a map.", buffer);
+    cj_cb_inc_array_index (ctx, /* update_key = */ 0);
+    return (CJ_CB_CONTINUE);
+  } else {
+    cj_cb_inc_array_index (ctx, /* update_key = */ 1);
+  }
+
   type = cj_get_type (key);
   status = parse_value (buffer, &vt, type);
   if (status != 0)
@@ -202,8 +250,11 @@ static int cj_cb_number (void *ctx,
   return (CJ_CB_CONTINUE);
 } /* int cj_cb_number */
 
-static int cj_cb_map_key (void *ctx, const unsigned char *val,
-    yajl_len_t len)
+/* Queries the key-tree of the parent context for "in_name" and, if found,
+ * updates the "key" field of the current context. Otherwise, "key" is set to
+ * NULL. */
+static int cj_cb_map_key (void *ctx,
+    unsigned char const *in_name, yajl_len_t in_name_len)
 {
   cj_t *db = (cj_t *)ctx;
   c_avl_tree_t *tree;
@@ -212,12 +263,16 @@ static int cj_cb_map_key (void *ctx, const unsigned char *val,
 
   if (tree != NULL)
   {
-    cj_key_t *value;
+    cj_key_t *value = NULL;
     char *name;
+    size_t name_len;
 
+    /* Create a null-terminated version of the name. */
     name = db->state[db->depth].name;
-    len = COUCH_MIN(len, sizeof (db->state[db->depth].name)-1);
-    sstrncpy (name, (char *)val, len+1);
+    name_len = COUCH_MIN ((size_t) in_name_len,
+        sizeof (db->state[db->depth].name) - 1);
+    memcpy (name, in_name, name_len);
+    name[name_len] = 0;
 
     if (c_avl_get (tree, name, (void *) &value) == 0)
       db->state[db->depth].key = value;
@@ -233,24 +288,6 @@ static int cj_cb_map_key (void *ctx, const unsigned char *val,
 static int cj_cb_string (void *ctx, const unsigned char *val,
     yajl_len_t len)
 {
-  cj_t *db = (cj_t *)ctx;
-  char str[len + 1];
-
-  /* Create a null-terminated version of the string. */
-  memcpy (str, val, len);
-  str[len] = 0;
-
-  /* No configuration for this string -> simply return. */
-  if (db->state[db->depth].key == NULL)
-    return (CJ_CB_CONTINUE);
-
-  if (!CJ_IS_KEY (db->state[db->depth].key))
-  {
-    NOTICE ("curl_json plugin: Found string \"%s\", but the configuration "
-        "expects a map here.", str);
-    return (CJ_CB_CONTINUE);
-  }
-
   /* Handle the string as if it was a number. */
   return (cj_cb_number (ctx, (const char *) val, len));
 } /* int cj_cb_string */
@@ -260,7 +297,8 @@ static int cj_cb_start (void *ctx)
   cj_t *db = (cj_t *)ctx;
   if (++db->depth >= YAJL_MAX_DEPTH)
   {
-    ERROR ("curl_json plugin: %s depth exceeds max, aborting.", db->url);
+    ERROR ("curl_json plugin: %s depth exceeds max, aborting.",
+           db->url ? db->url : db->sock);
     return (CJ_CB_ABORT);
   }
   return (CJ_CB_CONTINUE);
@@ -276,6 +314,7 @@ static int cj_cb_end (void *ctx)
 
 static int cj_cb_start_map (void *ctx)
 {
+  cj_cb_inc_array_index (ctx, /* update_key = */ 1);
   return cj_cb_start (ctx);
 }
 
@@ -286,17 +325,25 @@ static int cj_cb_end_map (void *ctx)
 
 static int cj_cb_start_array (void * ctx)
 {
+  cj_t *db = (cj_t *)ctx;
+  cj_cb_inc_array_index (ctx, /* update_key = */ 1);
+  if (db->depth+1 < YAJL_MAX_DEPTH) {
+    db->state[db->depth+1].in_array = 1;
+    db->state[db->depth+1].index = 0;
+  }
   return cj_cb_start (ctx);
 }
 
 static int cj_cb_end_array (void * ctx)
 {
+  cj_t *db = (cj_t *)ctx;
+  db->state[db->depth].in_array = 0;
   return cj_cb_end (ctx);
 }
 
 static yajl_callbacks ycallbacks = {
-  NULL, /* null */
-  NULL, /* boolean */
+  cj_cb_null, /* null */
+  cj_cb_boolean, /* boolean */
   NULL, /* integer */
   NULL, /* double */
   cj_cb_number,
@@ -364,6 +411,8 @@ static void cj_free (void *arg) /* {{{ */
   sfree (db->instance);
   sfree (db->host);
 
+  sfree (db->sock);
+
   sfree (db->url);
   sfree (db->user);
   sfree (db->pass);
@@ -598,20 +647,20 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
   memset (db, 0, sizeof (*db));
 
   if (strcasecmp ("URL", ci->key) == 0)
-  {
     status = cf_util_get_string (ci, &db->url);
-    if (status != 0)
-    {
-      sfree (db);
-      return (status);
-    }
-  }
+  else if (strcasecmp ("Sock", ci->key) == 0)
+    status = cf_util_get_string (ci, &db->sock);
   else
   {
     ERROR ("curl_json plugin: cj_config: "
            "Invalid key: %s", ci->key);
     return (-1);
   }
+  if (status != 0)
+  {
+    sfree (db);
+    return (status);
+  }
 
   /* Fill the `cj_t' structure.. */
   for (i = 0; i < ci->children_num; i++)
@@ -622,19 +671,19 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string (child, &db->instance);
     else if (strcasecmp ("Host", child->key) == 0)
       status = cf_util_get_string (child, &db->host);
-    else if (strcasecmp ("User", child->key) == 0)
+    else if (db->url && strcasecmp ("User", child->key) == 0)
       status = cf_util_get_string (child, &db->user);
-    else if (strcasecmp ("Password", child->key) == 0)
+    else if (db->url && strcasecmp ("Password", child->key) == 0)
       status = cf_util_get_string (child, &db->pass);
-    else if (strcasecmp ("VerifyPeer", child->key) == 0)
+    else if (db->url && strcasecmp ("VerifyPeer", child->key) == 0)
       status = cf_util_get_boolean (child, &db->verify_peer);
-    else if (strcasecmp ("VerifyHost", child->key) == 0)
+    else if (db->url && strcasecmp ("VerifyHost", child->key) == 0)
       status = cf_util_get_boolean (child, &db->verify_host);
-    else if (strcasecmp ("CACert", child->key) == 0)
+    else if (db->url && strcasecmp ("CACert", child->key) == 0)
       status = cf_util_get_string (child, &db->cacert);
-    else if (strcasecmp ("Header", child->key) == 0)
+    else if (db->url && strcasecmp ("Header", child->key) == 0)
       status = cj_config_append_string ("Header", &db->headers, child);
-    else if (strcasecmp ("Post", child->key) == 0)
+    else if (db->url && strcasecmp ("Post", child->key) == 0)
       status = cf_util_get_string (child, &db->post_body);
     else if (strcasecmp ("Key", child->key) == 0)
       status = cj_config_add_key (db, child);
@@ -652,11 +701,11 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
   {
     if (db->tree == NULL)
     {
-      WARNING ("curl_json plugin: No (valid) `Key' block "
-               "within `URL' block `%s'.", db->url);
+      WARNING ("curl_json plugin: No (valid) `Key' block within `%s' \"`%s'\".",
+               db->url ? "URL" : "Sock", db->url ? db->url : db->sock);
       status = -1;
     }
-    if (status == 0)
+    if (status == 0 && db->url)
       status = cj_init_curl (db);
   }
 
@@ -677,7 +726,7 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
     ud.free_func = cj_free;
 
     ssnprintf (cb_name, sizeof (cb_name), "curl_json-%s-%s",
-               db->instance, db->url);
+               db->instance, db->url ? db->url : db->sock);
 
     plugin_register_complex_read (/* group = */ NULL, cb_name, cj_read,
                                   /* interval = */ NULL, &ud);
@@ -706,7 +755,8 @@ static int cj_config (oconfig_item_t *ci) /* {{{ */
   {
     oconfig_item_t *child = ci->children + i;
 
-    if (strcasecmp ("URL", child->key) == 0)
+    if (strcasecmp ("Sock", child->key) == 0
+        || strcasecmp ("URL", child->key) == 0)
     {
       status = cj_config_add_url (child);
       if (status == 0)
@@ -766,47 +816,97 @@ static void cj_submit (cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
   plugin_dispatch_values (&vl);
 } /* }}} int cj_submit */
 
-static int cj_curl_perform (cj_t *db, CURL *curl) /* {{{ */
+static int cj_sock_perform (cj_t *db) /* {{{ */
 {
-  int status;
-  long rc;
-  char *url;
-  yajl_handle yprev = db->yajl;
+  char errbuf[1024];
+  struct sockaddr_un sa_unix = {};
+  sa_unix.sun_family = AF_UNIX;
+  sstrncpy (sa_unix.sun_path, db->sock, sizeof (sa_unix.sun_path));
 
-  db->yajl = yajl_alloc (&ycallbacks,
-#if HAVE_YAJL_V2
-      /* alloc funcs = */ NULL,
-#else
-      /* alloc funcs = */ NULL, NULL,
-#endif
-      /* context = */ (void *)db);
-  if (db->yajl == NULL)
+  int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (fd < 0)
+    return (-1);
+  if (connect (fd, (struct sockaddr *)&sa_unix, sizeof(sa_unix)) < 0)
   {
-    ERROR ("curl_json plugin: yajl_alloc failed.");
-    db->yajl = yprev;
+    ERROR ("curl_json plugin: connect(%s) failed: %s",
+           (db->sock != NULL) ? db->sock : "<null>",
+           sstrerror(errno, errbuf, sizeof (errbuf)));
+    close (fd);
     return (-1);
   }
 
+  ssize_t red;
+  do {
+    unsigned char buffer[4096];
+    red = read (fd, buffer, sizeof(buffer));
+    if (red < 0) {
+        ERROR ("curl_json plugin: read(%s) failed: %s",
+               (db->sock != NULL) ? db->sock : "<null>",
+               sstrerror(errno, errbuf, sizeof (errbuf)));
+        close (fd);
+        return (-1);
+    }
+    if (!cj_curl_callback (buffer, red, 1, db))
+        break;
+  } while (red > 0);
+  close (fd);
+  return (0);
+} /* }}} int cj_sock_perform */
+
+
+static int cj_curl_perform(cj_t *db) /* {{{ */
+{
+  int status;
+  long rc;
+  char *url;
   url = NULL;
-  curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
+  curl_easy_getinfo(db->curl, CURLINFO_EFFECTIVE_URL, &url);
 
-  status = curl_easy_perform (curl);
+  status = curl_easy_perform (db->curl);
   if (status != CURLE_OK)
   {
     ERROR ("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
            status, db->curl_errbuf, (url != NULL) ? url : "<null>");
-    yajl_free (db->yajl);
-    db->yajl = yprev;
     return (-1);
   }
 
-  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
+  curl_easy_getinfo(db->curl, CURLINFO_RESPONSE_CODE, &rc);
 
   /* The response code is zero if a non-HTTP transport was used. */
   if ((rc != 0) && (rc != 200))
   {
     ERROR ("curl_json plugin: curl_easy_perform failed with "
         "response code %ld (%s)", rc, url);
+    return (-1);
+  }
+  return (0);
+} /* }}} int cj_curl_perform */
+
+static int cj_perform (cj_t *db) /* {{{ */
+{
+  int status;
+  yajl_handle yprev = db->yajl;
+
+  db->yajl = yajl_alloc (&ycallbacks,
+#if HAVE_YAJL_V2
+      /* alloc funcs = */ NULL,
+#else
+      /* alloc funcs = */ NULL, NULL,
+#endif
+      /* context = */ (void *)db);
+  if (db->yajl == NULL)
+  {
+    ERROR ("curl_json plugin: yajl_alloc failed.");
+    db->yajl = yprev;
+    return (-1);
+  }
+
+  if (db->url)
+    status = cj_curl_perform (db);
+  else
+    status = cj_sock_perform (db);
+  if (status < 0)
+  {
     yajl_free (db->yajl);
     db->yajl = yprev;
     return (-1);
@@ -834,7 +934,7 @@ static int cj_curl_perform (cj_t *db, CURL *curl) /* {{{ */
   yajl_free (db->yajl);
   db->yajl = yprev;
   return (0);
-} /* }}} int cj_curl_perform */
+} /* }}} int cj_perform */
 
 static int cj_read (user_data_t *ud) /* {{{ */
 {
@@ -853,7 +953,7 @@ static int cj_read (user_data_t *ud) /* {{{ */
   db->state[db->depth].tree = db->tree;
   db->key = NULL;
 
-  return cj_curl_perform (db, db->curl);
+  return cj_perform (db);
 } /* }}} int cj_read */
 
 void module_register (void)
index 39536d9..422dc8c 100644 (file)
@@ -1,6 +1,9 @@
 /**
  * collectd - src/netlink.c
  * Copyright (C) 2007-2010  Florian octo Forster
+ * Copyright (C) 2008-2012  Sebastian Harl
+ * Copyright (C) 2013       Andreas Henriksson
+ * Copyright (C) 2013       Marc Fournier
  *
  * 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
@@ -17,6 +20,9 @@
  *
  * Authors:
  *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Andreas Henriksson <andreas at fatal.se>
+ *   Marc Fournier <marc.fournier at camptocamp.com>
  **/
 
 #include "collectd.h"
 # include <linux/pkt_sched.h>
 #endif
 
-#if HAVE_LIBNETLINK_H
-# include <libnetlink.h>
-#elif HAVE_IPROUTE_LIBNETLINK_H
-# include <iproute/libnetlink.h>
-#elif HAVE_LINUX_LIBNETLINK_H
-# include <linux/libnetlink.h>
-#endif
+#include <libmnl/libmnl.h>
 
 typedef struct ir_ignorelist_s
 {
@@ -54,19 +54,19 @@ typedef struct ir_ignorelist_s
 static int ir_ignorelist_invert = 1;
 static ir_ignorelist_t *ir_ignorelist_head = NULL;
 
-static struct rtnl_handle rth;
+static struct mnl_socket *nl;
 
 static char **iflist = NULL;
 static size_t iflist_len = 0;
 
 static const char *config_keys[] =
 {
-       "Interface",
-       "VerboseInterface",
-       "QDisc",
-       "Class",
-       "Filter",
-       "IgnoreSelected"
+        "Interface",
+        "VerboseInterface",
+        "QDisc",
+        "Class",
+        "Filter",
+        "IgnoreSelected"
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
@@ -117,7 +117,7 @@ static int add_ignorelist (const char *dev, const char *type,
   return (0);
 } /* int add_ignorelist */
 
-/* 
+/*
  * Checks wether a data set should be ignored. Returns `true' is the value
  * should be ignored, `false' otherwise.
  */
@@ -135,24 +135,24 @@ static int check_ignorelist (const char *dev,
   {
     /* i->device == NULL  =>  match all devices */
     if ((i->device != NULL)
-       && (strcasecmp (i->device, dev) != 0))
+        && (strcasecmp (i->device, dev) != 0))
       continue;
 
     if (strcasecmp (i->type, type) != 0)
       continue;
 
     if ((i->inst != NULL) && (type_instance != NULL)
-       && (strcasecmp (i->inst, type_instance) != 0))
+        && (strcasecmp (i->inst, type_instance) != 0))
       continue;
 
     DEBUG ("netlink plugin: check_ignorelist: "
-       "(dev = %s; type = %s; inst = %s) matched "
-       "(dev = %s; type = %s; inst = %s)",
-       dev, type,
-       type_instance == NULL ? "(nil)" : type_instance,
-       i->device == NULL ? "(nil)" : i->device,
-       i->type,
-       i->inst == NULL ? "(nil)" : i->inst);
+        "(dev = %s; type = %s; inst = %s) matched "
+        "(dev = %s; type = %s; inst = %s)",
+        dev, type,
+        type_instance == NULL ? "(nil)" : type_instance,
+        i->device == NULL ? "(nil)" : i->device,
+        i->type,
+        i->inst == NULL ? "(nil)" : i->inst);
 
     return (ir_ignorelist_invert ? 0 : 1);
   } /* for i */
@@ -204,46 +204,8 @@ static void submit_two (const char *dev, const char *type,
   plugin_dispatch_values (&vl);
 } /* void submit_two */
 
-static int link_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
-    struct nlmsghdr *nmh, void __attribute__((unused)) *args)
+static int update_iflist (struct ifinfomsg *msg, const char *dev)
 {
-  struct ifinfomsg *msg;
-  int msg_len;
-  struct rtattr *attrs[IFLA_MAX + 1];
-  struct rtnl_link_stats *stats;
-
-  const char *dev;
-
-  if (nmh->nlmsg_type != RTM_NEWLINK)
-  {
-    ERROR ("netlink plugin: link_filter: Don't know how to handle type %i.",
-       nmh->nlmsg_type);
-    return (-1);
-  }
-
-  msg = NLMSG_DATA (nmh);
-
-  msg_len = nmh->nlmsg_len - NLMSG_LENGTH(sizeof (struct ifinfomsg));
-  if (msg_len < 0)
-  {
-    ERROR ("netlink plugin: link_filter: msg_len = %i < 0;", msg_len);
-    return (-1);
-  }
-
-  memset (attrs, '\0', sizeof (attrs));
-  if (parse_rtattr (attrs, IFLA_MAX, IFLA_RTA (msg), msg_len) != 0)
-  {
-    ERROR ("netlink plugin: link_filter: parse_rtattr failed.");
-    return (-1);
-  }
-
-  if (attrs[IFLA_IFNAME] == NULL)
-  {
-    ERROR ("netlink plugin: link_filter: attrs[IFLA_IFNAME] == NULL");
-    return (-1);
-  }
-  dev = RTA_DATA (attrs[IFLA_IFNAME]);
-
   /* Update the `iflist'. It's used to know which interfaces exist and query
    * them later for qdiscs and classes. */
   if ((msg->ifi_index >= 0) && ((size_t) msg->ifi_index >= iflist_len))
@@ -253,12 +215,12 @@ static int link_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
     temp = (char **) realloc (iflist, (msg->ifi_index + 1) * sizeof (char *));
     if (temp == NULL)
     {
-      ERROR ("netlink plugin: link_filter: realloc failed.");
+      ERROR ("netlink plugin: update_iflist: realloc failed.");
       return (-1);
     }
 
     memset (temp + iflist_len, '\0',
-       (msg->ifi_index + 1 - iflist_len) * sizeof (char *));
+        (msg->ifi_index + 1 - iflist_len) * sizeof (char *));
     iflist = temp;
     iflist_len = msg->ifi_index + 1;
   }
@@ -269,12 +231,12 @@ static int link_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
     iflist[msg->ifi_index] = strdup (dev);
   }
 
-  if (attrs[IFLA_STATS] == NULL)
-  {
-    DEBUG ("netlink plugin: link_filter: No statistics for interface %s.", dev);
-    return (0);
-  }
-  stats = RTA_DATA (attrs[IFLA_STATS]);
+  return (0);
+} /* int update_iflist */
+
+static void check_ignorelist_and_submit (const char *dev,
+    struct rtnl_link_stats *stats)
+{
 
   if (check_ignorelist (dev, "interface", NULL) == 0)
   {
@@ -311,158 +273,261 @@ static int link_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
     DEBUG ("netlink plugin: Ignoring %s/if_detail.", dev);
   }
 
-  return (0);
-} /* int link_filter */
+} /* void check_ignorelist_and_submit */
+
+static int link_filter_cb (const struct nlmsghdr *nlh,
+    void *args __attribute__((unused)))
+{
+  struct ifinfomsg *ifm = mnl_nlmsg_get_payload (nlh);
+  struct nlattr *attr;
+  struct rtnl_link_stats *stats = NULL;
+  const char *dev = NULL;
+
+  if (nlh->nlmsg_type != RTM_NEWLINK)
+  {
+    ERROR ("netlink plugin: link_filter_cb: Don't know how to handle type %i.",
+        nlh->nlmsg_type);
+    return MNL_CB_ERROR;
+  }
+
+  /* Scan attribute list for device name. */
+  mnl_attr_for_each (attr, nlh, sizeof (*ifm))
+  {
+    if (mnl_attr_get_type (attr) != IFLA_IFNAME)
+      continue;
+
+    if (mnl_attr_validate (attr, MNL_TYPE_STRING) < 0)
+    {
+      ERROR ("netlink plugin: link_filter_cb: IFLA_IFNAME mnl_attr_validate failed.");
+      return MNL_CB_ERROR;
+    }
+
+    dev = mnl_attr_get_str (attr);
+    if (update_iflist (ifm, dev) < 0)
+      return MNL_CB_ERROR;
+    break;
+  }
+
+  if (dev == NULL)
+  {
+    ERROR ("netlink plugin: link_filter_cb: dev == NULL");
+    return MNL_CB_ERROR;
+  }
+
+  mnl_attr_for_each (attr, nlh, sizeof (*ifm))
+  {
+    if (mnl_attr_get_type (attr) != IFLA_STATS)
+      continue;
+
+    if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (*stats)) < 0)
+    {
+      ERROR ("netlink plugin: link_filter_cb: IFLA_STATS mnl_attr_validate2 failed.");
+      return MNL_CB_ERROR;
+    }
+    stats = mnl_attr_get_payload (attr);
+
+    check_ignorelist_and_submit (dev, stats);
+    break;
+  }
+
+  if (stats == NULL)
+  {
+    DEBUG ("netlink plugin: link_filter: No statistics for interface %s.", dev);
+    return MNL_CB_OK;
+  }
 
-static int qos_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
-    struct nlmsghdr *nmh, void *args)
+  return MNL_CB_OK;
+} /* int link_filter_cb */
+
+#if HAVE_TCA_STATS2
+static int qos_attr_cb (const struct nlattr *attr, void *data)
 {
-  struct tcmsg *msg;
-  int msg_len;
-  struct rtattr *attrs[TCA_MAX + 1];
+  struct gnet_stats_basic **bs = (struct gnet_stats_basic **)data;
+
+  /* skip unsupported attribute in user-space */
+  if (mnl_attr_type_valid (attr, TCA_STATS_MAX) < 0)
+    return MNL_CB_OK;
+
+  if (mnl_attr_get_type (attr) == TCA_STATS_BASIC)
+  {
+    if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (**bs)) < 0)
+    {
+      ERROR ("netlink plugin: qos_attr_cb: TCA_STATS_BASIC mnl_attr_validate2 failed.");
+      return MNL_CB_ERROR;
+    }
+    *bs = mnl_attr_get_payload (attr);
+    return MNL_CB_STOP;
+  }
+
+  return MNL_CB_OK;
+} /* qos_attr_cb */
+#endif
+
+static int qos_filter_cb (const struct nlmsghdr *nlh, void *args)
+{
+  struct tcmsg *tm = mnl_nlmsg_get_payload (nlh);
+  struct nlattr *attr;
 
   int wanted_ifindex = *((int *) args);
 
   const char *dev;
+  const char *kind = NULL;
 
   /* char *type_instance; */
   char *tc_type;
   char tc_inst[DATA_MAX_NAME_LEN];
 
-  if (nmh->nlmsg_type == RTM_NEWQDISC)
+  _Bool stats_submitted = 0;
+
+  if (nlh->nlmsg_type == RTM_NEWQDISC)
     tc_type = "qdisc";
-  else if (nmh->nlmsg_type == RTM_NEWTCLASS)
+  else if (nlh->nlmsg_type == RTM_NEWTCLASS)
     tc_type = "class";
-  else if (nmh->nlmsg_type == RTM_NEWTFILTER)
+  else if (nlh->nlmsg_type == RTM_NEWTFILTER)
     tc_type = "filter";
   else
   {
-    ERROR ("netlink plugin: qos_filter: Don't know how to handle type %i.",
-       nmh->nlmsg_type);
-    return (-1);
-  }
-
-  msg = NLMSG_DATA (nmh);
-
-  msg_len = nmh->nlmsg_len - sizeof (struct tcmsg);
-  if (msg_len < 0)
-  {
-    ERROR ("netlink plugin: qos_filter: msg_len = %i < 0;", msg_len);
-    return (-1);
+    ERROR ("netlink plugin: qos_filter_cb: Don't know how to handle type %i.",
+        nlh->nlmsg_type);
+    return MNL_CB_ERROR;
   }
 
-  if (msg->tcm_ifindex != wanted_ifindex)
+  if (tm->tcm_ifindex != wanted_ifindex)
   {
-    DEBUG ("netlink plugin: qos_filter: Got %s for interface #%i, "
-       "but expected #%i.",
-       tc_type, msg->tcm_ifindex, wanted_ifindex);
-    return (0);
+    DEBUG ("netlink plugin: qos_filter_cb: Got %s for interface #%i, "
+        "but expected #%i.",
+        tc_type, tm->tcm_ifindex, wanted_ifindex);
+    return MNL_CB_OK;
   }
 
-  if ((msg->tcm_ifindex >= 0)
-      && ((size_t) msg->tcm_ifindex >= iflist_len))
+  if ((tm->tcm_ifindex >= 0)
+      && ((size_t) tm->tcm_ifindex >= iflist_len))
   {
-    ERROR ("netlink plugin: qos_filter: msg->tcm_ifindex = %i "
-       ">= iflist_len = %zu",
-       msg->tcm_ifindex, iflist_len);
-    return (-1);
+    ERROR ("netlink plugin: qos_filter_cb: tm->tcm_ifindex = %i "
+        ">= iflist_len = %zu",
+        tm->tcm_ifindex, iflist_len);
+    return MNL_CB_ERROR;
   }
 
-  dev = iflist[msg->tcm_ifindex];
+  dev = iflist[tm->tcm_ifindex];
   if (dev == NULL)
   {
-    ERROR ("netlink plugin: qos_filter: iflist[%i] == NULL",
-       msg->tcm_ifindex);
-    return (-1);
+    ERROR ("netlink plugin: qos_filter_cb: iflist[%i] == NULL",
+        tm->tcm_ifindex);
+    return MNL_CB_ERROR;
   }
 
-  memset (attrs, '\0', sizeof (attrs));
-  if (parse_rtattr (attrs, TCA_MAX, TCA_RTA (msg), msg_len) != 0)
+  mnl_attr_for_each (attr, nlh, sizeof (*tm))
   {
-    ERROR ("netlink plugin: qos_filter: parse_rtattr failed.");
-    return (-1);
+    if (mnl_attr_get_type (attr) != TCA_KIND)
+      continue;
+
+    if (mnl_attr_validate (attr, MNL_TYPE_STRING) < 0)
+    {
+      ERROR ("netlink plugin: qos_filter_cb: TCA_KIND mnl_attr_validate failed.");
+      return MNL_CB_ERROR;
+    }
+
+    kind = mnl_attr_get_str (attr);
+    break;
   }
 
-  if (attrs[TCA_KIND] == NULL)
+  if (kind == NULL)
   {
-    ERROR ("netlink plugin: qos_filter: attrs[TCA_KIND] == NULL");
+    ERROR ("netlink plugin: qos_filter_cb: kind == NULL");
     return (-1);
   }
 
-  { /* The the ID */
+  { /* The ID */
     uint32_t numberic_id;
 
-    numberic_id = msg->tcm_handle;
+    numberic_id = tm->tcm_handle;
     if (strcmp (tc_type, "filter") == 0)
-      numberic_id = msg->tcm_parent;
+      numberic_id = tm->tcm_parent;
 
     ssnprintf (tc_inst, sizeof (tc_inst), "%s-%x:%x",
-       (const char *) RTA_DATA (attrs[TCA_KIND]),
-       numberic_id >> 16,
-       numberic_id & 0x0000FFFF);
+        kind,
+        numberic_id >> 16,
+        numberic_id & 0x0000FFFF);
   }
 
-  DEBUG ("netlink plugin: qos_filter: got %s for %s (%i).",
-      tc_type, dev, msg->tcm_ifindex);
-  
+  DEBUG ("netlink plugin: qos_filter_cb: got %s for %s (%i).",
+      tc_type, dev, tm->tcm_ifindex);
+
   if (check_ignorelist (dev, tc_type, tc_inst))
-    return (0);
+    return MNL_CB_OK;
 
 #if HAVE_TCA_STATS2
-  if (attrs[TCA_STATS2])
+  mnl_attr_for_each (attr, nlh, sizeof (*tm))
   {
-    struct rtattr *attrs_stats[TCA_STATS_MAX + 1];
+    struct gnet_stats_basic *bs = NULL;
+
+    if (mnl_attr_get_type (attr) != TCA_STATS2)
+      continue;
 
-    memset (attrs_stats, '\0', sizeof (attrs_stats));
-    parse_rtattr_nested (attrs_stats, TCA_STATS_MAX, attrs[TCA_STATS2]);
+    if (mnl_attr_validate (attr, MNL_TYPE_NESTED) < 0)
+    {
+      ERROR ("netlink plugin: qos_filter_cb: TCA_STATS2 mnl_attr_validate failed.");
+      return MNL_CB_ERROR;
+    }
 
-    if (attrs_stats[TCA_STATS_BASIC])
+    mnl_attr_parse_nested (attr, qos_attr_cb, &bs);
+
+    if (bs != NULL)
     {
-      struct gnet_stats_basic bs;
       char type_instance[DATA_MAX_NAME_LEN];
 
-      ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
-         tc_type, tc_inst);
+      stats_submitted = 1;
 
-      memset (&bs, '\0', sizeof (bs));
-      memcpy (&bs, RTA_DATA (attrs_stats[TCA_STATS_BASIC]),
-         MIN (RTA_PAYLOAD (attrs_stats[TCA_STATS_BASIC]), sizeof(bs)));
+      ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
+          tc_type, tc_inst);
 
-      submit_one (dev, "ipt_bytes", type_instance, bs.bytes);
-      submit_one (dev, "ipt_packets", type_instance, bs.packets);
+      submit_one (dev, "ipt_bytes", type_instance, bs->bytes);
+      submit_one (dev, "ipt_packets", type_instance, bs->packets);
     }
+
+    break;
   }
 #endif /* TCA_STATS2 */
-#if HAVE_TCA_STATS && HAVE_TCA_STATS2
-  else
-#endif
+
 #if HAVE_TCA_STATS
-  if (attrs[TCA_STATS] != NULL)
+  mnl_attr_for_each (attr, nlh, sizeof (*tm))
   {
-    struct tc_stats ts;
-    char type_instance[DATA_MAX_NAME_LEN];
+    struct tc_stats *ts = NULL;
+
+    if (mnl_attr_get_type (attr) != TCA_STATS)
+      continue;
+
+    if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (*ts)) < 0)
+    {
+      ERROR ("netlink plugin: qos_filter_cb: TCA_STATS mnl_attr_validate2 failed.");
+      return MNL_CB_ERROR;
+    }
+    ts = mnl_attr_get_payload (attr);
+
+    if (!stats_submitted && ts != NULL)
+    {
+      char type_instance[DATA_MAX_NAME_LEN];
 
-    ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
-       tc_type, tc_inst);
+      ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
+          tc_type, tc_inst);
 
-    memset(&ts, '\0', sizeof (ts));
-    memcpy(&ts, RTA_DATA (attrs[TCA_STATS]),
-       MIN (RTA_PAYLOAD (attrs[TCA_STATS]), sizeof (ts)));
+      submit_one (dev, "ipt_bytes", type_instance, ts->bytes);
+      submit_one (dev, "ipt_packets", type_instance, ts->packets);
+    }
 
-    submit_one (dev, "ipt_bytes", type_instance, ts.bytes);
-    submit_one (dev, "ipt_packets", type_instance, ts.packets);
+    break;
   }
+
 #endif /* TCA_STATS */
-#if HAVE_TCA_STATS || HAVE_TCA_STATS2
-  else
+
+#if !(HAVE_TCA_STATS && HAVE_TCA_STATS2)
+  DEBUG ("netlink plugin: qos_filter_cb: Have neither TCA_STATS2 nor "
+      "TCA_STATS.");
 #endif
-  {
-    DEBUG ("netlink plugin: qos_filter: Have neither TCA_STATS2 nor "
-       "TCA_STATS.");
-  }
 
-  return (0);
-} /* int qos_filter */
+  return MNL_CB_OK;
+} /* int qos_filter_cb */
 
 static int ir_config (const char *key, const char *value)
 {
@@ -488,14 +553,14 @@ static int ir_config (const char *key, const char *value)
     if (fields_num != 1)
     {
       ERROR ("netlink plugin: Invalid number of fields for option "
-         "`%s'. Got %i, expected 1.", key, fields_num);
+          "`%s'. Got %i, expected 1.", key, fields_num);
       status = -1;
     }
     else
     {
       add_ignorelist (fields[0], "interface", NULL);
       if (strcasecmp (key, "VerboseInterface") == 0)
-       add_ignorelist (fields[0], "if_detail", NULL);
+        add_ignorelist (fields[0], "if_detail", NULL);
       status = 0;
     }
   }
@@ -506,13 +571,13 @@ static int ir_config (const char *key, const char *value)
     if ((fields_num < 1) || (fields_num > 2))
     {
       ERROR ("netlink plugin: Invalid number of fields for option "
-         "`%s'. Got %i, expected 1 or 2.", key, fields_num);
+          "`%s'. Got %i, expected 1 or 2.", key, fields_num);
       return (-1);
     }
     else
     {
       add_ignorelist (fields[0], key,
-         (fields_num == 2) ? fields[1] : NULL);
+          (fields_num == 2) ? fields[1] : NULL);
       status = 0;
     }
   }
@@ -521,15 +586,15 @@ static int ir_config (const char *key, const char *value)
     if (fields_num != 1)
     {
       ERROR ("netlink plugin: Invalid number of fields for option "
-         "`IgnoreSelected'. Got %i, expected 1.", fields_num);
+          "`IgnoreSelected'. Got %i, expected 1.", fields_num);
       status = -1;
     }
     else
     {
       if (IS_TRUE (fields[0]))
-       ir_ignorelist_invert = 0;
+        ir_ignorelist_invert = 0;
       else
-       ir_ignorelist_invert = 1;
+        ir_ignorelist_invert = 1;
       status = 0;
     }
   }
@@ -541,11 +606,16 @@ static int ir_config (const char *key, const char *value)
 
 static int ir_init (void)
 {
-  memset (&rth, '\0', sizeof (rth));
+  nl = mnl_socket_open (NETLINK_ROUTE);
+  if (nl == NULL)
+  {
+    ERROR ("netlink plugin: ir_init: mnl_socket_open failed.");
+    return (-1);
+  }
 
-  if (rtnl_open (&rth, 0) != 0)
+  if (mnl_socket_bind (nl, 0, MNL_SOCKET_AUTOPID) < 0)
   {
-    ERROR ("netlink plugin: ir_init: rtnl_open failed.");
+    ERROR ("netlink plugin: ir_init: mnl_socket_bind failed.");
     return (-1);
   }
 
@@ -554,35 +624,51 @@ static int ir_init (void)
 
 static int ir_read (void)
 {
-  struct tcmsg tm;
-  int ifindex;
+  char buf[MNL_SOCKET_BUFFER_SIZE];
+  struct nlmsghdr *nlh;
+  struct rtgenmsg *rt;
+  int ret;
+  unsigned int seq, portid;
+
+  size_t ifindex;
 
   static const int type_id[] = { RTM_GETQDISC, RTM_GETTCLASS, RTM_GETTFILTER };
   static const char *type_name[] = { "qdisc", "class", "filter" };
 
-  if (rtnl_wilddump_request (&rth, AF_UNSPEC, RTM_GETLINK) < 0)
+  portid = mnl_socket_get_portid (nl);
+
+  nlh = mnl_nlmsg_put_header (buf);
+  nlh->nlmsg_type = RTM_GETLINK;
+  nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+  nlh->nlmsg_seq = seq = time (NULL);
+  rt = mnl_nlmsg_put_extra_header (nlh, sizeof (*rt));
+  rt->rtgen_family = AF_PACKET;
+
+  if (mnl_socket_sendto (nl, nlh, nlh->nlmsg_len) < 0)
   {
     ERROR ("netlink plugin: ir_read: rtnl_wilddump_request failed.");
     return (-1);
   }
 
-#ifdef RTNL_DUMP_FILTER_FIVE_ARGS
-  if (rtnl_dump_filter (&rth, link_filter, /* arg1 = */ NULL,
-       NULL, NULL) != 0)
-#elif defined(RTNL_DUMP_FILTER_THREE_ARGS)
-  if (rtnl_dump_filter (&rth, link_filter, /* arg = */ NULL) != 0)
-#else
-#error "Failed to determine the number of arguments to 'rtnl_dump_filter'!"
-#endif
+  ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
+  while (ret > 0)
   {
-    ERROR ("netlink plugin: ir_read: rtnl_dump_filter failed.");
+    ret = mnl_cb_run (buf, ret, seq, portid, link_filter_cb, NULL);
+    if (ret <= MNL_CB_STOP)
+      break;
+    ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
+  }
+  if (ret < 0)
+  {
+    ERROR ("netlink plugin: ir_read: mnl_socket_recvfrom failed.");
     return (-1);
   }
 
-  /* `link_filter' will update `iflist' which is used here to iterate over all
-   * interfaces. */
-  for (ifindex = 0; (size_t) ifindex < iflist_len; ifindex++)
+  /* `link_filter_cb' will update `iflist' which is used here to iterate
+   * over all interfaces. */
+  for (ifindex = 1; ifindex < iflist_len; ifindex++)
   {
+    struct tcmsg *tm;
     size_t type_index;
 
     if (iflist[ifindex] == NULL)
@@ -592,36 +678,42 @@ static int ir_read (void)
     {
       if (check_ignorelist (iflist[ifindex], type_name[type_index], NULL))
       {
-       DEBUG ("netlink plugin: ir_read: check_ignorelist (%s, %s, (nil)) "
-           "== TRUE", iflist[ifindex], type_name[type_index]);
-       continue;
+        DEBUG ("netlink plugin: ir_read: check_ignorelist (%s, %s, (nil)) "
+            "== TRUE", iflist[ifindex], type_name[type_index]);
+        continue;
       }
 
-      DEBUG ("netlink plugin: ir_read: querying %s from %s (%i).",
-         type_name[type_index], iflist[ifindex], ifindex);
+      DEBUG ("netlink plugin: ir_read: querying %s from %s (%lu).",
+          type_name[type_index], iflist[ifindex], ifindex);
 
-      memset (&tm, '\0', sizeof (tm));
-      tm.tcm_family = AF_UNSPEC;
-      tm.tcm_ifindex = ifindex;
+      nlh = mnl_nlmsg_put_header (buf);
+      nlh->nlmsg_type = type_id[type_index];
+      nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+      nlh->nlmsg_seq = seq = time (NULL);
+      tm = mnl_nlmsg_put_extra_header (nlh, sizeof (*tm));
+      tm->tcm_family = AF_PACKET;
+      tm->tcm_ifindex = ifindex;
 
-      if (rtnl_dump_request (&rth, type_id[type_index], &tm, sizeof (tm)) < 0)
+      if (mnl_socket_sendto (nl, nlh, nlh->nlmsg_len) < 0)
       {
-       ERROR ("netlink plugin: ir_read: rtnl_dump_request failed.");
-       continue;
+        ERROR ("netlink plugin: ir_read: mnl_socket_sendto failed.");
+        continue;
       }
 
-#ifdef RTNL_DUMP_FILTER_FIVE_ARGS
-      if (rtnl_dump_filter (&rth, qos_filter, (void *) &ifindex,
-           NULL, NULL) != 0)
-#elif defined(RTNL_DUMP_FILTER_THREE_ARGS)
-      if (rtnl_dump_filter (&rth, qos_filter, /* arg = */ &ifindex) != 0)
-#else
-#error "Failed to determine the number of arguments to 'rtnl_dump_filter'!"
-#endif
+      ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
+      while (ret > 0)
       {
-       ERROR ("netlink plugin: ir_read: rtnl_dump_filter failed.");
-       continue;
+        ret = mnl_cb_run (buf, ret, seq, portid, qos_filter_cb, &ifindex);
+        if (ret <= MNL_CB_STOP)
+          break;
+        ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
       }
+      if (ret < 0)
+      {
+        ERROR ("netlink plugin: ir_read:mnl_socket_recvfrom failed.");
+        continue;
+      }
+
     } /* for (type_index) */
   } /* for (if_index) */
 
@@ -630,12 +722,12 @@ static int ir_read (void)
 
 static int ir_shutdown (void)
 {
-  if ((rth.fd != 0) || (rth.seq != 0) || (rth.dump != 0))
+  if (nl)
   {
-    rtnl_close(&rth);
-    memset (&rth, '\0', sizeof (rth));
+    mnl_socket_close (nl);
+    nl = NULL;
   }
-  
+
   return (0);
 } /* int ir_shutdown */
 
index 8083390..2f28329 100644 (file)
@@ -198,7 +198,7 @@ static int srrd_update (char *filename, char *template,
 } /* int srrd_update */
 #endif /* !HAVE_THREADSAFE_LIBRRD */
 
-static int value_list_to_string (char *buffer, int buffer_len,
+static int value_list_to_string_multiple (char *buffer, int buffer_len,
                const data_set_t *ds, const value_list_t *vl)
 {
        int offset;
@@ -242,6 +242,45 @@ static int value_list_to_string (char *buffer, int buffer_len,
        } /* for ds->ds_num */
 
        return (0);
+} /* int value_list_to_string_multiple */
+
+static int value_list_to_string (char *buffer, int buffer_len,
+               const data_set_t *ds, const value_list_t *vl)
+{
+       int status;
+       time_t tt;
+
+       if (ds->ds_num != 1)
+               return (value_list_to_string_multiple (buffer, buffer_len,
+                                       ds, vl));
+
+       tt = CDTIME_T_TO_TIME_T (vl->time);
+       switch (ds->ds[0].type)
+       {
+               case DS_TYPE_DERIVE:
+                       status = ssnprintf (buffer, buffer_len, "%u:%"PRIi64,
+                               (unsigned) tt, vl->values[0].derive);
+                       break;
+               case DS_TYPE_GAUGE:
+                       status = ssnprintf (buffer, buffer_len, "%u:%lf",
+                               (unsigned) tt, vl->values[0].gauge);
+                       break;
+               case DS_TYPE_COUNTER:
+                       status = ssnprintf (buffer, buffer_len, "%u:%llu",
+                               (unsigned) tt, vl->values[0].counter);
+                       break;
+               case DS_TYPE_ABSOLUTE:
+                       status = ssnprintf (buffer, buffer_len, "%u:%"PRIu64,
+                               (unsigned) tt, vl->values[0].absolute);
+                       break;
+               default:
+                       return (EINVAL);
+       }
+
+       if ((status < 1) || (status >= buffer_len))
+               return (ENOMEM);
+
+       return (0);
 } /* int value_list_to_string */
 
 static int value_list_to_filename (char *buffer, size_t buffer_size,
diff --git a/src/sigrok.c b/src/sigrok.c
new file mode 100644 (file)
index 0000000..ba3e406
--- /dev/null
@@ -0,0 +1,399 @@
+/*
+ * collectd - src/sigrok.c
+ * Copyright (C) 2013 Bert Vermeulen <bert@biot.com>
+ *
+ * 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, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <pthread.h>
+
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+
+/* Minimum interval between dispatches coming from this plugin. The RRD
+ * plugin, at least, complains when written to with sub-second intervals.*/
+#define DEFAULT_MIN_DISPATCH_INTERVAL TIME_T_TO_CDTIME_T(0)
+
+static pthread_t sr_thread;
+static int sr_thread_running = FALSE;
+GSList *config_devices;
+static int num_devices;
+static int loglevel = SR_LOG_WARN;
+static struct sr_context *sr_ctx;
+
+struct config_device {
+       char *name;
+       char *driver;
+       char *conn;
+       char *serialcomm;
+       struct sr_dev_inst *sdi;
+       cdtime_t min_dispatch_interval;
+       cdtime_t last_dispatch;
+};
+
+
+static int sigrok_log_callback(void*cb_data __attribute__((unused)),
+               int msg_loglevel, const char *format, va_list args)
+{
+       char s[512];
+
+       if (msg_loglevel <= loglevel) {
+               vsnprintf(s, 512, format, args);
+               plugin_log(LOG_INFO, "sigrok plugin: %s", s);
+       }
+
+       return 0;
+}
+
+static int sigrok_config_device(oconfig_item_t *ci)
+{
+       struct config_device *cfdev;
+       int i;
+
+       if (!(cfdev = malloc(sizeof(struct config_device)))) {
+               ERROR("sigrok plugin: malloc() failed.");
+               return -1;
+       }
+       memset(cfdev, 0, sizeof(*cfdev));
+       if (cf_util_get_string(ci, &cfdev->name)) {
+               free(cfdev);
+               WARNING("sigrok plugin: Invalid device name.");
+               return -1;
+       }
+       cfdev->min_dispatch_interval = DEFAULT_MIN_DISPATCH_INTERVAL;
+
+       for (i = 0; i < ci->children_num; i++) {
+               oconfig_item_t *item = ci->children + i;
+               if (!strcasecmp(item->key, "driver"))
+                       cf_util_get_string(item, &cfdev->driver);
+               else if (!strcasecmp(item->key, "conn"))
+                       cf_util_get_string(item, &cfdev->conn);
+               else if (!strcasecmp(item->key, "serialcomm"))
+                       cf_util_get_string(item, &cfdev->serialcomm);
+               else if (!strcasecmp(item->key, "minimuminterval"))
+                       cf_util_get_cdtime(item, &cfdev->min_dispatch_interval);
+               else
+                       WARNING("sigrok plugin: Invalid keyword \"%s\".",
+                                       item->key);
+       }
+
+       config_devices = g_slist_append(config_devices, cfdev);
+
+       return 0;
+}
+
+static int sigrok_config(oconfig_item_t *ci)
+{
+       int i;
+
+       for (i = 0; i < ci->children_num; i++) {
+               oconfig_item_t *item = ci->children + i;
+               if (strcasecmp("LogLevel", item->key) == 0) {
+                       int status;
+                       int tmp = -1;
+
+                       status = cf_util_get_int (item, &tmp);
+                       if (status != 0)
+                               continue;
+                       else if ((tmp < 0) || (tmp > 5)) {
+                               ERROR ("sigrok plugin: The \"LogLevel\" "
+                                               "configuration option expects "
+                                               "an integer between 0 and 5 "
+                                               "(inclusive); you provided %i.",
+                                               tmp);
+                               continue;
+                       }
+                       loglevel = tmp;
+               } else if (!strcasecmp(item->key, "Device"))
+                       sigrok_config_device(item);
+               else
+                       WARNING("sigrok plugin: Invalid keyword \"%s\".",
+                                       item->key);
+       }
+
+       return 0;
+}
+
+static char *sigrok_value_type(const struct sr_datafeed_analog *analog)
+{
+       char *s;
+
+       if (analog->mq == SR_MQ_VOLTAGE)
+               s = "voltage";
+       else if (analog->mq == SR_MQ_CURRENT)
+               s = "current";
+       else if (analog->mq == SR_MQ_FREQUENCY)
+               s = "frequency";
+       else if (analog->mq == SR_MQ_POWER)
+               s = "power";
+       else if (analog->mq == SR_MQ_TEMPERATURE)
+               s = "temperature";
+       else if (analog->mq == SR_MQ_RELATIVE_HUMIDITY)
+               s = "humidity";
+       else if (analog->mq == SR_MQ_SOUND_PRESSURE_LEVEL)
+               s = "spl";
+       else
+               s = "gauge";
+
+       return s;
+}
+
+static void sigrok_feed_callback(const struct sr_dev_inst *sdi,
+               const struct sr_datafeed_packet *packet, void *cb_data)
+{
+       const struct sr_datafeed_analog *analog;
+       struct config_device *cfdev;
+       GSList *l;
+       value_t value;
+       value_list_t vl = VALUE_LIST_INIT;
+
+       /* Find this device's configuration. */
+       cfdev = NULL;
+       for (l = config_devices; l; l = l->next) {
+               cfdev = l->data;
+               if (cfdev->sdi == sdi) {
+                       /* Found it. */
+                       break;
+               }
+               cfdev = NULL;
+       }
+
+       if (!cfdev) {
+               ERROR("sigrok plugin: Received data from driver \"%s\" but "
+                               "can't find a configuration / device matching "
+                               "it.", sdi->driver->name);
+               return;
+       }
+
+       if (packet->type == SR_DF_END) {
+               /* TODO: try to restart acquisition after a delay? */
+               WARNING("sigrok plugin: acquisition for \"%s\" ended.",
+                               cfdev->name);
+               return;
+       }
+
+       if (packet->type != SR_DF_ANALOG)
+               return;
+
+       if ((cfdev->min_dispatch_interval != 0)
+                       && ((cdtime() - cfdev->last_dispatch)
+                               < cfdev->min_dispatch_interval))
+               return;
+
+       /* Ignore all but the first sample on the first probe. */
+       analog = packet->payload;
+       value.gauge = analog->data[0];
+       vl.values = &value;
+       vl.values_len = 1;
+       sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+       sstrncpy(vl.plugin, "sigrok", sizeof(vl.plugin));
+       ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance),
+                       "%s", cfdev->name);
+       sstrncpy(vl.type, sigrok_value_type(analog), sizeof(vl.type));
+
+       plugin_dispatch_values(&vl);
+       cfdev->last_dispatch = cdtime();
+}
+
+static void sigrok_free_drvopts(struct sr_config *src)
+{
+       g_variant_unref(src->data);
+       g_free(src);
+}
+
+static int sigrok_init_driver(struct config_device *cfdev,
+               struct sr_dev_driver *drv)
+{
+       struct sr_config *src;
+       GSList *devlist, *drvopts;
+       char hwident[512];
+
+       if (sr_driver_init(sr_ctx, drv) != SR_OK)
+               /* Error was logged by libsigrok. */
+               return -1;
+
+       drvopts = NULL;
+       if (cfdev->conn) {
+               if (!(src = malloc(sizeof(struct sr_config))))
+                       return -1;
+               src->key = SR_CONF_CONN;
+               src->data = g_variant_new_string(cfdev->conn);
+               drvopts = g_slist_append(drvopts, src);
+       }
+       if (cfdev->serialcomm) {
+               if (!(src = malloc(sizeof(struct sr_config))))
+                       return -1;
+               src->key = SR_CONF_SERIALCOMM;
+               src->data = g_variant_new_string(cfdev->serialcomm);
+               drvopts = g_slist_append(drvopts, src);
+       }
+       devlist = sr_driver_scan(drv, drvopts);
+       g_slist_free_full(drvopts, (GDestroyNotify)sigrok_free_drvopts);
+       if (!devlist) {
+               /* Not an error, but the user should know about it. */
+               WARNING("sigrok plugin: No device found for \"%s\".",
+                               cfdev->name);
+               return 0;
+       }
+
+       if (g_slist_length(devlist) > 1) {
+               INFO("sigrok plugin: %d sigrok devices for device entry "
+                               "\"%s\": must be 1.",
+                               g_slist_length(devlist), cfdev->name);
+               return -1;
+       }
+       cfdev->sdi = devlist->data;
+       g_slist_free(devlist);
+       ssnprintf(hwident, sizeof(hwident), "%s %s %s",
+                       cfdev->sdi->vendor ? cfdev->sdi->vendor : "",
+                       cfdev->sdi->model ? cfdev->sdi->model : "",
+                       cfdev->sdi->version ? cfdev->sdi->version : "");
+       INFO("sigrok plugin: Device \"%s\" is a %s", cfdev->name, hwident);
+
+       if (sr_dev_open(cfdev->sdi) != SR_OK)
+               return -1;
+
+       if (sr_session_dev_add(cfdev->sdi) != SR_OK)
+               return -1;
+
+       return 1;
+}
+
+static void *sigrok_read_thread(void *arg __attribute__((unused)))
+{
+       struct sr_dev_driver *drv, **drvlist;
+       GSList *l;
+       struct config_device *cfdev;
+       int ret, i;
+
+       sr_log_callback_set(sigrok_log_callback, NULL);
+       sr_log_loglevel_set(loglevel);
+
+       if ((ret = sr_init(&sr_ctx)) != SR_OK) {
+               ERROR("sigrok plugin: Failed to initialize libsigrok: %s.",
+                               sr_strerror(ret));
+               return NULL;
+       }
+
+       if (!sr_session_new())
+               return NULL;
+
+       num_devices = 0;
+       drvlist = sr_driver_list();
+       for (l = config_devices; l; l = l->next) {
+               cfdev = l->data;
+               drv = NULL;
+               for (i = 0; drvlist[i]; i++) {
+                       if (!strcmp(drvlist[i]->name, cfdev->driver)) {
+                               drv = drvlist[i];
+                               break;
+                       }
+               }
+               if (!drv) {
+                       ERROR("sigrok plugin: Unknown driver \"%s\".",
+                                       cfdev->driver);
+                       return NULL;
+               }
+
+               if ((ret = sigrok_init_driver(cfdev, drv)) < 0)
+                       /* Error was already logged. */
+                       return NULL;
+
+               num_devices += ret;
+       }
+
+       if (num_devices > 0) {
+               /* Do this only when we're sure there's hardware to talk to. */
+               if (sr_session_datafeed_callback_add(sigrok_feed_callback, NULL)
+                               != SR_OK)
+                       return NULL;
+
+               /* Start acquisition on all devices. */
+               if (sr_session_start() != SR_OK)
+                       return NULL;
+
+               /* Main loop, runs forever. */
+               sr_session_run();
+
+               sr_session_stop();
+               sr_session_dev_remove_all();
+       }
+
+       sr_session_destroy();
+
+       sr_exit(sr_ctx);
+
+       pthread_exit(NULL);
+       sr_thread_running = FALSE;
+
+       return NULL;
+}
+
+static int sigrok_init(void)
+{
+       int status;
+
+       if (sr_thread_running) {
+               ERROR("sigrok plugin: Thread already running.");
+               return -1;
+       }
+
+       if ((status = plugin_thread_create(&sr_thread, NULL, sigrok_read_thread,
+                       NULL)) != 0) {
+               ERROR("sigrok plugin: Failed to create thread: %s.",
+                               strerror(status));
+               return -1;
+       }
+       sr_thread_running = TRUE;
+
+       return 0;
+}
+
+static int sigrok_shutdown(void)
+{
+       struct config_device *cfdev;
+       GSList *l;
+
+       if (sr_thread_running) {
+               pthread_cancel(sr_thread);
+               pthread_join(sr_thread, NULL);
+       }
+
+       for (l = config_devices; l; l = l->next) {
+               cfdev = l->data;
+               free(cfdev->name);
+               free(cfdev->driver);
+               free(cfdev->conn);
+               free(cfdev->serialcomm);
+               free(cfdev);
+       }
+       g_slist_free(config_devices);
+
+       return 0;
+}
+
+void module_register(void)
+{
+       plugin_register_complex_config("sigrok", sigrok_config);
+       plugin_register_init("sigrok", sigrok_init);
+       plugin_register_shutdown("sigrok", sigrok_shutdown);
+}
diff --git a/src/statsd.c b/src/statsd.c
new file mode 100644 (file)
index 0000000..72a7779
--- /dev/null
@@ -0,0 +1,908 @@
+/**
+ * collectd - src/statsd.c
+ *
+ * Copyright (C) 2013       Florian octo Forster
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ */
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "configfile.h"
+#include "utils_avltree.h"
+#include "utils_complain.h"
+#include "utils_latency.h"
+
+#include <pthread.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <poll.h>
+
+#ifndef STATSD_DEFAULT_NODE
+# define STATSD_DEFAULT_NODE NULL
+#endif
+
+#ifndef STATSD_DEFAULT_SERVICE
+# define STATSD_DEFAULT_SERVICE "8125"
+#endif
+
+enum metric_type_e
+{
+  STATSD_COUNTER,
+  STATSD_TIMER,
+  STATSD_GAUGE,
+  STATSD_SET
+};
+typedef enum metric_type_e metric_type_t;
+
+struct statsd_metric_s
+{
+  metric_type_t type;
+  double value;
+  latency_counter_t *latency;
+  c_avl_tree_t *set;
+  unsigned long updates_num;
+};
+typedef struct statsd_metric_s statsd_metric_t;
+
+static c_avl_tree_t   *metrics_tree = NULL;
+static pthread_mutex_t metrics_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static pthread_t network_thread;
+static _Bool     network_thread_running = 0;
+static _Bool     network_thread_shutdown = 0;
+
+static char *conf_node = NULL;
+static char *conf_service = NULL;
+
+static _Bool conf_delete_counters = 0;
+static _Bool conf_delete_timers   = 0;
+static _Bool conf_delete_gauges   = 0;
+static _Bool conf_delete_sets     = 0;
+
+static double *conf_timer_percentile = NULL;
+static size_t  conf_timer_percentile_num = 0;
+
+static _Bool conf_timer_lower     = 0;
+static _Bool conf_timer_upper     = 0;
+static _Bool conf_timer_sum       = 0;
+static _Bool conf_timer_count     = 0;
+
+/* Must hold metrics_lock when calling this function. */
+static statsd_metric_t *statsd_metric_lookup_unsafe (char const *name, /* {{{ */
+    metric_type_t type)
+{
+  char key[DATA_MAX_NAME_LEN + 2];
+  char *key_copy;
+  statsd_metric_t *metric;
+  int status;
+
+  switch (type)
+  {
+    case STATSD_COUNTER: key[0] = 'c'; break;
+    case STATSD_TIMER:   key[0] = 't'; break;
+    case STATSD_GAUGE:   key[0] = 'g'; break;
+    case STATSD_SET:     key[0] = 's'; break;
+    default: return (NULL);
+  }
+
+  key[1] = ':';
+  sstrncpy (&key[2], name, sizeof (key) - 2);
+
+  status = c_avl_get (metrics_tree, key, (void *) &metric);
+  if (status == 0)
+    return (metric);
+
+  key_copy = strdup (key);
+  if (key_copy == NULL)
+  {
+    ERROR ("statsd plugin: strdup failed.");
+    return (NULL);
+  }
+
+  metric = malloc (sizeof (*metric));
+  if (metric == NULL)
+  {
+    ERROR ("statsd plugin: malloc failed.");
+    sfree (key_copy);
+    return (NULL);
+  }
+  memset (metric, 0, sizeof (*metric));
+
+  metric->type = type;
+  metric->latency = NULL;
+  metric->set = NULL;
+
+  status = c_avl_insert (metrics_tree, key_copy, metric);
+  if (status != 0)
+  {
+    ERROR ("statsd plugin: c_avl_insert failed.");
+    sfree (key_copy);
+    sfree (metric);
+    return (NULL);
+  }
+
+  return (metric);
+} /* }}} statsd_metric_lookup_unsafe */
+
+static int statsd_metric_set (char const *name, double value, /* {{{ */
+    metric_type_t type)
+{
+  statsd_metric_t *metric;
+
+  pthread_mutex_lock (&metrics_lock);
+
+  metric = statsd_metric_lookup_unsafe (name, type);
+  if (metric == NULL)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    return (-1);
+  }
+
+  metric->value = value;
+  metric->updates_num++;
+
+  pthread_mutex_unlock (&metrics_lock);
+
+  return (0);
+} /* }}} int statsd_metric_set */
+
+static int statsd_metric_add (char const *name, double delta, /* {{{ */
+    metric_type_t type)
+{
+  statsd_metric_t *metric;
+
+  pthread_mutex_lock (&metrics_lock);
+
+  metric = statsd_metric_lookup_unsafe (name, type);
+  if (metric == NULL)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    return (-1);
+  }
+
+  metric->value += delta;
+  metric->updates_num++;
+
+  pthread_mutex_unlock (&metrics_lock);
+
+  return (0);
+} /* }}} int statsd_metric_add */
+
+static int statsd_parse_value (char const *str, value_t *ret_value) /* {{{ */
+{
+  char *endptr = NULL;
+
+  ret_value->gauge = (gauge_t) strtod (str, &endptr);
+  if ((str == endptr) || ((endptr != NULL) && (*endptr != 0)))
+    return (-1);
+
+  return (0);
+} /* }}} int statsd_parse_value */
+
+static int statsd_handle_counter (char const *name, /* {{{ */
+    char const *value_str,
+    char const *extra)
+{
+  value_t value;
+  value_t scale;
+  int status;
+
+  if ((extra != NULL) && (extra[0] != '@'))
+    return (-1);
+
+  scale.gauge = 1.0;
+  if (extra != NULL)
+  {
+    status = statsd_parse_value (extra + 1, &scale);
+    if (status != 0)
+      return (status);
+
+    if (!isfinite (scale.gauge) || (scale.gauge <= 0.0) || (scale.gauge > 1.0))
+      return (-1);
+  }
+
+  value.gauge = 1.0;
+  status = statsd_parse_value (value_str, &value);
+  if (status != 0)
+    return (status);
+
+  return (statsd_metric_add (name, (double) (value.gauge / scale.gauge),
+        STATSD_COUNTER));
+} /* }}} int statsd_handle_counter */
+
+static int statsd_handle_gauge (char const *name, /* {{{ */
+    char const *value_str)
+{
+  value_t value;
+  int status;
+
+  value.gauge = 0;
+  status = statsd_parse_value (value_str, &value);
+  if (status != 0)
+    return (status);
+
+  if ((value_str[0] == '+') || (value_str[0] == '-'))
+    return (statsd_metric_add (name, (double) value.gauge, STATSD_GAUGE));
+  else
+    return (statsd_metric_set (name, (double) value.gauge, STATSD_GAUGE));
+} /* }}} int statsd_handle_gauge */
+
+static int statsd_handle_timer (char const *name, /* {{{ */
+    char const *value_str)
+{
+  statsd_metric_t *metric;
+  value_t value_ms;
+  cdtime_t value;
+  int status;
+
+  value_ms.derive = 0;
+  status = statsd_parse_value (value_str, &value_ms);
+  if (status != 0)
+    return (status);
+
+  value = MS_TO_CDTIME_T (value_ms.gauge);
+
+  pthread_mutex_lock (&metrics_lock);
+
+  metric = statsd_metric_lookup_unsafe (name, STATSD_TIMER);
+  if (metric == NULL)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    return (-1);
+  }
+
+  if (metric->latency == NULL)
+    metric->latency = latency_counter_create ();
+  if (metric->latency == NULL)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    return (-1);
+  }
+
+  latency_counter_add (metric->latency, value);
+  metric->updates_num++;
+
+  pthread_mutex_unlock (&metrics_lock);
+  return (0);
+} /* }}} int statsd_handle_timer */
+
+static int statsd_handle_set (char const *name, /* {{{ */
+    char const *set_key_orig)
+{
+  statsd_metric_t *metric = NULL;
+  char *set_key;
+  int status;
+
+  pthread_mutex_lock (&metrics_lock);
+
+  metric = statsd_metric_lookup_unsafe (name, STATSD_SET);
+  if (metric == NULL)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    return (-1);
+  }
+
+  /* Make sure metric->set exists. */
+  if (metric->set == NULL)
+    metric->set = c_avl_create ((void *) strcmp);
+
+  if (metric->set == NULL)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    ERROR ("statsd plugin: c_avl_create failed.");
+    return (-1);
+  }
+
+  set_key = strdup (set_key_orig);
+  if (set_key == NULL)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    ERROR ("statsd plugin: strdup failed.");
+    return (-1);
+  }
+
+  status = c_avl_insert (metric->set, set_key, /* value = */ NULL);
+  if (status < 0)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    if (status < 0)
+      ERROR ("statsd plugin: c_avl_insert (\"%s\") failed with status %i.",
+          set_key, status);
+    sfree (set_key);
+    return (-1);
+  }
+  else if (status > 0) /* key already exists */
+  {
+    sfree (set_key);
+  }
+
+  metric->updates_num++;
+
+  pthread_mutex_unlock (&metrics_lock);
+  return (0);
+} /* }}} int statsd_handle_set */
+
+static int statsd_parse_line (char *buffer) /* {{{ */
+{
+  char *name = buffer;
+  char *value;
+  char *type;
+  char *extra;
+
+  type = strchr (name, '|');
+  if (type == NULL)
+    return (-1);
+  *type = 0;
+  type++;
+
+  value = strrchr (name, ':');
+  if (value == NULL)
+    return (-1);
+  *value = 0;
+  value++;
+
+  extra = strchr (type, '|');
+  if (extra != NULL)
+  {
+    *extra = 0;
+    extra++;
+  }
+
+  if (strcmp ("c", type) == 0)
+    return (statsd_handle_counter (name, value, extra));
+
+  /* extra is only valid for counters */
+  if (extra != NULL)
+    return (-1);
+
+  if (strcmp ("g", type) == 0)
+    return (statsd_handle_gauge (name, value));
+  else if (strcmp ("ms", type) == 0)
+    return (statsd_handle_timer (name, value));
+  else if (strcmp ("s", type) == 0)
+    return (statsd_handle_set (name, value));
+  else
+    return (-1);
+} /* }}} void statsd_parse_line */
+
+static void statsd_parse_buffer (char *buffer) /* {{{ */
+{
+  while (buffer != NULL)
+  {
+    char orig[64];
+    char *next;
+    int status;
+
+    next = strchr (buffer, '\n');
+    if (next != NULL)
+    {
+      *next = 0;
+      next++;
+    }
+
+    if (*buffer == 0)
+    {
+      buffer = next;
+      continue;
+    }
+
+    sstrncpy (orig, buffer, sizeof (orig));
+
+    status = statsd_parse_line (buffer);
+    if (status != 0)
+      ERROR ("statsd plugin: Unable to parse line: \"%s\"", orig);
+
+    buffer = next;
+  }
+} /* }}} void statsd_parse_buffer */
+
+static void statsd_network_read (int fd) /* {{{ */
+{
+  char buffer[4096];
+  size_t buffer_size;
+  ssize_t status;
+
+  status = recv (fd, buffer, sizeof (buffer), /* flags = */ MSG_DONTWAIT);
+  if (status < 0)
+  {
+    char errbuf[1024];
+
+    if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+      return;
+
+    ERROR ("statsd plugin: recv(2) failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return;
+  }
+
+  buffer_size = (size_t) status;
+  if (buffer_size >= sizeof (buffer))
+    buffer_size = sizeof (buffer) - 1;
+  buffer[buffer_size] = 0;
+
+  statsd_parse_buffer (buffer);
+} /* }}} void statsd_network_read */
+
+static int statsd_network_init (struct pollfd **ret_fds, /* {{{ */
+    size_t *ret_fds_num)
+{
+  struct pollfd *fds = NULL;
+  size_t fds_num = 0;
+
+  struct addrinfo ai_hints;
+  struct addrinfo *ai_list = NULL;
+  struct addrinfo *ai_ptr;
+  int status;
+
+  char const *node = (conf_node != NULL) ? conf_node : STATSD_DEFAULT_NODE;
+  char const *service = (conf_service != NULL)
+    ? conf_service : STATSD_DEFAULT_SERVICE;
+
+  memset (&ai_hints, 0, sizeof (ai_hints));
+  ai_hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+  ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+  ai_hints.ai_family = AF_UNSPEC;
+  ai_hints.ai_socktype = SOCK_DGRAM;
+
+  status = getaddrinfo (node, service, &ai_hints, &ai_list);
+  if (status != 0)
+  {
+    ERROR ("statsd plugin: getaddrinfo (\"%s\", \"%s\") failed: %s",
+        node, service, gai_strerror (status));
+    return (status);
+  }
+
+  for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+  {
+    int fd;
+    struct pollfd *tmp;
+
+    char dbg_node[NI_MAXHOST];
+    char dbg_service[NI_MAXSERV];
+
+    fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
+    if (fd < 0)
+    {
+      char errbuf[1024];
+      ERROR ("statsd plugin: socket(2) failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      continue;
+    }
+
+    getnameinfo (ai_ptr->ai_addr, ai_ptr->ai_addrlen,
+        dbg_node, sizeof (dbg_node), dbg_service, sizeof (dbg_service),
+        NI_DGRAM | NI_NUMERICHOST | NI_NUMERICSERV);
+    DEBUG ("statsd plugin: Trying to bind to [%s]:%s ...", dbg_node, dbg_service);
+
+    status = bind (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      ERROR ("statsd plugin: bind(2) failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      close (fd);
+      continue;
+    }
+
+    tmp = realloc (fds, sizeof (*fds) * (fds_num + 1));
+    if (tmp == NULL)
+    {
+      ERROR ("statsd plugin: realloc failed.");
+      continue;
+    }
+    fds = tmp;
+    tmp = fds + fds_num;
+    fds_num++;
+
+    memset (tmp, 0, sizeof (*tmp));
+    tmp->fd = fd;
+    tmp->events = POLLIN | POLLPRI;
+  }
+
+  freeaddrinfo (ai_list);
+
+  if (fds_num == 0)
+  {
+    ERROR ("statsd plugin: Unable to create listening socket for [%s]:%s.",
+        (node != NULL) ? node : "::", service);
+    return (ENOENT);
+  }
+
+  *ret_fds = fds;
+  *ret_fds_num = fds_num;
+  return (0);
+} /* }}} int statsd_network_init */
+
+static void *statsd_network_thread (void *args) /* {{{ */
+{
+  struct pollfd *fds = NULL;
+  size_t fds_num = 0;
+  int status;
+  size_t i;
+
+  status = statsd_network_init (&fds, &fds_num);
+  if (status != 0)
+  {
+    ERROR ("statsd plugin: Unable to open listening sockets.");
+    pthread_exit ((void *) 0);
+  }
+
+  while (!network_thread_shutdown)
+  {
+    status = poll (fds, (nfds_t) fds_num, /* timeout = */ -1);
+    if (status < 0)
+    {
+      char errbuf[1024];
+
+      if ((errno == EINTR) || (errno == EAGAIN))
+        continue;
+
+      ERROR ("statsd plugin: poll(2) failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      break;
+    }
+
+    for (i = 0; i < fds_num; i++)
+    {
+      if ((fds[i].revents & (POLLIN | POLLPRI)) == 0)
+        continue;
+
+      statsd_network_read (fds[i].fd);
+      fds[i].revents = 0;
+    }
+  } /* while (!network_thread_shutdown) */
+
+  /* Clean up */
+  for (i = 0; i < fds_num; i++)
+    close (fds[i].fd);
+  sfree (fds);
+
+  return ((void *) 0);
+} /* }}} void *statsd_network_thread */
+
+static int statsd_config_timer_percentile (oconfig_item_t *ci) /* {{{ */
+{
+  double percent = NAN;
+  double *tmp;
+  int status;
+
+  status = cf_util_get_double (ci, &percent);
+  if (status != 0)
+    return (status);
+
+  if ((percent <= 0.0) || (percent >= 100))
+  {
+    ERROR ("statsd plugin: The value for \"%s\" must be between 0 and 100, "
+        "exclusively.", ci->key);
+    return (ERANGE);
+  }
+
+  tmp = realloc (conf_timer_percentile,
+      sizeof (*conf_timer_percentile) * (conf_timer_percentile_num + 1));
+  if (tmp == NULL)
+  {
+    ERROR ("statsd plugin: realloc failed.");
+    return (ENOMEM);
+  }
+  conf_timer_percentile = tmp;
+  conf_timer_percentile[conf_timer_percentile_num] = percent;
+  conf_timer_percentile_num++;
+
+  return (0);
+} /* }}} int statsd_config_timer_percentile */
+
+static int statsd_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (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, &conf_node);
+    else if (strcasecmp ("Port", child->key) == 0)
+      cf_util_get_service (child, &conf_service);
+    else if (strcasecmp ("DeleteCounters", child->key) == 0)
+      cf_util_get_boolean (child, &conf_delete_counters);
+    else if (strcasecmp ("DeleteTimers", child->key) == 0)
+      cf_util_get_boolean (child, &conf_delete_timers);
+    else if (strcasecmp ("DeleteGauges", child->key) == 0)
+      cf_util_get_boolean (child, &conf_delete_gauges);
+    else if (strcasecmp ("DeleteSets", child->key) == 0)
+      cf_util_get_boolean (child, &conf_delete_sets);
+    else if (strcasecmp ("TimerLower", child->key) == 0)
+      cf_util_get_boolean (child, &conf_timer_lower);
+    else if (strcasecmp ("TimerUpper", child->key) == 0)
+      cf_util_get_boolean (child, &conf_timer_upper);
+    else if (strcasecmp ("TimerSum", child->key) == 0)
+      cf_util_get_boolean (child, &conf_timer_sum);
+    else if (strcasecmp ("TimerCount", child->key) == 0)
+      cf_util_get_boolean (child, &conf_timer_count);
+    else if (strcasecmp ("TimerPercentile", child->key) == 0)
+      statsd_config_timer_percentile (child);
+    else
+      ERROR ("statsd plugin: The \"%s\" config option is not valid.",
+          child->key);
+  }
+
+  return (0);
+} /* }}} int statsd_config */
+
+static int statsd_init (void) /* {{{ */
+{
+  pthread_mutex_lock (&metrics_lock);
+  if (metrics_tree == NULL)
+    metrics_tree = c_avl_create ((void *) strcmp);
+
+  if (!network_thread_running)
+  {
+    int status;
+
+    status = pthread_create (&network_thread,
+        /* attr = */ NULL,
+        statsd_network_thread,
+        /* args = */ NULL);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      pthread_mutex_unlock (&metrics_lock);
+      ERROR ("statsd plugin: pthread_create failed: %s",
+          sstrerror (errno, errbuf, sizeof (errbuf)));
+      return (status);
+    }
+  }
+  network_thread_running = 1;
+
+  pthread_mutex_unlock (&metrics_lock);
+
+  return (0);
+} /* }}} int statsd_init */
+
+/* Must hold metrics_lock when calling this function. */
+static int statsd_metric_clear_set_unsafe (statsd_metric_t *metric) /* {{{ */
+{
+  void *key;
+  void *value;
+
+  if ((metric == NULL) || (metric->type != STATSD_SET))
+    return (EINVAL);
+
+  if (metric->set == NULL)
+    return (0);
+
+  while (c_avl_pick (metric->set, &key, &value) == 0)
+  {
+    sfree (key);
+    sfree (value);
+  }
+
+  return (0);
+} /* }}} int statsd_metric_clear_set_unsafe */
+
+/* Must hold metrics_lock when calling this function. */
+static int statsd_metric_submit_unsafe (char const *name, /* {{{ */
+    statsd_metric_t const *metric)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  vl.values = values;
+  vl.values_len = 1;
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "statsd", sizeof (vl.plugin));
+
+  if (metric->type == STATSD_GAUGE)
+    sstrncpy (vl.type, "gauge", sizeof (vl.type));
+  else if (metric->type == STATSD_TIMER)
+    sstrncpy (vl.type, "latency", sizeof (vl.type));
+  else if (metric->type == STATSD_SET)
+    sstrncpy (vl.type, "objects", sizeof (vl.type));
+  else /* if (metric->type == STATSD_COUNTER) */
+    sstrncpy (vl.type, "derive", sizeof (vl.type));
+
+  sstrncpy (vl.type_instance, name, sizeof (vl.type_instance));
+
+  if (metric->type == STATSD_GAUGE)
+    values[0].gauge = (gauge_t) metric->value;
+  else if (metric->type == STATSD_TIMER)
+  {
+    size_t i;
+
+    if (metric->updates_num == 0)
+      return (0);
+
+    vl.time = cdtime ();
+
+    ssnprintf (vl.type_instance, sizeof (vl.type_instance),
+        "%s-average", name);
+    values[0].gauge = CDTIME_T_TO_DOUBLE (
+        latency_counter_get_average (metric->latency));
+    plugin_dispatch_values (&vl);
+
+    if (conf_timer_lower) {
+      ssnprintf (vl.type_instance, sizeof (vl.type_instance),
+          "%s-lower", name);
+      values[0].gauge = CDTIME_T_TO_DOUBLE (
+          latency_counter_get_min (metric->latency));
+      plugin_dispatch_values (&vl);
+    }
+
+    if (conf_timer_upper) {
+      ssnprintf (vl.type_instance, sizeof (vl.type_instance),
+          "%s-upper", name);
+      values[0].gauge = CDTIME_T_TO_DOUBLE (
+          latency_counter_get_max (metric->latency));
+      plugin_dispatch_values (&vl);
+    }
+
+    if (conf_timer_sum) {
+      ssnprintf (vl.type_instance, sizeof (vl.type_instance),
+          "%s-sum", name);
+      values[0].gauge = CDTIME_T_TO_DOUBLE (
+          latency_counter_get_sum (metric->latency));
+      plugin_dispatch_values (&vl);
+    }
+
+    for (i = 0; i < conf_timer_percentile_num; i++)
+    {
+      ssnprintf (vl.type_instance, sizeof (vl.type_instance),
+          "%s-percentile-%.0f", name, conf_timer_percentile[i]);
+      values[0].gauge = CDTIME_T_TO_DOUBLE (
+          latency_counter_get_percentile (
+            metric->latency, conf_timer_percentile[i]));
+      plugin_dispatch_values (&vl);
+    }
+
+    /* Keep this at the end, since vl.type is set to "gauge" here. The
+     * vl.type's above are implicitly set to "latency". */
+    if (conf_timer_count) {
+      sstrncpy (vl.type, "gauge", sizeof (vl.type));
+      ssnprintf (vl.type_instance, sizeof (vl.type_instance),
+          "%s-count", name);
+      values[0].gauge = latency_counter_get_num (metric->latency);
+      plugin_dispatch_values (&vl);
+    }
+
+    latency_counter_reset (metric->latency);
+    return (0);
+  }
+  else if (metric->type == STATSD_SET)
+  {
+    if (metric->set == NULL)
+      values[0].gauge = 0.0;
+    else
+      values[0].gauge = (gauge_t) c_avl_size (metric->set);
+  }
+  else
+    values[0].derive = (derive_t) metric->value;
+
+  return (plugin_dispatch_values (&vl));
+} /* }}} int statsd_metric_submit_unsafe */
+
+static int statsd_read (void) /* {{{ */
+{
+  c_avl_iterator_t *iter;
+  char *name;
+  statsd_metric_t *metric;
+
+  char **to_be_deleted = NULL;
+  size_t to_be_deleted_num = 0;
+  size_t i;
+
+  pthread_mutex_lock (&metrics_lock);
+
+  if (metrics_tree == NULL)
+  {
+    pthread_mutex_unlock (&metrics_lock);
+    return (0);
+  }
+
+  iter = c_avl_get_iterator (metrics_tree);
+  while (c_avl_iterator_next (iter, (void *) &name, (void *) &metric) == 0)
+  {
+    if ((metric->updates_num == 0)
+        && ((conf_delete_counters && (metric->type == STATSD_COUNTER))
+          || (conf_delete_timers && (metric->type == STATSD_TIMER))
+          || (conf_delete_gauges && (metric->type == STATSD_GAUGE))
+          || (conf_delete_sets && (metric->type == STATSD_SET))))
+    {
+      DEBUG ("statsd plugin: Deleting metric \"%s\".", name);
+      strarray_add (&to_be_deleted, &to_be_deleted_num, name);
+      continue;
+    }
+
+    /* Names have a prefix, e.g. "c:", which determines the (statsd) type.
+     * Remove this here. */
+    statsd_metric_submit_unsafe (name + 2, metric);
+
+    /* Reset the metric. */
+    metric->updates_num = 0;
+    if (metric->type == STATSD_SET)
+      statsd_metric_clear_set_unsafe (metric);
+  }
+  c_avl_iterator_destroy (iter);
+
+  for (i = 0; i < to_be_deleted_num; i++)
+  {
+    int status;
+
+    status = c_avl_remove (metrics_tree, to_be_deleted[i],
+        (void *) &name, (void *) &metric);
+    if (status != 0)
+    {
+      ERROR ("stats plugin: c_avl_remove (\"%s\") failed with status %i.",
+          to_be_deleted[i], status);
+      continue;
+    }
+
+    sfree (name);
+    sfree (metric);
+  }
+
+  pthread_mutex_unlock (&metrics_lock);
+
+  strarray_free (to_be_deleted, to_be_deleted_num);
+
+  return (0);
+} /* }}} int statsd_read */
+
+static int statsd_shutdown (void) /* {{{ */
+{
+  void *key;
+  void *value;
+
+  pthread_mutex_lock (&metrics_lock);
+
+  if (network_thread_running)
+  {
+    network_thread_shutdown = 1;
+    pthread_kill (network_thread, SIGTERM);
+    pthread_join (network_thread, /* retval = */ NULL);
+  }
+  network_thread_running = 0;
+
+  while (c_avl_pick (metrics_tree, &key, &value) == 0)
+  {
+    sfree (key);
+    sfree (value);
+  }
+  c_avl_destroy (metrics_tree);
+  metrics_tree = NULL;
+
+  sfree (conf_node);
+  sfree (conf_service);
+
+  pthread_mutex_unlock (&metrics_lock);
+
+  return (0);
+} /* }}} int statsd_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("statsd", statsd_config);
+  plugin_register_init ("statsd", statsd_init);
+  plugin_register_read ("statsd", statsd_read);
+  plugin_register_shutdown ("statsd", statsd_shutdown);
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
index f1d30f0..3a5e207 100644 (file)
@@ -87,7 +87,7 @@ io_packets            rx:DERIVE:0:U, tx:DERIVE:0:U
 ipt_bytes              value:DERIVE:0:U
 ipt_packets            value:DERIVE:0:U
 irq                    value:DERIVE:0:U
-latency                        value:GAUGE:0:65535
+latency                        value:GAUGE:0:U
 links                  value:GAUGE:0:U
 load                   shortterm:GAUGE:0:5000, midterm:GAUGE:0:5000, longterm:GAUGE:0:5000
 md_disks               value:GAUGE:0:U
@@ -160,10 +160,11 @@ signal_quality            value:GAUGE:0:U
 snr                    value:GAUGE:0:U
 spam_check             value:GAUGE:0:U
 spam_score             value:GAUGE:U:U
+spl                    value:GAUGE:U:U
 swap_io                        value:DERIVE:0:U
 swap                   value:GAUGE:0:1099511627776
 tcp_connections                value:GAUGE:0:4294967295
-temperature            value:GAUGE:-273.15:U
+temperature            value:GAUGE:U:U
 threads                        value:GAUGE:0:U
 time_dispersion                value:GAUGE:-1000000:1000000
 timeleft               value:GAUGE:0:U
diff --git a/src/utils_latency.c b/src/utils_latency.c
new file mode 100644 (file)
index 0000000..94da211
--- /dev/null
@@ -0,0 +1,187 @@
+/**
+ * collectd - src/utils_latency.c
+ * Copyright (C) 2013  Florian Forster
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <ff at octo.it>
+ **/
+
+#include "collectd.h"
+#include "utils_latency.h"
+#include "common.h"
+
+#ifndef LATENCY_HISTOGRAM_SIZE
+# define LATENCY_HISTOGRAM_SIZE 1000
+#endif
+
+struct latency_counter_s
+{
+  cdtime_t start_time;
+
+  cdtime_t sum;
+  size_t num;
+
+  cdtime_t min;
+  cdtime_t max;
+
+  int histogram[LATENCY_HISTOGRAM_SIZE];
+};
+
+latency_counter_t *latency_counter_create () /* {{{ */
+{
+  latency_counter_t *lc;
+
+  lc = malloc (sizeof (*lc));
+  if (lc == NULL)
+    return (NULL);
+
+  latency_counter_reset (lc);
+  return (lc);
+} /* }}} latency_counter_t *latency_counter_create */
+
+void latency_counter_destroy (latency_counter_t *lc) /* {{{ */
+{
+  sfree (lc);
+} /* }}} void latency_counter_destroy */
+
+void latency_counter_add (latency_counter_t *lc, cdtime_t latency) /* {{{ */
+{
+  size_t latency_ms;
+
+  if ((lc == NULL) || (latency == 0))
+    return;
+
+  lc->sum += latency;
+  lc->num++;
+
+  if ((lc->min == 0) && (lc->max == 0))
+    lc->min = lc->max = latency;
+  if (lc->min > latency)
+    lc->min = latency;
+  if (lc->max < latency)
+    lc->max = latency;
+
+  /* A latency of _exactly_ 1.0 ms should be stored in the buffer 0, so
+   * subtract one from the cdtime_t value so that exactly 1.0 ms get sorted
+   * accordingly. */
+  latency_ms = (size_t) CDTIME_T_TO_MS (latency - 1);
+  if (latency_ms < STATIC_ARRAY_SIZE (lc->histogram))
+    lc->histogram[latency_ms]++;
+} /* }}} void latency_counter_add */
+
+void latency_counter_reset (latency_counter_t *lc) /* {{{ */
+{
+  if (lc == NULL)
+    return;
+
+  memset (lc, 0, sizeof (*lc));
+  lc->start_time = cdtime ();
+} /* }}} void latency_counter_reset */
+
+cdtime_t latency_counter_get_min (latency_counter_t *lc) /* {{{ */
+{
+  if (lc == NULL)
+    return (0);
+  return (lc->min);
+} /* }}} cdtime_t latency_counter_get_min */
+
+cdtime_t latency_counter_get_max (latency_counter_t *lc) /* {{{ */
+{
+  if (lc == NULL)
+    return (0);
+  return (lc->max);
+} /* }}} cdtime_t latency_counter_get_max */
+
+cdtime_t latency_counter_get_sum (latency_counter_t *lc) /* {{{ */
+{
+  if (lc == NULL)
+    return (0);
+  return (lc->sum);
+} /* }}} cdtime_t latency_counter_get_sum */
+
+size_t latency_counter_get_num (latency_counter_t *lc) /* {{{ */
+{
+  if (lc == NULL)
+    return (0);
+  return (lc->num);
+} /* }}} size_t latency_counter_get_num */
+
+cdtime_t latency_counter_get_average (latency_counter_t *lc) /* {{{ */
+{
+  double average;
+
+  if (lc == NULL)
+    return (0);
+
+  average = CDTIME_T_TO_DOUBLE (lc->sum) / ((double) lc->num);
+  return (DOUBLE_TO_CDTIME_T (average));
+} /* }}} cdtime_t latency_counter_get_average */
+
+cdtime_t latency_counter_get_percentile (latency_counter_t *lc,
+    double percent)
+{
+  double percent_upper;
+  double percent_lower;
+  double ms_upper;
+  double ms_lower;
+  double ms_interpolated;
+  int sum;
+  size_t i;
+
+  if ((lc == NULL) || !((percent > 0.0) && (percent < 100.0)))
+    return (0);
+
+  /* Find index i so that at least "percent" events are within i+1 ms. */
+  percent_upper = 0.0;
+  percent_lower = 0.0;
+  sum = 0;
+  for (i = 0; i < LATENCY_HISTOGRAM_SIZE; i++)
+  {
+    percent_lower = percent_upper;
+    sum += lc->histogram[i];
+    if (sum == 0)
+      percent_upper = 0.0;
+    else
+      percent_upper = 100.0 * ((double) sum) / ((double) lc->num);
+
+    if (percent_upper >= percent)
+      break;
+  }
+
+  if (i >= LATENCY_HISTOGRAM_SIZE)
+    return (0);
+
+  assert (percent_upper >= percent);
+  assert (percent_lower < percent);
+
+  ms_upper = (double) (i + 1);
+  ms_lower = (double) i;
+  if (i == 0)
+    return (MS_TO_CDTIME_T (ms_upper));
+
+  ms_interpolated = (((percent_upper - percent) * ms_lower)
+      + ((percent - percent_lower) * ms_upper))
+    / (percent_upper - percent_lower);
+
+  return (MS_TO_CDTIME_T (ms_interpolated));
+} /* }}} cdtime_t latency_counter_get_percentile */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_latency.h b/src/utils_latency.h
new file mode 100644 (file)
index 0000000..3787c77
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * collectd - src/utils_latency.h
+ * Copyright (C) 2013  Florian Forster
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <ff at octo.it>
+ **/
+
+#include "collectd.h"
+#include "utils_time.h"
+
+struct latency_counter_s;
+typedef struct latency_counter_s latency_counter_t;
+
+latency_counter_t *latency_counter_create ();
+void latency_counter_destroy (latency_counter_t *lc);
+
+void latency_counter_add (latency_counter_t *lc, cdtime_t latency);
+void latency_counter_reset (latency_counter_t *lc);
+
+cdtime_t latency_counter_get_min (latency_counter_t *lc);
+cdtime_t latency_counter_get_max (latency_counter_t *lc);
+cdtime_t latency_counter_get_sum (latency_counter_t *lc);
+size_t   latency_counter_get_num (latency_counter_t *lc);
+cdtime_t latency_counter_get_average (latency_counter_t *lc);
+cdtime_t latency_counter_get_percentile (latency_counter_t *lc,
+    double percent);
+
+/* vim: set sw=2 sts=2 et : */
index e2ced0e..3a8dddd 100644 (file)
@@ -67,7 +67,9 @@ struct user_config_s {
 #endif
        _Bool collect_struct;
        _Bool collect_totals;
+#ifdef HAVE_VARNISH_V3
        _Bool collect_uptime;
+#endif
        _Bool collect_vcl;
        _Bool collect_workers;
 };
@@ -381,10 +383,12 @@ static void varnish_monitor (const user_config_t *conf, /* {{{ */
                varnish_submit_gauge (conf->instance, "struct", "current_sessions", "sess",      stats->n_sess);
                /* N struct object         */
                varnish_submit_gauge (conf->instance, "struct", "objects", "object",             stats->n_object);
+#ifdef HAVE_VARNISH_V3
                /* N unresurrected objects */
                varnish_submit_gauge (conf->instance, "struct", "objects", "vampireobject",      stats->n_vampireobject);
                /* N struct objectcore     */
                varnish_submit_gauge (conf->instance, "struct", "objects", "objectcore",         stats->n_objectcore);
+#endif
                /* N struct objecthead     */
                varnish_submit_gauge (conf->instance, "struct", "objects", "objecthead",         stats->n_objecthead);
 #ifdef HAVE_VARNISH_V2
@@ -417,11 +421,13 @@ static void varnish_monitor (const user_config_t *conf, /* {{{ */
                varnish_submit_derive (conf->instance, "totals", "total_bytes", "body-bytes",   stats->s_bodybytes);
        }
 
+#ifdef HAVE_VARNISH_V3
        if (conf->collect_uptime)
        {
                /* Client uptime */
                varnish_submit_gauge (conf->instance, "uptime", "uptime", "client_uptime", stats->uptime);
        }
+#endif
 
        if (conf->collect_vcl)
        {
@@ -569,7 +575,9 @@ static int varnish_config_apply_default (user_config_t *conf) /* {{{ */
        conf->collect_sms         = 0;
        conf->collect_struct      = 0;
        conf->collect_totals      = 0;
+#ifdef HAVE_VARNISH_V3
        conf->collect_uptime      = 0;
+#endif
        conf->collect_vcl         = 0;
        conf->collect_workers     = 0;
 
@@ -691,8 +699,10 @@ static int varnish_config_instance (const oconfig_item_t *ci) /* {{{ */
                        cf_util_get_boolean (child, &conf->collect_struct);
                else if (strcasecmp ("CollectTotals", child->key) == 0)
                        cf_util_get_boolean (child, &conf->collect_totals);
+#ifdef HAVE_VARNISH_V3
                else if (strcasecmp ("CollectUptime", child->key) == 0)
                        cf_util_get_boolean (child, &conf->collect_uptime);
+#endif
                else if (strcasecmp ("CollectVCL", child->key) == 0)
                        cf_util_get_boolean (child, &conf->collect_vcl);
                else if (strcasecmp ("CollectWorkers", child->key) == 0)
@@ -731,7 +741,9 @@ static int varnish_config_instance (const oconfig_item_t *ci) /* {{{ */
 #endif
                        && !conf->collect_struct
                        && !conf->collect_totals
+#ifdef HAVE_VARNISH_V3
                        && !conf->collect_uptime
+#endif
                        && !conf->collect_vcl
                        && !conf->collect_workers)
        {