src/utils_threshold.c: Cleanups.
[collectd.git] / src / utils_threshold.c
index ddce422..d831a35 100644 (file)
@@ -1,6 +1,9 @@
 /**
  * collectd - src/utils_threshold.c
- * Copyright (C) 2007,2008  Florian octo Forster
+ * Copyright (C) 2007-2009  Florian octo Forster
+ * Copyright (C) 2008-2009  Sebastian Harl
+ * Copyright (C) 2009       Andrés J. Díaz
+ *
  *
  * 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
@@ -17,6 +20,8 @@
  *
  * Author:
  *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Andrés J. Díaz <ajdiaz at connectical.com>
  **/
 
 #include "collectd.h"
@@ -24,6 +29,8 @@
 #include "plugin.h"
 #include "utils_avltree.h"
 #include "utils_cache.h"
+#include "utils_threshold.h"
+#include "utils_subst.h"
 
 #include <assert.h>
 #include <pthread.h>
  * {{{ */
 #define UT_FLAG_INVERT  0x01
 #define UT_FLAG_PERSIST 0x02
-
-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;
-  int flags;
-  struct threshold_s *next;
-} threshold_t;
+#define UT_FLAG_PERCENTAGE 0x04
+#define UT_FLAG_INTERESTING 0x08
 /* }}} */
 
 /*
@@ -184,9 +177,8 @@ static int ut_config_type_instance (threshold_t *th, oconfig_item_t *ci)
     return (-1);
   }
 
-  strncpy (th->type_instance, ci->values[0].value.string,
+  sstrncpy (th->type_instance, ci->values[0].value.string,
       sizeof (th->type_instance));
-  th->type_instance[sizeof (th->type_instance) - 1] = '\0';
 
   return (0);
 } /* int ut_config_type_instance */
@@ -227,41 +219,91 @@ static int ut_config_type_min (threshold_t *th, oconfig_item_t *ci)
   return (0);
 } /* int ut_config_type_min */
 
-static int ut_config_type_invert (threshold_t *th, oconfig_item_t *ci)
+static int ut_config_type_hits (threshold_t *th, oconfig_item_t *ci)
 {
   if ((ci->values_num != 1)
-      || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
   {
-    WARNING ("threshold values: The `Invert' option needs exactly one "
-       "boolean argument.");
+    WARNING ("threshold values: The `%s' option needs exactly one "
+      "number argument.", ci->key);
     return (-1);
   }
 
-  if (ci->values[0].value.boolean)
-    th->flags |= UT_FLAG_INVERT;
-  else
-    th->flags &= ~UT_FLAG_INVERT;
+  th->hits = ci->values[0].value.number;
 
   return (0);
-} /* int ut_config_type_invert */
+} /* int ut_config_type_hits */
 
-static int ut_config_type_persist (threshold_t *th, oconfig_item_t *ci)
+static int ut_config_type_hysteresis (threshold_t *th, oconfig_item_t *ci)
 {
   if ((ci->values_num != 1)
-      || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
   {
-    WARNING ("threshold values: The `Persist' option needs exactly one "
-       "boolean argument.");
+    WARNING ("threshold values: The `%s' option needs exactly one "
+      "number argument.", ci->key);
     return (-1);
   }
 
-  if (ci->values[0].value.boolean)
-    th->flags |= UT_FLAG_PERSIST;
-  else
-    th->flags &= ~UT_FLAG_PERSIST;
+  th->hysteresis = ci->values[0].value.number;
 
   return (0);
-} /* int ut_config_type_persist */
+} /* int ut_config_type_hysteresis */
+
+static int ut_config_type_message (threshold_t *th, oconfig_item_t *ci)
+{
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `%s' option needs exactly one "
+      "string argument.", ci->key);
+    return (-1);
+  }
+
+  if (ci->values[0].value.string[0] == 0)
+  {
+    WARNING ("threshold values: The `%s' option does not accept empty strings.",
+        ci->key);
+    return (-1);
+  }
+
+  th->message = strdup (ci->values[0].value.string);
+  if (th->message == NULL)
+  {
+    ERROR ("ut_config_type_message: sstrdup failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* int ut_config_type_message */
+
+static int ut_config_type_missingmessage (threshold_t *th, oconfig_item_t *ci)
+{
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `%s' option needs exactly one "
+      "string argument.", ci->key);
+    return (-1);
+  }
+
+  if (ci->values[0].value.string[0] == 0)
+  {
+    WARNING ("threshold values: The `%s' option does not accept empty strings.",
+        ci->key);
+    return (-1);
+  }
+
+  th->missing_message = strdup (ci->values[0].value.string);
+  if (th->missing_message == NULL)
+  {
+    ERROR ("ut_config_type_missingmessage: sstrdup failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* int ut_config_type_missingmessage */
 
 static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
 {
@@ -284,13 +326,16 @@ static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
   }
 
   memcpy (&th, th_orig, sizeof (th));
-  strncpy (th.type, ci->values[0].value.string, sizeof (th.type));
-  th.type[sizeof (th.type) - 1] = '\0';
+  sstrncpy (th.type, ci->values[0].value.string, sizeof (th.type));
 
   th.warning_min = NAN;
   th.warning_max = NAN;
   th.failure_min = NAN;
   th.failure_max = NAN;
+  th.hits = 0;
+  th.hysteresis = 0;
+  th.message = NULL;
+  th.flags = UT_FLAG_INTERESTING; /* interesting by default */
 
   for (i = 0; i < ci->children_num; i++)
   {
@@ -307,10 +352,22 @@ static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
     else if ((strcasecmp ("WarningMin", option->key) == 0)
        || (strcasecmp ("FailureMin", option->key) == 0))
       status = ut_config_type_min (&th, option);
+    else if (strcasecmp ("Interesting", option->key) == 0)
+      status = cf_util_get_flag (option, &th.flags, UT_FLAG_INTERESTING);
     else if (strcasecmp ("Invert", option->key) == 0)
-      status = ut_config_type_invert (&th, option);
+      status = cf_util_get_flag (option, &th.flags, UT_FLAG_INVERT);
     else if (strcasecmp ("Persist", option->key) == 0)
-      status = ut_config_type_persist (&th, option);
+      status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERSIST);
+    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)
+      status = ut_config_type_hits (&th, option);
+    else if (strcasecmp ("Hysteresis", option->key) == 0)
+      status = ut_config_type_hysteresis (&th, option);
+    else if (strcasecmp ("Message", option->key) == 0)
+      status = ut_config_type_message (&th, option);
+    else if (strcasecmp ("MissingMessage", option->key) == 0)
+      status = ut_config_type_missingmessage (&th, option);
     else
     {
       WARNING ("threshold values: Option `%s' not allowed inside a `Type' "
@@ -340,9 +397,8 @@ static int ut_config_plugin_instance (threshold_t *th, oconfig_item_t *ci)
     return (-1);
   }
 
-  strncpy (th->plugin_instance, ci->values[0].value.string,
+  sstrncpy (th->plugin_instance, ci->values[0].value.string,
       sizeof (th->plugin_instance));
-  th->plugin_instance[sizeof (th->plugin_instance) - 1] = '\0';
 
   return (0);
 } /* int ut_config_plugin_instance */
@@ -369,8 +425,7 @@ static int ut_config_plugin (const threshold_t *th_orig, oconfig_item_t *ci)
   }
 
   memcpy (&th, th_orig, sizeof (th));
-  strncpy (th.plugin, ci->values[0].value.string, sizeof (th.plugin));
-  th.plugin[sizeof (th.plugin) - 1] = '\0';
+  sstrncpy (th.plugin, ci->values[0].value.string, sizeof (th.plugin));
 
   for (i = 0; i < ci->children_num; i++)
   {
@@ -417,8 +472,7 @@ static int ut_config_host (const threshold_t *th_orig, oconfig_item_t *ci)
   }
 
   memcpy (&th, th_orig, sizeof (th));
-  strncpy (th.host, ci->values[0].value.string, sizeof (th.host));
-  th.host[sizeof (th.host) - 1] = '\0';
+  sstrncpy (th.host, ci->values[0].value.string, sizeof (th.host));
 
   for (i = 0; i < ci->children_num; i++)
   {
@@ -472,6 +526,10 @@ int ut_config (const oconfig_item_t *ci)
   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++)
   {
@@ -501,51 +559,144 @@ int ut_config (const oconfig_item_t *ci)
  */
 /* }}} */
 
-static threshold_t *threshold_search (const data_set_t *ds,
-    const value_list_t *vl)
+static threshold_t *threshold_search (const value_list_t *vl)
 {
   threshold_t *th;
 
   if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
-         ds->type, vl->type_instance)) != NULL)
+         vl->type, vl->type_instance)) != NULL)
     return (th);
   else if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
-         ds->type, NULL)) != NULL)
+         vl->type, NULL)) != NULL)
     return (th);
   else if ((th = threshold_get (vl->host, vl->plugin, NULL,
-         ds->type, vl->type_instance)) != NULL)
+         vl->type, vl->type_instance)) != NULL)
     return (th);
   else if ((th = threshold_get (vl->host, vl->plugin, NULL,
-         ds->type, NULL)) != NULL)
+         vl->type, NULL)) != NULL)
     return (th);
   else if ((th = threshold_get (vl->host, "", NULL,
-         ds->type, vl->type_instance)) != NULL)
+         vl->type, vl->type_instance)) != NULL)
     return (th);
   else if ((th = threshold_get (vl->host, "", NULL,
-         ds->type, NULL)) != NULL)
+         vl->type, NULL)) != NULL)
     return (th);
   else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
-         ds->type, vl->type_instance)) != NULL)
+         vl->type, vl->type_instance)) != NULL)
     return (th);
   else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
-         ds->type, NULL)) != NULL)
+         vl->type, NULL)) != NULL)
     return (th);
   else if ((th = threshold_get ("", vl->plugin, NULL,
-         ds->type, vl->type_instance)) != NULL)
+         vl->type, vl->type_instance)) != NULL)
     return (th);
   else if ((th = threshold_get ("", vl->plugin, NULL,
-         ds->type, NULL)) != NULL)
+         vl->type, NULL)) != NULL)
     return (th);
   else if ((th = threshold_get ("", "", NULL,
-         ds->type, vl->type_instance)) != NULL)
+         vl->type, vl->type_instance)) != NULL)
     return (th);
   else if ((th = threshold_get ("", "", NULL,
-         ds->type, NULL)) != NULL)
+         vl->type, NULL)) != NULL)
     return (th);
 
   return (NULL);
 } /* threshold_t *threshold_search */
 
