Merge remote-tracking branch 'origin/pr/998'
authorMarc Fournier <marc.fournier@camptocamp.com>
Sun, 12 Apr 2015 07:26:52 +0000 (09:26 +0200)
committerMarc Fournier <marc.fournier@camptocamp.com>
Sun, 12 Apr 2015 07:26:52 +0000 (09:26 +0200)
21 files changed:
AUTHORS
README
configure.ac
contrib/redhat/collectd.spec
src/Makefile.am
src/apache.c
src/ascent.c
src/barometer.c
src/bind.c
src/collectd-perl.pod
src/collectd.conf.in
src/collectd.conf.pod
src/curl.c
src/curl_json.c
src/curl_xml.c
src/ipc.c [new file with mode: 0644]
src/nginx.c
src/powerdns.c
src/types.db
src/write_http.c
src/write_sensu.c [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
index 072e7ff..027ac96 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -106,6 +106,9 @@ Fabian Linzberger <e at lefant.net>
 Fabien Wernli <cpan at faxm0dem.org>
  - Solaris improvements in the memory and interfaces plugin.
 
+Fabrice A. Marie <fabrice at kibinlabs.com>
+ - write_sensu plugin.
+
 Flavio Stanchina <flavio at stanchina.net>
  - mbmon plugin.
 
diff --git a/README b/README
index 9fb9004..12ab7c6 100644 (file)
--- a/README
+++ b/README
@@ -33,9 +33,9 @@ Features
       Statistics about Ascent, a free server for the game `World of Warcraft'.
 
     - barometer
-      Using digital barometer sensor MPL115A2 or MPL3115 from Freescale
-      provides absolute barometric pressure, air pressure reduced to sea level
-      and temperature.
+      Reads absolute barometric pressure, air pressure reduced to sea level and
+      temperature.  Supported sensors are MPL115A2 and MPL3115 from Freescale
+      and BMP085 from Bosch.
 
     - battery
       Batterycharge, -current and voltage of ACPI and PMU based laptop
@@ -123,13 +123,17 @@ Features
       Interface traffic: Number of octets, packets and errors for each
       interface.
 
-    - iptables
-      Iptables' counters: Number of bytes that were matched by a certain
-      iptables rule.
+    - ipc
+      IPC counters: semaphores used, number of allocated segments in shared
+      memory and more.
 
     - ipmi
       IPMI (Intelligent Platform Management Interface) sensors information.
 
+    - iptables
+      Iptables' counters: Number of bytes that were matched by a certain
+      iptables rule.
+
     - ipvs
       IPVS connection statistics (number of connections, octets and packets
       for each service and destination).
@@ -217,13 +221,13 @@ Features
     - ntpd
       NTP daemon statistics: Local clock drift, offset to peers, etc.
 
+    - numa
+      Information about Non-Uniform Memory Access (NUMA).
+
     - nut
       Network UPS tools: UPS current, voltage, power, charge, utilisation,
       temperature, etc. See upsd(8).
 
-    - numa
-      Information about Non-Uniform Memory Access (NUMA).
-
     - olsrd
       Queries routing information from the “Optimized Link State Routing”
       daemon.
@@ -424,10 +428,6 @@ Features
       can be configured to avoid logging send errors (especially useful when
       using UDP).
 
-    - write_tsdb
-      Sends data OpenTSDB, a scalable no master, no shared state time series
-      database.
-
     - write_http
       Sends the values collected by collectd to a web-server using HTTP POST
       requests. The transmitted data is either in a form understood by the
@@ -448,6 +448,14 @@ Features
     - write_riemann
       Sends data to Riemann, a stream processing and monitoring system.
 
+    - write_sensu
+      Sends data to Sensu, a stream processing and monitoring system, via the
+      Sensu client local TCP socket.
+
+    - write_tsdb
+      Sends data OpenTSDB, a scalable no master, no shared state time series
+      database.
+
   * Logging is, as everything in collectd, provided by plugins. The following
     plugins keep up informed about what's going on:
 
index c83805e..0b8fa18 100644 (file)
@@ -1700,6 +1700,10 @@ then
                 [have_curlopt_username="yes"],
                 [have_curlopt_username="no"],
                 [[#include <curl/curl.h>]])
+               AC_CHECK_DECL(CURLOPT_TIMEOUT_MS,
+                [have_curlopt_timeout="yes"],
+                [have_curlopt_timeout="no"],
+                [[#include <curl/curl.h>]])
        fi
 fi
 if test "x$with_libcurl" = "xyes"
@@ -1713,6 +1717,11 @@ then
        then
                AC_DEFINE(HAVE_CURLOPT_USERNAME, 1, [Define if libcurl supports CURLOPT_USERNAME option.])
        fi
+
+       if test "x$have_curlopt_timeout" = "xyes"
+       then
+               AC_DEFINE(HAVE_CURLOPT_TIMEOUT_MS, 1, [Define if libcurl supports CURLOPT_TIMEOUT_MS option.])
+       fi
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBCURL, test "x$with_libcurl" = "xyes")
 # }}}
@@ -5153,6 +5162,7 @@ then
        plugin_entropy="yes"
        plugin_fscache="yes"
        plugin_interface="yes"
+       plugin_ipc="yes"
        plugin_irq="yes"
        plugin_load="yes"
        plugin_lvm="yes"
@@ -5194,6 +5204,7 @@ fi
 if test "x$ac_system" = "xAIX"
 then
         plugin_tcpconns="yes"
+        plugin_ipc="yes"
 fi
 
 # FreeBSD
@@ -5492,6 +5503,7 @@ AC_PLUGIN([fscache],     [$plugin_fscache],    [fscache statistics])
 AC_PLUGIN([gmond],       [$with_libganglia],   [Ganglia plugin])
 AC_PLUGIN([hddtemp],     [yes],                [Query hddtempd])
 AC_PLUGIN([interface],   [$plugin_interface],  [Interface traffic statistics])
+AC_PLUGIN([ipc],         [$plugin_ipc],        [IPC statistics])
 AC_PLUGIN([ipmi],        [$plugin_ipmi],       [IPMI sensor statistics])
 AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
 AC_PLUGIN([ipvs],        [$plugin_ipvs],       [IPVS connection statistics])
@@ -5585,6 +5597,7 @@ AC_PLUGIN([write_log], [yes],                  [Log output plugin])
 AC_PLUGIN([write_mongodb], [$with_libmongoc],  [MongoDB output plugin])
 AC_PLUGIN([write_redis], [$with_libhiredis],    [Redis output plugin])
 AC_PLUGIN([write_riemann], [$have_protoc_c],   [Riemann output plugin])
+AC_PLUGIN([write_sensu], [yes],                [Sensu output plugin])
 AC_PLUGIN([write_tsdb],  [yes],                [TSDB output plugin])
 AC_PLUGIN([xmms],        [$with_libxmms],      [XMMS statistics])
 AC_PLUGIN([zfs_arc],     [$plugin_zfs_arc],    [ZFS ARC statistics])
@@ -5866,6 +5879,7 @@ Configuration:
     gmond . . . . . . . . $enable_gmond
     hddtemp . . . . . . . $enable_hddtemp
     interface . . . . . . $enable_interface
+    ipc . . . . . . . . . $enable_ipc
     ipmi  . . . . . . . . $enable_ipmi
     iptables  . . . . . . $enable_iptables
     ipvs  . . . . . . . . $enable_ipvs
@@ -5958,6 +5972,7 @@ Configuration:
     write_mongodb . . . . $enable_write_mongodb
     write_redis . . . . . $enable_write_redis
     write_riemann . . . . $enable_write_riemann
+    write_sensu . . . . . $enable_write_sensu
     write_tsdb  . . . . . $enable_write_tsdb
     xmms  . . . . . . . . $enable_xmms
     zfs_arc . . . . . . . $enable_zfs_arc
index 0e5cd86..e023a34 100644 (file)
@@ -99,6 +99,7 @@
 %define with_gmond 0%{!?_without_gmond:0%{?_has_recent_libganglia}}
 %define with_hddtemp 0%{!?_without_hddtemp:1}
 %define with_interface 0%{!?_without_interface:1}
+%define with_ipc 0%{!?_without_ipc:1}
 %define with_ipmi 0%{!?_without_ipmi:1}
 %define with_iptables 0%{!?_without_iptables:0%{?_has_working_libiptc}}
 %define with_ipvs 0%{!?_without_ipvs:0%{?_has_ip_vs_h}}
 %define with_write_log 0%{!?_without_write_log:1}
 %define with_write_redis 0%{!?_without_write_redis:0%{?_has_hiredis}}
 %define with_write_riemann 0%{!?_without_write_riemann:1}
+%define with_write_sensu 0%{!?_without_write_sensu:1}
 %define with_write_tsdb 0%{!?_without_write_tsdb:1}
 %define with_zfs_arc 0%{!?_without_zfs_arc:1}
 %define with_zookeeper 0%{!?_without_zookeeper:1}
 
 Summary:       Statistics collection daemon for filling RRD files
 Name:          collectd
-Version:       5.4.0
+Version:       5.4.2
 Release:       1%{?dist}
 URL:           http://collectd.org
 Source:                http://collectd.org/files/%{name}-%{version}.tar.bz2
@@ -1027,6 +1029,12 @@ Collectd utilities
 %define _with_interface --disable-interface
 %endif
 
+%if %{with_ipc}
+%define _with_ipc --enable-ipc
+%else
+%define _with_ipc --disable-ipc
+%endif
+
 %if %{with_ipmi}
 %define _with_ipmi --enable-ipmi
 %else
@@ -1523,6 +1531,12 @@ Collectd utilities
 %define _with_write_riemann --disable-write_riemann
 %endif
 
+%if %{with_write_sensu}
+%define _with_write_sensu --enable-write_sensu
+%else
+%define _with_write_sensu --disable-write_sensu
+%endif
+
 %if %{with_write_tsdb}
 %define _with_write_tsdb --enable-write_tsdb
 %else
@@ -1595,6 +1609,7 @@ Collectd utilities
        %{?_with_gmond} \
        %{?_with_hddtemp} \
        %{?_with_interface} \
+       %{?_with_ipc} \
        %{?_with_ipmi} \
        %{?_with_iptables} \
        %{?_with_ipvs} \
@@ -1681,6 +1696,7 @@ Collectd utilities
        %{?_with_write_http} \
        %{?_with_write_log} \
        %{?_with_write_riemann} \
+       %{?_with_write_sensu} \
        %{?_with_write_tsdb}
 
 
@@ -1873,6 +1889,9 @@ fi
 %if %{with_interface}
 %{_libdir}/%{name}/interface.so
 %endif
+%if %{with_ipc}
+%{_libdir}/%{name}/ipc.so
+%endif
 %if %{with_ipvs}
 %{_libdir}/%{name}/ipvs.so
 %endif
@@ -1993,6 +2012,9 @@ fi
 %if %{with_write_log}
 %{_libdir}/%{name}/write_log.so
 %endif
+%if %{with_write_sensu}
+%{_libdir}/%{name}/write_sensu.so
+%endif
 %if %{with_write_tsdb}
 %{_libdir}/%{name}/write_tsdb.so
 %endif
@@ -2291,7 +2313,7 @@ fi
 %changelog
 # * TODO 5.5.0-1
 # - New upstream version
-# - New plugins enabled by default: ceph, drbd, log_logstash, write_tsdb, smart, openldap, redis, write_redis, zookeeper, write_log
+# - New plugins enabled by default: ceph, drbd, log_logstash, write_tsdb, smart, openldap, redis, write_redis, zookeeper, write_log, write_sensu, ipc
 # - New plugins disabled by default: barometer, write_kafka
 # - Enable zfs_arc, now supported on Linux
 # - Install disk plugin in a dedicated package, as it depends on libudev
index 14708c0..1990700 100644 (file)
@@ -406,6 +406,13 @@ interface_la_LIBADD += -lperfstat
 endif
 endif # BUILD_PLUGIN_INTERFACE
 
+if BUILD_PLUGIN_IPC
+pkglib_LTLIBRARIES += ipc.la
+ipc_la_SOURCES = ipc.c
+ipc_la_CFLAGS = $(AM_CFLAGS)
+ipc_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+endif
+
 if BUILD_PLUGIN_IPTABLES
 pkglib_LTLIBRARIES += iptables.la
 iptables_la_SOURCES = iptables.c
@@ -1216,6 +1223,12 @@ write_riemann_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 write_riemann_la_LIBADD = -lprotobuf-c
 endif
 
+if BUILD_PLUGIN_WRITE_SENSU
+pkglib_LTLIBRARIES += write_sensu.la
+write_sensu_la_SOURCES = write_sensu.c
+write_sensu_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+endif
+
 if BUILD_PLUGIN_WRITE_TSDB
 pkglib_LTLIBRARIES += write_tsdb.la
 write_tsdb_la_SOURCES = write_tsdb.c
index 1099248..0c6318e 100644 (file)
@@ -48,11 +48,13 @@ struct apache_s
        _Bool verify_peer;
        _Bool verify_host;
        char *cacert;
+       char *ssl_ciphers;
        char *server; /* user specific server type */
        char *apache_buffer;
        char apache_curl_error[CURL_ERROR_SIZE];
        size_t apache_buffer_size;
        size_t apache_buffer_fill;
+       int timeout;
        CURL *curl;
 }; /* apache_s */
 
@@ -72,6 +74,7 @@ static void apache_free (apache_t *st)
        sfree (st->user);
        sfree (st->pass);
        sfree (st->cacert);
+       sfree (st->ssl_ciphers);
        sfree (st->server);
        sfree (st->apache_buffer);
        if (st->curl) {
@@ -179,6 +182,8 @@ static int config_add (oconfig_item_t *ci)
        }
        memset (st, 0, sizeof (*st));
 
+       st->timeout = -1;
+
        status = cf_util_get_string (ci, &st->name);
        if (status != 0)
        {
@@ -205,8 +210,12 @@ static int config_add (oconfig_item_t *ci)
                        status = cf_util_get_boolean (child, &st->verify_host);
                else if (strcasecmp ("CACert", child->key) == 0)
                        status = cf_util_get_string (child, &st->cacert);
+               else if (strcasecmp ("SSLCiphers", child->key) == 0)
+                       status = cf_util_get_string (child, &st->ssl_ciphers);
                else if (strcasecmp ("Server", child->key) == 0)
                        status = cf_util_get_string (child, &st->server);
+               else if (strcasecmp ("Timeout", child->key) == 0)
+                       status = cf_util_get_int (child, &st->timeout);
                else
                {
                        WARNING ("apache plugin: Option `%s' not allowed here.",
@@ -366,6 +375,16 @@ static int init_host (apache_t *st) /* {{{ */
                        st->verify_host ? 2L : 0L);
        if (st->cacert != NULL)
                curl_easy_setopt (st->curl, CURLOPT_CAINFO, st->cacert);
+       if (st->ssl_ciphers != NULL)
+               curl_easy_setopt (st->curl, CURLOPT_SSL_CIPHER_LIST,st->ssl_ciphers);
+
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+       if (st->timeout >= 0)
+               curl_easy_setopt (st->curl, CURLOPT_TIMEOUT_MS, (long) st->timeout);
+       else
+               curl_easy_setopt (st->curl, CURLOPT_TIMEOUT_MS,
+                               CDTIME_T_TO_MS(plugin_get_interval()));
+#endif
 
        return (0);
 } /* }}} int init_host */
index e9d25bd..11175af 100644 (file)
@@ -102,6 +102,7 @@ static char *pass        = NULL;
 static char *verify_peer = NULL;
 static char *verify_host = NULL;
 static char *cacert      = NULL;
+static char *timeout     = NULL;
 
 static CURL *curl = NULL;
 
@@ -117,7 +118,8 @@ static const char *config_keys[] =
   "Password",
   "VerifyPeer",
   "VerifyHost",
-  "CACert"
+  "CACert",
+  "Timeout",
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
@@ -518,6 +520,8 @@ static int ascent_config (const char *key, const char *value) /* {{{ */
     return (config_set (&verify_host, value));
   else if (strcasecmp (key, "CACert") == 0)
     return (config_set (&cacert, value));
+  else if (strcasecmp (key, "Timeout") == 0)
+    return (config_set (&timeout, value));
   else
     return (-1);
 } /* }}} int ascent_config */
