ad5cad1b439906b31248963b33b4b99b23fd4870
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)
105 {
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)
125 {
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)
146 {
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)
167 {
168 return 0;
169 } /* sc_collectd_shutdown */
171 static int
172 sc_collectd_collect(sc_object_t *user_data)
173 {
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)
290 {
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)
356 {
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)
373 {
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 : */