+/* char *ut_build_message
+ *
+ * Return a custom formated message for dataset, values and previously created
+ * notification (which must include time and other fields), if th is present,
+ * templates for threshold will be interpreted, if th is NULL these will be
+ * skipped.
+ */
+int ut_build_message(char *out, size_t bufsize, const char *fmt,
+    const data_set_t *ds, int ds_index, const value_list_t *vl, const gauge_t *values,
+    const notification_t *n, const threshold_t *th)
+{
+  /* TODO: We could provide here a way to use meta information on thresholds
+   * directly in the future. */
+  char msg[NOTIF_MAX_MSG_LEN];
+  int rates_failed;
+
+  int i;
+
+  sstrncpy (msg, fmt, sizeof (msg));
+
+#define REPLACE_FIELD(t,v) do { \
+  char temp[NOTIF_MAX_MSG_LEN]; \
+  if (subst_string (temp, sizeof (temp), msg, (t), (v)) != NULL) \
+    sstrncpy (msg, temp, sizeof (msg)); \
+} while (0)
+
+#define REPLACE_FIELD_F(t,f) do { \
+  char f_str[64]; \
+  ssnprintf (f_str, sizeof (f_str), "%g", (f)); \
+  REPLACE_FIELD ((t), f_str); \
+} while (0)
+
+#define REPLACE_FIELD_I(t,i) do { \
+  char i_str[64]; \
+  ssnprintf (i_str, sizeof (i_str), "%i", (i)); \
+  REPLACE_FIELD ((t), i_str); \
+} while (0)
+
+  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);
+
+  /* ds_index is set to -1 if the value is missing (there is no data source /
+   * value we could reasonably use. */
+  if (ds_index >= 0)
+  {
+    REPLACE_FIELD ("%{data_source}", ds->ds[ds_index].name);
+
+    /* This is the offending value, its equivalent to %{ds:value}, if
+     * value is the data_source name. */
+    REPLACE_FIELD_F ("%{value}", (double) values[ds_index]);
+  }
+
+  /* Now replace all %{ds:<template>} like target_notification does */
+  rates_failed = 0;
+  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 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 (values != NULL)
+      ssnprintf (value_str, sizeof (value_str),
+          "%g", (double) values[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);
+  }
+
+  if (th != NULL)
+  {
+    REPLACE_FIELD_F ("%{warning_min}", th->warning_min);
+    REPLACE_FIELD_F ("%{warning_max}", th->warning_max);
+    REPLACE_FIELD_F ("%{failure_min}", th->failure_min);
+    REPLACE_FIELD_F ("%{failure_max}", th->failure_max);
+    REPLACE_FIELD_F ("%{hysteresis}",  th->hysteresis);
+    REPLACE_FIELD_I ("%{hits}",        th->hits);
+  }
+
+  sstrncpy (out, msg, bufsize);
+  return (0);
+} /* int ut_build_message */
+
 /*
  * int ut_report_state
  *
@@ -568,6 +719,22 @@ static int ut_report_state (const data_set_t *ds,
 
   int status;
 
+  /* Check if hits matched */
+  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) )
+    {
+        DEBUG("ut_report_state: reset uc_get_hits = 0");
+        uc_set_hits(ds,vl,0); /* reset hit counter and notify */
+    } else {
+      DEBUG("ut_report_state: th->hits = %d, uc_get_hits = %d",th->hits,uc_get_hits(ds,vl));
+      (void) uc_inc_hits(ds,vl,1); /* increase hit counter */
+      return (0);
+    }
+  } /* end check hits */
+
   state_old = uc_get_state (ds, vl);
 
   /* If the state didn't change, only report if `persistent' is specified and
@@ -597,81 +764,106 @@ static int ut_report_state (const data_set_t *ds,
 
   n.time = vl->time;
 
-  status = snprintf (buf, bufsize, "Host %s, plugin %s",
-      vl->host, vl->plugin);
-  buf += status;
-  bufsize -= status;
-
-  if (vl->plugin_instance[0] != '\0')
+  /* Format custom message if present */
+  if (th->message != NULL)
   {
-    status = snprintf (buf, bufsize, " (instance %s)",
-       vl->plugin_instance);
+    status = ut_build_message (buf, bufsize, th->message,
+        ds, ds_index, vl, values,
+        &n, th);
     buf += status;
     bufsize -= status;
   }
