Add custom message for threshold and missings.
[collectd.git] / src / utils_threshold.c
1 /**
2  * collectd - src/utils_threshold.c
3  * Copyright (C) 2007-2009  Florian octo Forster
4  * Copyright (C) 2008-2009  Sebastian Harl
5  * Copyright (C) 2009       Andrés J. Díaz
6  *
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the
10  * Free Software Foundation; only version 2 of the License is applicable.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
20  *
21  * Author:
22  *   Florian octo Forster <octo at verplant.org>
23  *   Sebastian Harl <sh at tokkee.org>
24  *   Andrés J. Díaz <ajdiaz at connectical.com>
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30 #include "utils_avltree.h"
31 #include "utils_cache.h"
32 #include "utils_threshold.h"
33 #include "utils_subst.h"
34
35 #include <assert.h>
36 #include <pthread.h>
37
38 /*
39  * Private data structures
40  * {{{ */
41 #define UT_FLAG_INVERT  0x01
42 #define UT_FLAG_PERSIST 0x02
43 #define UT_FLAG_PERCENTAGE 0x04
44 #define UT_FLAG_INTERESTING 0x08
45 /* }}} */
46
47 /*
48  * Private (static) variables
49  * {{{ */
50 static c_avl_tree_t   *threshold_tree = NULL;
51 static pthread_mutex_t threshold_lock = PTHREAD_MUTEX_INITIALIZER;
52 /* }}} */
53
54 /*
55  * Threshold management
56  * ====================
57  * The following functions add, delete, search, etc. configured thresholds to
58  * the underlying AVL trees.
59  * {{{ */
60 static threshold_t *threshold_get (const char *hostname,
61     const char *plugin, const char *plugin_instance,
62     const char *type, const char *type_instance)
63 {
64   char name[6 * DATA_MAX_NAME_LEN];
65   threshold_t *th = NULL;
66
67   format_name (name, sizeof (name),
68       (hostname == NULL) ? "" : hostname,
69       (plugin == NULL) ? "" : plugin, plugin_instance,
70       (type == NULL) ? "" : type, type_instance);
71   name[sizeof (name) - 1] = '\0';
72
73   if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
74     return (th);
75   else
76     return (NULL);
77 } /* threshold_t *threshold_get */
78
79 static int ut_threshold_add (const threshold_t *th)
80 {
81   char name[6 * DATA_MAX_NAME_LEN];
82   char *name_copy;
83   threshold_t *th_copy;
84   threshold_t *th_ptr;
85   int status = 0;
86
87   if (format_name (name, sizeof (name), th->host,
88         th->plugin, th->plugin_instance,
89         th->type, th->type_instance) != 0)
90   {
91     ERROR ("ut_threshold_add: format_name failed.");
92     return (-1);
93   }
94
95   name_copy = strdup (name);
96   if (name_copy == NULL)
97   {
98     ERROR ("ut_threshold_add: strdup failed.");
99     return (-1);
100   }
101
102   th_copy = (threshold_t *) malloc (sizeof (threshold_t));
103   if (th_copy == NULL)
104   {
105     sfree (name_copy);
106     ERROR ("ut_threshold_add: malloc failed.");
107     return (-1);
108   }
109   memcpy (th_copy, th, sizeof (threshold_t));
110   th_ptr = NULL;
111
112   DEBUG ("ut_threshold_add: Adding entry `%s'", name);
113
114   pthread_mutex_lock (&threshold_lock);
115
116   th_ptr = threshold_get (th->host, th->plugin, th->plugin_instance,
117       th->type, th->type_instance);
118
119   while ((th_ptr != NULL) && (th_ptr->next != NULL))
120     th_ptr = th_ptr->next;
121
122   if (th_ptr == NULL) /* no such threshold yet */
123   {
124     status = c_avl_insert (threshold_tree, name_copy, th_copy);
125   }
126   else /* th_ptr points to the last threshold in the list */
127   {
128     th_ptr->next = th_copy;
129     /* name_copy isn't needed */
130     sfree (name_copy);
131   }
132
133   pthread_mutex_unlock (&threshold_lock);
134
135   if (status != 0)
136   {
137     ERROR ("ut_threshold_add: c_avl_insert (%s) failed.", name);
138     sfree (name_copy);
139     sfree (th_copy);
140   }
141
142   return (status);
143 } /* int ut_threshold_add */
144 /*
145  * End of the threshold management functions
146  * }}} */
147
148 /*
149  * Configuration
150  * =============
151  * The following approximately two hundred functions are used to handle the
152  * configuration and fill the threshold list.
153  * {{{ */
154 static int ut_config_type_datasource (threshold_t *th, oconfig_item_t *ci)
155 {
156   if ((ci->values_num != 1)
157       || (ci->values[0].type != OCONFIG_TYPE_STRING))
158   {
159     WARNING ("threshold values: The `DataSource' option needs exactly one "
160         "string argument.");
161     return (-1);
162   }
163
164   sstrncpy (th->data_source, ci->values[0].value.string,
165       sizeof (th->data_source));
166
167   return (0);
168 } /* int ut_config_type_datasource */
169
170 static int ut_config_type_instance (threshold_t *th, oconfig_item_t *ci)
171 {
172   if ((ci->values_num != 1)
173       || (ci->values[0].type != OCONFIG_TYPE_STRING))
174   {
175     WARNING ("threshold values: The `Instance' option needs exactly one "
176         "string argument.");
177     return (-1);
178   }
179
180   sstrncpy (th->type_instance, ci->values[0].value.string,
181       sizeof (th->type_instance));
182
183   return (0);
184 } /* int ut_config_type_instance */
185
186 static int ut_config_type_max (threshold_t *th, oconfig_item_t *ci)
187 {
188   if ((ci->values_num != 1)
189       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
190   {
191     WARNING ("threshold values: The `%s' option needs exactly one "
192         "number argument.", ci->key);
193     return (-1);
194   }
195
196   if (strcasecmp (ci->key, "WarningMax") == 0)
197     th->warning_max = ci->values[0].value.number;
198   else
199     th->failure_max = ci->values[0].value.number;
200
201   return (0);
202 } /* int ut_config_type_max */
203
204 static int ut_config_type_min (threshold_t *th, oconfig_item_t *ci)
205 {
206   if ((ci->values_num != 1)
207       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
208   {
209     WARNING ("threshold values: The `%s' option needs exactly one "
210         "number argument.", ci->key);
211     return (-1);
212   }
213
214   if (strcasecmp (ci->key, "WarningMin") == 0)
215     th->warning_min = ci->values[0].value.number;
216   else
217     th->failure_min = ci->values[0].value.number;
218
219   return (0);
220 } /* int ut_config_type_min */
221
222 static int ut_config_type_hits (threshold_t *th, oconfig_item_t *ci)
223 {
224   if ((ci->values_num != 1)
225       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
226   {
227     WARNING ("threshold values: The `%s' option needs exactly one "
228       "number argument.", ci->key);
229     return (-1);
230   }
231
232   th->hits = ci->values[0].value.number;
233
234   return (0);
235 } /* int ut_config_type_hits */
236
237 static int ut_config_type_hysteresis (threshold_t *th, oconfig_item_t *ci)
238 {
239   if ((ci->values_num != 1)
240       || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
241   {
242     WARNING ("threshold values: The `%s' option needs exactly one "
243       "number argument.", ci->key);
244     return (-1);
245   }
246
247   th->hysteresis = ci->values[0].value.number;
248
249   return (0);
250 } /* int ut_config_type_hysteresis */
251
252 static int ut_config_type_message (threshold_t *th, oconfig_item_t *ci)
253 {
254
255   if ((ci->values_num != 1)
256       || (ci->values[0].type != OCONFIG_TYPE_STRING))
257   {
258     WARNING ("threshold values: The `%s' option needs exactly one "
259       "string argument.", ci->key);
260     return (-1);
261   }
262
263   if (ci->values[0].value.string[0] == 0)
264   {
265     WARNING ("threshold values: The `%s' option does not accept empty strings.",
266         ci->key);
267     return (-1);
268   }
269
270   th->message = strdup (ci->values[0].value.string);
271   if (th->message == NULL)
272   {
273     ERROR ("ut_config_type_message: sstrdup failed.");
274     return (-1);
275   }
276
277   return (0);
278 } /* int ut_config_type_message */
279
280 static int ut_config_type_missingmessage (threshold_t *th, oconfig_item_t *ci)
281 {
282
283   if ((ci->values_num != 1)
284       || (ci->values[0].type != OCONFIG_TYPE_STRING))
285   {
286     WARNING ("threshold values: The `%s' option needs exactly one "
287       "string argument.", ci->key);
288     return (-1);
289   }
290
291   if (ci->values[0].value.string[0] == 0)
292   {
293     WARNING ("threshold values: The `%s' option does not accept empty strings.",
294         ci->key);
295     return (-1);
296   }
297
298   th->missing_message = strdup (ci->values[0].value.string);
299   if (th->missing_message == NULL)
300   {
301     ERROR ("ut_config_type_missingmessage: sstrdup failed.");
302     return (-1);
303   }
304
305   return (0);
306 } /* int ut_config_type_missingmessage */
307
308 static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
309 {
310   int i;
311   threshold_t th;
312   int status = 0;
313
314   if ((ci->values_num != 1)
315       || (ci->values[0].type != OCONFIG_TYPE_STRING))
316   {
317     WARNING ("threshold values: The `Type' block needs exactly one string "
318         "argument.");
319     return (-1);
320   }
321
322   if (ci->children_num < 1)
323   {
324     WARNING ("threshold values: The `Type' block needs at least one option.");
325     return (-1);
326   }
327
328   memcpy (&th, th_orig, sizeof (th));
329   sstrncpy (th.type, ci->values[0].value.string, sizeof (th.type));
330
331   th.warning_min = NAN;
332   th.warning_max = NAN;
333   th.failure_min = NAN;
334   th.failure_max = NAN;
335   th.hits = 0;
336   th.hysteresis = 0;
337   th.message = NULL;
338   th.flags = UT_FLAG_INTERESTING; /* interesting by default */
339
340   for (i = 0; i < ci->children_num; i++)
341   {
342     oconfig_item_t *option = ci->children + i;
343     status = 0;
344
345     if (strcasecmp ("Instance", option->key) == 0)
346       status = ut_config_type_instance (&th, option);
347     else if (strcasecmp ("DataSource", option->key) == 0)
348       status = ut_config_type_datasource (&th, option);
349     else if ((strcasecmp ("WarningMax", option->key) == 0)
350         || (strcasecmp ("FailureMax", option->key) == 0))
351       status = ut_config_type_max (&th, option);
352     else if ((strcasecmp ("WarningMin", option->key) == 0)
353         || (strcasecmp ("FailureMin", option->key) == 0))
354       status = ut_config_type_min (&th, option);
355     else if (strcasecmp ("Interesting", option->key) == 0)
356       status = cf_util_get_flag (option, &th.flags, UT_FLAG_INTERESTING);
357     else if (strcasecmp ("Invert", option->key) == 0)
358       status = cf_util_get_flag (option, &th.flags, UT_FLAG_INVERT);
359     else if (strcasecmp ("Persist", option->key) == 0)
360       status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERSIST);
361     else if (strcasecmp ("Percentage", option->key) == 0)
362       status = cf_util_get_flag (option, &th.flags, UT_FLAG_PERCENTAGE);
363     else if (strcasecmp ("Hits", option->key) == 0)
364       status = ut_config_type_hits (&th, option);
365     else if (strcasecmp ("Hysteresis", option->key) == 0)
366       status = ut_config_type_hysteresis (&th, option);
367     else if (strcasecmp ("Message", option->key) == 0)
368       status = ut_config_type_message (&th, option);
369     else if (strcasecmp ("MissingMessage", option->key) == 0)
370       status = ut_config_type_missingmessage (&th, option);
371     else
372     {
373       WARNING ("threshold values: Option `%s' not allowed inside a `Type' "
374           "block.", option->key);
375       status = -1;
376     }
377
378     if (status != 0)
379       break;
380   }
381
382   if (status == 0)
383   {
384     status = ut_threshold_add (&th);
385   }
386
387   return (status);
388 } /* int ut_config_type */
389
390 static int ut_config_plugin_instance (threshold_t *th, oconfig_item_t *ci)
391 {
392   if ((ci->values_num != 1)
393       || (ci->values[0].type != OCONFIG_TYPE_STRING))
394   {
395     WARNING ("threshold values: The `Instance' option needs exactly one "
396         "string argument.");
397     return (-1);
398   }
399
400   sstrncpy (th->plugin_instance, ci->values[0].value.string,
401       sizeof (th->plugin_instance));
402
403   return (0);
404 } /* int ut_config_plugin_instance */
405
406 static int ut_config_plugin (const threshold_t *th_orig, oconfig_item_t *ci)
407 {
408   int i;
409   threshold_t th;
410   int status = 0;
411
412   if ((ci->values_num != 1)
413       || (ci->values[0].type != OCONFIG_TYPE_STRING))
414   {
415     WARNING ("threshold values: The `Plugin' block needs exactly one string "
416         "argument.");
417     return (-1);
418   }
419
420   if (ci->children_num < 1)
421   {
422     WARNING ("threshold values: The `Plugin' block needs at least one nested "
423         "block.");
424     return (-1);
425   }
426
427   memcpy (&th, th_orig, sizeof (th));
428   sstrncpy (th.plugin, ci->values[0].value.string, sizeof (th.plugin));
429
430   for (i = 0; i < ci->children_num; i++)
431   {
432     oconfig_item_t *option = ci->children + i;
433     status = 0;
434
435     if (strcasecmp ("Type", option->key) == 0)
436       status = ut_config_type (&th, option);
437     else if (strcasecmp ("Instance", option->key) == 0)
438       status = ut_config_plugin_instance (&th, option);
439     else
440     {
441       WARNING ("threshold values: Option `%s' not allowed inside a `Plugin' "
442           "block.", option->key);
443       status = -1;
444     }
445
446     if (status != 0)
447       break;
448   }
449
450   return (status);
451 } /* int ut_config_plugin */
452
453 static int ut_config_host (const threshold_t *th_orig, oconfig_item_t *ci)
454 {
455   int i;
456   threshold_t th;
457   int status = 0;
458
459   if ((ci->values_num != 1)
460       || (ci->values[0].type != OCONFIG_TYPE_STRING))
461   {
462     WARNING ("threshold values: The `Host' block needs exactly one string "
463         "argument.");
464     return (-1);
465   }
466
467   if (ci->children_num < 1)
468   {
469     WARNING ("threshold values: The `Host' block needs at least one nested "
470         "block.");
471     return (-1);
472   }
473
474   memcpy (&th, th_orig, sizeof (th));
475   sstrncpy (th.host, ci->values[0].value.string, sizeof (th.host));
476
477   for (i = 0; i < ci->children_num; i++)
478   {
479     oconfig_item_t *option = ci->children + i;
480     status = 0;
481
482     if (strcasecmp ("Type", option->key) == 0)
483       status = ut_config_type (&th, option);
484     else if (strcasecmp ("Plugin", option->key) == 0)
485       status = ut_config_plugin (&th, option);
486     else
487     {
488       WARNING ("threshold values: Option `%s' not allowed inside a `Host' "
489           "block.", option->key);
490       status = -1;
491     }
492
493     if (status != 0)
494       break;
495   }
496
497   return (status);
498 } /* int ut_config_host */
499
500 int ut_config (const oconfig_item_t *ci)
501 {
502   int i;
503   int status = 0;
504
505   threshold_t th;
506
507   if (ci->values_num != 0)
508   {
509     ERROR ("threshold values: The `Threshold' block may not have any "
510         "arguments.");
511     return (-1);
512   }
513
514   if (threshold_tree == NULL)
515   {
516     threshold_tree = c_avl_create ((void *) strcmp);
517     if (threshold_tree == NULL)
518     {
519       ERROR ("ut_config: c_avl_create failed.");
520       return (-1);
521     }
522   }
523
524   memset (&th, '\0', sizeof (th));
525   th.warning_min = NAN;
526   th.warning_max = NAN;
527   th.failure_min = NAN;
528   th.failure_max = NAN;
529
530   th.hits = 0;
531   th.hysteresis = 0;
532   th.flags = UT_FLAG_INTERESTING; /* interesting by default */
533     
534   for (i = 0; i < ci->children_num; i++)
535   {
536     oconfig_item_t *option = ci->children + i;
537     status = 0;
538
539     if (strcasecmp ("Type", option->key) == 0)
540       status = ut_config_type (&th, option);
541     else if (strcasecmp ("Plugin", option->key) == 0)
542       status = ut_config_plugin (&th, option);
543     else if (strcasecmp ("Host", option->key) == 0)
544       status = ut_config_host (&th, option);
545     else
546     {
547       WARNING ("threshold values: Option `%s' not allowed here.", option->key);
548       status = -1;
549     }
550
551     if (status != 0)
552       break;
553   }
554
555   return (status);
556 } /* int um_config */
557 /*
558  * End of the functions used to configure threshold values.
559  */
560 /* }}} */
561
562 static threshold_t *threshold_search (const value_list_t *vl)
563 {
564   threshold_t *th;
565
566   if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
567           vl->type, vl->type_instance)) != NULL)
568     return (th);
569   else if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
570           vl->type, NULL)) != NULL)
571     return (th);
572   else if ((th = threshold_get (vl->host, vl->plugin, NULL,
573           vl->type, vl->type_instance)) != NULL)
574     return (th);
575   else if ((th = threshold_get (vl->host, vl->plugin, NULL,
576           vl->type, NULL)) != NULL)
577     return (th);
578   else if ((th = threshold_get (vl->host, "", NULL,
579           vl->type, vl->type_instance)) != NULL)
580     return (th);
581   else if ((th = threshold_get (vl->host, "", NULL,
582           vl->type, NULL)) != NULL)
583     return (th);
584   else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
585           vl->type, vl->type_instance)) != NULL)
586     return (th);
587   else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
588           vl->type, NULL)) != NULL)
589     return (th);
590   else if ((th = threshold_get ("", vl->plugin, NULL,
591           vl->type, vl->type_instance)) != NULL)
592     return (th);
593   else if ((th = threshold_get ("", vl->plugin, NULL,
594           vl->type, NULL)) != NULL)
595     return (th);
596   else if ((th = threshold_get ("", "", NULL,
597           vl->type, vl->type_instance)) != NULL)
598     return (th);
599   else if ((th = threshold_get ("", "", NULL,
600           vl->type, NULL)) != NULL)
601     return (th);
602
603   return (NULL);
604 } /* threshold_t *threshold_search */
605
606 /* char *ut_build_message
607  *
608  * Return a custom formated message for dataset, values and previously created
609  * notification (which must include time and other fields), if th is present,
610  * templates for threshold will be interpreted, if th is NULL these will be
611  * skipped.
612  */
613 int ut_build_message(char *out, size_t bufsize, const char *fmt,
614     const data_set_t *ds, int ds_index, const value_list_t *vl, const gauge_t *values,
615     const notification_t *n, const threshold_t *th)
616 {
617
618   /* TODO: We could provide here a way to use meta information on thresholds
619    * directly in the future. */
620   char msg[NOTIF_MAX_MSG_LEN];
621   char temp[NOTIF_MAX_MSG_LEN];
622   gauge_t *rates;
623   int rates_failed;
624
625   int i;
626
627   sstrncpy (msg, fmt, sizeof (msg));
628
629 #define REPLACE_FIELD(t,v) \
630   if (subst_string (temp, sizeof (temp), msg, t, v) != NULL) \
631   sstrncpy (msg, temp, sizeof (msg));
632
633   char ftoa_temp[NOTIF_MAX_MSG_LEN];
634 #define FTOA(string,f) \
635   memset(string,0x00,sizeof(string)); \
636   snprintf(string, sizeof(string), "%f", f);
637
638   char itoa_temp[NOTIF_MAX_MSG_LEN];
639 #define ITOA(string,i) \
640   memset(string,0x00,sizeof(string)); \
641   snprintf(string, sizeof(string), "%i", i);
642
643   REPLACE_FIELD ("%{host}", n->host);
644   REPLACE_FIELD ("%{plugin}", n->plugin);
645   REPLACE_FIELD ("%{plugin_instance}", n->plugin_instance);
646   REPLACE_FIELD ("%{type}", n->type);
647   REPLACE_FIELD ("%{type_instance}", n->type_instance);
648   REPLACE_FIELD ("%{data_source}", ds->ds[ds_index].name);
649
650   /* This is the offending value, its equivalent to %{ds:value}, if
651    * value is the data_source name. */
652   FTOA(ftoa_temp,values[ds_index])
653     REPLACE_FIELD ("%{value}", ftoa_temp);
654
655   /* Now replace all %{ds:<template>} like target_notification does */
656   rates_failed = 0;
657   rates = NULL;
658   for (i = 0; i < ds->ds_num; i++)
659   {
660     char template[DATA_MAX_NAME_LEN];
661     char value_str[DATA_MAX_NAME_LEN];
662
663     ssnprintf (template, sizeof (template), "%%{ds:%s}", ds->ds[i].name);
664
665     if (ds->ds[i].type != DS_TYPE_GAUGE)
666     {
667       if ((rates == NULL) && (rates_failed == 0))
668       {
669         rates = uc_get_rate (ds, vl);
670         if (rates == NULL)
671           rates_failed = 1;
672       }
673     }
674
675     /* If this is a gauge value, use the current value. */
676     if (ds->ds[i].type == DS_TYPE_GAUGE)
677       ssnprintf (value_str, sizeof (value_str),
678           "%g", (double) vl->values[i].gauge);
679     /* If it's a counter, try to use the current rate. This may fail, if the
680      * value has been renamed. */
681     else if (rates != NULL)
682       ssnprintf (value_str, sizeof (value_str),
683           "%g", (double) rates[i]);
684     /* Since we don't know any better, use the string `unknown'. */
685     else
686       sstrncpy (value_str, "unknown", sizeof (value_str));
687
688     REPLACE_FIELD (template, value_str);
689   }
690   sfree (rates);
691
692   if (th != NULL) {
693     if ( !isnan(th->warning_min)) {
694       FTOA(ftoa_temp,th->warning_min)
695         REPLACE_FIELD ("%{warning_min}", ftoa_temp);
696     }
697     if ( !isnan(th->warning_max)) {
698       FTOA(ftoa_temp,th->warning_max)
699         REPLACE_FIELD ("%{warning_max}", ftoa_temp);
700     }
701     if ( !isnan(th->failure_min)) {
702       FTOA(ftoa_temp,th->failure_min)
703         REPLACE_FIELD ("%{failure_min}", ftoa_temp);
704     }
705     if ( !isnan(th->failure_max)) {
706       FTOA(ftoa_temp,th->failure_max)
707         REPLACE_FIELD ("%{failure_max}", ftoa_temp);
708     }
709
710     FTOA(ftoa_temp,th->hysteresis)
711       REPLACE_FIELD ("%{hysteresis}", ftoa_temp);
712
713     ITOA(itoa_temp,th->hits)
714       REPLACE_FIELD ("%{hits}", itoa_temp);
715   }
716
717   return ssnprintf (out, bufsize, "%s", msg);
718 } /* int ut_build_message */
719
720 /*
721  * int ut_report_state
722  *
723  * Checks if the `state' differs from the old state and creates a notification
724  * if appropriate.
725  * Does not fail.
726  */
727 static int ut_report_state (const data_set_t *ds,
728     const value_list_t *vl,
729     const threshold_t *th,
730     const gauge_t *values,
731     int ds_index,
732     int state)
733 { /* {{{ */
734   int state_old;
735   notification_t n;
736
737   char *buf;
738   size_t bufsize;
739
740   int status;
741
742   /* Check if hits matched */
743   if ( (th->hits != 0) )
744   {
745     int hits = uc_get_hits(ds,vl);
746     /* The STATE_OKAY always reset hits, or if hits reaise the limit */
747     if ( (state == STATE_OKAY) || (hits > th->hits) )
748     {
749         DEBUG("ut_report_state: reset uc_get_hits = 0");
750         uc_set_hits(ds,vl,0); /* reset hit counter and notify */
751     } else {
752       DEBUG("ut_report_state: th->hits = %d, uc_get_hits = %d",th->hits,uc_get_hits(ds,vl));
753       (void) uc_inc_hits(ds,vl,1); /* increase hit counter */
754       return (0);
755     }
756   } /* end check hits */
757
758   state_old = uc_get_state (ds, vl);
759
760   /* If the state didn't change, only report if `persistent' is specified and
761    * the state is not `okay'. */
762   if (state == state_old)
763   {
764     if ((th->flags & UT_FLAG_PERSIST) == 0)
765       return (0);
766     else if (state == STATE_OKAY)
767       return (0);
768   }
769
770   if (state != state_old)
771     uc_set_state (ds, vl, state);
772
773   NOTIFICATION_INIT_VL (&n, vl, ds);
774
775   buf = n.message;
776   bufsize = sizeof (n.message);
777
778   if (state == STATE_OKAY)
779     n.severity = NOTIF_OKAY;
780   else if (state == STATE_WARNING)
781     n.severity = NOTIF_WARNING;
782   else
783     n.severity = NOTIF_FAILURE;
784
785   n.time = vl->time;
786
787   /* Format custom message if present */
788   if (th->message != NULL)
789   {
790     status = ut_build_message (buf, bufsize, th->message,
791         ds, ds_index, vl, values,
792         &n, th);
793     buf += status;
794     bufsize -= status;
795   }
796   else /* No custom message. Using default message for threshold */
797   {
798     status = ssnprintf (buf, bufsize, "Host %s, plugin %s",
799         vl->host, vl->plugin);
800     buf += status;
801     bufsize -= status;
802
803     if (vl->plugin_instance[0] != '\0')
804     {
805       status = ssnprintf (buf, bufsize, " (instance %s)",
806           vl->plugin_instance);
807       buf += status;
808       bufsize -= status;
809     }
810
811     status = ssnprintf (buf, bufsize, " type %s", vl->type);
812     buf += status;
813     bufsize -= status;
814
815     if (vl->type_instance[0] != '\0')
816     {
817       status = ssnprintf (buf, bufsize, " (instance %s)",
818           vl->type_instance);
819       buf += status;
820       bufsize -= status;
821     }
822
823     /* Build okay notification message */
824     if (state == STATE_OKAY)
825     {
826       status = ssnprintf (buf, bufsize, ": All data sources are within range again.");
827       buf += status;
828       bufsize -= status;
829     }
830     else /* build non-okay notification message */
831     {
832       double min;
833       double max;
834
835       min = (state == STATE_ERROR) ? th->failure_min : th->warning_min;
836       max = (state == STATE_ERROR) ? th->failure_max : th->warning_max;
837
838       if (th->flags & UT_FLAG_INVERT)
839       {
840         if (!isnan (min) && !isnan (max))
841         {
842           status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
843               "%f. That is within the %s region of %f%s and %f%s.",
844               ds->ds[ds_index].name, values[ds_index],
845               (state == STATE_ERROR) ? "failure" : "warning",
846               min, ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "",
847               max, ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
848         }
849         else
850         {
851           status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
852               "%f. That is %s the %s threshold of %f%s.",
853               ds->ds[ds_index].name, values[ds_index],
854               isnan (min) ? "below" : "above",
855               (state == STATE_ERROR) ? "failure" : "warning",
856               isnan (min) ? max : min,
857               ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
858         }
859       }
860       else /* is not inverted */
861       {
862         status = ssnprintf (buf, bufsize, ": Data source \"%s\" is currently "
863             "%f. That is %s the %s threshold of %f%s.",
864             ds->ds[ds_index].name, values[ds_index],
865             (values[ds_index] < min) ? "below" : "above",
866             (state == STATE_ERROR) ? "failure" : "warning",
867             (values[ds_index] < min) ? min : max,
868             ((th->flags & UT_FLAG_PERCENTAGE) != 0) ? "%" : "");
869       }
870       buf += status;
871       bufsize -= status;
872     }
873   }
874
875   /* adds meta to notification */
876   plugin_notification_meta_add_string (&n, "DataSource",
877       ds->ds[ds_index].name);
878   plugin_notification_meta_add_double (&n, "CurrentValue", values[ds_index]);
879   plugin_notification_meta_add_double (&n, "WarningMin", th->warning_min);
880   plugin_notification_meta_add_double (&n, "WarningMax", th->warning_max);
881   plugin_notification_meta_add_double (&n, "FailureMin", th->failure_min);
882   plugin_notification_meta_add_double (&n, "FailureMax", th->failure_max);
883
884   plugin_dispatch_notification (&n);
885
886   plugin_notification_meta_free (n.meta);
887   return (0);
888 } /* }}} int ut_report_state */
889
890 /*
891  * int ut_check_one_data_source
892  *
893  * Checks one data source against the given threshold configuration. If the
894  * `DataSource' option is set in the threshold, and the name does NOT match,
895  * `okay' is returned. If the threshold does match, its failure and warning
896  * min and max values are checked and `failure' or `warning' is returned if
897  * appropriate.
898  * Does not fail.
899  */
900 static int ut_check_one_data_source (const data_set_t *ds,
901     const value_list_t __attribute__((unused)) *vl,
902     const threshold_t *th,
903     const gauge_t *values,
904     int ds_index)
905 { /* {{{ */
906   const char *ds_name;
907   int is_warning = 0;
908   int is_failure = 0;
909   int prev_state = STATE_OKAY;
910
911   /* check if this threshold applies to this data source */
912   if (ds != NULL)
913   {
914     ds_name = ds->ds[ds_index].name;
915     if ((th->data_source[0] != 0)
916         && (strcmp (ds_name, th->data_source) != 0))
917       return (STATE_OKAY);
918   }
919
920   if ((th->flags & UT_FLAG_INVERT) != 0)
921   {
922     is_warning--;
923     is_failure--;
924   }
925
926   /* XXX: This is an experimental code, not optimized, not fast, not reliable,
927    * and probably, do not work as you expect. Enjoy! :D */
928   if ( (th->hysteresis > 0) && ((prev_state = uc_get_state(ds,vl)) != STATE_OKAY) )
929   {
930     switch(prev_state)
931     {
932       case STATE_ERROR:
933         if ( (!isnan (th->failure_min) && ((th->failure_min + th->hysteresis) < values[ds_index])) ||
934              (!isnan (th->failure_max) && ((th->failure_max - th->hysteresis) > values[ds_index])) )
935           return (STATE_OKAY);
936         else
937           is_failure++;
938       case STATE_WARNING:
939         if ( (!isnan (th->warning_min) && ((th->warning_min + th->hysteresis) < values[ds_index])) ||
940              (!isnan (th->warning_max) && ((th->warning_max - th->hysteresis) > values[ds_index])) )
941           return (STATE_OKAY);
942         else
943           is_warning++;
944      }
945   }
946   else { /* no hysteresis */
947     if ((!isnan (th->failure_min) && (th->failure_min > values[ds_index]))
948         || (!isnan (th->failure_max) && (th->failure_max < values[ds_index])))
949       is_failure++;
950
951     if ((!isnan (th->warning_min) && (th->warning_min > values[ds_index]))
952         || (!isnan (th->warning_max) && (th->warning_max < values[ds_index])))
953       is_warning++;
954  }
955
956   if (is_failure != 0)
957     return (STATE_ERROR);
958
959   if (is_warning != 0)
960     return (STATE_WARNING);
961
962   return (STATE_OKAY);
963 } /* }}} int ut_check_one_data_source */
964
965 /*
966  * int ut_check_one_threshold
967  *
968  * Checks all data sources of a value list against the given threshold, using
969  * the ut_check_one_data_source function above. Returns the worst status,
970  * which is `okay' if nothing has failed.
971  * Returns less than zero if the data set doesn't have any data sources.
972  */
973 static int ut_check_one_threshold (const data_set_t *ds,
974     const value_list_t *vl,
975     const threshold_t *th,
976     const gauge_t *values,
977     int *ret_ds_index)
978 { /* {{{ */
979   int ret = -1;
980   int ds_index = -1;
981   int i;
982   gauge_t values_copy[ds->ds_num];
983
984   memcpy (values_copy, values, sizeof (values_copy));
985
986   if ((th->flags & UT_FLAG_PERCENTAGE) != 0)
987   {
988     int num = 0;
989     gauge_t sum=0.0;
990
991     if (ds->ds_num == 1)
992     {
993       WARNING ("ut_check_one_threshold: The %s type has only one data "
994           "source, but you have configured to check this as a percentage. "
995           "That doesn't make much sense, because the percentage will always "
996           "be 100%%!", ds->type);
997     }
998
999     /* Prepare `sum' and `num'. */
1000     for (i = 0; i < ds->ds_num; i++)
1001       if (!isnan (values[i]))
1002       {
1003         num++;
1004         sum += values[i];
1005       }
1006
1007     if ((num == 0) /* All data sources are undefined. */
1008         || (sum == 0.0)) /* Sum is zero, cannot calculate percentage. */
1009     {
1010       for (i = 0; i < ds->ds_num; i++)
1011         values_copy[i] = NAN;
1012     }
1013     else /* We can actually calculate the percentage. */
1014     {
1015       for (i = 0; i < ds->ds_num; i++)
1016         values_copy[i] = 100.0 * values[i] / sum;
1017     }
1018   } /* if (UT_FLAG_PERCENTAGE) */
1019
1020   for (i = 0; i < ds->ds_num; i++)
1021   {
1022     int status;
1023
1024     status = ut_check_one_data_source (ds, vl, th, values_copy, i);
1025     if (ret < status)
1026     {
1027       ret = status;
1028       ds_index = i;
1029     }
1030   } /* for (ds->ds_num) */
1031
1032   if (ret_ds_index != NULL)
1033     *ret_ds_index = ds_index;
1034
1035   return (ret);
1036 } /* }}} int ut_check_one_threshold */
1037
1038 /*
1039  * int ut_check_threshold (PUBLIC)
1040  *
1041  * Gets a list of matching thresholds and searches for the worst status by one
1042  * of the thresholds. Then reports that status using the ut_report_state
1043  * function above. 
1044  * Returns zero on success and if no threshold has been configured. Returns
1045  * less than zero on failure.
1046  */
1047 int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
1048 { /* {{{ */
1049   threshold_t *th;
1050   gauge_t *values;
1051   int status;
1052
1053   int worst_state = -1;
1054   threshold_t *worst_th = NULL;
1055   int worst_ds_index = -1;
1056
1057   if (threshold_tree == NULL)
1058     return (0);
1059
1060   /* Is this lock really necessary? So far, thresholds are only inserted at
1061    * startup. -octo */
1062   pthread_mutex_lock (&threshold_lock);
1063   th = threshold_search (vl);
1064   pthread_mutex_unlock (&threshold_lock);
1065   if (th == NULL)
1066     return (0);
1067
1068   DEBUG ("ut_check_threshold: Found matching threshold(s)");
1069
1070   values = uc_get_rate (ds, vl);
1071   if (values == NULL)
1072     return (0);
1073
1074   while (th != NULL)
1075   {
1076     int ds_index = -1;
1077
1078     status = ut_check_one_threshold (ds, vl, th, values, &ds_index);
1079     if (status < 0)
1080     {
1081       ERROR ("ut_check_threshold: ut_check_one_threshold failed.");
1082       sfree (values);
1083       return (-1);
1084     }
1085
1086     if (worst_state < status)
1087     {
1088       worst_state = status;
1089       worst_th = th;
1090       worst_ds_index = ds_index;
1091     }
1092
1093     th = th->next;
1094   } /* while (th) */
1095
1096   status = ut_report_state (ds, vl, worst_th, values,
1097       worst_ds_index, worst_state);
1098   if (status != 0)
1099   {
1100     ERROR ("ut_check_threshold: ut_report_state failed.");
1101     sfree (values);
1102     return (-1);
1103   }
1104
1105   sfree (values);
1106
1107   return (0);
1108 } /* }}} int ut_check_threshold */
1109
1110 /*
1111  * int ut_check_interesting (PUBLIC)
1112  *
1113  * Given an identification returns
1114  * 0: No threshold is defined.
1115  * 1: A threshold has been found. The flag `persist' is off.
1116  * 2: A threshold has been found. The flag `persist' is on.
1117  *    (That is, it is expected that many notifications are sent until the
1118  *    problem disappears.)
1119  */
1120 int ut_check_interesting (const char *name)
1121 { /* {{{ */
1122   char *name_copy = NULL;
1123   char *host = NULL;
1124   char *plugin = NULL;
1125   char *plugin_instance = NULL;
1126   char *type = NULL;
1127   char *type_instance = NULL;
1128   int status;
1129   data_set_t ds;
1130   value_list_t vl;
1131   threshold_t *th;
1132
1133   /* If there is no tree nothing is interesting. */
1134   if (threshold_tree == NULL)
1135     return (0);
1136
1137   name_copy = strdup (name);
1138   if (name_copy == NULL)
1139   {
1140     ERROR ("ut_check_interesting: strdup failed.");
1141     return (-1);
1142   }
1143
1144   status = parse_identifier (name_copy, &host,
1145       &plugin, &plugin_instance, &type, &type_instance);
1146   if (status != 0)
1147   {
1148     ERROR ("ut_check_interesting: parse_identifier failed.");
1149     sfree (name_copy);
1150     return (-1);
1151   }
1152
1153   memset (&ds, '\0', sizeof (ds));
1154   memset (&vl, '\0', sizeof (vl));
1155
1156   sstrncpy (vl.host, host, sizeof (vl.host));
1157   sstrncpy (vl.plugin, plugin, sizeof (vl.plugin));
1158   if (plugin_instance != NULL)
1159     sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
1160   sstrncpy (ds.type, type, sizeof (ds.type));
1161   sstrncpy (vl.type, type, sizeof (vl.type));
1162   if (type_instance != NULL)
1163     sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
1164
1165   sfree (name_copy);
1166   host = plugin = plugin_instance = type = type_instance = NULL;
1167
1168   th = threshold_search (&vl);
1169   if (th == NULL)
1170     return (0);
1171
1172   if ((th->flags & UT_FLAG_INTERESTING) == 0)
1173     return (0);
1174
1175   if ((th->flags & UT_FLAG_PERSIST) == 0)
1176     return (1);
1177   return (2);
1178 } /* }}} int ut_check_interesting */
1179
1180 int ut_search_threshold (const value_list_t *vl, /* {{{ */
1181     threshold_t *ret_threshold)
1182 {
1183   threshold_t *t;
1184
1185   if (vl == NULL)
1186     return (EINVAL);
1187
1188   t = threshold_search (vl);
1189   if (t == NULL)
1190     return (ENOENT);
1191
1192   memcpy (ret_threshold, t, sizeof (*ret_threshold));
1193   ret_threshold->next = NULL;
1194
1195   return (0);
1196 } /* }}} int ut_search_threshold */
1197
1198 /* vim: set sw=2 ts=8 sts=2 tw=78 et fdm=marker : */