treewide: stop checking for AI_ADDRCONFIG
[collectd.git] / src / write_sensu.c
1 /**
2  * collectd - src/write_sensu.c
3  * Copyright (C) 2015 Fabrice A. Marie
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Fabrice A. Marie <fabrice at kibinlabs.com>
25  */
26
27 #define _GNU_SOURCE
28
29 #include "collectd.h"
30 #include "plugin.h"
31 #include "common.h"
32 #include "configfile.h"
33 #include "utils_cache.h"
34 #include <arpa/inet.h>
35 #include <errno.h>
36 #include <netdb.h>
37 #include <inttypes.h>
38 #include <stddef.h>
39
40 #include <stdlib.h>
41 #define SENSU_HOST              "localhost"
42 #define SENSU_PORT              "3030"
43
44 struct str_list {
45         int nb_strs;
46         char **strs;
47 };
48
49 struct sensu_host {
50         char                    *name;
51         char                    *event_service_prefix;
52         struct str_list metric_handlers;
53         struct str_list notification_handlers;
54 #define F_READY      0x01
55         uint8_t                  flags;
56         pthread_mutex_t  lock;
57         _Bool            notifications;
58         _Bool            metrics;
59         _Bool                    store_rates;
60         _Bool                    always_append_ds;
61         char                    *separator;
62         char                    *node;
63         char                    *service;
64         int              s;
65         struct addrinfo *res;
66         int                          reference_count;
67 };
68
69 static char     *sensu_tags = NULL;
70 static char     **sensu_attrs = NULL;
71 static size_t sensu_attrs_num;
72
73 static int add_str_to_list(struct str_list *strs,
74                 const char *str_to_add) /* {{{ */
75 {
76         char **old_strs_ptr = strs->strs;
77         char *newstr = strdup(str_to_add);
78         if (newstr == NULL) {
79                 ERROR("write_sensu plugin: Unable to alloc memory");
80                 return -1;
81         }
82         strs->strs = realloc(strs->strs, sizeof(char *) *(strs->nb_strs + 1));
83         if (strs->strs == NULL) {
84                 strs->strs = old_strs_ptr;
85                 free(newstr);
86                 ERROR("write_sensu plugin: Unable to alloc memory");
87                 return -1;
88         }
89         strs->strs[strs->nb_strs] = newstr;
90         strs->nb_strs++;
91         return 0;
92 }
93 /* }}} int add_str_to_list */
94
95 static void free_str_list(struct str_list *strs) /* {{{ */
96 {
97         int i;
98         for (i=0; i<strs->nb_strs; i++)
99                 free(strs->strs[i]);
100         free(strs->strs);
101 }
102 /* }}} void free_str_list */
103
104 static int sensu_connect(struct sensu_host *host) /* {{{ */
105 {
106         int                      e;
107         struct addrinfo         *ai, hints;
108         char const              *node;
109         char const              *service;
110
111         // Resolve the target if we haven't done already
112         if (!(host->flags & F_READY)) {
113                 memset(&hints, 0, sizeof(hints));
114                 memset(&service, 0, sizeof(service));
115                 host->res = NULL;
116                 hints.ai_family = AF_INET;
117                 hints.ai_socktype = SOCK_STREAM;
118                 hints.ai_flags = AI_ADDRCONFIG;
119
120                 node = (host->node != NULL) ? host->node : SENSU_HOST;
121                 service = (host->service != NULL) ? host->service : SENSU_PORT;
122
123                 if ((e = getaddrinfo(node, service, &hints, &(host->res))) != 0) {
124                         ERROR("write_sensu plugin: Unable to resolve host \"%s\": %s",
125                                         node, gai_strerror(e));
126                         return -1;
127                 }
128                 DEBUG("write_sensu plugin: successfully resolved host/port: %s/%s",
129                                 node, service);
130                 host->flags |= F_READY;
131         }
132
133         struct linger so_linger;
134         host->s = -1;
135         for (ai = host->res; ai != NULL; ai = ai->ai_next) {
136                 // create the socket
137                 if ((host->s = socket(ai->ai_family,
138                                       ai->ai_socktype,
139                                       ai->ai_protocol)) == -1) {
140                         continue;
141                 }
142
143                 // Set very low close() lingering
144                 so_linger.l_onoff = 1;
145                 so_linger.l_linger = 3;
146                 if (setsockopt(host->s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger) != 0)
147                         WARNING("write_sensu plugin: failed to set socket close() lingering");
148
149                 // connect the socket
150                 if (connect(host->s, ai->ai_addr, ai->ai_addrlen) != 0) {
151                         close(host->s);
152                         host->s = -1;
153                         continue;
154                 }
155                 DEBUG("write_sensu plugin: connected");
156                 break;
157         }
158
159         if (host->s < 0) {
160                 WARNING("write_sensu plugin: Unable to connect to sensu client");
161                 return -1;
162         }
163         return 0;
164 } /* }}} int sensu_connect */
165
166 static void sensu_close_socket(struct sensu_host *host) /* {{{ */
167 {
168         if (host->s != -1)
169                 close(host->s);
170         host->s = -1;
171
172 } /* }}} void sensu_close_socket */
173
174 static char *build_json_str_list(const char *tag, struct str_list const *list) /* {{{ */
175 {
176         int res;
177         char *ret_str = NULL;
178         char *temp_str;
179         int i;
180         if (list->nb_strs == 0) {
181                 ret_str = malloc(sizeof(char));
182                 if (ret_str == NULL) {
183                         ERROR("write_sensu plugin: Unable to alloc memory");
184                         return NULL;
185                 }
186                 ret_str[0] = '\0';
187         }
188
189         res = asprintf(&temp_str, "\"%s\": [\"%s\"", tag, list->strs[0]);
190         if (res == -1) {
191                 ERROR("write_sensu plugin: Unable to alloc memory");
192                 free(ret_str);
193                 return NULL;
194         }
195         for (i=1; i<list->nb_strs; i++) {
196                 res = asprintf(&ret_str, "%s, \"%s\"", temp_str, list->strs[i]);
197                 free(temp_str);
198                 if (res == -1) {
199                         ERROR("write_sensu plugin: Unable to alloc memory");
200                         return NULL;
201                 }
202                 temp_str = ret_str;
203         }
204         res = asprintf(&ret_str, "%s]", temp_str);
205         free(temp_str);
206         if (res == -1) {
207                 ERROR("write_sensu plugin: Unable to alloc memory");
208                 return NULL;
209         }
210
211         return ret_str;
212 } /* }}} char *build_json_str_list*/
213
214 static int sensu_format_name2(char *ret, int ret_len,
215                 const char *hostname,
216                 const char *plugin, const char *plugin_instance,
217                 const char *type, const char *type_instance,
218                 const char *separator)
219 {
220         char *buffer;
221         size_t buffer_size;
222
223         buffer = ret;
224         buffer_size = (size_t) ret_len;
225
226 #define APPEND(str) do {          \
227         size_t l = strlen (str);        \
228         if (l >= buffer_size)           \
229                 return (ENOBUFS);             \
230         memcpy (buffer, (str), l);      \
231         buffer += l; buffer_size -= l;  \
232 } while (0)
233
234         assert (plugin != NULL);
235         assert (type != NULL);
236
237         APPEND (hostname);
238         APPEND (separator);
239         APPEND (plugin);
240         if ((plugin_instance != NULL) && (plugin_instance[0] != 0))
241         {
242                 APPEND ("-");
243                 APPEND (plugin_instance);
244         }
245         APPEND (separator);
246         APPEND (type);
247         if ((type_instance != NULL) && (type_instance[0] != 0))
248         {
249                 APPEND ("-");
250                 APPEND (type_instance);
251         }
252         assert (buffer_size > 0);
253         buffer[0] = 0;
254
255 #undef APPEND
256         return (0);
257 } /* int sensu_format_name2 */
258
259 static void in_place_replace_sensu_name_reserved(char *orig_name) /* {{{ */
260 {
261         int i;
262         int len=strlen(orig_name);
263         for (i=0; i<len; i++) {
264                 // some plugins like ipmi generate special characters in metric name
265                 switch(orig_name[i]) {
266                         case '(': orig_name[i] = '_'; break;
267                         case ')': orig_name[i] = '_'; break;
268                         case ' ': orig_name[i] = '_'; break;
269                         case '"': orig_name[i] = '_'; break;
270                         case '\'': orig_name[i] = '_'; break;
271                         case '+': orig_name[i] = '_'; break;
272                 }
273         }
274 } /* }}} char *replace_sensu_name_reserved */
275
276 static char *sensu_value_to_json(struct sensu_host const *host, /* {{{ */
277                 data_set_t const *ds,
278                 value_list_t const *vl, size_t index,
279                 gauge_t const *rates,
280                 int status)
281 {
282         char name_buffer[5 * DATA_MAX_NAME_LEN];
283         char service_buffer[6 * DATA_MAX_NAME_LEN];
284         size_t i;
285         char *ret_str;
286         char *temp_str;
287         char *value_str;
288         int res;
289         // First part of the JSON string
290         const char *part1 = "{\"name\": \"collectd\", \"type\": \"metric\"";
291
292         char *handlers_str = build_json_str_list("handlers", &(host->metric_handlers));
293         if (handlers_str == NULL) {
294                 ERROR("write_sensu plugin: Unable to alloc memory");
295                 return NULL;
296         }
297
298         // incorporate the handlers
299         if (strlen(handlers_str) == 0) {
300                 free(handlers_str);
301                 ret_str = strdup(part1);
302                 if (ret_str == NULL) {
303                         ERROR("write_sensu plugin: Unable to alloc memory");
304                         return NULL;
305                 }
306         }
307         else {
308                 res = asprintf(&ret_str, "%s, %s", part1, handlers_str);
309                 free(handlers_str);
310                 if (res == -1) {
311                         ERROR("write_sensu plugin: Unable to alloc memory");
312                         return NULL;
313                 }
314         }
315
316         // incorporate the plugin name information
317         res = asprintf(&temp_str, "%s, \"collectd_plugin\": \"%s\"", ret_str, vl->plugin);
318         free(ret_str);
319         if (res == -1) {
320                 ERROR("write_sensu plugin: Unable to alloc memory");
321                 return NULL;
322         }
323         ret_str = temp_str;
324
325         // incorporate the plugin type
326         res = asprintf(&temp_str, "%s, \"collectd_plugin_type\": \"%s\"", ret_str, vl->type);
327         free(ret_str);
328         if (res == -1) {
329                 ERROR("write_sensu plugin: Unable to alloc memory");
330                 return NULL;
331         }
332         ret_str = temp_str;
333
334         // incorporate the plugin instance if any
335         if (vl->plugin_instance[0] != 0) {
336                 res = asprintf(&temp_str, "%s, \"collectd_plugin_instance\": \"%s\"", ret_str, vl->plugin_instance);
337                 free(ret_str);
338                 if (res == -1) {
339                         ERROR("write_sensu plugin: Unable to alloc memory");
340                         return NULL;
341                 }
342                 ret_str = temp_str;
343         }
344
345         // incorporate the plugin type instance if any
346         if (vl->type_instance[0] != 0) {
347                 res = asprintf(&temp_str, "%s, \"collectd_plugin_type_instance\": \"%s\"", ret_str, vl->type_instance);
348                 free(ret_str);
349                 if (res == -1) {
350                         ERROR("write_sensu plugin: Unable to alloc memory");
351                         return NULL;
352                 }
353                 ret_str = temp_str;
354         }
355
356         // incorporate the data source type
357         if ((ds->ds[index].type != DS_TYPE_GAUGE) && (rates != NULL)) {
358                 char ds_type[DATA_MAX_NAME_LEN];
359                 ssnprintf (ds_type, sizeof (ds_type), "%s:rate", DS_TYPE_TO_STRING(ds->ds[index].type));
360                 res = asprintf(&temp_str, "%s, \"collectd_data_source_type\": \"%s\"", ret_str, ds_type);
361                 free(ret_str);
362                 if (res == -1) {
363                         ERROR("write_sensu plugin: Unable to alloc memory");
364                         return NULL;
365                 }
366                 ret_str = temp_str;
367         } else {
368                 res = asprintf(&temp_str, "%s, \"collectd_data_source_type\": \"%s\"", ret_str, DS_TYPE_TO_STRING(ds->ds[index].type));
369                 free(ret_str);
370                 if (res == -1) {
371                         ERROR("write_sensu plugin: Unable to alloc memory");
372                         return NULL;
373                 }
374                 ret_str = temp_str;
375         }
376
377         // incorporate the data source name
378         res = asprintf(&temp_str, "%s, \"collectd_data_source_name\": \"%s\"", ret_str, ds->ds[index].name);
379         free(ret_str);
380         if (res == -1) {
381                 ERROR("write_sensu plugin: Unable to alloc memory");
382                 return NULL;
383         }
384         ret_str = temp_str;
385
386         // incorporate the data source index
387         {
388                 char ds_index[DATA_MAX_NAME_LEN];
389                 ssnprintf (ds_index, sizeof (ds_index), "%zu", index);
390                 res = asprintf(&temp_str, "%s, \"collectd_data_source_index\": %s", ret_str, ds_index);
391                 free(ret_str);
392                 if (res == -1) {
393                         ERROR("write_sensu plugin: Unable to alloc memory");
394                         return NULL;
395                 }
396                 ret_str = temp_str;
397         }
398
399         // add key value attributes from config if any
400         for (i = 0; i < sensu_attrs_num; i += 2) {
401                 res = asprintf(&temp_str, "%s, \"%s\": \"%s\"", ret_str, sensu_attrs[i], sensu_attrs[i+1]);
402                 free(ret_str);
403                 if (res == -1) {
404                         ERROR("write_sensu plugin: Unable to alloc memory");
405                         return NULL;
406                 }
407                 ret_str = temp_str;
408         }
409
410         // incorporate sensu tags from config if any
411         if ((sensu_tags != NULL) && (strlen(sensu_tags) != 0)) {
412                 res = asprintf(&temp_str, "%s, %s", ret_str, sensu_tags);
413                 free(ret_str);
414                 if (res == -1) {
415                         ERROR("write_sensu plugin: Unable to alloc memory");
416                         return NULL;
417                 }
418                 ret_str = temp_str;
419         }
420
421         // calculate the value and set to a string
422         if (ds->ds[index].type == DS_TYPE_GAUGE) {
423                 res = asprintf(&value_str, GAUGE_FORMAT, vl->values[index].gauge);
424                 if (res == -1) {
425                         free(ret_str);
426                         ERROR("write_sensu plugin: Unable to alloc memory");
427                         return NULL;
428                 }
429         } else if (rates != NULL) {
430                 res = asprintf(&value_str, GAUGE_FORMAT, rates[index]);
431                 if (res == -1) {
432                         free(ret_str);
433                         ERROR("write_sensu plugin: Unable to alloc memory");
434                         return NULL;
435                 }
436         } else {
437                 if (ds->ds[index].type == DS_TYPE_DERIVE) {
438                         res = asprintf(&value_str, "%"PRIi64, vl->values[index].derive);
439                         if (res == -1) {
440                                 free(ret_str);
441                                 ERROR("write_sensu plugin: Unable to alloc memory");
442                                 return NULL;
443                         }
444                 }
445                 else if (ds->ds[index].type == DS_TYPE_ABSOLUTE) {
446                         res = asprintf(&value_str, "%"PRIu64, vl->values[index].absolute);
447                         if (res == -1) {
448                                 free(ret_str);
449                                 ERROR("write_sensu plugin: Unable to alloc memory");
450                                 return NULL;
451                         }
452                 }
453                 else {
454                         res = asprintf(&value_str, "%llu", vl->values[index].counter);
455                         if (res == -1) {
456                                 free(ret_str);
457                                 ERROR("write_sensu plugin: Unable to alloc memory");
458                                 return NULL;
459                         }
460                 }
461         }
462
463         // Generate the full service name
464         sensu_format_name2(name_buffer, sizeof(name_buffer),
465                 vl->host, vl->plugin, vl->plugin_instance,
466                 vl->type, vl->type_instance, host->separator);
467         if (host->always_append_ds || (ds->ds_num > 1)) {
468                 if (host->event_service_prefix == NULL)
469                         ssnprintf(service_buffer, sizeof(service_buffer), "%s.%s",
470                                         name_buffer, ds->ds[index].name);
471                 else
472                         ssnprintf(service_buffer, sizeof(service_buffer), "%s%s.%s",
473                                         host->event_service_prefix, name_buffer, ds->ds[index].name);
474         } else {
475                 if (host->event_service_prefix == NULL)
476                         sstrncpy(service_buffer, name_buffer, sizeof(service_buffer));
477                 else
478                         ssnprintf(service_buffer, sizeof(service_buffer), "%s%s",
479                                         host->event_service_prefix, name_buffer);
480         }
481
482         // Replace collectd sensor name reserved characters so that time series DB is happy
483         in_place_replace_sensu_name_reserved(service_buffer);
484
485         // finalize the buffer by setting the output and closing curly bracket
486         res = asprintf(&temp_str, "%s, \"output\": \"%s %s %ld\"}\n", ret_str, service_buffer, value_str, CDTIME_T_TO_TIME_T(vl->time));
487         free(ret_str);
488         free(value_str);
489         if (res == -1) {
490                 ERROR("write_sensu plugin: Unable to alloc memory");
491                 return NULL;
492         }
493         ret_str = temp_str;
494
495         DEBUG("write_sensu plugin: Successfully created json for metric: "
496                         "host = \"%s\", service = \"%s\"",
497                         vl->host, service_buffer);
498         return ret_str;
499 } /* }}} char *sensu_value_to_json */
500
501 /*
502  * Uses replace_str2() implementation from
503  * http://creativeandcritical.net/str-replace-c/
504  * copyright (c) Laird Shaw, under public domain.
505  */
506 static char *replace_str(const char *str, const char *old, /* {{{ */
507                 const char *new)
508 {
509         char *ret, *r;
510         const char *p, *q;
511         size_t oldlen = strlen(old);
512         size_t count = strlen(new);
513         size_t retlen;
514         size_t newlen = count;
515         int samesize = (oldlen == newlen);
516
517         if (!samesize) {
518                 for (count = 0, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen)
519                         count++;
520                 /* This is undefined if p - str > PTRDIFF_MAX */
521                 retlen = p - str + strlen(p) + count * (newlen - oldlen);
522         } else
523                 retlen = strlen(str);
524
525         ret = calloc(1, retlen + 1);
526         if (ret == NULL)
527                 return NULL;
528         // added to original: not optimized, but keeps valgrind happy.
529
530         r = ret;
531         p = str;
532         while (1) {
533                 /* If the old and new strings are different lengths - in other
534                  * words we have already iterated through with strstr above,
535                  * and thus we know how many times we need to call it - then we
536                  * can avoid the final (potentially lengthy) call to strstr,
537                  * which we already know is going to return NULL, by
538                  * decrementing and checking count.
539                  */
540                 if (!samesize && !count--)
541                         break;
542                 /* Otherwise i.e. when the old and new strings are the same
543                  * length, and we don't know how many times to call strstr,
544                  * we must check for a NULL return here (we check it in any
545                  * event, to avoid further conditions, and because there's
546                  * no harm done with the check even when the old and new
547                  * strings are different lengths).
548                  */
549                 if ((q = strstr(p, old)) == NULL)
550                         break;
551                 /* This is undefined if q - p > PTRDIFF_MAX */
552                 ptrdiff_t l = q - p;
553                 memcpy(r, p, l);
554                 r += l;
555                 memcpy(r, new, newlen);
556                 r += newlen;
557                 p = q + oldlen;
558         }
559         strncpy(r, p, strlen(p));
560
561         return ret;
562 } /* }}} char *replace_str */
563
564 static char *replace_json_reserved(const char *message) /* {{{ */
565 {
566         char *msg = replace_str(message, "\\", "\\\\");
567         if (msg == NULL) {
568                 ERROR("write_sensu plugin: Unable to alloc memory");
569                 return NULL;
570         }
571         char *tmp = replace_str(msg, "\"", "\\\"");
572         free(msg);
573         if (tmp == NULL) {
574                 ERROR("write_sensu plugin: Unable to alloc memory");
575                 return NULL;
576         }
577         msg = replace_str(tmp, "\n", "\\\n");
578         free(tmp);
579         if (msg == NULL) {
580                 ERROR("write_sensu plugin: Unable to alloc memory");
581                 return NULL;
582         }
583         return msg;
584 } /* }}} char *replace_json_reserved */
585
586 static char *sensu_notification_to_json(struct sensu_host *host, /* {{{ */
587                 notification_t const *n)
588 {
589         char service_buffer[6 * DATA_MAX_NAME_LEN];
590         char const *severity;
591         notification_meta_t *meta;
592         char *ret_str;
593         char *temp_str;
594         int status;
595         size_t i;
596         int res;
597         // add the severity/status
598         switch (n->severity) {
599                 case NOTIF_OKAY:
600                         severity = "OK";
601                         status = 0;
602                         break;
603                 case NOTIF_WARNING:
604                         severity = "WARNING";
605                         status = 1;
606                         break;
607                 case NOTIF_FAILURE:
608                         severity = "CRITICAL";
609                         status = 2;
610                         break;
611                 default:
612                         severity = "UNKNOWN";
613                         status = 3;
614         }
615         res = asprintf(&temp_str, "{\"status\": %d", status);
616         if (res == -1) {
617                 ERROR("write_sensu plugin: Unable to alloc memory");
618                 return NULL;
619         }
620         ret_str = temp_str;
621
622         // incorporate the timestamp
623         res = asprintf(&temp_str, "%s, \"timestamp\": %ld", ret_str, CDTIME_T_TO_TIME_T(n->time));
624         free(ret_str);
625         if (res == -1) {
626                 ERROR("write_sensu plugin: Unable to alloc memory");
627                 return NULL;
628         }
629         ret_str = temp_str;
630
631         char *handlers_str = build_json_str_list("handlers", &(host->notification_handlers));
632         if (handlers_str == NULL) {
633                 ERROR("write_sensu plugin: Unable to alloc memory");
634                 free(ret_str);
635                 return NULL;
636         }
637         // incorporate the handlers
638         if (strlen(handlers_str) != 0) {
639                 res = asprintf(&temp_str, "%s, %s", ret_str, handlers_str);
640                 free(ret_str);
641                 free(handlers_str);
642                 if (res == -1) {
643                         ERROR("write_sensu plugin: Unable to alloc memory");
644                         return NULL;
645                 }
646                 ret_str = temp_str;
647         } else {
648                 free(handlers_str);
649         }
650
651         // incorporate the plugin name information if any
652         if (n->plugin[0] != 0) {
653                 res = asprintf(&temp_str, "%s, \"collectd_plugin\": \"%s\"", ret_str, n->plugin);
654                 free(ret_str);
655                 if (res == -1) {
656                         ERROR("write_sensu plugin: Unable to alloc memory");
657                         return NULL;
658                 }
659                 ret_str = temp_str;
660         }
661
662         // incorporate the plugin type if any
663         if (n->type[0] != 0) {
664                 res = asprintf(&temp_str, "%s, \"collectd_plugin_type\": \"%s\"", ret_str, n->type);
665                 free(ret_str);
666                 if (res == -1) {
667                         ERROR("write_sensu plugin: Unable to alloc memory");
668                         return NULL;
669                 }
670                 ret_str = temp_str;
671         }
672
673         // incorporate the plugin instance if any
674         if (n->plugin_instance[0] != 0) {
675                 res = asprintf(&temp_str, "%s, \"collectd_plugin_instance\": \"%s\"", ret_str, n->plugin_instance);
676                 free(ret_str);
677                 if (res == -1) {
678                         ERROR("write_sensu plugin: Unable to alloc memory");
679                         return NULL;
680                 }
681                 ret_str = temp_str;
682         }
683
684         // incorporate the plugin type instance if any
685         if (n->type_instance[0] != 0) {
686                 res = asprintf(&temp_str, "%s, \"collectd_plugin_type_instance\": \"%s\"", ret_str, n->type_instance);
687                 free(ret_str);
688                 if (res == -1) {
689                         ERROR("write_sensu plugin: Unable to alloc memory");
690                         return NULL;
691                 }
692                 ret_str = temp_str;
693         }
694
695         // add key value attributes from config if any
696         for (i = 0; i < sensu_attrs_num; i += 2) {
697                 res = asprintf(&temp_str, "%s, \"%s\": \"%s\"", ret_str, sensu_attrs[i], sensu_attrs[i+1]);
698                 free(ret_str);
699                 if (res == -1) {
700                         ERROR("write_sensu plugin: Unable to alloc memory");
701                         return NULL;
702                 }
703                 ret_str = temp_str;
704         }
705
706         // incorporate sensu tags from config if any
707         if ((sensu_tags != NULL) && (strlen(sensu_tags) != 0)) {
708                 res = asprintf(&temp_str, "%s, %s", ret_str, sensu_tags);
709                 free(ret_str);
710                 if (res == -1) {
711                         ERROR("write_sensu plugin: Unable to alloc memory");
712                         return NULL;
713                 }
714                 ret_str = temp_str;
715         }
716
717         // incorporate the service name
718         sensu_format_name2(service_buffer, sizeof(service_buffer),
719                                 /* host */ "", n->plugin, n->plugin_instance,
720                                 n->type, n->type_instance, host->separator);
721         // replace sensu event name chars that are considered illegal
722         in_place_replace_sensu_name_reserved(service_buffer);
723         res = asprintf(&temp_str, "%s, \"name\": \"%s\"", ret_str, &service_buffer[1]);
724         free(ret_str);
725         if (res == -1) {
726                 ERROR("write_sensu plugin: Unable to alloc memory");
727                 return NULL;
728         }
729         ret_str = temp_str;
730
731         // incorporate the check output
732         if (n->message[0] != 0) {
733                 char *msg = replace_json_reserved(n->message);
734                 if (msg == NULL) {
735                         ERROR("write_sensu plugin: Unable to alloc memory");
736                         free(ret_str);
737                         return NULL;
738                 }
739                 res = asprintf(&temp_str, "%s, \"output\": \"%s - %s\"", ret_str, severity, msg);
740                 free(ret_str);
741                 free(msg);
742                 if (res == -1) {
743                         ERROR("write_sensu plugin: Unable to alloc memory");
744                         return NULL;
745                 }
746                 ret_str = temp_str;
747         }
748
749         // Pull in values from threshold and add extra attributes
750         for (meta = n->meta; meta != NULL; meta = meta->next) {
751                 if (strcasecmp("CurrentValue", meta->name) == 0 && meta->type == NM_TYPE_DOUBLE) {
752                         res = asprintf(&temp_str, "%s, \"current_value\": \"%.8f\"", ret_str, meta->nm_value.nm_double);
753                         free(ret_str);
754                         if (res == -1) {
755                                 ERROR("write_sensu plugin: Unable to alloc memory");
756                                 return NULL;
757                         }
758                         ret_str = temp_str;
759                 }
760                 if (meta->type == NM_TYPE_STRING) {
761                         res = asprintf(&temp_str, "%s, \"%s\": \"%s\"", ret_str, meta->name, meta->nm_value.nm_string);
762                         free(ret_str);
763                         if (res == -1) {
764                                 ERROR("write_sensu plugin: Unable to alloc memory");
765                                 return NULL;
766                         }
767                         ret_str = temp_str;
768                 }
769         }
770
771         // close the curly bracket
772         res = asprintf(&temp_str, "%s}\n", ret_str);
773         free(ret_str);
774         if (res == -1) {
775                 ERROR("write_sensu plugin: Unable to alloc memory");
776                 return NULL;
777         }
778         ret_str = temp_str;
779
780         DEBUG("write_sensu plugin: Successfully created JSON for notification: "
781                                 "host = \"%s\", service = \"%s\", state = \"%s\"",
782                                 n->host, service_buffer, severity);
783         return ret_str;
784 } /* }}} char *sensu_notification_to_json */
785
786 static int sensu_send_msg(struct sensu_host *host, const char *msg) /* {{{ */
787 {
788         int status = 0;
789         size_t  buffer_len;
790
791         status = sensu_connect(host);
792         if (status != 0)
793                 return status;
794
795         buffer_len = strlen(msg);
796
797         status = (int) swrite(host->s, msg, buffer_len);
798         sensu_close_socket(host);
799
800         if (status != 0) {
801                 char errbuf[1024];
802                 ERROR("write_sensu plugin: Sending to Sensu at %s:%s failed: %s",
803                                 (host->node != NULL) ? host->node : SENSU_HOST,
804                                 (host->service != NULL) ? host->service : SENSU_PORT,
805                                 sstrerror(errno, errbuf, sizeof(errbuf)));
806                 return -1;
807         }
808
809         return 0;
810 } /* }}} int sensu_send_msg */
811
812
813 static int sensu_send(struct sensu_host *host, char const *msg) /* {{{ */
814 {
815         int status = 0;
816
817         status = sensu_send_msg(host, msg);
818         if (status != 0) {
819                 host->flags &= ~F_READY;
820                 if (host->res != NULL) {
821                         freeaddrinfo(host->res);
822                         host->res = NULL;
823                 }
824                 return status;
825         }
826
827         return 0;
828 } /* }}} int sensu_send */
829
830
831 static int sensu_write(const data_set_t *ds, /* {{{ */
832               const value_list_t *vl,
833               user_data_t *ud)
834 {
835         int status = 0;
836         int statuses[vl->values_len];
837         struct sensu_host       *host = ud->data;
838         gauge_t *rates = NULL;
839         size_t i;
840         char *msg;
841
842         pthread_mutex_lock(&host->lock);
843         memset(statuses, 0, vl->values_len * sizeof(*statuses));
844
845         if (host->store_rates) {
846                 rates = uc_get_rate(ds, vl);
847                 if (rates == NULL) {
848                         ERROR("write_sensu plugin: uc_get_rate failed.");
849                         pthread_mutex_unlock(&host->lock);
850                         return -1;
851                 }
852         }
853         for (i = 0; i < vl->values_len; i++) {
854                 msg = sensu_value_to_json(host, ds, vl, (int) i, rates, statuses[i]);
855                 if (msg == NULL) {
856                         sfree(rates);
857                         pthread_mutex_unlock(&host->lock);
858                         return -1;
859                 }
860                 status = sensu_send(host, msg);
861                 free(msg);
862                 if (status != 0) {
863                         ERROR("write_sensu plugin: sensu_send failed with status %i", status);
864                         pthread_mutex_unlock(&host->lock);
865                         sfree(rates);
866                         return status;
867                 }
868         }
869         sfree(rates);
870         pthread_mutex_unlock(&host->lock);
871         return status;
872 } /* }}} int sensu_write */
873
874 static int sensu_notification(const notification_t *n, user_data_t *ud) /* {{{ */
875 {
876         int     status;
877         struct sensu_host *host = ud->data;
878         char *msg;
879
880         pthread_mutex_lock(&host->lock);
881
882         msg = sensu_notification_to_json(host, n);
883         if (msg == NULL) {
884                 pthread_mutex_unlock(&host->lock);
885                 return -1;
886         }
887
888         status = sensu_send(host, msg);
889         free(msg);
890         if (status != 0)
891                 ERROR("write_sensu plugin: sensu_send failed with status %i", status);
892         pthread_mutex_unlock(&host->lock);
893
894         return status;
895 } /* }}} int sensu_notification */
896
897 static void sensu_free(void *p) /* {{{ */
898 {
899         struct sensu_host *host = p;
900
901         if (host == NULL)
902                 return;
903
904         pthread_mutex_lock(&host->lock);
905
906         host->reference_count--;
907         if (host->reference_count > 0) {
908                 pthread_mutex_unlock(&host->lock);
909                 return;
910         }
911
912         sensu_close_socket(host);
913         if (host->res != NULL) {
914                 freeaddrinfo(host->res);
915                 host->res = NULL;
916         }
917         sfree(host->service);
918         sfree(host->event_service_prefix);
919         sfree(host->name);
920         sfree(host->node);
921         sfree(host->separator);
922         free_str_list(&(host->metric_handlers));
923         free_str_list(&(host->notification_handlers));
924         pthread_mutex_destroy(&host->lock);
925         sfree(host);
926 } /* }}} void sensu_free */
927
928
929 static int sensu_config_node(oconfig_item_t *ci) /* {{{ */
930 {
931         struct sensu_host       *host = NULL;
932         int                                     status = 0;
933         int                                     i;
934         oconfig_item_t          *child;
935         char                            callback_name[DATA_MAX_NAME_LEN];
936         user_data_t                     ud;
937
938         if ((host = calloc(1, sizeof(*host))) == NULL) {
939                 ERROR("write_sensu plugin: calloc failed.");
940                 return ENOMEM;
941         }
942         pthread_mutex_init(&host->lock, NULL);
943         host->reference_count = 1;
944         host->node = NULL;
945         host->service = NULL;
946         host->notifications = 0;
947         host->metrics = 0;
948         host->store_rates = 1;
949         host->always_append_ds = 0;
950         host->metric_handlers.nb_strs = 0;
951         host->metric_handlers.strs = NULL;
952         host->notification_handlers.nb_strs = 0;
953         host->notification_handlers.strs = NULL;
954         host->separator = strdup("/");
955         if (host->separator == NULL) {
956                 ERROR("write_sensu plugin: Unable to alloc memory");
957                 sensu_free(host);
958                 return -1;
959         }
960
961         status = cf_util_get_string(ci, &host->name);
962         if (status != 0) {
963                 WARNING("write_sensu plugin: Required host name is missing.");
964                 sensu_free(host);
965                 return -1;
966         }
967
968         for (i = 0; i < ci->children_num; i++) {
969                 child = &ci->children[i];
970                 status = 0;
971
972                 if (strcasecmp("Host", child->key) == 0) {
973                         status = cf_util_get_string(child, &host->node);
974                         if (status != 0)
975                                 break;
976                 } else if (strcasecmp("Notifications", child->key) == 0) {
977                         status = cf_util_get_boolean(child, &host->notifications);
978                         if (status != 0)
979                                 break;
980                 } else if (strcasecmp("Metrics", child->key) == 0) {
981                                         status = cf_util_get_boolean(child, &host->metrics);
982                                         if (status != 0)
983                                                 break;
984                 } else if (strcasecmp("EventServicePrefix", child->key) == 0) {
985                         status = cf_util_get_string(child, &host->event_service_prefix);
986                         if (status != 0)
987                                 break;
988                 } else if (strcasecmp("Separator", child->key) == 0) {
989                                 status = cf_util_get_string(child, &host->separator);
990                                 if (status != 0)
991                                         break;
992                 } else if (strcasecmp("MetricHandler", child->key) == 0) {
993                         char *temp_str = NULL;
994                         status = cf_util_get_string(child, &temp_str);
995                         if (status != 0)
996                                 break;
997                         status = add_str_to_list(&(host->metric_handlers), temp_str);
998                         free(temp_str);
999                         if (status != 0)
1000                                 break;
1001                 } else if (strcasecmp("NotificationHandler", child->key) == 0) {
1002                         char *temp_str = NULL;
1003                         status = cf_util_get_string(child, &temp_str);
1004                         if (status != 0)
1005                                 break;
1006                         status = add_str_to_list(&(host->notification_handlers), temp_str);
1007                         free(temp_str);
1008                         if (status != 0)
1009                                 break;
1010                 } else if (strcasecmp("Port", child->key) == 0) {
1011                         status = cf_util_get_service(child, &host->service);
1012                         if (status != 0) {
1013                                 ERROR("write_sensu plugin: Invalid argument "
1014                                                 "configured for the \"Port\" "
1015                                                 "option.");
1016                                 break;
1017                         }
1018                 } else if (strcasecmp("StoreRates", child->key) == 0) {
1019                         status = cf_util_get_boolean(child, &host->store_rates);
1020                         if (status != 0)
1021                                 break;
1022                 } else if (strcasecmp("AlwaysAppendDS", child->key) == 0) {
1023                         status = cf_util_get_boolean(child,
1024                                         &host->always_append_ds);
1025                         if (status != 0)
1026                                 break;
1027                 } else {
1028                         WARNING("write_sensu plugin: ignoring unknown config "
1029                                 "option: \"%s\"", child->key);
1030                 }
1031         }
1032         if (status != 0) {
1033                 sensu_free(host);
1034                 return status;
1035         }
1036
1037         if (host->metrics && (host->metric_handlers.nb_strs == 0)) {
1038                         sensu_free(host);
1039                         WARNING("write_sensu plugin: metrics enabled but no MetricHandler defined. Giving up.");
1040                         return -1;
1041                 }
1042
1043         if (host->notifications && (host->notification_handlers.nb_strs == 0)) {
1044                 sensu_free(host);
1045                 WARNING("write_sensu plugin: notifications enabled but no NotificationHandler defined. Giving up.");
1046                 return -1;
1047         }
1048
1049         if ((host->notification_handlers.nb_strs > 0) && (host->notifications == 0)) {
1050                 WARNING("write_sensu plugin: NotificationHandler given so forcing notifications to be enabled");
1051                 host->notifications = 1;
1052         }
1053
1054         if ((host->metric_handlers.nb_strs > 0) && (host->metrics == 0)) {
1055                 WARNING("write_sensu plugin: MetricHandler given so forcing metrics to be enabled");
1056                 host->metrics = 1;
1057         }
1058
1059         if (!(host->notifications || host->metrics)) {
1060                 WARNING("write_sensu plugin: neither metrics nor notifications enabled. Giving up.");
1061                 sensu_free(host);
1062                 return -1;
1063         }
1064
1065         ssnprintf(callback_name, sizeof(callback_name), "write_sensu/%s", host->name);
1066         ud.data = host;
1067         ud.free_func = sensu_free;
1068
1069         pthread_mutex_lock(&host->lock);
1070
1071         if (host->metrics) {
1072                 status = plugin_register_write(callback_name, sensu_write, &ud);
1073                 if (status != 0)
1074                         WARNING("write_sensu plugin: plugin_register_write (\"%s\") "
1075                                         "failed with status %i.",
1076                                         callback_name, status);
1077                 else /* success */
1078                         host->reference_count++;
1079         }
1080
1081         if (host->notifications) {
1082                 status = plugin_register_notification(callback_name, sensu_notification, &ud);
1083                 if (status != 0)
1084                         WARNING("write_sensu plugin: plugin_register_notification (\"%s\") "
1085                                         "failed with status %i.",
1086                                         callback_name, status);
1087                 else
1088                         host->reference_count++;
1089         }
1090
1091         if (host->reference_count <= 1) {
1092                 /* Both callbacks failed => free memory.
1093                  * We need to unlock here, because sensu_free() will lock.
1094                  * This is not a race condition, because we're the only one
1095                  * holding a reference. */
1096                 pthread_mutex_unlock(&host->lock);
1097                 sensu_free(host);
1098                 return -1;
1099         }
1100
1101         host->reference_count--;
1102         pthread_mutex_unlock(&host->lock);
1103
1104         return status;
1105 } /* }}} int sensu_config_node */
1106
1107 static int sensu_config(oconfig_item_t *ci) /* {{{ */
1108 {
1109         int              i;
1110         oconfig_item_t  *child;
1111         int              status;
1112         struct str_list sensu_tags_arr;
1113
1114         sensu_tags_arr.nb_strs = 0;
1115         sensu_tags_arr.strs = NULL;
1116
1117         for (i = 0; i < ci->children_num; i++)  {
1118                 child = &ci->children[i];
1119
1120                 if (strcasecmp("Node", child->key) == 0) {
1121                         sensu_config_node(child);
1122                 } else if (strcasecmp(child->key, "attribute") == 0) {
1123                         if (child->values_num != 2) {
1124                                 WARNING("sensu attributes need both a key and a value.");
1125                                 continue;
1126                         }
1127                         if (child->values[0].type != OCONFIG_TYPE_STRING ||
1128                                         child->values[1].type != OCONFIG_TYPE_STRING) {
1129                                 WARNING("sensu attribute needs string arguments.");
1130                                 continue;
1131                         }
1132
1133                         strarray_add(&sensu_attrs, &sensu_attrs_num, child->values[0].value.string);
1134                         strarray_add(&sensu_attrs, &sensu_attrs_num, child->values[1].value.string);
1135
1136                         DEBUG("write_sensu plugin: New attribute: %s => %s",
1137                                         child->values[0].value.string,
1138                                         child->values[1].value.string);
1139                 } else if (strcasecmp(child->key, "tag") == 0) {
1140                         char *tmp = NULL;
1141                         status = cf_util_get_string(child, &tmp);
1142                         if (status != 0)
1143                                 continue;
1144
1145                         status = add_str_to_list(&sensu_tags_arr, tmp);
1146                         sfree(tmp);
1147                         if (status != 0)
1148                                 continue;
1149                         DEBUG("write_sensu plugin: Got tag: %s", tmp);
1150                 } else {
1151                         WARNING("write_sensu plugin: Ignoring unknown "
1152                                  "configuration option \"%s\" at top level.",
1153                                  child->key);
1154                 }
1155         }
1156         if (sensu_tags_arr.nb_strs > 0) {
1157                 sfree (sensu_tags);
1158                 sensu_tags = build_json_str_list("tags", &sensu_tags_arr);
1159                 free_str_list(&sensu_tags_arr);
1160                 if (sensu_tags == NULL) {
1161                         ERROR("write_sensu plugin: Unable to alloc memory");
1162                         return -1;
1163                 }
1164         }
1165         return 0;
1166 } /* }}} int sensu_config */
1167
1168 void module_register(void)
1169 {
1170         plugin_register_complex_config("write_sensu", sensu_config);
1171 }
1172
1173 /* vim: set sw=8 sts=8 ts=8 noet : */