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