5215c9ca63a15c744687f239205c16af565214a9
[collectd.git] / src / threshold.c
1 /**
2  * collectd - src/threshold.c
3  * Copyright (C) 2007-2010  Florian Forster
4  * Copyright (C) 2008-2009  Sebastian Harl
5  * Copyright (C) 2009       Andrés J. Díaz
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation; only version 2 of the License is applicable.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  *
20  * Author:
21  *   Florian octo Forster <octo at collectd.org>
22  *   Sebastian Harl <sh at tokkee.org>
23  *   Andrés J. Díaz <ajdiaz at connectical.com>
24  **/
25
26 #include "collectd.h"
27 #include "common.h"
28 #include "plugin.h"
29 #include "utils_avltree.h"
30 #include "utils_cache.h"
31
32 #include <assert.h>
33 #include <pthread.h>
34
35 /*
36  * Private data structures
37  * {{{ */
38 #define UT_FLAG_INVERT  0x01
39 #define UT_FLAG_PERSIST 0x02
40 #define UT_FLAG_PERCENTAGE 0x04
41 #define UT_FLAG_INTERESTING 0x08
42 #define UT_FLAG_PERSIST_OK 0x10
43 typedef struct threshold_s
44 {
45   char host[DATA_MAX_NAME_LEN];
46   char plugin[DATA_MAX_NAME_LEN];
47   char plugin_instance[DATA_MAX_NAME_LEN];
48   char type[DATA_MAX_NAME_LEN];
49   char type_instance[DATA_MAX_NAME_LEN];
50   char data_source[DATA_MAX_NAME_LEN];
51   gauge_t warning_min;
52   gauge_t warning_max;
53   gauge_t failure_min;
54   gauge_t failure_max;
55   gauge_t hysteresis;
56   unsigned int flags;
57   int hits;
58   struct threshold_s *next;
59 } threshold_t;
60 /* }}} */
61
62 /*
63  * Private (static) variables
64  * {{{ */
65 static c_avl_tree_t   *threshold_tree = NULL;
66 static pthread_mutex_t threshold_lock = PTHREAD_MUTEX_INITIALIZER;
67 /* }}} */
68
69 /*
70  * Threshold management
71  * ====================
72  * The following functions add, delete, search, etc. configured thresholds to
73  * the underlying AVL trees.
74  */
75 /*
76  * threshold_t *threshold_get
77  *
78  * Retrieve one specific threshold configuration. For looking up a threshold
79  * matching a value_list_t, see "threshold_search" below. Returns NULL if the
80  * specified threshold doesn't exist.
81  */
82 static threshold_t *threshold_get (const char *hostname,
83     const char *plugin, const char *plugin_instance,
84     const char *type, const char *type_instance)
85 { /* {{{ */
86   char name[6 * DATA_MAX_NAME_LEN];
87   threshold_t *th = NULL;
88
89   format_name (name, sizeof (name),
90       (hostname == NULL) ? "" : hostname,
91       (plugin == NULL) ? "" : plugin, plugin_instance,
92       (type == NULL) ? "" : type, type_instance);
93   name[sizeof (name) - 1] = '\0';
94
95   if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
96     return (th);
97   else
98     return (NULL);
99 } /* }}} threshold_t *threshold_get */
100
101 /*
102  * int ut_threshold_add
103  *
104  * Adds a threshold configuration to the list of thresholds. The threshold_t
105  * structure is copied and may be destroyed after this call. Returns zero on
106  * success, non-zero otherwise.
107  */
108 static int ut_threshold_add (const threshold_t *th)
109 { /* {{{ */
110   char name[6 * DATA_MAX_NAME_LEN];
111   char *name_copy;
112   threshold_t *th_copy;
113   threshold_t *th_ptr;
114   int status = 0;
115
116   if (format_name (name, sizeof (name), th->host,
117         th->plugin, th->plugin_instance,
118         th->type, th->type_instance) != 0)
119   {
120     ERROR ("ut_threshold_add: format_name failed.");
121     return (-1);
122   }
123
124   name_copy = strdup (name);
125   if (name_copy == NULL)
126   {
127     ERROR ("ut_threshold_add: strdup failed.");
128     return (-1);
129   }
130
131   th_copy = (threshold_t *) malloc (sizeof (threshold_t));
132   if (th_copy == NULL)
133   {
134     sfree (name_copy);
135     ERROR ("ut_threshold_add: malloc failed.");
136     return (-1);
137   }
138   memcpy (th_copy, th, sizeof (threshold_t));
139   th_ptr = NULL;
140
141   DEBUG ("ut_threshold_add: Adding entry `%s'", name);
142
143   pthread_mutex_lock (&threshold_lock);
144
145   th_ptr = threshold_get (th->host, th->plugin, th->plugin_instance,
146       th->type, th->type_instance);
147
148   while ((th_ptr != NULL) && (th_ptr->next != NULL))
149     th_ptr = th_ptr->next;
150
151   if (th_ptr == NULL) /* no such threshold yet */
152   {
153     status = c_avl_insert (threshold_tree, name_copy, th_copy);
154   }
155   else /* th_ptr points to the last threshold in the list */
156   {
157     th_ptr->next = th_copy;
158     /* name_copy isn't needed */
159     sfree (name_copy);
160   }
161
162   pthread_mutex_unlock (&threshold_lock);
163
164   if (status != 0)
165   {
166     ERROR ("ut_threshold_add: c_avl_insert (%s) failed.", name);
167     sfree (name_copy);
168     sfree (th_copy);
169   }
170
171   return (status);
172 } /* }}} int ut_threshold_add */
173
174 /* 
175  * threshold_t *threshold_search
176  *
177  * Searches for a threshold configuration using all the possible variations of
178  * "Host", "Plugin" and "Type" blocks. Returns NULL if no threshold could be
179  * found.
180  * XXX: This is likely the least efficient function in collectd.
181  */
182 static threshold_t *threshold_search (const value_list_t *vl)
183 { /* {{{ */
184   threshold_t *th;
185
186   if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
187           vl->type, vl->type_instance)) != NULL)
188     return (th);
189   else if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
190           vl->type, NULL)) != NULL)
191     return (th);
192   else if ((th = threshold_get (vl->host, vl->plugin, NULL,
193           vl->type, vl->type_instance)) != NULL)
194     return (th);
195   else if ((th = threshold_get (vl->host, vl->plugin, NULL,
196           vl->type, NULL)) != NULL)
197     return (th);
198   else if ((th = threshold_get (vl->host, "", NULL,
199           vl->type, vl->type_instance)) != NULL)
200     return (th);
201   else if ((th = threshold_get (vl->host, "", NULL,
202           vl->type, NULL)) != NULL)
203     return (th);
204   else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
205           vl->type, vl->type_instance)) != NULL)
206     return (th);
207   else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
208           vl->type, NULL)) != NULL)
209     return (th);
210   else if ((th = threshold_get ("", vl->plugin, NULL,
211           vl->type, vl->type_instance)) != NULL)
212     return (th);
213   else if ((th = threshold_get ("", vl->plugin, NULL,
214           vl->type, NULL)) != NULL)
215     return (th);
216   else if ((th = threshold_get ("", "", NULL,
217           vl->type, vl->type_instance)) != NULL)
218     return (th);
219   else if ((th = threshold_get ("", "", NULL,
220           vl->type, NULL)) != NULL)
221     return (th);
222
223   return (NULL);
224 } /* }}} threshold_t *threshold_search */
225
226 /*
227  * Configuration
228  * =============
229  * The following approximately two hundred functions are used to handle the
230  * configuration and fill the threshold list.
231  * {{{ */
232 static int ut_config_type_datasource (threshold_t *th, oconfig_item_t *ci)
233 {
234   if ((ci->values_num != 1)
235       || (ci->values[0].type != OCONFIG_TYPE_STRING))
236   {
237     WARNING ("threshold values: The `DataSource' option needs exactly one "
238         "string argument.");
239     return (-1);
240   }
241
242   sstrncpy (th->data_source, ci->values[0].value.string,
243       sizeof (th->data_source));
244
245   return (0);
246 } /* int ut_config_type_datasource */
247
248 static int ut_config_type_instance (threshold_t *th, oconfig_item_t *ci)
249 {
250   if ((ci->values_num != 1)
251       || (ci->values[0].type != OCONFIG_TYPE_STRING))
252   {
253     WARNING ("threshold values: The `Instance' option needs exactly one "
254         "string argument.");
255     return (-1);
256   }
257
258   sstrncpy (th->type_instance, ci->values[0].value.string,
259       sizeof (th->type_instance));
260
261   return (0);
262 } /* int ut_config_type_instance */
263
264 static int ut_config_type_max (threshold_t *th, oconfig_item_t *ci)
265 {
266   if ((ci->values_num != 1)
267       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
268   {
269     WARNING ("threshold values: The `%s' option needs exactly one "
270         "number argument.", ci->key);
271     return (-1);
272   }
273
274   if (strcasecmp (ci->key, "WarningMax") == 0)
275     th->warning_max = ci->values[0].value.number;
276   else
277     th->failure_max = ci->values[0].value.number;
278
279   return (0);
280 } /* int ut_config_type_max */
281
282 static int ut_config_type_min (threshold_t *th, oconfig_item_t *ci)
283 {
284   if ((ci->values_num != 1)
285       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
286   {
287     WARNING ("threshold values: The `%s' option needs exactly one "
288         "number argument.", ci->key);
289     return (-1);
290   }
291
292   if (strcasecmp (ci->key, "WarningMin") == 0)
293     th->warning_min = ci->values[0].value.number;
294   else
295     th->failure_min = ci->values[0].value.number;
296
297   return (0);
298 } /* int ut_config_type_min */
299
300 static int ut_config_type_hits (threshold_t *th, oconfig_item_t *ci)
301 {
302   if ((ci->values_num != 1)
303       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
304   {
305     WARNING ("threshold values: The `%s' option needs exactly one "
306       "number argument.", ci->key);
307     return (-1);
308   }
309
310   th->hits = ci->values[0].value.number;
311
312   return (0);
313 } /* int ut_config_type_hits */
314
315 static int ut_config_type_hysteresis (threshold_t *th, oconfig_item_t *ci)
316 {
317   if ((ci->values_num != 1)
318       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
319   {
320     WARNING ("threshold values: The `%s' option needs exactly one "
321       "number argument.", ci->key);
322     return (-1);
323   }
324
325   th->hysteresis = ci->values[0].value.number;
326
327   return (0);
328 } /* int ut_config_type_hysteresis */
329
330 static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
331 {
332   int i;
333   threshold_t th;
334   int status = 0;
335
336   if ((ci->values_num != 1)
337       || (ci->values[0].type != OCONFIG_TYPE_STRING))
338   {
339     WARNING ("threshold values: The `Type' block needs exactly one string "
340         "argument.");
341     return (-1);
342   }
343
344   if (ci->children_num < 1)
345   {
346     WARNING ("threshold values: The `Type' block needs at least one option.");
347     return (-1);
348   }
349
350   memcpy (&th, th_orig, sizeof (th));
351   sstrncpy (th.type, ci->values[0].value.string, sizeof (th.type));
352
353   th.warning_min = NAN;
354   th.warning_max = NAN;
355   th.failure_min = NAN;
356   th.failure_max = NAN;
357   th.hits = 0;
358   th.hysteresis = 0;
359   th.flags = UT_FLAG_INTERESTING; /* interesting by default */
360
361   for (i = 0; i < ci->children_num; i++)
362   {
363     oconfig_item_t *option = ci->children + i;
364     status = 0;
365
366     if (strcasecmp ("Instance", option->key) == 0)
367       status = ut_config_type_instance (&th, option);
368     else if (strcasecmp ("DataSource", option->key) == 0)
369       status = ut_config_type_datasource (&th, option);
370     else if ((strcasecmp ("WarningMax", option->key) == 0)
371         || (strcasecmp ("FailureMax", option->key) == 0))
372       status = ut_config_type_max (&th, option);
373     else if ((strcasecmp ("WarningMin", option->key) == 0)
374         || (strcasecmp ("FailureMin", option->key) == 0))
375       status = ut_config_type_min (&th, option);
376     else if (strcasecmp ("Interesting", option->key) == 0)
377       status = cf_util_get_flag (option, &th.flags, UT_FLAG_INTERESTING);
378     else if (strcasecmp ("Invert", option->key) == 0)
379       status = cf_util_get_flag (option, &th.flags, UT_FLAG_INVERT);
380     else if (strcasecmp ("Persist", option->key) == 0)
381       status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERSIST);
382     else if (strcasecmp ("PersistOK", option->key) == 0)
383       status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERSIST_OK);
384     else if (strcasecmp ("Percentage", option->key) == 0)
385       status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERCENTAGE);
386     else if (strcasecmp ("Hits", option->key) == 0)
387       status = ut_config_type_hits (&th, option);
388     else if (strcasecmp ("Hysteresis", option->key) == 0)
389       status = ut_config_type_hysteresis (&th, option);
390     else
391     {
392       WARNING ("threshold values: Option `%s' not allowed inside a `Type' "
393           "block.", option->key);
394       status = -1;
395     }
396
397     if (status != 0)
398       break;
399   }
400
401   if (status == 0)
402   {
403     status = ut_threshold_add (&th);
404   }
405
406   return (status);
407 } /* int ut_config_type */
408
409 static int ut_config_plugin_instance (threshold_t *th, oconfig_item_t *ci)
410 {
411   if ((ci->values_num != 1)
412       || (ci->values[0].type != OCONFIG_TYPE_STRING))
413   {
414     WARNING ("threshold values: The `Instance' option needs exactly one "
415         "string argument.");
416     return (-1);
417   }
418
419   sstrncpy (th->plugin_instance, ci->values[0].value.string,
420       sizeof (th->plugin_instance));
421
422   return (0);
423 } /* int ut_config_plugin_instance */
424
425 static int ut_config_plugin (const threshold_t *th_orig, oconfig_item_t *ci)
426 {
427   int i;
428   threshold_t th;
429   int status = 0;
430
431   if ((ci->values_num != 1)
432       || (ci->values[0].type != OCONFIG_TYPE_STRING))
433   {
434     WARNING ("threshold values: The `Plugin' block needs exactly one string "
435         "argument.");
436     return (-1);
437   }
438
439   if (ci->children_num < 1)
440   {
441     WARNING ("threshold values: The `Plugin' block needs at least one nested "
442         "block.");
443     return (-1);
444   }
445
446   memcpy (&th, th_orig, sizeof (th));
447   sstrncpy (th.plugin, ci->values[0].value.string, sizeof (th.plugin));
448
449   for (i = 0; i < ci->children_num; i++)
450   {
451     oconfig_item_t *option = ci->children + i;
452     status = 0;
453
454     if (strcasecmp ("Type", option->key) == 0)
455       status = ut_config_type (&th, option);
456     else if (strcasecmp ("Instance", option->key) == 0)
457       status = ut_config_plugin_instance (&th, option);
458     else
459     {
460       WARNING ("threshold values: Option `%s' not allowed inside a `Plugin' "
461           "block.", option->key);
462       status = -1;
463     }
464
465     if (status != 0)
466       break;
467   }
468
469   return (status);
470 } /* int ut_config_plugin */
471
472 static int ut_config_host (const threshold_t *th_orig, oconfig_item_t *ci)
473 {
474   int i;
475   threshold_t th;
476   int status = 0;
477
478   if ((ci->values_num != 1)
479       || (ci->values[0].type != OCONFIG_TYPE_STRING))
480   {
481     WARNING ("threshold values: The `Host' block needs exactly one string "
482         "argument.");
483     return (-1);
484   }
485
486   if (ci->children_num < 1)
487   {
488     WARNING ("threshold values: The `Host' block needs at least one nested "
489         "block.");
490     return (-1);
491   }
492
493   memcpy (&th, th_orig, sizeof (th));
494   sstrncpy (th.host, ci->values[0].value.string, sizeof (th.host));
495
496   for (i = 0; i < ci->children_num; i++)
497   {
498     oconfig_item_t *option = ci->children + i;
499     status = 0;
500
501     if (strcasecmp ("Type", option->key) == 0)
502       status = ut_config_type (&th, option);
503     else if (strcasecmp ("Plugin", option->key) == 0)
504       status = ut_config_plugin (&th, option);
505     else
506     {
507       WARNING ("threshold values: Option `%s' not allowed inside a `Host' "
508           "block.", option->key);
509       status = -1;
510     }
511
512     if (status != 0)
513       break;
514   }
515
516   return (status);
517 } /* int ut_config_host */
518 /*
519  * End of the functions used to configure threshold values.
520  */
521 /* }}} */
522
523 /*
524  * int ut_report_state
525  *
526  * Checks if the `state' differs from the old state and creates a notification
527  * if appropriate.
528  * Does not fail.
529  */
530 static int ut_report_state (const data_set_t *ds,
531     const value_list_t *vl,
532     const threshold_t *th,
533     const gauge_t *values,
534     int ds_index,
535     int state)
536 { /* {{{ */
537   int state_old;
538   notification_t n;
539
540   char *buf;
541   size_t bufsize;
542
543   int status;
544
545   /* Check if hits matched */
546   if ( (th->hits != 0) )
547   {
548     int hits = uc_get_hits(ds,vl);
549     /* STATE_OKAY resets hits unless PERSIST_OK flag is set. Hits resets if
550      * threshold is hit. */
551     if ( ( (state == STATE_OKAY) && ((th->flags & UT_FLAG_PERSIST_OK) == 0) ) || (hits > th->hits) )
552     {
553         DEBUG("ut_report_state: reset uc_get_hits = 0");
554         uc_set_hits(ds,vl,0); /* reset hit counter and notify */
555     } else {
556       DEBUG("ut_report_state: th->hits = %d, uc_get_hits = %d",th->hits,uc_get_hits(ds,vl));
557       (void) uc_inc_hits(ds,vl,1); /* increase hit counter */
558       return (0);
559     }
560   } /* end check hits */
561
562   state_old = uc_get_state (ds, vl);
563
564   /* If the state didn't change, report if `persistent' is specified. If the
565    * state is `okay', then only report if `persist_ok` flag is set. */
566   if (state == state_old)
567   {
568     if ((th->flags & UT_FLAG_PERSIST) == 0)
569       return (0);
570     else if ( (state == STATE_OKAY) && ((th->flags & UT_FLAG_PERSIST_OK) == 0) )
571       return (0);
572   }
573
574   if (state != state_old)
575     uc_set_state (ds, vl, state);
576
577   NOTIFICATION_INIT_VL (&n, vl);
578
579   buf = n.message;
580   bufsize = sizeof (n.message);
581
582   if (state == STATE_OKAY)
583     n.severity = NOTIF_OKAY;
584   else if (state == STATE_WARNING)
585     n.severity = NOTIF_WARNING;
586   else
587     n.severity = NOTIF_FAILURE;
588
589   n.time = vl->time;
590
591   status = ssnprintf (buf, bufsize, "Host %s, plugin %s",
592       vl->host, vl->plugin);
593   buf += status;
594   bufsize -= status;
595
596   if (vl->plugin_instance[0] != '\0')
597   {
598     status = ssnprintf (buf, bufsize, " (instance %s)",
599         vl->plugin_instance);
600     buf += status;
601     bufsize -= status;
602   }
603
604   status = ssnprintf (buf, bufsize, " type %s", vl->type);
605   buf += status;
606   bufsize -= status;
607
608   if (vl->type_instance[0] != '\0')
609   {
610     status = ssnprintf (buf, bufsize, " (instance %s)",
611         vl->type_instance);
612     buf += status;
613     bufsize -= status;
614   }
615
616   plugin_notification_meta_add_string (&n, "DataSource",
617       ds->ds[ds_index].name);
618   plugin_notification_meta_add_double (&n, "CurrentValue", values[ds_index]);
619   plugin_notification_meta_add_double (&n, "WarningMin", th->warning_min);
620   plugin_notification_meta_add_double (&n, "WarningMax", th->warning_max);
621   plugin_notification_meta_add_double (&n, "FailureMin", th->failure_min);
622   plugin_notification_meta_add_double (&n, "FailureMax", th->failure_max);
623
624   /* Send an okay notification */
625   if (state == STATE_OKAY)
626   {
627     if (state_old == STATE_MISSING)
628       status = ssnprintf (buf, bufsize,
629           ": Value is no longer missing.");
630     else
631       status = ssnprintf (buf, bufsize,
632           ": All data sources are within range again. "
633           "Current value of \"%s\" is %f.",
634           ds->ds[ds_index].name, values[ds_index]);
635     buf += status;
636     bufsize -= status;
637   }
638   else
639   {
640     double min;
641     double max;
642
643     min = (state == STATE_ERROR) ? th->failure_min : th->warning_min;
644     max = (state == STATE_ERROR) ? th->failure_max : th->warning_max;
645
646     if (th->flags & UT_FLAG_INVERT)
647     {
648       if (!isnan (min) && !isnan (max))
649       {
650         status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
651             "%f. That is within the %s region of %f%s and %f%s.",
652             ds->ds[ds_index].name, values[ds_index],
653             (state == STATE_ERROR) ? "failure" : "warning",
654             min, ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "",
655             max, ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
656       }
657       else
658       {
659         status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
660             "%f. That is %s the %s threshold of %f%s.",
661             ds->ds[ds_index].name, values[ds_index],
662             isnan (min) ? "below" : "above",
663             (state == STATE_ERROR) ? "failure" : "warning",
664             isnan (min) ? max : min,
665             ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
666       }
667     }
668     else if (th->flags & UT_FLAG_PERCENTAGE)
669     {
670       gauge_t value;
671       gauge_t sum;
672       int i;
673
674       sum = 0.0;
675       for (i = 0; i < vl->values_len; i++)
676       {
677         if (isnan (values[i]))
678           continue;
679
680         sum += values[i];
681       }
682
683       if (sum == 0.0)
684         value = NAN;
685       else
686         value = 100.0 * values[ds_index] / sum;
687
688       status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
689           "%g (%.2f%%). That is %s the %s threshold of %.2f%%.",
690           ds->ds[ds_index].name, values[ds_index], value,
691           (value < min) ? "below" : "above",
692           (state == STATE_ERROR) ? "failure" : "warning",
693           (value < min) ? min : max);
694     }
695     else /* is not inverted */
696     {
697       status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
698           "%f. That is %s the %s threshold of %f.",
699           ds->ds[ds_index].name, values[ds_index],
700           (values[ds_index] < min) ? "below" : "above",
701           (state == STATE_ERROR) ? "failure" : "warning",
702           (values[ds_index] < min) ? min : max);
703     }
704     buf += status;
705     bufsize -= status;
706   }
707
708   plugin_dispatch_notification (&n);
709
710   plugin_notification_meta_free (n.meta);
711   return (0);
712 } /* }}} int ut_report_state */
713
714 /*
715  * int ut_check_one_data_source
716  *
717  * Checks one data source against the given threshold configuration. If the
718  * `DataSource' option is set in the threshold, and the name does NOT match,
719  * `okay' is returned. If the threshold does match, its failure and warning
720  * min and max values are checked and `failure' or `warning' is returned if
721  * appropriate.
722  * Does not fail.
723  */
724 static int ut_check_one_data_source (const data_set_t *ds,
725     const value_list_t __attribute__((unused)) *vl,
726     const threshold_t *th,
727     const gauge_t *values,
728     int ds_index)
729 { /* {{{ */
730   const char *ds_name;
731   int is_warning = 0;
732   int is_failure = 0;
733   int prev_state = STATE_OKAY;
734
735   /* check if this threshold applies to this data source */
736   if (ds != NULL)
737   {
738     ds_name = ds->ds[ds_index].name;
739     if ((th->data_source[0] != 0)
740         && (strcmp (ds_name, th->data_source) != 0))
741       return (STATE_OKAY);
742   }
743
744   if ((th->flags & UT_FLAG_INVERT) != 0)
745   {
746     is_warning--;
747     is_failure--;
748   }
749
750   /* XXX: This is an experimental code, not optimized, not fast, not reliable,
751    * and probably, do not work as you expect. Enjoy! :D */
752   if (th->hysteresis > 0)
753   {
754     prev_state = uc_get_state(ds,vl);
755     /* The purpose of hysteresis is elliminating flapping state when the value
756      * oscilates around the thresholds. In other words, what is important is
757      * the previous state; if the new value would trigger a transition, make
758      * sure that we artificially widen the range which is considered to apply
759      * for the previous state, and only trigger the notification if the value
760      * is outside of this expanded range.
761      *
762      * There is no hysteresis for the OKAY state.
763      * */
764     gauge_t hysteresis_for_warning = 0, hysteresis_for_failure = 0;
765     switch (prev_state)
766     {
767       case STATE_ERROR:
768         hysteresis_for_failure = th->hysteresis;
769         break;
770       case STATE_WARNING:
771         hysteresis_for_warning = th->hysteresis;
772         break;
773       case STATE_OKAY:
774         /* do nothing -- the hysteresis only applies to the non-normal states */
775         break;
776     }
777
778     if ((!isnan (th->failure_min) && (th->failure_min + hysteresis_for_failure > values[ds_index]))
779         || (!isnan (th->failure_max) && (th->failure_max - hysteresis_for_failure < values[ds_index])))
780       is_failure++;
781
782     if ((!isnan (th->warning_min) && (th->warning_min + hysteresis_for_warning > values[ds_index]))
783         || (!isnan (th->warning_max) && (th->warning_max - hysteresis_for_warning < values[ds_index])))
784       is_warning++;
785
786   }
787   else { /* no hysteresis */
788     if ((!isnan (th->failure_min) && (th->failure_min > values[ds_index]))
789         || (!isnan (th->failure_max) && (th->failure_max < values[ds_index])))
790       is_failure++;
791
792     if ((!isnan (th->warning_min) && (th->warning_min > values[ds_index]))
793         || (!isnan (th->warning_max) && (th->warning_max < values[ds_index])))
794       is_warning++;
795   }
796
797   if (is_failure != 0)
798     return (STATE_ERROR);
799
800   if (is_warning != 0)
801     return (STATE_WARNING);
802
803   return (STATE_OKAY);
804 } /* }}} int ut_check_one_data_source */
805
806 /*
807  * int ut_check_one_threshold
808  *
809  * Checks all data sources of a value list against the given threshold, using
810  * the ut_check_one_data_source function above. Returns the worst status,
811  * which is `okay' if nothing has failed.
812  * Returns less than zero if the data set doesn't have any data sources.
813  */
814 static int ut_check_one_threshold (const data_set_t *ds,
815     const value_list_t *vl,
816     const threshold_t *th,
817     const gauge_t *values,
818     int *ret_ds_index)
819 { /* {{{ */
820   int ret = -1;
821   int ds_index = -1;
822   int i;
823   gauge_t values_copy[ds->ds_num];
824
825   memcpy (values_copy, values, sizeof (values_copy));
826
827   if ((th->flags & UT_FLAG_PERCENTAGE) != 0)
828   {
829     int num = 0;
830     gauge_t sum=0.0;
831
832     if (ds->ds_num == 1)
833     {
834       WARNING ("ut_check_one_threshold: The %s type has only one data "
835           "source, but you have configured to check this as a percentage. "
836           "That doesn't make much sense, because the percentage will always "
837           "be 100%%!", ds->type);
838     }
839
840     /* Prepare `sum' and `num'. */
841     for (i = 0; i < ds->ds_num; i++)
842       if (!isnan (values[i]))
843       {
844         num++;
845         sum += values[i];
846       }
847
848     if ((num == 0) /* All data sources are undefined. */
849         || (sum == 0.0)) /* Sum is zero, cannot calculate percentage. */
850     {
851       for (i = 0; i < ds->ds_num; i++)
852         values_copy[i] = NAN;
853     }
854     else /* We can actually calculate the percentage. */
855     {
856       for (i = 0; i < ds->ds_num; i++)
857         values_copy[i] = 100.0 * values[i] / sum;
858     }
859   } /* if (UT_FLAG_PERCENTAGE) */
860
861   for (i = 0; i < ds->ds_num; i++)
862   {
863     int status;
864
865     status = ut_check_one_data_source (ds, vl, th, values_copy, i);
866     if (ret < status)
867     {
868       ret = status;
869       ds_index = i;
870     }
871   } /* for (ds->ds_num) */
872
873   if (ret_ds_index != NULL)
874     *ret_ds_index = ds_index;
875
876   return (ret);
877 } /* }}} int ut_check_one_threshold */
878
879 /*
880  * int ut_check_threshold
881  *
882  * Gets a list of matching thresholds and searches for the worst status by one
883  * of the thresholds. Then reports that status using the ut_report_state
884  * function above. 
885  * Returns zero on success and if no threshold has been configured. Returns
886  * less than zero on failure.
887  */
888 static int ut_check_threshold (const data_set_t *ds, const value_list_t *vl,
889     __attribute__((unused)) user_data_t *ud)
890 { /* {{{ */
891   threshold_t *th;
892   gauge_t *values;
893   int status;
894
895   int worst_state = -1;
896   threshold_t *worst_th = NULL;
897   int worst_ds_index = -1;
898
899   if (threshold_tree == NULL)
900     return (0);
901
902   /* Is this lock really necessary? So far, thresholds are only inserted at
903    * startup. -octo */
904   pthread_mutex_lock (&threshold_lock);
905   th = threshold_search (vl);
906   pthread_mutex_unlock (&threshold_lock);
907   if (th == NULL)
908     return (0);
909
910   DEBUG ("ut_check_threshold: Found matching threshold(s)");
911
912   values = uc_get_rate (ds, vl);
913   if (values == NULL)
914     return (0);
915
916   while (th != NULL)
917   {
918     int ds_index = -1;
919
920     status = ut_check_one_threshold (ds, vl, th, values, &ds_index);
921     if (status < 0)
922     {
923       ERROR ("ut_check_threshold: ut_check_one_threshold failed.");
924       sfree (values);
925       return (-1);
926     }
927
928     if (worst_state < status)
929     {
930       worst_state = status;
931       worst_th = th;
932       worst_ds_index = ds_index;
933     }
934
935     th = th->next;
936   } /* while (th) */
937
938   status = ut_report_state (ds, vl, worst_th, values,
939       worst_ds_index, worst_state);
940   if (status != 0)
941   {
942     ERROR ("ut_check_threshold: ut_report_state failed.");
943     sfree (values);
944     return (-1);
945   }
946
947   sfree (values);
948
949   return (0);
950 } /* }}} int ut_check_threshold */
951
952 /*
953  * int ut_missing
954  *
955  * This function is called whenever a value goes "missing".
956  */
957 static int ut_missing (const value_list_t *vl,
958     __attribute__((unused)) user_data_t *ud)
959 { /* {{{ */
960   threshold_t *th;
961   cdtime_t missing_time;
962   char identifier[6 * DATA_MAX_NAME_LEN];
963   notification_t n;
964
965   if (threshold_tree == NULL)
966     return (0);
967
968   th = threshold_search (vl);
969   /* dispatch notifications for "interesting" values only */
970   if ((th == NULL) || ((th->flags & UT_FLAG_INTERESTING) == 0))
971     return (0);
972
973   missing_time = cdtime () - vl->time;
974   FORMAT_VL (identifier, sizeof (identifier), vl);
975
976   NOTIFICATION_INIT_VL (&n, vl);
977   ssnprintf (n.message, sizeof (n.message),
978       "%s has not been updated for %.3f seconds.",
979       identifier, CDTIME_T_TO_DOUBLE (missing_time));
980
981   plugin_dispatch_notification (&n);
982
983   return (0);
984 } /* }}} int ut_missing */
985
986 int ut_config (oconfig_item_t *ci)
987 { /* {{{ */
988   int i;
989   int status = 0;
990
991   threshold_t th;
992
993   if (threshold_tree == NULL)
994   {
995     threshold_tree = c_avl_create ((void *) strcmp);
996     if (threshold_tree == NULL)
997     {
998       ERROR ("ut_config: c_avl_create failed.");
999       return (-1);
1000     }
1001   }
1002
1003   memset (&th, '\0', sizeof (th));
1004   th.warning_min = NAN;
1005   th.warning_max = NAN;
1006   th.failure_min = NAN;
1007   th.failure_max = NAN;
1008
1009   th.hits = 0;
1010   th.hysteresis = 0;
1011   th.flags = UT_FLAG_INTERESTING; /* interesting by default */
1012     
1013   for (i = 0; i < ci->children_num; i++)
1014   {
1015     oconfig_item_t *option = ci->children + i;
1016     status = 0;
1017
1018     if (strcasecmp ("Type", option->key) == 0)
1019       status = ut_config_type (&th, option);
1020     else if (strcasecmp ("Plugin", option->key) == 0)
1021       status = ut_config_plugin (&th, option);
1022     else if (strcasecmp ("Host", option->key) == 0)
1023       status = ut_config_host (&th, option);
1024     else
1025     {
1026       WARNING ("threshold values: Option `%s' not allowed here.", option->key);
1027       status = -1;
1028     }
1029
1030     if (status != 0)
1031       break;
1032   }
1033
1034   if (c_avl_size (threshold_tree) > 0) {
1035     plugin_register_missing ("threshold", ut_missing,
1036         /* user data = */ NULL);
1037     plugin_register_write ("threshold", ut_check_threshold,
1038         /* user data = */ NULL);
1039   }
1040
1041   return (status);
1042 } /* }}} int um_config */
1043
1044 void module_register (void)
1045 {
1046   plugin_register_complex_config ("threshold", ut_config);
1047 }
1048
1049 /* vim: set sw=2 ts=8 sts=2 tw=78 et fdm=marker : */