@@ -586,6 +590,14 @@ static int ascent_init (void) /* {{{ */
   if (cacert != NULL)
     curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
 
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+  if (timeout != NULL)
+    curl_easy_setopt (curl, CURLOPT_TIMEOUT_MS, atol(timeout));
+  else
+    curl_easy_setopt (curl, CURLOPT_TIMEOUT_MS,
+       CDTIME_T_TO_MS(plugin_get_interval()));
+#endif
+
   return (0);
 } /* }}} int ascent_init */
 
index 95b05f4..2bfd51e 100644 (file)
 #define MPL3115_NUM_CONV_VALS       5
 
 
+/* ------------ BMP085 defines ------------ */
+/* I2C address of the BMP085 sensor */
+#define BMP085_I2C_ADDRESS          0x77
+
+/* register addresses */            
+#define BMP085_ADDR_ID_REG          0xD0
+#define BMP085_ADDR_VERSION         0xD1
+
+#define BMP085_ADDR_CONV            0xF6
+
+#define BMP085_ADDR_CTRL_REG        0xF4
+#define BMP085_ADDR_COEFFS          0xAA
+
+/* register sizes */                
+#define BMP085_NUM_COEFFS           22
+
+/* commands, values */
+#define BMP085_CHIP_ID              0x55
+
+#define BMP085_CMD_CONVERT_TEMP     0x2E
+
+#define BMP085_CMD_CONVERT_PRESS_0  0x34
+#define BMP085_CMD_CONVERT_PRESS_1  0x74
+#define BMP085_CMD_CONVERT_PRESS_2  0xB4
+#define BMP085_CMD_CONVERT_PRESS_3  0xF4
+
+/* in us */
+#define BMP085_TIME_CNV_TEMP        4500
+
+#define BMP085_TIME_CNV_PRESS_0     4500
+#define BMP085_TIME_CNV_PRESS_1     7500
+#define BMP085_TIME_CNV_PRESS_2    13500
+#define BMP085_TIME_CNV_PRESS_3    25500
+
+
 /* ------------ Normalization ------------ */
 /* Mean sea level pressure normalization methods */
 #define MSLP_NONE          0
 /** Temperature reference history depth for averaging. See #get_reference_temperature */
 #define REF_TEMP_AVG_NUM   5
 
+
 /* ------------------------------------------ */
+
+/** Supported sensor types */
+enum Sensor_type {
+    Sensor_none = 0,
+    Sensor_MPL115,
+    Sensor_MPL3115,
+    Sensor_BMP085
+};
+
 static const char *config_keys[] =
 {
     "Device",
@@ -146,9 +191,15 @@ static int    config_normalize    = 0;     /**< normalization method */
 static _Bool  configured          = 0;     /**< the whole plugin config status */
                                   
 static int    i2c_bus_fd          = -1;    /**< I2C bus device FD */
-                                  
-static _Bool  is_MPL3115          = 0;    /**< is this MPL3115? */
-static __s32  oversample_MPL3115  = 0;    /**< MPL3115 CTRL1 oversample setting */
+
+static enum Sensor_type sensor_type = Sensor_none; /**< detected/used sensor type */
+
+static __s32  mpl3115_oversample  = 0;    /**< MPL3115 CTRL1 oversample setting */
+
+// BMP085 configuration
+static unsigned      bmp085_oversampling; /**< BMP085 oversampling (0-3) */
+static unsigned long bmp085_timeCnvPress; /**< BMP085 conversion time for pressure in us */
+static __u8          bmp085_cmdCnvPress;  /**< BMP085 pressure conversion command */
 
 
 /* MPL115 conversion coefficients */
@@ -159,6 +210,21 @@ static double mpl115_coeffC12;
 static double mpl115_coeffC11;
 static double mpl115_coeffC22;
 
+/* BMP085 conversion coefficients */
+static short bmp085_AC1;
+static short bmp085_AC2;
+static short bmp085_AC3;
+static unsigned short bmp085_AC4;
+static unsigned short bmp085_AC5;
+static unsigned short bmp085_AC6;
+static short bmp085_B1;
+static short bmp085_B2;
+static short bmp085_MB;
+static short bmp085_MC;
+static short bmp085_MD;
+
+
+
 /* ------------------------ averaging ring buffer ------------------------ */
 /*  Used only for MPL115. MPL3115 supports real oversampling in the device so */
 /*  no need for any postprocessing. */
@@ -484,9 +550,45 @@ static int get_reference_temperature(double * result)
     return 0;
 }
 
+
 /* ------------------------ MPL115 access ------------------------ */
 
 /** 
+ * Detect presence of a MPL115 pressure sensor.
+ *
+ * Unfortunately there seems to be no ID register so we just try to read first
+ * conversion coefficient from device at MPL115 address and hope it is really
+ * MPL115. We should use this check as the last resort (which would be the typical
+ * case anyway since MPL115 is the least accurate sensor).
+ * As a sideeffect will leave set I2C slave address.
+ * 
+ * @return 1 if MPL115, 0 otherwise
+ */
+static int MPL115_detect(void)
+{
+    __s32 res;
+    char errbuf[1024];
+
+    if (ioctl(i2c_bus_fd, I2C_SLAVE_FORCE, MPL115_I2C_ADDRESS) < 0)
+    {
+        ERROR("barometer: MPL115_detect problem setting i2c slave address to 0x%02X: %s",
+              MPL115_I2C_ADDRESS,
+              sstrerror (errno, errbuf, sizeof (errbuf)));
+        return 0 ;
+    }
+
+    res = i2c_smbus_read_byte_data(i2c_bus_fd, MPL115_ADDR_COEFFS);
+    if(res >= 0)
+    {
+        DEBUG ("barometer: MPL115_detect - positive detection");
+        return 1;
+    }
+
+    DEBUG ("barometer: MPL115_detect - negative detection");
+    return 0;
+}
+
+/** 
  * Read the MPL115 sensor conversion coefficients.
  *
  * These are (device specific) constants so we can read them just once.
@@ -510,7 +612,7 @@ static int MPL115_read_coeffs(void)
                                         mpl115_coeffs);
     if (res < 0)
     {
-        ERROR ("barometer: read_mpl115_coeffs - problem reading data: %s",
+        ERROR ("barometer: MPL115_read_coeffs - problem reading data: %s",
                sstrerror (errno, errbuf, sizeof (errbuf)));
         return -1;
     }
@@ -567,7 +669,7 @@ static int MPL115_read_coeffs(void)
     mpl115_coeffC22 /= 32.0; //16-11=5
     mpl115_coeffC22 /= 33554432.0;          /* 10+15=25 fract */
 
