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