Merge pull request #396 from radford/perl-debug-compile-fix
[collectd.git] / src / threshold.c
index 7bbc59e..8815a00 100644 (file)
 #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
@@ -170,58 +113,6 @@ static int ut_threshold_add (const threshold_t *th)
   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
  * =============
@@ -378,6 +269,8 @@ static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
       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)
@@ -512,57 +405,6 @@ static int ut_config_host (const threshold_t *th_orig, oconfig_item_t *ci)
 
   return (status);
 } /* int ut_config_host */
-
-int ut_config (oconfig_item_t *ci)
-{
-  int i;
-  int status = 0;
-
-  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;
-    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.
  */
@@ -594,8 +436,9 @@ static int ut_report_state (const data_set_t *ds,
   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 */
@@ -608,13 +451,13 @@ static int ut_report_state (const data_set_t *ds,
 
   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);
   }
 
@@ -676,7 +519,9 @@ static int ut_report_state (const data_set_t *ds,
           ": Value is no longer missing.");
     else
       status = ssnprintf (buf, bufsize,
-          ": All data sources are within range again.");
+          ": All data sources are within range again. "
+          "Current value of \"%s\" is %f.",
+          ds->ds[ds_index].name, values[ds_index]);
     buf += status;
     bufsize -= status;
   }
@@ -794,23 +639,40 @@ static int ut_check_one_data_source (const data_set_t *ds,
 
   /* 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]))
@@ -820,7 +682,7 @@ static int ut_check_one_data_source (const data_set_t *ds,
     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);
@@ -909,7 +771,7 @@ static int ut_check_one_threshold (const data_set_t *ds,
  *
  * 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.
  */
@@ -989,31 +851,92 @@ static int ut_missing (const value_list_t *vl,
   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);
-  if (th == NULL)
+  /* dispatch notifications for "interesting" values only */
+  if ((th == NULL) || ((th->flags & UT_FLAG_INTERESTING) == 0))
     return (0);
 
-  missing_time = cdtime () - vl->time;
+  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;
+
+  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;
+    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;
+  }
+
+  if (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_missing ("threshold", ut_missing,
-      /* user data = */ NULL);
-  plugin_register_write ("threshold", ut_check_threshold,
-      /* user data = */ NULL);
 }
 
 /* vim: set sw=2 ts=8 sts=2 tw=78 et fdm=marker : */