#include "plugin.h"
#include "utils_avltree.h"
#include "utils_cache.h"
+#include "utils_threshold.h"
#include <assert.h>
#include <pthread.h>
/*
- * Private data structures
- * {{{ */
-#define UT_FLAG_INVERT 0x01
-#define UT_FLAG_PERSIST 0x02
-#define UT_FLAG_PERCENTAGE 0x04
-#define UT_FLAG_INTERESTING 0x08
-typedef struct threshold_s
-{
- char host[DATA_MAX_NAME_LEN];
- char plugin[DATA_MAX_NAME_LEN];
- char plugin_instance[DATA_MAX_NAME_LEN];
- char type[DATA_MAX_NAME_LEN];
- char type_instance[DATA_MAX_NAME_LEN];
- char data_source[DATA_MAX_NAME_LEN];
- gauge_t warning_min;
- gauge_t warning_max;
- gauge_t failure_min;
- gauge_t failure_max;
- gauge_t hysteresis;
- unsigned int flags;
- int hits;
- struct threshold_s *next;
-} threshold_t;
-/* }}} */
-
-/*
- * Private (static) variables
- * {{{ */
-static c_avl_tree_t *threshold_tree = NULL;
-static pthread_mutex_t threshold_lock = PTHREAD_MUTEX_INITIALIZER;
-/* }}} */
-
-/*
* Threshold management
* ====================
* The following functions add, delete, search, etc. configured thresholds to
* the underlying AVL trees.
*/
-/*
- * threshold_t *threshold_get
- *
- * Retrieve one specific threshold configuration. For looking up a threshold
- * matching a value_list_t, see "threshold_search" below. Returns NULL if the
- * specified threshold doesn't exist.
- */
-static threshold_t *threshold_get (const char *hostname,
- const char *plugin, const char *plugin_instance,
- const char *type, const char *type_instance)
-{ /* {{{ */
- char name[6 * DATA_MAX_NAME_LEN];
- threshold_t *th = NULL;
-
- format_name (name, sizeof (name),
- (hostname == NULL) ? "" : hostname,
- (plugin == NULL) ? "" : plugin, plugin_instance,
- (type == NULL) ? "" : type, type_instance);
- name[sizeof (name) - 1] = '\0';
-
- if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
- return (th);
- else
- return (NULL);
-} /* }}} threshold_t *threshold_get */
/*
* int ut_threshold_add
return (status);
} /* }}} int ut_threshold_add */
-/*
- * threshold_t *threshold_search
- *
- * Searches for a threshold configuration using all the possible variations of
- * "Host", "Plugin" and "Type" blocks. Returns NULL if no threshold could be
- * found.
- * XXX: This is likely the least efficient function in collectd.
- */
-static threshold_t *threshold_search (const value_list_t *vl)
-{ /* {{{ */
- threshold_t *th;
-
- if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
- vl->type, vl->type_instance)) != NULL)
- return (th);
- else if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
- vl->type, NULL)) != NULL)
- return (th);
- else if ((th = threshold_get (vl->host, vl->plugin, NULL,
- vl->type, vl->type_instance)) != NULL)
- return (th);
- else if ((th = threshold_get (vl->host, vl->plugin, NULL,
- vl->type, NULL)) != NULL)
- return (th);
- else if ((th = threshold_get (vl->host, "", NULL,
- vl->type, vl->type_instance)) != NULL)
- return (th);
- else if ((th = threshold_get (vl->host, "", NULL,
- vl->type, NULL)) != NULL)
- return (th);
- else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
- vl->type, vl->type_instance)) != NULL)
- return (th);
- else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
- vl->type, NULL)) != NULL)
- return (th);
- else if ((th = threshold_get ("", vl->plugin, NULL,
- vl->type, vl->type_instance)) != NULL)
- return (th);
- else if ((th = threshold_get ("", vl->plugin, NULL,
- vl->type, NULL)) != NULL)
- return (th);
- else if ((th = threshold_get ("", "", NULL,
- vl->type, vl->type_instance)) != NULL)
- return (th);
- else if ((th = threshold_get ("", "", NULL,
- vl->type, NULL)) != NULL)
- return (th);
-
- return (NULL);
-} /* }}} threshold_t *threshold_search */
-
/*
* Configuration
* =============
for (i = 0; i < ci->children_num; i++)
{
oconfig_item_t *option = ci->children + i;
- status = 0;
if (strcasecmp ("Instance", option->key) == 0)
status = ut_config_type_instance (&th, option);
status = cf_util_get_flag (option, &th.flags, UT_FLAG_INVERT);
else if (strcasecmp ("Persist", option->key) == 0)
status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERSIST);
+ else if (strcasecmp ("PersistOK", option->key) == 0)
+ status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERSIST_OK);
else if (strcasecmp ("Percentage", option->key) == 0)
status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERCENTAGE);
else if (strcasecmp ("Hits", option->key) == 0)
for (i = 0; i < ci->children_num; i++)
{
oconfig_item_t *option = ci->children + i;
- status = 0;
if (strcasecmp ("Type", option->key) == 0)
status = ut_config_type (&th, option);
for (i = 0; i < ci->children_num; i++)
{
oconfig_item_t *option = ci->children + i;
- status = 0;
if (strcasecmp ("Type", option->key) == 0)
status = ut_config_type (&th, option);
return (status);
} /* int ut_config_host */
-
-int ut_config (oconfig_item_t *ci)
-{
- int i;
- int status = 0;
-
- threshold_t th;
-
- if (ci->values_num != 0)
- {
- ERROR ("threshold values: The `Threshold' block may not have any "
- "arguments.");
- return (-1);
- }
-
- if (threshold_tree == NULL)
- {
- threshold_tree = c_avl_create ((void *) strcmp);
- if (threshold_tree == NULL)
- {
- ERROR ("ut_config: c_avl_create failed.");
- return (-1);
- }
- }
-
- memset (&th, '\0', sizeof (th));
- th.warning_min = NAN;
- th.warning_max = NAN;
- th.failure_min = NAN;
- th.failure_max = NAN;
-
- th.hits = 0;
- th.hysteresis = 0;
- th.flags = UT_FLAG_INTERESTING; /* interesting by default */
-
- for (i = 0; i < ci->children_num; i++)
- {
- oconfig_item_t *option = ci->children + i;
- status = 0;
-
- if (strcasecmp ("Type", option->key) == 0)
- status = ut_config_type (&th, option);
- else if (strcasecmp ("Plugin", option->key) == 0)
- status = ut_config_plugin (&th, option);
- else if (strcasecmp ("Host", option->key) == 0)
- status = ut_config_host (&th, option);
- else
- {
- WARNING ("threshold values: Option `%s' not allowed here.", option->key);
- status = -1;
- }
-
- if (status != 0)
- break;
- }
-
- return (status);
-} /* int um_config */
/*
* End of the functions used to configure threshold values.
*/
if ( (th->hits != 0) )
{
int hits = uc_get_hits(ds,vl);
- /* The STATE_OKAY always reset hits, or if hits reaise the limit */
- if ( (state == STATE_OKAY) || (hits > th->hits) )
+ /* STATE_OKAY resets hits unless PERSIST_OK flag is set. Hits resets if
+ * threshold is hit. */
+ if ( ( (state == STATE_OKAY) && ((th->flags & UT_FLAG_PERSIST_OK) == 0) ) || (hits > th->hits) )
{
DEBUG("ut_report_state: reset uc_get_hits = 0");
uc_set_hits(ds,vl,0); /* reset hit counter and notify */
state_old = uc_get_state (ds, vl);
- /* If the state didn't change, only report if `persistent' is specified and
- * the state is not `okay'. */
+ /* If the state didn't change, report if `persistent' is specified. If the
+ * state is `okay', then only report if `persist_ok` flag is set. */
if (state == state_old)
{
if ((th->flags & UT_FLAG_PERSIST) == 0)
return (0);
- else if (state == STATE_OKAY)
+ else if ( (state == STATE_OKAY) && ((th->flags & UT_FLAG_PERSIST_OK) == 0) )
return (0);
}
if (state != state_old)
uc_set_state (ds, vl, state);
- NOTIFICATION_INIT_VL (&n, vl, ds);
+ NOTIFICATION_INIT_VL (&n, vl);
buf = n.message;
bufsize = sizeof (n.message);
if (state == STATE_OKAY)
{
if (state_old == STATE_MISSING)
- status = ssnprintf (buf, bufsize,
- ": Value is no longer missing.");
+ ssnprintf (buf, bufsize, ": Value is no longer missing.");
else
- status = ssnprintf (buf, bufsize,
- ": All data sources are within range again.");
- buf += status;
- bufsize -= status;
+ ssnprintf (buf, bufsize,
+ ": All data sources are within range again. "
+ "Current value of \"%s\" is %f.",
+ ds->ds[ds_index].name, values[ds_index]);
}
else
{
{
if (!isnan (min) && !isnan (max))
{
- status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
+ ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
"%f. That is within the %s region of %f%s and %f%s.",
ds->ds[ds_index].name, values[ds_index],
(state == STATE_ERROR) ? "failure" : "warning",
}
else
{
- status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
- "%f. That is %s the %s threshold of %f%s.",
- ds->ds[ds_index].name, values[ds_index],
- isnan (min) ? "below" : "above",
- (state == STATE_ERROR) ? "failure" : "warning",
- isnan (min) ? max : min,
- ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
+ ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
+ "%f. That is %s the %s threshold of %f%s.",
+ ds->ds[ds_index].name, values[ds_index],
+ isnan (min) ? "below" : "above",
+ (state == STATE_ERROR) ? "failure" : "warning",
+ isnan (min) ? max : min,
+ ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
}
}
+ else if (th->flags & UT_FLAG_PERCENTAGE)
+ {
+ gauge_t value;
+ gauge_t sum;
+ int i;
+
+ sum = 0.0;
+ for (i = 0; i < vl->values_len; i++)
+ {
+ if (isnan (values[i]))
+ continue;
+
+ sum += values[i];
+ }
+
+ if (sum == 0.0)
+ value = NAN;
+ else
+ value = 100.0 * values[ds_index] / sum;
+
+ ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
+ "%g (%.2f%%). That is %s the %s threshold of %.2f%%.",
+ ds->ds[ds_index].name, values[ds_index], value,
+ (value < min) ? "below" : "above",
+ (state == STATE_ERROR) ? "failure" : "warning",
+ (value < min) ? min : max);
+ }
else /* is not inverted */
{
- status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
- "%f. That is %s the %s threshold of %f%s.",
- ds->ds[ds_index].name, values[ds_index],
- (values[ds_index] < min) ? "below" : "above",
- (state == STATE_ERROR) ? "failure" : "warning",
- (values[ds_index] < min) ? min : max,
- ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
+ ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
+ "%f. That is %s the %s threshold of %f.",
+ ds->ds[ds_index].name, values[ds_index],
+ (values[ds_index] < min) ? "below" : "above",
+ (state == STATE_ERROR) ? "failure" : "warning",
+ (values[ds_index] < min) ? min : max);
}
- buf += status;
- bufsize -= status;
}
plugin_dispatch_notification (&n);
/* XXX: This is an experimental code, not optimized, not fast, not reliable,
* and probably, do not work as you expect. Enjoy! :D */
- if ( (th->hysteresis > 0) && ((prev_state = uc_get_state(ds,vl)) != STATE_OKAY) )
- {
- switch(prev_state)
+ if (th->hysteresis > 0)
+ {
+ prev_state = uc_get_state(ds,vl);
+ /* The purpose of hysteresis is elliminating flapping state when the value
+ * oscilates around the thresholds. In other words, what is important is
+ * the previous state; if the new value would trigger a transition, make
+ * sure that we artificially widen the range which is considered to apply
+ * for the previous state, and only trigger the notification if the value
+ * is outside of this expanded range.
+ *
+ * There is no hysteresis for the OKAY state.
+ * */
+ gauge_t hysteresis_for_warning = 0, hysteresis_for_failure = 0;
+ switch (prev_state)
{
case STATE_ERROR:
- if ( (!isnan (th->failure_min) && ((th->failure_min + th->hysteresis) < values[ds_index])) ||
- (!isnan (th->failure_max) && ((th->failure_max - th->hysteresis) > values[ds_index])) )
- return (STATE_OKAY);
- else
- is_failure++;
+ hysteresis_for_failure = th->hysteresis;
+ break;
case STATE_WARNING:
- if ( (!isnan (th->warning_min) && ((th->warning_min + th->hysteresis) < values[ds_index])) ||
- (!isnan (th->warning_max) && ((th->warning_max - th->hysteresis) > values[ds_index])) )
- return (STATE_OKAY);
- else
- is_warning++;
- }
+ hysteresis_for_warning = th->hysteresis;
+ break;
+ case STATE_OKAY:
+ /* do nothing -- the hysteresis only applies to the non-normal states */
+ break;
+ }
+
+ if ((!isnan (th->failure_min) && (th->failure_min + hysteresis_for_failure > values[ds_index]))
+ || (!isnan (th->failure_max) && (th->failure_max - hysteresis_for_failure < values[ds_index])))
+ is_failure++;
+
+ if ((!isnan (th->warning_min) && (th->warning_min + hysteresis_for_warning > values[ds_index]))
+ || (!isnan (th->warning_max) && (th->warning_max - hysteresis_for_warning < values[ds_index])))
+ is_warning++;
+
}
else { /* no hysteresis */
if ((!isnan (th->failure_min) && (th->failure_min > values[ds_index]))
if ((!isnan (th->warning_min) && (th->warning_min > values[ds_index]))
|| (!isnan (th->warning_max) && (th->warning_max < values[ds_index])))
is_warning++;
- }
+ }
if (is_failure != 0)
return (STATE_ERROR);
} /* }}} int ut_check_one_threshold */
/*
- * int ut_check_threshold (PUBLIC)
+ * int ut_check_threshold
*
* Gets a list of matching thresholds and searches for the worst status by one
* of the thresholds. Then reports that status using the ut_report_state
- * function above.
+ * function above.
* Returns zero on success and if no threshold has been configured. Returns
* less than zero on failure.
*/
return (0);
} /* }}} int ut_check_threshold */
+/*
+ * int ut_missing
+ *
+ * This function is called whenever a value goes "missing".
+ */
+static int ut_missing (const value_list_t *vl,
+ __attribute__((unused)) user_data_t *ud)
+{ /* {{{ */
+ threshold_t *th;
+ cdtime_t missing_time;
+ char identifier[6 * DATA_MAX_NAME_LEN];
+ notification_t n;
+ cdtime_t now;
+
+ if (threshold_tree == NULL)
+ return (0);
+
+ th = threshold_search (vl);
+ /* dispatch notifications for "interesting" values only */
+ if ((th == NULL) || ((th->flags & UT_FLAG_INTERESTING) == 0))
+ return (0);
+
+ now = cdtime ();
+ missing_time = now - vl->time;
+ FORMAT_VL (identifier, sizeof (identifier), vl);
+
+ NOTIFICATION_INIT_VL (&n, vl);
+ ssnprintf (n.message, sizeof (n.message),
+ "%s has not been updated for %.3f seconds.",
+ identifier, CDTIME_T_TO_DOUBLE (missing_time));
+ n.time = now;
+
+ plugin_dispatch_notification (&n);
+
+ return (0);
+} /* }}} int ut_missing */
+
+int ut_config (oconfig_item_t *ci)
+{ /* {{{ */
+ int i;
+ int status = 0;
+ int old_size = c_avl_size (threshold_tree);
+
+ threshold_t th;
+
+ if (threshold_tree == NULL)
+ {
+ threshold_tree = c_avl_create ((void *) strcmp);
+ if (threshold_tree == NULL)
+ {
+ ERROR ("ut_config: c_avl_create failed.");
+ return (-1);
+ }
+ }
+
+ memset (&th, '\0', sizeof (th));
+ th.warning_min = NAN;
+ th.warning_max = NAN;
+ th.failure_min = NAN;
+ th.failure_max = NAN;
+
+ th.hits = 0;
+ th.hysteresis = 0;
+ th.flags = UT_FLAG_INTERESTING; /* interesting by default */
+
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *option = ci->children + i;
+
+ if (strcasecmp ("Type", option->key) == 0)
+ status = ut_config_type (&th, option);
+ else if (strcasecmp ("Plugin", option->key) == 0)
+ status = ut_config_plugin (&th, option);
+ else if (strcasecmp ("Host", option->key) == 0)
+ status = ut_config_host (&th, option);
+ else
+ {
+ WARNING ("threshold values: Option `%s' not allowed here.", option->key);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ /* register callbacks if this is the first time we see a valid config */
+ if ((old_size == 0) && (c_avl_size (threshold_tree) > 0))
+ {
+ plugin_register_missing ("threshold", ut_missing,
+ /* user data = */ NULL);
+ plugin_register_write ("threshold", ut_check_threshold,
+ /* user data = */ NULL);
+ }
+
+ return (status);
+} /* }}} int um_config */
+
void module_register (void)
{
plugin_register_complex_config ("threshold", ut_config);
- plugin_register_write ("threshold", ut_check_threshold,
- /* user data = */ NULL);
}
/* vim: set sw=2 ts=8 sts=2 tw=78 et fdm=marker : */