-
-  status = snprintf (buf, bufsize, " type %s", ds->type);
-  buf += status;
-  bufsize -= status;
-
-  if (vl->type_instance[0] != '\0')
+  else /* No custom message. Using default message for threshold */
   {
-    status = snprintf (buf, bufsize, " (instance %s)",
-       vl->type_instance);
+    status = ssnprintf (buf, bufsize, "Host %s, plugin %s",
+        vl->host, vl->plugin);
     buf += status;
     bufsize -= status;
-  }
 
-  /* Send an okay notification */
-  if (state == STATE_OKAY)
-  {
-    status = snprintf (buf, bufsize, ": All data sources are within range again.");
+    if (vl->plugin_instance[0] != '\0')
+    {
+      status = ssnprintf (buf, bufsize, " (instance %s)",
+          vl->plugin_instance);
+      buf += status;
+      bufsize -= status;
+    }
+
+    status = ssnprintf (buf, bufsize, " type %s", vl->type);
     buf += status;
     bufsize -= status;
-  }
-  else
-  {
-    double min;
-    double max;
 
-    min = (state == STATE_ERROR) ? th->failure_min : th->warning_min;
-    max = (state == STATE_ERROR) ? th->failure_max : th->warning_max;
+    if (vl->type_instance[0] != '\0')
+    {
+      status = ssnprintf (buf, bufsize, " (instance %s)",
+          vl->type_instance);
+      buf += status;
+      bufsize -= status;
+    }
 
-    if (th->flags & UT_FLAG_INVERT)
+    /* Build okay notification message */
+    if (state == STATE_OKAY)
+    {
+      status = ssnprintf (buf, bufsize, ": All data sources are within range again.");
+      buf += status;
+      bufsize -= status;
+    }
+    else /* build non-okay notification message */
     {
-      if (!isnan (min) && !isnan (max))
+      double min;
+      double max;
+
+      min = (state == STATE_ERROR) ? th->failure_min : th->warning_min;
+      max = (state == STATE_ERROR) ? th->failure_max : th->warning_max;
+
+      if (th->flags & UT_FLAG_INVERT)
       {
-       status = snprintf (buf, bufsize, ": Data source \"%s\" is currently "
-           "%f. That is within the %s region of %f and %f.",
-           ds->ds[ds_index].name, values[ds_index],
-           (state == STATE_ERROR) ? "failure" : "warning",
-           min, max);
+        if (!isnan (min) && !isnan (max))
+        {
+          status = 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",
+              min, ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "",
+              max, ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
+        }
+        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) ? "%" : "");
+        }
       }
