From: Marc Fournier Date: Sun, 12 Apr 2015 07:26:52 +0000 (+0200) Subject: Merge remote-tracking branch 'origin/pr/998' X-Git-Tag: collectd-5.5.0~43 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=dc7e14787cc4273927d5d0cdbd00fd41a0bee928;hp=125f3bece6725fba00e5961882692bd671e38bcb;p=collectd.git Merge remote-tracking branch 'origin/pr/998' --- diff --git a/AUTHORS b/AUTHORS index 072e7ffe..027ac969 100644 --- a/AUTHORS +++ b/AUTHORS @@ -106,6 +106,9 @@ Fabian Linzberger Fabien Wernli - Solaris improvements in the memory and interfaces plugin. +Fabrice A. Marie + - write_sensu plugin. + Flavio Stanchina - mbmon plugin. diff --git a/README b/README index 9fb90043..12ab7c65 100644 --- 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: diff --git a/configure.ac b/configure.ac index c83805e3..0b8fa181 100644 --- a/configure.ac +++ b/configure.ac @@ -1700,6 +1700,10 @@ then [have_curlopt_username="yes"], [have_curlopt_username="no"], [[#include ]]) + AC_CHECK_DECL(CURLOPT_TIMEOUT_MS, + [have_curlopt_timeout="yes"], + [have_curlopt_timeout="no"], + [[#include ]]) 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 diff --git a/contrib/redhat/collectd.spec b/contrib/redhat/collectd.spec index 0e5cd86f..e023a341 100644 --- a/contrib/redhat/collectd.spec +++ b/contrib/redhat/collectd.spec @@ -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}} @@ -169,6 +170,7 @@ %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} @@ -211,7 +213,7 @@ 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 diff --git a/src/Makefile.am b/src/Makefile.am index 14708c01..19907007 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/apache.c b/src/apache.c index 1099248a..0c6318e3 100644 --- a/src/apache.c +++ b/src/apache.c @@ -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 */ diff --git a/src/ascent.c b/src/ascent.c index e9d25bd5..11175af5 100644 --- a/src/ascent.c +++ b/src/ascent.c @@ -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 */ diff --git a/src/barometer.c b/src/barometer.c index 95b05f4e..2bfd51e0 100644 --- a/src/barometer.c +++ b/src/barometer.c @@ -111,6 +111,41 @@ #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 @@ -120,7 +155,17 @@ /** 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: + * - /barometer-bmp085/pressure-normalized + * - /barometer-bmp085/pressure-absolute + * - /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; diff --git a/src/bind.c b/src/bind.c index 59eb2491..2ad50f10 100644 --- a/src/bind.c +++ b/src/bind.c @@ -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 */ diff --git a/src/collectd-perl.pod b/src/collectd-perl.pod index f1426de1..7308648f 100644 --- a/src/collectd-perl.pod +++ b/src/collectd-perl.pod @@ -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 (see L) and provides a lot more functionality, too. -When loading the C, the B 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 module used by the plugin -itself. See the documentation of the B option in L -for details. - =head1 CONFIGURATION =over 4 diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 8d7622a1..d31ef157 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -122,6 +122,7 @@ #@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 @@ -154,18 +155,14 @@ #@BUILD_PLUGIN_OPENLDAP_TRUE@LoadPlugin openldap #@BUILD_PLUGIN_OPENVPN_TRUE@LoadPlugin openvpn #@BUILD_PLUGIN_ORACLE_TRUE@LoadPlugin oracle -#@BUILD_PLUGIN_PERL_TRUE@ -#@BUILD_PLUGIN_PERL_TRUE@ Globals true -#@BUILD_PLUGIN_PERL_TRUE@ +#@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@ -#@BUILD_PLUGIN_PYTHON_TRUE@ Globals true -#@BUILD_PLUGIN_PYTHON_TRUE@ +#@BUILD_PLUGIN_PYTHON_TRUE@LoadPlugin python #@BUILD_PLUGIN_REDIS_TRUE@LoadPlugin redis #@BUILD_PLUGIN_ROUTEROS_TRUE@LoadPlugin routeros #@BUILD_PLUGIN_RRDCACHED_TRUE@LoadPlugin rrdcached @@ -203,6 +200,7 @@ #@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 @@ -1311,6 +1309,8 @@ # Format "Command" # StoreRates false # BufferSize 4096 +# LowSpeedLimit 0 +# Timeout 0 # # @@ -1359,6 +1359,24 @@ # Attribute "foo" "bar" # +# +# +# Host "localhost" +# Port 3030 +# StoreRates true +# AlwaysAppendDS false +# Notifications true +# Metrics true +# EventServicePrefix "" +# MetricHandler "influx" +# MetricHandler "default" +# NotificationHandler "flapjack" +# NotificationHandler "howling_monkey" +# +# Tag "foobar" +# Attribute "foo" "bar" +# + # # # Host "localhost" diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index bd781074..a7a58167 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -98,7 +98,6 @@ Options inside a B block can override default settings and influence the way plugins are loaded, e.g.: - Globals true Interval 60 @@ -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 and are checked by default depends on the distribution you use. +=item B I + +Specifies which ciphers to use in the connection. The list of ciphers +must specify valid ciphers. See +L for details. + +=item B I + +The B option sets the overall timeout for HTTP requests to B, in +milliseconds. By default, the configured B is used to set the +timeout. + =back =head2 Plugin C @@ -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 and are checked by default depends on the distribution you use. +=item B I + +The B option sets the overall timeout for HTTP requests to B, in +milliseconds. By default, the configured B is used to set the +timeout. + =back =head2 Plugin C -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 or -I). -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 from Freescale, +see L. + + +=item I from Freescale +see L. + + +=item I 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 I -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 I -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 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 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 measurements (value of 1 +means no averaging). Minimal size is 1, maximal 1024. + +For I 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 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 I -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 I -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 I -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 (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 or C. =item B<1> - international formula for conversion , -See I. -For this method you have to configure C but do not need C -(uses fixed global temperature average instead). +See +L. +For this method you have to configure C but do not need +C (uses fixed global temperature average instead). =item B<2> - formula as recommended by the Deutsche Wetterdienst (German Meteorological Service). -See I -For this method you have to configure both C and C. +See L +For this method you have to configure both C and +C. =back @@ -978,15 +1028,15 @@ The altitude (in meters) of the location where you meassure the pressure. =item B I -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 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 /-/- -( is usually omitted when there is just single value type). -Or you can figure it out from the path of the output data files. +( 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 I + +The B option sets the overall timeout for HTTP requests to B, in +milliseconds. By default, the configured B is used to set the +timeout. + =item B I Collect statistics about a specific I<"view">. BIND can behave different, @@ -1455,6 +1511,10 @@ C). Measure response time for the request. If this setting is enabled, B blocks (see below) are optional. Disabled by default. +Beware that requests will get aborted if they take too long to complete. Adjust +B accordingly if you expect B to report such slow +requests. + =item B B|B Measure response code for the request. If this setting is enabled, B @@ -1469,6 +1529,18 @@ plugin below on how matches are defined. If the B or B options are set to B, B blocks are optional. +=item B I + +The B option sets the overall timeout for HTTP requests to B, in +milliseconds. By default, the configured B 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 to 0. + +If B is 0 or bigger than the B, keep in mind that each slow +network connection will stall one read thread. Adjust the B global +setting accordingly to prevent this from blocking other plugins. + =back =head2 Plugin C @@ -1555,6 +1627,8 @@ URL. By default the global B setting will be used. =item B I +=item B I + These options behave exactly equivalent to the appropriate options of the I plugin. Please see there for a detailed description. @@ -1655,6 +1729,8 @@ Examples: =item B I +=item B I + These options behave exactly equivalent to the appropriate options of the I. 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 and are checked by default depends on the distribution you use. +=item B I + +The B option sets the overall timeout for HTTP requests to B, in +milliseconds. By default, the configured B is used to set the +timeout. + =back =head2 Plugin C @@ -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 I + +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 I 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 to authenticate when connecting to I. -=item B I +=item B I The B 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. Service name or port number to connect to. Defaults to C<27017>. -=item B I +=item B I Set the timeout for each operation on I to I milliseconds. Setting this option to zero means no timeout, which is the default. @@ -7138,6 +7227,26 @@ are available on the server side. I must be at least 1024 and cannot exceed the size of an C, i.e. 2EGByte. Defaults to C<4096>. +=item B I + +Sets the minimal transfer rate in I 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 I + +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 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. The optimal value to set B 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 @@ -7296,7 +7405,7 @@ The B 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 I +=item B I The B option sets the socket connection timeout, in milliseconds. @@ -7424,6 +7533,116 @@ attribute for each metric being sent out to I. =back +=head2 Plugin C + +The I will send values to I, a powerful stream +aggregation and monitoring system. The plugin sends I encoded data to +a local I client using a TCP socket. + +At the moment, the I 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 client. + +Synopsis: + + + + Host "localhost" + Port "3030" + StoreRates true + AlwaysAppendDS false + MetricHandler "influx" + MetricHandler "default" + NotificationHandler "flapjack" + NotificationHandler "howling_monkey" + Notifications true + + Tag "foobar" + Attribute "foo" "bar" + + +The following options are understood by the I: + +=over 4 + +=item EB IE + +The plugin's configuration consists of one or more B blocks. Each block +is given a unique I and specifies one connection to an instance of +I. Inside the B block, the following per-connection options are +understood: + +=over 4 + +=item B I
+ +Hostname or address to connect to. Defaults to C. + +=item B I + +Service name or port number to connect to. Defaults to C<3030>. + +=item B B|B + +If set to B (the default), convert counter values to rates. If set to +B counter values are stored as is, i.e. as an increasing integer number. + +This will be reflected in the C tag: If +B is enabled, converted values will have "rate" appended to the +data source type, e.g. C. + +=item B B|B + +If set the B, append the name of the I (DS) to the +"service", i.e. the field that, together with the "host" field, uniquely +identifies a metric in I. If set to B (the default), this is +only done when there is more than one DS. + +=item B B|B + +If set to B, create I events for notifications. This is B +by default. At least one of B or B should be enabled. + +=item B B|B + +If set to B, create I events for metrics. This is B +by default. At least one of B or B should be enabled. + + +=item B I + +Sets the separator for I metrics name or checks. Defaults to "/". + +=item B I + +Add a handler that will be set when metrics are sent to I. You can add +several of them, one per line. Defaults to no handler. + +=item B I + +Add a handler that will be set when notifications are sent to I. You can +add several of them, one per line. Defaults to no handler. + +=item B I + +Add the given string as a prefix to the event service name. +If B not set or set to an empty string (""), +no prefix will be used. + +=back + +=item B I + +Add the given string as an additional tag to the metric being sent to +I. + +=item B I I + +Consider the two given strings to be the key and value of an additional +attribute for each metric being sent out to I. + +=back + =head2 Plugin C The I will collect statistics from a I server diff --git a/src/curl.c b/src/curl.c index 0e5d2cfa..b750f80b 100644 --- a/src/curl.c +++ b/src/curl.c @@ -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); diff --git a/src/curl_json.c b/src/curl_json.c index 09db7866..3a5a3ab8 100644 --- a/src/curl_json.c +++ b/src/curl_json.c @@ -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); diff --git a/src/curl_xml.c b/src/curl_xml.c index c9f06518..9049d990 100644 --- a/src/curl_xml.c +++ b/src/curl_xml.c @@ -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 index 00000000..2d2db2ab --- /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 + * Copyright (C) 2010 Manuel L. Sanmartin + * + * 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 + * Manuel L. Sanmartin + **/ + +/* Many of this code is based on busybox ipc implementation, which is: + * (C) Rodney Radford 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 for semctl() */ + /* X/OPEN tells us to use for msgctl() */ + /* X/OPEN tells us to use for shmctl() */ +# include +# include +# include +# include +# include + + /* 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 */ +# 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 +/* #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; ishm_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; ilow_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 index 00000000..3f146f3c --- /dev/null +++ b/src/write_sensu.c @@ -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 + */ + +#include "collectd.h" +#include "plugin.h" +#include "common.h" +#include "configfile.h" +#include "utils_cache.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#ifndef HAVE_ASPRINTF +/* + * Uses asprintf() portable implementation from + * https://github.com/littlstar/asprintf.c/blob/master/ + * copyright (c) 2014 joseph werle under MIT license. + */ +#include +#include + +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; inb_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; inb_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; imetric_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 : */