-    DEBUG("barometer: read_mpl115_coeffs: a0=%lf, b1=%lf, b2=%lf, c12=%lf, c11=%lf, c22=%lf",
+    DEBUG("barometer: MPL115_read_coeffs: a0=%lf, b1=%lf, b2=%lf, c12=%lf, c11=%lf, c22=%lf",
           mpl115_coeffA0, 
           mpl115_coeffB1, 
           mpl115_coeffB2, 
@@ -578,7 +680,7 @@ static int MPL115_read_coeffs(void)
 }
 
 
-/*
+/**
  * Convert raw adc values to real data using the sensor coefficients.
  *
  * @param adc_pressure adc pressure value to be converted
@@ -598,7 +700,7 @@ static void MPL115_convert_adc_to_real(double   adc_pressure,
     
     *pressure = ((1150.0-500.0) * Pcomp / 1023.0) + 500.0;
     *temperature = (472.0 - adc_temp) / 5.35 + 25.0;
-    DEBUG ("barometer: convert_adc_to_real - got %lf hPa, %lf C",
+    DEBUG ("barometer: MPL115_convert_adc_to_real - got %lf hPa, %lf C",
            *pressure,
            *temperature);
 }
@@ -709,12 +811,23 @@ static int MPL115_read_averaged(double * pressure, double * temperature)
 
 /** 
  * Detect presence of a MPL3115 pressure sensor by checking register "WHO AM I"
+ *
+ * As a sideeffect will leave set I2C slave address.
  * 
  * @return 1 if MPL3115, 0 otherwise
  */
 static int MPL3115_detect(void)
 {
     __s32 res;
+    char errbuf[1024];
+
+    if (ioctl(i2c_bus_fd, I2C_SLAVE_FORCE, MPL3115_I2C_ADDRESS) < 0)
+    {
+        ERROR("barometer: MPL3115_detect problem setting i2c slave address to 0x%02X: %s",
+              MPL3115_I2C_ADDRESS,
+              sstrerror (errno, errbuf, sizeof (errbuf)));
+        return 0 ;
+    }
 
     res = i2c_smbus_read_byte_data(i2c_bus_fd, MPL3115_REG_WHO_AM_I);
     if(res == MPL3115_WHO_AM_I_RESP)
@@ -739,45 +852,45 @@ static void MPL3115_adjust_oversampling(void)
     if(config_oversample > 100)
     {
         new_val = 128;
-        oversample_MPL3115 = MPL3115_CTRL_REG1_OST_128;
+        mpl3115_oversample = MPL3115_CTRL_REG1_OST_128;
     }
     else if(config_oversample > 48)
     {
         new_val = 64;
-        oversample_MPL3115 = MPL3115_CTRL_REG1_OST_64;
+        mpl3115_oversample = MPL3115_CTRL_REG1_OST_64;
     }
     else if(config_oversample > 24)
     {
         new_val = 32;
-        oversample_MPL3115 = MPL3115_CTRL_REG1_OST_32;
+        mpl3115_oversample = MPL3115_CTRL_REG1_OST_32;
     }
     else if(config_oversample > 12)
     {
         new_val = 16;
-        oversample_MPL3115 = MPL3115_CTRL_REG1_OST_16;
+        mpl3115_oversample = MPL3115_CTRL_REG1_OST_16;
     }
     else if(config_oversample > 6)
     {
         new_val = 8;
-        oversample_MPL3115 = MPL3115_CTRL_REG1_OST_8;
+        mpl3115_oversample = MPL3115_CTRL_REG1_OST_8;
     }
     else if(config_oversample > 3)
     {
         new_val = 4;
-        oversample_MPL3115 = MPL3115_CTRL_REG1_OST_4;
+        mpl3115_oversample = MPL3115_CTRL_REG1_OST_4;
     }
     else if(config_oversample > 1)
     {
         new_val = 2;
-        oversample_MPL3115 = MPL3115_CTRL_REG1_OST_2;
+        mpl3115_oversample = MPL3115_CTRL_REG1_OST_2;
     }
     else
     {
         new_val = 1;
-        oversample_MPL3115 = MPL3115_CTRL_REG1_OST_1;
+        mpl3115_oversample = MPL3115_CTRL_REG1_OST_1;
     }
 
-    DEBUG("barometer: correcting oversampling for MPL3115 from %d to %d",
+    DEBUG("barometer: MPL3115_adjust_oversampling - correcting oversampling from %d to %d",
           config_oversample, 
           new_val);
     config_oversample = new_val;
@@ -859,7 +972,7 @@ static int MPL3115_read(double * pressure, double * temperature)
     
     tmp_value = (data[0] << 16) | (data[1] << 8) | data[2];
     *pressure = ((double) tmp_value) / 4.0 / 16.0 / 100.0;
-    DEBUG ("barometer: MPL3115_read, absolute pressure = %lf hPa", *pressure);
+    DEBUG ("barometer: MPL3115_read - absolute pressure = %lf hPa", *pressure);
     
     if(data[3] > 0x7F)
     {
@@ -873,7 +986,7 @@ static int MPL3115_read(double * pressure, double * temperature)
     }
     
     *temperature += (double)(data[4]) / 256.0;
-    DEBUG ("barometer: MPL3115_read, temperature = %lf C", *temperature);
+    DEBUG ("barometer: MPL3115_read - temperature = %lf C", *temperature);
     
     return 0;
 }
@@ -938,7 +1051,7 @@ static int MPL3115_init_sensor(void)
     /* Set to barometer with an OSR */ 
     res = i2c_smbus_write_byte_data(i2c_bus_fd, 
                                     MPL3115_REG_CTRL_REG1, 
-                                    oversample_MPL3115);
+                                    mpl3115_oversample);
     if (res < 0)
     {
         ERROR ("barometer: MPL3115_init_sensor - problem configuring CTRL_REG1: %s",
@@ -949,6 +1062,327 @@ static int MPL3115_init_sensor(void)
     return 0;
 }
 
+/* ------------------------ BMP085 access ------------------------ */
+
+/** 
+ * Detect presence of a BMP085 pressure sensor by checking its ID register
+ *
+ * As a sideeffect will leave set I2C slave address.
+ * 
+ * @return 1 if BMP085, 0 otherwise
+ */
+static int BMP085_detect(void)
+{
+    __s32 res;
+    char errbuf[1024];
+
+    if (ioctl(i2c_bus_fd, I2C_SLAVE_FORCE, BMP085_I2C_ADDRESS) < 0)
+    {
+        ERROR("barometer: BMP085_detect - problem setting i2c slave address to 0x%02X: %s",
+              BMP085_I2C_ADDRESS,
+              sstrerror (errno, errbuf, sizeof (errbuf)));
+        return 0 ;
+    }
+
+    res = i2c_smbus_read_byte_data(i2c_bus_fd, BMP085_ADDR_ID_REG);
+    if(res == BMP085_CHIP_ID)
+    {
+        DEBUG ("barometer: BMP085_detect - positive detection");
+
+        /* get version */
+        res = i2c_smbus_read_byte_data(i2c_bus_fd, BMP085_ADDR_VERSION );
+        if (res < 0)
+        {
+            ERROR("barometer: BMP085_detect - problem checking chip version: %s",
+                  sstrerror (errno, errbuf, sizeof (errbuf)));
+            return 0 ;
+        }
+        DEBUG ("barometer: BMP085_detect - chip version ML:0x%02X AL:0x%02X",
+               res & 0x0f,
+               (res & 0xf0) >> 4);
+        return 1;
+    }
+
+    DEBUG ("barometer: BMP085_detect - negative detection");
+    return 0;
+}
+
+
+/** 
+ * Adjusts oversampling settings to values supported by BMP085
+ *
+ * BMP085 supports only 1,2,4 or 8 samples. 
+ */
+static void BMP085_adjust_oversampling(void)
+{
+    int new_val = 0;
+
+    if( config_oversample > 6 ) /* 8 */
+    {
+        new_val = 8;
+        bmp085_oversampling = 3;
+        bmp085_cmdCnvPress = BMP085_CMD_CONVERT_PRESS_3;
+        bmp085_timeCnvPress = BMP085_TIME_CNV_PRESS_3;
+    }
+    else if( config_oversample > 3 ) /* 4 */
+    {
+        new_val = 4;
+        bmp085_oversampling = 2;
+        bmp085_cmdCnvPress = BMP085_CMD_CONVERT_PRESS_2;
+        bmp085_timeCnvPress = BMP085_TIME_CNV_PRESS_2;
+    }
+    else if( config_oversample > 1 ) /* 2 */
+    {
+        new_val = 2;
+        bmp085_oversampling = 1;
+        bmp085_cmdCnvPress = BMP085_CMD_CONVERT_PRESS_1;
+        bmp085_timeCnvPress = BMP085_TIME_CNV_PRESS_1;
+    }
+    else /* 1 */
+    {
+        new_val = 1;
+        bmp085_oversampling = 0;
+        bmp085_cmdCnvPress = BMP085_CMD_CONVERT_PRESS_0;
+        bmp085_timeCnvPress = BMP085_TIME_CNV_PRESS_0;
+    }
+
+    DEBUG("barometer: BMP085_adjust_oversampling - correcting oversampling from %d to %d",
+          config_oversample, 
+          new_val);
+    config_oversample = new_val;
+}
+
+
+/** 
+ * Read the BMP085 sensor conversion coefficients.
+ *
+ * These are (device specific) constants so we can read them just once.
+ *
+ * @return Zero when successful
+ */
+static int BMP085_read_coeffs(void)
+{
+    __s32 res;
+    __u8 coeffs[BMP085_NUM_COEFFS]; 
+    char errbuf[1024];
+
+    res = i2c_smbus_read_i2c_block_data(i2c_bus_fd, 
+                                        BMP085_ADDR_COEFFS,
+                                        BMP085_NUM_COEFFS, 
+                                        coeffs);
+    if (res < 0)
+    {
+        ERROR ("barometer: BMP085_read_coeffs - problem reading data: %s",
+               sstrerror (errno, errbuf, sizeof (errbuf)));
+        return -1;
+    }
+    
+    bmp085_AC1 = ((int16_t)  coeffs[0]  <<8) | (int16_t)  coeffs[1];
+    bmp085_AC2 = ((int16_t)  coeffs[2]  <<8) | (int16_t)  coeffs[3];
+    bmp085_AC3 = ((int16_t)  coeffs[4]  <<8) | (int16_t)  coeffs[5];
+    bmp085_AC4 = ((uint16_t) coeffs[6]  <<8) | (uint16_t) coeffs[7];
+    bmp085_AC5 = ((uint16_t) coeffs[8]  <<8) | (uint16_t) coeffs[9];
+    bmp085_AC6 = ((uint16_t) coeffs[10] <<8) | (uint16_t) coeffs[11];
+    bmp085_B1 =  ((int16_t)  coeffs[12] <<8) | (int16_t)  coeffs[13];
+    bmp085_B2 =  ((int16_t)  coeffs[14] <<8) | (int16_t)  coeffs[15];
+    bmp085_MB =  ((int16_t)  coeffs[16] <<8) | (int16_t)  coeffs[17];
+    bmp085_MC =  ((int16_t)  coeffs[18] <<8) | (int16_t)  coeffs[19];
+    bmp085_MD =  ((int16_t)  coeffs[20] <<8) | (int16_t)  coeffs[21];
+
+    DEBUG("barometer: BMP085_read_coeffs - AC1=%d, AC2=%d, AC3=%d, AC4=%u,"\
+          " AC5=%u, AC6=%u, B1=%d, B2=%d, MB=%d, MC=%d, MD=%d",
+          bmp085_AC1,
+          bmp085_AC2,
+          bmp085_AC3,
+          bmp085_AC4,
+          bmp085_AC5,
+          bmp085_AC6,
+          bmp085_B1,
+          bmp085_B2,
+          bmp085_MB,
+          bmp085_MC,
+          bmp085_MD);
+
+    return 0;
+}
+
+
+/**
+ * Convert raw BMP085 adc values to real data using the sensor coefficients.
+ *
+ * @param adc_pressure adc pressure value to be converted
+ * @param adc_temp     adc temperature value to be converted
+ * @param pressure     computed real pressure
+ * @param temperature  computed real temperature
+ */
+static void BMP085_convert_adc_to_real(long adc_pressure,
+                                       long adc_temperature,
+                                       double * pressure,
+                                       double * temperature)
+
+{
+    long X1, X2, X3;
+    long B3, B5, B6;
+    unsigned long B4, B7;
+
+    long T;
+    long P;
+
+
+    /* calculate real temperature */
+    X1 = ( (adc_temperature - bmp085_AC6) * bmp085_AC5) >> 15;
+    X2 = (bmp085_MC << 11) / (X1 + bmp085_MD);
+
+    /* B5, T */
+    B5 = X1 + X2;
+    T = (B5 + 8) >> 4;
+    *temperature = (double)T * 0.1;
+
+    /* calculate real pressure */
+    /* in general X1, X2, X3 are recycled while values of B3, B4, B5, B6 are kept */
+
+    /* B6, B3 */
+    B6 = B5 - 4000;
+    X1 = ((bmp085_B2 * ((B6 * B6)>>12)) >> 11 );
+    X2 = (((long)bmp085_AC2 * B6) >> 11);
+    X3 = X1 + X2;
+    B3 = (((((long)bmp085_AC1 * 4) + X3) << bmp085_oversampling) + 2) >> 2;
+    
+    /* B4 */
+    X1 = (((long)bmp085_AC3*B6) >> 13);
+    X2 = (bmp085_B1*((B6*B6) >> 12) ) >> 16;
+    X3 = ((X1 + X2) + 2 ) >> 2;
+    B4 = ((long)bmp085_AC4* (unsigned long)(X3 + 32768)) >> 15;
+    
+    /* B7, P */
+    B7 =  (unsigned long)(adc_pressure - B3)*(50000>>bmp085_oversampling);
+    if( B7 < 0x80000000 )
+    {
+        P = (B7 << 1) / B4;
+    }
+    else
+    {
+        P = (B7/B4) << 1;
+    }
+    X1 = (P >> 8) * (P >> 8);
+    X1 = (X1 * 3038) >> 16;
+    X2 = ((-7357) * P) >> 16;
+    P = P + ( ( X1 + X2 + 3791 ) >> 4);
+    
+    *pressure = P / 100.0; // in [hPa] 
+    DEBUG ("barometer: BMP085_convert_adc_to_real - got %lf hPa, %lf C",
+           *pressure,
+           *temperature);
+}
+
+    
+/** 
+ * Read compensated sensor measurements
+ *
+ * @param pressure    averaged measured pressure
+ * @param temperature averaged measured temperature
+ *
+ * @return Zero when successful
+ */
+static int BMP085_read(double * pressure, double * temperature)
+{
+    __s32 res;
+    __u8 measBuff[3];
+
+    long adc_pressure;
+    long adc_temperature;
+
+    char errbuf[1024];
+
+    /* start conversion of temperature */
+    res = i2c_smbus_write_byte_data( i2c_bus_fd,
+                                     BMP085_ADDR_CTRL_REG,
+                                     BMP085_CMD_CONVERT_TEMP );
+    if (res < 0)
+    {
+        ERROR ("barometer: BMP085_read - problem requesting temperature conversion: %s",
+               sstrerror (errno, errbuf, sizeof (errbuf)));
+        return 1;
+    }
+
+    usleep(BMP085_TIME_CNV_TEMP); /* wait for the conversion */
+
+    res = i2c_smbus_read_i2c_block_data( i2c_bus_fd,
+                                         BMP085_ADDR_CONV, 
+                                         2,
+                                         measBuff); 
+    if (res < 0)
+    {
+        ERROR ("barometer: BMP085_read - problem reading temperature data: %s",
+               sstrerror (errno, errbuf, sizeof (errbuf)));
+        return 1;
+    }
+
+    adc_temperature = ( (unsigned short)measBuff[0] << 8 ) + measBuff[1]; 
+    
+
+    /* get presure */
+    res = i2c_smbus_write_byte_data( i2c_bus_fd,
+                                     BMP085_ADDR_CTRL_REG, 
+                                     bmp085_cmdCnvPress );
+    if (res < 0)
+    {
+        ERROR ("barometer: BMP085_read - problem requesting pressure conversion: %s",
+               sstrerror (errno, errbuf, sizeof (errbuf)));
+        return 1;
+    }
+
+    usleep(bmp085_timeCnvPress); /* wait for the conversion */
+
+    res = i2c_smbus_read_i2c_block_data( i2c_bus_fd,
+                                         BMP085_ADDR_CONV, 
+                                         3,
+                                         measBuff );
+    if (res < 0)
+    {
+        ERROR ("barometer: BMP085_read - problem reading pressure data: %s",
+               sstrerror (errno, errbuf, sizeof (errbuf)));
+        return 1;
+    }
+
+    adc_pressure = (long)((((ulong)measBuff[0]<<16) | ((ulong)measBuff[1]<<8) | (ulong)measBuff[2] ) >> (8 - bmp085_oversampling));
+    
+
+    DEBUG ("barometer: BMP085_read - raw pressure ADC value = %ld, " \
+           "raw temperature ADC value = %ld",
+           adc_pressure,
+           adc_temperature);
+
+    BMP085_convert_adc_to_real(adc_pressure, adc_temperature, pressure, temperature);
+
+    return 0;
+}
+
+
+
+/* ------------------------ Sensor detection ------------------------ */
+/** 
+ * Detect presence of a supported sensor.
+ *
+ * As a sideeffect will leave set I2C slave address.
+ * The detection is done in the order BMP085, MPL3115, MPL115 and stops after
+ * first sensor beeing found.
+ * 
+ * @return detected sensor type
+ */
+enum Sensor_type Detect_sensor_type(void)
+{
+    if(BMP085_detect())
+        return Sensor_BMP085;
+
+    else if(MPL3115_detect())
+        return Sensor_MPL3115;
+
+    else if(MPL115_detect())
+        return Sensor_MPL115;
+
+    return Sensor_none;
+}
 
 /* ------------------------ Common functionality ------------------------ */
 
@@ -975,10 +1409,6 @@ static double abs_to_mean_sea_level_pressure(double abs_pressure)
     double temp = 0.0;
     int result = 0;
 
-    DEBUG ("barometer: abs_to_mean_sea_level_pressure: absPressure = %lf, method = %d",
-           abs_pressure,
-           config_normalize);
-
     if (config_normalize >= MSLP_DEU_WETT)
     {
         result = get_reference_temperature(&temp);
@@ -996,7 +1426,7 @@ static double abs_to_mean_sea_level_pressure(double abs_pressure)
         
     case MSLP_INTERNATIONAL:
         mean = abs_pressure / \
-            pow(1.0 - 0.0065*config_altitude/288.15, 0.0065*0.0289644/(8.31447*0.0065));
+            pow(1.0 - 0.0065*config_altitude/288.15, 9.80665*0.0289644/(8.31447*0.0065));
         break;
         
     case MSLP_DEU_WETT:
@@ -1019,6 +1449,11 @@ static double abs_to_mean_sea_level_pressure(double abs_pressure)
         break;
     }
 
+    DEBUG ("barometer: abs_to_mean_sea_level_pressure: absPressure = %lf hPa, method = %d, meanPressure = %lf hPa",
+           abs_pressure,
+           config_normalize,
+           mean);
+
     return mean; 
 }
 
@@ -1047,7 +1482,7 @@ static int collectd_barometer_config (const char *key, const char *value)
         if (oversampling_tmp < 1 || oversampling_tmp > 1024)
         {
             WARNING ("barometer: collectd_barometer_config: invalid oversampling: %d." \
-                     " Allowed values are 1 to 1024 (for MPL115) or 128 (for MPL3115).",
+                     " Allowed values are 1 to 1024 (for MPL115) or 1 to 128 (for MPL3115) or 1 to 8 (for BMP085).",
                      oversampling_tmp);
             return 1;
         }
@@ -1103,7 +1538,7 @@ static int collectd_barometer_shutdown(void)
 {
     DEBUG ("barometer: collectd_barometer_shutdown");
 
-    if(!is_MPL3115)
+    if(sensor_type == Sensor_MPL115)
     {
         averaging_delete (&pressure_averaging);
         averaging_delete (&temperature_averaging);
@@ -1268,6 +1703,69 @@ static int MPL3115_collectd_barometer_read (void)
 
 
 /** 
+ * Plugin read callback for BMP085.
+ * 
+ *  Dispatching will create values:
+ *  - <hostname>/barometer-bmp085/pressure-normalized
+ *  - <hostname>/barometer-bmp085/pressure-absolute
+ *  - <hostname>/barometer-bmp085/temperature
+ *
+ * @return Zero when successful.
+ */
+static int BMP085_collectd_barometer_read (void)
+{
+    int result = 0;
+    
+    double pressure        = 0.0;
+    double temperature     = 0.0;
+    double norm_pressure   = 0.0;
+    
+    value_list_t vl = VALUE_LIST_INIT;
+    value_t      values[1];
+    
+    DEBUG("barometer: BMP085_collectd_barometer_read");
+    
+    if (!configured)
+    {
+        return -1;
+    }
+    
+    result = BMP085_read(&pressure, &temperature);
+    if(result)
+        return result;
+
+    norm_pressure = abs_to_mean_sea_level_pressure(pressure);
+
+    sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+    sstrncpy (vl.plugin, "barometer", sizeof (vl.plugin));
+    sstrncpy (vl.plugin_instance, "bmp085", sizeof (vl.plugin_instance));
+
+    vl.values_len = 1;
+    vl.values = values;
+
+    /* dispatch normalized air pressure */
+    sstrncpy (vl.type, "pressure", sizeof (vl.type));
+    sstrncpy (vl.type_instance, "normalized", sizeof (vl.type_instance));
+    values[0].gauge = norm_pressure;
+    plugin_dispatch_values (&vl);
+
+    /* dispatch absolute air pressure */
+    sstrncpy (vl.type, "pressure", sizeof (vl.type));
+    sstrncpy (vl.type_instance, "absolute", sizeof (vl.type_instance));
+    values[0].gauge = pressure;
+    plugin_dispatch_values (&vl);
+
+    /* dispatch sensor temperature */
+    sstrncpy (vl.type, "temperature", sizeof (vl.type));
+    sstrncpy (vl.type_instance, "", sizeof (vl.type_instance));
+    values[0].gauge = temperature;
+    plugin_dispatch_values (&vl);
+
+    return 0;
+}
+
+
+/** 
  * Initialization callback
  * 
  * Check config, initialize I2C bus access, conversion coefficients and averaging
@@ -1313,28 +1811,26 @@ static int collectd_barometer_init (void)
         return -1;
     }
 
-    if (ioctl(i2c_bus_fd, I2C_SLAVE_FORCE, MPL115_I2C_ADDRESS) < 0)
-    {
-        ERROR("barometer: collectd_barometer_init problem setting i2c slave address to 0x%02X: %s",
-              MPL115_I2C_ADDRESS,
-              sstrerror (errno, errbuf, sizeof (errbuf)));
-        return -1;
-    }
-
-    /* detect sensor type - MPL115 or MPL3115 */
-    is_MPL3115 = MPL3115_detect();
+    /* detect sensor type - this will also set slave address */
+    sensor_type = Detect_sensor_type();
 
     /* init correct sensor type */
-    if(is_MPL3115) /* MPL3115 */
+    switch(sensor_type)
+    {
+/* MPL3115 */
+    case Sensor_MPL3115:
     {
         MPL3115_adjust_oversampling();
-
+        
         if(MPL3115_init_sensor())
             return -1;
-
+        
         plugin_register_read ("barometer", MPL3115_collectd_barometer_read);
     }
-    else /* MPL115 */
+    break;
+
+/* MPL115 */
+    case Sensor_MPL115:
     {
         if (averaging_create (&pressure_averaging, config_oversample))
         {
@@ -1350,9 +1846,29 @@ static int collectd_barometer_init (void)
         
         if (MPL115_read_coeffs() < 0)
             return -1;
-
+        
         plugin_register_read ("barometer", MPL115_collectd_barometer_read);
     }
+    break;
+
+/* BMP085 */
+    case Sensor_BMP085:
+    {
+        BMP085_adjust_oversampling();
+
+        if (BMP085_read_coeffs() < 0)
+            return -1;
+
+        plugin_register_read ("barometer", BMP085_collectd_barometer_read);
+    }
+    break;
+
+/* anything else -> error */
+    default:
+        ERROR("barometer: collectd_barometer_init - no supported sensor found");
+        return -1;
+    }
+        
 
     configured = 1;
     return 0;
index 59eb249..2ad50f1 100644 (file)
@@ -109,6 +109,7 @@ static int global_server_stats     = 1;
 static int global_zone_maint_stats = 1;
 static int global_resolver_stats   = 0;
 static int global_memory_stats     = 1;
+static int timeout                 = -1;
 
 static cb_view_t *views = NULL;
 static size_t     views_num = 0;
@@ -266,7 +267,7 @@ static void submit (time_t ts, const char *plugin_instance, /* {{{ */
   if (type_instance) {
     sstrncpy(vl.type_instance, type_instance,
         sizeof(vl.type_instance));
-    replace_special (vl.plugin_instance, sizeof (vl.plugin_instance));
+    replace_special (vl.type_instance, sizeof (vl.type_instance));
   }
   plugin_dispatch_values(&vl);
 } /* }}} void submit */
@@ -369,9 +370,11 @@ static int bind_xml_read_derive (xmlDoc *doc, xmlNode *node, /* {{{ */
   {
     ERROR ("bind plugin: Parsing string \"%s\" to derive value failed.",
         str_ptr);
+    xmlFree(str_ptr);
     return (-1);
   }
 
+  xmlFree(str_ptr);
   *ret_value = value.derive;
   return (0);
 } /* }}} int bind_xml_read_derive */
@@ -714,25 +717,40 @@ static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
   int i;
   size_t j;
 
-  path_obj = xmlXPathEvalExpression (BAD_CAST "name", path_ctx);
-  if (path_obj == NULL)
+  if (version >= 3)
   {
-    ERROR ("bind plugin: xmlXPathEvalExpression failed.");
-    return (-1);
+    char *n = (char *) xmlGetProp (node, BAD_CAST "name");
+    char *c = (char *) xmlGetProp (node, BAD_CAST "rdataclass");
+    if (n && c)
+    {
+      zone_name = (char *) xmlMalloc(strlen(n) + strlen(c) + 2);
+      snprintf(zone_name, strlen(n) + strlen(c) + 2, "%s/%s", n, c);
+    }
+    xmlFree(n);
+    xmlFree(c);
   }
-
-  for (i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+  else
   {
-    zone_name = (char *) xmlNodeListGetString (doc,
-        path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
-    if (zone_name != NULL)
-      break;
+    path_obj = xmlXPathEvalExpression (BAD_CAST "name", path_ctx);
+    if (path_obj == NULL)
+    {
+      ERROR ("bind plugin: xmlXPathEvalExpression failed.");
+      return (-1);
+    }
+
+    for (i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+    {
+      zone_name = (char *) xmlNodeListGetString (doc,
+          path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
+      if (zone_name != NULL)
+        break;
+    }
+    xmlXPathFreeObject (path_obj);
   }
 
   if (zone_name == NULL)
   {
     ERROR ("bind plugin: Could not determine zone name.");
-    xmlXPathFreeObject (path_obj);
     return (-1);
   }
 
@@ -746,10 +764,7 @@ static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
   zone_name = NULL;
 
   if (j >= views->zones_num)
-  {
-    xmlXPathFreeObject (path_obj);
     return (0);
-  }
 
   zone_name = view->zones[j];
 
@@ -768,14 +783,31 @@ static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
     ssnprintf (plugin_instance, sizeof (plugin_instance), "%s-zone-%s",
         view->name, zone_name);
 
-    bind_parse_generic_value_list (/* xpath = */ "counters",
+    if (version == 3)
+    {
+      list_info_ptr_t list_info =
+      {
+        plugin_instance,
+        /* type = */ "dns_qtype"
+      };
+      bind_parse_generic_name_attr_value_list (/* xpath = */ "counters[@type='rcode']",
         /* callback = */ bind_xml_table_callback,
         /* user_data = */ &table_ptr,
         doc, path_ctx, current_time, DS_TYPE_COUNTER);
+      bind_parse_generic_name_attr_value_list (/* xpath = */ "counters[@type='qtype']",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
+    }
+    else
+    {
+      bind_parse_generic_value_list (/* xpath = */ "counters",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, path_ctx, current_time, DS_TYPE_COUNTER);
+    }
   } /* }}} */
 
-  xmlXPathFreeObject (path_obj);
-
   return (0);
 } /* }}} int bind_xml_stats_handle_zone */
 
@@ -968,8 +1000,7 @@ static int bind_xml_stats_handle_view (int version, xmlDoc *doc, /* {{{ */
         doc, path_ctx, current_time, DS_TYPE_GAUGE);
   } /* }}} */
 
-  // v3 does not provide per-zone stats any more
-  if (version < 3 && view->zones_num > 0)
+  if (view->zones_num > 0)
     bind_xml_stats_search_zones (version, doc, path_ctx, node, view,
         current_time);
 
@@ -1695,6 +1726,8 @@ static int bind_config (oconfig_item_t *ci) /* {{{ */
       bind_config_add_view (child);
     else if (strcasecmp ("ParseTime", child->key) == 0)
       cf_util_get_boolean (child, &config_parse_time);
+    else if (strcasecmp ("Timeout", child->key) == 0)
+      cf_util_get_int (child, &timeout);
     else
     {
       WARNING ("bind plugin: Unknown configuration option "
@@ -1724,6 +1757,11 @@ static int bind_init (void) /* {{{ */
   curl_easy_setopt (curl, CURLOPT_URL, (url != NULL) ? url : BIND_DEFAULT_URL);
   curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
   curl_easy_setopt (curl, CURLOPT_MAXREDIRS, 50L);
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+  curl_easy_setopt (curl, CURLOPT_TIMEOUT_MS, (timeout >= 0) ?
+      (long) timeout : CDTIME_T_TO_MS(plugin_get_interval()));
+#endif
+
 
   return (0);
 } /* }}} int bind_init */
index f1426de..7308648 100644 (file)
@@ -27,12 +27,6 @@ for collectd in Perl. This is a lot more efficient than executing a
 Perl-script every time you want to read a value with the C<exec plugin> (see
 L<collectd-exec(5)>) and provides a lot more functionality, too.
 
-When loading the C<perl plugin>, the B<Globals> option should be enabled.
-Else, the perl plugin will fail to load any Perl modules implemented in C,
-which includes, amongst many others, the B<threads> module used by the plugin
-itself. See the documentation of the B<Globals> option in L<collectd.conf(5)>
-for details.
-
 =head1 CONFIGURATION
 
 =over 4
index 8d7622a..d31ef15 100644 (file)
 #@BUILD_PLUGIN_GMOND_TRUE@LoadPlugin gmond
 #@BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
 @BUILD_PLUGIN_INTERFACE_TRUE@@BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
+#@BUILD_PLUGIN_IPC_TRUE@@BUILD_PLUGIN_IPC_TRUE@LoadPlugin ipc
 #@BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables
 #@BUILD_PLUGIN_IPMI_TRUE@LoadPlugin ipmi
 #@BUILD_PLUGIN_IPVS_TRUE@LoadPlugin ipvs
 #@BUILD_PLUGIN_OPENLDAP_TRUE@LoadPlugin openldap
 #@BUILD_PLUGIN_OPENVPN_TRUE@LoadPlugin openvpn
 #@BUILD_PLUGIN_ORACLE_TRUE@LoadPlugin oracle
-#@BUILD_PLUGIN_PERL_TRUE@<LoadPlugin perl>
-#@BUILD_PLUGIN_PERL_TRUE@  Globals true
-#@BUILD_PLUGIN_PERL_TRUE@</LoadPlugin>
+#@BUILD_PLUGIN_PERL_TRUE@LoadPlugin perl
 #@BUILD_PLUGIN_PINBA_TRUE@LoadPlugin pinba
 #@BUILD_PLUGIN_PING_TRUE@LoadPlugin ping
 #@BUILD_PLUGIN_POSTGRESQL_TRUE@LoadPlugin postgresql
 #@BUILD_PLUGIN_POWERDNS_TRUE@LoadPlugin powerdns
 #@BUILD_PLUGIN_PROCESSES_TRUE@LoadPlugin processes
 #@BUILD_PLUGIN_PROTOCOLS_TRUE@LoadPlugin protocols
-#@BUILD_PLUGIN_PYTHON_TRUE@<LoadPlugin python>
-#@BUILD_PLUGIN_PYTHON_TRUE@  Globals true
-#@BUILD_PLUGIN_PYTHON_TRUE@</LoadPlugin>
+#@BUILD_PLUGIN_PYTHON_TRUE@LoadPlugin python
 #@BUILD_PLUGIN_REDIS_TRUE@LoadPlugin redis
 #@BUILD_PLUGIN_ROUTEROS_TRUE@LoadPlugin routeros
 #@BUILD_PLUGIN_RRDCACHED_TRUE@LoadPlugin rrdcached
 #@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_WRITE_SENSU_TRUE@LoadPlugin write_sensu
 #@BUILD_PLUGIN_WRITE_TSDB_TRUE@LoadPlugin write_tsdb
 #@BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
 #@BUILD_PLUGIN_ZFS_ARC_TRUE@LoadPlugin zfs_arc
 #              Format "Command"
 #              StoreRates false
 #              BufferSize 4096
+#              LowSpeedLimit 0
+#              Timeout 0
 #      </Node>
 #</Plugin>
 
 #      Attribute "foo" "bar"
 #</Plugin>
 
+#<Plugin write_sensu>
+#      <Node "example">
+#              Host "localhost"
+#              Port 3030
+#              StoreRates true
+#              AlwaysAppendDS false
+#              Notifications true
+#              Metrics true
+#              EventServicePrefix ""
+#              MetricHandler "influx"
+#              MetricHandler "default"
+#              NotificationHandler "flapjack"
+#              NotificationHandler "howling_monkey"
+#      </Node>
+#      Tag "foobar"
+#      Attribute "foo" "bar"
+#</Plugin>
+
 #<Plugin write_tsdb>
 #      <Node>
 #              Host "localhost"
index bd78107..a7a5816 100644 (file)
@@ -98,7 +98,6 @@ Options inside a B<LoadPlugin> block can override default settings and
 influence the way plugins are loaded, e.g.:
 
  <LoadPlugin perl>
-   Globals true
    Interval 60
  </LoadPlugin>
 
@@ -775,6 +774,18 @@ 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<SSLCiphers> I<list of ciphers>
+
+Specifies which ciphers to use in the connection. The list of ciphers
+must specify valid ciphers. See
+L<http://www.openssl.org/docs/apps/ciphers.html> for details.
+
+=item B<Timeout> I<Milliseconds>
+
+The B<Timeout> option sets the overall timeout for HTTP requests to B<URL>, in
+milliseconds. By default, the configured B<Interval> is used to set the
+timeout.
+
 =back
 
 =head2 Plugin C<apcups>
@@ -863,18 +874,38 @@ 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<Timeout> I<Milliseconds>
+
+The B<Timeout> option sets the overall timeout for HTTP requests to B<URL>, in
+milliseconds. By default, the configured B<Interval> is used to set the
+timeout.
+
 =back
 
 =head2 Plugin C<barometer>
 
-This plugin reads absolute air pressure using digital barometer sensor MPL115A2
-or MPL3115 from Freescale (sensor attached to any I2C bus available in
-the computer, for HW details see
-I<http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=MPL115A> or
-I<http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=MPL3115A2>).
-The sensor type - one fo these two - is detected automatically by the plugin
-and indicated in the plugin_instance (typically you will see subdirectory
-"barometer-mpl115" or "barometer-mpl3115").
+This plugin reads absolute air pressure using digital barometer sensor on a I2C
+bus. Supported sensors are:
+
+=over 5
+
+=item I<MPL115A2> from Freescale,
+see L<http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=MPL115A>.
+
+
+=item I<MPL3115> from Freescale
+see L<http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=MPL3115A2>.
+
+
+=item I<BMP085> from Bosch Sensortec
+
+=back
+
+The sensor type - one of the above - is detected automatically by the plugin
+and indicated in the plugin_instance (you will see subdirectory
+"barometer-mpl115" or "barometer-mpl3115", or "barometer-bmp085"). The order of
+detection is BMP085 -> MPL3115 -> MPL115A2, the first one found will be used
+(only one sensor can be used by the plugin).
 
 The plugin provides absolute barometric pressure, air pressure reduced to sea
 level (several possible approximations) and as an auxiliary value also internal
@@ -885,11 +916,11 @@ It was developed and tested under Linux only. The only platform dependency is
 the standard Linux i2c-dev interface (the particular bus driver has to
 support the SM Bus command subset).
 
-The reduction or normalization to mean sea level pressure requires (depedning on
-selected method/approximation) also altitude and reference to temperature sensor(s).
-When multiple temperature sensors are configured the minumum of their values is
-always used (expecting that the warmer ones are affected by e.g. direct sun light
-at that moment).
+The reduction or normalization to mean sea level pressure requires (depending
+on selected method/approximation) also altitude and reference to temperature
+sensor(s).  When multiple temperature sensors are configured the minumum of
+their values is always used (expecting that the warmer ones are affected by
+e.g. direct sun light at that moment).
 
 Synopsis:
 
@@ -907,8 +938,10 @@ Synopsis:
 
 =item B<Device> I<device>
 
-Device name of the I2C bus to which the sensor is connected. Note that typically
-you need to have loaded the i2c-dev module.
+The only mandatory configuration parameter.
+
+Device name of the I2C bus to which the sensor is connected. Note that
+typically you need to have loaded the i2c-dev module.
 Using i2c-tools you can check/list i2c buses available on your system by:
 
   i2cdetect -l
@@ -922,52 +955,69 @@ connected and detected on address 0x60.
 
 =item B<Oversampling> I<value>
 
-For MPL115 this is the size of the averaging window. To filter out sensor noise
-a simple averaging using floating window of configurable size is used. The plugin
-will use average of the last C<value> measurements (value of 1 means no averaging).
-Minimal size is 1, maximal 1024.
+Optional parameter controlling the oversampling/accuracy. Default value
+is 1 providing fastest and least accurate reading.
+
+For I<MPL115> this is the size of the averaging window. To filter out sensor
+noise a simple averaging using floating window of this configurable size is
+used. The plugin will use average of the last C<value> measurements (value of 1
+means no averaging).  Minimal size is 1, maximal 1024.
+
+For I<MPL3115> this is the oversampling value. The actual oversampling is
+performed by the sensor and the higher value the higher accuracy and longer
+conversion time (although nothing to worry about in the collectd context).
+Supported values are: 1, 2, 4, 8, 16, 32, 64 and 128. Any other value is
+adjusted by the plugin to the closest supported one.
 
-For MPL3115 this is the oversampling value. The actual oversampling is performed
-by the sensor and the higher value the higher accuracy and longer conversion time
-(although nothing to worry about in the collectd context). Supported values are:
-1, 2, 4, 8, 16, 32, 64 and 128. Any other value is adjusted by the plugin to
-the closest supported one. Default is 128.
+For I<BMP085> this is the oversampling value. The actual oversampling is
+performed by the sensor and the higher value the higher accuracy and longer
+conversion time (although nothing to worry about in the collectd context).
+Supported values are: 1, 2, 4, 8. Any other value is adjusted by the plugin to
+the closest supported one.
 
 =item B<PressureOffset> I<offset>
 
-You can further calibrate the sensor by supplying pressure and/or temperature offsets.
-This is added to the measured/caclulated value (i.e. if the measured value is too high
-then use negative offset).
+Optional parameter for MPL3115 only.
+
+You can further calibrate the sensor by supplying pressure and/or temperature
+offsets.  This is added to the measured/caclulated value (i.e. if the measured
+value is too high then use negative offset).
 In hPa, default is 0.0.
 
 =item B<TemperatureOffset> I<offset>
 
-You can further calibrate the sensor by supplying pressure and/or temperature offsets.
-This is added to the measured/caclulated value (i.e. if the measured value is too high
-then use negative offset).
+Optional parameter for MPL3115 only.
+
+You can further calibrate the sensor by supplying pressure and/or temperature
+offsets.  This is added to the measured/caclulated value (i.e. if the measured
+value is too high then use negative offset).
 In C, default is 0.0.
 
 =item B<Normalization> I<method>
 
-Normalization method - what approximation/model is used to compute mean sea
+Optional parameter, default value is 0.
+
+Normalization method - what approximation/model is used to compute the mean sea
 level pressure from the air absolute pressure.
 
 Supported values of the C<method> (integer between from 0 to 2) are:
 
 =over 5
 
-=item B<0> - no conversion, absolute pressrure is simply copied over. For this method you
+=item B<0> - no conversion, absolute pressure is simply copied over. For this method you
        do not need to configure C<Altitude> or C<TemperatureSensor>.
 
 =item B<1> - international formula for conversion ,
-See I<http://en.wikipedia.org/wiki/Atmospheric_pressure#Altitude_atmospheric_pressure_variation>.
-For this method you have to configure C<Altitude> but do not need C<TemperatureSensor>
-(uses fixed global temperature average instead).
+See
+L<http://en.wikipedia.org/wiki/Atmospheric_pressure#Altitude_atmospheric_pressure_variation>.
+For this method you have to configure C<Altitude> but do not need
+C<TemperatureSensor> (uses fixed global temperature average instead).
 
 =item B<2> - formula as recommended by the Deutsche Wetterdienst (German
 Meteorological Service).
-See I<http://de.wikipedia.org/wiki/Barometrische_H%C3%B6henformel#Theorie>
-For this method you have to configure both  C<Altitude> and C<TemperatureSensor>.
+See L<http://de.wikipedia.org/wiki/Barometrische_H%C3%B6henformel#Theorie>
+For this method you have to configure both  C<Altitude> and
+C<TemperatureSensor>.
 
 =back
 
@@ -978,15 +1028,15 @@ The altitude (in meters) of the location where you meassure the pressure.
 
 =item B<TemperatureSensor> I<reference>
 
-Temperature sensor which should be used as a reference when normalizing the pressure.
-When specified more sensors a minumum is found and uses each time.
-The temperature reading directly from this pressure sensor/plugin
-is typically not suitable as the pressure sensor
-will be probably inside while we want outside temperature.
-The collectd reference name is something like
+Temperature sensor(s) which should be used as a reference when normalizing the
+pressure using C<Normalization> method 2.
+When specified more sensors a minumum is found and used each time.  The
+temperature reading directly from this pressure sensor/plugin is typically not
+suitable as the pressure sensor will be probably inside while we want outside
+temperature.  The collectd reference name is something like
 <hostname>/<plugin_name>-<plugin_instance>/<type>-<type_instance>
-(<type_instance> is usually omitted when there is just single value type).
-Or you can figure it out from the path of the output data files.
+(<type_instance> is usually omitted when there is just single value type). Or
+you can figure it out from the path of the output data files.
 
 =back
 
@@ -1127,6 +1177,12 @@ Collect global memory statistics.
 
 Default: Enabled.
 
+=item B<Timeout> I<Milliseconds>
+
+The B<Timeout> option sets the overall timeout for HTTP requests to B<URL>, in
+milliseconds. By default, the configured B<Interval> is used to set the
+timeout.
+
 =item B<View> I<Name>
 
 Collect statistics about a specific I<"view">. BIND can behave different,
@@ -1455,6 +1511,10 @@ C<application/x-www-form-urlencoded>).
 Measure response time for the request. If this setting is enabled, B<Match>
 blocks (see below) are optional. Disabled by default.
 
+Beware that requests will get aborted if they take too long to complete. Adjust
+B<Timeout> accordingly if you expect B<MeasureResponseTime> to report such slow
+requests.
+
 =item B<MeasureResponseCode> B<true>|B<false>
 
 Measure response code for the request. If this setting is enabled, B<Match>
@@ -1469,6 +1529,18 @@ plugin below on how matches are defined. If the B<MeasureResponseTime> or
 B<MeasureResponseCode> options are set to B<true>, B<Match> blocks are
 optional.
 
+=item B<Timeout> I<Milliseconds>
+
+The B<Timeout> option sets the overall timeout for HTTP requests to B<URL>, in
+milliseconds. By default, the configured B<Interval> is used to set the
+timeout. Prior to version 5.5.0, there was no timeout and requests could hang
+indefinitely. This legacy behaviour can be achieved by setting the value of
+B<Timeout> to 0.
+
+If B<Timeout> is 0 or bigger than the B<Interval>, keep in mind that each slow
+network connection will stall one read thread. Adjust the B<ReadThreads> global
+setting accordingly to prevent this from blocking other plugins.
+
 =back
 
 =head2 Plugin C<curl_json>
@@ -1555,6 +1627,8 @@ URL. By default the global B<Interval> setting will be used.
 
 =item B<Post> I<Body>
 
+=item B<Timeout> I<Milliseconds>
+
 These options behave exactly equivalent to the appropriate options of the
 I<cURL> plugin. Please see there for a detailed description.
 
@@ -1655,6 +1729,8 @@ Examples:
 
 =item B<Post> I<Body>
 
+=item B<Timeout> I<Milliseconds>
+
 These options behave exactly equivalent to the appropriate options of the
 I<cURL plugin>. Please see there for a detailed description.
 
@@ -4082,6 +4158,12 @@ 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<Timeout> I<Milliseconds>
+
+The B<Timeout> option sets the overall timeout for HTTP requests to B<URL>, in
+milliseconds. By default, the configured B<Interval> is used to set the
+timeout.
+
 =back
 
 =head2 Plugin C<notify_desktop>
@@ -5050,6 +5132,13 @@ activating this option. The draw-back is, that data covering the specified
 amount of time will be lost, for example, if a single statement within the
 transaction fails or if the database server crashes.
 
+=item B<Instance> I<name>
+
+Specify the plugin instance name that should be used instead of the database
+name (which is the default, if this option has not been specified). This
+allows to query multiple databases of the same name on the same host (e.g.
+when running multiple database server versions in parallel).
+
 =item B<Host> I<hostname>
 
 Specify the hostname or IP of the PostgreSQL server to connect to. If the
@@ -5484,7 +5573,7 @@ that numerical port numbers must be given as a string, too.
 
 Use I<Password> to authenticate when connecting to I<Redis>.
 
-=item B<Timeout> I<Timeout in miliseconds>
+=item B<Timeout> I<Milliseconds>
 
 The B<Timeout> option set the socket timeout for node response. Since the Redis
 read function is blocking, you should keep this value as low as possible. Keep
@@ -7012,7 +7101,7 @@ Hostname or address to connect to. Defaults to C<localhost>.
 
 Service name or port number to connect to. Defaults to C<27017>.
 
-=item B<Timeout> I<Timeout>
+=item B<Timeout> I<Milliseconds>
 
 Set the timeout for each operation on I<MongoDB> to I<Timeout> milliseconds.
 Setting this option to zero means no timeout, which is the default.
@@ -7138,6 +7227,26 @@ are available on the server side. I<Bytes> must be at least 1024 and cannot
 exceed the size of an C<int>, i.e. 2E<nbsp>GByte.
 Defaults to C<4096>.
 
+=item B<LowSpeedLimit> I<Bytes per Second>
+
+Sets the minimal transfer rate in I<Bytes per Second> below which the
+connection with the HTTP server will be considered too slow and aborted. All
+the data submitted over this connection will probably be lost. Defaults to 0,
+which means no minimum transfer rate is enforced.
+
+=item B<Timeout> I<Timeout>
+
+Sets the maximum time in milliseconds given for HTTP POST operations to
+complete. When this limit is reached, the POST operation will be aborted, and
+all the data in the current send buffer will probably be lost. Defaults to 0,
+which means the connection never times out.
+
+The C<write_http> plugin regularly submits the collected values to the HTTP
+server. How frequently this happens depends on how much data you are collecting
+and the size of B<BufferSize>. The optimal value to set B<Timeout> to is
+slightly below this interval, which you can estimate by monitoring the network
+traffic between collectd and the HTTP server.
+
 =back
 
 =head2 Plugin C<write_kafka>
@@ -7296,7 +7405,7 @@ The B<Port> option is the TCP port on which the Redis instance accepts
 connections. Either a service name of a port number may be given. Please note
 that numerical port numbers must be given as a string, too.
 
-=item B<Timeout> I<Timeout in miliseconds>
+=item B<Timeout> I<Milliseconds>
 
 The B<Timeout> option sets the socket connection timeout, in milliseconds.
 
@@ -7424,6 +7533,116 @@ attribute for each metric being sent out to I<Riemann>.
 
 =back
 
+=head2 Plugin C<write_sensu>
+
+The I<write_sensu plugin> will send values to I<Sensu>, a powerful stream
+aggregation and monitoring system. The plugin sends I<JSON> encoded data to
+a local I<Sensu> client using a TCP socket.
+
+At the moment, the I<write_sensu plugin> does not send over a collectd_host
+parameter so it is not possible to use one collectd instance as a gateway for
+others. Each collectd host must pair with one I<Sensu> client.
+
+Synopsis:
+
+ <Plugin "write_sensu">
+   <Node "example">
+     Host "localhost"
+     Port "3030"
+     StoreRates true
+     AlwaysAppendDS false
+     MetricHandler "influx"
+     MetricHandler "default"
+     NotificationHandler "flapjack"
+     NotificationHandler "howling_monkey"
+     Notifications true
+   </Node>
+   Tag "foobar"
+   Attribute "foo" "bar"
+ </Plugin>
+
+The following options are understood by the I<write_sensu 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<Sensu>. Inside 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<3030>.
+
+=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<collectd_data_source_type> tag: If
+B<StoreRates> is enabled, converted values will have "rate" appended to the
+data source type, e.g.  C<collectd_data_source_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<Sensu>. If set to B<false> (the default), this is
+only done when there is more than one DS.
+
+=item B<Notifications> B<false>|B<true>
+
+If set to B<true>, create I<Sensu> events for notifications. This is B<false>
+by default. At least one of B<Notifications> or B<Metrics> should be enabled.
+
+=item B<Metrics> B<false>|B<true>
+
+If set to B<true>, create I<Sensu> events for metrics. This is B<false>
+by default. At least one of B<Notifications> or B<Metrics> should be enabled.
+
+
+=item B<Separator> I<String>
+
+Sets the separator for I<Sensu> metrics name or checks. Defaults to "/".
+
+=item B<MetricHandler> I<String>
+
+Add a handler that will be set when metrics are sent to I<Sensu>. You can add
+several of them, one per line. Defaults to no handler.
+
+=item B<NotificationHandler> I<String>
+
+Add a handler that will be set when notifications are sent to I<Sensu>. You can
+add several of them, one per line. Defaults to no handler.
+
+=item B<EventServicePrefix> I<String>
+
+Add the given string as a prefix to the event service name.
+If B<EventServicePrefix> not set or set to an empty string (""),
+no prefix will be used.
+
+=back
+
+=item B<Tag> I<String>
+
+Add the given string as an additional tag to the metric being sent to
+I<Sensu>.
+
+=item B<Attribute> I<String> I<String>
+
+Consider the two given strings to be the key and value of an additional
+attribute for each metric being sent out to I<Sensu>.
+
+=back
+
 =head2 Plugin C<zookeeper>
 
 The I<zookeeper plugin> will collect statistics from a I<Zookeeper> server
index 0e5d2cf..b750f80 100644 (file)
@@ -66,6 +66,7 @@ struct web_page_s /* {{{ */
   char *post_body;
   _Bool response_time;
   _Bool response_code;
+  int timeout;
 
   CURL *curl;
   char curl_errbuf[CURL_ERROR_SIZE];
@@ -410,6 +411,14 @@ static int cc_page_init_curl (web_page_t *wp) /* {{{ */
   if (wp->post_body != NULL)
     curl_easy_setopt (wp->curl, CURLOPT_POSTFIELDS, wp->post_body);
 
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+  if (wp->timeout >= 0)
+    curl_easy_setopt (wp->curl, CURLOPT_TIMEOUT_MS, (long) wp->timeout);
+  else
+    curl_easy_setopt (wp->curl, CURLOPT_TIMEOUT_MS,
+       CDTIME_T_TO_MS(plugin_get_interval()));
+#endif
+
   return (0);
 } /* }}} int cc_page_init_curl */
 
@@ -440,6 +449,7 @@ static int cc_config_add_page (oconfig_item_t *ci) /* {{{ */
   page->verify_host = 1;
   page->response_time = 0;
   page->response_code = 0;
+  page->timeout = -1;
 
   page->instance = strdup (ci->values[0].value.string);
   if (page->instance == NULL)
@@ -480,6 +490,8 @@ static int cc_config_add_page (oconfig_item_t *ci) /* {{{ */
       status = cc_config_append_string ("Header", &page->headers, child);
     else if (strcasecmp ("Post", child->key) == 0)
       status = cf_util_get_string (child, &page->post_body);
+    else if (strcasecmp ("Timeout", child->key) == 0)
+      status = cf_util_get_int (child, &page->timeout);
     else
     {
       WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
@@ -653,7 +665,7 @@ static int cc_read_page (web_page_t *wp) /* {{{ */
   status = curl_easy_perform (wp->curl);
   if (status != CURLE_OK)
   {
-    ERROR ("curl plugin: curl_easy_perform failed with staus %i: %s",
+    ERROR ("curl plugin: curl_easy_perform failed with status %i: %s",
         status, wp->curl_errbuf);
     return (-1);
   }
@@ -666,7 +678,7 @@ static int cc_read_page (web_page_t *wp) /* {{{ */
     long response_code = 0;
     status = curl_easy_getinfo(wp->curl, CURLINFO_RESPONSE_CODE, &response_code);
     if(status != CURLE_OK) {
-      ERROR ("curl plugin: Fetching response code failed with staus %i: %s",
+      ERROR ("curl plugin: Fetching response code failed with status %i: %s",
         status, wp->curl_errbuf);
     } else {
       cc_submit_response_code(wp, response_code);
index 09db786..3a5a3ab 100644 (file)
@@ -78,6 +78,7 @@ struct cj_s /* {{{ */
   struct curl_slist *headers;
   char *post_body;
   cdtime_t interval;
+  int timeout;
 
   CURL *curl;
   char curl_errbuf[CURL_ERROR_SIZE];
@@ -650,6 +651,17 @@ static int cj_init_curl (cj_t *db) /* {{{ */
   if (db->post_body != NULL)
     curl_easy_setopt (db->curl, CURLOPT_POSTFIELDS, db->post_body);
 
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+  if (db->timeout >= 0)
+    curl_easy_setopt (db->curl, CURLOPT_TIMEOUT_MS, (long) db->timeout);
+  else if (db->interval > 0)
+    curl_easy_setopt (db->curl, CURLOPT_TIMEOUT_MS,
+        CDTIME_T_TO_MS(db->timeout));
+  else
+    curl_easy_setopt (db->curl, CURLOPT_TIMEOUT_MS,
+        CDTIME_T_TO_MS(plugin_get_interval()));
+#endif
+
   return (0);
 } /* }}} int cj_init_curl */
 
@@ -675,6 +687,8 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
   }
   memset (db, 0, sizeof (*db));
 
+  db->timeout = -1;
+
   if (strcasecmp ("URL", ci->key) == 0)
     status = cf_util_get_string (ci, &db->url);
   else if (strcasecmp ("Sock", ci->key) == 0)
@@ -720,6 +734,8 @@ static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
       status = cj_config_add_key (db, child);
     else if (strcasecmp ("Interval", child->key) == 0)
       status = cf_util_get_cdtime(child, &db->interval);
+    else if (strcasecmp ("Timeout", child->key) == 0)
+      status = cf_util_get_int (child, &db->timeout);
     else
     {
       WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
index c9f0651..9049d99 100644 (file)
@@ -81,6 +81,7 @@ struct cx_s /* {{{ */
   _Bool verify_host;
   char *cacert;
   char *post_body;
+  int timeout;
   struct curl_slist *headers;
 
   cx_namespace_t *namespaces;
@@ -884,6 +885,14 @@ static int cx_init_curl (cx_t *db) /* {{{ */
   if (db->post_body != NULL)
     curl_easy_setopt (db->curl, CURLOPT_POSTFIELDS, db->post_body);
 
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+  if (db->timeout >= 0)
+    curl_easy_setopt (db->curl, CURLOPT_TIMEOUT_MS, (long) db->timeout);
+  else
+    curl_easy_setopt (db->curl, CURLOPT_TIMEOUT_MS,
+       CDTIME_T_TO_MS(plugin_get_interval()));
+#endif
+
   return (0);
 } /* }}} int cx_init_curl */
 
@@ -909,6 +918,8 @@ static int cx_config_add_url (oconfig_item_t *ci) /* {{{ */
   }
   memset (db, 0, sizeof (*db));
 
+  db->timeout = -1;
+
   if (strcasecmp ("URL", ci->key) == 0)
   {
     status = cf_util_get_string (ci, &db->url);
@@ -954,6 +965,8 @@ static int cx_config_add_url (oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string (child, &db->post_body);
     else if (strcasecmp ("Namespace", child->key) == 0)
       status = cx_config_add_namespace (db, child);
+    else if (strcasecmp ("Timeout", child->key) == 0)
+      status = cf_util_get_int (child, &db->timeout);
     else
     {
       WARNING ("curl_xml plugin: Option `%s' not allowed here.", child->key);
diff --git a/src/ipc.c b/src/ipc.c
new file mode 100644 (file)
index 0000000..2d2db2a
--- /dev/null
+++ b/src/ipc.c
@@ -0,0 +1,313 @@
+/**
+ * collectd - src/ipc.c, based on src/memcached.c
+ * Copyright (C) 2010       Andres J. Diaz <ajdiaz@connectical.com>
+ * Copyright (C) 2010       Manuel L. Sanmartin <manuel.luis@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Andres J. Diaz <ajdiaz@connectical.com>
+ *   Manuel L. Sanmartin <manuel.luis@gmail>
+ **/
+
+/* Many of this code is based on busybox ipc implementation, which is:
+ *   (C) Rodney Radford <rradford@mindspring.com> and distributed under GPLv2.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#if KERNEL_LINUX
+  /* X/OPEN tells us to use <sys/{types,ipc,sem}.h> for semctl() */
+  /* X/OPEN tells us to use <sys/{types,ipc,msg}.h> for msgctl() */
+  /* X/OPEN tells us to use <sys/{types,ipc,shm}.h> for shmctl() */
+# include <sys/types.h>
+# include <sys/ipc.h>
+# include <sys/sem.h>
+# include <sys/msg.h>
+# include <sys/shm.h>
+
+  /* For older kernels the same holds for the defines below */
+# ifndef MSG_STAT
+#  define MSG_STAT    11
+#  define MSG_INFO    12
+# endif
+
+# ifndef SHM_STAT
+#   define SHM_STAT        13
+#   define SHM_INFO        14
+    struct shm_info {
+        int used_ids;
+        ulong shm_tot;      /* total allocated shm */
+        ulong shm_rss;      /* total resident shm */
+        ulong shm_swp;      /* total swapped shm */
+        ulong swap_attempts;
+        ulong swap_successes;
+    };
+# endif
+
+# ifndef SEM_STAT
+#  define SEM_STAT    18
+#  define SEM_INFO    19
+# endif
+
+  /* The last arg of semctl is a union semun, but where is it defined?
+     X/OPEN tells us to define it ourselves, but until recently
+     Linux include files would also define it. */
+# if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
+    /* union semun is defined by including <sys/sem.h> */
+# else
+    /* according to X/OPEN we have to define it ourselves */
+    union semun {
+      int val;
+      struct semid_ds *buf;
+      unsigned short *array;
+      struct seminfo *__buf;
+    };
+# endif
+static long pagesize_g;
+/* #endif  KERNEL_LINUX */
+#elif KERNEL_AIX
+# include <sys/ipc_info.h>
+/* #endif KERNEL_AIX */
+#else
+# error "No applicable input method."
+#endif
+
+__attribute__ ((nonnull(1)))
+static void ipc_submit_g (const char *plugin_instance,
+                          const char *type,
+                          const char *type_instance,
+                          gauge_t value) /* {{{ */
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  values[0].gauge = value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "ipc", sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+  sstrncpy (vl.type, type, sizeof (vl.type));
+  if (type_instance != NULL)
+    sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* }}} */
+
+#if KERNEL_AIX
+static caddr_t ipc_get_info (cid_t cid, int cmd, int version, int stsize, int *nmemb) /* {{{ */
+{
+  int size = 0;
+  caddr_t buff = NULL;
+
+  if (get_ipc_info(cid, cmd, version, buff, &size) < 0)
+  {
+    if (errno != ENOSPC) {
+      char errbuf[1024];
+      WARNING ("ipc plugin: get_ipc_info: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+      return (NULL);
+    }
+  }
+
+  if (size == 0)
+    return NULL;
+
+  if (size % stsize) {
+    ERROR ("ipc plugin: ipc_get_info: missmatch struct size and buffer size");
+    return (NULL);
+  }
+
+  *nmemb = size / stsize;
+
+  buff = (caddr_t)malloc (size);
+  if (buff == NULL)  {
+    ERROR ("ipc plugin: ipc_get_info malloc failed.");
+    return (NULL);
+  }
+
+  if (get_ipc_info(cid, cmd, version, buff, &size) < 0)
+  {
+    char errbuf[1024];
+    WARNING ("ipc plugin: get_ipc_info: %s",
+      sstrerror (errno, errbuf, sizeof (errbuf)));
+    free(buff);
+    return (NULL);
+  }
+
+  return buff;
+} /* }}} */
+#endif /* KERNEL_AIX */
+
+static int ipc_read_sem (void) /* {{{ */
+{
+#if KERNEL_LINUX
+  struct seminfo seminfo;
+  union semun arg;
+
+  arg.array = (ushort *) (void *) &seminfo;
+
+  if ( semctl(0, 0, SEM_INFO, arg) < 0 )
+  {
+    ERROR("Kernel is not configured for semaphores");
+    return (-1);
+  }
+
+  ipc_submit_g("sem", "count", "arrays", seminfo.semusz);
+  ipc_submit_g("sem", "count", "total", seminfo.semaem);
+
+/* #endif KERNEL_LINUX */
+#elif KERNEL_AIX
+  ipcinfo_sem_t *ipcinfo_sem;
+  unsigned short sem_nsems=0;
+  unsigned short sems=0;
+  int i,n;
+
+  ipcinfo_sem = (ipcinfo_sem_t *)ipc_get_info(0,
+    GET_IPCINFO_SEM_ALL, IPCINFO_SEM_VERSION, sizeof(ipcinfo_sem_t), &n);
+  if (ipcinfo_sem == NULL)
+    return -1;
+
+  for (i=0; i<n; i++) {
+    sem_nsems += ipcinfo_sem[i].sem_nsems;
+    sems++;
+  }
+  free(ipcinfo_sem);
+
+  ipc_submit_g("sem", "count", "arrays", sem_nsems);
+  ipc_submit_g("sem", "count", "total", sems);
+#endif /* KERNEL_AIX */
+
+  return (0);
+}
+/* }}} */
+
+static int ipc_read_shm (void) /* {{{ */
+{
+#if KERNEL_LINUX
+  struct shm_info shm_info;
+
+  if ( shmctl(0, SHM_INFO, (struct shmid_ds *) (void *) &shm_info) < 0 )
+  {
+    ERROR("Kernel is not configured for shared memory");
+    return (-1);
+  }
+  ipc_submit_g("shm", "segments", NULL, shm_info.used_ids);
+  ipc_submit_g("shm", "bytes", "total", shm_info.shm_tot * pagesize_g);
+  ipc_submit_g("shm", "bytes", "rss", shm_info.shm_rss * pagesize_g);
+  ipc_submit_g("shm", "bytes", "swapped", shm_info.shm_swp * pagesize_g);
+/* #endif KERNEL_LINUX */
+#elif KERNEL_AIX
+  ipcinfo_shm_t *ipcinfo_shm;
+  ipcinfo_shm_t *pshm;
+  unsigned int shm_segments=0;
+  size64_t shm_bytes=0;
+  int i,n;
+
+  ipcinfo_shm = (ipcinfo_shm_t *)ipc_get_info(0,
+    GET_IPCINFO_SHM_ALL, IPCINFO_SHM_VERSION, sizeof(ipcinfo_shm_t), &n);
+  if (ipcinfo_shm == NULL)
+    return -1;
+
+  for (i=0, pshm=ipcinfo_shm; i<n; i++, pshm++) {
+    shm_segments++;
+    shm_bytes += pshm->shm_segsz;
+  }
+  free(ipcinfo_shm);
+
+  ipc_submit_g("shm", "segments", NULL, shm_segments);
+  ipc_submit_g("shm", "bytes", "total", shm_bytes);
+
+#endif /* KERNEL_AIX */
+  return (0);
+}
+/* }}} */
+
+static int ipc_read_msg (void) /* {{{ */
+{
+#if KERNEL_LINUX
+  struct msginfo msginfo;
+
+  if ( msgctl(0, MSG_INFO, (struct msqid_ds *) (void *) &msginfo) < 0 )
+  {
+    ERROR("Kernel is not configured for message queues");
+    return (-1);
+  }
+  ipc_submit_g("msg", "count", "queues", msginfo.msgmni);
+  ipc_submit_g("msg", "count", "headers", msginfo.msgmap);
+  ipc_submit_g("msg", "count", "space", msginfo.msgtql);
+/* #endif KERNEL_LINUX */
+#elif KERNEL_AIX
+  ipcinfo_msg_t *ipcinfo_msg;
+  uint32_t msg_used_space=0;
+  uint32_t msg_alloc_queues=0;
+  msgqnum32_t msg_qnum=0;
+  int i,n;
+
+  ipcinfo_msg = (ipcinfo_msg_t *)ipc_get_info(0,
+    GET_IPCINFO_MSG_ALL, IPCINFO_MSG_VERSION, sizeof(ipcinfo_msg_t), &n);
+  if (ipcinfo_msg == NULL)
+    return -1;
+
+  for (i=0; i<n; i++) {
+    msg_alloc_queues++;
+    msg_used_space += ipcinfo_msg[i].msg_cbytes;
+    msg_qnum += ipcinfo_msg[i].msg_qnum;
+  }
+  free(ipcinfo_msg);
+
+  ipc_submit_g("msg", "count", "queues", msg_alloc_queues);
+  ipc_submit_g("msg", "count", "headers", msg_qnum);
+  ipc_submit_g("msg", "count", "space", msg_used_space);
+#endif /* KERNEL_AIX */
+  return (0);
+}
+/* }}} */
+
+static int ipc_read (void) /* {{{ */
+{
+  int x = 0;
+  x |= ipc_read_shm();
+  x |= ipc_read_sem();
+  x |= ipc_read_msg();
+
+  return (x);
+}
+/* }}} */
+
+#ifdef KERNEL_LINUX
+static int ipc_init (void) /* {{{ */
+{
+  pagesize_g = sysconf(_SC_PAGESIZE);
+  return (0);
+}
+/* }}} */
+#endif /* KERNEL_LINUX */
+
+void module_register (void) /* {{{ */
+{
+#ifdef KERNEL_LINUX
+  plugin_register_init ("ipc", ipc_init);
+#endif
+  plugin_register_read ("ipc", ipc_read);
+}
+/* }}} */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
index b0daa05..4e4ce3b 100644 (file)
@@ -39,6 +39,7 @@ static char *pass        = NULL;
 static char *verify_peer = NULL;
 static char *verify_host = NULL;
 static char *cacert      = NULL;
+static char *timeout     = NULL;
 
 static CURL *curl = NULL;
 
@@ -53,7 +54,8 @@ static const char *config_keys[] =
   "Password",
   "VerifyPeer",
   "VerifyHost",
-  "CACert"
+  "CACert",
+  "Timeout"
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
@@ -107,6 +109,8 @@ static int config (const char *key, const char *value)
     return (config_set (&verify_host, value));
   else if (strcasecmp (key, "cacert") == 0)
     return (config_set (&cacert, value));
+  else if (strcasecmp (key, "timeout") == 0)
+    return (config_set (&timeout, value));
   else
     return (-1);
 } /* int config */
@@ -177,6 +181,18 @@ static int init (void)
     curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
   }
 
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+  if (timeout != NULL)
+  {
+    curl_easy_setopt (curl, CURLOPT_TIMEOUT_MS, atol(timeout));
+  }
+  else
+  {
+    curl_easy_setopt (curl, CURLOPT_TIMEOUT_MS,
+       CDTIME_T_TO_MS(plugin_get_interval()));
+  }
+#endif
+
   return (0);
 } /* void init */
 
index 151e09c..ec337bf 100644 (file)
@@ -84,10 +84,10 @@ typedef struct statname_lookup_s statname_lookup_t;
 
 /* Description of statistics returned by the recursor: {{{
 all-outqueries      counts the number of outgoing UDP queries since starting
-answers0-1          counts the number of queries answered within 1 milisecond
+answers0-1          counts the number of queries answered within 1 millisecond
 answers100-1000     counts the number of queries answered within 1 second
-answers10-100       counts the number of queries answered within 100 miliseconds
-answers1-10         counts the number of queries answered within 10 miliseconds
+answers10-100       counts the number of queries answered within 100 milliseconds
+answers1-10         counts the number of queries answered within 10 milliseconds
 answers-slow        counts the number of queries answered after 1 second
 cache-entries       shows the number of entries in the cache
 cache-hits          counts the number of cache hits since starting
index be1df7f..5474659 100644 (file)
@@ -179,6 +179,7 @@ response_code               value:GAUGE:0:U
 route_etx              value:GAUGE:0:U
 route_metric           value:GAUGE:0:U
 routes                 value:GAUGE:0:U
+segments               value:GAUGE:0:65535
 serial_octets          rx:DERIVE:0:U, tx:DERIVE:0:U
 signal_noise           value:GAUGE:U:0
 signal_power           value:GAUGE:U:0
index 8d3b85b..ed596bb 100644 (file)
@@ -59,6 +59,9 @@ struct wh_callback_s
         char *clientkeypass;
         long sslversion;
         _Bool store_rates;
+        int   low_speed_limit;
+        time_t low_speed_time;
+        int timeout;
 
 #define WH_FORMAT_COMMAND 0
 #define WH_FORMAT_JSON    1
@@ -121,6 +124,19 @@ static int wh_callback_init (wh_callback_t *cb) /* {{{ */
                 return (-1);
         }
 
+        if (cb->low_speed_limit > 0 && cb->low_speed_time > 0)
+        {
+                curl_easy_setopt (cb->curl, CURLOPT_LOW_SPEED_LIMIT,
+                                  (long) (cb->low_speed_limit * cb->low_speed_time));
+                curl_easy_setopt (cb->curl, CURLOPT_LOW_SPEED_TIME,
+                                  (long) cb->low_speed_time);
+        }
+
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+        if (cb->timeout > 0)
+                curl_easy_setopt (cb->curl, CURLOPT_TIMEOUT_MS, (long) cb->timeout);
+#endif
+
         curl_easy_setopt (cb->curl, CURLOPT_NOSIGNAL, 1L);
         curl_easy_setopt (cb->curl, CURLOPT_USERAGENT, COLLECTD_USERAGENT);
 
@@ -520,6 +536,8 @@ static int wh_config_node (oconfig_item_t *ci) /* {{{ */
         cb->verify_host = 1;
         cb->format = WH_FORMAT_COMMAND;
         cb->sslversion = CURL_SSLVERSION_DEFAULT;
+        cb->low_speed_limit = 0;
+        cb->timeout = 0;
 
         pthread_mutex_init (&cb->send_lock, /* attr = */ NULL);
 
@@ -587,6 +605,10 @@ static int wh_config_node (oconfig_item_t *ci) /* {{{ */
                         cf_util_get_boolean (child, &cb->store_rates);
                 else if (strcasecmp ("BufferSize", child->key) == 0)
                         cf_util_get_int (child, &buffer_size);
+                else if (strcasecmp ("LowSpeedLimit", child->key) == 0)
+                        cf_util_get_int (child, &cb->low_speed_limit);
+                else if (strcasecmp ("Timeout", child->key) == 0)
+                        cf_util_get_int (child, &cb->timeout);
                 else
                 {
                         ERROR ("write_http plugin: Invalid configuration "
@@ -602,6 +624,9 @@ static int wh_config_node (oconfig_item_t *ci) /* {{{ */
                 return (-1);
         }
 
+        if (cb->low_speed_limit > 0)
+                cb->low_speed_time = CDTIME_T_TO_TIME_T(plugin_get_interval());
+
         /* Determine send_buffer_size. */
         cb->send_buffer_size = WRITE_HTTP_DEFAULT_BUFFER_SIZE;
         if (buffer_size >= 1024)
diff --git a/src/write_sensu.c b/src/write_sensu.c
new file mode 100644 (file)
index 0000000..3f146f3
--- /dev/null
@@ -0,0 +1,1232 @@
+/**
+ * collectd - src/write_sensu.c
+ * Copyright (C) 2015 Fabrice A. Marie
+ *
+ * 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:
+ *   Fabrice A. Marie <fabrice at kibinlabs.com>
+ */
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "configfile.h"
+#include "utils_cache.h"
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stddef.h>
+
+#include <stdlib.h>
+#ifndef HAVE_ASPRINTF
+/*
+ * Uses asprintf() portable implementation from
+ * https://github.com/littlstar/asprintf.c/blob/master/
+ * copyright (c) 2014 joseph werle <joseph.werle@gmail.com> under MIT license.
+ */
+#include <stdio.h>
+#include <stdarg.h>
+
+int vasprintf(char **str, const char *fmt, va_list args) {
+       int size = 0;
+       va_list tmpa;
+       // copy
+       va_copy(tmpa, args);
+       // apply variadic arguments to
+       // sprintf with format to get size
+       size = vsnprintf(NULL, size, fmt, tmpa);
+       // toss args
+       va_end(tmpa);
+       // return -1 to be compliant if
+       // size is less than 0
+       if (size < 0) { return -1; }
+       // alloc with size plus 1 for `\0'
+       *str = (char *) malloc(size + 1);
+       // return -1 to be compliant
+       // if pointer is `NULL'
+       if (NULL == *str) { return -1; }
+       // format string with original
+       // variadic arguments and set new size
+       size = vsprintf(*str, fmt, args);
+       return size;
+}
+
+int asprintf(char **str, const char *fmt, ...) {
+       int size = 0;
+       va_list args;
+       // init variadic argumens
+       va_start(args, fmt);
+       // format and get size
+       size = vasprintf(str, fmt, args);
+       // toss args
+       va_end(args);
+       return size;
+}
+
+#endif
+
+#define SENSU_HOST             "localhost"
+#define SENSU_PORT             "3030"
+
+struct str_list {
+       int nb_strs;
+       char **strs;
+};
+
+struct sensu_host {
+       char                    *name;
+       char                    *event_service_prefix;
+       struct str_list metric_handlers;
+       struct str_list notification_handlers;
+#define F_READY      0x01
+       uint8_t                  flags;
+       pthread_mutex_t  lock;
+       _Bool            notifications;
+       _Bool            metrics;
+       _Bool                    store_rates;
+       _Bool                    always_append_ds;
+       char                    *separator;
+       char                    *node;
+       char                    *service;
+       int              s;
+       struct addrinfo *res;
+       int                          reference_count;
+};
+
+static char    *sensu_tags;
+static char    **sensu_attrs;
+static size_t sensu_attrs_num;
+static const char *alloc_err ="write_sensu plugin: Unable to alloc memory";
+
+static int add_str_to_list(struct str_list *strs,
+               const char *str_to_add) /* {{{ */
+{
+       char **old_strs_ptr = strs->strs;
+       char *newstr = strdup(str_to_add);
+       if (newstr == NULL) {
+               ERROR(alloc_err);
+               return -1;
+       }
+       strs->strs = realloc(strs->strs, sizeof(char *) *(strs->nb_strs + 1));
+       if (strs->strs == NULL) {
+               strs->strs = old_strs_ptr;
+               free(newstr);
+               ERROR(alloc_err);
+               return -1;
+       }
+       strs->strs[strs->nb_strs] = newstr;
+       strs->nb_strs++;
+       return 0;
+}
+/* }}} int add_str_to_list */
+
+static void free_str_list(struct str_list *strs) /* {{{ */
+{
+       int i;
+       for (i=0; i<strs->nb_strs; i++)
+               free(strs->strs[i]);
+       free(strs->strs);
+}
+/* }}} void free_str_list */
+
+static int sensu_connect(struct sensu_host *host) /* {{{ */
+{
+       int                      e;
+       struct addrinfo         *ai, hints;
+       char const              *node;
+       char const              *service;
+
+       // Resolve the target if we haven't done already
+       if (!(host->flags & F_READY)) {
+               memset(&hints, 0, sizeof(hints));
+               memset(&service, 0, sizeof(service));
+               host->res = NULL;
+               hints.ai_family = AF_INET;
+               hints.ai_socktype = SOCK_STREAM;
+#ifdef AI_ADDRCONFIG
+               hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+
+               node = (host->node != NULL) ? host->node : SENSU_HOST;
+               service = (host->service != NULL) ? host->service : SENSU_PORT;
+
+               if ((e = getaddrinfo(node, service, &hints, &(host->res))) != 0) {
+                       ERROR("write_sensu plugin: Unable to resolve host \"%s\": %s",
+                                       node, gai_strerror(e));
+                       return -1;
+               }
+               DEBUG("write_sensu plugin: successfully resolved host/port: %s/%s",
+                               node, service);
+               host->flags |= F_READY;
+       }
+
+       struct linger so_linger;
+       host->s = -1;
+       for (ai = host->res; ai != NULL; ai = ai->ai_next) {
+               // create the socket
+               if ((host->s = socket(ai->ai_family,
+                                     ai->ai_socktype,
+                                     ai->ai_protocol)) == -1) {
+                       continue;
+               }
+
+               // Set very low close() lingering
+               so_linger.l_onoff = 1;
+               so_linger.l_linger = 3;
+               if (setsockopt(host->s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger) != 0)
+                       WARNING("write_sensu plugin: failed to set socket close() lingering");
+
+               // connect the socket
+               if (connect(host->s, ai->ai_addr, ai->ai_addrlen) != 0) {
+                       close(host->s);
+                       host->s = -1;
+                       continue;
+               }
+               DEBUG("write_sensu plugin: connected");
+               break;
+       }
+
+       if (host->s < 0) {
+               WARNING("write_sensu plugin: Unable to connect to sensu client");
+               return -1;
+       }
+       return 0;
+} /* }}} int sensu_connect */
+
+static void sensu_close_socket(struct sensu_host *host) /* {{{ */
+{
+       if (host->s != -1)
+               close(host->s);
+       host->s = -1;
+
+} /* }}} void sensu_close_socket */
+
+static char *build_json_str_list(const char *tag, struct str_list const *list) /* {{{ */
+{
+       int res;
+       char *ret_str;
+       char *temp_str;
+       int i;
+       if (list->nb_strs == 0) {
+               ret_str = malloc(sizeof(char));
+               if (ret_str == NULL) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str[0] = '\0';
+       }
+
+       res = asprintf(&temp_str, "\"%s\": [\"%s\"", tag, list->strs[0]);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       for (i=1; i<list->nb_strs; i++) {
+               res = asprintf(&ret_str, "%s, \"%s\"", temp_str, list->strs[i]);
+               free(temp_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               temp_str = ret_str;
+       }
+       res = asprintf(&ret_str, "%s]", temp_str);
+       free(temp_str);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+
+       return ret_str;
+} /* }}} char *build_json_str_list*/
+
+int sensu_format_name2(char *ret, int ret_len,
+               const char *hostname,
+               const char *plugin, const char *plugin_instance,
+               const char *type, const char *type_instance,
+               const char *separator)
+{
+       char *buffer;
+       size_t buffer_size;
+
+       buffer = ret;
+       buffer_size = (size_t) ret_len;
+
+#define APPEND(str) do {          \
+       size_t l = strlen (str);        \
+       if (l >= buffer_size)           \
+               return (ENOBUFS);             \
+       memcpy (buffer, (str), l);      \
+       buffer += l; buffer_size -= l;  \
+} while (0)
+
+       assert (plugin != NULL);
+       assert (type != NULL);
+
+       APPEND (hostname);
+       APPEND (separator);
+       APPEND (plugin);
+       if ((plugin_instance != NULL) && (plugin_instance[0] != 0))
+       {
+               APPEND ("-");
+               APPEND (plugin_instance);
+       }
+       APPEND (separator);
+       APPEND (type);
+       if ((type_instance != NULL) && (type_instance[0] != 0))
+       {
+               APPEND ("-");
+               APPEND (type_instance);
+       }
+       assert (buffer_size > 0);
+       buffer[0] = 0;
+
+#undef APPEND
+       return (0);
+} /* int sensu_format_name2 */
+
+static void in_place_replace_sensu_name_reserved(char *orig_name) /* {{{ */
+{
+       int i;
+       int len=strlen(orig_name);
+       for (i=0; i<len; i++) {
+               // some plugins like ipmi generate special characters in metric name
+               switch(orig_name[i]) {
+                       case '(': orig_name[i] = '_'; break;
+                       case ')': orig_name[i] = '_'; break;
+                       case ' ': orig_name[i] = '_'; break;
+                       case '"': orig_name[i] = '_'; break;
+                       case '\'': orig_name[i] = '_'; break;
+                       case '+': orig_name[i] = '_'; break;
+               }
+       }
+} /* }}} char *replace_sensu_name_reserved */
+
+static char *sensu_value_to_json(struct sensu_host const *host, /* {{{ */
+               data_set_t const *ds,
+               value_list_t const *vl, size_t index,
+               gauge_t const *rates,
+               int status)
+{
+       char name_buffer[5 * DATA_MAX_NAME_LEN];
+       char service_buffer[6 * DATA_MAX_NAME_LEN];
+       int i;
+       char *ret_str;
+       char *temp_str;
+       char *value_str;
+       int res;
+       // First part of the JSON string
+       const char *part1 = "{\"name\": \"collectd\", \"type\": \"metric\"";
+
+       char *handlers_str = build_json_str_list("handlers", &(host->metric_handlers));
+       if (handlers_str == NULL) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+
+       // incorporate the handlers
+       if (strlen(handlers_str) == 0) {
+               free(handlers_str);
+               ret_str = strdup(part1);
+               if (ret_str == NULL) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+       }
+       else {
+               res = asprintf(&ret_str, "%s, %s", part1, handlers_str);
+               free(handlers_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+       }
+
+       // incorporate the plugin name information
+       res = asprintf(&temp_str, "%s, \"collectd_plugin\": \"%s\"", ret_str, vl->plugin);
+       free(ret_str);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       ret_str = temp_str;
+
+       // incorporate the plugin type
+       res = asprintf(&temp_str, "%s, \"collectd_plugin_type\": \"%s\"", ret_str, vl->type);
+       free(ret_str);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       ret_str = temp_str;
+
+       // incorporate the plugin instance if any
+       if (vl->plugin_instance[0] != 0) {
+               res = asprintf(&temp_str, "%s, \"collectd_plugin_instance\": \"%s\"", ret_str, vl->plugin_instance);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate the plugin type instance if any
+       if (vl->type_instance[0] != 0) {
+               res = asprintf(&temp_str, "%s, \"collectd_plugin_type_instance\": \"%s\"", ret_str, vl->type_instance);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate the data source type
+       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));
+               res = asprintf(&temp_str, "%s, \"collectd_data_source_type\": \"%s\"", ret_str, ds_type);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       } else {
+               res = asprintf(&temp_str, "%s, \"collectd_data_source_type\": \"%s\"", ret_str, DS_TYPE_TO_STRING(ds->ds[index].type));
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate the data source name
+       res = asprintf(&temp_str, "%s, \"collectd_data_source_name\": \"%s\"", ret_str, ds->ds[index].name);
+       free(ret_str);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       ret_str = temp_str;
+
+       // incorporate the data source index
+       {
+               char ds_index[DATA_MAX_NAME_LEN];
+               ssnprintf (ds_index, sizeof (ds_index), "%zu", index);
+               res = asprintf(&temp_str, "%s, \"collectd_data_source_index\": %s", ret_str, ds_index);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // add key value attributes from config if any
+       for (i = 0; i < sensu_attrs_num; i += 2) {
+               res = asprintf(&temp_str, "%s, \"%s\": \"%s\"", ret_str, sensu_attrs[i], sensu_attrs[i+1]);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate sensu tags from config if any
+       if (strlen(sensu_tags) != 0) {
+               res = asprintf(&temp_str, "%s, %s", ret_str, sensu_tags);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // calculate the value and set to a string
+       if (ds->ds[index].type == DS_TYPE_GAUGE) {
+               double tmp_v = (double) vl->values[index].gauge;
+               res = asprintf(&value_str, "%.8f", tmp_v, sensu_tags);
+               if (res == -1) {
+                       free(ret_str);
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+       } else if (rates != NULL) {
+               double tmp_v = (double) rates[index];
+               res = asprintf(&value_str, "%.8f", tmp_v, sensu_tags);
+               if (res == -1) {
+                       free(ret_str);
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+       } else {
+               int64_t tmp_v;
+               if (ds->ds[index].type == DS_TYPE_DERIVE)
+                       tmp_v = (int64_t) vl->values[index].derive;
+               else if (ds->ds[index].type == DS_TYPE_ABSOLUTE)
+                       tmp_v = (int64_t) vl->values[index].absolute;
+               else
+                       tmp_v = (int64_t) vl->values[index].counter;
+               res = asprintf(&value_str, "%lld", tmp_v, sensu_tags);
+               if (res == -1) {
+                       free(ret_str);
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+       }
+
+       // Generate the full service name
+       sensu_format_name2(name_buffer, sizeof(name_buffer),
+               vl->host, vl->plugin, vl->plugin_instance,
+               vl->type, vl->type_instance, host->separator);
+       if (host->always_append_ds || (ds->ds_num > 1)) {
+               if (host->event_service_prefix == NULL)
+                       ssnprintf(service_buffer, sizeof(service_buffer), "%s.%s",
+                                       name_buffer, ds->ds[index].name);
+               else
+                       ssnprintf(service_buffer, sizeof(service_buffer), "%s%s.%s",
+                                       host->event_service_prefix, name_buffer, ds->ds[index].name);
+       } else {
+               if (host->event_service_prefix == NULL)
+                       sstrncpy(service_buffer, name_buffer, sizeof(service_buffer));
+               else
+                       ssnprintf(service_buffer, sizeof(service_buffer), "%s%s",
+                                       host->event_service_prefix, name_buffer);
+       }
+
+       // Replace collectd sensor name reserved characters so that time series DB is happy
+       in_place_replace_sensu_name_reserved(service_buffer);
+
+       // finalize the buffer by setting the output and closing curly bracket
+       res = asprintf(&temp_str, "%s, \"output\": \"%s %s %ld\"}\n", ret_str, service_buffer, value_str, CDTIME_T_TO_TIME_T(vl->time));
+       free(ret_str);
+       free(value_str);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       ret_str = temp_str;
+
+       DEBUG("write_sensu plugin: Successfully created json for metric: "
+                       "host = \"%s\", service = \"%s\"",
+                       vl->host, service_buffer);
+       return ret_str;
+} /* }}} char *sensu_value_to_json */
+
+/*
+ * Uses replace_str2() implementation from
+ * http://creativeandcritical.net/str-replace-c/
+ * copyright (c) Laird Shaw, under public domain.
+ */
+char *replace_str(const char *str, const char *old, /* {{{ */
+               const char *new)
+{
+       char *ret, *r;
+       const char *p, *q;
+       size_t oldlen = strlen(old);
+       size_t count = strlen(new);
+       size_t retlen = count;
+       size_t newlen = count;
+       int samesize = (oldlen == newlen);
+
+       if (!samesize) {
+               for (count = 0, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen)
+                       count++;
+               /* This is undefined if p - str > PTRDIFF_MAX */
+               retlen = p - str + strlen(p) + count * (newlen - oldlen);
+       } else
+               retlen = strlen(str);
+
+       ret = malloc(retlen + 1);
+       if (ret == NULL)
+               return NULL;
+       // added to original: not optimized, but keeps valgrind happy.
+       memset(ret, 0, retlen + 1);
+
+       r = ret;
+       p = str;
+       while (1) {
+               /* If the old and new strings are different lengths - in other
+                * words we have already iterated through with strstr above,
+                * and thus we know how many times we need to call it - then we
+                * can avoid the final (potentially lengthy) call to strstr,
+                * which we already know is going to return NULL, by
+                * decrementing and checking count.
+                */
+               if (!samesize && !count--)
+                       break;
+               /* Otherwise i.e. when the old and new strings are the same
+                * length, and we don't know how many times to call strstr,
+                * we must check for a NULL return here (we check it in any
+                * event, to avoid further conditions, and because there's
+                * no harm done with the check even when the old and new
+                * strings are different lengths).
+                */
+               if ((q = strstr(p, old)) == NULL)
+                       break;
+               /* This is undefined if q - p > PTRDIFF_MAX */
+               ptrdiff_t l = q - p;
+               memcpy(r, p, l);
+               r += l;
+               memcpy(r, new, newlen);
+               r += newlen;
+               p = q + oldlen;
+       }
+       strncpy(r, p, strlen(p));
+
+       return ret;
+} /* }}} char *replace_str */
+
+static char *replace_json_reserved(const char *message) /* {{{ */
+{
+       char *msg = replace_str(message, "\\", "\\\\");
+       if (msg == NULL) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       char *tmp = replace_str(msg, "\"", "\\\"");
+       free(msg);
+       if (tmp == NULL) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       msg = replace_str(tmp, "\n", "\\\n");
+       free(tmp);
+       if (msg == NULL) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       return msg;
+} /* }}} char *replace_json_reserved */
+
+static char *sensu_notification_to_json(struct sensu_host *host, /* {{{ */
+               notification_t const *n)
+{
+       char service_buffer[6 * DATA_MAX_NAME_LEN];
+       char const *severity;
+       notification_meta_t *meta;
+       char *ret_str;
+       char *temp_str;
+       int status;
+       int i;
+       int res;
+       // add the severity/status
+       switch (n->severity) {
+               case NOTIF_OKAY:
+                       severity = "OK";
+                       status = 0;
+                       break;
+               case NOTIF_WARNING:
+                       severity = "WARNING";
+                       status = 1;
+                       break;
+               case NOTIF_FAILURE:
+                       severity = "CRITICAL";
+                       status = 2;
+                       break;
+               default:
+                       severity = "UNKNOWN";
+                       status = 3;
+       }
+       res = asprintf(&temp_str, "{\"status\": %d", status);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       ret_str = temp_str;
+
+       // incorporate the timestamp
+       res = asprintf(&temp_str, "%s, \"timestamp\": %ld", ret_str, CDTIME_T_TO_TIME_T(n->time));
+       free(ret_str);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       ret_str = temp_str;
+
+       char *handlers_str = build_json_str_list("handlers", &(host->notification_handlers));
+       if (handlers_str == NULL) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       // incorporate the handlers
+       if (strlen(handlers_str) != 0) {
+               res = asprintf(&temp_str, "%s, %s", ret_str, handlers_str);
+               free(ret_str);
+               free(handlers_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       } else {
+               free(handlers_str);
+       }
+
+       // incorporate the plugin name information if any
+       if (n->plugin[0] != 0) {
+               res = asprintf(&temp_str, "%s, \"collectd_plugin\": \"%s\"", ret_str, n->plugin);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate the plugin type if any
+       if (n->type[0] != 0) {
+               res = asprintf(&temp_str, "%s, \"collectd_plugin_type\": \"%s\"", ret_str, n->type);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate the plugin instance if any
+       if (n->plugin_instance[0] != 0) {
+               res = asprintf(&temp_str, "%s, \"collectd_plugin_instance\": \"%s\"", ret_str, n->plugin_instance);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate the plugin type instance if any
+       if (n->type_instance[0] != 0) {
+               res = asprintf(&temp_str, "%s, \"collectd_plugin_type_instance\": \"%s\"", ret_str, n->type_instance);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // add key value attributes from config if any
+       for (i = 0; i < sensu_attrs_num; i += 2) {
+               res = asprintf(&temp_str, "%s, \"%s\": \"%s\"", ret_str, sensu_attrs[i], sensu_attrs[i+1]);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate sensu tags from config if any
+       if (strlen(sensu_tags) != 0) {
+               res = asprintf(&temp_str, "%s, %s", ret_str, sensu_tags);
+               free(ret_str);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // incorporate the service name
+       sensu_format_name2(service_buffer, sizeof(service_buffer),
+                               /* host */ "", n->plugin, n->plugin_instance,
+                               n->type, n->type_instance, host->separator);
+       // replace sensu event name chars that are considered illegal
+       in_place_replace_sensu_name_reserved(service_buffer);
+       res = asprintf(&temp_str, "%s, \"name\": \"%s\"", ret_str, &service_buffer[1]);
+       free(ret_str);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       ret_str = temp_str;
+
+       // incorporate the check output
+       if (n->message[0] != 0) {
+               char *msg = replace_json_reserved(n->message);
+               if (msg == NULL) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               res = asprintf(&temp_str, "%s, \"output\": \"%s - %s\"", ret_str, severity, msg);
+               free(ret_str);
+               free(msg);
+               if (res == -1) {
+                       ERROR(alloc_err);
+                       return NULL;
+               }
+               ret_str = temp_str;
+       }
+
+       // Pull in values from threshold and add extra attributes
+       for (meta = n->meta; meta != NULL; meta = meta->next) {
+               if (strcasecmp("CurrentValue", meta->name) == 0 && meta->type == NM_TYPE_DOUBLE) {
+                       res = asprintf(&temp_str, "%s, \"current_value\": \"%.8f\"", ret_str, meta->nm_value.nm_double);
+                       free(ret_str);
+                       if (res == -1) {
+                               ERROR(alloc_err);
+                               return NULL;
+                       }
+                       ret_str = temp_str;
+               }
+               if (meta->type == NM_TYPE_STRING) {
+                       res = asprintf(&temp_str, "%s, \"%s\": \"%s\"", ret_str, meta->name, meta->nm_value.nm_string);
+                       free(ret_str);
+                       if (res == -1) {
+                               ERROR(alloc_err);
+                               return NULL;
+                       }
+                       ret_str = temp_str;
+               }
+       }
+
+       // close the curly bracket
+       res = asprintf(&temp_str, "%s}\n", ret_str);
+       free(ret_str);
+       if (res == -1) {
+               ERROR(alloc_err);
+               return NULL;
+       }
+       ret_str = temp_str;
+
+       DEBUG("write_sensu plugin: Successfully created JSON for notification: "
+                               "host = \"%s\", service = \"%s\", state = \"%s\"",
+                               n->host, service_buffer, severity);
+       return ret_str;
+} /* }}} char *sensu_notification_to_json */
+
+static int sensu_send_msg(struct sensu_host *host, const char *msg) /* {{{ */
+{
+       int status = 0;
+       size_t  buffer_len;
+
+       status = sensu_connect(host);
+       if (status != 0)
+               return status;
+
+       buffer_len = strlen(msg);
+
+       status = (int) swrite(host->s, msg, buffer_len);
+       sensu_close_socket(host);
+
+       if (status != 0) {
+               char errbuf[1024];
+               ERROR("write_sensu plugin: Sending to Sensu at %s:%s failed: %s",
+                               (host->node != NULL) ? host->node : SENSU_HOST,
+                               (host->service != NULL) ? host->service : SENSU_PORT,
+                               sstrerror(errno, errbuf, sizeof(errbuf)));
+               return -1;
+       }
+
+       return 0;
+} /* }}} int sensu_send_msg */
+
+
+static int sensu_send(struct sensu_host *host, char const *msg) /* {{{ */
+{
+       int status = 0;
+
+       status = sensu_send_msg(host, msg);
+       if (status != 0) {
+               host->flags &= ~F_READY;
+               if (host->res != NULL) {
+                       freeaddrinfo(host->res);
+                       host->res = NULL;
+               }
+               return status;
+       }
+
+       return 0;
+} /* }}} int sensu_send */
+
+
+static int sensu_write(const data_set_t *ds, /* {{{ */
+             const value_list_t *vl,
+             user_data_t *ud)
+{
+       int status = 0;
+       int statuses[vl->values_len];
+       struct sensu_host       *host = ud->data;
+       gauge_t *rates = NULL;
+       int i;
+       char *msg;
+
+       pthread_mutex_lock(&host->lock);
+       memset(statuses, 0, vl->values_len * sizeof(*statuses));
+
+       if (host->store_rates) {
+               rates = uc_get_rate(ds, vl);
+               if (rates == NULL) {
+                       ERROR("write_sensu plugin: uc_get_rate failed.");
+                       pthread_mutex_unlock(&host->lock);
+                       return -1;
+               }
+       }
+       for (i = 0; i < (size_t) vl->values_len; i++) {
+               msg = sensu_value_to_json(host, ds, vl, (int) i, rates, statuses[i]);
+               if (msg == NULL) {
+                       sfree(rates);
+                       pthread_mutex_unlock(&host->lock);
+                       return -1;
+               }
+               status = sensu_send(host, msg);
+               free(msg);
+               if (status != 0) {
+                       ERROR("write_sensu plugin: sensu_send failed with status %i", status);
+                       pthread_mutex_unlock(&host->lock);
+                       sfree(rates);
+                       return status;
+               }
+       }
+       sfree(rates);
+       pthread_mutex_unlock(&host->lock);
+       return status;
+} /* }}} int sensu_write */
+
+static int sensu_notification(const notification_t *n, user_data_t *ud) /* {{{ */
+{
+       int     status;
+       struct sensu_host *host = ud->data;
+       char *msg;
+
+       pthread_mutex_lock(&host->lock);
+
+       msg = sensu_notification_to_json(host, n);
+       if (msg == NULL) {
+               pthread_mutex_unlock(&host->lock);
+               return -1;
+       }
+
+       status = sensu_send(host, msg);
+       free(msg);
+       if (status != 0)
+               ERROR("write_sensu plugin: sensu_send failed with status %i", status);
+       pthread_mutex_unlock(&host->lock);
+
+       return status;
+} /* }}} int sensu_notification */
+
+static void sensu_free(void *p) /* {{{ */
+{
+       struct sensu_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;
+       }
+
+       sensu_close_socket(host);
+       if (host->res != NULL) {
+               freeaddrinfo(host->res);
+               host->res = NULL;
+       }
+       sfree(host->service);
+       sfree(host->event_service_prefix);
+       sfree(host->name);
+       sfree(host->node);
+       sfree(host->separator);
+       free_str_list(&(host->metric_handlers));
+       free_str_list(&(host->notification_handlers));
+       pthread_mutex_destroy(&host->lock);
+       sfree(host);
+} /* }}} void sensu_free */
+
+
+static int sensu_config_node(oconfig_item_t *ci) /* {{{ */
+{
+       struct sensu_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_sensu plugin: calloc failed.");
+               return ENOMEM;
+       }
+       pthread_mutex_init(&host->lock, NULL);
+       host->reference_count = 1;
+       host->node = NULL;
+       host->service = NULL;
+       host->notifications = 0;
+       host->metrics = 0;
+       host->store_rates = 1;
+       host->always_append_ds = 0;
+       host->metric_handlers.nb_strs = 0;
+       host->metric_handlers.strs = NULL;
+       host->notification_handlers.nb_strs = 0;
+       host->notification_handlers.strs = NULL;
+       host->separator = strdup("/");
+       if (host->separator == NULL) {
+               ERROR(alloc_err);
+               sensu_free(host);
+               return -1;
+       }
+
+       status = cf_util_get_string(ci, &host->name);
+       if (status != 0) {
+               WARNING("write_sensu plugin: Required host name is missing.");
+               sensu_free(host);
+               return -1;
+       }
+
+       for (i = 0; i < ci->children_num; i++) {
+               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("Notifications", child->key) == 0) {
+                       status = cf_util_get_boolean(child, &host->notifications);
+                       if (status != 0)
+                               break;
+               } else if (strcasecmp("Metrics", child->key) == 0) {
+                                       status = cf_util_get_boolean(child, &host->metrics);
+                                       if (status != 0)
+                                               break;
+               } else if (strcasecmp("EventServicePrefix", child->key) == 0) {
+                       status = cf_util_get_string(child, &host->event_service_prefix);
+                       if (status != 0)
+                               break;
+               } else if (strcasecmp("Separator", child->key) == 0) {
+                               status = cf_util_get_string(child, &host->separator);
+                               if (status != 0)
+                                       break;
+               } else if (strcasecmp("MetricHandler", child->key) == 0) {
+                       char *temp_str = NULL;
+                       status = cf_util_get_string(child, &temp_str);
+                       if (status != 0)
+                               break;
+                       status = add_str_to_list(&(host->metric_handlers), temp_str);
+                       free(temp_str);
+                       if (status != 0)
+                               break;
+               } else if (strcasecmp("NotificationHandler", child->key) == 0) {
+                       char *temp_str = NULL;
+                       status = cf_util_get_string(child, &temp_str);
+                       if (status != 0)
+                               break;
+                       status = add_str_to_list(&(host->notification_handlers), temp_str);
+                       free(temp_str);
+                       if (status != 0)
+                               break;
+               } else if (strcasecmp("Port", child->key) == 0) {
+                       status = cf_util_get_service(child, &host->service);
+                       if (status != 0) {
+                               ERROR("write_sensu plugin: Invalid argument "
+                                               "configured for the \"Port\" "
+                                               "option.");
+                               break;
+                       }
+               } 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_sensu plugin: ignoring unknown config "
+                               "option: \"%s\"", child->key);
+               }
+       }
+       if (status != 0) {
+               sensu_free(host);
+               return status;
+       }
+
+       if (host->metrics && (host->metric_handlers.nb_strs == 0)) {
+                       sensu_free(host);
+                       WARNING("write_sensu plugin: metrics enabled but no MetricHandler defined. Giving up.");
+                       return -1;
+               }
+
+       if (host->notifications && (host->notification_handlers.nb_strs == 0)) {
+               sensu_free(host);
+               WARNING("write_sensu plugin: notifications enabled but no NotificationHandler defined. Giving up.");
+               return -1;
+       }
+
+       if ((host->notification_handlers.nb_strs > 0) && (host->notifications == 0)) {
+               WARNING("write_sensu plugin: NotificationHandler given so forcing notifications to be enabled");
+               host->notifications = 1;
+       }
+
+       if ((host->metric_handlers.nb_strs > 0) && (host->metrics == 0)) {
+               WARNING("write_sensu plugin: MetricHandler given so forcing metrics to be enabled");
+               host->metrics = 1;
+       }
+
+       if (!(host->notifications || host->metrics)) {
+               WARNING("write_sensu plugin: neither metrics nor notifications enabled. Giving up.");
+               sensu_free(host);
+               return -1;
+       }
+
+       ssnprintf(callback_name, sizeof(callback_name), "write_sensu/%s", host->name);
+       ud.data = host;
+       ud.free_func = sensu_free;
+
+       pthread_mutex_lock(&host->lock);
+
+       if (host->metrics) {
+               status = plugin_register_write(callback_name, sensu_write, &ud);
+               if (status != 0)
+                       WARNING("write_sensu plugin: plugin_register_write (\"%s\") "
+                                       "failed with status %i.",
+                                       callback_name, status);
+               else /* success */
+                       host->reference_count++;
+       }
+
+       if (host->notifications) {
+               status = plugin_register_notification(callback_name, sensu_notification, &ud);
+               if (status != 0)
+                       WARNING("write_sensu plugin: plugin_register_notification (\"%s\") "
+                                       "failed with status %i.",
+                                       callback_name, status);
+               else
+                       host->reference_count++;
+       }
+
+       if (host->reference_count <= 1) {
+               /* Both callbacks failed => free memory.
+                * We need to unlock here, because sensu_free() will lock.
+                * This is not a race condition, because we're the only one
+                * holding a reference. */
+               pthread_mutex_unlock(&host->lock);
+               sensu_free(host);
+               return -1;
+       }
+
+       host->reference_count--;
+       pthread_mutex_unlock(&host->lock);
+
+       return status;
+} /* }}} int sensu_config_node */
+
+static int sensu_config(oconfig_item_t *ci) /* {{{ */
+{
+       int              i;
+       oconfig_item_t  *child;
+       int              status;
+       struct str_list sensu_tags_arr;
+
+       sensu_tags_arr.nb_strs = 0;
+       sensu_tags_arr.strs = NULL;
+       sensu_tags = malloc(sizeof(char));
+       if (sensu_tags == NULL) {
+               ERROR(alloc_err);
+               return -1;
+       }
+       sensu_tags[0] = '\0';
+
+       for (i = 0; i < ci->children_num; i++)  {
+               child = &ci->children[i];
+
+               if (strcasecmp("Node", child->key) == 0) {
+                       sensu_config_node(child);
+               } else if (strcasecmp(child->key, "attribute") == 0) {
+                       char *key = NULL;
+                       char *val = NULL;
+
+                       if (child->values_num != 2) {
+                               WARNING("sensu attributes need both a key and a value.");
+                               free(sensu_tags);
+                               return -1;
+                       }
+                       if (child->values[0].type != OCONFIG_TYPE_STRING ||
+                           child->values[1].type != OCONFIG_TYPE_STRING) {
+                               WARNING("sensu attribute needs string arguments.");
+                               free(sensu_tags);
+                               return -1;
+                       }
+                       if ((key = strdup(child->values[0].value.string)) == NULL) {
+                               ERROR(alloc_err);
+                               free(sensu_tags);
+                               return -1;
+                       }
+                       if ((val = strdup(child->values[1].value.string)) == NULL) {
+                               free(sensu_tags);
+                               free(key);
+                               ERROR(alloc_err);
+                               return -1;
+                       }
+                       strarray_add(&sensu_attrs, &sensu_attrs_num, key);
+                       strarray_add(&sensu_attrs, &sensu_attrs_num, val);
+                       DEBUG("write_sensu: got attr: %s => %s", key, val);
+                       sfree(key);
+                       sfree(val);
+               } else if (strcasecmp(child->key, "tag") == 0) {
+                       char *tmp = NULL;
+                       status = cf_util_get_string(child, &tmp);
+                       if (status != 0)
+                               continue;
+
+                       status = add_str_to_list(&sensu_tags_arr, tmp);
+                       sfree(tmp);
+                       if (status != 0)
+                               continue;
+                       DEBUG("write_sensu plugin: Got tag: %s", tmp);
+               } else {
+                       WARNING("write_sensu plugin: Ignoring unknown "
+                                "configuration option \"%s\" at top level.",
+                                child->key);
+               }
+       }
+       if (sensu_tags_arr.nb_strs > 0) {
+               free(sensu_tags);
+               sensu_tags = build_json_str_list("tags", &sensu_tags_arr);
+               free_str_list(&sensu_tags_arr);
+               if (sensu_tags == NULL) {
+                       ERROR(alloc_err);
+                       return -1;
+               }
+       }
+       return 0;
+} /* }}} int sensu_config */
+
+void module_register(void)
+{
+       plugin_register_complex_config("write_sensu", sensu_config);
+}
+
+/* vim: set sw=8 sts=8 ts=8 noet : */