-      else
+      else /* is not inverted */
       {
-       status = snprintf (buf, bufsize, ": Data source \"%s\" is currently "
-           "%f. That is %s the %s threshold of %f.",
-           ds->ds[ds_index].name, values[ds_index],
-           isnan (min) ? "below" : "above",
-           (state == STATE_ERROR) ? "failure" : "warning",
-           isnan (min) ? max : min);
+        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) ? "%" : "");
       }
+      buf += status;
+      bufsize -= status;
     }
-    else /* is not inverted */
-    {
-      status = snprintf (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;
   }
 
+  /* adds meta to notification */
+  plugin_notification_meta_add_string (&n, "DataSource",
+      ds->ds[ds_index].name);
+  plugin_notification_meta_add_double (&n, "CurrentValue", values[ds_index]);
+  plugin_notification_meta_add_double (&n, "WarningMin", th->warning_min);
+  plugin_notification_meta_add_double (&n, "WarningMax", th->warning_max);
+  plugin_notification_meta_add_double (&n, "FailureMin", th->failure_min);
+  plugin_notification_meta_add_double (&n, "FailureMax", th->failure_max);
+
   plugin_dispatch_notification (&n);
 
+  plugin_notification_meta_free (n.meta);
   return (0);
 } /* }}} int ut_report_state */
 
