a3d865bac973fae6354faa1be3d95669fe7b57fa
[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 "plugin.h"
29 #include "utils/avltree/avltree.h"
30 #include "utils/common/common.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 (state == STATE_UNKNOWN) {
313       /* From UNKNOWN to UNKNOWN. Persist doesn't apply here. */
314       return 0;
315     } else if ((th->flags & UT_FLAG_PERSIST) == 0)
316       return 0;
317     else if ((state == STATE_OKAY) && ((th->flags & UT_FLAG_PERSIST_OK) == 0))
318       return 0;
319   }
320
321   if (state != state_old)
322     uc_set_state(ds, vl, state);
323
324   NOTIFICATION_INIT_VL(&n, vl);
325
326   buf = n.message;
327   bufsize = sizeof(n.message);
328
329   if (state == STATE_OKAY)
330     n.severity = NOTIF_OKAY;
331   else if (state == STATE_WARNING)
332     n.severity = NOTIF_WARNING;
333   else
334     n.severity = NOTIF_FAILURE;
335
336   n.time = vl->time;
337
338   status = snprintf(buf, bufsize, "Host %s, plugin %s", vl->host, vl->plugin);
339   buf += status;
340   bufsize -= status;
341
342   if (vl->plugin_instance[0] != '\0') {
343     status = snprintf(buf, bufsize, " (instance %s)", vl->plugin_instance);
344     buf += status;
345     bufsize -= status;
346   }
347
348   status = snprintf(buf, bufsize, " type %s", vl->type);
349   buf += status;
350   bufsize -= status;
351
352   if (vl->type_instance[0] != '\0') {
353     status = snprintf(buf, bufsize, " (instance %s)", vl->type_instance);
354     buf += status;
355     bufsize -= status;
356   }
357
358   plugin_notification_meta_add_string(&n, "DataSource", ds->ds[ds_index].name);
359   plugin_notification_meta_add_double(&n, "CurrentValue", values[ds_index]);
360   plugin_notification_meta_add_double(&n, "WarningMin", th->warning_min);
361   plugin_notification_meta_add_double(&n, "WarningMax", th->warning_max);
362   plugin_notification_meta_add_double(&n, "FailureMin", th->failure_min);
363   plugin_notification_meta_add_double(&n, "FailureMax", th->failure_max);
364
365   /* Send an okay notification */
366   if (state == STATE_OKAY) {
367     if (state_old == STATE_MISSING)
368       snprintf(buf, bufsize, ": Value is no longer missing.");
369     else
370       snprintf(buf, bufsize, ": All data sources are within range again. "
371                              "Current value of \"%s\" is %f.",
372                ds->ds[ds_index].name, values[ds_index]);
373   } else if (state == STATE_UNKNOWN) {
374     ERROR("ut_report_state: metric transition to UNKNOWN from a different "
375           "state. This shouldn't happen.");
376     return 0;
377   } else {
378     double min;
379     double max;
380
381     min = (state == STATE_ERROR) ? th->failure_min : th->warning_min;
382     max = (state == STATE_ERROR) ? th->failure_max : th->warning_max;
383
384     if (th->flags & UT_FLAG_INVERT) {
385       if (!isnan(min) && !isnan(max)) {
386         snprintf(buf, bufsize,
387                  ": Data source \"%s\" is currently "
388                  "%f. That is within the %s region of %f%s and %f%s.",
389                  ds->ds[ds_index].name, values[ds_index],
390                  (state == STATE_ERROR) ? "failure" : "warning", min,
391                  ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "", max,
392                  ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
393       } else {
394         snprintf(buf, bufsize, ": Data source \"%s\" is currently "
395                                "%f. That is %s the %s threshold of %f%s.",
396                  ds->ds[ds_index].name, values[ds_index],
397                  isnan(min) ? "below" : "above",
398                  (state == STATE_ERROR) ? "failure" : "warning",
399                  isnan(min) ? max : min,
400                  ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
401       }
402     } else if (th->flags & UT_FLAG_PERCENTAGE) {
403       gauge_t value;
404       gauge_t sum;
405
406       sum = 0.0;
407       for (size_t i = 0; i < vl->values_len; i++) {
408         if (isnan(values[i]))
409           continue;
410
411         sum += values[i];
412       }
413
414       if (sum == 0.0)
415         value = NAN;
416       else
417         value = 100.0 * values[ds_index] / sum;
418
419       snprintf(buf, bufsize,
420                ": Data source \"%s\" is currently "
421                "%g (%.2f%%). That is %s the %s threshold of %.2f%%.",
422                ds->ds[ds_index].name, values[ds_index], value,
423                (value < min) ? "below" : "above",
424                (state == STATE_ERROR) ? "failure" : "warning",
425                (value < min) ? min : max);
426     } else /* is not inverted */
427     {
428       snprintf(buf, bufsize, ": Data source \"%s\" is currently "
429                              "%f. That is %s the %s threshold of %f.",
430                ds->ds[ds_index].name, values[ds_index],
431                (values[ds_index] < min) ? "below" : "above",
432                (state == STATE_ERROR) ? "failure" : "warning",
433                (values[ds_index] < min) ? min : max);
434     }
435   }
436
437   plugin_dispatch_notification(&n);
438
439   plugin_notification_meta_free(n.meta);
440   return 0;
441 } /* }}} int ut_report_state */
442
443 /*
444  * int ut_check_one_data_source
445  *
446  * Checks one data source against the given threshold configuration. If the
447  * `DataSource' option is set in the threshold, and the name does NOT match,
448  * `okay' is returned. If the threshold does match, its failure and warning
449  * min and max values are checked and `failure' or `warning' is returned if
450  * appropriate.
451  * Does not fail.
452  */
453 static int ut_check_one_data_source(
454     const data_set_t *ds, const value_list_t __attribute__((unused)) * vl,
455     const threshold_t *th, const gauge_t *values, int ds_index) { /* {{{ */
456   const char *ds_name;
457   int is_warning = 0;
458   int is_failure = 0;
459   int prev_state = STATE_OKAY;
460
461   /* check if this threshold applies to this data source */
462   if (ds != NULL) {
463     ds_name = ds->ds[ds_index].name;
464     if ((th->data_source[0] != 0) && (strcmp(ds_name, th->data_source) != 0))
465       return STATE_UNKNOWN;
466   }
467
468   if ((th->flags & UT_FLAG_INVERT) != 0) {
469     is_warning--;
470     is_failure--;
471   }
472
473   /* XXX: This is an experimental code, not optimized, not fast, not reliable,
474    * and probably, do not work as you expect. Enjoy! :D */
475   if (th->hysteresis > 0) {
476     prev_state = uc_get_state(ds, vl);
477     /* The purpose of hysteresis is elliminating flapping state when the value
478      * oscilates around the thresholds. In other words, what is important is
479      * the previous state; if the new value would trigger a transition, make
480      * sure that we artificially widen the range which is considered to apply
481      * for the previous state, and only trigger the notification if the value
482      * is outside of this expanded range.
483      *
484      * There is no hysteresis for the OKAY state.
485      * */
486     gauge_t hysteresis_for_warning = 0, hysteresis_for_failure = 0;
487     switch (prev_state) {
488     case STATE_ERROR:
489       hysteresis_for_failure = th->hysteresis;
490       break;
491     case STATE_WARNING:
492       hysteresis_for_warning = th->hysteresis;
493       break;
494     case STATE_UNKNOWN:
495     case STATE_OKAY:
496       /* do nothing -- the hysteresis only applies to the non-normal states */
497       break;
498     }
499
500     if ((!isnan(th->failure_min) &&
501          (th->failure_min + hysteresis_for_failure > values[ds_index])) ||
502         (!isnan(th->failure_max) &&
503          (th->failure_max - hysteresis_for_failure < values[ds_index])))
504       is_failure++;
505
506     if ((!isnan(th->warning_min) &&
507          (th->warning_min + hysteresis_for_warning > values[ds_index])) ||
508         (!isnan(th->warning_max) &&
509          (th->warning_max - hysteresis_for_warning < values[ds_index])))
510       is_warning++;
511
512   } else { /* no hysteresis */
513     if ((!isnan(th->failure_min) && (th->failure_min > values[ds_index])) ||
514         (!isnan(th->failure_max) && (th->failure_max < values[ds_index])))
515       is_failure++;
516
517     if ((!isnan(th->warning_min) && (th->warning_min > values[ds_index])) ||
518         (!isnan(th->warning_max) && (th->warning_max < values[ds_index])))
519       is_warning++;
520   }
521
522   if (is_failure != 0)
523     return STATE_ERROR;
524
525   if (is_warning != 0)
526     return STATE_WARNING;
527
528   return STATE_OKAY;
529 } /* }}} int ut_check_one_data_source */
530
531 /*
532  * int ut_check_one_threshold
533  *
534  * Checks all data sources of a value list against the given threshold, using
535  * the ut_check_one_data_source function above. Returns the worst status,
536  * which is `okay' if nothing has failed or `unknown' if no valid datasource was
537  * defined.
538  * Returns less than zero if the data set doesn't have any data sources.
539  */
540 static int ut_check_one_threshold(const data_set_t *ds, const value_list_t *vl,
541                                   const threshold_t *th, const gauge_t *values,
542                                   int *ret_ds_index) { /* {{{ */
543   int ret = -1;
544   int ds_index = -1;
545   gauge_t values_copy[ds->ds_num];
546
547   memcpy(values_copy, values, sizeof(values_copy));
548
549   if ((th->flags & UT_FLAG_PERCENTAGE) != 0) {
550     int num = 0;
551     gauge_t sum = 0.0;
552
553     if (ds->ds_num == 1) {
554       WARNING(
555           "ut_check_one_threshold: The %s type has only one data "
556           "source, but you have configured to check this as a percentage. "
557           "That doesn't make much sense, because the percentage will always "
558           "be 100%%!",
559           ds->type);
560     }
561
562     /* Prepare `sum' and `num'. */
563     for (size_t i = 0; i < ds->ds_num; i++)
564       if (!isnan(values[i])) {
565         num++;
566         sum += values[i];
567       }
568
569     if ((num == 0)       /* All data sources are undefined. */
570         || (sum == 0.0)) /* Sum is zero, cannot calculate percentage. */
571     {
572       for (size_t i = 0; i < ds->ds_num; i++)
573         values_copy[i] = NAN;
574     } else /* We can actually calculate the percentage. */
575     {
576       for (size_t i = 0; i < ds->ds_num; i++)
577         values_copy[i] = 100.0 * values[i] / sum;
578     }
579   } /* if (UT_FLAG_PERCENTAGE) */
580
581   for (size_t i = 0; i < ds->ds_num; i++) {
582     int status;
583
584     status = ut_check_one_data_source(ds, vl, th, values_copy, i);
585     if (ret < status) {
586       ret = status;
587       ds_index = i;
588     }
589   } /* for (ds->ds_num) */
590
591   if (ret_ds_index != NULL)
592     *ret_ds_index = ds_index;
593
594   return ret;
595 } /* }}} int ut_check_one_threshold */
596
597 /*
598  * int ut_check_threshold
599  *
600  * Gets a list of matching thresholds and searches for the worst status by one
601  * of the thresholds. Then reports that status using the ut_report_state
602  * function above.
603  * Returns zero on success and if no threshold has been configured. Returns
604  * less than zero on failure.
605  */
606 static int ut_check_threshold(const data_set_t *ds, const value_list_t *vl,
607                               __attribute__((unused))
608                               user_data_t *ud) { /* {{{ */
609   threshold_t *th;
610   gauge_t *values;
611   int status;
612
613   int worst_state = -1;
614   threshold_t *worst_th = NULL;
615   int worst_ds_index = -1;
616
617   if (threshold_tree == NULL)
618     return 0;
619
620   /* Is this lock really necessary? So far, thresholds are only inserted at
621    * startup. -octo */
622   pthread_mutex_lock(&threshold_lock);
623   th = threshold_search(vl);
624   pthread_mutex_unlock(&threshold_lock);
625   if (th == NULL)
626     return 0;
627
628   DEBUG("ut_check_threshold: Found matching threshold(s)");
629
630   values = uc_get_rate(ds, vl);
631   if (values == NULL)
632     return 0;
633
634   while (th != NULL) {
635     int ds_index = -1;
636
637     status = ut_check_one_threshold(ds, vl, th, values, &ds_index);
638     if (status < 0) {
639       ERROR("ut_check_threshold: ut_check_one_threshold failed.");
640       sfree(values);
641       return -1;
642     }
643
644     if (worst_state < status) {
645       worst_state = status;
646       worst_th = th;
647       worst_ds_index = ds_index;
648     }
649
650     th = th->next;
651   } /* while (th) */
652
653   status =
654       ut_report_state(ds, vl, worst_th, values, worst_ds_index, worst_state);
655   if (status != 0) {
656     ERROR("ut_check_threshold: ut_report_state failed.");
657     sfree(values);
658     return -1;
659   }
660
661   sfree(values);
662
663   return 0;
664 } /* }}} int ut_check_threshold */
665
666 /*
667  * int ut_missing
668  *
669  * This function is called whenever a value goes "missing".
670  */
671 static int ut_missing(const value_list_t *vl,
672                       __attribute__((unused)) user_data_t *ud) { /* {{{ */
673   threshold_t *th;
674   cdtime_t missing_time;
675   char identifier[6 * DATA_MAX_NAME_LEN];
676   notification_t n;
677   cdtime_t now;
678
679   if (threshold_tree == NULL)
680     return 0;
681
682   th = threshold_search(vl);
683   /* dispatch notifications for "interesting" values only */
684   if ((th == NULL) || ((th->flags & UT_FLAG_INTERESTING) == 0))
685     return 0;
686
687   now = cdtime();
688   missing_time = now - vl->time;
689   FORMAT_VL(identifier, sizeof(identifier), vl);
690
691   NOTIFICATION_INIT_VL(&n, vl);
692   snprintf(n.message, sizeof(n.message),
693            "%s has not been updated for %.3f seconds.", identifier,
694            CDTIME_T_TO_DOUBLE(missing_time));
695   n.time = now;
696
697   plugin_dispatch_notification(&n);
698
699   return 0;
700 } /* }}} int ut_missing */
701
702 static int ut_config(oconfig_item_t *ci) { /* {{{ */
703   int status = 0;
704   int old_size = c_avl_size(threshold_tree);
705
706   if (threshold_tree == NULL) {
707     threshold_tree = c_avl_create((int (*)(const void *, const void *))strcmp);
708     if (threshold_tree == NULL) {
709       ERROR("ut_config: c_avl_create failed.");
710       return -1;
711     }
712   }
713
714   threshold_t th = {
715       .warning_min = NAN,
716       .warning_max = NAN,
717       .failure_min = NAN,
718       .failure_max = NAN,
719       .flags = UT_FLAG_INTERESTING /* interesting by default */
720   };
721
722   for (int i = 0; i < ci->children_num; i++) {
723     oconfig_item_t *option = ci->children + i;
724
725     if (strcasecmp("Type", option->key) == 0)
726       status = ut_config_type(&th, option);
727     else if (strcasecmp("Plugin", option->key) == 0)
728       status = ut_config_plugin(&th, option);
729     else if (strcasecmp("Host", option->key) == 0)
730       status = ut_config_host(&th, option);
731     else {
732       WARNING("threshold values: Option `%s' not allowed here.", option->key);
733       status = -1;
734     }
735
736     if (status != 0)
737       break;
738   }
739
740   /* register callbacks if this is the first time we see a valid config */
741   if ((old_size == 0) && (c_avl_size(threshold_tree) > 0)) {
742     plugin_register_missing("threshold", ut_missing,
743                             /* user data = */ NULL);
744     plugin_register_write("threshold", ut_check_threshold,
745                           /* user data = */ NULL);
746   }
747
748   return status;
749 } /* }}} int um_config */
750
751 void module_register(void) {
752   plugin_register_complex_config("threshold", ut_config);
753 }