Code

Merge branch 'collectd-5.2' into collectd-5.3
authorFlorian Forster <octo@collectd.org>
Fri, 12 Jul 2013 16:21:31 +0000 (18:21 +0200)
committerFlorian Forster <octo@collectd.org>
Fri, 12 Jul 2013 16:21:31 +0000 (18:21 +0200)
45 files changed:
.gitignore
AUTHORS
ChangeLog
README
configure.in
contrib/redhat/collectd.spec
src/Makefile.am
src/aggregation.c
src/apcups.c
src/collectd.conf.in
src/collectd.conf.pod
src/collectd.h
src/common.c
src/common.h
src/configfile.c
src/curl.c
src/curl_json.c
src/curl_xml.c
src/dbi.c
src/disk.c
src/libvirt.c
src/netapp.c
src/network.c
src/nfs.c
src/plugin.c
src/plugin.h
src/postgresql.c
src/riemann.proto [new file with mode: 0644]
src/rrdcached.c
src/rrdtool.c
src/snmp.c
src/tail_csv.c [new file with mode: 0644]
src/types.db
src/utils_random.c [new file with mode: 0644]
src/utils_random.h [new file with mode: 0644]
src/utils_rrdcreate.c
src/utils_rrdcreate.h
src/utils_vl_lookup.c
src/utils_vl_lookup.h
src/utils_vl_lookup_test.c
src/uuid.c
src/write_graphite.c
src/write_mongodb.c
src/write_riemann.c [new file with mode: 0644]
version-gen.sh

index 75cb307deb9d76420968d5d8024ac28baaa55b5c..6e87aaf27590797fe199103b5ebb94cd592aea7a 100644 (file)
@@ -49,6 +49,9 @@ src/liboconfig/parser.c
 src/liboconfig/parser.h
 src/liboconfig/scanner.c
 
+# protobuf stuff:
+src/*.pb-c.[ch]
+
 # make dist stuff:
 /collectd-*.tar.gz
 /collectd-*.tar.bz2
diff --git a/AUTHORS b/AUTHORS
index 78dbad146745dc81edeb800909297eebd3735028..45645d1a26e52700130ed68c6dc7042307f27895 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -91,6 +91,9 @@ Jason Pepas <cell at ices.utexas.edu>
 Jérôme Renard <jerome.renard at gmail.com>
  - varnish plugin.
 
+Kris Nielander <nielander at fox-it.com>
+ - tail_csv plugin.
+
 Luboš Staněk <kolektor at atlas.cz>
  - sensors plugin improvements.
  - Time and effort to find a nasty bug in the ntpd-plugin.
@@ -165,6 +168,10 @@ Peter Holik <peter at holik.at>
 Phoenix Kayo <kayo.k11.4 at gmail.com>
  - pinba plugin.
 
+Pierre-Yves Ritschard <pyr at spootnik.org>
+ - Write-Riemann plugin.
+ - Write-Graphite plugin: Notification support.
+
 Piotr Hosowicz <the55 at wp.pl>
  - SMF manifest for collectd.
 
index 78cfff4e15a6a0efe69092653db5f04063efd0d7..27f58e9a4747a51c51a37bb516f9d55df094a520 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,42 @@
+2013-04-09, Version 5.3.0
+       * collectd: The "Include" statements can now be limited to include
+         only matching files in a directory. Thanks to Sebastian Harl for his
+         patch.
+       * collectd: Dispatches / writes are now handled by a thread pool. This
+         improves reliability and throughput for instances configured to act
+         as a "server". Thanks to Sebastian Harl and Dan Fandrich for
+         reviewing this change and fixing bugs.
+       * aggregation plugin: Selection of value lists is now possible using
+         regular expressions. Parts of the identifier of the resulting metric
+         can now be set via the configuration file.
+       * apcups plugin: The "ReportSeconds" option has been implemented.
+       * curl* plugins: Support for POST requests and custom request headers
+         has been added. Thanks to Dan Fandrich for his patch.
+       * curl_xml plugin: Support for XML namespaces has been added. Thanks
+         to Dan Fandrich for his patch.
+       * dbi plugin: Support for numeric options has been added. The
+         "Host" option has been added. Thanks to Daniel Hilst for his patch.
+       * disk plugin: Support for systems with >256 has been fixed. Thanks to
+         Greg Mason for his patch.
+       * libvirt plugin: Support for memory allocation has been added. Thanks
+         to Johan Wirén for his patch.
+       * netapp plugin: Support for "SnapVault", "VFiler" and deduplication /
+         compression and quota metrics. Thanks to Sebastian Harl for his
+         patches and teamix GmbH for sponsoring this work.
+       * postgresql plugin: The reconnection logic has been improved. Thanks
+         to Sebastian Harl for his patches.
+       * rrdtool, rrdcached plugins: The "CreateFilesAsync" option has been
+         implemented. When enabled, new RRD files will be created
+         asynchronously, which improved throughput of "server" instances.
+         Many thanks to Yves Mettier for all his input and code.
+       * tail_csv plugin: This new plugins allows to read metrics from CSV
+         files, such as Snort's statistics file. Thanks to Kris Nielander for
+         his patch.
+       * write_mongodb plugin: Authentication options have been added.
+       * write_riemann plugin: This new plugin allows sending metrics to
+         Riemann, a stream processing and alerting tool. Big thanks to
+         Pierre-Yves Ritschard for his work.
+
 2013-04-08, Version 5.2.2
        * Build system: A bad interaction between the Java detection code and
          libltdl has been fixed. Thanks to Dave Cottlehuber for his patch.
diff --git a/README b/README
index 45dcadb83f931d87c92fcf4ffd36e56455893d15..c3c454720484f71c3915bf6f0917ce2e27dd93ae 100644 (file)
--- a/README
+++ b/README
@@ -133,7 +133,7 @@ Features
       technique built into IBM's POWER processors.
 
     - libvirt
-      CPU, disk and network I/O statistics from virtual machines.
+      CPU, memory, disk and network I/O statistics from virtual machines.
 
     - madwifi
       Queries very detailed usage statistics from wireless LAN adapters and
@@ -284,6 +284,10 @@ Features
       Follows (tails) logfiles, parses them by lines and submits matched
       values.
 
+    - tail_csv
+      Follows (tails) files in CSV format, parses each line and submits
+      extracted values.
+
     - tape
       Bytes and operations read and written on tape devices. Solaris only.
 
@@ -379,9 +383,15 @@ Features
       requests. The transmitted data is either in a form understood by the
       Exec plugin or formatted in JSON.
 
+    - write_mongodb
+      Sends data to MongoDB, a NoSQL database.
+
     - write_redis
       Sends the values to a Redis key-value database server.
 
+    - write_riemann
+      Sends data to Riemann, a stream processing and monitoring system.
+
   * Logging is, as everything in collectd, provided by plugins. The following
     plugins keep up informed about what's going on:
 
index 7212f02f9bea985771de5e3e344f80ec1032048b..a62d608bf21fe35ac40df7662f6c64ea67a24264 100644 (file)
@@ -49,6 +49,13 @@ AC_PROG_YACC
 PKG_PROG_PKG_CONFIG
 
 AC_CHECK_PROG([have_protoc_c], [protoc-c], [yes], [no])
+AC_CHECK_HEADERS([google/protobuf-c/protobuf-c.h],
+                 [have_protobuf_c_h="yes"],
+                 [have_protobuf_c_h="no"])
+if test "x$have_protoc_c" = "xyes" && test "x$have_protobuf_c_h" != "xyes"
+then
+       have_protoc_c="no (unable to find <google/protobuf-c/protobuf-c.h>)"
+fi
 AM_CONDITIONAL(HAVE_PROTOC_C, test "x$have_protoc_c" = "xyes")
 
 AC_MSG_CHECKING([for kernel type ($host_os)])
@@ -128,7 +135,7 @@ AC_HEADER_SYS_WAIT
 AC_HEADER_DIRENT
 AC_HEADER_STDBOOL
 
-AC_CHECK_HEADERS(stdio.h errno.h math.h stdarg.h syslog.h fcntl.h signal.h assert.h sys/types.h sys/socket.h sys/select.h poll.h netdb.h arpa/inet.h sys/resource.h sys/param.h kstat.h regex.h sys/ioctl.h endian.h sys/isa_defs.h)
+AC_CHECK_HEADERS(stdio.h errno.h math.h stdarg.h syslog.h fcntl.h signal.h assert.h sys/types.h sys/socket.h sys/select.h poll.h netdb.h arpa/inet.h sys/resource.h sys/param.h kstat.h regex.h sys/ioctl.h endian.h sys/isa_defs.h fnmatch.h libgen.h)
 
 # For ping library
 AC_CHECK_HEADERS(netinet/in_systm.h, [], [],
@@ -270,7 +277,19 @@ if test "x$ac_system" = "xDarwin"
 then
        AC_CHECK_HEADERS(mach/mach_init.h mach/host_priv.h mach/mach_error.h mach/mach_host.h mach/mach_port.h mach/mach_types.h mach/message.h mach/processor_set.h mach/processor.h mach/processor_info.h mach/task.h mach/thread_act.h mach/vm_region.h mach/vm_map.h mach/vm_prot.h mach/vm_statistics.h mach/kern_return.h)
        AC_CHECK_HEADERS(CoreFoundation/CoreFoundation.h IOKit/IOKitLib.h IOKit/IOTypes.h IOKit/ps/IOPSKeys.h IOKit/IOBSD.h IOKit/storage/IOBlockStorageDriver.h)
+       # For the battery plugin
+       AC_CHECK_HEADERS(IOKit/ps/IOPowerSources.h, [], [],
+[
+#if HAVE_IOKIT_IOKITLIB_H
+#  include <IOKit/IOKitLib.h>
+#endif
+#if HAVE_IOKIT_IOTYPES_H
+#  include <IOKit/IOTypes.h>
+#endif
+])
+
 fi
+
 AC_CHECK_HEADERS(sys/sysctl.h, [], [],
 [
 #if HAVE_SYS_TYPES_H
@@ -298,7 +317,7 @@ else
 fi
 
 # For hddtemp module
-AC_CHECK_HEADERS(linux/major.h libgen.h)
+AC_CHECK_HEADERS(linux/major.h)
 
 # For md module (Linux only)
 if test "x$ac_system" = "xLinux"
@@ -315,17 +334,6 @@ else
        have_linux_raid_md_u_h="no"
 fi
 
-# For the battery plugin
-AC_CHECK_HEADERS(IOKit/ps/IOPowerSources.h, [], [],
-[
-#if HAVE_IOKIT_IOKITLIB_H
-#  include <IOKit/IOKitLib.h>
-#endif
-#if HAVE_IOKIT_IOTYPES_H
-#  include <IOKit/IOTypes.h>
-#endif
-])
-
 # For the swap module
 have_linux_wireless_h="no"
 if test "x$ac_system" = "xLinux"
@@ -1836,6 +1844,8 @@ then
 
        if test "$with_libgcrypt" != "no"; then
                AM_PATH_LIBGCRYPT(1:1.2.0,,with_libgcrypt="no (version 1.2.0+ required)")
+               GCRYPT_CPPFLAGS="$LIBGCRYPT_CPPFLAGS $LIBGCRYPT_CFLAGS"
+               GCRYPT_LIBS="$LIBGCRYPT_LIBS"
        fi
 fi
 
@@ -4964,6 +4974,7 @@ AC_PLUGIN([swap],        [$plugin_swap],       [Swap usage statistics])
 AC_PLUGIN([syslog],      [$have_syslog],       [Syslog logging plugin])
 AC_PLUGIN([table],       [yes],                [Parsing of tabular data])
 AC_PLUGIN([tail],        [yes],                [Parsing of logfiles])
+AC_PLUGIN([tail_csv],    [yes],                [Parsing of CSV files])
 AC_PLUGIN([tape],        [$plugin_tape],       [Tape drive statistics])
 AC_PLUGIN([target_notification], [yes],        [The notification target])
 AC_PLUGIN([target_replace], [yes],             [The replace target])
@@ -4986,8 +4997,9 @@ AC_PLUGIN([vserver],     [$plugin_vserver],    [Linux VServer statistics])
 AC_PLUGIN([wireless],    [$plugin_wireless],   [Wireless statistics])
 AC_PLUGIN([write_graphite], [yes],             [Graphite / Carbon output plugin])
 AC_PLUGIN([write_http],  [$with_libcurl],      [HTTP output plugin])
-AC_PLUGIN([write_redis], [$with_libcredis],    [Redis output plugin])
 AC_PLUGIN([write_mongodb], [$with_libmongoc],  [MongoDB output plugin])
+AC_PLUGIN([write_redis], [$with_libcredis],    [Redis output plugin])
+AC_PLUGIN([write_riemann], [$have_protoc_c],   [Riemann output plugin])
 AC_PLUGIN([xmms],        [$with_libxmms],      [XMMS statistics])
 AC_PLUGIN([zfs_arc],     [$plugin_zfs_arc],    [ZFS ARC statistics])
 
@@ -5296,6 +5308,7 @@ Configuration:
     syslog  . . . . . . . $enable_syslog
     table . . . . . . . . $enable_table
     tail  . . . . . . . . $enable_tail
+    tail_csv  . . . . . . $enable_tail_csv
     tape  . . . . . . . . $enable_tape
     target_notification . $enable_target_notification
     target_replace  . . . $enable_target_replace
@@ -5318,8 +5331,9 @@ Configuration:
     wireless  . . . . . . $enable_wireless
     write_graphite  . . . $enable_write_graphite
     write_http  . . . . . $enable_write_http
-    write_redis . . . . . $enable_write_redis
     write_mongodb . . . . $enable_write_mongodb
+    write_redis . . . . . $enable_write_redis
+    write_riemann . . . . $enable_write_riemann
     xmms  . . . . . . . . $enable_xmms
     zfs_arc . . . . . . . $enable_zfs_arc
 
index 926dac993f53f496bbdb57c97eef2100fdd9d8b6..86f317f7a1b80e43a1555de8912178a2058fdef0 100644 (file)
@@ -71,6 +71,7 @@
 %define with_syslog 0%{!?_without_syslog:1}
 %define with_table 0%{!?_without_table:1}
 %define with_tail 0%{!?_without_tail:1}
+%define with_tail_csv 0%{!?_without_tail_csv:1}
 %define with_tcpconns 0%{!?_without_tcpconns:1}
 %define with_teamspeak2 0%{!?_without_teamspeak2:1}
 %define with_ted 0%{!?_without_ted:1}
@@ -86,6 +87,7 @@
 %define with_wireless 0%{!?_without_wireless:1}
 %define with_write_graphite 0%{!?_without_write_graphite:1}
 %define with_write_http 0%{!?_without_write_http:1}
+%define with_write_riemann 0%{!?_without_write_riemann:1}
 
 # disabled plugins
 %define with_apple_sensors 0%{!?_without_apple_sensors:0}
 
 Summary:       Statistics collection daemon for filling RRD files
 Name:          collectd
-Version:       5.2.0
-Release:       3%{?dist}
+Version:       5.3.0
+Release:       1%{?dist}
 URL:           http://collectd.org
-Source:                http://collectd.org/files/%{name}-%{version}.tar.gz
+Source:                http://collectd.org/files/%{name}-%{version}.tar.bz2
 License:       GPLv2
 Group:         System Environment/Daemons
 BuildRoot:     %{_tmppath}/%{name}-%{version}-root
@@ -509,6 +511,16 @@ BuildRequires:     credis-devel
 The Write Redis plugin stores values in Redis, a “data structures server”.
 %endif
 
+%if %{with_write_riemann}
+%package write_riemann
+Summary:       riemann plugin for collectd
+Group:         System Environment/Daemons
+Requires:      %{name}%{?_isa} = %{version}-%{release}
+BuildRequires: protobuf-c-devel
+%description write_riemann
+The riemann plugin submits values to Riemann, an event stream processor.
+%endif
+
 %package collection3
 Summary:       Web-based viewer for collectd
 Group:         System Environment/Daemons
@@ -553,7 +565,6 @@ Development files for libcollectdclient
 %prep
 %setup -q
 
-
 %build
 %if %{with_aggregation}
 %define _with_aggregation --enable-aggregation
@@ -1041,6 +1052,12 @@ Development files for libcollectdclient
 %define _with_tail --disable-tail
 %endif
 
+%if %{with_tail_csv}
+%define _with_tail_csv --enable-tail_csv
+%else
+%define _with_tail_csv --disable-tail_csv
+%endif
+
 %if %{with_tape}
 %define _with_tape --enable-tape
 %else
@@ -1155,6 +1172,12 @@ Development files for libcollectdclient
 %define _with_write_redis --disable-write_redis --without-libcredis
 %endif
 
+%if %{with_write_riemann}
+%define _with_write_riemann --enable-write_riemann
+%else
+%define _with_write_riemann --disable-write_riemann
+%endif
+
 %if %{with_xmms}
 %define _with_xmms --enable-xmms
 %else
@@ -1271,6 +1294,7 @@ Development files for libcollectdclient
        %{?_with_syslog} \
        %{?_with_table} \
        %{?_with_tail} \
+       %{?_with_tail_csv} \
        %{?_with_tcpconns} \
        %{?_with_teamspeak2} \
        %{?_with_ted} \
@@ -1284,7 +1308,8 @@ Development files for libcollectdclient
        %{?_with_vserver} \
        %{?_with_wireless}\
        %{?_with_write_graphite} \
-       %{?_with_write_http}
+       %{?_with_write_http} \
+       %{?_with_write_riemann}
 
 
 %{__make} %{?_smp_mflags}
@@ -1375,6 +1400,7 @@ fi
 %{_mandir}/man1/collectd.1*
 %{_mandir}/man1/collectdctl.1*
 %{_mandir}/man1/collectdmon.1*
+%{_mandir}/man1/collectd-tg.1*
 %{_mandir}/man5/collectd-email.5*
 %{_mandir}/man5/collectd-exec.5*
 %{_mandir}/man5/collectd-threshold.5*
@@ -1514,6 +1540,9 @@ fi
 %if %{with_tail}
 %{_libdir}/%{name}/tail.so
 %endif
+%if %{with_tail_csv}
+%{_libdir}/%{name}/tail_csv.so
+%endif
 %if %{with_tcpconns}
 %{_libdir}/%{name}/tcpconns.so
 %endif
@@ -1767,6 +1796,11 @@ fi
 %{_libdir}/%{name}/write_redis.so
 %endif
 
+%if %{with_write_riemann}
+%files write_riemann
+%{_libdir}/%{name}/write_riemann.so
+%endif
+
 %files collection3
 %{_localstatedir}/www/collection3
 %{_sysconfdir}/httpd/conf.d/collection3.conf
@@ -1779,6 +1813,12 @@ fi
 %doc contrib/
 
 %changelog
+* Wed Apr 10 2013 Marc Fournier <marc.fournier@camptocamp.com> 5.3.0-1
+- New upstream version
+- Enabled write_riemann plugin
+- Enabled tail_csv plugin
+- Installed collectd-tc manpage
+
 * Thu Jan 11 2013 Marc Fournier <marc.fournier@camptocamp.com> 5.2.0-3
 - remove dependency on libstatgrab, which isn't required on linux
 
index aa014100158c3622212bf5e41d6a3c8a1168d773..2bebec55c6d88ff320f94689c4eb795ead97a7cf 100644 (file)
@@ -33,6 +33,7 @@ collectd_SOURCES = collectd.c collectd.h \
                   utils_ignorelist.c utils_ignorelist.h \
                   utils_llist.c utils_llist.h \
                   utils_parse_option.c utils_parse_option.h \
+                  utils_random.c utils_random.h \
                   utils_tail_match.c utils_tail_match.h \
                   utils_match.c utils_match.h \
                   utils_subst.c utils_subst.h \
@@ -135,7 +136,7 @@ collectd_tg_DEPENDENCIES = libcollectdclient/libcollectdclient.la
 pkglib_LTLIBRARIES = 
 
 BUILT_SOURCES = 
-CLEANFILES = 
+CLEANFILES =
 
 if BUILD_PLUGIN_AGGREGATION
 pkglib_LTLIBRARIES += aggregation.la
@@ -903,10 +904,9 @@ collectd_DEPENDENCIES += pf.la
 endif
 
 if BUILD_PLUGIN_PINBA
-BUILT_SOURCES += pinba.pb-c.c pinba.pb-c.h
-CLEANFILES += pinba.pb-c.c pinba.pb-c.h
 pkglib_LTLIBRARIES += pinba.la
 pinba_la_SOURCES = pinba.c
+nodist_pinba_la_SOURCES = pinba.pb-c.c pinba.pb-c.h
 pinba_la_LDFLAGS = -module -avoid-version
 pinba_la_LIBADD = -lprotobuf-c
 collectd_LDADD += "-dlopen" pinba.la
@@ -1103,6 +1103,14 @@ collectd_LDADD += "-dlopen" tail.la
 collectd_DEPENDENCIES += tail.la
 endif
 
+if BUILD_PLUGIN_TAIL_CSV
+pkglib_LTLIBRARIES += tail_csv.la
+tail_csv_la_SOURCES = tail_csv.c
+tail_csv_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" tail_csv.la
+collectd_DEPENDENCIES += tail_csv.la
+endif
+
 if BUILD_PLUGIN_TAPE
 pkglib_LTLIBRARIES += tape.la
 tape_la_SOURCES = tape.c
@@ -1342,6 +1350,16 @@ collectd_LDADD += "-dlopen" write_redis.la
 collectd_DEPENDENCIES += write_redis.la
 endif
 
+if BUILD_PLUGIN_WRITE_RIEMANN
+pkglib_LTLIBRARIES += write_riemann.la
+write_riemann_la_SOURCES = write_riemann.c
+nodist_write_riemann_la_SOURCES = riemann.pb-c.c riemann.pb-c.h
+write_riemann_la_LDFLAGS = -module -avoid-version
+write_riemann_la_LIBADD = -lprotobuf-c
+collectd_LDADD += "-dlopen" write_riemann.la
+collectd_DEPENDENCIES += write_riemann.la
+endif
+
 if BUILD_PLUGIN_XMMS
 pkglib_LTLIBRARIES += xmms.la
 xmms_la_SOURCES = xmms.c
@@ -1382,7 +1400,7 @@ dist_man_MANS = collectd.1 \
 
 #collectd_1_SOURCES = collectd.pod
 
-EXTRA_DIST = types.db pinba.proto
+EXTRA_DIST = types.db
 
 EXTRA_DIST +=   collectd.conf.pod \
                collectd-email.pod \
@@ -1417,8 +1435,26 @@ EXTRA_DIST +=   collectd.conf.pod \
                echo "$@ has some POD errors!"; false; \
        fi
 
+# Protocol buffer for the "pinba" plugin.
+EXTRA_DIST += pinba.proto
+if HAVE_PROTOC_C
+CLEANFILES += pinba.pb-c.c pinba.pb-c.h
+BUILT_SOURCES += pinba.pb-c.c pinba.pb-c.h
+
 pinba.pb-c.c pinba.pb-c.h: pinba.proto
        protoc-c -I$(srcdir) --c_out . $(srcdir)/pinba.proto
+endif
+
+# Protocol buffer for the "write_riemann" plugin.
+EXTRA_DIST += riemann.proto
+if HAVE_PROTOC_C
+CLEANFILES += riemann.pb-c.c riemann.pb-c.h
+
+BUILT_SOURCES += riemann.pb-c.c riemann.pb-c.h
+
+riemann.pb-c.c riemann.pb-c.h: riemann.proto
+       protoc-c -I$(srcdir) --c_out . $(srcdir)/riemann.proto
+endif
 
 install-exec-hook:
        $(mkinstalldirs) $(DESTDIR)$(sysconfdir)
index db33c177c039fa1676d9fcdc690b47d428f9a82b..0c0f19d6fe1585301443754bf6bc8933511d8bc5 100644 (file)
  **/
 
 #include "collectd.h"
+
+#include <pthread.h>
+
 #include "plugin.h"
 #include "common.h"
 #include "configfile.h"
 #include "meta_data.h"
 #include "utils_cache.h" /* for uc_get_rate() */
+#include "utils_subst.h"
 #include "utils_vl_lookup.h"
 
