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