Merge pull request #3329 from efuss/fix-3311
[collectd.git] / src / check_uptime.c
1 /**
2  * collectd - src/check_uptime.c
3  * Copyright (C) 2007-2019  Florian Forster
4  * Copyright (C) 2019  Pavel V. Rochnyack
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Author:
20  *   Florian octo Forster <octo at collectd.org>
21  *   Pavel Rochnyak <pavel2000 ngs.ru>
22  **/
23
24 #include "collectd.h"
25 #include "plugin.h"
26 #include "utils/avltree/avltree.h"
27 #include "utils/common/common.h"
28 #include "utils_cache.h"
29
30 /* Types are registered only in `config` phase, so access is not protected by
31  * locks */
32 c_avl_tree_t *types_tree = NULL;
33
34 static int format_uptime(unsigned long uptime_sec, char *buf, size_t bufsize) {
35
36   unsigned int uptime_days = uptime_sec / 24 / 3600;
37   uptime_sec -= uptime_days * 24 * 3600;
38   unsigned int uptime_hours = uptime_sec / 3600;
39   uptime_sec -= uptime_hours * 3600;
40   unsigned int uptime_mins = uptime_sec / 60;
41   uptime_sec -= uptime_mins * 60;
42
43   int ret = 0;
44   if (uptime_days) {
45     ret += snprintf(buf + ret, bufsize - ret, " %u day(s)", uptime_days);
46   }
47   if (uptime_days || uptime_hours) {
48     ret += snprintf(buf + ret, bufsize - ret, " %u hour(s)", uptime_hours);
49   }
50   if (uptime_days || uptime_hours || uptime_mins) {
51     ret += snprintf(buf + ret, bufsize - ret, " %u min", uptime_mins);
52   }
53   ret += snprintf(buf + ret, bufsize - ret, " %lu sec.", uptime_sec);
54   return ret;
55 }
56
57 static int cu_notify(enum cache_event_type_e event_type, const value_list_t *vl,
58                      gauge_t old_uptime, gauge_t new_uptime) {
59   notification_t n;
60   NOTIFICATION_INIT_VL(&n, vl);
61
62   int status;
63   char *buf = n.message;
64   size_t bufsize = sizeof(n.message);
65
66   n.time = vl->time;
67
68   const char *service = "Service";
69   if (strcmp(vl->plugin, "uptime") == 0)
70     service = "Host";
71
72   switch (event_type) {
73   case CE_VALUE_NEW:
74     n.severity = NOTIF_OKAY;
75     status = snprintf(buf, bufsize, "%s is running.", service);
76     buf += status;
77     bufsize -= status;
78     break;
79   case CE_VALUE_UPDATE:
80     n.severity = NOTIF_WARNING;
81     status = snprintf(buf, bufsize, "%s just restarted.", service);
82     buf += status;
83     bufsize -= status;
84     break;
85   case CE_VALUE_EXPIRED:
86     n.severity = NOTIF_FAILURE;
87     status = snprintf(buf, bufsize, "%s is unreachable.", service);
88     buf += status;
89     bufsize -= status;
90     break;
91   }
92
93   if (!isnan(old_uptime)) {
94     status = snprintf(buf, bufsize, " Uptime was:");
95     buf += status;
96     bufsize -= status;
97
98     status = format_uptime(old_uptime, buf, bufsize);
99     buf += status;
100     bufsize -= status;
101
102     plugin_notification_meta_add_double(&n, "LastValue", old_uptime);
103   }
104
105   if (!isnan(new_uptime)) {
106     status = snprintf(buf, bufsize, " Uptime now:");
107     buf += status;
108     bufsize -= status;
109
110     status = format_uptime(new_uptime, buf, bufsize);
111     buf += status;
112     bufsize -= status;
113
114     plugin_notification_meta_add_double(&n, "CurrentValue", new_uptime);
115   }
116
117   plugin_dispatch_notification(&n);
118
119   plugin_notification_meta_free(n.meta);
120   return 0;
121 }
122
123 static int cu_cache_event(cache_event_t *event,
124                           __attribute__((unused)) user_data_t *ud) {
125   gauge_t values_history[2];
126
127   /* For CE_VALUE_EXPIRED */
128   int ret;
129   value_t *values;
130   size_t values_num;
131   gauge_t old_uptime = NAN;
132
133   switch (event->type) {
134   case CE_VALUE_NEW:
135     DEBUG("check_uptime: CE_VALUE_NEW, %s", event->value_list_name);
136     if (c_avl_get(types_tree, event->value_list->type, NULL) == 0) {
137       event->ret = 1;
138       assert(event->value_list->values_len > 0);
139       cu_notify(CE_VALUE_NEW, event->value_list, NAN /* old */,
140                 event->value_list->values[0].gauge /* new */);
141     }
142     break;
143   case CE_VALUE_UPDATE:
144     DEBUG("check_uptime: CE_VALUE_UPDATE, %s", event->value_list_name);
145     if (uc_get_history_by_name(event->value_list_name, values_history, 2, 1)) {
146       ERROR("check_uptime plugin: Failed to get value history for %s.",
147             event->value_list_name);
148     } else {
149       if (!isnan(values_history[0]) && !isnan(values_history[1]) &&
150           values_history[0] < values_history[1]) {
151         cu_notify(CE_VALUE_UPDATE, event->value_list,
152                   values_history[1] /* old */, values_history[0] /* new */);
153       }
154     }
155     break;
156   case CE_VALUE_EXPIRED:
157     DEBUG("check_uptime: CE_VALUE_EXPIRED, %s", event->value_list_name);
158     ret = uc_get_value_by_name(event->value_list_name, &values, &values_num);
159     if (ret == 0) {
160       old_uptime = values[0].gauge;
161       sfree(values);
162     }
163
164     cu_notify(CE_VALUE_EXPIRED, event->value_list, old_uptime, NAN /* new */);
165     break;
166   }
167   return 0;
168 }
169
170 static int cu_config(oconfig_item_t *ci) {
171   if (types_tree == NULL) {
172     types_tree = c_avl_create((int (*)(const void *, const void *))strcmp);
173     if (types_tree == NULL) {
174       ERROR("check_uptime plugin: c_avl_create failed.");
175       return -1;
176     }
177   }
178
179   for (int i = 0; i < ci->children_num; ++i) {
180     oconfig_item_t *child = ci->children + i;
181     if (strcasecmp("Type", child->key) == 0) {
182       if ((child->values_num != 1) ||
183           (child->values[0].type != OCONFIG_TYPE_STRING)) {
184         WARNING("check_uptime plugin: The `Type' option needs exactly one "
185                 "string argument.");
186         return -1;
187       }
188       char *type = child->values[0].value.string;
189
190       if (c_avl_get(types_tree, type, NULL) == 0) {
191         ERROR("check_uptime plugin: Type `%s' already added.", type);
192         return -1;
193       }
194
195       char *type_copy = strdup(type);
196       if (type_copy == NULL) {
197         ERROR("check_uptime plugin: strdup failed.");
198         return -1;
199       }
200
201       int status = c_avl_insert(types_tree, type_copy, NULL);
202       if (status != 0) {
203         ERROR("check_uptime plugin: c_avl_insert failed.");
204         sfree(type_copy);
205         return -1;
206       }
207     } else
208       WARNING("check_uptime plugin: Ignore unknown config option `%s'.",
209               child->key);
210   }
211
212   return 0;
213 }
214
215 static int cu_init(void) {
216   if (types_tree == NULL) {
217     types_tree = c_avl_create((int (*)(const void *, const void *))strcmp);
218     if (types_tree == NULL) {
219       ERROR("check_uptime plugin: c_avl_create failed.");
220       return -1;
221     }
222     /* Default configuration */
223     char *type = strdup("uptime");
224     if (type == NULL) {
225       ERROR("check_uptime plugin: strdup failed.");
226       return -1;
227     }
228     int status = c_avl_insert(types_tree, type, NULL);
229     if (status != 0) {
230       ERROR("check_uptime plugin: c_avl_insert failed.");
231       sfree(type);
232       return -1;
233     }
234   }
235
236   int ret = 0;
237   char *type;
238   void *val;
239   c_avl_iterator_t *iter = c_avl_get_iterator(types_tree);
240   while (c_avl_iterator_next(iter, (void *)&type, (void *)&val) == 0) {
241     data_set_t const *ds = plugin_get_ds(type);
242     if (ds == NULL) {
243       ERROR("check_uptime plugin: Failed to look up type \"%s\".", type);
244       ret = -1;
245       continue;
246     }
247     if (ds->ds_num != 1) {
248       ERROR("check_uptime plugin: The type \"%s\" has %" PRIsz " data sources. "
249             "Only types with a single GAUGE data source are supported.",
250             ds->type, ds->ds_num);
251       ret = -1;
252       continue;
253     }
254     if (ds->ds[0].type != DS_TYPE_GAUGE) {
255       ERROR("check_uptime plugin: The type \"%s\" has wrong data source type. "
256             "Only types with a single GAUGE data source are supported.",
257             ds->type);
258       ret = -1;
259       continue;
260     }
261   }
262   c_avl_iterator_destroy(iter);
263
264   if (ret == 0)
265     plugin_register_cache_event("check_uptime", cu_cache_event, NULL);
266
267   return ret;
268 }
269
270 void module_register(void) {
271   plugin_register_complex_config("check_uptime", cu_config);
272   plugin_register_init("check_uptime", cu_init);
273 }