1 /*
2 * SysDB - src/plugins/backend/collectd/unixsock.c
3 * Copyright (C) 2012-2014 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 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "sysdb.h"
33 #include "core/plugin.h"
34 #include "core/store.h"
35 #include "utils/error.h"
36 #include "utils/unixsock.h"
38 #include "liboconfig/utils.h"
40 #include <assert.h>
42 #include <errno.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <strings.h>
49 SDB_PLUGIN_MAGIC;
51 /*
52 * private data types
53 */
55 typedef struct {
56 sdb_unixsock_client_t *client;
58 char *ts_type;
59 char *ts_base;
60 } user_data_t;
61 #define UD(obj) ((user_data_t *)(obj))
63 typedef struct {
64 char *current_host;
65 sdb_time_t current_timestamp;
66 int metrics_updated;
67 int metrics_failed;
69 user_data_t *ud;
70 } state_t;
71 #define STATE_INIT { NULL, 0, 0, 0, NULL }
73 /*
74 * private helper functions
75 */
77 static void
78 user_data_destroy(void *obj)
79 {
80 user_data_t *ud = UD(obj);
82 if (! obj)
83 return;
85 if (ud->client)
86 sdb_unixsock_client_destroy(ud->client);
87 ud->client = NULL;
89 if (ud->ts_type)
90 free(ud->ts_type);
91 if (ud->ts_base)
92 free(ud->ts_base);
93 ud->ts_type = ud->ts_base = NULL;
95 free(ud);
96 } /* user_data_destroy */
98 /* store the specified host-name (once per iteration) */
99 static int
100 store_host(state_t *state, const char *hostname, sdb_time_t last_update)
101 {
102 int status;
104 if (last_update > state->current_timestamp)
105 state->current_timestamp = last_update;
107 if (state->current_host && (! strcasecmp(state->current_host, hostname)))
108 return 0;
109 /* else: first/new host */
111 if (state->current_host) {
112 sdb_log(SDB_LOG_DEBUG, "Added/updated %i metric%s (%i failed) for host '%s'.",
113 state->metrics_updated, state->metrics_updated == 1 ? "" : "s",
114 state->metrics_failed, state->current_host);
115 state->metrics_updated = state->metrics_failed = 0;
116 free(state->current_host);
117 }
119 state->current_host = strdup(hostname);
120 if (! state->current_host) {
121 char errbuf[1024];
122 sdb_log(SDB_LOG_ERR, "Failed to allocate string buffer: %s",
123 sdb_strerror(errno, errbuf, sizeof(errbuf)));
124 return -1;
125 }
127 status = sdb_plugin_store_host(hostname, last_update);
129 if (status < 0) {
130 sdb_log(SDB_LOG_ERR, "Failed to store/update host '%s'.", hostname);
131 return -1;
132 }
133 else if (status > 0) /* value too old */
134 return 0;
136 sdb_log(SDB_LOG_DEBUG, "Added/updated host '%s' "
137 "(last update timestamp = %"PRIsdbTIME").",
138 hostname, last_update);
139 return 0;
140 } /* store_host */
142 static int
143 add_metrics(const char *hostname, char *plugin, char *type,
144 sdb_time_t last_update, user_data_t *ud)
145 {
146 char name[strlen(plugin) + strlen(type) + 2];
147 char *plugin_instance, *type_instance;
149 char metric_id[(ud->ts_base ? strlen(ud->ts_base) : 0)
150 + strlen(hostname) + sizeof(name) + 7];
151 sdb_metric_store_t store = { ud->ts_type, metric_id, NULL, last_update };
153 sdb_data_t data = { SDB_TYPE_STRING, { .string = NULL } };
155 int status;
157 snprintf(name, sizeof(name), "%s/%s", plugin, type);
159 if (ud->ts_base) {
160 snprintf(metric_id, sizeof(metric_id), "%s/%s/%s.rrd",
161 ud->ts_base, hostname, name);
162 status = sdb_plugin_store_metric(hostname, name, &store, last_update);
163 }
164 else
165 status = sdb_plugin_store_metric(hostname, name, NULL, last_update);
166 if (status < 0) {
167 sdb_log(SDB_LOG_ERR, "Failed to store/update metric '%s/%s'.", hostname, name);
168 return -1;
169 }
171 plugin_instance = strchr(plugin, '-');
172 if (plugin_instance) {
173 *plugin_instance = '\0';
174 ++plugin_instance;
176 data.data.string = plugin_instance;
177 sdb_plugin_store_metric_attribute(hostname, name,
178 "plugin_instance", &data, last_update);
179 }
181 type_instance = strchr(type, '-');
182 if (type_instance) {
183 *type_instance = '\0';
184 ++type_instance;
186 data.data.string = type_instance;
187 sdb_plugin_store_metric_attribute(hostname, name,
188 "type_instance", &data, last_update);
189 }
191 data.data.string = plugin;
192 sdb_plugin_store_metric_attribute(hostname, name, "plugin", &data, last_update);
193 data.data.string = type;
194 sdb_plugin_store_metric_attribute(hostname, name, "type", &data, last_update);
195 return 0;
196 } /* add_metrics */
198 static int
199 get_data(sdb_unixsock_client_t __attribute__((unused)) *client,
200 size_t n, sdb_data_t *data, sdb_object_t *user_data)
201 {
202 state_t *state;
203 sdb_data_t last_update;
205 char *hostname;
206 char *plugin;
207 char *type;
209 assert(user_data);
211 /* 0: <last_update> <hostname>
212 * 1: <plugin>
213 * 2: <type> */
214 assert(n == 3);
215 assert((data[0].type == SDB_TYPE_STRING)
216 && (data[1].type == SDB_TYPE_STRING)
217 && (data[2].type == SDB_TYPE_STRING));
219 hostname = data[0].data.string;
220 plugin = data[1].data.string;
221 type = data[2].data.string;
223 hostname = strchr(hostname, ' ');
224 if (! hostname) {
225 sdb_log(SDB_LOG_ERR, "Expected to find a space character "
226 "in the LISTVAL response");
227 return -1;
228 }
229 *hostname = '\0';
230 ++hostname;
232 if (sdb_data_parse(data[0].data.string, SDB_TYPE_DATETIME, &last_update)) {
233 char errbuf[1024];
234 sdb_log(SDB_LOG_ERR, "Failed to parse timestamp '%s' "
235 "returned by LISTVAL: %s", data[0].data.string,
236 sdb_strerror(errno, errbuf, sizeof(errbuf)));
237 return -1;
238 }
240 state = SDB_OBJ_WRAPPER(user_data)->data;
241 if (store_host(state, hostname, last_update.data.datetime))
242 return -1;
244 if (add_metrics(hostname, plugin, type,
245 last_update.data.datetime, state->ud))
246 ++state->metrics_failed;
247 else
248 ++state->metrics_updated;
249 return 0;
250 } /* get_data */
252 /*
253 * plugin API
254 */
256 static int
257 sdb_collectd_init(sdb_object_t *user_data)
258 {
259 user_data_t *ud;
261 if (! user_data)
262 return -1;
264 ud = SDB_OBJ_WRAPPER(user_data)->data;
265 if (sdb_unixsock_client_connect(ud->client)) {
266 sdb_log(SDB_LOG_ERR, "Failed to connect to collectd.");
267 return -1;
268 }
270 sdb_log(SDB_LOG_INFO, "Successfully connected to collectd @ %s.",
271 sdb_unixsock_client_path(ud->client));
272 return 0;
273 } /* sdb_collectd_init */
275 static int
276 sdb_collectd_collect(sdb_object_t *user_data)
277 {
278 user_data_t *ud;
280 char buffer[1024];
281 char *line;
282 char *msg;
284 char *endptr = NULL;
285 long int count;
287 state_t state = STATE_INIT;
288 sdb_object_wrapper_t state_obj = SDB_OBJECT_WRAPPER_STATIC(&state);
290 if (! user_data)
291 return -1;
293 ud = SDB_OBJ_WRAPPER(user_data)->data;
294 state.ud = ud;
296 if (sdb_unixsock_client_send(ud->client, "LISTVAL") <= 0) {
297 sdb_log(SDB_LOG_ERR, "Failed to send LISTVAL command to collectd @ %s.",
298 sdb_unixsock_client_path(ud->client));
299 return -1;
300 }
302 line = sdb_unixsock_client_recv(ud->client, buffer, sizeof(buffer));
303 if (! line) {
304 sdb_log(SDB_LOG_ERR, "Failed to read status of LISTVAL command from collectd @ %s.",
305 sdb_unixsock_client_path(ud->client));
306 return -1;
307 }
309 msg = strchr(line, ' ');
310 if (msg) {
311 *msg = '\0';
312 ++msg;
313 }
315 errno = 0;
316 count = strtol(line, &endptr, /* base */ 0);
317 if (errno || (line == endptr)) {
318 sdb_log(SDB_LOG_ERR, "Failed to parse status of LISTVAL command from collectd @ %s.",
319 sdb_unixsock_client_path(ud->client));
320 return -1;
321 }
323 if (count < 0) {
324 sdb_log(SDB_LOG_ERR, "Failed to get value list from collectd @ %s: %s",
325 sdb_unixsock_client_path(ud->client),
326 msg ? msg : line);
327 return -1;
328 }
330 if (sdb_unixsock_client_process_lines(ud->client, get_data,
331 SDB_OBJ(&state_obj), count, /* delim */ "/",
332 /* column count = */ 3,
333 SDB_TYPE_STRING, SDB_TYPE_STRING, SDB_TYPE_STRING)) {
334 sdb_log(SDB_LOG_ERR, "Failed to read response from collectd @ %s.",
335 sdb_unixsock_client_path(ud->client));
336 return -1;
337 }
339 if (state.current_host) {
340 sdb_log(SDB_LOG_DEBUG, "Added/updated %i metric%s (%i failed) for host '%s'.",
341 state.metrics_updated, state.metrics_updated == 1 ? "" : "s",
342 state.metrics_failed, state.current_host);
343 free(state.current_host);
344 }
345 return 0;
346 } /* collect */
348 static int
349 sdb_collectd_config_instance(oconfig_item_t *ci)
350 {
351 char *name = NULL;
352 char *socket_path = NULL;
354 sdb_object_t *user_data;
355 user_data_t *ud;
357 int i;
359 if (oconfig_get_string(ci, &name)) {
360 sdb_log(SDB_LOG_ERR, "Instance requires a single string argument\n"
361 "\tUsage: <Instance NAME>");
362 return -1;
363 }
365 ud = calloc(1, sizeof(*ud));
366 if (! ud) {
367 char errbuf[1024];
368 sdb_log(SDB_LOG_ERR, "Failed to allocate user-data object: %s",
369 sdb_strerror(errno, errbuf, sizeof(errbuf)));
370 return -1;
371 }
373 for (i = 0; i < ci->children_num; ++i) {
374 oconfig_item_t *child = ci->children + i;
376 if (! strcasecmp(child->key, "Socket"))
377 oconfig_get_string(child, &socket_path);
378 else if (! strcasecmp(child->key, "TimeseriesBackend"))
379 oconfig_get_string(child, &ud->ts_type);
380 else if (! strcasecmp(child->key, "TimeseriesBaseURL"))
381 oconfig_get_string(child, &ud->ts_base);
382 else
383 sdb_log(SDB_LOG_WARNING, "Ignoring unknown config option '%s' "
384 "inside <Instance %s>.", child->key, name);
385 }
387 if ((ud->ts_type == NULL) != (ud->ts_base == NULL)) {
388 sdb_log(SDB_LOG_ERR, "Both options, TimeseriesBackend and TimeseriesBaseURL, "
389 "have to be specified.");
390 ud->ts_type = ud->ts_base = NULL;
391 user_data_destroy(ud);
392 return -1;
393 }
395 if (ud->ts_type) {
396 /* TODO: add support for other backend types
397 * -> will require different ID generation */
398 if (strcasecmp(ud->ts_type, "rrdtool")
399 && strcasecmp(ud->ts_type, "rrdcached")) {
400 sdb_log(SDB_LOG_ERR, "TimeseriesBackend '%s' is not supported - "
401 "use 'rrdtool' instead.", ud->ts_type);
402 ud->ts_type = ud->ts_base = NULL;
403 user_data_destroy(ud);
404 return -1;
405 }
407 ud->ts_type = strdup(ud->ts_type);
408 ud->ts_base = strdup(ud->ts_base);
409 if ((! ud->ts_type) || (! ud->ts_base)) {
410 sdb_log(SDB_LOG_ERR, "Failed to duplicate a string");
411 user_data_destroy(ud);
412 return -1;
413 }
414 }
416 if (! socket_path) {
417 sdb_log(SDB_LOG_ERR, "Instance '%s' missing the 'Socket' option.", name);
418 user_data_destroy(ud);
419 return -1;
420 }
422 ud->client = sdb_unixsock_client_create(socket_path);
423 if (! ud->client) {
424 char errbuf[1024];
425 sdb_log(SDB_LOG_ERR, "Failed to create unixsock client: %s",
426 sdb_strerror(errno, errbuf, sizeof(errbuf)));
427 user_data_destroy(ud);
428 return -1;
429 }
431 user_data = sdb_object_create_wrapper("collectd-userdata", ud,
432 user_data_destroy);
433 if (! user_data) {
434 sdb_log(SDB_LOG_ERR, "Failed to allocate user-data wrapper object");
435 user_data_destroy(ud);
436 return -1;
437 }
439 sdb_plugin_register_init(name, sdb_collectd_init, user_data);
440 sdb_plugin_register_collector(name, sdb_collectd_collect,
441 /* interval */ NULL, user_data);
443 /* pass control to the list */
444 sdb_object_deref(user_data);
445 return 0;
446 } /* sdb_collectd_config_instance */
448 static int
449 sdb_collectd_config(oconfig_item_t *ci)
450 {
451 int i;
453 if (! ci) /* nothing to do to deconfigure this plugin */
454 return 0;
456 for (i = 0; i < ci->children_num; ++i) {
457 oconfig_item_t *child = ci->children + i;
459 if (! strcasecmp(child->key, "Instance"))
460 sdb_collectd_config_instance(child);
461 else
462 sdb_log(SDB_LOG_WARNING, "Ignoring unknown config option '%s'.", child->key);
463 }
464 return 0;
465 } /* sdb_collectd_config */
467 int
468 sdb_module_init(sdb_plugin_info_t *info)
469 {
470 sdb_plugin_set_info(info, SDB_PLUGIN_INFO_DESC,
471 "backend accessing the system statistics collection daemon "
472 "throught the UNIXSOCK interface");
473 sdb_plugin_set_info(info, SDB_PLUGIN_INFO_COPYRIGHT,
474 "Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>");
475 sdb_plugin_set_info(info, SDB_PLUGIN_INFO_LICENSE, "BSD");
476 sdb_plugin_set_info(info, SDB_PLUGIN_INFO_VERSION, SDB_VERSION);
477 sdb_plugin_set_info(info, SDB_PLUGIN_INFO_PLUGIN_VERSION, SDB_VERSION);
479 sdb_plugin_register_config(sdb_collectd_config);
480 return 0;
481 } /* sdb_version_extra */
483 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */