Code

store: Don't return an error if an updated host is too old.
[sysdb.git] / src / backend / collectd.c
1 /*
2  * syscollector - src/backend/collectd.c
3  * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 #include "syscollector.h"
29 #include "core/plugin.h"
30 #include "core/store.h"
31 #include "utils/string.h"
32 #include "utils/unixsock.h"
34 #include "liboconfig/utils.h"
36 #include <assert.h>
38 #include <errno.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
44 SC_PLUGIN_MAGIC;
46 /*
47  * private helper functions
48  */
50 static int
51 sc_collectd_parse_value(char *line,
52                 sc_time_t *timestamp, char **host, char **svc)
53 {
54         char *timestamp_str;
55         double timestamp_dbl;
57         char *hostname, *service;
59         char *endptr = NULL;
61         timestamp_str = line;
62         hostname = strchr(timestamp_str, ' ');
64         if (! hostname) {
65                 fprintf(stderr, "collectd backend: Failed to find hostname "
66                                 "in LISTVAL output, line: %s\n", line);
67                 return -1;
68         }
70         *hostname = '\0';
71         ++hostname;
73         service = strchr(hostname, '/');
75         if (! service)
76                 fprintf(stderr, "collectd backend: Failed to find service name "
77                                 "in LISTVAL output, line: %s\n", line);
78         else {
79                 *service = '\0';
80                 ++service;
81         }
83         errno = 0;
84         timestamp_dbl = strtod(timestamp_str, &endptr);
85         if (errno || (timestamp_str == endptr)) {
86                 char errbuf[1024];
87                 fprintf(stderr, "collectd backend: Failed to "
88                                 "parse timestamp (%s): %s\n", timestamp_str,
89                                 sc_strerror(errno, errbuf, sizeof(errbuf)));
90                 return -1;
91         }
92         if (endptr && (*endptr != '\0'))
93                 fprintf(stderr, "collectd backend: Ignoring garbage "
94                                 "after number when parsing timestamp: %s.\n",
95                                 endptr);
97         *timestamp = DOUBLE_TO_SC_TIME(timestamp_dbl);
98         *host = hostname;
99         *svc  = service;
100         return 0;
101 } /* sc_collectd_parse_value */
103 static int
104 sc_collectd_add_host(char *hostname, sc_time_t last_update)
106         sc_host_t host = SC_HOST_INIT;
108         host.host_name = hostname;
109         host.host_last_update = last_update;
111         if (sc_store_host(&host)) {
112                 fprintf(stderr, "collectd backend: Failed to store/update "
113                                 "host '%s'.\n", hostname);
114                 return -1;
115         }
117         fprintf(stderr, "collectd backend: Added/updated host '%s' "
118                         "(last update timestamp = %"PRIscTIME").\n",
119                         hostname, last_update);
120         return 0;
121 } /* sc_collectd_add_host */
123 static int
124 sc_collectd_add_svc(char *hostname, char *name, sc_time_t last_update)
126         sc_service_t svc = SC_SVC_INIT;
128         svc.hostname = hostname;
129         svc.svc_name = name;
130         svc.svc_last_update = last_update;
132         if (sc_store_service(&svc)) {
133                 fprintf(stderr, "collectd backend: Failed to store/update "
134                                 "service '%s/%s'.\n", hostname, name);
135                 return -1;
136         }
137         return 0;
138 } /* sc_collectd_add_svc */
140 /*
141  * plugin API
142  */
144 static int
145 sc_collectd_init(sc_object_t *user_data)
147         sc_unixsock_client_t *client;
149         if (! user_data)
150                 return -1;
152         client = SC_OBJ_WRAPPER(user_data)->data;
153         if (sc_unixsock_client_connect(client)) {
154                 fprintf(stderr, "collectd backend: "
155                                 "Failed to connect to collectd.\n");
156                 return -1;
157         }
159         fprintf(stderr, "collectd backend: Successfully "
160                         "connected to collectd @ %s.\n",
161                         sc_unixsock_client_path(client));
162         return 0;
163 } /* sc_collectd_init */
165 static int
166 sc_collectd_shutdown(__attribute__((unused)) sc_object_t *user_data)
168         return 0;
169 } /* sc_collectd_shutdown */
171 static int
172 sc_collectd_collect(sc_object_t *user_data)
174         sc_unixsock_client_t *client;
176         char  buffer[1024];
177         char *line;
178         char *msg;
180         char *endptr = NULL;
181         long int count, i;
183         char *current_host = NULL;
184         sc_time_t current_timestamp = 0;
186         int svc_updated = 0;
187         int svc_failed  = 0;
189         if (! user_data)
190                 return -1;
192         client = SC_OBJ_WRAPPER(user_data)->data;
194         if (sc_unixsock_client_send(client, "LISTVAL") <= 0) {
195                 fprintf(stderr, "collectd backend: Failed to send LISTVAL command "
196                                 "to collectd @ %s.\n", sc_unixsock_client_path(client));
197                 return -1;
198         }
200         line = sc_unixsock_client_recv(client, buffer, sizeof(buffer));
201         if (! line) {
202                 fprintf(stderr, "collectd backend: Failed to read status "
203                                 "of LISTVAL command from collectd @ %s.\n",
204                                 sc_unixsock_client_path(client));
205                 return -1;
206         }
208         msg = strchr(line, ' ');
209         if (msg) {
210                 *msg = '\0';
211                 ++msg;
212         }
214         errno = 0;
215         count = strtol(line, &endptr, /* base */ 0);
216         if (errno || (line == endptr)) {
217                 fprintf(stderr, "collectd backend: Failed to parse status "
218                                 "of LISTVAL command from collectd @ %s.\n",
219                                 sc_unixsock_client_path(client));
220                 return -1;
221         }
223         if (count < 0) {
224                 fprintf(stderr, "collectd backend: Failed to get value list "
225                                 "from collectd @ %s: %s\n", sc_unixsock_client_path(client),
226                                 msg ? msg : line);
227                 return -1;
228         }
230         for (i = 0; i < count; ++i) {
231                 char *hostname = NULL, *service = NULL;
232                 sc_time_t last_update = 0;
234                 line = sc_unixsock_client_recv(client, buffer, sizeof(buffer));
236                 if (sc_collectd_parse_value(line, &last_update, &hostname, &service))
237                         continue;
239                 if (! current_host)
240                         current_host = strdup(hostname);
241                 if (! current_host) {
242                         char errbuf[1024];
243                         fprintf(stderr, "collectd backend: Failed to allocate "
244                                         "string buffer: %s\n",
245                                         sc_strerror(errno, errbuf, sizeof(errbuf)));
246                         return -1;
247                 }
249                 if (! sc_store_get_host(hostname))
250                         sc_collectd_add_host(hostname, last_update);
252                 if (sc_collectd_add_svc(hostname, service, last_update))
253                         ++svc_failed;
254                 else
255                         ++svc_updated;
257                 assert(hostname && service);
258                 if (! strcasecmp(current_host, hostname)) {
259                         if (last_update > current_timestamp)
260                                 current_timestamp = last_update;
261                         continue;
262                 }
264                 /* new host */
265                 sc_collectd_add_host(current_host, current_timestamp);
267                 fprintf(stderr, "collectd backend: Added/updated "
268                                 "%i service%s (%i failed) for host '%s'.\n",
269                                 svc_updated, svc_updated == 1 ? "" : "s",
270                                 svc_failed, current_host);
271                 svc_updated = svc_failed = 0;
273                 free(current_host);
274                 current_host = strdup(hostname);
275                 current_timestamp = last_update;
276         }
278         if (current_host) {
279                 sc_collectd_add_host(current_host, current_timestamp);
280                 fprintf(stderr, "collectd backend: Added/updated "
281                                 "%i service%s (%i failed) for host '%s'.\n",
282                                 svc_updated, svc_updated == 1 ? "" : "s",
283                                 svc_failed, current_host);
284         }
285         return 0;
286 } /* sc_collectd_collect */
288 static int
289 sc_collectd_config_instance(oconfig_item_t *ci)
291         char *name = NULL;
292         char *socket = NULL;
294         char cb_name[1024];
296         sc_object_t *user_data;
297         sc_unixsock_client_t *client;
299         int i;
301         if (oconfig_get_string(ci, &name)) {
302                 fprintf(stderr, "collectd backend: Instance requires a single "
303                                 "string argument\n\tUsage: <Instance NAME>\n");
304                 return -1;
305         }
307         for (i = 0; i < ci->children_num; ++i) {
308                 oconfig_item_t *child = ci->children + i;
310                 if (! strcasecmp(child->key, "Socket"))
311                         oconfig_get_string(child, &socket);
312                 else
313                         fprintf(stderr, "collectd backend: Ignoring unknown config "
314                                         "option '%s' inside <Instance %s>.\n",
315                                         child->key, name);
316         }
318         if (! socket) {
319                 fprintf(stderr, "collectd backend: Instance '%s' missing "
320                                 "the 'Socket' option.\n", name);
321                 return -1;
322         }
324         snprintf(cb_name, sizeof(cb_name), "collectd-%s", name);
325         cb_name[sizeof(cb_name) - 1] = '\0';
327         client = sc_unixsock_client_create(socket);
328         if (! client) {
329                 char errbuf[1024];
330                 fprintf(stderr, "collectd backend: Failed to create unixsock client: "
331                                 "%s\n", sc_strerror(errno, errbuf, sizeof(errbuf)));
332                 return -1;
333         }
335         user_data = sc_object_create_wrapper(client,
336                         (void (*)(void *))sc_unixsock_client_destroy);
337         if (! user_data) {
338                 sc_unixsock_client_destroy(client);
339                 fprintf(stderr, "collectd backend: Failed to allocate sc_object_t\n");
340                 return -1;
341         }
343         sc_plugin_register_init(cb_name, sc_collectd_init, user_data);
344         sc_plugin_register_shutdown(cb_name, sc_collectd_shutdown, user_data);
346         sc_plugin_register_collector(cb_name, sc_collectd_collect,
347                         /* interval */ NULL, user_data);
349         /* pass control to the list */
350         sc_object_deref(user_data);
351         return 0;
352 } /* sc_collectd_config_instance */
354 static int
355 sc_collectd_config(oconfig_item_t *ci)
357         int i;
359         for (i = 0; i < ci->children_num; ++i) {
360                 oconfig_item_t *child = ci->children + i;
362                 if (! strcasecmp(child->key, "Instance"))
363                         sc_collectd_config_instance(child);
364                 else
365                         fprintf(stderr, "collectd backend: Ignoring unknown config "
366                                         "option '%s'.\n", child->key);
367         }
368         return 0;
369 } /* sc_collectd_config */
371 int
372 sc_module_init(sc_plugin_info_t *info)
374         sc_plugin_set_info(info, SC_PLUGIN_INFO_NAME, "collectd");
375         sc_plugin_set_info(info, SC_PLUGIN_INFO_DESC,
376                         "backend accessing the system statistics collection daemon");
377         sc_plugin_set_info(info, SC_PLUGIN_INFO_COPYRIGHT,
378                         "Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>");
379         sc_plugin_set_info(info, SC_PLUGIN_INFO_LICENSE, "BSD");
380         sc_plugin_set_info(info, SC_PLUGIN_INFO_VERSION, SC_VERSION);
381         sc_plugin_set_info(info, SC_PLUGIN_INFO_PLUGIN_VERSION, SC_VERSION);
383         sc_plugin_register_config("collectd", sc_collectd_config);
384         return 0;
385 } /* sc_version_extra */
387 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */