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