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