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