-#include <pthread.h>
+#define AGG_MATCHES_ALL(str) (strcmp ("/.*/", str) == 0)
+#define AGG_FUNC_PLACEHOLDER "%{aggregation}"
 
 struct aggregation_s /* {{{ */
 {
   identifier_t ident;
+  unsigned int group_by;
+
+  unsigned int regex_fields;
+
+  char *set_host;
+  char *set_plugin;
+  char *set_plugin_instance;
+  char *set_type_instance;
 
   _Bool calc_num;
   _Bool calc_sum;
@@ -78,6 +91,23 @@ static lookup_t *lookup = NULL;
 static pthread_mutex_t agg_instance_list_lock = PTHREAD_MUTEX_INITIALIZER;
 static agg_instance_t *agg_instance_list_head = NULL;
 
+static _Bool agg_is_regex (char const *str) /* {{{ */
+{
+  size_t len;
+
+  if (str == NULL)
+    return (0);
+
+  len = strlen (str);
+  if (len < 3)
+    return (0);
+
+  if ((str[0] == '/') && (str[len - 1] == '/'))
+    return (1);
+  else
+    return (0);
+} /* }}} _Bool agg_is_regex */
+
 static void agg_destroy (aggregation_t *agg) /* {{{ */
 {
   sfree (agg);
@@ -116,6 +146,92 @@ static void agg_instance_destroy (agg_instance_t *inst) /* {{{ */
   inst->max = NAN;
 } /* }}} void agg_instance_destroy */
 
+static int agg_instance_create_name (agg_instance_t *inst, /* {{{ */
+    value_list_t const *vl, aggregation_t const *agg)
+{
+#define COPY_FIELD(buffer, buffer_size, field, group_mask, all_value) do { \
+  if (agg->set_ ## field != NULL) \
+    sstrncpy (buffer, agg->set_ ## field, buffer_size); \
+  else if ((agg->regex_fields & group_mask) \
+      && (agg->group_by & group_mask)) \
+    sstrncpy (buffer, vl->field, buffer_size); \
+  else if ((agg->regex_fields & group_mask) \
+      && (AGG_MATCHES_ALL (agg->ident.field))) \
+    sstrncpy (buffer, all_value, buffer_size); \
+  else \
+    sstrncpy (buffer, agg->ident.field, buffer_size); \
+} while (0)
+
+  /* Host */
+  COPY_FIELD (inst->ident.host, sizeof (inst->ident.host),
+      host, LU_GROUP_BY_HOST, "global");
+
+  /* Plugin */
+  if (agg->set_plugin != NULL)
+    sstrncpy (inst->ident.plugin, agg->set_plugin,
+        sizeof (inst->ident.plugin));
+  else
+    sstrncpy (inst->ident.plugin, "aggregation", sizeof (inst->ident.plugin));
+
+  /* Plugin instance */
+  if (agg->set_plugin_instance != NULL)
+    sstrncpy (inst->ident.plugin_instance, agg->set_plugin_instance,
+        sizeof (inst->ident.plugin_instance));
+  else
+  {
+    char tmp_plugin[DATA_MAX_NAME_LEN];
+    char tmp_plugin_instance[DATA_MAX_NAME_LEN] = "";
+
+    if ((agg->regex_fields & LU_GROUP_BY_PLUGIN)
+        && (agg->group_by & LU_GROUP_BY_PLUGIN))
+      sstrncpy (tmp_plugin, vl->plugin, sizeof (tmp_plugin));
+    else if ((agg->regex_fields & LU_GROUP_BY_PLUGIN)
+        && (AGG_MATCHES_ALL (agg->ident.plugin)))
+      sstrncpy (tmp_plugin, "", sizeof (tmp_plugin));
+    else
+      sstrncpy (tmp_plugin, agg->ident.plugin, sizeof (tmp_plugin));
+
+    if ((agg->regex_fields & LU_GROUP_BY_PLUGIN_INSTANCE)
+        && (agg->group_by & LU_GROUP_BY_PLUGIN_INSTANCE))
+      sstrncpy (tmp_plugin_instance, vl->plugin_instance,
+          sizeof (tmp_plugin_instance));
+    else if ((agg->regex_fields & LU_GROUP_BY_PLUGIN_INSTANCE)
+        && (AGG_MATCHES_ALL (agg->ident.plugin_instance)))
+      sstrncpy (tmp_plugin_instance, "", sizeof (tmp_plugin_instance));
+    else
+      sstrncpy (tmp_plugin_instance, agg->ident.plugin_instance,
+          sizeof (tmp_plugin_instance));
+
+    if ((strcmp ("", tmp_plugin) == 0)
+        && (strcmp ("", tmp_plugin_instance) == 0))
+      sstrncpy (inst->ident.plugin_instance, AGG_FUNC_PLACEHOLDER,
+          sizeof (inst->ident.plugin_instance));
+    else if (strcmp ("", tmp_plugin) != 0)
+      ssnprintf (inst->ident.plugin_instance,
+          sizeof (inst->ident.plugin_instance),
+          "%s-%s", tmp_plugin, AGG_FUNC_PLACEHOLDER);
+    else if (strcmp ("", tmp_plugin_instance) != 0)
+      ssnprintf (inst->ident.plugin_instance,
+          sizeof (inst->ident.plugin_instance),
+          "%s-%s", tmp_plugin_instance, AGG_FUNC_PLACEHOLDER);
+    else
+      ssnprintf (inst->ident.plugin_instance,
+          sizeof (inst->ident.plugin_instance),
+          "%s-%s-%s", tmp_plugin, tmp_plugin_instance, AGG_FUNC_PLACEHOLDER);
+  }
+
+  /* Type */
+  sstrncpy (inst->ident.type, agg->ident.type, sizeof (inst->ident.type));
+
+  /* Type instance */
+  COPY_FIELD (inst->ident.type_instance, sizeof (inst->ident.type_instance),
+      type_instance, LU_GROUP_BY_TYPE_INSTANCE, "");
+
+#undef COPY_FIELD
+
+  return (0);
+} /* }}} int agg_instance_create_name */
+
 /* Create a new aggregation instance. */
 static agg_instance_t *agg_instance_create (data_set_t const *ds, /* {{{ */
     value_list_t const *vl, aggregation_t *agg)
@@ -135,19 +251,7 @@ static agg_instance_t *agg_instance_create (data_set_t const *ds, /* {{{ */
 
   inst->ds_type = ds->ds[0].type;
 
-#define COPY_FIELD(fld) do { \
-  sstrncpy (inst->ident.fld, \
-      LU_IS_ANY (agg->ident.fld) ? vl->fld : agg->ident.fld, \
-      sizeof (inst->ident.fld)); \
-} while (0)
-
-  COPY_FIELD (host);
-  COPY_FIELD (plugin);
-  COPY_FIELD (plugin_instance);
-  COPY_FIELD (type);
-  COPY_FIELD (type_instance);
-
-#undef COPY_FIELD
+  agg_instance_create_name (inst, vl, agg);
 
   inst->min = NAN;
   inst->max = NAN;
@@ -240,8 +344,8 @@ static int agg_instance_read_func (agg_instance_t *inst, /* {{{ */
   int status;
 
   if (pi_prefix[0] != 0)
-    ssnprintf (vl->plugin_instance, sizeof (vl->plugin_instance), "%s-%s",
-        pi_prefix, func);
+    subst_string (vl->plugin_instance, sizeof (vl->plugin_instance),
+        pi_prefix, AGG_FUNC_PLACEHOLDER, func);
   else
     sstrncpy (vl->plugin_instance, func, sizeof (vl->plugin_instance));
 
@@ -263,7 +367,7 @@ static int agg_instance_read_func (agg_instance_t *inst, /* {{{ */
   vl->values = &v;
   vl->values_len = 1;
 
-  plugin_dispatch_values_secure (vl);
+  plugin_dispatch_values (vl);
 
   vl->values = NULL;
   vl->values_len = 0;
@@ -274,7 +378,6 @@ static int agg_instance_read_func (agg_instance_t *inst, /* {{{ */
 static int agg_instance_read (agg_instance_t *inst, cdtime_t t) /* {{{ */
 {
   value_list_t vl = VALUE_LIST_INIT;
-  char pi_prefix[DATA_MAX_NAME_LEN];
 
   /* Pre-set all the fields in the value list that will not change per
    * aggregation type (sum, average, ...). The struct will be re-used and must
@@ -291,39 +394,16 @@ static int agg_instance_read (agg_instance_t *inst, cdtime_t t) /* {{{ */
   }
   meta_data_add_boolean (vl.meta, "aggregation:created", 1);
 
-  if (LU_IS_ALL (inst->ident.host))
-    sstrncpy (vl.host, "global", sizeof (vl.host));
-  else
-    sstrncpy (vl.host, inst->ident.host, sizeof (vl.host));
-
-  sstrncpy (vl.plugin, "aggregation", sizeof (vl.plugin));
-
-  if (LU_IS_ALL (inst->ident.plugin))
-  {
-    if (LU_IS_ALL (inst->ident.plugin_instance))
-      sstrncpy (pi_prefix, "", sizeof (pi_prefix));
-    else
-      sstrncpy (pi_prefix, inst->ident.plugin_instance, sizeof (pi_prefix));
-  }
-  else
-  {
-    if (LU_IS_ALL (inst->ident.plugin_instance))
-      sstrncpy (pi_prefix, inst->ident.plugin, sizeof (pi_prefix));
-    else
-      ssnprintf (pi_prefix, sizeof (pi_prefix),
-          "%s-%s", inst->ident.plugin, inst->ident.plugin_instance);
-  }
-
+  sstrncpy (vl.host, inst->ident.host, sizeof (vl.host));
+  sstrncpy (vl.plugin, inst->ident.plugin, sizeof (vl.plugin));
   sstrncpy (vl.type, inst->ident.type, sizeof (vl.type));
-
-  if (!LU_IS_ALL (inst->ident.type_instance))
-    sstrncpy (vl.type_instance, inst->ident.type_instance,
-        sizeof (vl.type_instance));
+  sstrncpy (vl.type_instance, inst->ident.type_instance,
+      sizeof (vl.type_instance));
 
 #define READ_FUNC(func, rate) do { \
   if (inst->state_ ## func != NULL) { \
     agg_instance_read_func (inst, #func, rate, \
-        inst->state_ ## func, &vl, pi_prefix, t); \
+        inst->state_ ## func, &vl, inst->ident.plugin_instance, t); \
   } \
 } while (0)
 
@@ -424,14 +504,13 @@ static int agg_config_handle_group_by (oconfig_item_t const *ci, /* {{{ */
     value = ci->values[i].value.string;
 
     if (strcasecmp ("Host", value) == 0)
-      sstrncpy (agg->ident.host, LU_ANY, sizeof (agg->ident.host));
+      agg->group_by |= LU_GROUP_BY_HOST;
     else if (strcasecmp ("Plugin", value) == 0)
-      sstrncpy (agg->ident.plugin, LU_ANY, sizeof (agg->ident.plugin));
+      agg->group_by |= LU_GROUP_BY_PLUGIN;
     else if (strcasecmp ("PluginInstance", value) == 0)
-      sstrncpy (agg->ident.plugin_instance, LU_ANY,
-          sizeof (agg->ident.plugin_instance));
+      agg->group_by |= LU_GROUP_BY_PLUGIN_INSTANCE;
     else if (strcasecmp ("TypeInstance", value) == 0)
-      sstrncpy (agg->ident.type_instance, LU_ANY, sizeof (agg->ident.type_instance));
+      agg->group_by |= LU_GROUP_BY_TYPE_INSTANCE;
     else if (strcasecmp ("Type", value) == 0)
       ERROR ("aggregation plugin: Grouping by type is not supported.");
     else
@@ -457,12 +536,12 @@ static int agg_config_aggregation (oconfig_item_t *ci) /* {{{ */
   }
   memset (agg, 0, sizeof (*agg));
 
-  sstrncpy (agg->ident.host, LU_ALL, sizeof (agg->ident.host));
-  sstrncpy (agg->ident.plugin, LU_ALL, sizeof (agg->ident.plugin));
-  sstrncpy (agg->ident.plugin_instance, LU_ALL,
+  sstrncpy (agg->ident.host, "/.*/", sizeof (agg->ident.host));
+  sstrncpy (agg->ident.plugin, "/.*/", sizeof (agg->ident.plugin));
+  sstrncpy (agg->ident.plugin_instance, "/.*/",
       sizeof (agg->ident.plugin_instance));
-  sstrncpy (agg->ident.type, LU_ALL, sizeof (agg->ident.type));
-  sstrncpy (agg->ident.type_instance, LU_ALL,
+  sstrncpy (agg->ident.type, "/.*/", sizeof (agg->ident.type));
+  sstrncpy (agg->ident.type_instance, "/.*/",
       sizeof (agg->ident.type_instance));
 
   for (i = 0; i < ci->children_num; i++)
@@ -484,6 +563,14 @@ static int agg_config_aggregation (oconfig_item_t *ci) /* {{{ */
     else if (strcasecmp ("TypeInstance", child->key) == 0)
       cf_util_get_string_buffer (child, agg->ident.type_instance,
           sizeof (agg->ident.type_instance));
+    else if (strcasecmp ("SetHost", child->key) == 0)
+      cf_util_get_string (child, &agg->set_host);
+    else if (strcasecmp ("SetPlugin", child->key) == 0)
+      cf_util_get_string (child, &agg->set_plugin);
+    else if (strcasecmp ("SetPluginInstance", child->key) == 0)
+      cf_util_get_string (child, &agg->set_plugin_instance);
+    else if (strcasecmp ("SetTypeInstance", child->key) == 0)
+      cf_util_get_string (child, &agg->set_type_instance);
     else if (strcasecmp ("GroupBy", child->key) == 0)
       agg_config_handle_group_by (child, agg);
     else if (strcasecmp ("CalculateNum", child->key) == 0)
@@ -503,9 +590,18 @@ static int agg_config_aggregation (oconfig_item_t *ci) /* {{{ */
           "<Aggregation /> blocks and will be ignored.", child->key);
   }
 
+  if (agg_is_regex (agg->ident.host))
+    agg->regex_fields |= LU_GROUP_BY_HOST;
+  if (agg_is_regex (agg->ident.plugin))
+    agg->regex_fields |= LU_GROUP_BY_PLUGIN;
+  if (agg_is_regex (agg->ident.plugin_instance))
+    agg->regex_fields |= LU_GROUP_BY_PLUGIN_INSTANCE;
+  if (agg_is_regex (agg->ident.type_instance))
+    agg->regex_fields |= LU_GROUP_BY_TYPE_INSTANCE;
+
   /* Sanity checking */
   is_valid = 1;
-  if (LU_IS_ALL (agg->ident.type)) /* {{{ */
+  if (strcmp ("/.*/", agg->ident.type) == 0) /* {{{ */
   {
     ERROR ("aggregation plugin: It appears you did not specify the required "
         "\"Type\" option in this aggregation. "
@@ -518,20 +614,31 @@ static int agg_config_aggregation (oconfig_item_t *ci) /* {{{ */
   else if (strchr (agg->ident.type, '/') != NULL)
   {
     ERROR ("aggregation plugin: The \"Type\" may not contain the '/' "
-        "character. Especially, it may not be a wildcard. The current "
+        "character. Especially, it may not be a regex. The current "
         "value is \"%s\".", agg->ident.type);
     is_valid = 0;
   } /* }}} */
 
-  if (!LU_IS_ALL (agg->ident.host) /* {{{ */
-      && !LU_IS_ALL (agg->ident.plugin)
-      && !LU_IS_ALL (agg->ident.plugin_instance)
-      && !LU_IS_ALL (agg->ident.type_instance))
+  /* Check that there is at least one regex field without a grouping. {{{ */
+  if ((agg->regex_fields & ~agg->group_by) == 0)
   {
     ERROR ("aggregation plugin: An aggregation must contain at least one "
         "wildcard. This is achieved by leaving at least one of the \"Host\", "
         "\"Plugin\", \"PluginInstance\" and \"TypeInstance\" options blank "
-        "and not grouping by that field. "
+        "or using a regular expression and not grouping by that field. "
+        "(Host \"%s\", Plugin \"%s\", PluginInstance \"%s\", "
+        "Type \"%s\", TypeInstance \"%s\")",
+        agg->ident.host, agg->ident.plugin, agg->ident.plugin_instance,
+        agg->ident.type, agg->ident.type_instance);
+    is_valid = 0;
+  } /* }}} */
+
+  /* Check that all grouping fields are regular expressions. {{{ */
+  if (agg->group_by & ~agg->regex_fields)
+  {
+    ERROR ("aggregation plugin: Only wildcard fields (fields for which a "
+        "regular expression is configured or which are left blank) can be "
+        "specified in the \"GroupBy\" option. "
         "(Host \"%s\", Plugin \"%s\", PluginInstance \"%s\", "
         "Type \"%s\", TypeInstance \"%s\")",
         agg->ident.host, agg->ident.plugin, agg->ident.plugin_instance,
@@ -557,7 +664,7 @@ static int agg_config_aggregation (oconfig_item_t *ci) /* {{{ */
     return (-1);
   } /* }}} */
 
-  status = lookup_add (lookup, &agg->ident, agg);
+  status = lookup_add (lookup, &agg->ident, agg->group_by, agg);
   if (status != 0)
   {
     ERROR ("aggregation plugin: lookup_add failed with status %i.", status);
index 137c0f2f17239e4b1bc429704cb5623266456fbd..ee15b14f66cf8d1899f767aac09915ee9e895878 100644 (file)
@@ -70,6 +70,8 @@ struct apc_detail_s
 /* Default values for contacting daemon */
 static char *conf_host = NULL;
 static int   conf_port = NISPORT;
+/* Defaults to false for backwards compatibility. */
+static _Bool conf_report_seconds = 0;
 
 static int global_sockfd = -1;
 
@@ -81,9 +83,9 @@ static const char *config_keys[] =
 {
        "Host",
        "Port",
-       NULL
+       "ReportSeconds"
 };
-static int config_keys_num = 2;
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 static int net_shutdown (int *fd)
 {
@@ -360,7 +362,13 @@ static int apc_query_server (char *host, int port,
                        else if (strcmp ("LINEFREQ", key) == 0)
                                apcups_detail->linefreq = value;
                        else if (strcmp ("TIMELEFT", key) == 0)
+                       {
+                               /* Convert minutes to seconds if requested by
+                                * the user. */
+                               if (conf_report_seconds)
+                                       value *= 60.0;
                                apcups_detail->timeleft = value;
+                       }
 
                        tokptr = strtok_r (NULL, ":", &toksaveptr);
                } /* while (tokptr != NULL) */
@@ -403,6 +411,13 @@ static int apcups_config (const char *key, const char *value)
                }
                conf_port = port_tmp;
        }
+       else if (strcasecmp (key, "ReportSeconds") == 0)
+       {
+               if (IS_TRUE (value))
+                       conf_report_seconds = 1;
+               else
+                       conf_report_seconds = 0;
+       }
        else
        {
                return (-1);
index db81a4ad5241c36ad0135a035c47051a1f9c4afd..5af15684092acb486b57d90023f7203da1a9abaa 100644 (file)
@@ -28,6 +28,7 @@
 
 #Timeout      2
 #ReadThreads  5
+#WriteThreads 5
 
 ##############################################################################
 # Logging                                                                    #
 #@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
 #@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table
 #@BUILD_PLUGIN_TAIL_TRUE@LoadPlugin tail
+#@BUILD_PLUGIN_TAIL_CSV_TRUE@LoadPlugin tail_csv
 #@BUILD_PLUGIN_TAPE_TRUE@LoadPlugin tape
 #@BUILD_PLUGIN_TCPCONNS_TRUE@LoadPlugin tcpconns
 #@BUILD_PLUGIN_TEAMSPEAK2_TRUE@LoadPlugin teamspeak2
 #@BUILD_PLUGIN_WIRELESS_TRUE@LoadPlugin wireless
 #@BUILD_PLUGIN_WRITE_GRAPHITE_TRUE@LoadPlugin write_graphite
 #@BUILD_PLUGIN_WRITE_HTTP_TRUE@LoadPlugin write_http
-#@BUILD_PLUGIN_WRITE_REDIS_TRUE@LoadPlugin write_redis
 #@BUILD_PLUGIN_WRITE_MONGODB_TRUE@LoadPlugin write_mongodb
+#@BUILD_PLUGIN_WRITE_REDIS_TRUE@LoadPlugin write_redis
+#@BUILD_PLUGIN_WRITE_RIEMANN_TRUE@LoadPlugin write_riemann
 #@BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
 #@BUILD_PLUGIN_ZFS_ARC_TRUE@LoadPlugin zfs_arc
 
 #<Plugin apcups>
 #      Host "localhost"
 #      Port "3551"
+#      ReportSeconds true
 #</Plugin>
 
 #<Plugin ascent>
 #              #SelectDB "custdb0"
 #              Query "num_of_customers"
 #              #Query "..."
+#              #Host "..."
 #      </Database>
 #</Plugin>
 
 #      DaemonAddress "unix:/tmp/rrdcached.sock"
 #      DataDir "@localstatedir@/lib/@PACKAGE_NAME@/rrd"
 #      CreateFiles true
+#      CreateFilesAsync false
 #      CollectStatistics true
 #</Plugin>
 
 #<Plugin rrdtool>
 #      DataDir "@localstatedir@/lib/@PACKAGE_NAME@/rrd"
+#      CreateFilesAsync false
 #      CacheTimeout 120
 #      CacheFlush   900
+#      WritesPerSecond 50
 #</Plugin>
 
 #<Plugin sensors>
 #  </File>
 #</Plugin>
 
+#<Plugin "tail_csv">
+#   <Metric "dropped">
+#       Type "percent"
+#       Instance "dropped"
+#       ValueFrom 1
+#   </Metric>
+#   <Metric "mbps">
+#       Type "bytes"
+#       Instance "wire-realtime"
+#       ValueFrom 2
+#   </Metric>
+#   <Metric "alerts">
+#       Type "alerts_per_second"
+#       ValueFrom 3
+#   </Metric>
+#   <Metric "kpps">
+#       Type "kpackets_wire_per_sec.realtime"
+#       ValueFrom 4
+#   </Metric>
+#   <File "/var/log/snort/snort.stats">
+#       Instance "snort-eth0"
+#       Interval 600
+#       Collect "dropped" "mbps" "alerts" "kpps"
+#       TimeFrom 0
+#   </File>
+#</Plugin>
+
 #<Plugin tcpconns>
 #      ListeningPorts false
 #      LocalPort "25"
 #</Plugin>
 
 #<Plugin write_graphite>
-#  <Carbon>
+#  <Node "example">
 #    Host "localhost"
 #    Port "2003"
 #    Prefix "collectd"
 #    Postfix "collectd"
-#    StoreRates false
+#    StoreRates true
 #    AlwaysAppendDS false
 #    EscapeCharacter "_"
-#  </Carbon>
+#  </Node>
 #</Plugin>
 
 #<Plugin write_http>
 #      </URL>
 #</Plugin>
 
+#<Plugin write_mongodb>
+#      <Node "example">
+#              Host "localhost"
+#              Port "27017"
+#              Timeout 1000
+#              StoreRates false
+#              Database "auth_db"
+#              User "auth_user"
+#              Password "auth_passwd"
+#      </Node>
+#</Plugin>
+
 #<Plugin write_redis>
 #      <Node "example">
 #              Host "localhost"
 #      </Node>
 #</Plugin>
 
-#<Plugin write_mongodb>
+#<Plugin write_riemann>
 #      <Node "example">
 #              Host "localhost"
-#              Port "27017"
-#              Timeout 1000
-#              StoreRates false
+#              Port 5555
+#              Protocol UDP
+#              StoreRates true
+#              AlwaysAppendDS false
 #      </Node>
+#      Tag "foobar"
 #</Plugin>
 
 ##############################################################################
index 0521779cc17b1640f151e5b68a516741378bbd71..0a2a5f7790de1ca30e5b93aa714d97af5073b3d0 100644 (file)
@@ -121,6 +121,29 @@ use statements like the following:
 
   Include "/etc/collectd.d/*.conf"
 
+Starting with version 5.3, this may also be a block in which further options
+affecting the behavior of B<Include> may be specified. The following option is
+currently allowed:
+
+  <Include "/etc/collectd.d">
+    Filter "*.conf"
+  </Include>
+
+=over 4
+
+=item B<Filter> I<pattern>
+
+If the C<fnmatch> function is available on your system, a shell-like wildcard
+I<pattern> may be specified to filter which files to include. This may be used
+in combination with recursively including a directory to easily be able to
+arbitrarily mix configuration files and other documents (e.g. README files).
+The given example is similar to the first example above but includes all files
+matching C<*.conf> in any subdirectory of C</etc/collectd.d>:
+
+  Include "/etc/collectd.d" "*.conf"
+
+=back
+
 If more than one files are included by a single B<Include> option, the files
 will be included in lexicographical order (as defined by the C<strcmp>
 function). Thus, you can e.E<nbsp>g. use numbered prefixes to specify the
@@ -174,8 +197,14 @@ see L<collectd-threshold(5)> for details.
 
 Number of threads to start for reading plugins. The default value is B<5>, but
 you may want to increase this if you have more than five plugins that take a
-long time to read. Mostly those are plugin that do network-IO. Setting this to
-a value higher than the number of plugins you've loaded is totally useless.
+long time to read. Mostly those are plugins that do network-IO. Setting this to
+a value higher than the number of registered read callbacks is not recommended.
+
+=item B<WriteThreads> I<Num>
+
+Number of threads to start for dispatching value lists to write plugins. The
+default value is B<5>, but you may want to increase this if you have more than
+five plugins that may take relatively long to write to.
 
 =item B<Hostname> I<Name>
 
@@ -299,11 +328,72 @@ aggregations. The following options are valid inside B<Aggregation> blocks:
 Selects the value lists to be added to this aggregation. B<Type> must be a
 valid data set name, see L<types.db(5)> for details.
 
+If the string starts with and ends with a slash (C</>), the string is
+interpreted as a I<regular expression>. The regex flavor used are POSIX
+extended regular expressions as described in L<regex(7)>. Example usage:
+
+ Host "/^db[0-9]\\.example\\.com$/"
+
 =item B<GroupBy> B<Host>|B<Plugin>|B<PluginInstance>|B<TypeInstance>
 
 Group valued by the specified field. The B<GroupBy> option may be repeated to
 group by multiple fields.
 
+=item B<SetHost> I<Host>
+
+=item B<SetPlugin> I<Plugin>
+
+=item B<SetPluginInstance> I<PluginInstance>
+
+=item B<SetTypeInstance> I<TypeInstance>
+
+Sets the appropriate part of the identifier to the provided string.
+
+The I<PluginInstance> should include the placeholder C<%{aggregation}> which
+will be replaced with the aggregation function, e.g. "average". Not including
+the placeholder will result in duplication warnings and/or messed up values if
+more than one aggregation function are enabled.
+
+The following example calculates the average usage of all "even" CPUs:
+
+ <Plugin "aggregation">
+   <Aggregation>
+     Plugin "cpu"
+     PluginInstance "/[0,2,4,6,8]$/"
+     Type "cpu"
+     
+     SetPlugin "cpu"
+     SetPluginInstance "even-%{aggregation}"
+     
+     GroupBy "Host"
+     GroupBy "TypeInstance"
+     
+     CalculateAverage true
+   </Aggregation>
+ </Plugin>
+
+This will create the files:
+
+=over 4
+
+=item
+
+foo.example.com/cpu-even-average/cpu-idle
+
+=item
+
+foo.example.com/cpu-even-average/cpu-system
+
+=item
+
+foo.example.com/cpu-even-average/cpu-user
+
+=item
+
+...
+
+=back
+
 =item B<CalculateNum> B<true>|B<false>
 
 =item B<CalculateSum> B<true>|B<false>
@@ -573,6 +663,12 @@ B<apcupsd> can handle it.
 
 TCP-Port to connect to. Defaults to B<3551>.
 
+=item B<ReportSeconds> B<true|false>
+
+If set to B<true>, the time reported in the C<timeleft> metric will be
+converted to seconds. This is the recommended setting. If set to B<false>, the
+default for backwards compatibility, the time will be reported in minutes.
+
 =back
 
 =head2 Plugin C<ascent>
@@ -864,6 +960,19 @@ File that holds one or more SSL certificates. If you want to use HTTPS you will
 possibly need this option. What CA certificates come bundled with C<libcurl>
 and are checked by default depends on the distribution you use.
 
+=item B<Header> I<Header>
+
+A HTTP header to add to the request. Multiple headers are added if this option
+is specified more than once.
+
+=item B<Post> I<Body>
+
+Specifies that the HTTP operation should be a POST instead of a GET. The
+complete data to be posted is given as the argument.  This option will usually
+need to be accompanied by a B<Header> option to set an appropriate
+C<Content-Type> for the post body (e.g. to
+C<application/x-www-form-urlencoded>).
+
 =item B<MeasureResponseTime> B<true>|B<false>
 
 Measure response time for the request. If this setting is enabled, B<Match>
@@ -922,31 +1031,15 @@ The following options are valid within B<URL> blocks:
 Sets the plugin instance to I<Instance>.
 
 =item B<User> I<Name>
-
-Username to use if authorization is required to read the page.
-
 =item B<Password> I<Password>
-
-Password to use if authorization is required to read the page.
-
 =item B<VerifyPeer> B<true>|B<false>
-
-Enable or disable peer SSL certificate verification. See
-L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
-
 =item B<VerifyHost> B<true>|B<false>
-
-Enable or disable peer host name verification. If enabled, the plugin checks if
-the C<Common Name> or a C<Subject Alternate Name> field of the SSL certificate
-matches the host name provided by the B<URL> option. If this identity check
-fails, the connection is aborted. Obviously, only works when connecting to a
-SSL enabled server. Enabled by default.
-
 =item B<CACert> I<file>
+=item B<Header> I<Header>
+=item B<Post> I<Body>
 
-File that holds one or more SSL certificates. If you want to use HTTPS you will
-possibly need this option. What CA certificates come bundled with C<libcurl>
-and are checked by default depends on the distribution you use.
+These options behave exactly equivalent to the appropriate options of the
+I<cURL> plugin. Please see there for a detailed description.
 
 =back
 
@@ -1015,14 +1108,34 @@ host name setting.
 Use I<Instance> as the plugin instance when submitting values. Defaults to an
 empty string (no plugin instance).
 
+=item B<Namespace> I<Prefix> I<URL>
+
+If an XPath expression references namespaces, they must be specified
+with this option. I<Prefix> is the "namespace prefix" used in the XML document.
+I<URL> is the "namespace name", an URI reference uniquely identifying the
+namespace. The option can be repeated to register multiple namespaces.
+
+Examples:
+
+  Namespace "s" "http://schemas.xmlsoap.org/soap/envelope/"
+  Namespace "m" "http://www.w3.org/1998/Math/MathML"
+
 =item B<User> I<User>
+
 =item B<Password> I<Password>
+
 =item B<VerifyPeer> B<true>|B<false>
+
 =item B<VerifyHost> B<true>|B<false>
+
 =item B<CACert> I<CA Cert File>
 
+=item B<Header> I<Header>
+
+=item B<Post> I<Body>
+
 These options behave exactly equivalent to the appropriate options of the
-I<cURL> and I<cURL-JSON> plugins. Please see there for a detailed description.
+I<cURL plugin>. Please see there for a detailed description.
 
 =item E<lt>B<XPath> I<XPath-expression>E<gt>
 
@@ -1286,11 +1399,23 @@ documentation for each driver, somewhere at
 L<http://libdbi-drivers.sourceforge.net/>. However, the options "host",
 "username", "password", and "dbname" seem to be deE<nbsp>facto standards.
 
+DBDs can register two types of options: String options and numeric options. The
+plugin will use the C<dbi_conn_set_option> function when the configuration
+provides a string and the C<dbi_conn_require_option_numeric> function when the
+configuration provides a number. So these two lines will actually result in
+different calls being used:
+
+  DriverOption "Port" 1234      # numeric
+  DriverOption "Port" "1234"    # string
+
 Unfortunately, drivers are not too keen to report errors when an unknown option
 is passed to them, so invalid settings here may go unnoticed. This is not the
 plugin's fault, it will report errors if it gets them from the libraryE<nbsp>/
 the driver. If a driver complains about an option, the plugin will dump a
-complete list of all options understood by that driver to the log.
+complete list of all options understood by that driver to the log. There is no
+way to programatically find out if an option expects a string or a numeric
+argument, so you will have to refer to the appropriate DBD's documentation to
+find this out. Sorry.
 
 =item B<SelectDB> I<Database>
 
@@ -1305,6 +1430,11 @@ query needs to be defined I<before> this statement, i.E<nbsp>e. all query
 blocks you want to refer to must be placed above the database block you want to
 refer to them from.
 
+=item B<Host> I<Hostname>
+
+Sets the B<host> field of I<value lists> to I<Hostname> when dispatching
+values. Defaults to the global hostname setting.
+
 =back
 
 =head2 Plugin C<df>
@@ -2419,6 +2549,14 @@ Required capabilities are documented below.
       IgnoreSelectedSnapshot false
     </VolumeUsage>
     
+    <Quota>
+      Interval 60
+    </Quota>
+    
+    <Snapvault>
+      Interval 30
+    </Snapvault>
+    
     <System>
       Interval 30
       GetCPULoad     true
@@ -2426,6 +2564,13 @@ Required capabilities are documented below.
       GetDiskOps     true
       GetDiskIO      true
     </System>
+
+    <VFiler vfilerA>
+      Interval 60
+
+      SnapVault true
+      # ...
+    </VFiler>
    </Host>
  </Plugin>
 
@@ -2436,7 +2581,27 @@ The netapp plugin accepts the following configuration options:
 =item B<Host> I<Name>
 
 A host block defines one NetApp filer. It will appear in collectd with the name
-you specify here which does not have to be its real name nor its hostname.
+you specify here which does not have to be its real name nor its hostname (see
+the B<Address> option below).
+
+=item B<VFiler> I<Name>
+
+A B<VFiler> block may only be used inside a host block. It accepts all the
+same options as the B<Host> block (except for cascaded B<VFiler> blocks) and
+will execute all NetApp API commands in the context of the specified
+VFiler(R). It will appear in collectd with the name you specify here which
+does not have to be its real name. The VFiler name may be specified using the
+B<VFilerName> option. If this is not specified, it will default to the name
+you specify here.
+
+The VFiler block inherits all connection related settings from the surrounding
+B<Host> block (which appear before the B<VFiler> block) but they may be
+overwritten inside the B<VFiler> block.
+
+This feature is useful, for example, when using a VFiler as SnapVault target
+(supported since OnTap 8.1). In that case, the SnapVault statistics are not
+available in the host filer (vfiler0) but only in the respective VFiler
+context.
 
 =item B<Protocol> B<httpd>|B<http>
 
@@ -2480,6 +2645,19 @@ Mandatory
 
 Type: string
 
+=item B<VFilerName> I<Name>
+
+The name of the VFiler in which context to execute API commands. If not
+specified, the name provided to the B<VFiler> block will be used instead.
+
+Optional
+
+Type: string
+
+Default: name of the B<VFiler> block
+
+B<Note:> This option may only be used inside B<VFiler> blocks.
+
 =item B<Interval> I<Interval>
 
 B<TODO>
@@ -2810,6 +2988,38 @@ capacities will be selected anyway.
 
 =back
 
+=head3 The Quota block
+
+This will collect (tree) quota statistics (used disk space and number of used
+files). This mechanism is useful to get usage information for single qtrees.
+In case the quotas are not used for any other purpose, an entry similar to the
+following in C</etc/quotas> would be sufficient:
+
+  /vol/volA/some_qtree tree - - - - -
+
+After adding the entry, issue C<quota on -w volA> on the NetApp filer.
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Collect SnapVault(R) statistics every I<Seconds> seconds.
+
+=back
+
+=head3 The SnapVault block
+
+This will collect statistics about the time and traffic of SnapVault(R)
+transfers.
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Collect SnapVault(R) statistics every I<Seconds> seconds.
+
+=back
+
 =head2 Plugin C<netlink>
 
 The C<netlink> plugin uses a netlink socket to query the Linux kernel about
@@ -4467,6 +4677,16 @@ Enables or disables the creation of RRD files. If the daemon is not running
 locally, or B<DataDir> is set to a relative path, this will not work as
 expected. Default is B<true>.
 
+=item B<CreateFilesAsync> B<false>|B<true>
+
+When enabled, new RRD files are enabled asynchronously, using a separate thread
+that runs in the background. This prevents writes to block, which is a problem
+especially when many hundreds of files need to be created at once. However,
+since the purpose of creating the files asynchronously is I<not> to block until
+the file is available, values before the file is available will be discarded.
+When disabled (the default) files are created synchronously, blocking for a
+short while, while the file is being written.
+
 =item B<StepSize> I<Seconds>
 
 B<Force> the stepsize of newly created RRD-files. Ideally (and per default)
@@ -4524,8 +4744,18 @@ can safely ignore these settings.
 
 =item B<DataDir> I<Directory>
 
-Set the directory to store RRD-files under. Per default RRD-files are generated
-beneath the daemon's working directory, i.E<nbsp>e. the B<BaseDir>.
+Set the directory to store RRD files under. By default RRD files are generated
+beneath the daemon's working directory, i.e. the B<BaseDir>.
+
+=item B<CreateFilesAsync> B<false>|B<true>
+
+When enabled, new RRD files are enabled asynchronously, using a separate thread
+that runs in the background. This prevents writes to block, which is a problem
+especially when many hundreds of files need to be created at once. However,
+since the purpose of creating the files asynchronously is I<not> to block until
+the file is available, values before the file is available will be discarded.
+When disabled (the default) files are created synchronously, blocking for a
+short while, while the file is being written.
 
 =item B<StepSize> I<Seconds>
 
@@ -4546,7 +4776,7 @@ a very good reason to do so.
 
 The C<rrdtool plugin> calculates the number of PDPs per CDP based on the
 B<StepSize>, this setting and a timespan. This plugin creates RRD-files with
-three times five RRAs, i. e. five RRAs with the CFs B<MIN>, B<AVERAGE>, and
+three times five RRAs, i.e. five RRAs with the CFs B<MIN>, B<AVERAGE>, and
 B<MAX>. The five RRAs are optimized for graphs covering one hour, one day, one
 week, one month, and one year.
 
@@ -4942,6 +5172,97 @@ This optional setting sets the type instance to use.
 
 =back
 
+=head2 Plugin C<tail_csv>
+
+The I<tail_csv plugin> reads files in the CSV format, e.g. the statistics file
+written by I<Snort>.
+
+B<Synopsis:>
+
+ <Plugin "tail_csv">
+   <Metric "snort-dropped">
+       Type "percent"
+       Instance "dropped"
+       Index 1
+   </Metric>
+   <File "/var/log/snort/snort.stats">
+       Instance "snort-eth0"
+       Interval 600
+       Collect "snort-dropped"
+   </File>
+ </Plugin>
+
+The configuration consists of one or more B<Metric> blocks that define an index
+into the line of the CSV file and how this value is mapped to I<collectd's>
+internal representation. These are followed by one or more B<Instance> blocks
+which configure which file to read, in which interval and which metrics to
+extract.
+
+=over 4
+
+=item E<lt>B<Metric> I<Name>E<gt>
+
+The B<Metric> block configures a new metric to be extracted from the statistics
+file and how it is mapped on I<collectd's> data model. The string I<Name> is
+only used inside the B<Instance> blocks to refer to this block, so you can use
+one B<Metric> block for multiple CSV files.
+
+=over 4
+
+=item B<Type> I<Type>
+
+Configures which I<Type> to use when dispatching this metric. Types are defined
+in the L<types.db(5)> file, see the appropriate manual page for more
+information on specifying types. Only types with a single I<data source> are
+supported by the I<tail_csv plugin>. The information whether the value is an
+absolute value (i.e. a C<GAUGE>) or a rate (i.e. a C<DERIVE>) is taken from the
+I<Type's> definition.
+
+=item B<Instance> I<TypeInstance>
+
+If set, I<TypeInstance> is used to populate the type instance field of the
+created value lists. Otherwise, no type instance is used.
+
+=item B<ValueFrom> I<Index>
+
+Configure to read the value from the field with the zero-based index I<Index>.
+If the value is parsed as signed integer, unsigned integer or double depends on
+the B<Type> setting, see above.
+
+=back
+
+=item E<lt>B<File> I<Path>E<gt>
+
+Each B<File> block represents one CSV file to read. There must be at least one
+I<File> block but there can be multiple if you have multiple CSV files.
+
+=over 4
+
+=item B<Instance> I<PluginInstance>
+
+Sets the I<plugin instance> used when dispatching the values.
+
+=item B<Collect> I<Metric>
+
+Specifies which I<Metric> to collect. This option must be specified at least
+once, and you can use this option multiple times to specify more than one
+metric to be extracted from this statistic file.
+
+=item B<Interval> I<Seconds>
+
+Configures the interval in which to read values from this instance / file.
+Defaults to the plugin's default interval.
+
+=item B<TimeFrom> I<Index>
+
+Rather than using the local time when dispatching a value, read the timestamp
+from the field with the zero-based index I<Index>. The value is interpreted as
+seconds since epoch. The value is parsed as a double and may be factional.
+
+=back
+
+=back
+
 =head2 Plugin C<teamspeak2>
 
 The C<teamspeak2 plugin> connects to the query port of a teamspeak2 server and
@@ -5301,13 +5622,16 @@ minimize the number of network packets.
 Synopsis:
 
  <Plugin write_graphite>
-   <Carbon>
+   <Node "example">
      Host "localhost"
      Port "2003"
      Prefix "collectd"
-   </Carbon>
+   </Node>
  </Plugin>
 
+The configuration consists of one or more E<lt>B<Node>E<nbsp>I<Name>E<gt>
+blocks. Inside the B<Node> blocks, the following options are recognized:
+
 =over 4
 
 =item B<Host> I<Address>
@@ -5397,6 +5721,16 @@ If set to B<true> (the default), convert counter values to rates. If set to
 B<false> counter values are stored as is, i.e. as an increasing integer
 number.
 
+=item B<Database> I<Database>
+
+=item B<User> I<User>
+
+=item B<Password> I<Password>
+
+Sets the information used when authenticating to a I<MongoDB> database. The
+fields are optional (in which case no authentication is attempted), but if you
+want to use authentication all three fields must be set.
+
 =back
 
 =head2 Plugin C<write_http>
@@ -5463,6 +5797,77 @@ number.
 
 =back
 
+=head2 Plugin C<write_riemann>
+
+The I<write_riemann plugin> will send values to I<Riemann>, a powerfull stream
+aggregation and monitoring system. The plugin sends I<Protobuf> encoded data to
+I<Riemann> using UDP packets.
+
+Synopsis:
+
+ <Plugin "write_riemann">
+   <Node "example">
+     Host "localhost"
+     Port "5555"
+     Protocol UDP
+     StoreRates true
+     AlwaysAppendDS false
+     Delay 10
+   </Node>
+   Tag "foobar"
+ </Plugin>
+
+The following options are understood by the I<write_riemann plugin>:
+
+=over 4
+
+=item E<lt>B<Node> I<Name>E<gt>
+
+The plugin's configuration consists of one or more B<Node> blocks. Each block
+is given a unique I<Name> and specifies one connection to an instance of
+I<Riemann>. Indise the B<Node> block, the following per-connection options are
+understood:
+
+=over 4
+
+=item B<Host> I<Address>
+
+Hostname or address to connect to. Defaults to C<localhost>.
+
+=item B<Port> I<Service>
+
+Service name or port number to connect to. Defaults to C<5555>.
+
+=item B<Protocol> B<UDP>|B<TCP>
+
+Specify the protocol to use when communicating with I<Riemann>. Defaults to
+B<UDP>.
+
+=item B<StoreRates> B<true>|B<false>
+
+If set to B<true> (the default), convert counter values to rates. If set to
+B<false> counter values are stored as is, i.e. as an increasing integer number.
+
+This will be reflected in the C<ds_type> tag: If B<StoreRates> is enabled,
+converted values will have "rate" appended to the data source type, e.g.
+C<ds_type:derive:rate>.
+
+=item B<AlwaysAppendDS> B<false>|B<true>
+
+If set the B<true>, append the name of the I<Data Source> (DS) to the
+"service", i.e. the field that, together with the "host" field, uniquely
+identifies a metric in I<Riemann>. If set to B<false> (the default), this is
+only done when there is more than one DS.
+
+=back
+
+=item B<Tag> I<String>
+
+Add the given string as an additional tag to the metric being sent to
+I<Riemann>.
+
+=back
+
 =head1 THRESHOLD CONFIGURATION
 
 Starting with version C<4.3.0> collectd has support for B<monitoring>. By that
index c0994d19276e501a1170ed2cfde979c112eece79..7cb405c04bd43d25ec6b807f730419cbf39a93bb 100644 (file)
@@ -262,8 +262,6 @@ typedef int _Bool;
 # define COLLECTD_DEFAULT_INTERVAL 10.0
 #endif
 
-#define STATIC_ARRAY_LEN(array) (sizeof (array) / sizeof ((array)[0]))
-
 /* Remove GNU specific __attribute__ settings when using another compiler */
 #if !__GNUC__
 # define __attribute__(x) /**/
index 5de7d97b9ef3e9dfb090816aa2c1418567e4f43a..c41c4fe629dd14685e543964d9a7c05b7d8f399b 100644 (file)
@@ -361,6 +361,22 @@ int strunescape (char *buf, size_t buf_len)
        return (0);
 } /* int strunescape */
 
+size_t strstripnewline (char *buffer)
+{
+       size_t buffer_len = strlen (buffer);
+
+       while (buffer_len > 0)
+       {
+               if ((buffer[buffer_len - 1] != '\n')
+                               && (buffer[buffer_len - 1] != '\r'))
+                       break;
+               buffer[buffer_len] = 0;
+               buffer_len--;
+       }
+
+       return (buffer_len);
+} /* size_t strstripnewline */
+
 int escape_slashes (char *buf, int buf_len)
 {
        int i;
@@ -1403,3 +1419,35 @@ int strtoderive (const char *string, derive_t *ret_value) /* {{{ */
        *ret_value = tmp;
        return (0);
 } /* }}} int strtoderive */
+
+int strarray_add (char ***ret_array, size_t *ret_array_len, char const *str) /* {{{ */
+{
+       char **array;
+       size_t array_len = *ret_array_len;
+
+       if (str == NULL)
+               return (EINVAL);
+
+       array = realloc (*ret_array,
+            (array_len + 1) * sizeof (*array));
+       if (array == NULL)
+               return (ENOMEM);
+       *ret_array = array;
+
+       array[array_len] = strdup (str);
+       if (array[array_len] == NULL)
+               return (ENOMEM);
+
+       array_len++;
+        *ret_array_len = array_len;
+       return (0);
+} /* }}} int strarray_add */
+
+void strarray_free (char **array, size_t array_len) /* {{{ */
+{
+       size_t i;
+
+       for (i = 0; i < array_len; i++)
+               sfree (array[i]);
+       sfree (array);
+} /* }}} void strarray_free */
index 8a7d986534919efa65e449c17313882f363cce93..ae8e311f9983e8ff63441c0ab2936d287220ae67 100644 (file)
@@ -208,6 +208,13 @@ int strsubstitute (char *str, char c_from, char c_to);
  */
 int strunescape (char *buf, size_t buf_len);
 
+/**
+ * Removed trailing newline characters (CR and LF) from buffer, which must be
+ * null terminated. Returns the length of the resulting string.
+ */
+__attribute__((nonnull (1)))
+size_t strstripnewline (char *buffer);
+
 /*
  * NAME
  *   timeval_cmp
@@ -317,4 +324,7 @@ int service_name_to_port_number (const char *service_name);
  * failure. If failure is returned, ret_value is not touched. */
 int strtoderive (const char *string, derive_t *ret_value);
 
+int strarray_add (char ***ret_array, size_t *ret_array_len, char const *str);
+void strarray_free (char **array, size_t array_len);
+
 #endif /* COMMON_H */
index be777c52a02cbb0074c405fa7909ab0a3968c3b9..154c041cee2d8b6d01ff4021c723810993c91dac 100644 (file)
 # include <wordexp.h>
 #endif /* HAVE_WORDEXP_H */
 
+#if HAVE_FNMATCH_H
+# include <fnmatch.h>
+#endif /* HAVE_FNMATCH_H */
+
+#if HAVE_LIBGEN_H
+# include <libgen.h>
+#endif /* HAVE_LIBGEN_H */
+
 #define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str))
 
 /*
@@ -90,7 +98,7 @@ static cf_value_map_t cf_value_map[] =
        {"PluginDir",  dispatch_value_plugindir},
        {"LoadPlugin", dispatch_loadplugin}
 };
-static int cf_value_map_num = STATIC_ARRAY_LEN (cf_value_map);
+static int cf_value_map_num = STATIC_ARRAY_SIZE (cf_value_map);
 
 static cf_global_option_t cf_global_options[] =
 {
@@ -100,11 +108,12 @@ static cf_global_option_t cf_global_options[] =
        {"FQDNLookup",  NULL, "true"},
        {"Interval",    NULL, NULL},
        {"ReadThreads", NULL, "5"},
+       {"WriteThreads", NULL, "5"},
        {"Timeout",     NULL, "2"},
        {"PreCacheChain",  NULL, "PreCache"},
        {"PostCacheChain", NULL, "PostCache"}
 };
-static int cf_global_options_num = STATIC_ARRAY_LEN (cf_global_options);
+static int cf_global_options_num = STATIC_ARRAY_SIZE (cf_global_options);
 
 static int cf_default_typesdb = 1;
 
@@ -535,7 +544,8 @@ static int cf_ci_append_children (oconfig_item_t *dst, oconfig_item_t *src)
 } /* int cf_ci_append_children */
 
 #define CF_MAX_DEPTH 8
-static oconfig_item_t *cf_read_generic (const char *path, int depth);
+static oconfig_item_t *cf_read_generic (const char *path,
+               const char *pattern, int depth);
 
 static int cf_include_all (oconfig_item_t *root, int depth)
 {
@@ -546,9 +556,9 @@ static int cf_include_all (oconfig_item_t *root, int depth)
                oconfig_item_t *new;
                oconfig_item_t *old;
 
-               /* Ignore all blocks, including `Include' blocks. */
-               if (root->children[i].children_num != 0)
-                       continue;
+               char *pattern = NULL;
+
+               int j;
 
                if (strcasecmp (root->children[i].key, "Include") != 0)
                        continue;
@@ -562,7 +572,20 @@ static int cf_include_all (oconfig_item_t *root, int depth)
                        continue;
                }
 
-               new = cf_read_generic (old->values[0].value.string, depth + 1);
+               for (j = 0; j < old->children_num; ++j)
+               {
+                       oconfig_item_t *child = old->children + j;
+
+                       if (strcasecmp (child->key, "Filter") == 0)
+                               cf_util_get_string (child, &pattern);
+                       else
+                               ERROR ("configfile: Option `%s' not allowed in <Include> block.",
+                                               child->key);
+               }
+
+               new = cf_read_generic (old->values[0].value.string, pattern, depth + 1);
+               sfree (pattern);
+
                if (new == NULL)
                        return (-1);
 
@@ -579,13 +602,35 @@ static int cf_include_all (oconfig_item_t *root, int depth)
        return (0);
 } /* int cf_include_all */
 
-static oconfig_item_t *cf_read_file (const char *file, int depth)
+static oconfig_item_t *cf_read_file (const char *file,
+               const char *pattern, int depth)
 {
        oconfig_item_t *root;
        int status;
 
        assert (depth < CF_MAX_DEPTH);
 
+       if (pattern != NULL) {
+#if HAVE_FNMATCH_H && HAVE_LIBGEN_H
+               char *tmp = sstrdup (file);
+               char *filename = basename (tmp);
+
+               if ((filename != NULL) && (fnmatch (pattern, filename, 0) != 0)) {
+                       DEBUG ("configfile: Not including `%s' because it "
+                                       "does not match pattern `%s'.",
+                                       filename, pattern);
+                       free (tmp);
+                       return (NULL);
+               }
+
+               free (tmp);
+#else
+               ERROR ("configfile: Cannot apply pattern filter '%s' "
+                               "to file '%s': functions basename() and / or "
+                               "fnmatch() not available.", pattern, file);
+#endif /* HAVE_FNMATCH_H && HAVE_LIBGEN_H */
+       }
+
        root = oconfig_parse_file (file);
        if (root == NULL)
        {
@@ -608,7 +653,8 @@ static int cf_compare_string (const void *p1, const void *p2)
        return strcmp (*(const char **) p1, *(const char **) p2);
 }
 
-static oconfig_item_t *cf_read_dir (const char *dir, int depth)
+static oconfig_item_t *cf_read_dir (const char *dir,
+               const char *pattern, int depth)
 {
        oconfig_item_t *root = NULL;
        DIR *dh;
@@ -683,7 +729,7 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth)
                oconfig_item_t *temp;
                char *name = filenames[i];
 
-               temp = cf_read_generic (name, depth);
+               temp = cf_read_generic (name, pattern, depth);
                if (temp == NULL)
                {
                        /* An error should already have been reported. */
@@ -714,7 +760,8 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth)
  * simpler function is used which does not do any such expansion.
  */
 #if HAVE_WORDEXP_H
-static oconfig_item_t *cf_read_generic (const char *path, int depth)
+static oconfig_item_t *cf_read_generic (const char *path,
+               const char *pattern, int depth)
 {
        oconfig_item_t *root = NULL;
        int status;
@@ -767,9 +814,9 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth)
                }
 
                if (S_ISREG (statbuf.st_mode))
-                       temp = cf_read_file (path_ptr, depth);
+                       temp = cf_read_file (path_ptr, pattern, depth);
                else if (S_ISDIR (statbuf.st_mode))
-                       temp = cf_read_dir (path_ptr, depth);
+                       temp = cf_read_dir (path_ptr, pattern, depth);
                else
                {
                        WARNING ("configfile: %s is neither a file nor a "
@@ -794,7 +841,8 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth)
 /* #endif HAVE_WORDEXP_H */
 
 #else /* if !HAVE_WORDEXP_H */
-static oconfig_item_t *cf_read_generic (const char *path, int depth)
+static oconfig_item_t *cf_read_generic (const char *path,
+               const char *pattern, int depth)
 {
        struct stat statbuf;
        int status;
@@ -817,9 +865,9 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth)
        }
 
        if (S_ISREG (statbuf.st_mode))
-               return (cf_read_file (path, depth));
+               return (cf_read_file (path, pattern, depth));
        else if (S_ISDIR (statbuf.st_mode))
-               return (cf_read_dir (path, depth));
+               return (cf_read_dir (path, pattern, depth));
 
        ERROR ("configfile: %s is neither a file nor a directory.", path);
        return (NULL);
@@ -993,7 +1041,7 @@ int cf_read (char *filename)
        oconfig_item_t *conf;
        int i;
 
-       conf = cf_read_generic (filename, 0 /* depth */);
+       conf = cf_read_generic (filename, /* pattern = */ NULL, /* depth = */ 0);
        if (conf == NULL)
        {
                ERROR ("Unable to read config file %s.", filename);
index 32ba74527405a8393767a1901093fd571db01c3c..c6e2ae9c1f521350db159dc2922dafcb6fb54f41 100644 (file)
@@ -60,6 +60,8 @@ struct web_page_s /* {{{ */
   int   verify_peer;
   int   verify_host;
   char *cacert;
+  struct curl_slist *headers;
+  char *post_body;
   int   response_time;
 
   CURL *curl;
@@ -148,6 +150,8 @@ static void cc_web_page_free (web_page_t *wp) /* {{{ */
   sfree (wp->pass);
   sfree (wp->credentials);
   sfree (wp->cacert);
+  sfree (wp->post_body);
+  curl_slist_free_all (wp->headers);
 
   sfree (wp->buffer);
 
@@ -173,6 +177,23 @@ static int cc_config_add_string (const char *name, char **dest, /* {{{ */
   return (0);
 } /* }}} int cc_config_add_string */
 
+static int cc_config_append_string (const char *name, struct curl_slist **dest, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl plugin: `%s' needs exactly one string argument.", name);
+    return (-1);
+  }
+
+  *dest = curl_slist_append(*dest, ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* }}} int cc_config_append_string */
+
+
 static int cc_config_set_boolean (const char *name, int *dest, /* {{{ */
     oconfig_item_t *ci)
 {
@@ -405,6 +426,10 @@ static int cc_page_init_curl (web_page_t *wp) /* {{{ */
       wp->verify_host ? 2L : 0L);
   if (wp->cacert != NULL)
     curl_easy_setopt (wp->curl, CURLOPT_CAINFO, wp->cacert);
+  if (wp->headers != NULL)
+    curl_easy_setopt (wp->curl, CURLOPT_HTTPHEADER, wp->headers);
+  if (wp->post_body != NULL)
+    curl_easy_setopt (wp->curl, CURLOPT_POSTFIELDS, wp->post_body);
 
   return (0);
 } /* }}} int cc_page_init_curl */
@@ -466,6 +491,10 @@ static int cc_config_add_page (oconfig_item_t *ci) /* {{{ */
     else if (strcasecmp ("Match", child->key) == 0)
       /* Be liberal with failing matches => don't set `status'. */
       cc_config_add_match (page, child);
+    else if (strcasecmp ("Header", child->key) == 0)
+      status = cc_config_append_string ("Header", &page->headers, child);
+    else if (strcasecmp ("Post", child->key) == 0)
+      status = cc_config_add_string ("Post", &page->post_body, child);
     else
     {
       WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
index 19efc5d6fd54f0f119ff1ef0bfcec9d445a7b541..deee460bb3327e6291d6536190ac68e7a7d87cb0 100644 (file)
@@ -67,6 +67,8 @@ struct cj_s /* {{{ */
   _Bool verify_peer;
   _Bool verify_host;
   char *cacert;
+  struct curl_slist *headers;
+  char *post_body;
 
   CURL *curl;
   char curl_errbuf[CURL_ERROR_SIZE];
@@ -367,6 +369,8 @@ static void cj_free (void *arg) /* {{{ */
   sfree (db->pass);
   sfree (db->credentials);
   sfree (db->cacert);
+  sfree (db->post_body);
+  curl_slist_free_all (db->headers);
 
   sfree (db);
 } /* }}} void cj_free */
@@ -378,6 +382,22 @@ static c_avl_tree_t *cj_avl_create(void)
   return c_avl_create ((int (*) (const void *, const void *)) strcmp);
 }
 
+static int cj_config_append_string (const char *name, struct curl_slist **dest, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl_json plugin: `%s' needs exactly one string argument.", name);
+    return (-1);
+  }
+
+  *dest = curl_slist_append(*dest, ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* }}} int cj_config_append_string */
+
 static int cj_config_add_key (cj_t *db, /* {{{ */
                                    oconfig_item_t *ci)
 {
@@ -547,6 +567,10 @@ static int cj_init_curl (cj_t *db) /* {{{ */
                     db->verify_host ? 2L : 0L);
   if (db->cacert != NULL)
     curl_easy_setopt (db->curl, CURLOPT_CAINFO, db->cacert);
+  if (db->headers != NULL)
+    curl_easy_setopt (db->curl, CURLOPT_HTTPHEADER, db->headers);
+  if (db->post_body != NULL)
+    curl_easy_setopt (db->curl, CURLOPT_POSTFIELDS, db->post_body);
 
   return (0);
 } /* }}} int cj_init_curl */
@@ -608,6 +632,10 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_boolean (child, &db->verify_host);
     else if (strcasecmp ("CACert", child->key) == 0)
       status = cf_util_get_string (child, &db->cacert);
+    else if (strcasecmp ("Header", child->key) == 0)
+      status = cj_config_append_string ("Header", &db->headers, child);
+    else if (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);
     else
index 75f5cc33d946e74cfb3d77f006d4babfaadb6513..0b4130473f4e3359dc9cbb66059831ac09386a34 100644 (file)
@@ -28,6 +28,7 @@
 #include <libxml/parser.h>
 #include <libxml/tree.h>
 #include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
 
 #include <curl/curl.h>
 
@@ -58,6 +59,14 @@ struct cx_xpath_s /* {{{ */
 typedef struct cx_xpath_s cx_xpath_t;
 /* }}} */
 
+struct cx_namespace_s /* {{{ */
+{
+  char *prefix;
+  char *url;
+};
+typedef struct cx_namespace_s cx_namespace_t;
+/* }}} */
+
 struct cx_s /* {{{ */
 {
   char *instance;
@@ -70,6 +79,11 @@ struct cx_s /* {{{ */
   _Bool verify_peer;
   _Bool verify_host;
   char *cacert;
+  char *post_body;
+  struct curl_slist *headers;
+
+  cx_namespace_t *namespaces;
+  size_t namespaces_num;
 
   CURL *curl;
   char curl_errbuf[CURL_ERROR_SIZE];
@@ -160,6 +174,7 @@ static void cx_list_free (llist_t *list) /* {{{ */
 static void cx_free (void *arg) /* {{{ */
 {
   cx_t *db;
+  size_t i;
 
   DEBUG ("curl_xml plugin: cx_free (arg = %p);", arg);
 
@@ -184,10 +199,35 @@ static void cx_free (void *arg) /* {{{ */
   sfree (db->pass);
   sfree (db->credentials);
   sfree (db->cacert);
+  sfree (db->post_body);
+  curl_slist_free_all (db->headers);
+
+  for (i = 0; i < db->namespaces_num; i++)
+  {
+    sfree (db->namespaces[i].prefix);
+    sfree (db->namespaces[i].url);
+  }
+  sfree (db->namespaces);
 
   sfree (db);
 } /* }}} void cx_free */
 
+static int cx_config_append_string (const char *name, struct curl_slist **dest, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl_xml plugin: `%s' needs exactly one string argument.", name);
+    return (-1);
+  }
+
+  *dest = curl_slist_append(*dest, ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* }}} int cx_config_append_string */
+
 static int cx_check_type (const data_set_t *ds, cx_xpath_t *xpath) /* {{{ */
 {
   if (!ds)
@@ -225,7 +265,8 @@ static xmlXPathObjectPtr cx_evaluate_xpath (xmlXPathContextPtr xpath_ctx, /* {{{
 
 static int cx_if_not_text_node (xmlNodePtr node) /* {{{ */
 {
-  if (node->type == XML_TEXT_NODE || node->type == XML_ATTRIBUTE_NODE)
+  if (node->type == XML_TEXT_NODE || node->type == XML_ATTRIBUTE_NODE ||
+      node->type == XML_ELEMENT_NODE)
     return (0);
 
   WARNING ("curl_xml plugin: "
@@ -342,7 +383,7 @@ static int cx_handle_instance_xpath (xmlXPathContextPtr xpath_ctx, /* {{{ */
   memset (vl->type_instance, 0, sizeof (vl->type_instance));
 
   /* If the base xpath returns more than one block, the result is assumed to be
-   * a table. The `Instnce' option is not optional in this case. Check for the
+   * a table. The `Instance' option is not optional in this case. Check for the
    * condition and inform the user. */
   if (is_table && (vl->type_instance == NULL))
   {
@@ -520,6 +561,7 @@ static int cx_parse_stats_xml(xmlChar* xml, cx_t *db) /* {{{ */
   int status;
   xmlDocPtr doc;
   xmlXPathContextPtr xpath_ctx;
+  size_t i;
 
   /* Load the XML */
   doc = xmlParseDoc(xml);
@@ -537,6 +579,22 @@ static int cx_parse_stats_xml(xmlChar* xml, cx_t *db) /* {{{ */
     return (-1);
   }
 
+  for (i = 0; i < db->namespaces_num; i++)
+  {
+    cx_namespace_t const *ns = db->namespaces + i;
+    status = xmlXPathRegisterNs (xpath_ctx,
+        BAD_CAST ns->prefix, BAD_CAST ns->url);
+    if (status != 0)
+    {
+      ERROR ("curl_xml plugin: "
+          "unable to register NS with prefix=\"%s\" and href=\"%s\"\n",
+          ns->prefix, ns->url);
+      xmlXPathFreeContext(xpath_ctx);
+      xmlFreeDoc (doc);
+      return (status);
+    }
+  }
+
   status = cx_handle_parsed_xml (doc, xpath_ctx, db);
   /* Cleanup */
   xmlXPathFreeContext(xpath_ctx);
@@ -726,6 +784,46 @@ static int cx_config_add_xpath (cx_t *db, /* {{{ */
   return (status);
 } /* }}} int cx_config_add_xpath */
 
+static int cx_config_add_namespace (cx_t *db, /* {{{ */
+    oconfig_item_t *ci)
+{
+  cx_namespace_t *ns;
+
+  if ((ci->values_num != 2)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING)
+      || (ci->values[1].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl_xml plugin: The `Namespace' option "
+             "needs exactly two string arguments.");
+    return (EINVAL);
+  }
+
+  ns = realloc (db->namespaces, sizeof (*db->namespaces)
+      * (db->namespaces_num + 1));
+  if (ns == NULL)
+  {
+    ERROR ("curl_xml plugin: realloc failed.");
+    return (ENOMEM);
+  }
+  db->namespaces = ns;
+  ns = db->namespaces + db->namespaces_num;
+  memset (ns, 0, sizeof (*ns));
+
+  ns->prefix = strdup (ci->values[0].value.string);
+  ns->url = strdup (ci->values[1].value.string);
+
+  if ((ns->prefix == NULL) || (ns->url == NULL))
+  {
+    sfree (ns->prefix);
+    sfree (ns->url);
+    ERROR ("curl_xml plugin: strdup failed.");
+    return (ENOMEM);
+  }
+
+  db->namespaces_num++;
+  return (0);
+} /* }}} int cx_config_add_namespace */
+
 /* Initialize db->curl */
 static int cx_init_curl (cx_t *db) /* {{{ */
 {
@@ -769,6 +867,10 @@ static int cx_init_curl (cx_t *db) /* {{{ */
                     db->verify_host ? 2L : 0L);
   if (db->cacert != NULL)
     curl_easy_setopt (db->curl, CURLOPT_CAINFO, db->cacert);
+  if (db->headers != NULL)
+    curl_easy_setopt (db->curl, CURLOPT_HTTPHEADER, db->headers);
+  if (db->post_body != NULL)
+    curl_easy_setopt (db->curl, CURLOPT_POSTFIELDS, db->post_body);
 
   return (0);
 } /* }}} int cx_init_curl */
@@ -832,6 +934,12 @@ static int cx_config_add_url (oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string (child, &db->cacert);
     else if (strcasecmp ("xpath", child->key) == 0)
       status = cx_config_add_xpath (db, child);
+    else if (strcasecmp ("Header", child->key) == 0)
+      status = cx_config_append_string ("Header", &db->headers, child);
+    else if (strcasecmp ("Post", child->key) == 0)
+      status = cf_util_get_string (child, &db->post_body);
+    else if (strcasecmp ("Namespace", child->key) == 0)
+      status = cx_config_add_namespace (db, child);
     else
     {
       WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key);
index e15de3e89075bf1380b6f7ad57566f5a2948d7f5..80488d8478f6df99c748de2e4e39e872b1236701 100644 (file)
--- a/src/dbi.c
+++ b/src/dbi.c
@@ -1,6 +1,6 @@
 /**
  * collectd - src/dbi.c
- * Copyright (C) 2008,2009  Florian octo Forster
+ * Copyright (C) 2008-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
@@ -16,7 +16,7 @@
  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  *
  * Authors:
- *   Florian octo Forster <octo at verplant.org>
+ *   Florian octo Forster <octo at collectd.org>
  **/
 
 #include "collectd.h"
 struct cdbi_driver_option_s /* {{{ */
 {
   char *key;
-  char *value;
+  union
+  {
+    char *string;
+    int numeric;
+  } value;
+  _Bool is_numeric;
 };
 typedef struct cdbi_driver_option_s cdbi_driver_option_t; /* }}} */
 
@@ -43,6 +48,7 @@ struct cdbi_database_s /* {{{ */
   char *select_db;
 
   char *driver;
+  char *host;
   cdbi_driver_option_t *driver_options;
   size_t driver_options_num;
 
@@ -159,7 +165,8 @@ static void cdbi_database_free (cdbi_database_t *db) /* {{{ */
   for (i = 0; i < db->driver_options_num; i++)
   {
     sfree (db->driver_options[i].key);
-    sfree (db->driver_options[i].value);
+    if (!db->driver_options[i].is_numeric)
+      sfree (db->driver_options[i].value.string);
   }
   sfree (db->driver_options);
 
@@ -200,10 +207,11 @@ static int cdbi_config_add_database_driver_option (cdbi_database_t *db, /* {{{ *
 
   if ((ci->values_num != 2)
       || (ci->values[0].type != OCONFIG_TYPE_STRING)
-      || (ci->values[1].type != OCONFIG_TYPE_STRING))
+      || ((ci->values[1].type != OCONFIG_TYPE_STRING)
+        && (ci->values[1].type != OCONFIG_TYPE_NUMBER)))
   {
     WARNING ("dbi plugin: The `DriverOption' config option "
-        "needs exactly two string arguments.");
+        "needs exactly two arguments.");
     return (-1);
   }
 
@@ -217,6 +225,7 @@ static int cdbi_config_add_database_driver_option (cdbi_database_t *db, /* {{{ *
 
   db->driver_options = option;
   option = db->driver_options + db->driver_options_num;
+  memset (option, 0, sizeof (*option));
 
   option->key = strdup (ci->values[0].value.string);
   if (option->key == NULL)
@@ -225,12 +234,21 @@ static int cdbi_config_add_database_driver_option (cdbi_database_t *db, /* {{{ *
     return (-1);
   }
 
-  option->value = strdup (ci->values[1].value.string);
-  if (option->value == NULL)
+  if (ci->values[1].type == OCONFIG_TYPE_STRING)
   {
-    ERROR ("dbi plugin: strdup failed.");
-    sfree (option->key);
-    return (-1);
+    option->value.string = strdup (ci->values[1].value.string);
+    if (option->value.string == NULL)
+    {
+      ERROR ("dbi plugin: strdup failed.");
+      sfree (option->key);
+      return (-1);
+    }
+  }
+  else
+  {
+    assert (ci->values[1].type == OCONFIG_TYPE_NUMBER);
+    option->value.numeric = (int) (ci->values[1].value.number + .5);
+    option->is_numeric = 1;
   }
 
   db->driver_options_num++;
@@ -280,6 +298,8 @@ static int cdbi_config_add_database (oconfig_item_t *ci) /* {{{ */
     else if (strcasecmp ("Query", child->key) == 0)
       status = udb_query_pick_from_list (child, queries, queries_num,
           &db->queries, &db->queries_num);
+    else if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &db->host);
     else
     {
       WARNING ("dbi plugin: Option `%s' not allowed here.", child->key);
@@ -537,7 +557,7 @@ static int cdbi_read_database_query (cdbi_database_t *db, /* {{{ */
     sstrncpy (column_names[i], column_name, DATA_MAX_NAME_LEN);
   } /* }}} for (i = 0; i < column_num; i++) */
 
-  udb_query_prepare_result (q, prep_area, hostname_g,
+  udb_query_prepare_result (q, prep_area, (db->host ? db->host : hostname_g),
       /* plugin = */ "dbi", db->name,
       column_names, column_num, /* interval = */ 0);
 
@@ -661,24 +681,38 @@ static int cdbi_connect_database (cdbi_database_t *db) /* {{{ */
    * trouble finding out how to configure the plugin correctly.. */
   for (i = 0; i < db->driver_options_num; i++)
   {
-    DEBUG ("dbi plugin: cdbi_connect_database (%s): "
-        "key = %s; value = %s;",
-        db->name,
-        db->driver_options[i].key,
-        db->driver_options[i].value);
+    if (db->driver_options[i].is_numeric)
+    {
+      status = dbi_conn_set_option_numeric (connection,
+          db->driver_options[i].key, db->driver_options[i].value.numeric);
+      if (status != 0)
+      {
+        char errbuf[1024];
+        ERROR ("dbi plugin: cdbi_connect_database (%s): "
+            "dbi_conn_set_option_numeric (\"%s\", %i) failed: %s.",
+            db->name,
+            db->driver_options[i].key, db->driver_options[i].value.numeric,
+            cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+      }
+    }
+    else
+    {
+      status = dbi_conn_set_option (connection,
+          db->driver_options[i].key, db->driver_options[i].value.string);
+      if (status != 0)
+      {
+        char errbuf[1024];
+        ERROR ("dbi plugin: cdbi_connect_database (%s): "
+            "dbi_conn_set_option (\"%s\", \"%s\") failed: %s.",
+            db->name,
+            db->driver_options[i].key, db->driver_options[i].value.string,
+            cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+      }
+    }
 
-    status = dbi_conn_set_option (connection,
-        db->driver_options[i].key, db->driver_options[i].value);
     if (status != 0)
     {
-      char errbuf[1024];
-      const char *opt;
-
-      ERROR ("dbi plugin: cdbi_connect_database (%s): "
-          "dbi_conn_set_option (%s, %s) failed: %s.",
-          db->name,
-          db->driver_options[i].key, db->driver_options[i].value,
-          cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+      char const *opt;
 
       INFO ("dbi plugin: This is a list of all options understood "
           "by the `%s' driver:", db->driver);
index 3728d556531619bcdf093ca758fbcf0b88fe71b2..36d0a0c3450cc46b46d8a5d3725dedbaad9c2231 100644 (file)
@@ -109,7 +109,7 @@ static diskstats_t *disklist;
 /* #endif KERNEL_LINUX */
 
 #elif HAVE_LIBKSTAT
-#define MAX_NUMDISK 256
+#define MAX_NUMDISK 1024
 extern kstat_ctl_t *kc;
 static kstat_t *ksp[MAX_NUMDISK];
 static int numdisk = 0;
index c53a81d4b5d1e4261667954b079d26e36a797e07..cfabaaa235d6efe81da7896e4c51185d6afa08a3 100644 (file)
@@ -177,6 +177,25 @@ init_value_list (value_list_t *vl, virDomainPtr dom)
     vl->host[sizeof (vl->host) - 1] = '\0';
 } /* void init_value_list */
 
+static void
+memory_submit (gauge_t memory, virDomainPtr dom)
+{
+    value_t values[1];
+    value_list_t vl = VALUE_LIST_INIT;
+
+    init_value_list (&vl, dom);
+
+    values[0].gauge = memory;
+
+    vl.values = values;
+    vl.values_len = 1;
+
+    sstrncpy (vl.type, "memory", sizeof (vl.type));
+    sstrncpy (vl.type_instance, "total", sizeof (vl.type_instance));
+
+    plugin_dispatch_values (&vl);
+}
+
 static void
 cpu_submit (unsigned long long cpu_time,
             virDomainPtr dom, const char *type)
@@ -407,7 +426,7 @@ lv_read (void)
                  interface_devices[i].path);
 #endif
 
-    /* Get CPU usage, VCPU usage for each domain. */
+    /* Get CPU usage, memory, VCPU usage for each domain. */
     for (i = 0; i < nr_domains; ++i) {
         virDomainInfo info;
         virVcpuInfoPtr vinfo = NULL;
@@ -423,6 +442,7 @@ lv_read (void)
         }
 
         cpu_submit (info.cpuTime, domains[i], "virt_cpu_total");
+        memory_submit ((gauge_t) info.memory * 1024, domains[i]);
 
         vinfo = malloc (info.nrVirtCpu * sizeof (vinfo[0]));
         if (vinfo == NULL) {
index d35a0910a1d2c483d77922ddf0b94a8eacf48b11..f446d597de1f27610b0be1c7e2cfde8957509a79 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/netapp.c
  * Copyright (C) 2009,2010  Sven Trenkel
+ * Copyright (C) 2012-2013  teamix GmbH
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -21,7 +22,8 @@
  * DEALINGS IN THE SOFTWARE.
  *
  * Authors:
- *   Sven Trenkel <collectd at semidefinite.de>  
+ *   Sven Trenkel <collectd at semidefinite.de>
+ *   Sebastian 'tokkee' Harl <sh@teamix.net>
  **/
 
 #include "collectd.h"
@@ -181,16 +183,18 @@ typedef struct {
  *
  * \brief Configuration struct for volume usage data (free / used).
  */
-#define CFG_VOLUME_USAGE_DF             0x0002
-#define CFG_VOLUME_USAGE_SNAP           0x0004
-#define CFG_VOLUME_USAGE_ALL            0x0006
-#define HAVE_VOLUME_USAGE_NORM_FREE     0x0010
-#define HAVE_VOLUME_USAGE_NORM_USED     0x0020
-#define HAVE_VOLUME_USAGE_SNAP_RSVD     0x0040
-#define HAVE_VOLUME_USAGE_SNAP_USED     0x0080
-#define HAVE_VOLUME_USAGE_SIS_SAVED     0x0100
-#define HAVE_VOLUME_USAGE_ALL           0x01f0
-#define IS_VOLUME_USAGE_OFFLINE         0x0200
+#define CFG_VOLUME_USAGE_DF              0x0002
+#define CFG_VOLUME_USAGE_SNAP            0x0004
+#define CFG_VOLUME_USAGE_ALL             0x0006
+#define HAVE_VOLUME_USAGE_NORM_FREE      0x0010
+#define HAVE_VOLUME_USAGE_NORM_USED      0x0020
+#define HAVE_VOLUME_USAGE_SNAP_RSVD      0x0040
+#define HAVE_VOLUME_USAGE_SNAP_USED      0x0080
+#define HAVE_VOLUME_USAGE_SIS_SAVED      0x0100
+#define HAVE_VOLUME_USAGE_COMPRESS_SAVED 0x0200
+#define HAVE_VOLUME_USAGE_DEDUP_SAVED    0x0400
+#define HAVE_VOLUME_USAGE_ALL            0x07f0
+#define IS_VOLUME_USAGE_OFFLINE          0x0800
 struct data_volume_usage_s;
 typedef struct data_volume_usage_s data_volume_usage_t;
 struct data_volume_usage_s {
@@ -204,6 +208,8 @@ struct data_volume_usage_s {
        uint64_t snap_reserved;
        uint64_t snap_used;
        uint64_t sis_saved;
+       uint64_t compress_saved;
+       uint64_t dedup_saved;
 
        data_volume_usage_t *next;
 };
@@ -219,6 +225,26 @@ typedef struct {
 } cfg_volume_usage_t;
 /* }}} cfg_volume_usage_t */
 
+/*! Data types for quota statistics {{{
+ *
+ * \brief Persistent data for quota statistics
+ */
+typedef struct {
+       cna_interval_t interval;
+       na_elem_t *query;
+} cfg_quota_t;
+/* }}} cfg_quota_t */
+
+/*! Data types for SnapVault statistics {{{
+ *
+ * \brief Persistent data for SnapVault(R) statistics
+ */
+typedef struct {
+       cna_interval_t interval;
+       na_elem_t *query;
+} cfg_snapvault_t;
+/* }}} cfg_snapvault_t */
+
 /*! Data types for system statistics {{{
  *
  * \brief Persistent data for system performance counters
@@ -242,6 +268,7 @@ struct host_config_s {
        int port;
        char *username;
        char *password;
+       char *vfiler;
        cdtime_t interval;
 
        na_server_t *srv;
@@ -249,6 +276,8 @@ struct host_config_s {
        cfg_disk_t *cfg_disk;
        cfg_volume_perf_t *cfg_volume_perf;
        cfg_volume_usage_t *cfg_volume_usage;
+       cfg_quota_t *cfg_quota;
+       cfg_snapvault_t *cfg_snapvault;
        cfg_system_t *cfg_system;
 
        struct host_config_s *next;
@@ -354,6 +383,28 @@ static void free_cfg_volume_usage (cfg_volume_usage_t *cvu) /* {{{ */
        sfree (cvu);
 } /* }}} void free_cfg_volume_usage */
 
+static void free_cfg_quota (cfg_quota_t *q) /* {{{ */
+{
+       if (q == NULL)
+               return;
+
+       if (q->query != NULL)
+               na_elem_free (q->query);
+
+       sfree (q);
+} /* }}} void free_cfg_quota */
+
+static void free_cfg_snapvault (cfg_snapvault_t *sv) /* {{{ */
+{
+       if (sv == NULL)
+               return;
+
+       if (sv->query != NULL)
+               na_elem_free (sv->query);
+
+       sfree (sv);
+} /* }}} void free_cfg_snapvault */
+
 static void free_cfg_system (cfg_system_t *cs) /* {{{ */
 {
        if (cs == NULL)
@@ -378,11 +429,14 @@ static void free_host_config (host_config_t *hc) /* {{{ */
        sfree (hc->host);
        sfree (hc->username);
        sfree (hc->password);
+       sfree (hc->vfiler);
 
        free_cfg_disk (hc->cfg_disk);
        free_cfg_wafl (hc->cfg_wafl);
        free_cfg_volume_perf (hc->cfg_volume_perf);
        free_cfg_volume_usage (hc->cfg_volume_usage);
+       free_cfg_quota (hc->cfg_quota);
+       free_cfg_snapvault (hc->cfg_snapvault);
        free_cfg_system (hc->cfg_system);
 
        if (hc->srv != NULL)
@@ -1380,6 +1434,8 @@ static int cna_submit_volume_usage_data (const char *hostname, /* {{{ */
                uint64_t norm_used = v->norm_used;
                uint64_t norm_free = v->norm_free;
                uint64_t sis_saved = v->sis_saved;
+               uint64_t compress_saved = v->compress_saved;
+               uint64_t dedup_saved = v->dedup_saved;
                uint64_t snap_reserve_used = 0;
                uint64_t snap_reserve_free = v->snap_reserved;
                uint64_t snap_norm_used = v->snap_used;
@@ -1424,6 +1480,16 @@ static int cna_submit_volume_usage_data (const char *hostname, /* {{{ */
                                        "df_complex", "sis_saved",
                                        (double) sis_saved, /* timestamp = */ 0, interval);
 
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_COMPRESS_SAVED))
+                       submit_double (hostname, /* plugin instance = */ plugin_instance,
+                                       "df_complex", "compression_saved",
+                                       (double) compress_saved, /* timestamp = */ 0, interval);
+
+               if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_DEDUP_SAVED))
+                       submit_double (hostname, /* plugin instance = */ plugin_instance,
+                                       "df_complex", "dedup_saved",
+                                       (double) dedup_saved, /* timestamp = */ 0, interval);
+
                if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_USED))
                        submit_double (hostname, /* plugin instance = */ plugin_instance,
                                        "df_complex", "used",
@@ -1532,6 +1598,101 @@ static void cna_handle_volume_snap_usage(const host_config_t *host, /* {{{ */
        v->flags |= HAVE_VOLUME_USAGE_SNAP_USED;
 } /* }}} void cna_handle_volume_snap_usage */
 
+static void cna_handle_volume_sis_data (const host_config_t *host, /* {{{ */
+               data_volume_usage_t *v, na_elem_t *sis)
+{
+       const char *sis_state;
+       uint64_t sis_saved_reported;
+
+       if (na_elem_child(sis, "sis-info"))
+               sis = na_elem_child(sis, "sis-info");
+
+       sis_state = na_child_get_string(sis, "state");
+       if (sis_state == NULL)
+               return;
+
+       /* If SIS is not enabled, there's nothing left to do for this volume. */
+       if (strcmp ("enabled", sis_state) != 0)
+               return;
+
+       sis_saved_reported = na_child_get_uint64(sis, "size-saved", UINT64_MAX);
+       if (sis_saved_reported == UINT64_MAX)
+               return;
+
+       /* size-saved is actually a 32 bit number, so ... time for some guesswork. */
+       if ((sis_saved_reported >> 32) != 0) {
+               /* In case they ever fix this bug. */
+               v->sis_saved = sis_saved_reported;
+               v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
+       } else { /* really hacky work-around code. {{{ */
+               uint64_t sis_saved_percent;
+               uint64_t sis_saved_guess;
+               uint64_t overflow_guess;
+               uint64_t guess1, guess2, guess3;
+
+               /* Check if we have v->norm_used. Without it, we cannot calculate
+                * sis_saved_guess. */
+               if ((v->flags & HAVE_VOLUME_USAGE_NORM_USED) == 0)
+                       return;
+
+               sis_saved_percent = na_child_get_uint64(sis, "percentage-saved", UINT64_MAX);
+               if (sis_saved_percent > 100)
+                       return;
+
+               /* The "size-saved" value is a 32bit unsigned integer. This is a bug and
+                * will hopefully be fixed in later versions. To work around the bug, try
+                * to figure out how often the 32bit integer wrapped around by using the
+                * "percentage-saved" value. Because the percentage is in the range
+                * [0-100], this should work as long as the saved space does not exceed
+                * 400 GBytes. */
+               /* percentage-saved = size-saved / (size-saved + size-used) */
+               if (sis_saved_percent < 100)
+                       sis_saved_guess = v->norm_used * sis_saved_percent / (100 - sis_saved_percent);
+               else
+                       sis_saved_guess = v->norm_used;
+
+               overflow_guess = sis_saved_guess >> 32;
+               guess1 = overflow_guess ? ((overflow_guess - 1) << 32) + sis_saved_reported : sis_saved_reported;
+               guess2 = (overflow_guess << 32) + sis_saved_reported;
+               guess3 = ((overflow_guess + 1) << 32) + sis_saved_reported;
+
+               if (sis_saved_guess < guess2) {
+                       if ((sis_saved_guess - guess1) < (guess2 - sis_saved_guess))
+                               v->sis_saved = guess1;
+                       else
+                               v->sis_saved = guess2;
+               } else {
+                       if ((sis_saved_guess - guess2) < (guess3 - sis_saved_guess))
+                               v->sis_saved = guess2;
+                       else
+                               v->sis_saved = guess3;
+               }
+               v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
+       } /* }}} end of 32-bit workaround */
+} /* }}} void cna_handle_volume_sis_data */
+
+/* ONTAP >= 8.1 uses SIS for managing dedup and compression */
+static void cna_handle_volume_sis_saved (const host_config_t *host, /* {{{ */
+               data_volume_usage_t *v, na_elem_t *sis)
+{
+       uint64_t saved;
+
+       if (na_elem_child(sis, "sis-info"))
+               sis = na_elem_child(sis, "sis-info");
+
+       saved = na_child_get_uint64(sis, "compress-saved", UINT64_MAX);
+       if (saved != UINT64_MAX) {
+               v->compress_saved = saved;
+               v->flags |= HAVE_VOLUME_USAGE_COMPRESS_SAVED;
+       }
+
+       saved = na_child_get_uint64(sis, "dedup-saved", UINT64_MAX);
+       if (saved != UINT64_MAX) {
+               v->dedup_saved = saved;
+               v->flags |= HAVE_VOLUME_USAGE_DEDUP_SAVED;
+       }
+} /* }}} void cna_handle_volume_sis_saved */
+
 static int cna_handle_volume_usage_data (const host_config_t *host, /* {{{ */
                cfg_volume_usage_t *cfg_volume, na_elem_t *data)
 {
@@ -1559,8 +1720,6 @@ static int cna_handle_volume_usage_data (const host_config_t *host, /* {{{ */
                uint64_t value;
 
                na_elem_t *sis;
-               const char *sis_state;
-               uint64_t sis_saved_reported;
 
                volume_name = na_child_get_string (elem_volume, "name");
                if (volume_name == NULL)
@@ -1602,74 +1761,10 @@ static int cna_handle_volume_usage_data (const host_config_t *host, /* {{{ */
                }
 
                sis = na_elem_child(elem_volume, "sis");
-               if (sis == NULL)
-                       continue;
-
-               if (na_elem_child(sis, "sis-info"))
-                       sis = na_elem_child(sis, "sis-info");
-               
-               sis_state = na_child_get_string(sis, "state");
-               if (sis_state == NULL)
-                       continue;
-
-               /* If SIS is not enabled, there's nothing left to do for this volume. */
-               if (strcmp ("enabled", sis_state) != 0)
-                       continue;
-
-               sis_saved_reported = na_child_get_uint64(sis, "size-saved", UINT64_MAX);
-               if (sis_saved_reported == UINT64_MAX)
-                       continue;
-
-               /* size-saved is actually a 32 bit number, so ... time for some guesswork. */
-               if ((sis_saved_reported >> 32) != 0) {
-                       /* In case they ever fix this bug. */
-                       v->sis_saved = sis_saved_reported;
-                       v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
-               } else { /* really hacky work-around code. {{{ */
-                       uint64_t sis_saved_percent;
-                       uint64_t sis_saved_guess;
-                       uint64_t overflow_guess;
-                       uint64_t guess1, guess2, guess3;
-
-                       /* Check if we have v->norm_used. Without it, we cannot calculate
-                        * sis_saved_guess. */
-                       if ((v->flags & HAVE_VOLUME_USAGE_NORM_USED) == 0)
-                               continue;
-
-                       sis_saved_percent = na_child_get_uint64(sis, "percentage-saved", UINT64_MAX);
-                       if (sis_saved_percent > 100)
-                               continue;
-
-                       /* The "size-saved" value is a 32bit unsigned integer. This is a bug and
-                        * will hopefully be fixed in later versions. To work around the bug, try
-                        * to figure out how often the 32bit integer wrapped around by using the
-                        * "percentage-saved" value. Because the percentage is in the range
-                        * [0-100], this should work as long as the saved space does not exceed
-                        * 400 GBytes. */
-                       /* percentage-saved = size-saved / (size-saved + size-used) */
-                       if (sis_saved_percent < 100)
-                               sis_saved_guess = v->norm_used * sis_saved_percent / (100 - sis_saved_percent);
-                       else
-                               sis_saved_guess = v->norm_used;
-
-                       overflow_guess = sis_saved_guess >> 32;
-                       guess1 = overflow_guess ? ((overflow_guess - 1) << 32) + sis_saved_reported : sis_saved_reported;
-                       guess2 = (overflow_guess << 32) + sis_saved_reported;
-                       guess3 = ((overflow_guess + 1) << 32) + sis_saved_reported;
-
-                       if (sis_saved_guess < guess2) {
-                               if ((sis_saved_guess - guess1) < (guess2 - sis_saved_guess))
-                                       v->sis_saved = guess1;
-                               else
-                                       v->sis_saved = guess2;
-                       } else {
-                               if ((sis_saved_guess - guess2) < (guess3 - sis_saved_guess))
-                                       v->sis_saved = guess2;
-                               else
-                                       v->sis_saved = guess3;
-                       }
-                       v->flags |= HAVE_VOLUME_USAGE_SIS_SAVED;
-               } /* }}} end of 32-bit workaround */
+               if (sis != NULL) {
+                       cna_handle_volume_sis_data (host, v, sis);
+                       cna_handle_volume_sis_saved (host, v, sis);
+               }
        } /* for (elem_volume) */
 
        return (cna_submit_volume_usage_data (host->name, cfg_volume,
@@ -1735,6 +1830,292 @@ static int cna_query_volume_usage (host_config_t *host) /* {{{ */
        return (status);
 } /* }}} int cna_query_volume_usage */
 
+/* Data corresponding to <Quota /> */
+static int cna_handle_quota_data (const host_config_t *host, /* {{{ */
+               cfg_quota_t *cfg_quota, na_elem_t *data)
+{
+       na_elem_t *elem_quota;
+       na_elem_t *elem_quotas;
+       na_elem_iter_t iter_quota;
+
+       elem_quotas = na_elem_child (data, "quotas");
+       if (elem_quotas == NULL)
+       {
+               ERROR ("netapp plugin: cna_handle_quota_data: "
+                               "na_elem_child (\"quotas\") failed "
+                               "for host %s.", host->name);
+               return (-1);
+       }
+
+       iter_quota = na_child_iterator (elem_quotas);
+       for (elem_quota = na_iterator_next (&iter_quota);
+                       elem_quota != NULL;
+                       elem_quota = na_iterator_next (&iter_quota))
+       {
+               const char *quota_type, *volume_name, *tree_name;
+               uint64_t value;
+
+               char plugin_instance[DATA_MAX_NAME_LEN];
+
+               quota_type = na_child_get_string (elem_quota, "quota-type");
+               if (quota_type == NULL)
+                       continue;
+
+               /* possible TODO: support other types as well */
+               if (strcmp (quota_type, "tree") != 0)
+                       continue;
+
+               tree_name = na_child_get_string (elem_quota, "tree");
+               if ((tree_name == NULL) || (*tree_name == '\0'))
+                       continue;
+
+               volume_name = na_child_get_string (elem_quota, "volume");
+               if (volume_name == NULL)
+                       continue;
+
+               ssnprintf (plugin_instance, sizeof (plugin_instance),
+                               "quota-%s-%s", volume_name, tree_name);
+
+               value = na_child_get_uint64 (elem_quota, "disk-used", UINT64_MAX);
+               if (value != UINT64_MAX) {
+                       value *= 1024; /* disk-used reports kilobytes */
+                       submit_double (host->name, plugin_instance,
+                                       /* type = */ "df_complex", /* type instance = */ NULL,
+                                       (double)value, /* timestamp = */ 0,
+                                       host->cfg_quota->interval.interval);
+               }
+
+               value = na_child_get_uint64 (elem_quota, "files-used", UINT64_MAX);
+               if (value != UINT64_MAX) {
+                       submit_double (host->name, plugin_instance,
+                                       /* type = */ "files", /* type instance = */ NULL,
+                                       (double)value, /* timestamp = */ 0,
+                                       host->cfg_quota->interval.interval);
+               }
+       } /* for (elem_quota) */
+
+       return (0);
+} /* }}} int cna_handle_volume_usage_data */
+
+static int cna_setup_quota (cfg_quota_t *cq) /* {{{ */
+{
+       if (cq == NULL)
+               return (EINVAL);
+
+       if (cq->query != NULL)
+               return (0);
+
+       cq->query = na_elem_new ("quota-report");
+       if (cq->query == NULL)
+       {
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+
+       return (0);
+} /* }}} int cna_setup_quota */
+
+static int cna_query_quota (host_config_t *host) /* {{{ */
+{
+       na_elem_t *data;
+       int status;
+       cdtime_t now;
+
+       if (host == NULL)
+               return (EINVAL);
+
+       /* If the user did not configure quota statistics, return without
+        * doing anything. */
+       if (host->cfg_quota == NULL)
+               return (0);
+
+       now = cdtime ();
+       if ((host->cfg_quota->interval.interval + host->cfg_quota->interval.last_read) > now)
+               return (0);
+
+       status = cna_setup_quota (host->cfg_quota);
+       if (status != 0)
+               return (status);
+       assert (host->cfg_quota->query != NULL);
+
+       data = na_server_invoke_elem (host->srv, host->cfg_quota->query);
+       if (na_results_status (data) != NA_OK)
+       {
+               ERROR ("netapp plugin: cna_query_quota: na_server_invoke_elem failed for host %s: %s",
+                               host->name, na_results_reason (data));
+               na_elem_free (data);
+               return (-1);
+       }
+
+       status = cna_handle_quota_data (host, host->cfg_quota, data);
+
+       if (status == 0)
+               host->cfg_quota->interval.last_read = now;
+
+       na_elem_free (data);
+       return (status);
+} /* }}} int cna_query_quota */
+
+/* Data corresponding to <SnapVault /> */
+static int cna_handle_snapvault_data (const char *hostname, /* {{{ */
+               cfg_snapvault_t *cfg_snapvault, na_elem_t *data, cdtime_t interval)
+{
+       na_elem_t *status;
+       na_elem_iter_t status_iter;
+
+       status = na_elem_child (data, "status-list");
+       if (! status) {
+               ERROR ("netapp plugin: SnapVault status record missing status-list");
+               return (0);
+       }
+
+       status_iter = na_child_iterator (status);
+       for (status = na_iterator_next (&status_iter);
+                       status != NULL;
+                       status = na_iterator_next (&status_iter))
+       {
+               const char *dest_sys, *dest_path, *src_sys, *src_path;
+               char plugin_instance[DATA_MAX_NAME_LEN];
+               uint64_t value;
+
+               dest_sys  = na_child_get_string (status, "destination-system");
+               dest_path = na_child_get_string (status, "destination-path");
+               src_sys   = na_child_get_string (status, "source-system");
+               src_path  = na_child_get_string (status, "source-path");
+
+               if ((! dest_sys) || (! dest_path) || (! src_sys) || (! src_path))
+                       continue;
+
+               value = na_child_get_uint64 (status, "lag-time", UINT64_MAX);
+               if (value == UINT64_MAX) /* no successful baseline transfer yet */
+                       continue;
+
+               /* possible TODO: make plugin instance configurable */
+               ssnprintf (plugin_instance, sizeof (plugin_instance),
+                               "snapvault-%s", dest_path);
+               submit_double (hostname, plugin_instance, /* type = */ "delay", NULL,
+                               (double)value, /* timestamp = */ 0, interval);
+
+               value = na_child_get_uint64 (status, "last-transfer-duration", UINT64_MAX);
+               if (value != UINT64_MAX)
+                       submit_double (hostname, plugin_instance, /* type = */ "duration", "last_transfer",
+                                       (double)value, /* timestamp = */ 0, interval);
+
+               value = na_child_get_uint64 (status, "transfer-progress", UINT64_MAX);
+               if (value == UINT64_MAX)
+                       value = na_child_get_uint64 (status, "last-transfer-size", UINT64_MAX);
+               if (value != UINT64_MAX) {
+                       value *= 1024; /* this is kilobytes */
+                       submit_derive (hostname, plugin_instance, /* type = */ "if_rx_octets", "transferred",
+                                       value, /* timestamp = */ 0, interval);
+               }
+       } /* for (status) */
+
+       return (0);
+} /* }}} int cna_handle_snapvault_data */
+
+static int cna_handle_snapvault_iter (host_config_t *host, /* {{{ */
+               na_elem_t *data)
+{
+       const char *tag;
+
+       uint32_t records_count;
+       uint32_t i;
+
+       records_count = na_child_get_uint32 (data, "records", UINT32_MAX);
+       if (records_count == UINT32_MAX)
+               return 0;
+
+       tag = na_child_get_string (data, "tag");
+       if (! tag)
+               return 0;
+
+       DEBUG ("netapp plugin: Iterating %u SV records (tag = %s)", records_count, tag);
+
+       for (i = 0; i < records_count; ++i) {
+               na_elem_t *elem;
+
+               elem = na_server_invoke (host->srv,
+                               "snapvault-secondary-relationship-status-list-iter-next",
+                               "maximum", "1", "tag", tag, NULL);
+
+               if (na_results_status (elem) != NA_OK)
+               {
+                       ERROR ("netapp plugin: cna_handle_snapvault_iter: "
+                                       "na_server_invoke failed for host %s: %s",
+                                       host->name, na_results_reason (data));
+                       na_elem_free (elem);
+                       return (-1);
+               }
+
+               cna_handle_snapvault_data (host->name, host->cfg_snapvault, elem,
+                               host->cfg_snapvault->interval.interval);
+               na_elem_free (elem);
+       }
+
+       na_elem_free (na_server_invoke (host->srv,
+                       "snapvault-secondary-relationship-status-list-iter-end",
+                       "tag", tag, NULL));
+       return (0);
+} /* }}} int cna_handle_snapvault_iter */
+
+static int cna_setup_snapvault (cfg_snapvault_t *sv) /* {{{ */
+{
+       if (sv == NULL)
+               return (EINVAL);
+
+       if (sv->query != NULL)
+               return (0);
+
+       sv->query = na_elem_new ("snapvault-secondary-relationship-status-list-iter-start");
+       if (sv->query == NULL)
+       {
+               ERROR ("netapp plugin: na_elem_new failed.");
+               return (-1);
+       }
+
+       return (0);
+} /* }}} int cna_setup_snapvault */
+
+static int cna_query_snapvault (host_config_t *host) /* {{{ */
+{
+       na_elem_t *data;
+       int status;
+       cdtime_t now;
+
+       if (host == NULL)
+               return EINVAL;
+
+       if (host->cfg_snapvault == NULL)
+               return 0;
+
+       now = cdtime ();
+       if ((host->cfg_snapvault->interval.interval + host->cfg_snapvault->interval.last_read) > now)
+               return (0);
+
+       status = cna_setup_snapvault (host->cfg_snapvault);
+       if (status != 0)
+               return (status);
+       assert (host->cfg_snapvault->query != NULL);
+
+       data = na_server_invoke_elem (host->srv, host->cfg_snapvault->query);
+       if (na_results_status (data) != NA_OK)
+       {
+               ERROR ("netapp plugin: cna_query_snapvault: na_server_invoke_elem failed for host %s: %s",
+                               host->name, na_results_reason (data));
+               na_elem_free (data);
+               return (-1);
+       }
+
+       status = cna_handle_snapvault_iter (host, data);
+
+       if (status == 0)
+               host->cfg_snapvault->interval.last_read = now;
+
+       na_elem_free (data);
+       return (status);
+} /* }}} int cna_query_snapvault */
+
 /* Data corresponding to <System /> */
 static int cna_handle_system_data (const char *hostname, /* {{{ */
                cfg_system_t *cfg_system, na_elem_t *data, int interval)
@@ -2141,6 +2522,40 @@ static void cna_config_volume_usage_default (cfg_volume_usage_t *cvu, /* {{{ */
                ignorelist_set_invert (il, /* invert = */ 1);
 } /* }}} void cna_config_volume_usage_default */
 
+/* Corresponds to a <Quota /> block */
+static int cna_config_quota (host_config_t *host, oconfig_item_t *ci) /* {{{ */
+{
+       cfg_quota_t *cfg_quota;
+       int i;
+
+       if ((host == NULL) || (ci == NULL))
+               return (EINVAL);
+
+       if (host->cfg_quota == NULL)
+       {
+               cfg_quota = malloc (sizeof (*cfg_quota));
+               if (cfg_quota == NULL)
+                       return (ENOMEM);
+               memset (cfg_quota, 0, sizeof (*cfg_quota));
+               cfg_quota->query = NULL;
+
+               host->cfg_quota = cfg_quota;
+       }
+       cfg_quota = host->cfg_quota;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+
+               if (strcasecmp (item->key, "Interval") == 0)
+                       cna_config_get_interval (item, &cfg_quota->interval);
+               else
+                       WARNING ("netapp plugin: The option %s is not allowed within "
+                                       "`Quota' blocks.", item->key);
+       }
+
+       return (0);
+} /* }}} int cna_config_quota */
+
 /* Corresponds to a <Disks /> block */
 static int cna_config_disk(host_config_t *host, oconfig_item_t *ci) { /* {{{ */
        cfg_disk_t *cfg_disk;
@@ -2316,6 +2731,42 @@ static int cna_config_volume_usage(host_config_t *host, /* {{{ */
        return (0);
 } /* }}} int cna_config_volume_usage */
 
+/* Corresponds to a <SnapVault /> block */
+static int cna_config_snapvault (host_config_t *host, /* {{{ */
+               const oconfig_item_t *ci)
+{
+       cfg_snapvault_t *cfg_snapvault;
+       int i;
+
+       if ((host == NULL) || (ci == NULL))
+               return EINVAL;
+
+       if (host->cfg_snapvault == NULL)
+       {
+               cfg_snapvault = malloc (sizeof (*cfg_snapvault));
+               if (cfg_snapvault == NULL)
+                       return ENOMEM;
+               memset (cfg_snapvault, 0, sizeof (*cfg_snapvault));
+               cfg_snapvault->query = NULL;
+
+               host->cfg_snapvault = cfg_snapvault;
+       }
+
+       cfg_snapvault = host->cfg_snapvault;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+
+               if (strcasecmp (item->key, "Interval") == 0)
+                       cna_config_get_interval (item, &cfg_snapvault->interval);
+               else
+                       WARNING ("netapp plugin: The option %s is not allowed within "
+                                       "`SnapVault' blocks.", item->key);
+       }
+
+       return 0;
+} /* }}} int cna_config_snapvault */
+
 /* Corresponds to a <System /> block */
 static int cna_config_system (host_config_t *host, /* {{{ */
                oconfig_item_t *ci)
@@ -2372,38 +2823,131 @@ static int cna_config_system (host_config_t *host, /* {{{ */
 } /* }}} int cna_config_system */
 
 /* Corresponds to a <Host /> block. */
-static host_config_t *cna_config_host (const oconfig_item_t *ci) /* {{{ */
+static host_config_t *cna_alloc_host (void) /* {{{ */
 {
-       oconfig_item_t *item;
        host_config_t *host;
-       int status;
-       int i;
-       
-       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
-               WARNING("netapp plugin: \"Host\" needs exactly one string argument. Ignoring host block.");
-               return 0;
-       }
 
        host = malloc(sizeof(*host));
+       if (! host)
+               return (NULL);
        memset (host, 0, sizeof (*host));
+
        host->name = NULL;
        host->protocol = NA_SERVER_TRANSPORT_HTTPS;
        host->host = NULL;
        host->username = NULL;
        host->password = NULL;
+       host->vfiler = NULL;
        host->srv = NULL;
        host->cfg_wafl = NULL;
        host->cfg_disk = NULL;
        host->cfg_volume_perf = NULL;
        host->cfg_volume_usage = NULL;
+       host->cfg_quota = NULL;
+       host->cfg_snapvault = NULL;
        host->cfg_system = NULL;
 
-       status = cf_util_get_string (ci, &host->name);
-       if (status != 0)
-       {
-               sfree (host);
+       return (host);
+} /* }}} host_config_t *cna_alloc_host */
+
+static host_config_t *cna_shallow_clone_host (host_config_t *host) /* {{{ */
+{
+       host_config_t *clone;
+
+       if (host == NULL)
                return (NULL);
+
+       clone = cna_alloc_host ();
+       if (clone == NULL)
+               return (NULL);
+
+       if (host->name != NULL) {
+               clone->name = strdup (host->name);
+               if (clone->name == NULL) {
+                       free_host_config (clone);
+                       return NULL;
+               }
+       }
+
+       clone->protocol = host->protocol;
+
+       if (host->host != NULL) {
+               clone->host = strdup (host->host);
+               if (clone->host == NULL) {
+                       free_host_config (clone);
+                       return NULL;
+               }
+       }
+
+       clone->port = host->port;
+
+       if (host->username != NULL) {
+               clone->username = strdup (host->username);
+               if (clone->username == NULL) {
+                       free_host_config (clone);
+                       return NULL;
+               }
        }
+       if (host->password != NULL) {
+               clone->password = strdup (host->password);
+               if (clone->password == NULL) {
+                       free_host_config (clone);
+                       return NULL;
+               }
+       }
+
+       clone->interval = host->interval;
+
+       return (clone);
+} /* }}} host_config_t *cna_shallow_clone_host */
+
+static int cna_read (user_data_t *ud);
+
+static int cna_register_host (host_config_t *host) /* {{{ */
+{
+       char cb_name[256];
+       struct timespec interval;
+       user_data_t ud;
+
+       if (host->vfiler)
+               ssnprintf (cb_name, sizeof (cb_name), "netapp-%s-%s",
+                               host->name, host->vfiler);
+       else
+               ssnprintf (cb_name, sizeof (cb_name), "netapp-%s", host->name);
+
+       CDTIME_T_TO_TIMESPEC (host->interval, &interval);
+
+       memset (&ud, 0, sizeof (ud));
+       ud.data = host;
+       ud.free_func = (void (*) (void *)) free_host_config;
+
+       plugin_register_complex_read (/* group = */ NULL, cb_name,
+                       /* callback  = */ cna_read,
+                       /* interval  = */ (host->interval > 0) ? &interval : NULL,
+                       /* user data = */ &ud);
+
+       return (0);
+} /* }}} int cna_register_host */
+
+static int cna_config_host (host_config_t *host, /* {{{ */
+               const oconfig_item_t *ci)
+{
+       oconfig_item_t *item;
+       _Bool is_vfiler = 0;
+       int status;
+       int i;
+
+       if (! strcasecmp (ci->key, "VFiler"))
+               is_vfiler = 1;
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
+               WARNING ("netapp plugin: \"%s\" needs exactly one string argument. Ignoring host block.", ci->key);
+               return (1);
+       }
+
+       status = cf_util_get_string (ci, &host->name);
+       if (status != 0)
+               return (1);
 
        for (i = 0; i < ci->children_num; ++i) {
                item = ci->children + i;
@@ -2421,7 +2965,7 @@ static host_config_t *cna_config_host (const oconfig_item_t *ci) /* {{{ */
                } else if (!strcasecmp(item->key, "Protocol")) {
                        if ((item->values_num != 1) || (item->values[0].type != OCONFIG_TYPE_STRING) || (strcasecmp(item->values[0].value.string, "http") && strcasecmp(item->values[0].value.string, "https"))) {
                                WARNING("netapp plugin: \"Protocol\" needs to be either \"http\" or \"https\". Ignoring host block \"%s\".", ci->values[0].value.string);
-                               return 0;
+                               return (1);
                        }
                        if (!strcasecmp(item->values[0].value.string, "http")) host->protocol = NA_SERVER_TRANSPORT_HTTP;
                        else host->protocol = NA_SERVER_TRANSPORT_HTTPS;
@@ -2439,11 +2983,32 @@ static host_config_t *cna_config_host (const oconfig_item_t *ci) /* {{{ */
                        cna_config_volume_performance(host, item);
                } else if (!strcasecmp(item->key, "VolumeUsage")) {
                        cna_config_volume_usage(host, item);
+               } else if (!strcasecmp(item->key, "Quota")) {
+                       cna_config_quota(host, item);
+               } else if (!strcasecmp(item->key, "SnapVault")) {
+                       cna_config_snapvault(host, item);
                } else if (!strcasecmp(item->key, "System")) {
                        cna_config_system(host, item);
+               } else if ((!strcasecmp(item->key, "VFiler")) && (! is_vfiler)) {
+                       host_config_t *vfiler;
+
+                       vfiler = cna_shallow_clone_host (host);
+                       if (! vfiler) {
+                               ERROR ("netapp plugin: Failed to allocate host object for vfiler.");
+                               continue;
+                       }
+
+                       if (cna_config_host (vfiler, item)) {
+                               free_host_config (vfiler);
+                               continue;
+                       }
+
+                       cna_register_host (vfiler);
+               } else if ((!strcasecmp(item->key, "VFilerName")) && is_vfiler) {
+                       status = cf_util_get_string (item, &host->vfiler);
                } else {
-                       WARNING("netapp plugin: Ignoring unknown config option \"%s\" in host block \"%s\".",
-                                       item->key, ci->values[0].value.string);
+                       WARNING ("netapp plugin: Ignoring unknown config option \"%s\" in %s block \"%s\".",
+                                       item->key, is_vfiler ? "vfiler" : "host", ci->values[0].value.string);
                }
 
                if (status != 0)
@@ -2453,6 +3018,9 @@ static host_config_t *cna_config_host (const oconfig_item_t *ci) /* {{{ */
        if (host->host == NULL)
                host->host = strdup (host->name);
 
+       if (is_vfiler && (! host->vfiler))
+               host->vfiler = strdup (host->name);
+
        if (host->host == NULL)
                status = -1;
 
@@ -2466,12 +3034,9 @@ static host_config_t *cna_config_host (const oconfig_item_t *ci) /* {{{ */
        }
 
        if (status != 0)
-       {
-               free_host_config (host);
-               return (NULL);
-       }
+               return status;
 
-       return host;
+       return (0);
 } /* }}} host_config_t *cna_config_host */
 
 /*
@@ -2481,15 +3046,19 @@ static host_config_t *cna_config_host (const oconfig_item_t *ci) /* {{{ */
  */
 static int cna_init_host (host_config_t *host) /* {{{ */
 {
+       /* Request version 1.1 of the ONTAP API */
+       int major_version = 1, minor_version = 1;
+
        if (host == NULL)
                return (EINVAL);
 
        if (host->srv != NULL)
                return (0);
 
-       /* Request version 1.1 of the ONTAP API */
-       host->srv = na_server_open(host->host,
-                       /* major version = */ 1, /* minor version = */ 1); 
+       if (host->vfiler != NULL) /* Request version 1.7 of the ONTAP API */
+               minor_version = 7;
+
+       host->srv = na_server_open (host->host, major_version, minor_version);
        if (host->srv == NULL) {
                ERROR ("netapp plugin: na_server_open (%s) failed.", host->host);
                return (-1);
@@ -2502,6 +3071,18 @@ static int cna_init_host (host_config_t *host) /* {{{ */
        na_server_adminuser(host->srv, host->username, host->password);
        na_server_set_timeout(host->srv, 5 /* seconds */);
 
+       if (host->vfiler != NULL) {
+               if (! na_server_set_vfiler (host->srv, host->vfiler)) {
+                       ERROR ("netapp plugin: Failed to connect to VFiler '%s' on host '%s'.",
+                                       host->vfiler, host->host);
+                       return (-1);
+               }
+               else {
+                       INFO ("netapp plugin: Connected to VFiler '%s' on host '%s'.",
+                                       host->vfiler, host->host);
+               }
+       }
+
        return (0);
 } /* }}} int cna_init_host */
 
@@ -2538,6 +3119,14 @@ static int cna_read_internal (host_config_t *host) { /* {{{ */
        if (status != 0)
                return (status);
 
+       status = cna_query_quota (host);
+       if (status != 0)
+               return (status);
+
+       status = cna_query_snapvault (host);
+       if (status != 0)
+               return (status);
+
        status = cna_query_system (host);
        if (status != 0)
                return (status);
@@ -2579,27 +3168,19 @@ static int cna_config (oconfig_item_t *ci) { /* {{{ */
                if (strcasecmp(item->key, "Host") == 0)
                {
                        host_config_t *host;
-                       char cb_name[256];
-                       struct timespec interval;
-                       user_data_t ud;
 
-                       host = cna_config_host (item);
-                       if (host == NULL)
+                       host = cna_alloc_host ();
+                       if (host == NULL) {
+                               ERROR ("netapp plugin: Failed to allocate host object.");
                                continue;
+                       }
 
-                       ssnprintf (cb_name, sizeof (cb_name), "netapp-%s", host->name);
-
-                       CDTIME_T_TO_TIMESPEC (host->interval, &interval);
-
-                       memset (&ud, 0, sizeof (ud));
-                       ud.data = host;
-                       ud.free_func = (void (*) (void *)) free_host_config;
+                       if (cna_config_host (host, item) != 0) {
+                               free_host_config (host);
+                               continue;
+                       }
 
-                       plugin_register_complex_read (/* group = */ NULL, cb_name,
-                                       /* callback  = */ cna_read, 
-                                       /* interval  = */ (host->interval > 0) ? &interval : NULL,
-                                       /* user data = */ &ud);
-                       continue;
+                       cna_register_host (host);
                }
                else /* if (item->key != "Host") */
                {
index 97b965644e6673617de163e1235b590b05c3aa6c..98cb8eba100a8ccf19bb89d9dc39ce81d3f651e9 100644 (file)
@@ -460,7 +460,7 @@ static int network_dispatch_values (value_list_t *vl, /* {{{ */
     }
   }
 
-  plugin_dispatch_values_secure (vl);
+  plugin_dispatch_values (vl);
   stats_values_dispatched++;
 
   meta_data_destroy (vl->meta);
@@ -3336,13 +3336,13 @@ static int network_stats_read (void) /* {{{ */
        vl.values[0].derive = (derive_t) copy_octets_rx;
        vl.values[1].derive = (derive_t) copy_octets_tx;
        sstrncpy (vl.type, "if_octets", sizeof (vl.type));
-       plugin_dispatch_values_secure (&vl);
+       plugin_dispatch_values (&vl);
 
        /* Packets received / send */
        vl.values[0].derive = (derive_t) copy_packets_rx;
        vl.values[1].derive = (derive_t) copy_packets_tx;
        sstrncpy (vl.type, "if_packets", sizeof (vl.type));
-       plugin_dispatch_values_secure (&vl);
+       plugin_dispatch_values (&vl);
 
        /* Values (not) dispatched and (not) send */
        sstrncpy (vl.type, "total_values", sizeof (vl.type));
@@ -3351,28 +3351,28 @@ static int network_stats_read (void) /* {{{ */
        vl.values[0].derive = (derive_t) copy_values_dispatched;
        sstrncpy (vl.type_instance, "dispatch-accepted",
                        sizeof (vl.type_instance));
-       plugin_dispatch_values_secure (&vl);
+       plugin_dispatch_values (&vl);
 
        vl.values[0].derive = (derive_t) copy_values_not_dispatched;
        sstrncpy (vl.type_instance, "dispatch-rejected",
                        sizeof (vl.type_instance));
-       plugin_dispatch_values_secure (&vl);
+       plugin_dispatch_values (&vl);
 
        vl.values[0].derive = (derive_t) copy_values_sent;
        sstrncpy (vl.type_instance, "send-accepted",
                        sizeof (vl.type_instance));
-       plugin_dispatch_values_secure (&vl);
+       plugin_dispatch_values (&vl);
 
        vl.values[0].derive = (derive_t) copy_values_not_sent;
        sstrncpy (vl.type_instance, "send-rejected",
                        sizeof (vl.type_instance));
-       plugin_dispatch_values_secure (&vl);
+       plugin_dispatch_values (&vl);
 
        /* Receive queue length */
        vl.values[0].gauge = (gauge_t) copy_receive_list_length;
        sstrncpy (vl.type, "queue_length", sizeof (vl.type));
        vl.type_instance[0] = 0;
-       plugin_dispatch_values_secure (&vl);
+       plugin_dispatch_values (&vl);
 
        return (0);
 } /* }}} int network_stats_read */
index 1a4ed7487b0566378092edb3f6d8b3b84d49adab..f5b579e1384e0d8d186c8e5c8ff3c098e48ee15d 100644 (file)
--- a/src/nfs.c
+++ b/src/nfs.c
@@ -247,7 +247,7 @@ static void nfs_procedures_submit (const char *plugin_instance,
                vl.values = values + i;
                sstrncpy (vl.type_instance, type_instances[i],
                                sizeof (vl.type_instance));
-               plugin_dispatch_values_secure (&vl);
+               plugin_dispatch_values (&vl);
        }
 } /* void nfs_procedures_submit */
 
index 809c140f7e0d1ee8f9047e39d5a7c17eed4cac8a..4c6a0322f18200f92caee47adbe41ab089ee94f5 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/plugin.c
- * Copyright (C) 2005-2011  Florian octo Forster
+ * Copyright (C) 2005-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 "collectd.h"
-#include "utils_complain.h"
-
-#include <ltdl.h>
-
-#if HAVE_PTHREAD_H
-# include <pthread.h>
-#endif
-
 #include "common.h"
 #include "plugin.h"
 #include "configfile.h"
+#include "filter_chain.h"
 #include "utils_avltree.h"
+#include "utils_cache.h"
+#include "utils_complain.h"
 #include "utils_llist.h"
 #include "utils_heap.h"
-#include "utils_cache.h"
-#include "filter_chain.h"
+#include "utils_time.h"
+
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+
+#include <ltdl.h>
 
 /*
  * Private structures
@@ -63,12 +63,21 @@ struct read_func_s
        char rf_group[DATA_MAX_NAME_LEN];
        char rf_name[DATA_MAX_NAME_LEN];
        int rf_type;
-       struct timespec rf_interval;
-       struct timespec rf_effective_interval;
-       struct timespec rf_next_read;
+       cdtime_t rf_interval;
+       cdtime_t rf_effective_interval;
+       cdtime_t rf_next_read;
 };
 typedef struct read_func_s read_func_t;
 
+struct write_queue_s;
+typedef struct write_queue_s write_queue_t;
+struct write_queue_s
+{
+       value_list_t *vl;
+       plugin_ctx_t ctx;
+       write_queue_t *next;
+};
+
 /*
  * Private variables
  */
@@ -95,12 +104,22 @@ static pthread_cond_t  read_cond = PTHREAD_COND_INITIALIZER;
 static pthread_t      *read_threads = NULL;
 static int             read_threads_num = 0;
 
+static write_queue_t  *write_queue_head;
+static write_queue_t  *write_queue_tail;
+static _Bool           write_loop = 1;
+static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  write_cond = PTHREAD_COND_INITIALIZER;
+static pthread_t      *write_threads = NULL;
+static size_t          write_threads_num = 0;
+
 static pthread_key_t   plugin_ctx_key;
 static _Bool           plugin_ctx_key_initialized = 0;
 
 /*
  * Static functions
  */
+static int plugin_dispatch_values_internal (value_list_t *vl);
+
 static const char *plugin_get_dir (void)
 {
        if (plugindir == NULL)
@@ -341,13 +360,6 @@ static int plugin_load_file (char *file, uint32_t flags)
        return (0);
 }
 
-static _Bool timeout_reached(struct timespec timeout)
-{
-       struct timeval now;
-       gettimeofday(&now, NULL);
-       return (now.tv_sec >= timeout.tv_sec && now.tv_usec >= (timeout.tv_nsec / 1000));
-}
-
 static void *plugin_read_thread (void __attribute__((unused)) *args)
 {
        while (read_loop != 0)
@@ -373,18 +385,15 @@ static void *plugin_read_thread (void __attribute__((unused)) *args)
                }
                pthread_mutex_unlock (&read_lock);
 
-               if ((rf->rf_interval.tv_sec == 0) && (rf->rf_interval.tv_nsec == 0))
+               if (rf->rf_interval == 0)
                {
                        /* this should not happen, because the interval is set
                         * for each plugin when loading it
                         * XXX: issue a warning? */
-                       now = cdtime ();
-
-                       CDTIME_T_TO_TIMESPEC (plugin_get_interval (), &rf->rf_interval);
-
+                       rf->rf_interval = plugin_get_interval ();
                        rf->rf_effective_interval = rf->rf_interval;
 
-                       CDTIME_T_TO_TIMESPEC (now, &rf->rf_next_read);
+                       rf->rf_next_read = cdtime ();
                }
 
                /* sleep until this entry is due,
@@ -396,11 +405,15 @@ static void *plugin_read_thread (void __attribute__((unused)) *args)
                 * pthread_cond_timedwait returns. */
                rc = 0;
                while ((read_loop != 0)
-                               && !timeout_reached(rf->rf_next_read)
+                               && (cdtime () < rf->rf_next_read)
                                && rc == 0)
                {
+                       struct timespec ts = { 0 };
+
+                       CDTIME_T_TO_TIMESPEC (rf->rf_next_read, &ts);
+
                        rc = pthread_cond_timedwait (&read_cond, &read_lock,
-                               &rf->rf_next_read);
+                               &ts);
                }
 
                /* Must hold `read_lock' when accessing `rf->rf_type'. */
@@ -456,20 +469,14 @@ static void *plugin_read_thread (void __attribute__((unused)) *args)
                 * intervals in which it will be called. */
                if (status != 0)
                {
-                       rf->rf_effective_interval.tv_sec *= 2;
-                       rf->rf_effective_interval.tv_nsec *= 2;
-                       NORMALIZE_TIMESPEC (rf->rf_effective_interval);
-
-                       if (rf->rf_effective_interval.tv_sec >= 86400)
-                       {
-                               rf->rf_effective_interval.tv_sec = 86400;
-                               rf->rf_effective_interval.tv_nsec = 0;
-                       }
+                       rf->rf_effective_interval *= 2;
+                       if (rf->rf_effective_interval > TIME_T_TO_CDTIME_T (86400))
+                               rf->rf_effective_interval = TIME_T_TO_CDTIME_T (86400);
 
                        NOTICE ("read-function of plugin `%s' failed. "
-                                       "Will suspend it for %i seconds.",
+                                       "Will suspend it for %.3f seconds.",
                                        rf->rf_name,
-                                       (int) rf->rf_effective_interval.tv_sec);
+                                       CDTIME_T_TO_DOUBLE (rf->rf_effective_interval));
                }
                else
                {
@@ -481,32 +488,26 @@ static void *plugin_read_thread (void __attribute__((unused)) *args)
                now = cdtime ();
 
                DEBUG ("plugin_read_thread: Effective interval of the "
-                               "%s plugin is %i.%09i.",
+                               "%s plugin is %.3f seconds.",
                                rf->rf_name,
-                               (int) rf->rf_effective_interval.tv_sec,
-                               (int) rf->rf_effective_interval.tv_nsec);
+                               CDTIME_T_TO_DOUBLE (rf->rf_effective_interval));
 
                /* Calculate the next (absolute) time at which this function
                 * should be called. */
-               rf->rf_next_read.tv_sec = rf->rf_next_read.tv_sec
-                       + rf->rf_effective_interval.tv_sec;
-               rf->rf_next_read.tv_nsec = rf->rf_next_read.tv_nsec
-                       + rf->rf_effective_interval.tv_nsec;
-               NORMALIZE_TIMESPEC (rf->rf_next_read);
+               rf->rf_next_read += rf->rf_effective_interval;
 
                /* Check, if `rf_next_read' is in the past. */
-               if (TIMESPEC_TO_CDTIME_T (&rf->rf_next_read) < now)
+               if (rf->rf_next_read < now)
                {
                        /* `rf_next_read' is in the past. Insert `now'
                         * so this value doesn't trail off into the
                         * past too much. */
-                       CDTIME_T_TO_TIMESPEC (now, &rf->rf_next_read);
+                       rf->rf_next_read = now;
                }
 
-               DEBUG ("plugin_read_thread: Next read of the %s plugin at %i.%09i.",
+               DEBUG ("plugin_read_thread: Next read of the %s plugin at %.3f.",
                                rf->rf_name,
-                               (int) rf->rf_next_read.tv_sec,
-                               (int) rf->rf_next_read.tv_nsec);
+                               CDTIME_T_TO_DOUBLE (rf->rf_next_read));
 
                /* Re-insert this read function into the heap again. */
                c_heap_insert (read_heap, rf);
@@ -573,6 +574,244 @@ static void stop_read_threads (void)
        read_threads_num = 0;
 } /* void stop_read_threads */
 
+static void plugin_value_list_free (value_list_t *vl) /* {{{ */
+{
+       if (vl == NULL)
+               return;
+
+       meta_data_destroy (vl->meta);
+       sfree (vl->values);
+       sfree (vl);
+} /* }}} void plugin_value_list_free */
+
+static value_list_t *plugin_value_list_clone (value_list_t const *vl_orig) /* {{{ */
+{
+       value_list_t *vl;
+
+       if (vl_orig == NULL)
+               return (NULL);
+
+       vl = malloc (sizeof (*vl));
+       if (vl == NULL)
+               return (NULL);
+       memcpy (vl, vl_orig, sizeof (*vl));
+
+       vl->values = calloc (vl_orig->values_len, sizeof (*vl->values));
+       if (vl->values == NULL)
+       {
+               plugin_value_list_free (vl);
+               return (NULL);
+       }
+       memcpy (vl->values, vl_orig->values,
+                       vl_orig->values_len * sizeof (*vl->values));
+
+       vl->meta = meta_data_clone (vl->meta);
+       if ((vl_orig->meta != NULL) && (vl->meta == NULL))
+       {
+               plugin_value_list_free (vl);
+               return (NULL);
+       }
+
+       if (vl->time == 0)
+               vl->time = cdtime ();
+
+       /* Fill in the interval from the thread context, if it is zero. */
+       if (vl->interval == 0)
+       {
+               plugin_ctx_t ctx = plugin_get_ctx ();
+
+               if (ctx.interval != 0)
+                       vl->interval = ctx.interval;
+               else
+               {
+                       char name[6 * DATA_MAX_NAME_LEN];
+                       FORMAT_VL (name, sizeof (name), vl);
+                       ERROR ("plugin_value_list_clone: Unable to determine "
+                                       "interval from context for "
+                                       "value list \"%s\". "
+                                       "This indicates a broken plugin. "
+                                       "Please report this problem to the "
+                                       "collectd mailing list or at "
+                                       "<http://collectd.org/bugs/>.", name);
+                       vl->interval = cf_get_default_interval ();
+               }
+       }
+
+       return (vl);
+} /* }}} value_list_t *plugin_value_list_clone */
+
+static int plugin_write_enqueue (value_list_t const *vl) /* {{{ */
+{
+       write_queue_t *q;
+
+       q = malloc (sizeof (*q));
+       if (q == NULL)
+               return (ENOMEM);
+       q->next = NULL;
+
+       q->vl = plugin_value_list_clone (vl);
+       if (q->vl == NULL)
+       {
+               sfree (q);
+               return (ENOMEM);
+       }
+
+       /* Store context of caller (read plugin); otherwise, it would not be
+        * available to the write plugins when actually dispatching the
+        * value-list later on. */
+       q->ctx = plugin_get_ctx ();
+
+       pthread_mutex_lock (&write_lock);
+
+       if (write_queue_tail == NULL)
+       {
+               write_queue_head = q;
+               write_queue_tail = q;
+       }
+       else
+       {
+               write_queue_tail->next = q;
+               write_queue_tail = q;
+       }
+
+       pthread_cond_signal (&write_cond);
+       pthread_mutex_unlock (&write_lock);
+
+       return (0);
+} /* }}} int plugin_write_enqueue */
+
+static value_list_t *plugin_write_dequeue (void) /* {{{ */
+{
+       write_queue_t *q;
+       value_list_t *vl;
+
+       pthread_mutex_lock (&write_lock);
+
+       while (write_loop && (write_queue_head == NULL))
+               pthread_cond_wait (&write_cond, &write_lock);
+
+       if (write_queue_head == NULL)
+       {
+               pthread_mutex_unlock (&write_lock);
+               return (NULL);
+       }
+
+       q = write_queue_head;
+       write_queue_head = q->next;
+       if (write_queue_head == NULL)
+               write_queue_tail = NULL;
+
+       pthread_mutex_unlock (&write_lock);
+
+       (void) plugin_set_ctx (q->ctx);
+
+       vl = q->vl;
+       sfree (q);
+       return (vl);
+} /* }}} value_list_t *plugin_write_dequeue */
+
+static void *plugin_write_thread (void __attribute__((unused)) *args) /* {{{ */
+{
+       while (write_loop)
+       {
+               value_list_t *vl = plugin_write_dequeue ();
+               if (vl == NULL)
+                       continue;
+
+               plugin_dispatch_values_internal (vl);
+
+               plugin_value_list_free (vl);
+       }
+
+       pthread_exit (NULL);
+       return ((void *) 0);
+} /* }}} void *plugin_write_thread */
+
+static void start_write_threads (size_t num) /* {{{ */
+{
+       size_t i;
+
+       if (write_threads != NULL)
+               return;
+
+       write_threads = (pthread_t *) calloc (num, sizeof (pthread_t));
+       if (write_threads == NULL)
+       {
+               ERROR ("plugin: start_write_threads: calloc failed.");
+               return;
+       }
+
+       write_threads_num = 0;
+       for (i = 0; i < num; i++)
+       {
+               int status;
+
+               status = pthread_create (write_threads + write_threads_num,
+                               /* attr = */ NULL,
+                               plugin_write_thread,
+                               /* arg = */ NULL);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("plugin: start_write_threads: pthread_create failed "
+                                       "with status %i (%s).", status,
+                                       sstrerror (status, errbuf, sizeof (errbuf)));
+                       return;
+               }
+
+               write_threads_num++;
+       } /* for (i) */
+} /* }}} void start_write_threads */
+
+static void stop_write_threads (void) /* {{{ */
+{
+       write_queue_t *q;
+       int i;
+
+       if (write_threads == NULL)
+               return;
+
+       INFO ("collectd: Stopping %zu write threads.", write_threads_num);
+
+       pthread_mutex_lock (&write_lock);
+       write_loop = 0;
+       DEBUG ("plugin: stop_write_threads: Signalling `write_cond'");
+       pthread_cond_broadcast (&write_cond);
+       pthread_mutex_unlock (&write_lock);
+
+       for (i = 0; i < write_threads_num; i++)
+       {
+               if (pthread_join (write_threads[i], NULL) != 0)
+               {
+                       ERROR ("plugin: stop_write_threads: pthread_join failed.");
+               }
+               write_threads[i] = (pthread_t) 0;
+       }
+       sfree (write_threads);
+       write_threads_num = 0;
+
+       pthread_mutex_lock (&write_lock);
+       i = 0;
+       for (q = write_queue_head; q != NULL; )
+       {
+               write_queue_t *q1 = q;
+               plugin_value_list_free (q->vl);
+               q = q->next;
+               sfree (q1);
+               i++;
+       }
+       write_queue_head = NULL;
+       write_queue_tail = NULL;
+       pthread_mutex_unlock (&write_lock);
+
+       if (i > 0)
+       {
+               WARNING ("plugin: %i value list%s left after shutting down "
+                               "the write threads.",
+                               i, (i == 1) ? " was" : "s were");
+       }
+} /* }}} void stop_write_threads */
+
 /*
  * Public functions
  */
@@ -604,8 +843,6 @@ int plugin_load (const char *type, uint32_t flags)
        struct dirent *de;
        int status;
 
-       DEBUG ("type = %s", type);
-
        dir = plugin_get_dir ();
        ret = 1;
 
@@ -614,7 +851,7 @@ int plugin_load (const char *type, uint32_t flags)
        status = ssnprintf (typename, sizeof (typename), "%s.so", type);
        if ((status < 0) || ((size_t) status >= sizeof (typename)))
        {
-               WARNING ("snprintf: truncated: `%s.so'", type);
+               WARNING ("plugin_load: Filename too long: \"%s.so\"", type);
                return (-1);
        }
        typename_len = strlen (typename);
@@ -622,7 +859,7 @@ int plugin_load (const char *type, uint32_t flags)
        if ((dh = opendir (dir)) == NULL)
        {
                char errbuf[1024];
-               ERROR ("opendir (%s): %s", dir,
+               ERROR ("plugin_load: opendir (%s) failed: %s", dir,
                                sstrerror (errno, errbuf, sizeof (errbuf)));
                return (-1);
        }
@@ -636,25 +873,29 @@ int plugin_load (const char *type, uint32_t flags)
                                "%s/%s", dir, de->d_name);
                if ((status < 0) || ((size_t) status >= sizeof (filename)))
                {
-                       WARNING ("snprintf: truncated: `%s/%s'", dir, de->d_name);
+                       WARNING ("plugin_load: Filename too long: \"%s/%s\"",
+                                       dir, de->d_name);
                        continue;
                }
 
                if (lstat (filename, &statbuf) == -1)
                {
                        char errbuf[1024];
-                       WARNING ("stat %s: %s", filename,
+                       WARNING ("plugin_load: stat (\"%s\") failed: %s",
+                                       filename,
                                        sstrerror (errno, errbuf, sizeof (errbuf)));
                        continue;
                }
                else if (!S_ISREG (statbuf.st_mode))
                {
                        /* don't follow symlinks */
-                       WARNING ("stat %s: not a regular file", filename);
+                       WARNING ("plugin_load: %s is not a regular file.",
+                                       filename);
                        continue;
                }
 
-               if (plugin_load_file (filename, flags) == 0)
+               status = plugin_load_file (filename, flags);
+               if (status == 0)
                {
                        /* success */
                        ret = 0;
@@ -662,14 +903,16 @@ int plugin_load (const char *type, uint32_t flags)
                }
                else
                {
-                       fprintf (stderr, "Unable to load plugin %s.\n", type);
+                       ERROR ("plugin_load: Load plugin \"%s\" failed with "
+                                       "status %i.", type, status);
                }
        }
 
        closedir (dh);
 
-       if (filename[0] == '\0')
-               fprintf (stderr, "Could not find plugin %s.\n", type);
+       if (filename[0] == 0)
+               ERROR ("plugin_load: Could not find plugin \"%s\" in %s",
+                               type, dir);
 
        return (ret);
 }
@@ -706,13 +949,9 @@ static int plugin_compare_read_func (const void *arg0, const void *arg1)
        rf0 = arg0;
        rf1 = arg1;
 
-       if (rf0->rf_next_read.tv_sec < rf1->rf_next_read.tv_sec)
+       if (rf0->rf_next_read < rf1->rf_next_read)
                return (-1);
-       else if (rf0->rf_next_read.tv_sec > rf1->rf_next_read.tv_sec)
-               return (1);
-       else if (rf0->rf_next_read.tv_nsec < rf1->rf_next_read.tv_nsec)
-               return (-1);
-       else if (rf0->rf_next_read.tv_nsec > rf1->rf_next_read.tv_nsec)
+       else if (rf0->rf_next_read > rf1->rf_next_read)
                return (1);
        else
                return (0);
@@ -726,8 +965,8 @@ static int plugin_insert_read (read_func_t *rf)
        int status;
        llentry_t *le;
 
-       cdtime_t now = cdtime ();
-       CDTIME_T_TO_TIMESPEC (now, &rf->rf_next_read);
+       rf->rf_next_read = cdtime ();
+       rf->rf_effective_interval = rf->rf_interval;
 
        pthread_mutex_lock (&read_lock);
 
@@ -790,43 +1029,12 @@ static int plugin_insert_read (read_func_t *rf)
        return (0);
 } /* int plugin_insert_read */
 
-static int read_cb_wrapper (user_data_t *ud)
-{
-       int (*callback) (void);
-
-       if (ud == NULL)
-               return -1;
-
-       callback = ud->data;
-       return callback();
-} /* int read_cb_wrapper */
-
 int plugin_register_read (const char *name,
                int (*callback) (void))
 {
        read_func_t *rf;
-       plugin_ctx_t ctx = plugin_get_ctx ();
        int status;
 
-       if (ctx.interval != 0) {
-               /* If ctx.interval is not zero (== use the plugin or global
-                * interval), we need to use the "complex" read callback,
-                * because only that allows to specify a different interval.
-                * Wrap the callback using read_cb_wrapper(). */
-               struct timespec interval;
-               user_data_t user_data;
-
-               user_data.data = callback;
-               user_data.free_func = NULL;
-
-               CDTIME_T_TO_TIMESPEC (ctx.interval, &interval);
-               return plugin_register_complex_read (/* group = */ NULL,
-                               name, read_cb_wrapper, &interval, &user_data);
-       }
-
-       DEBUG ("plugin_register_read: default_interval = %.3f",
-                       CDTIME_T_TO_DOUBLE(plugin_get_interval ()));
-
        rf = malloc (sizeof (*rf));
        if (rf == NULL)
        {
@@ -838,13 +1046,11 @@ int plugin_register_read (const char *name,
        rf->rf_callback = (void *) callback;
        rf->rf_udata.data = NULL;
        rf->rf_udata.free_func = NULL;
-       rf->rf_ctx = ctx;
+       rf->rf_ctx = plugin_get_ctx ();
        rf->rf_group[0] = '\0';
        sstrncpy (rf->rf_name, name, sizeof (rf->rf_name));
        rf->rf_type = RF_SIMPLE;
-       rf->rf_interval.tv_sec = 0;
-       rf->rf_interval.tv_nsec = 0;
-       rf->rf_effective_interval = rf->rf_interval;
+       rf->rf_interval = plugin_get_interval ();
 
        status = plugin_insert_read (rf);
        if (status != 0)
@@ -859,7 +1065,6 @@ int plugin_register_complex_read (const char *group, const char *name,
                user_data_t *user_data)
 {
        read_func_t *rf;
-       plugin_ctx_t ctx = plugin_get_ctx ();
        int status;
 
        rf = malloc (sizeof (*rf));
@@ -878,18 +1083,9 @@ int plugin_register_complex_read (const char *group, const char *name,
        sstrncpy (rf->rf_name, name, sizeof (rf->rf_name));
        rf->rf_type = RF_COMPLEX;
        if (interval != NULL)
-       {
-               rf->rf_interval = *interval;
-       }
-       else if (ctx.interval != 0)
-       {
-               CDTIME_T_TO_TIMESPEC (ctx.interval, &rf->rf_interval);
-       }
-       rf->rf_effective_interval = rf->rf_interval;
-
-       DEBUG ("plugin_register_read: interval = %i.%09i",
-                       (int) rf->rf_interval.tv_sec,
-                       (int) rf->rf_interval.tv_nsec);
+               rf->rf_interval = TIMESPEC_TO_CDTIME_T (interval);
+       else
+               rf->rf_interval = plugin_get_interval ();
 
        /* Set user data */
        if (user_data == NULL)
@@ -902,7 +1098,7 @@ int plugin_register_complex_read (const char *group, const char *name,
                rf->rf_udata = *user_data;
        }
 
-       rf->rf_ctx = ctx;
+       rf->rf_ctx = plugin_get_ctx ();
 
        status = plugin_insert_read (rf);
        if (status != 0)
@@ -1169,6 +1365,15 @@ void plugin_init_all (void)
        chain_name = global_option_get ("PostCacheChain");
        post_cache_chain = fc_chain_get_by_name (chain_name);
 
+       {
+               char const *tmp = global_option_get ("WriteThreads");
+               int num = atoi (tmp);
+
+               if (num < 1)
+                       num = 5;
+
+               start_write_threads ((size_t) num);
+       }
 
        if ((list_init == NULL) && (read_heap == NULL))
                return;
@@ -1438,6 +1643,8 @@ void plugin_shutdown_all (void)
                plugin_set_ctx (old_ctx);
        }
 
+       stop_write_threads ();
+
        /* Write plugins which use the `user_data' pointer usually need the
         * same data available to the flush callback. If this is the case, set
         * the free_function to NULL when registering the flush callback and to
@@ -1493,7 +1700,7 @@ int plugin_dispatch_missing (const value_list_t *vl) /* {{{ */
   return (0);
 } /* int }}} plugin_dispatch_missing */
 
-int plugin_dispatch_values (value_list_t *vl)
+static int plugin_dispatch_values_internal (value_list_t *vl)
 {
        int status;
        static c_complain_t no_write_complaint = C_COMPLAIN_INIT_STATIC;
@@ -1544,29 +1751,10 @@ int plugin_dispatch_values (value_list_t *vl)
                return (-1);
        }
 
-       if (vl->time == 0)
-               vl->time = cdtime ();
-
-       if (vl->interval <= 0)
-       {
-               plugin_ctx_t ctx = plugin_get_ctx ();
-
-               if (ctx.interval != 0)
-                       vl->interval = ctx.interval;
-               else
-               {
-                       char name[6 * DATA_MAX_NAME_LEN];
-                       FORMAT_VL (name, sizeof (name), vl);
-                       ERROR ("plugin_dispatch_values: Unable to determine "
-                                       "interval from context for "
-                                       "value list \"%s\". "
-                                       "This indicates a broken plugin. "
-                                       "Please report this problem to the "
-                                       "collectd mailing list or at "
-                                       "<http://collectd.org/bugs/>.", name);
-                       vl->interval = cf_get_default_interval ();
-               }
-       }
+       /* Assured by plugin_value_list_clone(). The time is determined at
+        * _enqueue_ time. */
+       assert (vl->time != 0);
+       assert (vl->interval != 0);
 
        DEBUG ("plugin_dispatch_values: time = %.3f; interval = %.3f; "
                        "host = %s; "
@@ -1687,53 +1875,24 @@ int plugin_dispatch_values (value_list_t *vl)
        }
 
        return (0);
-} /* int plugin_dispatch_values */
+} /* int plugin_dispatch_values_internal */
 
-int plugin_dispatch_values_secure (const value_list_t *vl)
+int plugin_dispatch_values (value_list_t const *vl)
 {
-  value_list_t vl_copy;
-  int status;
-
-  if (vl == NULL)
-    return EINVAL;
-
-  memcpy (&vl_copy, vl, sizeof (vl_copy));
-
-  /* Write callbacks must not change the values and meta pointers, so we can
-   * savely skip copying those and make this more efficient. */
-  if ((pre_cache_chain == NULL) && (post_cache_chain == NULL))
-    return (plugin_dispatch_values (&vl_copy));
-
-  /* Set pointers to NULL, just to be on the save side. */
-  vl_copy.values = NULL;
-  vl_copy.meta = NULL;
-
-  vl_copy.values = malloc (sizeof (*vl_copy.values) * vl->values_len);
-  if (vl_copy.values == NULL)
-  {
-    ERROR ("plugin_dispatch_values_secure: malloc failed.");
-    return (ENOMEM);
-  }
-  memcpy (vl_copy.values, vl->values, sizeof (*vl_copy.values) * vl->values_len);
-
-  if (vl->meta != NULL)
-  {
-    vl_copy.meta = meta_data_clone (vl->meta);
-    if (vl_copy.meta == NULL)
-    {
-      ERROR ("plugin_dispatch_values_secure: meta_data_clone failed.");
-      free (vl_copy.values);
-      return (ENOMEM);
-    }
-  } /* if (vl->meta) */
-
-  status = plugin_dispatch_values (&vl_copy);
+       int status;
 
-  meta_data_destroy (vl_copy.meta);
-  free (vl_copy.values);
+       status = plugin_write_enqueue (vl);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("plugin_dispatch_values: plugin_write_enqueue failed "
+                               "with status %i (%s).", status,
+                               sstrerror (status, errbuf, sizeof (errbuf)));
+               return (status);
+       }
 
-  return (status);
-} /* int plugin_dispatch_values_secure */
+       return (0);
+}
 
 int plugin_dispatch_notification (const notification_t *notif)
 {
@@ -1857,6 +2016,12 @@ const data_set_t *plugin_get_ds (const char *name)
 {
        data_set_t *ds;
 
+       if (data_sets == NULL)
+       {
+               ERROR ("plugin_get_ds: No data sets are defined yet.");
+               return (NULL);
+       }
+
        if (c_avl_get (data_sets, name, (void *) &ds) != 0)
        {
                DEBUG ("No such dataset registered: %s", name);
index 0f35de5615b9357b616b1bbb60d9a1b7c069f894..635ff308f2cd4f83278554f4f79759a5b2efaeb4 100644 (file)
@@ -326,8 +326,7 @@ int plugin_unregister_notification (const char *name);
  *  `vl'        Value list of the values that have been read by a `read'
  *              function.
  */
-int plugin_dispatch_values (value_list_t *vl);
-int plugin_dispatch_values_secure (const value_list_t *vl);
+int plugin_dispatch_values (value_list_t const *vl);
 int plugin_dispatch_missing (const value_list_t *vl);
 
 int plugin_dispatch_notification (const notification_t *notif);
index 98ceb6d3dc78cec10bd7ed4966ee5d50a7204567..1eaf6bd3877570a56c0e50c045b7e90cc888cda2 100644 (file)
@@ -381,9 +381,6 @@ static int c_psql_check_connection (c_psql_database_t *db)
                c_psql_connect (db);
        }
 
-       /* "ping" */
-       PQclear (PQexec (db->conn, "SELECT 42;"));
-
        if (CONNECTION_OK != PQstatus (db->conn)) {
                PQreset (db->conn);
 
@@ -524,6 +521,12 @@ static int c_psql_exec_query (c_psql_database_t *db, udb_query_t *q,
        if (PGRES_TUPLES_OK != PQresultStatus (res)) {
                pthread_mutex_lock (&db->db_lock);
 
+               if ((CONNECTION_OK != PQstatus (db->conn))
+                               && (0 == c_psql_check_connection (db))) {
+                       PQclear (res);
+                       return c_psql_exec_query (db, q, prep_area);
+               }
+
                log_err ("Failed to execute SQL query: %s",
                                PQerrorMessage (db->conn));
                log_info ("SQL query was: %s",
diff --git a/src/riemann.proto b/src/riemann.proto
new file mode 100644 (file)
index 0000000..3e946a3
--- /dev/null
@@ -0,0 +1,45 @@
+option java_package = "com.aphyr.riemann";
+option java_outer_classname = "Proto";
+
+message State {
+  optional int64 time = 1;
+  optional string state = 2;
+  optional string service = 3;
+  optional string host = 4;
+  optional string description = 5;
+  optional bool once = 6;
+  repeated string tags = 7;
+  optional float ttl = 8;
+}
+
+message Event {
+  optional int64 time = 1;
+  optional string state = 2;
+  optional string service = 3;
+  optional string host = 4;
+  optional string description = 5;
+  repeated string tags = 7;
+  optional float ttl = 8;
+  repeated Attribute attributes = 9;
+
+  optional sint64 metric_sint64 = 13;
+  optional double metric_d = 14;
+  optional float metric_f = 15;
+}
+
+message Query {
+  optional string string = 1;
+}
+
+message Msg {
+  optional bool ok = 2;
+  optional string error = 3;
+  repeated State states = 4;
+  optional Query query = 5;
+  repeated Event events = 6;
+}
+
+message Attribute {
+  required string key = 1;
+  optional string value = 2;
+}
index cf23803138dec2bb8ee59cc8a6819785a72c5fb8..e5e11d4317b045fbb8587a3de3cc3f332653e858 100644 (file)
@@ -46,7 +46,9 @@ static rrdcreate_config_t rrdcreate_config =
        /* timespans_num = */ 0,
 
        /* consolidation_functions = */ NULL,
-       /* consolidation_functions_num = */ 0
+       /* consolidation_functions_num = */ 0,
+
+       /* async = */ 0
 };
 
 /*
@@ -224,6 +226,8 @@ static int rc_config (oconfig_item_t *ci)
       status = cf_util_get_string (child, &daemon_address);
     else if (strcasecmp ("CreateFiles", key) == 0)
       status = cf_util_get_boolean (child, &config_create_files);
+    else if (strcasecmp ("CreateFilesAsync", key) == 0)
+      status = cf_util_get_boolean (child, &rrdcreate_config.async);
     else if (strcasecmp ("CollectStatistics", key) == 0)
       status = cf_util_get_boolean (child, &config_collect_stats);
     else if (strcasecmp ("StepSize", key) == 0)
@@ -441,6 +445,8 @@ static int rc_write (const data_set_t *ds, const value_list_t *vl,
             filename);
         return (-1);
       }
+      else if (rrdcreate_config.async)
+        return (0);
     }
   }
 
index 916ce01d65f03585341eab2fd0ab3cae70669dd4..80833902a21e7b05656c3d6baa4fb91ffd5d170d 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/rrdtool.c
- * Copyright (C) 2006-2008  Florian octo Forster
+ * Copyright (C) 2006-2013  Florian octo Forster
  * Copyright (C) 2008-2008  Sebastian Harl
  * Copyright (C) 2009       Mariusz Gronczewski
  *
@@ -18,7 +18,7 @@
  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  *
  * Authors:
- *   Florian octo Forster <octo at verplant.org>
+ *   Florian octo Forster <octo at collectd.org>
  *   Sebastian Harl <sh at tokkee.org>
  *   Mariusz Gronczewski <xani666 at gmail.com>
  **/
@@ -27,6 +27,7 @@
 #include "plugin.h"
 #include "common.h"
 #include "utils_avltree.h"
+#include "utils_random.h"
 #include "utils_rrdcreate.h"
 
 #include <rrd.h>
@@ -75,6 +76,7 @@ static const char *config_keys[] =
 {
        "CacheTimeout",
        "CacheFlush",
+       "CreateFilesAsync",
        "DataDir",
        "StepSize",
        "HeartBeat",
@@ -102,7 +104,9 @@ static rrdcreate_config_t rrdcreate_config =
        /* timespans_num = */ 0,
 
        /* consolidation_functions = */ NULL,
-       /* consolidation_functions_num = */ 0
+       /* consolidation_functions_num = */ 0,
+
+       /* async = */ 0
 };
 
 /* XXX: If you need to lock both, cache_lock and queue_lock, at the same time,
@@ -631,11 +635,8 @@ static int rrd_cache_flush_identifier (cdtime_t timeout,
 
 static int64_t rrd_get_random_variation (void)
 {
-  double dbl_timeout;
-  cdtime_t ctm_timeout;
-  double rand_fact;
-  _Bool negative;
-  int64_t ret;
+  long min;
+  long max;
 
   if (random_timeout <= 0)
     return (0);
@@ -648,20 +649,10 @@ static int64_t rrd_get_random_variation (void)
          random_timeout = cache_timeout;
   }
 
-  /* This seems a bit complicated, but "random_timeout" is likely larger than
-   * RAND_MAX, so we can't simply use modulo here. */
-  dbl_timeout = CDTIME_T_TO_DOUBLE (random_timeout);
-  rand_fact = ((double) random ())
-    / ((double) RAND_MAX);
-  negative = (_Bool) (random () % 2);
-
-  ctm_timeout = DOUBLE_TO_CDTIME_T (dbl_timeout * rand_fact);
-
-  ret = (int64_t) ctm_timeout;
-  if (negative)
-    ret *= -1;
+  max = (long) (random_timeout / 2);
+  min = max - ((long) random_timeout);
 
-  return (ret);
+  return ((int64_t) cdrand_range (min, max));
 } /* int64_t rrd_get_random_variation */
 
 static int rrd_cache_insert (const char *filename,
@@ -889,6 +880,8 @@ static int rrd_write (const data_set_t *ds, const value_list_t *vl,
                                        ds, vl, &rrdcreate_config);
                        if (status != 0)
                                return (-1);
+                       else if (rrdcreate_config.async)
+                               return (0);
                }
                else
                {
@@ -987,6 +980,13 @@ static int rrd_config (const char *key, const char *value)
                if (temp > 0)
                        rrdcreate_config.heartbeat = temp;
        }
+       else if (strcasecmp ("CreateFilesAsync", key) == 0)
+       {
+               if (IS_TRUE (value))
+                       rrdcreate_config.async = 1;
+               else
+                       rrdcreate_config.async = 0;
+       }
        else if (strcasecmp ("RRARows", key) == 0)
        {
                int tmp = atoi (value);
index 045f09b13b41bd3a3eab2562e16d76618fefe72e..ad81c89214a926d8e194bad96d2dd207bf6fa707 100644 (file)
@@ -108,7 +108,7 @@ static int csnmp_read_host (user_data_t *ud);
  */
 static void csnmp_oid_init (oid_t *dst, oid const *src, size_t n)
 {
-  assert (n <= STATIC_ARRAY_LEN (dst->oid));
+  assert (n <= STATIC_ARRAY_SIZE (dst->oid));
   memcpy (dst->oid, src, sizeof (*src) * n);
   dst->oid_len = n;
 }
@@ -1373,12 +1373,12 @@ static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
         csnmp_table_values_t *vt;
         oid_t vb_name;
         oid_t suffix;
+        int ret;
 
         csnmp_oid_init (&vb_name, vb->name, vb->name_length);
 
         /* Calculate the current suffix. This is later used to check that the
          * suffix is increasing. This also checks if we left the subtree */
-        int ret;
         ret = csnmp_oid_suffix (&suffix, &vb_name, data->values + i);
         if (ret != 0)
         {
diff --git a/src/tail_csv.c b/src/tail_csv.c
new file mode 100644 (file)
index 0000000..a70b665
--- /dev/null
@@ -0,0 +1,565 @@
+/**
+ * collectd - src/tail_csv.c
+ * Copyright (C) 2013 Kris Nielander
+ * Copyright (C) 2013 Florian 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
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Kris Nielander <nielander at fox-it.com>
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
+#include "common.h" /* auxiliary functions */
+#include "utils_tail.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct metric_definition_s {
+    char *name;
+    char *type;
+    char *instance;
+    int data_source_type;
+    int value_from;
+    struct metric_definition_s *next;
+};
+typedef struct metric_definition_s metric_definition_t;
+
+struct instance_definition_s {
+    char *instance;
+    char *path;
+    cu_tail_t *tail;
+    metric_definition_t **metric_list;
+    size_t metric_list_len;
+    cdtime_t interval;
+    int time_from;
+    struct instance_definition_s *next;
+};
+typedef struct instance_definition_s instance_definition_t;
+
+/* Private */
+static metric_definition_t *metric_head = NULL;
+
+static int tcsv_submit (instance_definition_t *id,
+        metric_definition_t *md,
+        value_t v, cdtime_t t)
+{
+    /* Registration variables */
+    value_list_t vl = VALUE_LIST_INIT;
+
+    /* Register */
+    vl.values_len = 1;
+    vl.values = &v;
+
+    sstrncpy(vl.host, hostname_g, sizeof (vl.host));
+    sstrncpy(vl.plugin, "tail_csv", sizeof(vl.plugin));
+    if (id->instance != NULL)
+        sstrncpy(vl.plugin_instance, id->instance, sizeof(vl.plugin_instance));
+    sstrncpy(vl.type, md->type, sizeof(vl.type));
+    if (md->instance != NULL)
+        sstrncpy(vl.type_instance, md->instance, sizeof(vl.type_instance));
+
+    vl.time = t;
+    vl.interval = id->interval;
+
+    return (plugin_dispatch_values(&vl));
+}
+
+static cdtime_t parse_time (char const *tbuf)
+{
+    double t;
+    char *endptr = 0;
+
+    errno = 0;
+    t = strtod (tbuf, &endptr);
+    if ((errno != 0) || (endptr == NULL) || (endptr[0] != 0))
+        return (cdtime ());
+
+    return (DOUBLE_TO_CDTIME_T (t));
+}
+
+static int tcsv_read_metric (instance_definition_t *id,
+        metric_definition_t *md,
+        char **fields, size_t fields_num)
+{
+    value_t v;
+    cdtime_t t;
+    int status;
+
+    if (md->data_source_type == -1)
+        return (EINVAL);
+
+    if ((md->value_from >= fields_num) || (id->time_from >= fields_num))
+        return (EINVAL);
+
+    t = 0;
+    if (id->time_from >= 0)
+        t = parse_time (fields[id->time_from]);
+
+    status = parse_value (fields[md->value_from], &v, md->data_source_type);
+    if (status != 0)
+        return (status);
+
+    return (tcsv_submit (id, md, v, t));
+}
+
+static _Bool tcsv_check_index (int index, size_t fields_num, char const *name)
+{
+    if (index < 0)
+        return 1;
+    else if (((size_t) index) < fields_num)
+        return 1;
+
+    ERROR ("tail_csv plugin: Metric \"%s\": Request for index %i when "
+            "only %zu fields are available.",
+            name, index, fields_num);
+    return (0);
+}
+
+static int tcsv_read_buffer (instance_definition_t *id,
+        char *buffer, size_t buffer_size)
+{
+    char **metrics;
+    size_t metrics_num;
+
+    char *ptr;
+    size_t i;
+
+    /* Remove newlines at the end of line. */
+    while (buffer_size > 0) {
+        if ((buffer[buffer_size - 1] == '\n')
+                || (buffer[buffer_size - 1] == '\r')) {
+            buffer[buffer_size - 1] = 0;
+            buffer_size--;
+        } else {
+            break;
+        }
+    }
+
+    /* Ignore empty lines. */
+    if ((buffer_size == 0) || (buffer[0] == '#'))
+        return (0);
+
+    /* Count the number of fields. */
+    metrics_num = 1;
+    for (i = 0; i < buffer_size; i++) {
+        if (buffer[i] == ',')
+            metrics_num++;
+    }
+
+    if (metrics_num == 1) {
+        ERROR("tail_csv plugin: last line of `%s' does not contain "
+                "enough values.", id->path);
+        return (-1);
+    }
+
+    /* Create a list of all values */
+    metrics = calloc (metrics_num, sizeof (*metrics));
+    if (metrics == NULL) {
+        ERROR ("tail_csv plugin: calloc failed.");
+        return (ENOMEM);
+    }
+
+    ptr = buffer;
+    metrics[0] = ptr;
+    i = 1;
+    for (ptr = buffer; *ptr != 0; ptr++) {
+        if (*ptr != ',')
+            continue;
+
+        *ptr = 0;
+        metrics[i] = ptr + 1;
+        i++;
+    }
+    assert (i == metrics_num);
+
+    /* Register values */
+    for (i = 0; i < id->metric_list_len; ++i){
+        metric_definition_t *md = id->metric_list[i];
+
+        if (!tcsv_check_index (md->value_from, metrics_num, md->name)
+                || !tcsv_check_index (id->time_from, metrics_num, md->name))
+            continue;
+
+        tcsv_read_metric (id, md, metrics, metrics_num);
+    }
+
+    /* Free up resources */
+    sfree (metrics);
+    return (0);
+}
+
+static int tcsv_read (user_data_t *ud) {
+    instance_definition_t *id;
+    id = ud->data;
+
+    if (id->tail == NULL)
+    {
+        id->tail = cu_tail_create (id->path);
+        if (id->tail == NULL)
+        {
+            ERROR ("tail_csv plugin: cu_tail_create (\"%s\") failed.",
+                    id->path);
+            return (-1);
+        }
+    }
+
+    while (42)
+    {
+        char buffer[1024];
+        size_t buffer_len;
+        int status;
+
+        status = cu_tail_readline (id->tail, buffer, (int) sizeof (buffer));
+        if (status != 0)
+        {
+            ERROR ("tail_csv plugin: File \"%s\": cu_tail_readline failed "
+                    "with status %i.", id->path, status);
+            return (-1);
+        }
+
+        buffer_len = strlen (buffer);
+        if (buffer_len == 0)
+            break;
+
+        tcsv_read_buffer (id, buffer, buffer_len);
+    }
+
+    return (0);
+}
+
+static void tcsv_metric_definition_destroy(void *arg){
+    metric_definition_t *md;
+    metric_definition_t *next;
+
+    md = arg;
+    if (md == NULL)
+        return;
+
+    next = md->next;
+    md->next = NULL;
+
+    sfree(md->name);
+    sfree(md->type);
+    sfree(md->instance);
+    sfree(md);
+
+    tcsv_metric_definition_destroy (next);
+}
+
+static int tcsv_config_get_index(oconfig_item_t *ci, int *ret_index) {
+    int index;
+
+    if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)){
+        WARNING("tail_csv plugin: The \"%s\" config option needs exactly one "
+                "integer argument.", ci->key);
+        return (-1);
+    }
+
+    index = (int) ci->values[0].value.number;
+    if (index < 0) {
+        WARNING("tail_csv plugin: The \"%s\" config option must be positive "
+                "(or zero).", ci->key);
+        return (-1);
+    }
+
+    *ret_index = index;
+    return (0);
+}
+
+/* Parse metric  */
+static int tcsv_config_add_metric(oconfig_item_t *ci){
+    metric_definition_t *md;
+    int status = 0;
+    int i;
+
+    md = (metric_definition_t *)malloc(sizeof(*md));
+    if (md == NULL)
+        return (-1);
+    memset(md, 0, sizeof(*md));
+    md->name = NULL;
+    md->type = NULL;
+    md->instance = NULL;
+    md->data_source_type = -1;
+    md->value_from = -1;
+    md->next = NULL;
+
+    status = cf_util_get_string (ci, &md->name);
+    if (status != 0) {
+        sfree (md);
+        return (-1);
+    }
+
+    for (i = 0; i < ci->children_num; ++i){
+        oconfig_item_t *option = ci->children + i;
+        status = 0;
+
+        if (strcasecmp("Type", option->key) == 0)
+            status = cf_util_get_string(option, &md->type);
+        else if (strcasecmp("Instance", option->key) == 0)
+            status = cf_util_get_string(option, &md->instance);
+        else if (strcasecmp("ValueFrom", option->key) == 0)
+            status = tcsv_config_get_index (option, &md->value_from);
+        else {
+            WARNING("tail_csv plugin: Option `%s' not allowed here.", option->key);
+            status = -1;
+        }
+
+        if (status != 0)
+            break;
+    }
+
+    if (status != 0){
+        tcsv_metric_definition_destroy(md);
+        return (-1);
+    }
+
+    /* Verify all necessary options have been set. */
+    if (md->type == NULL) {
+        WARNING("tail_csv plugin: Option `Type' must be set.");
+        status = -1;
+    } else if (md->value_from < 0) {
+        WARNING("tail_csv plugin: Option `ValueFrom' must be set.");
+        status = -1;
+    }
+    if (status != 0) {
+        tcsv_metric_definition_destroy(md);
+        return (status);
+    }
+
+    if (metric_head == NULL)
+        metric_head = md;
+    else {
+        metric_definition_t *last;
+        last = metric_head;
+        while (last->next != NULL)
+            last = last->next;
+        last->next = md;
+    }
+
+    return (0);
+}
+
+static void tcsv_instance_definition_destroy(void *arg){
+    instance_definition_t *id;
+
+    id = arg;
+    if (id == NULL)
+        return;
+
+    if (id->tail != NULL)
+        cu_tail_destroy (id->tail);
+    id->tail = NULL;
+
+    sfree(id->instance);
+    sfree(id->path);
+    sfree(id->metric_list);
+    sfree(id);
+}
+
+static int tcsv_config_add_instance_collect(instance_definition_t *id, oconfig_item_t *ci){
+    metric_definition_t *metric;
+    int i;
+
+    if (ci->values_num < 1){
+        WARNING("tail_csv plugin: The `Collect' config option needs at least one argument.");
+        return (-1);
+    }
+
+    /* Verify string arguments */
+    for (i = 0; i < ci->values_num; ++i)
+        if (ci->values[i].type != OCONFIG_TYPE_STRING){
+            WARNING("tail_csv plugin: All arguments to `Collect' must be strings.");
+            return (-1);
+        }
+
+    id->metric_list = (metric_definition_t **)malloc(sizeof(metric_definition_t *) * ci->values_num);
+    if (id->metric_list == NULL)
+        return (-1);
+
+    for (i = 0; i < ci->values_num; ++i){
+        for (metric = metric_head; metric != NULL; metric = metric->next)
+            if (strcasecmp(ci->values[i].value.string, metric->name) == 0)
+                break;
+
+        if (metric == NULL){
+            WARNING("tail_csv plugin: `Collect' argument not found `%s'.", ci->values[i].value.string);
+            return (-1);
+        }
+
+        id->metric_list[i] = metric;
+        id->metric_list_len++;
+    }
+
+    return (0);
+}
+
+/* <File /> block */
+static int tcsv_config_add_file(oconfig_item_t *ci)
+{
+    instance_definition_t* id;
+    int status = 0;
+    int i;
+
+    /* Registration variables */
+    char cb_name[DATA_MAX_NAME_LEN];
+    user_data_t cb_data;
+    struct timespec cb_interval;
+
+    id = malloc(sizeof(*id));
+    if (id == NULL)
+        return (-1);
+    memset(id, 0, sizeof(*id));
+    id->instance = NULL;
+    id->path = NULL;
+    id->metric_list = NULL;
+    id->time_from = -1;
+    id->next = NULL;
+
+    status = cf_util_get_string (ci, &id->path);
+    if (status != 0) {
+        sfree (id);
+        return (status);
+    }
+
+    /* Use default interval. */
+    id->interval = plugin_get_interval();
+
+    for (i = 0; i < ci->children_num; ++i){
+        oconfig_item_t *option = ci->children + i;
+        status = 0;
+
+        if (strcasecmp("Instance", option->key) == 0)
+            status = cf_util_get_string(option, &id->instance);
+        else if (strcasecmp("Collect", option->key) == 0)
+            status = tcsv_config_add_instance_collect(id, option);
+        else if (strcasecmp("Interval", option->key) == 0)
+            cf_util_get_cdtime(option, &id->interval);
+        else if (strcasecmp("TimeFrom", option->key) == 0)
+            status = tcsv_config_get_index (option, &id->time_from);
+        else {
+            WARNING("tail_csv plugin: Option `%s' not allowed here.", option->key);
+            status = -1;
+        }
+
+        if (status != 0)
+            break;
+    }
+
+    if (status != 0){
+        tcsv_instance_definition_destroy(id);
+        return (-1);
+    }
+
+    /* Verify all necessary options have been set. */
+    if (id->path == NULL){
+        WARNING("tail_csv plugin: Option `Path' must be set.");
+        status = -1;
+    } else if (id->metric_list == NULL){
+        WARNING("tail_csv plugin: Option `Collect' must be set.");
+        status = -1;
+   }
+
+    if (status != 0){
+        tcsv_instance_definition_destroy(id);
+        return (-1);
+    }
+
+    ssnprintf (cb_name, sizeof (cb_name), "tail_csv/%s", id->path);
+    memset(&cb_data, 0, sizeof(cb_data));
+    cb_data.data = id;
+    cb_data.free_func = tcsv_instance_definition_destroy;
+    CDTIME_T_TO_TIMESPEC(id->interval, &cb_interval);
+    status = plugin_register_complex_read(NULL, cb_name, tcsv_read, &cb_interval, &cb_data);
+
+    if (status != 0){
+        ERROR("tail_csv plugin: Registering complex read function failed.");
+        tcsv_instance_definition_destroy(id);
+        return (-1);
+    }
+
+    return (0);
+}
+
+/* Parse blocks */
+static int tcsv_config(oconfig_item_t *ci){
+    int i;
+    for (i = 0; i < ci->children_num; ++i){
+        oconfig_item_t *child = ci->children + i;
+        if (strcasecmp("Metric", child->key) == 0)
+            tcsv_config_add_metric(child);
+        else if (strcasecmp("File", child->key) == 0)
+            tcsv_config_add_file(child);
+        else
+            WARNING("tail_csv plugin: Ignore unknown config option `%s'.", child->key);
+    }
+
+    return (0);
+} /* int tcsv_config */
+
+static int tcsv_init(void) { /* {{{ */
+    static _Bool have_init = 0;
+    metric_definition_t *md;
+
+    if (have_init)
+        return (0);
+
+    for (md = metric_head; md != NULL; md = md->next) {
+        data_set_t const *ds;
+
+        /* Retrieve the data source type from the types db. */
+        ds = plugin_get_ds(md->type);
+        if (ds == NULL)
+        {
+            ERROR ("tail_csv plugin: Failed to look up type \"%s\" for "
+                    "metric \"%s\". It may not be defined in the types.db "
+                    "file. Please read the types.db(5) manual page for more "
+                    "details.",
+                    md->type, md->name);
+            continue;
+        }
+        else if (ds->ds_num != 1)
+        {
+            ERROR ("tail_csv plugin: The type \"%s\" has %i data sources. "
+                    "Only types with a single data soure are supported.",
+                    ds->type, ds->ds_num);
+            continue;
+        }
+
+        md->data_source_type = ds->ds->type;
+    }
+
+    return (0);
+} /* }}} int tcsv_init */
+
+static int tcsv_shutdown (void) {
+    tcsv_metric_definition_destroy (metric_head);
+    metric_head = NULL;
+
+    return (0);
+}
+
+void module_register(void){
+    plugin_register_complex_config("tail_csv", tcsv_config);
+    plugin_register_init("tail_csv", tcsv_init);
+    plugin_register_shutdown("tail_csv", tcsv_shutdown);
+}
+
+/* vim: set sw=4 sts=4 et : */
index 2a1ebb33a1758913c1f3f12ba0cda90423eda1dd..fb443d9e99023e59bc367e2579c6adafe7ce7bb3 100644 (file)
@@ -52,6 +52,7 @@ dns_response          value:DERIVE:0:U
 dns_transfer           value:DERIVE:0:U
 dns_update             value:DERIVE:0:U
 dns_zops               value:DERIVE:0:U
+duration               seconds:GAUGE:0:U
 email_check            value:GAUGE:0:U
 email_count            value:GAUGE:0:U
 email_size             value:GAUGE:0:U
@@ -76,7 +77,9 @@ if_multicast          value:DERIVE:0:U
 if_octets              rx:DERIVE:0:U, tx:DERIVE:0:U
 if_packets             rx:DERIVE:0:U, tx:DERIVE:0:U
 if_rx_errors           value:DERIVE:0:U
+if_rx_octets           value:DERIVE:0:U
 if_tx_errors           value:DERIVE:0:U
+if_tx_octets           value:DERIVE:0:U
 invocations            value:DERIVE:0:U
 io_octets              rx:DERIVE:0:U, tx:DERIVE:0:U
 io_packets             rx:DERIVE:0:U, tx:DERIVE:0:U
diff --git a/src/utils_random.c b/src/utils_random.c
new file mode 100644 (file)
index 0000000..b873845
--- /dev/null
@@ -0,0 +1,75 @@
+/**
+ * collectd - src/utils_random.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 <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "utils_time.h"
+
+#include <pthread.h>
+
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+static _Bool have_seed = 0;
+static unsigned short seed[3];
+
+static void cdrand_seed (void)
+{
+  cdtime_t t;
+
+  if (have_seed)
+    return;
+
+  t = cdtime();
+
+  seed[0] = (unsigned short) t;
+  seed[1] = (unsigned short) (t >> 16);
+  seed[2] = (unsigned short) (t >> 32);
+
+  have_seed = 1;
+}
+
+double cdrand_d (void)
+{
+  double r;
+
+  pthread_mutex_lock (&lock);
+  cdrand_seed ();
+  r = erand48 (seed);
+  pthread_mutex_unlock (&lock);
+
+  return (r);
+}
+
+long cdrand_range (long min, long max)
+{
+  long range;
+  long r;
+
+  range = 1 + max - min;
+
+  r = (long) (0.5 + (cdrand_d () * range));
+  r += min;
+
+  return (r);
+}
diff --git a/src/utils_random.h b/src/utils_random.h
new file mode 100644 (file)
index 0000000..b05f4c8
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * collectd - src/utils_random.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 <octo at collectd.org>
+ **/
+
+/**
+ * Returns a random double value in the range [0..1), i.e. excluding 1.
+ *
+ * This function is thread- and reentrant-safe.
+ */
+double cdrand_d (void);
+
+/**
+ * Returns a random long between min and max, inclusively.
+ *
+ * If min is larger than max, the result may be rounded incorrectly and may be
+ * outside the intended range. This function is thread- and reentrant-safe.
+ */
+long cdrand_range (long min, long max);
index 091b5fa6dce1d2cc3bc48bb093298f39e5d0af46..a34e0da0103936de01b652996cb956b5d327f93a 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/utils_rrdcreate.c
- * Copyright (C) 2006-2008  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
@@ -16,7 +16,7 @@
  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  *
  * Authors:
- *   Florian octo Forster <octo at verplant.org>
+ *   Florian octo Forster <octo at collectd.org>
  **/
 
 #include "collectd.h"
 #include <pthread.h>
 #include <rrd.h>
 
+struct srrd_create_args_s
+{
+  char *filename;
+  unsigned long pdp_step;
+  time_t last_up;
+  int argc;
+  char **argv;
+};
+typedef struct srrd_create_args_s srrd_create_args_t;
+
+struct async_create_file_s;
+typedef struct async_create_file_s async_create_file_t;
+struct async_create_file_s
+{
+  char *filename;
+  async_create_file_t *next;
+};
+
 /*
  * Private variables
  */
@@ -51,6 +69,9 @@ static int rra_types_num = STATIC_ARRAY_SIZE (rra_types);
 static pthread_mutex_t librrd_lock = PTHREAD_MUTEX_INITIALIZER;
 #endif
 
+static async_create_file_t *async_creation_list = NULL;
+static pthread_mutex_t async_creation_lock = PTHREAD_MUTEX_INITIALIZER;
+
 /*
  * Private functions
  */
@@ -65,6 +86,71 @@ static void rra_free (int rra_num, char **rra_def) /* {{{ */
   sfree (rra_def);
 } /* }}} void rra_free */
 
+static void srrd_create_args_destroy (srrd_create_args_t *args)
+{
+  if (args == NULL)
+    return;
+
+  sfree (args->filename);
+  if (args->argv != NULL)
+  {
+    int i;
+    for (i = 0; i < args->argc; i++)
+      sfree (args->argv[i]);
+    sfree (args->argv);
+  }
+} /* void srrd_create_args_destroy */
+
+static srrd_create_args_t *srrd_create_args_create (const char *filename,
+    unsigned long pdp_step, time_t last_up,
+    int argc, const char **argv)
+{
+  srrd_create_args_t *args;
+
+  args = malloc (sizeof (*args));
+  if (args == NULL)
+  {
+    ERROR ("srrd_create_args_create: malloc failed.");
+    return (NULL);
+  }
+  memset (args, 0, sizeof (*args));
+  args->filename = NULL;
+  args->pdp_step = pdp_step;
+  args->last_up = last_up;
+  args->argv = NULL;
+
+  args->filename = strdup (filename);
+  if (args->filename == NULL)
+  {
+    ERROR ("srrd_create_args_create: strdup failed.");
+    srrd_create_args_destroy (args);
+    return (NULL);
+  }
+
+  args->argv = calloc ((size_t) (argc + 1), sizeof (*args->argv));
+  if (args->argv == NULL)
+  {
+    ERROR ("srrd_create_args_create: calloc failed.");
+    srrd_create_args_destroy (args);
+    return (NULL);
+  }
+
+  for (args->argc = 0; args->argc < argc; args->argc++)
+  {
+    args->argv[args->argc] = strdup (argv[args->argc]);
+    if (args->argv[args->argc] == NULL)
+    {
+      ERROR ("srrd_create_args_create: strdup failed.");
+      srrd_create_args_destroy (args);
+      return (NULL);
+    }
+  }
+  assert (args->argc == argc);
+  args->argv[args->argc] = NULL;
+
+  return (args);
+} /* srrd_create_args_t *srrd_create_args_create */
+
 /* * * * * * * * * *
  * WARNING:  Magic *
  * * * * * * * * * */
@@ -359,6 +445,197 @@ static int srrd_create (const char *filename, /* {{{ */
 } /* }}} int srrd_create */
 #endif /* !HAVE_THREADSAFE_LIBRRD */
 
+static int lock_file (char const *filename) /* {{{ */
+{
+  async_create_file_t *ptr;
+  struct stat sb;
+  int status;
+
+  pthread_mutex_lock (&async_creation_lock);
+
+  for (ptr = async_creation_list; ptr != NULL; ptr = ptr->next)
+    if (strcmp (filename, ptr->filename) == 0)
+      break;
+
+  if (ptr != NULL)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    return (EEXIST);
+  }
+
+  status = stat (filename, &sb);
+  if ((status == 0) || (errno != ENOENT))
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    return (EEXIST);
+  }
+
+  ptr = malloc (sizeof (*ptr));
+  if (ptr == NULL)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    return (ENOMEM);
+  }
+
+  ptr->filename = strdup (filename);
+  if (ptr->filename == NULL)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    sfree (ptr);
+    return (ENOMEM);
+  }
+
+  ptr->next = async_creation_list;
+  async_creation_list = ptr;
+
+  pthread_mutex_unlock (&async_creation_lock);
+
+  return (0);
+} /* }}} int lock_file */
+
+static int unlock_file (char const *filename) /* {{{ */
+{
+  async_create_file_t *this;
+  async_create_file_t *prev;
+
+
+  pthread_mutex_lock (&async_creation_lock);
+
+  prev = NULL;
+  for (this = async_creation_list; this != NULL; this = this->next)
+  {
+    if (strcmp (filename, this->filename) == 0)
+      break;
+    prev = this;
+  }
+
+  if (this == NULL)
+  {
+    pthread_mutex_unlock (&async_creation_lock);
+    return (ENOENT);
+  }
+
+  if (prev == NULL)
+  {
+    assert (this == async_creation_list);
+    async_creation_list = this->next;
+  }
+  else
+  {
+    assert (this == prev->next);
+    prev->next = this->next;
+  }
+  this->next = NULL;
+
+  pthread_mutex_unlock (&async_creation_lock);
+
+  sfree (this->filename);
+  sfree (this);
+
+  return (0);
+} /* }}} int unlock_file */
+
+static void *srrd_create_thread (void *targs) /* {{{ */
+{
+  srrd_create_args_t *args = targs;
+  char tmpfile[PATH_MAX];
+  int status;
+
+  status = lock_file (args->filename);
+  if (status != 0)
+  {
+    if (status == EEXIST)
+      NOTICE ("srrd_create_thread: File \"%s\" is already being created.",
+          args->filename);
+    else
+      ERROR ("srrd_create_thread: Unable to lock file \"%s\".",
+          args->filename);
+    srrd_create_args_destroy (args);
+    return (0);
+  }
+
+  ssnprintf (tmpfile, sizeof (tmpfile), "%s.async", args->filename);
+
+  status = srrd_create (tmpfile, args->pdp_step, args->last_up,
+      args->argc, (void *) args->argv);
+  if (status != 0)
+  {
+    WARNING ("srrd_create_thread: srrd_create (%s) returned status %i.",
+        args->filename, status);
+    unlink (tmpfile);
+    unlock_file (args->filename);
+    srrd_create_args_destroy (args);
+    return (0);
+  }
+
+  status = rename (tmpfile, args->filename);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("srrd_create_thread: rename (\"%s\", \"%s\") failed: %s",
+        tmpfile, args->filename,
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    unlink (tmpfile);
+    unlock_file (args->filename);
+    srrd_create_args_destroy (args);
+    return (0);
+  }
+
+  DEBUG ("srrd_create_thread: Successfully created RRD file \"%s\".",
+      args->filename);
+
+  unlock_file (args->filename);
+  srrd_create_args_destroy (args);
+
+  return (0);
+} /* }}} void *srrd_create_thread */
+
+static int srrd_create_async (const char *filename, /* {{{ */
+    unsigned long pdp_step, time_t last_up,
+    int argc, const char **argv)
+{
+  srrd_create_args_t *args;
+  pthread_t thread;
+  pthread_attr_t attr;
+  int status;
+
+  DEBUG ("srrd_create_async: Creating \"%s\" in the background.", filename);
+
+  args = srrd_create_args_create (filename, pdp_step, last_up, argc, argv);
+  if (args == NULL)
+    return (-1);
+
+  status = pthread_attr_init (&attr);
+  if (status != 0)
+  {
+    srrd_create_args_destroy (args);
+    return (-1);
+  }
+
+  status = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+  if (status != 0)
+  {
+    pthread_attr_destroy (&attr);
+    srrd_create_args_destroy (args);
+    return (-1);
+  }
+
+  status = pthread_create (&thread, &attr, srrd_create_thread, args);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("srrd_create_async: pthread_create failed: %s",
+        sstrerror (status, errbuf, sizeof (errbuf)));
+    pthread_attr_destroy (&attr);
+    srrd_create_args_destroy (args);
+    return (status);
+  }
+
+  pthread_attr_destroy (&attr);
+  /* args is freed in srrd_create_thread(). */
+  return (0);
+} /* }}} int srrd_create_async */
+
 /*
  * Public functions
  */
@@ -415,24 +692,36 @@ int cu_rrd_create_file (const char *filename, /* {{{ */
   else
     stepsize = (unsigned long) CDTIME_T_TO_TIME_T (vl->interval);
 
-  status = srrd_create (filename, stepsize, last_up,
-      argc, (const char **) argv);
-
-  free (argv);
-  ds_free (ds_num, ds_def);
-  rra_free (rra_num, rra_def);
-
-  if (status != 0)
+  if (cfg->async)
   {
-    WARNING ("cu_rrd_create_file: srrd_create (%s) returned status %i.",
-        filename, status);
+    status = srrd_create_async (filename, stepsize, last_up,
+        argc, (const char **) argv);
+    if (status != 0)
+      WARNING ("cu_rrd_create_file: srrd_create_async (%s) "
+          "returned status %i.",
+          filename, status);
   }
-  else
+  else /* synchronous */
   {
-    DEBUG ("cu_rrd_create_file: Successfully created RRD file \"%s\".",
-        filename);
+    status = srrd_create (filename, stepsize, last_up,
+        argc, (const char **) argv);
+
+    if (status != 0)
+    {
+      WARNING ("cu_rrd_create_file: srrd_create (%s) returned status %i.",
+          filename, status);
+    }
+    else
+    {
+      DEBUG ("cu_rrd_create_file: Successfully created RRD file \"%s\".",
+          filename);
+    }
   }
 
+  free (argv);
+  ds_free (ds_num, ds_def);
+  rra_free (rra_num, rra_def);
+
   return (status);
 } /* }}} int cu_rrd_create_file */
 
index 103ca57070ad3b99fa72183966eefdbd69a1fedc..fdfd6ecb9727f51a6589e551bda125db72018836 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/utils_rrdcreate.h
- * Copyright (C) 2008  Florian octo Forster
+ * Copyright (C) 2008-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
@@ -16,7 +16,7 @@
  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  *
  * Authors:
- *   Florian octo Forster <octo at verplant.org>
+ *   Florian octo Forster <octo at collectd.org>
  **/
 
 #ifndef UTILS_RRDCREATE_H
@@ -38,6 +38,8 @@ struct rrdcreate_config_s
 
   char **consolidation_functions;
   size_t consolidation_functions_num;
+
+  _Bool async;
 };
 typedef struct rrdcreate_config_s rrdcreate_config_t;
 
index 2dada2473d5c35b16aab662c04b920d5a08d0768..722c45230fce5669f34d999a644a0a186a9273c5 100644 (file)
@@ -25,6 +25,9 @@
  **/
 
 #include "collectd.h"
+
+#include <regex.h>
+
 #include "common.h"
 #include "utils_vl_lookup.h"
 #include "utils_avltree.h"
 /*
  * Types
  */
+struct part_match_s
+{
+  char str[DATA_MAX_NAME_LEN];
+  regex_t regex;
+  _Bool is_regex;
+};
+typedef struct part_match_s part_match_t;
+
+struct identifier_match_s
+{
+  part_match_t host;
+  part_match_t plugin;
+  part_match_t plugin_instance;
+  part_match_t type;
+  part_match_t type_instance;
+
+  unsigned int group_by;
+};
+typedef struct identifier_match_s identifier_match_t;
+
 struct lookup_s
 {
   c_avl_tree_t *by_type_tree;
@@ -64,7 +87,7 @@ struct user_obj_s
 struct user_class_s
 {
   void *user_class;
-  identifier_t ident;
+  identifier_match_t match;
   user_obj_t *user_obj_list; /* list of user_obj */
 };
 typedef struct user_class_s user_class_t;
@@ -87,6 +110,87 @@ typedef struct by_type_entry_s by_type_entry_t;
 /*
  * Private functions
  */
+static _Bool lu_part_matches (part_match_t const *match, /* {{{ */
+    char const *str)
+{
+  if (match->is_regex)
+  {
+    /* Short cut popular catch-all regex. */
+    if (strcmp (".*", match->str) == 0)
+      return (1);
+
+    int status = regexec (&match->regex, str,
+        /* nmatch = */ 0, /* pmatch = */ NULL,
+        /* flags = */ 0);
+    if (status == 0)
+      return (1);
+    else
+      return (0);
+  }
+  else if (strcmp (match->str, str) == 0)
+    return (1);
+  else
+    return (0);
+} /* }}} _Bool lu_part_matches */
+
+static int lu_copy_ident_to_match_part (part_match_t *match_part, /* {{{ */
+    char const *ident_part)
+{
+  size_t len = strlen (ident_part);
+  int status;
+
+  if ((len < 3) || (ident_part[0] != '/') || (ident_part[len - 1] != '/'))
+  {
+    sstrncpy (match_part->str, ident_part, sizeof (match_part->str));
+    match_part->is_regex = 0;
+    return (0);
+  }
+
+  /* Copy string without the leading slash. */
+  sstrncpy (match_part->str, ident_part + 1, sizeof (match_part->str));
+  assert (sizeof (match_part->str) > len);
+  /* strip trailing slash */
+  match_part->str[len - 2] = 0;
+  
+  status = regcomp (&match_part->regex, match_part->str,
+      /* flags = */ REG_EXTENDED);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    regerror (status, &match_part->regex, errbuf, sizeof (errbuf));
+    ERROR ("utils_vl_lookup: Compiling regular expression \"%s\" failed: %s",
+        match_part->str, errbuf);
+    return (EINVAL);
+  }
+  match_part->is_regex = 1;
+  
+  return (0);
+} /* }}} int lu_copy_ident_to_match_part */
+
+static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
+    identifier_t const *ident, unsigned int group_by)
+{
+  memset (match, 0, sizeof (*match));
+
+  match->group_by = group_by;
+
+#define COPY_FIELD(field) do { \
+  int status = lu_copy_ident_to_match_part (&match->field, ident->field); \
+  if (status != 0) \
+    return (status); \
+} while (0)
+
+  COPY_FIELD (host);
+  COPY_FIELD (plugin);
+  COPY_FIELD (plugin_instance);
+  COPY_FIELD (type);
+  COPY_FIELD (type_instance);
+
+#undef COPY_FIELD
+
+  return (0);
+} /* }}} int lu_copy_ident_to_match */
+
 static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
     data_set_t const *ds, value_list_t const *vl,
     user_class_t *user_class)
@@ -110,21 +214,21 @@ static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
     return (NULL);
   }
 
