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