@@ -686,7 +878,7 @@ static int ut_report_state (const data_set_t *ds,
  * Does not fail.
  */
 static int ut_check_one_data_source (const data_set_t *ds,
-    const value_list_t *vl,
+    const value_list_t __attribute__((unused)) *vl,
     const threshold_t *th,
     const gauge_t *values,
     int ds_index)
@@ -694,12 +886,16 @@ static int ut_check_one_data_source (const data_set_t *ds,
   const char *ds_name;
   int is_warning = 0;
   int is_failure = 0;
+  int prev_state = STATE_OKAY;
 
   /* check if this threshold applies to this data source */
-  ds_name = ds->ds[ds_index].name;
-  if ((th->data_source[0] != 0)
-      && (strcmp (ds_name, th->data_source) != 0))
-    return (STATE_OKAY);
+  if (ds != NULL)
+  {
+    ds_name = ds->ds[ds_index].name;
+    if ((th->data_source[0] != 0)
+       && (strcmp (ds_name, th->data_source) != 0))
+      return (STATE_OKAY);
+  }
 
   if ((th->flags & UT_FLAG_INVERT) != 0)
   {
@@ -707,15 +903,39 @@ static int ut_check_one_data_source (const data_set_t *ds,
     is_failure--;
   }
 
-  if ((!isnan (th->failure_min) && (th->failure_min > values[ds_index]))
-      || (!isnan (th->failure_max) && (th->failure_max < values[ds_index])))
-    is_failure++;
+  /* 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)
+    {
+      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++;
+      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++;
+     }
+  }
+  else { /* no hysteresis */
+    if ((!isnan (th->failure_min) && (th->failure_min > values[ds_index]))
+       || (!isnan (th->failure_max) && (th->failure_max < values[ds_index])))
+      is_failure++;
+
+    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);
 
-  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_warning != 0)
     return (STATE_WARNING);
 
@@ -739,12 +959,49 @@ static int ut_check_one_threshold (const data_set_t *ds,
   int ret = -1;
   int ds_index = -1;
   int i;
+  gauge_t values_copy[ds->ds_num];
+
+  memcpy (values_copy, values, sizeof (values_copy));
+
+  if ((th->flags & UT_FLAG_PERCENTAGE) != 0)
+  {
+    int num = 0;
+    gauge_t sum=0.0;
+
+    if (ds->ds_num == 1)
+    {
+      WARNING ("ut_check_one_threshold: The %s type has only one data "
+          "source, but you have configured to check this as a percentage. "
+          "That doesn't make much sense, because the percentage will always "
+          "be 100%%!", ds->type);
+    }
+
+    /* Prepare `sum' and `num'. */
+    for (i = 0; i < ds->ds_num; i++)
+      if (!isnan (values[i]))
+      {
+        num++;
+       sum += values[i];
+      }
+
+    if ((num == 0) /* All data sources are undefined. */
+        || (sum == 0.0)) /* Sum is zero, cannot calculate percentage. */
+    {
+      for (i = 0; i < ds->ds_num; i++)
+        values_copy[i] = NAN;
+    }
+    else /* We can actually calculate the percentage. */
+    {
+      for (i = 0; i < ds->ds_num; i++)
+        values_copy[i] = 100.0 * values[i] / sum;
+    }
+  } /* if (UT_FLAG_PERCENTAGE) */
 
   for (i = 0; i < ds->ds_num; i++)
   {
     int status;
 
-    status = ut_check_one_data_source (ds, vl, th, values, i);
+    status = ut_check_one_data_source (ds, vl, th, values_copy, i);
     if (ret < status)
     {
       ret = status;
@@ -783,7 +1040,7 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
   /* Is this lock really necessary? So far, thresholds are only inserted at
    * startup. -octo */
   pthread_mutex_lock (&threshold_lock);
-  th = threshold_search (ds, vl);
+  th = threshold_search (vl);
   pthread_mutex_unlock (&threshold_lock);
   if (th == NULL)
     return (0);
@@ -830,8 +1087,18 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
   return (0);
 } /* }}} int ut_check_threshold */
 
+/*
+ * int ut_check_interesting (PUBLIC)
+ *
+ * Given an identification returns
+ * 0: No threshold is defined.
+ * 1: A threshold has been found. The flag `persist' is off.
+ * 2: A threshold has been found. The flag `persist' is on.
+ *    (That is, it is expected that many notifications are sent until the
+ *    problem disappears.)
+ */
 int ut_check_interesting (const char *name)
-{
+{ /* {{{ */
   char *name_copy = NULL;
   char *host = NULL;
   char *plugin = NULL;
@@ -866,32 +1133,46 @@ int ut_check_interesting (const char *name)
   memset (&ds, '\0', sizeof (ds));
   memset (&vl, '\0', sizeof (vl));
 
-  strncpy (vl.host, host, sizeof (vl.host));
-  vl.host[sizeof (vl.host) - 1] = '\0';
-  strncpy (vl.plugin, plugin, sizeof (vl.plugin));
-  vl.plugin[sizeof (vl.plugin) - 1] = '\0';
+  sstrncpy (vl.host, host, sizeof (vl.host));
+  sstrncpy (vl.plugin, plugin, sizeof (vl.plugin));
   if (plugin_instance != NULL)
-  {
-    strncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
-    vl.plugin_instance[sizeof (vl.plugin_instance) - 1] = '\0';
-  }
-  strncpy (ds.type, type, sizeof (ds.type));
-  ds.type[sizeof (ds.type) - 1] = '\0';
+    sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+  sstrncpy (ds.type, type, sizeof (ds.type));
+  sstrncpy (vl.type, type, sizeof (vl.type));
   if (type_instance != NULL)
-  {
-    strncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
-    vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
-  }
+    sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
 
   sfree (name_copy);
   host = plugin = plugin_instance = type = type_instance = NULL;
 
-  th = threshold_search (&ds, &vl);
+  th = threshold_search (&vl);
   if (th == NULL)
     return (0);
+
+  if ((th->flags & UT_FLAG_INTERESTING) == 0)
+    return (0);
+
   if ((th->flags & UT_FLAG_PERSIST) == 0)
     return (1);
   return (2);
-} /* int ut_check_interesting */
+} /* }}} int ut_check_interesting */
+
+int ut_search_threshold (const value_list_t *vl, /* {{{ */
+    threshold_t *ret_threshold)
+{
+  threshold_t *t;
+
+  if (vl == NULL)
+    return (EINVAL);
+
+  t = threshold_search (vl);
+  if (t == NULL)
+    return (ENOENT);
+
+  memcpy (ret_threshold, t, sizeof (*ret_threshold));
+  ret_threshold->next = NULL;
+
+  return (0);
+} /* }}} int ut_search_threshold */
 
-/* vim: set sw=2 ts=8 sts=2 tw=78 fdm=marker : */
+/* vim: set sw=2 ts=8 sts=2 tw=78 et fdm=marker : */