-  sstrncpy (user_obj->ident.host,
-    LU_IS_ALL (user_class->ident.host) ?  "/all/" : vl->host,
-    sizeof (user_obj->ident.host));
-  sstrncpy (user_obj->ident.plugin,
-    LU_IS_ALL (user_class->ident.plugin) ?  "/all/" : vl->plugin,
-    sizeof (user_obj->ident.plugin));
-  sstrncpy (user_obj->ident.plugin_instance,
-    LU_IS_ALL (user_class->ident.plugin_instance) ?  "/all/" : vl->plugin_instance,
-    sizeof (user_obj->ident.plugin_instance));
-  sstrncpy (user_obj->ident.type,
-    LU_IS_ALL (user_class->ident.type) ?  "/all/" : vl->type,
-    sizeof (user_obj->ident.type));
-  sstrncpy (user_obj->ident.type_instance,
-    LU_IS_ALL (user_class->ident.type_instance) ?  "/all/" : vl->type_instance,
-    sizeof (user_obj->ident.type_instance));
+#define COPY_FIELD(field, group_mask) do { \
+  if (user_class->match.field.is_regex \
+      && ((user_class->match.group_by & group_mask) == 0)) \
+    sstrncpy (user_obj->ident.field, "/.*/", sizeof (user_obj->ident.field)); \
+  else \
+    sstrncpy (user_obj->ident.field, vl->field, sizeof (user_obj->ident.field)); \
+} while (0)
+
+  COPY_FIELD (host, LU_GROUP_BY_HOST);
+  COPY_FIELD (plugin, LU_GROUP_BY_PLUGIN);
+  COPY_FIELD (plugin_instance, LU_GROUP_BY_PLUGIN_INSTANCE);
+  COPY_FIELD (type, 0);
+  COPY_FIELD (type_instance, LU_GROUP_BY_TYPE_INSTANCE);
+
+#undef COPY_FIELD
 
   if (user_class->user_obj_list == NULL)
   {
@@ -150,14 +254,21 @@ static user_obj_t *lu_find_user_obj (user_class_t *user_class, /* {{{ */
       ptr != NULL;
       ptr = ptr->next)
   {
-    if (!LU_IS_ALL (ptr->ident.host)
-        && (strcmp (ptr->ident.host, vl->host) != 0))
+    if (user_class->match.host.is_regex
+        && (user_class->match.group_by & LU_GROUP_BY_HOST)
+        && (strcmp (vl->host, ptr->ident.host) != 0))
+      continue;
+    if (user_class->match.plugin.is_regex
+        && (user_class->match.group_by & LU_GROUP_BY_PLUGIN)
+        && (strcmp (vl->plugin, ptr->ident.plugin) != 0))
       continue;
-    if (!LU_IS_ALL (ptr->ident.plugin_instance)
-        && (strcmp (ptr->ident.plugin_instance, vl->plugin_instance) != 0))
+    if (user_class->match.plugin_instance.is_regex
+        && (user_class->match.group_by & LU_GROUP_BY_PLUGIN_INSTANCE)
+        && (strcmp (vl->plugin_instance, ptr->ident.plugin_instance) != 0))
       continue;
-    if (!LU_IS_ALL (ptr->ident.type_instance)
-        && (strcmp (ptr->ident.type_instance, vl->type_instance) != 0))
+    if (user_class->match.type_instance.is_regex
+        && (user_class->match.group_by & LU_GROUP_BY_TYPE_INSTANCE)
+        && (strcmp (vl->type_instance, ptr->ident.type_instance) != 0))
       continue;
 
     return (ptr);
@@ -173,21 +284,14 @@ static int lu_handle_user_class (lookup_t *obj, /* {{{ */
   user_obj_t *user_obj;
   int status;
 
-  assert (strcmp (vl->type, user_class->ident.type) == 0);
-  assert (LU_IS_WILDCARD (user_class->ident.plugin)
-      || (strcmp (vl->plugin, user_class->ident.plugin) == 0));
+  assert (strcmp (vl->type, user_class->match.type.str) == 0);
+  assert (user_class->match.plugin.is_regex
+      || (strcmp (vl->plugin, user_class->match.plugin.str)) == 0);
 
-  /* When we get here, type and plugin already match the user class. Now check
-   * the rest of the fields. */
-  if (!LU_IS_WILDCARD (user_class->ident.type_instance)
-      && (strcmp (vl->type_instance, user_class->ident.type_instance) != 0))
-    return (1);
-  if (!LU_IS_WILDCARD (user_class->ident.plugin_instance)
-      && (strcmp (vl->plugin_instance,
-          user_class->ident.plugin_instance) != 0))
-    return (1);
-  if (!LU_IS_WILDCARD (user_class->ident.host)
-      && (strcmp (vl->host, user_class->ident.host) != 0))
+  if (!lu_part_matches (&user_class->match.type_instance, vl->type_instance)
+      || !lu_part_matches (&user_class->match.plugin_instance, vl->plugin_instance)
+      || !lu_part_matches (&user_class->match.plugin, vl->plugin)
+      || !lu_part_matches (&user_class->match.host, vl->host))
     return (1);
 
   user_obj = lu_find_user_obj (user_class, vl);
@@ -292,14 +396,15 @@ static by_type_entry_t *lu_search_by_type (lookup_t *obj, /* {{{ */
 } /* }}} by_type_entry_t *lu_search_by_type */
 
 static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
-    identifier_t const *ident, user_class_list_t *user_class_list)
+    user_class_list_t *user_class_list)
 {
   user_class_list_t *ptr = NULL;
+  identifier_match_t const *match = &user_class_list->entry.match;
 
   /* Lookup user_class_list from the per-plugin structure. If this is the first
    * user_class to be added, the blocks return immediately. Otherwise they will
    * set "ptr" to non-NULL. */
-  if (LU_IS_WILDCARD (ident->plugin))
+  if (match->plugin.is_regex)
   {
     if (by_type->wildcard_plugin_list == NULL)
     {
@@ -314,11 +419,11 @@ static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
     int status;
 
     status = c_avl_get (by_type->by_plugin_tree,
-        ident->plugin, (void *) &ptr);
+        match->plugin.str, (void *) &ptr);
 
     if (status != 0) /* plugin not yet in tree */
     {
-      char *plugin_copy = strdup (ident->plugin);
+      char *plugin_copy = strdup (match->plugin.str);
 
       if (plugin_copy == NULL)
       {
@@ -478,7 +583,7 @@ void lookup_destroy (lookup_t *obj) /* {{{ */
 } /* }}} void lookup_destroy */
 
 int lookup_add (lookup_t *obj, /* {{{ */
-    identifier_t const *ident, void *user_class)
+    identifier_t const *ident, unsigned int group_by, void *user_class)
 {
   by_type_entry_t *by_type = NULL;
   user_class_list_t *user_class_obj;
@@ -495,11 +600,11 @@ int lookup_add (lookup_t *obj, /* {{{ */
   }
   memset (user_class_obj, 0, sizeof (*user_class_obj));
   user_class_obj->entry.user_class = user_class;
-  memmove (&user_class_obj->entry.ident, ident, sizeof (*ident));
+  lu_copy_ident_to_match (&user_class_obj->entry.match, ident, group_by);
   user_class_obj->entry.user_obj_list = NULL;
   user_class_obj->next = NULL;
 
-  return (lu_add_by_plugin (by_type, ident, user_class_obj));
+  return (lu_add_by_plugin (by_type, user_class_obj));
 } /* }}} int lookup_add */
 
 /* returns the number of successful calls to the callback function */
index c006fc7ff681ea88faed0b263f1b2a375bf34392..31787f53250b2ca893827170a8ab868f2a6ea024 100644 (file)
@@ -63,12 +63,11 @@ struct identifier_s
 };
 typedef struct identifier_s identifier_t;
 
-#define LU_ANY "/any/"
-#define LU_ALL "/all/"
-
-#define LU_IS_ANY(str) (strcmp (str, LU_ANY) == 0)
-#define LU_IS_ALL(str) (strcmp (str, LU_ALL) == 0)
-#define LU_IS_WILDCARD(str) (LU_IS_ANY(str) || LU_IS_ALL(str))
+#define LU_GROUP_BY_HOST            0x01
+#define LU_GROUP_BY_PLUGIN          0x02
+#define LU_GROUP_BY_PLUGIN_INSTANCE 0x04
+/* #define LU_GROUP_BY_TYPE            0x00 */
+#define LU_GROUP_BY_TYPE_INSTANCE   0x10
 
 /*
  * Functions
@@ -81,7 +80,7 @@ lookup_t *lookup_create (lookup_class_callback_t,
 void lookup_destroy (lookup_t *obj);
 
 int lookup_add (lookup_t *obj,
-    identifier_t const *ident, void *user_class);
+    identifier_t const *ident, unsigned int group_by, void *user_class);
 
 /* TODO(octo): Pass lookup_obj_callback_t to lookup_search()? */
 int lookup_search (lookup_t *obj,
index 6265b321f0b5481edf6bda51cb572793effdb254..bbb3a67fbe24d640029bef0f02cd7823f7bc2eac 100644 (file)
@@ -82,7 +82,8 @@ static void *lookup_class_callback (data_set_t const *ds,
 static void checked_lookup_add (lookup_t *obj, /* {{{ */
     char const *host,
     char const *plugin, char const *plugin_instance,
-    char const *type, char const *type_instance)
+    char const *type, char const *type_instance,
+    unsigned int group_by)
 {
   identifier_t ident;
   void *user_class;
@@ -98,7 +99,7 @@ static void checked_lookup_add (lookup_t *obj, /* {{{ */
   user_class = malloc (sizeof (ident));
   memmove (user_class, &ident, sizeof (ident));
 
-  status = lookup_add (obj, &ident, user_class);
+  status = lookup_add (obj, &ident, group_by, user_class);
   assert (status == 0);
 } /* }}} void test_add */
 
@@ -143,7 +144,7 @@ static void testcase0 (void)
 {
   lookup_t *obj = checked_lookup_create ();
 
-  checked_lookup_add (obj, "/any/", "test", "", "test", "/all/");
+  checked_lookup_add (obj, "/.*/", "test", "", "test", "/.*/", LU_GROUP_BY_HOST);
   checked_lookup_search (obj, "host0", "test", "", "test", "0",
       /* expect new = */ 1);
   checked_lookup_search (obj, "host0", "test", "", "test", "1",
@@ -160,7 +161,7 @@ static void testcase1 (void)
 {
   lookup_t *obj = checked_lookup_create ();
 
-  checked_lookup_add (obj, "/any/", "/all/", "/all/", "test", "/all/");
+  checked_lookup_add (obj, "/.*/", "/.*/", "/.*/", "test", "/.*/", LU_GROUP_BY_HOST);
   checked_lookup_search (obj, "host0", "plugin0", "", "test", "0",
       /* expect new = */ 1);
   checked_lookup_search (obj, "host0", "plugin0", "", "test", "1",
@@ -186,8 +187,8 @@ static void testcase2 (void)
   lookup_t *obj = checked_lookup_create ();
   int status;
 
-  checked_lookup_add (obj, "/any/", "plugin0", "", "test", "/all/");
-  checked_lookup_add (obj, "/any/", "/all/", "", "test", "ti0");
+  checked_lookup_add (obj, "/.*/", "plugin0", "", "test", "/.*/", LU_GROUP_BY_HOST);
+  checked_lookup_add (obj, "/.*/", "/.*/", "", "test", "ti0", LU_GROUP_BY_HOST);
 
   status = checked_lookup_search (obj, "host0", "plugin1", "", "test", "",
       /* expect new = */ 0);
@@ -205,10 +206,39 @@ static void testcase2 (void)
   lookup_destroy (obj);
 }
 
+static void testcase3 (void)
+{
+  lookup_t *obj = checked_lookup_create ();
+
+  checked_lookup_add (obj, "/^db[0-9]\\./", "cpu", "/.*/", "cpu", "/.*/",
+      LU_GROUP_BY_TYPE_INSTANCE);
+  checked_lookup_search (obj, "db0.example.com", "cpu", "0", "cpu", "user",
+      /* expect new = */ 1);
+  checked_lookup_search (obj, "db0.example.com", "cpu", "0", "cpu", "idle",
+      /* expect new = */ 1);
+  checked_lookup_search (obj, "db0.example.com", "cpu", "1", "cpu", "user",
+      /* expect new = */ 0);
+  checked_lookup_search (obj, "db0.example.com", "cpu", "1", "cpu", "idle",
+      /* expect new = */ 0);
+  checked_lookup_search (obj, "app0.example.com", "cpu", "0", "cpu", "user",
+      /* expect new = */ 0);
+  checked_lookup_search (obj, "app0.example.com", "cpu", "0", "cpu", "idle",
+      /* expect new = */ 0);
+  checked_lookup_search (obj, "db1.example.com", "cpu", "0", "cpu", "user",
+      /* expect new = */ 0);
+  checked_lookup_search (obj, "db1.example.com", "cpu", "0", "cpu", "idle",
+      /* expect new = */ 0);
+  checked_lookup_search (obj, "db1.example.com", "cpu", "0", "cpu", "system",
+      /* expect new = */ 1);
+
+  lookup_destroy (obj);
+}
+
 int main (int argc, char **argv) /* {{{ */
 {
   testcase0 ();
   testcase1 ();
   testcase2 ();
+  testcase3 ();
   return (EXIT_SUCCESS);
 } /* }}} int main */
index cf23f5b37eea4e6505f62cb8ac37c65def408a07..2df6063984c04b09ebc6365a38144f33c61692f8 100644 (file)
 #define UUID_PRINTABLE_COMPACT_LENGTH  (UUID_RAW_LENGTH * 2)
 #define UUID_PRINTABLE_NORMAL_LENGTH  (UUID_PRINTABLE_COMPACT_LENGTH + 4)
 
-#define HANDLE_PREFIX "Handle"
-#define SYSINFO_PREFIX "System Information"
-#define ALT_SYSINFO_PREFIX "\tSystem Information"
-#define UUID_PREFIX "\tUUID:"
-#define ALT_UUID_PREFIX "\t\tUUID:"
+static char *uuidfile = NULL;
+
+static const char *config_keys[] = {
+    "UUIDFile"
+};
 
 static int
 looks_like_a_uuid (const char *uuid)
@@ -65,42 +65,28 @@ static char *
 uuid_parse_dmidecode(FILE *file)
 {
     char line[1024];
-    int inSysInfo = 0;
 
-    for (;;) {
-        if (!fgets(line, sizeof(line)/sizeof(char), file)) {
-            return NULL;
-        }
-        if (strncmp(line, HANDLE_PREFIX,
-                    (sizeof(HANDLE_PREFIX)/sizeof(char))-1) == 0) {
-            /*printf("Got handle %s\n", line);*/
-            inSysInfo = 0;
-        } else if (strncmp(line, SYSINFO_PREFIX,
-                           (sizeof(SYSINFO_PREFIX)/sizeof(char))-1) == 0) {
-            /*printf("Got system info %s\n", line);*/
-            inSysInfo = 1;
-        } else if (strncmp(line, ALT_SYSINFO_PREFIX,
-                           (sizeof(ALT_SYSINFO_PREFIX)/sizeof(char))-1) == 0) {
-            /*printf("Got alt system info %s\n", line);*/
-            inSysInfo = 1;
-        }
-        
-        if (inSysInfo) {
-            if (strncmp(line, UUID_PREFIX,
-                        (sizeof(UUID_PREFIX)/sizeof(char))-1) == 0) {
-                char *uuid = line + (sizeof(UUID_PREFIX)/sizeof(char));
-                /*printf("Got uuid [%s]\n", uuid);*/
-                if (looks_like_a_uuid (uuid))
-                    return strdup (uuid);
-            }
-            if (strncmp(line, ALT_UUID_PREFIX,
-                        (sizeof(ALT_UUID_PREFIX)/sizeof(char))-1) == 0) {
-                char *uuid = line + (sizeof(ALT_UUID_PREFIX)/sizeof(char));
-                /*printf("Got alt uuid [%s]\n", uuid);*/
-                if (looks_like_a_uuid (uuid))
-                    return strdup (uuid);
-            }
-        }
+    while (fgets (line, sizeof (line), file) != NULL)
+    {
+        char *fields[4];
+        int fields_num;
+
+        strstripnewline (line);
+
+        /* Look for a line reading:
+         *   UUID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+         */
+        fields_num = strsplit (line, fields, STATIC_ARRAY_SIZE (fields));
+        if (fields_num != 2)
+            continue;
+
+        if (strcmp("UUID:", fields[0]) != 0)
+            continue;
+
+        if (!looks_like_a_uuid (fields[1]))
+            continue;
+
+        return strdup (fields[1]);
     }
     return NULL;
 }
@@ -187,23 +173,22 @@ static char *
 uuid_get_from_file(const char *path)
 {
     FILE *file;
-    char uuid[UUID_PRINTABLE_NORMAL_LENGTH+1];
+    char uuid[UUID_PRINTABLE_NORMAL_LENGTH + 1] = "";
 
-    if (!(file = fopen(path, "r"))) {
+    file = fopen (path, "r");
+    if (file == NULL)
         return NULL;
-    }
 
     if (!fgets(uuid, sizeof(uuid), file)) {
         fclose(file);
         return NULL;
     }
     fclose(file);
+    strstripnewline (uuid);
 
     return strdup (uuid);
 }
 
-static char *uuidfile = NULL;
-
 static char *
 uuid_get_local(void)
 {
@@ -231,23 +216,19 @@ uuid_get_local(void)
     return NULL;
 }
 
-static const char *config_keys[] = {
-    "UUIDFile",
-    NULL
-};
-#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
-
 static int
 uuid_config (const char *key, const char *value)
 {
     if (strcasecmp (key, "UUIDFile") == 0) {
-        if (uuidfile) {
-            ERROR ("UUIDFile given twice in configuration file");
-            return 1;
-        }
-        uuidfile = strdup (value);
-        return 0;
+        char *tmp = strdup (value);
+        if (tmp == NULL)
+            return -1;
+        sfree (uuidfile);
+        uuidfile = tmp;
+    } else {
+        return 1;
     }
+
     return 0;
 }
 
@@ -268,9 +249,9 @@ uuid_init (void)
 
 void module_register (void)
 {
-       plugin_register_config ("uuid", uuid_config,
-                            config_keys, NR_CONFIG_KEYS);
-       plugin_register_init ("uuid", uuid_init);
+    plugin_register_config ("uuid", uuid_config,
+            config_keys, STATIC_ARRAY_SIZE (config_keys));
+    plugin_register_init ("uuid", uuid_init);
 }
 
 /*
index 87befb6301e9bf35cc0593625d260fc313d110ff..949a8425178f700b952787f15bc0426f4f6d7299 100644 (file)
@@ -80,6 +80,8 @@ struct wg_callback
 {
     int      sock_fd;
 
+    char    *name;
+
     char    *node;
     char    *service;
     char    *prefix;
@@ -251,6 +253,7 @@ static void wg_callback_free (void *data)
     close(cb->sock_fd);
     cb->sock_fd = -1;
 
+    sfree(cb->name);
     sfree(cb->node);
     sfree(cb->service);
     sfree(cb->prefix);
@@ -421,7 +424,7 @@ static int config_set_char (char *dest,
     return (0);
 }
 
-static int wg_config_carbon (oconfig_item_t *ci)
+static int wg_config_node (oconfig_item_t *ci)
 {
     struct wg_callback *cb;
     user_data_t user_data;
@@ -436,6 +439,7 @@ static int wg_config_carbon (oconfig_item_t *ci)
     }
     memset (cb, 0, sizeof (*cb));
     cb->sock_fd = -1;
+    cb->name = NULL;
     cb->node = NULL;
     cb->service = NULL;
     cb->prefix = NULL;
@@ -443,6 +447,17 @@ static int wg_config_carbon (oconfig_item_t *ci)
     cb->escape_char = WG_DEFAULT_ESCAPE;
     cb->format_flags = GRAPHITE_STORE_RATES;
 
+    /* FIXME: Legacy configuration syntax. */
+    if (strcasecmp ("Carbon", ci->key) != 0)
+    {
+        int status = cf_util_get_string (ci, &cb->name);
+        if (status != 0)
+        {
+            wg_callback_free (cb);
+            return (status);
+        }
+    }
+
     pthread_mutex_init (&cb->send_lock, /* attr = */ NULL);
     C_COMPLAIN_INIT (&cb->init_complaint);
 
@@ -476,9 +491,14 @@ static int wg_config_carbon (oconfig_item_t *ci)
         }
     }
 
-    ssnprintf (callback_name, sizeof (callback_name), "write_graphite/%s/%s",
-            cb->node != NULL ? cb->node : WG_DEFAULT_NODE,
-            cb->service != NULL ? cb->service : WG_DEFAULT_SERVICE);
+    /* FIXME: Legacy configuration syntax. */
+    if (cb->name == NULL)
+        ssnprintf (callback_name, sizeof (callback_name), "write_graphite/%s/%s",
+                cb->node != NULL ? cb->node : WG_DEFAULT_NODE,
+                cb->service != NULL ? cb->service : WG_DEFAULT_SERVICE);
+    else
+        ssnprintf (callback_name, sizeof (callback_name), "write_graphite/%s",
+                cb->name);
 
     memset (&user_data, 0, sizeof (user_data));
     user_data.data = cb;
@@ -499,8 +519,11 @@ static int wg_config (oconfig_item_t *ci)
     {
         oconfig_item_t *child = ci->children + i;
 
-        if (strcasecmp ("Carbon", child->key) == 0)
-            wg_config_carbon (child);
+        if (strcasecmp ("Node", child->key) == 0)
+            wg_config_node (child);
+        /* FIXME: Remove this legacy mode in version 6. */
+        else if (strcasecmp ("Carbon", child->key) == 0)
+            wg_config_node (child);
         else
         {
             ERROR ("write_graphite plugin: Invalid configuration "
index c7b768205cdf2ca59e0938b8b5a955d3deb0e78a..30e261c9eee919e582f1d4da06ff52aced103a56 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/write_mongodb.c
- * Copyright (C) 2010-2012  Florian Forster
+ * Copyright (C) 2010-2013  Florian Forster
  * Copyright (C) 2010       Akkarit Sangpetch
  * Copyright (C) 2012       Chris Lundquist
  *
@@ -23,7 +23,7 @@
  * DEALINGS IN THE SOFTWARE.
  *
  * Authors:
- *   Florian Forster <ff at octo.it>
+ *   Florian Forster <octo at collectd.org>
  *   Akkarit Sangpetch <asangpet at andrew.cmu.edu>
  *   Chris Lundquist <clundquist at bluebox.net>
  **/
@@ -51,6 +51,11 @@ struct wm_node_s
   int port;
   int timeout;
 
+  /* Authentication information */
+  char *db;
+  char *user;
+  char *passwd;
+
   _Bool store_rates;
 
   mongo conn[1];
@@ -183,6 +188,23 @@ static int wm_write (const data_set_t *ds, /* {{{ */
       return (-1);
     }
 
+    if ((node->db != NULL) && (node->user != NULL) && (node->passwd != NULL))
+    {
+      status = mongo_cmd_authenticate (node->conn,
+          node->db, node->user, node->passwd);
+      if (status != MONGO_OK)
+      {
+        ERROR ("write_mongodb plugin: Authenticating to [%s]%i for database "
+            "\"%s\" as user \"%s\" failed.",
+          (node->host != NULL) ? node->host : "localhost",
+          (node->port != 0) ? node->port : MONGO_DEFAULT_PORT,
+          node->db, node->user);
+        mongo_destroy (node->conn);
+        pthread_mutex_unlock (&node->lock);
+        return (-1);
+      }
+    }
+
     if (node->timeout > 0) {
       status = mongo_set_op_timeout (node->conn, node->timeout);
       if (status != MONGO_OK) {
@@ -203,13 +225,14 @@ static int wm_write (const data_set_t *ds, /* {{{ */
     status = mongo_insert (node->conn, collection_name, bson_record);
   #endif
 
-  if(status != MONGO_OK)
+  if (status != MONGO_OK)
   {
     ERROR ( "write_mongodb plugin: error inserting record: %d", node->conn->err);
     if (node->conn->err != MONGO_BSON_INVALID)
       ERROR ("write_mongodb plugin: %s", node->conn->errstr);
-    else if (bson_record->err)
-      ERROR ("write_mongodb plugin: %s", bson_record->errstr);
+    else
+      ERROR ("write_mongodb plugin: Invalid BSON structure, error = %#x",
+          (unsigned int) bson_record->err);
 
     /* Disconnect except on data errors. */
     if ((node->conn->err != MONGO_BSON_INVALID)
@@ -281,6 +304,12 @@ static int wm_config_node (oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_int (child, &node->timeout);
     else if (strcasecmp ("StoreRates", child->key) == 0)
       status = cf_util_get_boolean (child, &node->store_rates);
+    else if (strcasecmp ("Database", child->key) == 0)
+      status = cf_util_get_string (child, &node->db);
+    else if (strcasecmp ("User", child->key) == 0)
+      status = cf_util_get_string (child, &node->user);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = cf_util_get_string (child, &node->passwd);
     else
       WARNING ("write_mongodb plugin: Ignoring unknown config option \"%s\".",
           child->key);
@@ -289,6 +318,20 @@ static int wm_config_node (oconfig_item_t *ci) /* {{{ */
       break;
   } /* for (i = 0; i < ci->children_num; i++) */
 
+  if ((node->db != NULL) || (node->user != NULL) || (node->passwd != NULL))
+  {
+    if ((node->db == NULL) || (node->user == NULL) || (node->passwd == NULL))
+    {
+      WARNING ("write_mongodb plugin: Authentication requires the "
+          "\"Database\", \"User\" and \"Password\" options to be specified, "
+          "but at last one of them is missing. Authentication will NOT be "
+          "used.");
+      sfree (node->db);
+      sfree (node->user);
+      sfree (node->passwd);
+    }
+  }
+
   if (status == 0)
   {
     char cb_name[DATA_MAX_NAME_LEN];
diff --git a/src/write_riemann.c b/src/write_riemann.c
new file mode 100644 (file)
index 0000000..15bb237
--- /dev/null
@@ -0,0 +1,757 @@
+/**
+ * collectd - src/write_riemann.c
+ *
+ * Copyright (C) 2012,2013  Pierre-Yves Ritschard
+ * 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:
+ *   Pierre-Yves Ritschard <pyr at spootnik.org>
+ *   Florian octo Forster <octo at collectd.org>
+ */
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "configfile.h"
+#include "utils_cache.h"
+#include "riemann.pb-c.h"
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <inttypes.h>
+#include <pthread.h>
+
+#define RIEMANN_HOST           "localhost"
+#define RIEMANN_PORT           "5555"
+
+struct riemann_host {
+       char                    *name;
+#define F_CONNECT               0x01
+       uint8_t                  flags;
+       pthread_mutex_t          lock;
+       _Bool                    store_rates;
+       _Bool                    always_append_ds;
+       char                    *node;
+       char                    *service;
+       _Bool                    use_tcp;
+       int                      s;
+
+       int                      reference_count;
+};
+
+static char    **riemann_tags;
+static size_t    riemann_tags_num;
+
+static void riemann_event_protobuf_free (Event *event) /* {{{ */
+{
+       size_t i;
+
+       if (event == NULL)
+               return;
+
+       sfree (event->state);
+       sfree (event->service);
+       sfree (event->host);
+       sfree (event->description);
+
+       strarray_free (event->tags, event->n_tags);
+       event->tags = NULL;
+       event->n_tags = 0;
+
+       for (i = 0; i < event->n_attributes; i++)
+       {
+               sfree (event->attributes[i]->key);
+               sfree (event->attributes[i]->value);
+               sfree (event->attributes[i]);
+       }
+       sfree (event->attributes);
+       event->n_attributes = 0;
+
+       sfree (event);
+} /* }}} void riemann_event_protobuf_free */
+
+static void riemann_msg_protobuf_free (Msg *msg) /* {{{ */
+{
+       size_t i;
+
+       if (msg == NULL)
+               return;
+
+       for (i = 0; i < msg->n_events; i++)
+       {
+               riemann_event_protobuf_free (msg->events[i]);
+               msg->events[i] = NULL;
+       }
+
+       sfree (msg->events);
+       msg->n_events = 0;
+
+       sfree (msg);
+} /* }}} void riemann_msg_protobuf_free */
+
+/* host->lock must be held when calling this function. */
+static int
+riemann_connect(struct riemann_host *host)
+{
+       int                      e;
+       struct addrinfo         *ai, *res, hints;
+       char const              *node;
+       char const              *service;
+
+       if (host->flags & F_CONNECT)
+               return 0;
+
+       memset(&hints, 0, sizeof(hints));
+       memset(&service, 0, sizeof(service));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = host->use_tcp ? SOCK_STREAM : SOCK_DGRAM;
+#ifdef AI_ADDRCONFIG
+       hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+
+       node = (host->node != NULL) ? host->node : RIEMANN_HOST;
+       service = (host->service != NULL) ? host->service : RIEMANN_PORT;
+
+       if ((e = getaddrinfo(node, service, &hints, &res)) != 0) {
+               ERROR ("write_riemann plugin: Unable to resolve host \"%s\": %s",
+                       node, gai_strerror(e));
+               return -1;
+       }
+
+       host->s = -1;
+       for (ai = res; ai != NULL; ai = ai->ai_next) {
+               if ((host->s = socket(ai->ai_family,
+                                     ai->ai_socktype,
+                                     ai->ai_protocol)) == -1) {
+                       continue;
+               }
+
+               if (connect(host->s, ai->ai_addr, ai->ai_addrlen) != 0) {
+                       close(host->s);
+                       host->s = -1;
+                       continue;
+               }
+
+               host->flags |= F_CONNECT;
+               DEBUG("write_riemann plugin: got a succesful connection for: %s:%s",
+                               node, service);
+               break;
+       }
+
+       freeaddrinfo(res);
+
+       if (host->s < 0) {
+               WARNING("write_riemann plugin: Unable to connect to Riemann at %s:%s",
+                               node, service);
+               return -1;
+       }
+       return 0;
+}
+
+/* host->lock must be held when calling this function. */
+static int
+riemann_disconnect (struct riemann_host *host)
+{
+       if ((host->flags & F_CONNECT) == 0)
+               return (0);
+
+       close (host->s);
+       host->s = -1;
+       host->flags &= ~F_CONNECT;
+
+       return (0);
+}
+
+static int
+riemann_send(struct riemann_host *host, Msg const *msg)
+{
+       u_char *buffer;
+       size_t  buffer_len;
+       int status;
+
+       pthread_mutex_lock (&host->lock);
+
+       status = riemann_connect (host);
+       if (status != 0)
+       {
+               pthread_mutex_unlock (&host->lock);
+               return status;
+       }
+
+       buffer_len = msg__get_packed_size(msg);
+       if (host->use_tcp)
+               buffer_len += 4;
+
+       buffer = malloc (buffer_len);
+       if (buffer == NULL) {
+               pthread_mutex_unlock (&host->lock);
+               ERROR ("write_riemann plugin: malloc failed.");
+               return ENOMEM;
+       }
+       memset (buffer, 0, buffer_len);
+
+       if (host->use_tcp)
+       {
+               uint32_t length = htonl ((uint32_t) (buffer_len - 4));
+               memcpy (buffer, &length, 4);
+               msg__pack(msg, buffer + 4);
+       }
+       else
+       {
+               msg__pack(msg, buffer);
+       }
+
+       status = (int) swrite (host->s, buffer, buffer_len);
+       if (status != 0)
+       {
+               char errbuf[1024];
+
+               riemann_disconnect (host);
+               pthread_mutex_unlock (&host->lock);
+
+               ERROR ("write_riemann plugin: Sending to Riemann at %s:%s failed: %s",
+                               (host->node != NULL) ? host->node : RIEMANN_HOST,
+                               (host->service != NULL) ? host->service : RIEMANN_PORT,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               sfree (buffer);
+               return -1;
+       }
+
+       pthread_mutex_unlock (&host->lock);
+       sfree (buffer);
+       return 0;
+}
+
+static int riemann_event_add_tag (Event *event, char const *tag) /* {{{ */
+{
+       return (strarray_add (&event->tags, &event->n_tags, tag));
+} /* }}} int riemann_event_add_tag */
+
+static int riemann_event_add_attribute (Event *event, /* {{{ */
+               char const *key, char const *value)
+{
+       Attribute **new_attributes;
+       Attribute *a;
+
+       new_attributes = realloc (event->attributes,
+                       sizeof (*event->attributes) * (event->n_attributes + 1));
+       if (new_attributes == NULL)
+       {
+               ERROR ("write_riemann plugin: realloc failed.");
+               return (ENOMEM);
+       }
+       event->attributes = new_attributes;
+
+       a = malloc (sizeof (*a));
+       if (a == NULL)
+       {
+               ERROR ("write_riemann plugin: malloc failed.");
+               return (ENOMEM);
+       }
+       attribute__init (a);
+
+       a->key = strdup (key);
+       if (value != NULL)
+               a->value = strdup (value);
+
+       event->attributes[event->n_attributes] = a;
+       event->n_attributes++;
+
+       return (0);
+} /* }}} int riemann_event_add_attribute */
+
+static Msg *riemann_notification_to_protobuf (struct riemann_host *host, /* {{{ */
+               notification_t const *n)
+{
+       Msg *msg;
+       Event *event;
+       char service_buffer[6 * DATA_MAX_NAME_LEN];
+       char const *severity;
+       notification_meta_t *meta;
+       int i;
+
+       msg = malloc (sizeof (*msg));
+       if (msg == NULL)
+       {
+               ERROR ("write_riemann plugin: malloc failed.");
+               return (NULL);
+       }
+       memset (msg, 0, sizeof (*msg));
+       msg__init (msg);
+
+       msg->events = malloc (sizeof (*msg->events));
+       if (msg->events == NULL)
+       {
+               ERROR ("write_riemann plugin: malloc failed.");
+               sfree (msg);
+               return (NULL);
+       }
+
+       event = malloc (sizeof (*event));
+       if (event == NULL)
+       {
+               ERROR ("write_riemann plugin: malloc failed.");
+               sfree (msg->events);
+               sfree (msg);
+               return (NULL);
+       }
+       memset (event, 0, sizeof (*event));
+       event__init (event);
+
+       msg->events[0] = event;
+       msg->n_events = 1;
+
+       event->host = strdup (n->host);
+       event->time = CDTIME_T_TO_TIME_T (n->time);
+       event->has_time = 1;
+
+       switch (n->severity)
+       {
+               case NOTIF_OKAY:        severity = "ok"; break;
+               case NOTIF_WARNING:     severity = "warning"; break;
+               case NOTIF_FAILURE:     severity = "critical"; break;
+               default:                severity = "unknown";
+       }
+       event->state = strdup (severity);
+
+       riemann_event_add_tag (event, "notification");
+       if (n->host[0] != 0)
+               riemann_event_add_attribute (event, "host", n->host);
+       if (n->plugin[0] != 0)
+               riemann_event_add_attribute (event, "plugin", n->plugin);
+       if (n->plugin_instance[0] != 0)
+               riemann_event_add_attribute (event, "plugin_instance",
+                               n->plugin_instance);
+
+       if (n->type[0] != 0)
+               riemann_event_add_attribute (event, "type", n->type);
+       if (n->type_instance[0] != 0)
+               riemann_event_add_attribute (event, "type_instance",
+                               n->type_instance);
+
+       for (i = 0; i < riemann_tags_num; i++)
+               riemann_event_add_tag (event, riemann_tags[i]);
+
+       format_name (service_buffer, sizeof (service_buffer),
+                       /* host = */ "", n->plugin, n->plugin_instance,
+                       n->type, n->type_instance);
+       event->service = strdup (&service_buffer[1]);
+
+       /* Pull in values from threshold */
+       for (meta = n->meta; meta != NULL; meta = meta->next)
+       {
+               if (strcasecmp ("CurrentValue", meta->name) != 0)
+                       continue;
+
+               event->metric_d = meta->nm_value.nm_double;
+               event->has_metric_d = 1;
+               break;
+       }
+
+       DEBUG ("write_riemann plugin: Successfully created protobuf for notification: "
+                       "host = \"%s\", service = \"%s\", state = \"%s\"",
+                       event->host, event->service, event->state);
+       return (msg);
+} /* }}} Msg *riemann_notification_to_protobuf */
+
+static Event *riemann_value_to_protobuf (struct riemann_host const *host, /* {{{ */
+               data_set_t const *ds,
+               value_list_t const *vl, size_t index,
+               gauge_t const *rates)
+{
+       Event *event;
+       char name_buffer[5 * DATA_MAX_NAME_LEN];
+       char service_buffer[6 * DATA_MAX_NAME_LEN];
+       int i;
+
+       event = malloc (sizeof (*event));
+       if (event == NULL)
+       {
+               ERROR ("write_riemann plugin: malloc failed.");
+               return (NULL);
+       }
+       memset (event, 0, sizeof (*event));
+       event__init (event);
+
+       event->host = strdup (vl->host);
+       event->time = CDTIME_T_TO_TIME_T (vl->time);
+       event->has_time = 1;
+       event->ttl = CDTIME_T_TO_TIME_T (2 * vl->interval);
+       event->has_ttl = 1;
+
+       riemann_event_add_attribute (event, "plugin", vl->plugin);
+       if (vl->plugin_instance[0] != 0)
+               riemann_event_add_attribute (event, "plugin_instance",
+                               vl->plugin_instance);
+
+       riemann_event_add_attribute (event, "type", vl->type);
+       if (vl->type_instance[0] != 0)
+               riemann_event_add_attribute (event, "type_instance",
+                               vl->type_instance);
+
+       if ((ds->ds[index].type != DS_TYPE_GAUGE) && (rates != NULL))
+       {
+               char ds_type[DATA_MAX_NAME_LEN];
+
+               ssnprintf (ds_type, sizeof (ds_type), "%s:rate",
+                               DS_TYPE_TO_STRING(ds->ds[index].type));
+               riemann_event_add_attribute (event, "ds_type", ds_type);
+       }
+       else
+       {
+               riemann_event_add_attribute (event, "ds_type",
+                               DS_TYPE_TO_STRING(ds->ds[index].type));
+       }
+       riemann_event_add_attribute (event, "ds_name", ds->ds[index].name);
+       {
+               char ds_index[DATA_MAX_NAME_LEN];
+
+               ssnprintf (ds_index, sizeof (ds_index), "%zu", index);
+               riemann_event_add_attribute (event, "ds_index", ds_index);
+       }
+
+       for (i = 0; i < riemann_tags_num; i++)
+               riemann_event_add_tag (event, riemann_tags[i]);
+
+       if (ds->ds[index].type == DS_TYPE_GAUGE)
+       {
+               event->has_metric_d = 1;
+               event->metric_d = (double) vl->values[index].gauge;
+       }
+       else if (rates != NULL)
+       {
+               event->has_metric_d = 1;
+               event->metric_d = (double) rates[index];
+       }
+       else
+       {
+               event->has_metric_sint64 = 1;
+               if (ds->ds[index].type == DS_TYPE_DERIVE)
+                       event->metric_sint64 = (int64_t) vl->values[index].derive;
+               else if (ds->ds[index].type == DS_TYPE_ABSOLUTE)
+                       event->metric_sint64 = (int64_t) vl->values[index].absolute;
+               else
+                       event->metric_sint64 = (int64_t) vl->values[index].counter;
+       }
+
+       format_name (name_buffer, sizeof (name_buffer),
+                       /* host = */ "", vl->plugin, vl->plugin_instance,
+                       vl->type, vl->type_instance);
+       if (host->always_append_ds || (ds->ds_num > 1))
+               ssnprintf (service_buffer, sizeof (service_buffer),
+                               "%s/%s", &name_buffer[1], ds->ds[index].name);
+       else
+               sstrncpy (service_buffer, &name_buffer[1],
+                               sizeof (service_buffer));
+
+       event->service = strdup (service_buffer);
+
+       DEBUG ("write_riemann plugin: Successfully created protobuf for metric: "
+                       "host = \"%s\", service = \"%s\"",
+                       event->host, event->service);
+       return (event);
+} /* }}} Event *riemann_value_to_protobuf */
+
+static Msg *riemann_value_list_to_protobuf (struct riemann_host const *host, /* {{{ */
+               data_set_t const *ds,
+               value_list_t const *vl)
+{
+       Msg *msg;
+       size_t i;
+       gauge_t *rates = NULL;
+
+       /* Initialize the Msg structure. */
+       msg = malloc (sizeof (*msg));
+       if (msg == NULL)
+       {
+               ERROR ("write_riemann plugin: malloc failed.");
+               return (NULL);
+       }
+       memset (msg, 0, sizeof (*msg));
+       msg__init (msg);
+
+       /* Set up events. First, the list of pointers. */
+       msg->n_events = (size_t) vl->values_len;
+       msg->events = calloc (msg->n_events, sizeof (*msg->events));
+       if (msg->events == NULL)
+       {
+               ERROR ("write_riemann plugin: calloc failed.");
+               riemann_msg_protobuf_free (msg);
+               return (NULL);
+       }
+
+       if (host->store_rates)
+       {
+               rates = uc_get_rate (ds, vl);
+               if (rates == NULL)
+               {
+                       ERROR ("write_riemann plugin: uc_get_rate failed.");
+                       riemann_msg_protobuf_free (msg);
+                       return (NULL);
+               }
+       }
+
+       for (i = 0; i < msg->n_events; i++)
+       {
+               msg->events[i] = riemann_value_to_protobuf (host, ds, vl,
+                               (int) i, rates);
+               if (msg->events[i] == NULL)
+               {
+                       riemann_msg_protobuf_free (msg);
+                       sfree (rates);
+                       return (NULL);
+               }
+       }
+
+       sfree (rates);
+       return (msg);
+} /* }}} Msg *riemann_value_list_to_protobuf */
+
+static int
+riemann_notification(const notification_t *n, user_data_t *ud)
+{
+       int                      status;
+       struct riemann_host     *host = ud->data;
+       Msg                     *msg;
+
+       msg = riemann_notification_to_protobuf (host, n);
+       if (msg == NULL)
+               return (-1);
+
+       status = riemann_send (host, msg);
+       if (status != 0)
+               ERROR ("write_riemann plugin: riemann_send failed with status %i",
+                               status);
+
+       riemann_msg_protobuf_free (msg);
+       return (status);
+} /* }}} int riemann_notification */
+
+static int
+riemann_write(const data_set_t *ds,
+             const value_list_t *vl,
+             user_data_t *ud)
+{
+       int                      status;
+       struct riemann_host     *host = ud->data;
+       Msg                     *msg;
+
+       msg = riemann_value_list_to_protobuf (host, ds, vl);
+       if (msg == NULL)
+               return (-1);
+
+       status = riemann_send (host, msg);
+       if (status != 0)
+               ERROR ("write_riemann plugin: riemann_send failed with status %i",
+                               status);
+
+       riemann_msg_protobuf_free (msg);
+       return status;
+}
+
+static void
+riemann_free(void *p)
+{
+       struct riemann_host     *host = p;
+
+       if (host == NULL)
+               return;
+
+       pthread_mutex_lock (&host->lock);
+
+       host->reference_count--;
+       if (host->reference_count > 0)
+       {
+               pthread_mutex_unlock (&host->lock);
+               return;
+       }
+
+       riemann_disconnect (host);
+
+       sfree(host->service);
+       pthread_mutex_destroy (&host->lock);
+       sfree(host);
+}
+
+static int
+riemann_config_node(oconfig_item_t *ci)
+{
+       struct riemann_host     *host = NULL;
+       int                      status = 0;
+       int                      i;
+       oconfig_item_t          *child;
+       char                     callback_name[DATA_MAX_NAME_LEN];
+       user_data_t              ud;
+
+       if ((host = calloc(1, sizeof (*host))) == NULL) {
+               ERROR ("write_riemann plugin: calloc failed.");
+               return ENOMEM;
+       }
+       pthread_mutex_init (&host->lock, NULL);
+       host->reference_count = 1;
+       host->node = NULL;
+       host->service = NULL;
+       host->store_rates = 1;
+       host->always_append_ds = 0;
+       host->use_tcp = 0;
+
+       status = cf_util_get_string (ci, &host->name);
+       if (status != 0) {
+               WARNING("write_riemann plugin: Required host name is missing.");
+               riemann_free (host);
+               return -1;
+       }
+
+       for (i = 0; i < ci->children_num; i++) {
+               /*
+                * The code here could be simplified but makes room
+                * for easy adding of new options later on.
+                */
+               child = &ci->children[i];
+               status = 0;
+
+               if (strcasecmp ("Host", child->key) == 0) {
+                       status = cf_util_get_string (child, &host->node);
+                       if (status != 0)
+                               break;
+               } else if (strcasecmp ("Port", child->key) == 0) {
+                       status = cf_util_get_service (child, &host->service);
+                       if (status != 0) {
+                               ERROR ("write_riemann plugin: Invalid argument "
+                                               "configured for the \"Port\" "
+                                               "option.");
+                               break;
+                       }
+               } else if (strcasecmp ("Protocol", child->key) == 0) {
+                       char tmp[16];
+                       status = cf_util_get_string_buffer (child,
+                                       tmp, sizeof (tmp));
+                       if (status != 0)
+                       {
+                               ERROR ("write_riemann plugin: cf_util_get_"
+                                               "string_buffer failed with "
+                                               "status %i.", status);
+                               break;
+                       }
+
+                       if (strcasecmp ("UDP", tmp) == 0)
+                               host->use_tcp = 0;
+                       else if (strcasecmp ("TCP", tmp) == 0)
+                               host->use_tcp = 1;
+                       else
+                               WARNING ("write_riemann plugin: The value "
+                                               "\"%s\" is not valid for the "
+                                               "\"Protocol\" option. Use "
+                                               "either \"UDP\" or \"TCP\".",
+                                               tmp);
+               } else if (strcasecmp ("StoreRates", child->key) == 0) {
+                       status = cf_util_get_boolean (child, &host->store_rates);
+                       if (status != 0)
+                               break;
+               } else if (strcasecmp ("AlwaysAppendDS", child->key) == 0) {
+                       status = cf_util_get_boolean (child,
+                                       &host->always_append_ds);
+                       if (status != 0)
+                               break;
+               } else {
+                       WARNING("write_riemann plugin: ignoring unknown config "
+                               "option: \"%s\"", child->key);
+               }
+       }
+       if (status != 0) {
+               riemann_free (host);
+               return status;
+       }
+
+       ssnprintf (callback_name, sizeof (callback_name), "write_riemann/%s",
+                       host->name);
+       ud.data = host;
+       ud.free_func = riemann_free;
+
+       pthread_mutex_lock (&host->lock);
+
+       status = plugin_register_write (callback_name, riemann_write, &ud);
+       if (status != 0)
+               WARNING ("write_riemann plugin: plugin_register_write (\"%s\") "
+                               "failed with status %i.",
+                               callback_name, status);
+       else /* success */
+               host->reference_count++;
+
+       status = plugin_register_notification (callback_name,
+                       riemann_notification, &ud);
+       if (status != 0)
+               WARNING ("write_riemann plugin: plugin_register_notification (\"%s\") "
+                               "failed with status %i.",
+                               callback_name, status);
+       else /* success */
+               host->reference_count++;
+
+       if (host->reference_count <= 1)
+       {
+               /* Both callbacks failed => free memory.
+                * We need to unlock here, because riemann_free() will lock.
+                * This is not a race condition, because we're the only one
+                * holding a reference. */
+               pthread_mutex_unlock (&host->lock);
+               riemann_free (host);
+               return (-1);
+       }
+
+       host->reference_count--;
+       pthread_mutex_unlock (&host->lock);
+
+       return status;
+}
+
+static int
+riemann_config(oconfig_item_t *ci)
+{
+       int              i;
+       oconfig_item_t  *child;
+       int              status;
+
+       for (i = 0; i < ci->children_num; i++)  {
+               child = &ci->children[i];
+
+               if (strcasecmp("Node", child->key) == 0) {
+                       riemann_config_node (child);
+               } else if (strcasecmp(child->key, "tag") == 0) {
+                       char *tmp = NULL;
+                       status = cf_util_get_string(child, &tmp);
+                       if (status != 0)
+                               continue;
+
+                       strarray_add (&riemann_tags, &riemann_tags_num, tmp);
+                       DEBUG("write_riemann plugin: Got tag: %s", tmp);
+                       sfree (tmp);
+               } else {
+                       WARNING ("write_riemann plugin: Ignoring unknown "
+                                "configuration option \"%s\" at top level.",
+                                child->key);
+               }
+       }
+       return (0);
+}
+
+void
+module_register(void)
+{
+       plugin_register_complex_config ("write_riemann", riemann_config);
+}
+
+/* vim: set sw=8 sts=8 ts=8 noet : */
index 82428e54cac8c6d39c2817146b6605a0c3ce0926..4be9a4946426f4a370d4880e40695fd53235c543 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 
-DEFAULT_VERSION="5.2.2.git"
+DEFAULT_VERSION="5.3.0.git"
 
 VERSION="`git describe 2> /dev/null | sed -e 's/^collectd-//'`"