From 380be79e07e62dd2e0a6d3bd6710dca3b46481a6 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Sat, 20 Dec 2008 17:07:59 +0100 Subject: [PATCH] notification target: Add a target that dispatches notifications. Not tested very well yet, but it works essentially. --- configure.in | 2 + src/Makefile.am | 8 ++ src/collectd.conf.in | 4 + src/collectd.conf.pod | 52 ++++++++ src/target_notification.c | 323 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 389 insertions(+) create mode 100644 src/target_notification.c diff --git a/configure.in b/configure.in index 3c7f9df2..6b8571d3 100644 --- a/configure.in +++ b/configure.in @@ -3023,6 +3023,7 @@ AC_PLUGIN([swap], [$plugin_swap], [Swap usage statistics]) AC_PLUGIN([syslog], [$have_syslog], [Syslog logging plugin]) AC_PLUGIN([tail], [yes], [Parsing of logfiles]) AC_PLUGIN([tape], [$plugin_tape], [Tape drive statistics]) +AC_PLUGIN([target_notification], [yes], [The notification target]) AC_PLUGIN([target_replace], [yes], [The replace target]) AC_PLUGIN([target_set], [yes], [The set target]) AC_PLUGIN([tcpconns], [$plugin_tcpconns], [TCP connection statistics]) @@ -3202,6 +3203,7 @@ Configuration: syslog . . . . . . . $enable_syslog tail . . . . . . . . $enable_tail tape . . . . . . . . $enable_tape + target_notification . $enable_target_notification target_replace . . . $enable_target_replace target_set . . . . . $enable_target_set tcpconns . . . . . . $enable_tcpconns diff --git a/src/Makefile.am b/src/Makefile.am index 60b1cfb4..f5776d5e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -742,6 +742,14 @@ collectd_LDADD += "-dlopen" tape.la collectd_DEPENDENCIES += tape.la endif +if BUILD_PLUGIN_TARGET_NOTIFICATION +pkglib_LTLIBRARIES += target_notification.la +target_notification_la_SOURCES = target_notification.c +target_notification_la_LDFLAGS = -module -avoid-version +collectd_LDADD += "-dlopen" target_notification.la +collectd_DEPENDENCIES += target_notification.la +endif + if BUILD_PLUGIN_TARGET_REPLACE pkglib_LTLIBRARIES += target_replace.la target_replace_la_SOURCES = target_replace.c diff --git a/src/collectd.conf.in b/src/collectd.conf.in index a474fdcd..01017e3e 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -462,8 +462,12 @@ FQDNLookup true # Load required matches: #@BUILD_PLUGIN_MATCH_REGEX_TRUE@LoadPlugin match_regex +#@BUILD_PLUGIN_MATCH_VALUE_TRUE@LoadPlugin match_value # Load required targets: +#@BUILD_PLUGIN_TARGET_NOTIFICATION_TRUE@LoadPlugin target_notification +#@BUILD_PLUGIN_TARGET_REPLACE_TRUE@LoadPlugin target_replace +#@BUILD_PLUGIN_TARGET_SET_TRUE@LoadPlugin target_set # The following block demonstrates the default behavior if no filtering is # configured at all: All values will be sent to all available write plugins. diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 0ffd4854..cc4fab4f 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -2787,6 +2787,58 @@ Example: =over 4 +=item B + +Creates and dispatches a notification. + +Available options: + +=over 4 + +=item B I + +This required option sets the message of the notification. The following +placeholders will be replaced by an appropriate value: + +=over 4 + +=item B<%{host}> + +=item B<%{plugin}> + +=item B<%{plugin_instance}> + +=item B<%{type}> + +=item B<%{type_instance}> + +These placeholders are replaced by the identifier field of the same name. + +=item B<%{ds:>IB<}> + +These placeholders are replaced by a (hopefully) human readable representation +of the current rate of this data source. If you changed the instance name +(using the B or B targets, see below), it may not be possible to +convert counter values to rates. + +=back + +Please note that these placeholders are B! + +=item B B<"FATAL">|B<"WARNING">|B<"OKAY"> + +Sets the severity of the message. If omitted, the severity B<"WARNING"> is +used. + +=back + +Example: + + + Message "Oops, the %{type_instance} temperature is currently %{ds:value}!" + Severity "WARNING" + + =item B Replaces parts of the identifier using regular expressions. diff --git a/src/target_notification.c b/src/target_notification.c new file mode 100644 index 00000000..f77d3387 --- /dev/null +++ b/src/target_notification.c @@ -0,0 +1,323 @@ +/** + * collectd - src/target_notification.c + * Copyright (C) 2008 Florian Forster + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Florian Forster + **/ + +/* + * First tell the compiler to stick to the C99 and POSIX standards as close as + * possible. + */ +#ifndef __STRICT_ANSI__ /* {{{ */ +# define __STRICT_ANSI__ +#endif + +#ifndef _ISOC99_SOURCE +# define _ISOC99_SOURCE +#endif + +#ifdef _POSIX_C_SOURCE +# undef _POSIX_C_SOURCE +#endif +#define _POSIX_C_SOURCE 200112L + +#if 0 +/* Single UNIX needed for strdup. */ +#ifdef _XOPEN_SOURCE +# undef _XOPEN_SOURCE +#endif +#define _XOPEN_SOURCE 500 +#endif + +#ifndef _REENTRANT +# define _REENTRANT +#endif + +#ifndef _THREAD_SAFE +# define _THREAD_SAFE +#endif + +#ifdef _GNU_SOURCE +# undef _GNU_SOURCE +#endif +/* }}} */ + +#include "collectd.h" +#include "common.h" +#include "filter_chain.h" +#include "utils_cache.h" +#include "utils_subst.h" + +struct tn_data_s +{ + int severity; + char *message; +}; +typedef struct tn_data_s tn_data_t; + +static int tn_config_add_severity (tn_data_t *data, /* {{{ */ + const oconfig_item_t *ci) +{ + if ((ci->values_num != 1) + || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("Target `notification': The `%s' option requires exactly one string " + "argument.", ci->key); + return (-1); + } + + if ((strcasecmp ("FAILURE", ci->values[0].value.string) == 0) + || (strcasecmp ("CRITICAL", ci->values[0].value.string) == 0)) + data->severity = NOTIF_FAILURE; + else if ((strcasecmp ("WARNING", ci->values[0].value.string) == 0) + || (strcasecmp ("WARN", ci->values[0].value.string) == 0)) + data->severity = NOTIF_WARNING; + else if (strcasecmp ("OKAY", ci->values[0].value.string) == 0) + data->severity = NOTIF_OKAY; + else + { + WARNING ("Target `notification': Unknown severity `%s'. " + "Will use `FAILURE' instead.", + ci->values[0].value.string); + data->severity = NOTIF_FAILURE; + } + + return (0); +} /* }}} int tn_config_add_severity */ + +static int tn_config_add_string (char **dest, /* {{{ */ + const oconfig_item_t *ci) +{ + char *temp; + + if (dest == NULL) + return (-EINVAL); + + if ((ci->values_num != 1) + || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("Target `notification': The `%s' option requires exactly one string " + "argument.", ci->key); + return (-1); + } + + if (ci->values[0].value.string[0] == 0) + { + ERROR ("Target `notification': The `%s' option does not accept empty strings.", + ci->key); + return (-1); + } + + temp = sstrdup (ci->values[0].value.string); + if (temp == NULL) + { + ERROR ("tn_config_add_string: sstrdup failed."); + return (-1); + } + + free (*dest); + *dest = temp; + + return (0); +} /* }}} int tn_config_add_string */ + +static int tn_destroy (void **user_data) /* {{{ */ +{ + tn_data_t *data; + + if (user_data == NULL) + return (-EINVAL); + + data = *user_data; + if (data == NULL) + return (0); + + sfree (data->message); + sfree (data); + + return (0); +} /* }}} int tn_destroy */ + +static int tn_create (const oconfig_item_t *ci, void **user_data) /* {{{ */ +{ + tn_data_t *data; + int status; + int i; + + data = (tn_data_t *) malloc (sizeof (*data)); + if (data == NULL) + { + ERROR ("tn_create: malloc failed."); + return (-ENOMEM); + } + memset (data, 0, sizeof (*data)); + + data->message = NULL; + data->severity = 0; + + status = 0; + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + + if (strcasecmp ("Message", child->key) == 0) + status = tn_config_add_string (&data->message, child); + else if (strcasecmp ("Severity", child->key) == 0) + status = tn_config_add_severity (data, child); + else + { + ERROR ("Target `notification': The `%s' configuration option is not understood " + "and will be ignored.", child->key); + status = 0; + } + + if (status != 0) + break; + } + + /* Additional sanity-checking */ + while (status == 0) + { + if ((data->severity != NOTIF_FAILURE) + && (data->severity != NOTIF_WARNING) + && (data->severity != NOTIF_OKAY)) + { + DEBUG ("Target `notification': Setting " + "the default severity `WARNING'."); + data->severity = NOTIF_WARNING; + } + + if (data->message == NULL) + { + ERROR ("Target `notification': No `Message' option has been specified. " + "Without it, the `Notification' target is useless."); + status = -1; + } + + break; + } + + if (status != 0) + { + tn_destroy ((void *) data); + return (status); + } + + *user_data = data; + return (0); +} /* }}} int tn_create */ + +static int tn_invoke (const data_set_t *ds, value_list_t *vl, /* {{{ */ + notification_meta_t **meta, void **user_data) +{ + tn_data_t *data; + notification_t n; + char temp[NOTIF_MAX_MSG_LEN]; + + gauge_t *rates; + int rates_failed; + + int i; + + if ((ds == NULL) || (vl == NULL) || (user_data == NULL)) + return (-EINVAL); + + data = *user_data; + if (data == NULL) + { + ERROR ("Target `notification': Invoke: `data' is NULL."); + return (-EINVAL); + } + + /* Initialize the structure. */ + memset (&n, 0, sizeof (n)); + n.severity = data->severity; + n.time = time (NULL); + sstrncpy (n.message, data->message, sizeof (n.message)); + sstrncpy (n.host, vl->host, sizeof (n.host)); + sstrncpy (n.plugin, vl->plugin, sizeof (n.plugin)); + sstrncpy (n.plugin_instance, vl->plugin_instance, + sizeof (n.plugin_instance)); + sstrncpy (n.type, vl->type, sizeof (n.type)); + sstrncpy (n.type_instance, vl->type_instance, + sizeof (n.type_instance)); + n.meta = NULL; + +#define REPLACE_FIELD(t,v) \ + if (subst_string (temp, sizeof (temp), n.message, t, v) != NULL) \ + sstrncpy (n.message, temp, sizeof (n.message)); + REPLACE_FIELD ("%{host}", n.host); + REPLACE_FIELD ("%{plugin}", n.plugin); + REPLACE_FIELD ("%{plugin_instance}", n.plugin_instance); + REPLACE_FIELD ("%{type}", n.type); + REPLACE_FIELD ("%{type_instance}", n.type_instance); + + rates_failed = 0; + rates = NULL; + for (i = 0; i < ds->ds_num; i++) + { + char template[DATA_MAX_NAME_LEN]; + char value_str[DATA_MAX_NAME_LEN]; + + ssnprintf (template, sizeof (template), "%%{ds:%s}", ds->ds[i].name); + + if (ds->ds[i].type != DS_TYPE_GAUGE) + { + if ((rates == NULL) && (rates_failed == 0)) + { + rates = uc_get_rate (ds, vl); + if (rates == NULL) + rates_failed = 1; + } + } + + /* If this is a gauge value, use the current value. */ + if (ds->ds[i].type == DS_TYPE_GAUGE) + ssnprintf (value_str, sizeof (value_str), + "%g", (double) vl->values[i].gauge); + /* If it's a counter, try to use the current rate. This may fail, if the + * value has been renamed. */ + else if (rates != NULL) + ssnprintf (value_str, sizeof (value_str), + "%g", (double) rates[i]); + /* Since we don't know any better, use the string `unknown'. */ + else + sstrncpy (value_str, "unknown", sizeof (value_str)); + + REPLACE_FIELD (template, value_str); + } + sfree (rates); + + plugin_dispatch_notification (&n); + + return (FC_TARGET_CONTINUE); +} /* }}} int tn_invoke */ + +void module_register (void) +{ + target_proc_t tproc; + + memset (&tproc, 0, sizeof (tproc)); + tproc.create = tn_create; + tproc.destroy = tn_destroy; + tproc.invoke = tn_invoke; + fc_register_target ("notification", tproc); +} /* module_register */ + +/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */ + -- 2.11.0