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