Merge branch 'collectd-5.5'
[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 #include "collectd.h"
28 #include "plugin.h"
29 #include "common.h"
30 #include "configfile.h"
31 #include "utils_cache.h"
32 #include <arpa/inet.h>
33 #include <errno.h>
34 #include <netdb.h>
35 #include <inttypes.h>
36 #include <pthread.h>
37 #include <stddef.h>
38
39 #include <stdlib.h>
40 #define SENSU_HOST              "localhost"
41 #define SENSU_PORT              "3030"
42
43 struct str_list {
44         int nb_strs;
45         char **strs;
46 };
47
48 struct sensu_host {
49         char                    *name;
50         char                    *event_service_prefix;
51         struct str_list metric_handlers;
52         struct str_list notification_handlers;
53 #define F_READY      0x01
54         uint8_t                  flags;
55         pthread_mutex_t  lock;
56         _Bool            notifications;
57         _Bool            metrics;
58         _Bool                    store_rates;
59         _Bool                    always_append_ds;
60         char                    *separator;
61         char                    *node;
62         char                    *service;
63         int              s;
64         struct addrinfo *res;
65         int                          reference_count;
66 };
67
68 static char     *sensu_tags = NULL;
69 static char     **sensu_attrs = NULL;
70 static size_t sensu_attrs_num;
71
72 static int add_str_to_list(struct str_list *strs,
73                 const char *str_to_add) /* {{{ */
74 {
75         char **old_strs_ptr = strs->strs;
76         char *newstr = strdup(str_to_add);
77         if (newstr == NULL) {
78                 ERROR("write_sensu plugin: Unable to alloc memory");
79                 return -1;
80         }
81         strs->strs = realloc(strs->strs, sizeof(char *) *(strs->nb_strs + 1));
82         if (strs->strs == NULL) {
83                 strs->strs = old_strs_ptr;
84                 free(newstr);
85                 ERROR("write_sensu plugin: Unable to alloc memory");
86                 return -1;
87         }
88         strs->strs[strs->nb_strs] = newstr;
89         strs->nb_strs++;
90         return 0;
91 }
92 /* }}} int add_str_to_list */
93
94 static void free_str_list(struct str_list *strs) /* {{{ */
95 {
96         int i;
97         for (i=0; i<strs->nb_strs; i++)
98                 free(strs->strs[i]);
99         free(strs->strs);
100 }
101 /* }}} void free_str_list */
102
103 static int sensu_connect(struct sensu_host *host) /* {{{ */
104 {
105         int                      e;
106         struct addrinfo         *ai, hints;
107         char const              *node;
108         char const              *service;
109
110         // Resolve the target if we haven't done already
111         if (!(host->flags & F_READY)) {
112                 memset(&hints, 0, sizeof(hints));
113                 memset(&service, 0, sizeof(service));
114                 host->res = NULL;
115                 hints.ai_family = AF_INET;
116                 hints.ai_socktype = SOCK_STREAM;
117 #ifdef AI_ADDRCONFIG
118                 hints.ai_flags |= AI_ADDRCONFIG;
119 #endif
120
121                 node = (host->node != NULL) ? host->node : SENSU_HOST;
122                 service = (host->service != NULL) ? host->service : SENSU_PORT;
123
124                 if ((e = getaddrinfo(node, service, &hints, &(host->res))) != 0) {
125                         ERROR("write_sensu plugin: Unable to resolve host \"%s\": %s",
126                                         node, gai_strerror(e));
127                         return -1;
128                 }
129                 DEBUG("write_sensu plugin: successfully resolved host/port: %s/%s",
130                                 node, service);
131                 host->flags |= F_READY;
132         }
133
134         struct linger so_linger;
135         host->s = -1;
136         for (ai = host->res; ai != NULL; ai = ai->ai_next) {
137                 // create the socket
138                 if ((host->s = socket(ai->ai_family,
139                                       ai->ai_socktype,
140                                       ai->ai_protocol)) == -1) {
141                         continue;
142                 }
143
144                 // Set very low close() lingering
145                 so_linger.l_onoff = 1;
146                 so_linger.l_linger = 3;
147                 if (setsockopt(host->s, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger) != 0)
148                         WARNING("write_sensu plugin: failed to set socket close() lingering");
149
150                 // connect the socket
151                 if (connect(host->s, ai->ai_addr, ai->ai_addrlen) != 0) {
152                         close(host->s);
153                         host->s = -1;
154                         continue;
155                 }
156                 DEBUG("write_sensu plugin: connected");
157                 break;
158         }
159
160         if (host->s < 0) {
161                 WARNING("write_sensu plugin: Unable to connect to sensu client");
162                 return -1;
163         }
164         return 0;
165 } /* }}} int sensu_connect */
166
167 static void sensu_close_socket(struct sensu_host *host) /* {{{ */
168 {
169         if (host->s != -1)
170                 close(host->s);
171         host->s = -1;
172
173 } /* }}} void sensu_close_socket */
174
175 static char *build_json_str_list(const char *tag, struct str_list const *list) /* {{{ */
176 {
177         int res;
178         char *ret_str;
179         char *temp_str;
180         int i;
181         if (list->nb_strs == 0) {
182                 ret_str = malloc(sizeof(char));
183                 if (ret_str == NULL) {
184                         ERROR("write_sensu plugin: Unable to alloc memory");
185                         return NULL;
186                 }
187                 ret_str[0] = '\0';
188         }
189
190         res = asprintf(&temp_str, "\"%s\": [\"%s\"", tag, list->strs[0]);
191         if (res == -1) {
192                 ERROR("write_sensu plugin: Unable to alloc memory");
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 = malloc(retlen + 1);
526         if (ret == NULL)
527                 return NULL;
528         // added to original: not optimized, but keeps valgrind happy.
529         memset(ret, 0, retlen + 1);
530
531         r = ret;
532         p = str;
533         while (1) {
534                 /* If the old and new strings are different lengths - in other
535                  * words we have already iterated through with strstr above,
536                  * and thus we know how many times we need to call it - then we
537                  * can avoid the final (potentially lengthy) call to strstr,
538                  * which we already know is going to return NULL, by
539                  * decrementing and checking count.
540                  */
541                 if (!samesize && !count--)
542                         break;
543                 /* Otherwise i.e. when the old and new strings are the same
544                  * length, and we don't know how many times to call strstr,
545                  * we must check for a NULL return here (we check it in any
546                  * event, to avoid further conditions, and because there's
547                  * no harm done with the check even when the old and new
548                  * strings are different lengths).
549                  */
550                 if ((q = strstr(p, old)) == NULL)
551                         break;
552                 /* This is undefined if q - p > PTRDIFF_MAX */
553                 ptrdiff_t l = q - p;
554                 memcpy(r, p, l);
555                 r += l;
556                 memcpy(r, new, newlen);
557                 r += newlen;
558                 p = q + oldlen;
559         }
560         strncpy(r, p, strlen(p));
561
562         return ret;
563 } /* }}} char *replace_str */
564
565 static char *replace_json_reserved(const char *message) /* {{{ */
566 {
567         char *msg = replace_str(message, "\\", "\\\\");
568         if (msg == NULL) {
569                 ERROR("write_sensu plugin: Unable to alloc memory");
570                 return NULL;
571         }
572         char *tmp = replace_str(msg, "\"", "\\\"");
573         free(msg);
574         if (tmp == NULL) {
575                 ERROR("write_sensu plugin: Unable to alloc memory");
576                 return NULL;
577         }
578         msg = replace_str(tmp, "\n", "\\\n");
579         free(tmp);
580         if (msg == NULL) {
581                 ERROR("write_sensu plugin: Unable to alloc memory");
582                 return NULL;
583         }
584         return msg;
585 } /* }}} char *replace_json_reserved */
586
587 static char *sensu_notification_to_json(struct sensu_host *host, /* {{{ */
588                 notification_t const *n)
589 {
590         char service_buffer[6 * DATA_MAX_NAME_LEN];
591         char const *severity;
592         notification_meta_t *meta;
593         char *ret_str;
594         char *temp_str;
595         int status;
596         size_t i;
597         int res;
598         // add the severity/status
599         switch (n->severity) {
600                 case NOTIF_OKAY:
601                         severity = "OK";
602                         status = 0;
603                         break;
604                 case NOTIF_WARNING:
605                         severity = "WARNING";
606                         status = 1;
607                         break;
608                 case NOTIF_FAILURE:
609                         severity = "CRITICAL";
610                         status = 2;
611                         break;
612                 default:
613                         severity = "UNKNOWN";
614                         status = 3;
615         }
616         res = asprintf(&temp_str, "{\"status\": %d", status);
617         if (res == -1) {
618                 ERROR("write_sensu plugin: Unable to alloc memory");
619                 return NULL;
620         }
621         ret_str = temp_str;
622
623         // incorporate the timestamp
624         res = asprintf(&temp_str, "%s, \"timestamp\": %ld", ret_str, CDTIME_T_TO_TIME_T(n->time));
625         free(ret_str);
626         if (res == -1) {
627                 ERROR("write_sensu plugin: Unable to alloc memory");
628                 return NULL;
629         }
630         ret_str = temp_str;
631
632         char *handlers_str = build_json_str_list("handlers", &(host->notification_handlers));
633         if (handlers_str == NULL) {
634                 ERROR("write_sensu plugin: Unable to alloc memory");
635                 free(ret_str);
636                 return NULL;
637         }
638         // incorporate the handlers
639         if (strlen(handlers_str) != 0) {
640                 res = asprintf(&temp_str, "%s, %s", ret_str, handlers_str);
641                 free(ret_str);
642                 free(handlers_str);
643                 if (res == -1) {
644                         ERROR("write_sensu plugin: Unable to alloc memory");
645                         return NULL;
646                 }
647                 ret_str = temp_str;
648         } else {
649                 free(handlers_str);
650         }
651
652         // incorporate the plugin name information if any
653         if (n->plugin[0] != 0) {
654                 res = asprintf(&temp_str, "%s, \"collectd_plugin\": \"%s\"", ret_str, n->plugin);
655                 free(ret_str);
656                 if (res == -1) {
657                         ERROR("write_sensu plugin: Unable to alloc memory");
658                         return NULL;
659                 }
660                 ret_str = temp_str;
661         }
662
663         // incorporate the plugin type if any
664         if (n->type[0] != 0) {
665                 res = asprintf(&temp_str, "%s, \"collectd_plugin_type\": \"%s\"", ret_str, n->type);
666                 free(ret_str);
667                 if (res == -1) {
668                         ERROR("write_sensu plugin: Unable to alloc memory");
669                         return NULL;
670                 }
671                 ret_str = temp_str;
672         }
673
674         // incorporate the plugin instance if any
675         if (n->plugin_instance[0] != 0) {
676                 res = asprintf(&temp_str, "%s, \"collectd_plugin_instance\": \"%s\"", ret_str, n->plugin_instance);
677                 free(ret_str);
678                 if (res == -1) {
679                         ERROR("write_sensu plugin: Unable to alloc memory");
680                         return NULL;
681                 }
682                 ret_str = temp_str;
683         }
684
685         // incorporate the plugin type instance if any
686         if (n->type_instance[0] != 0) {
687                 res = asprintf(&temp_str, "%s, \"collectd_plugin_type_instance\": \"%s\"", ret_str, n->type_instance);
688                 free(ret_str);
689                 if (res == -1) {
690                         ERROR("write_sensu plugin: Unable to alloc memory");
691                         return NULL;
692                 }
693                 ret_str = temp_str;
694         }
695
696         // add key value attributes from config if any
697         for (i = 0; i < sensu_attrs_num; i += 2) {
698                 res = asprintf(&temp_str, "%s, \"%s\": \"%s\"", ret_str, sensu_attrs[i], sensu_attrs[i+1]);
699                 free(ret_str);
700                 if (res == -1) {
701                         ERROR("write_sensu plugin: Unable to alloc memory");
702                         return NULL;
703                 }
704                 ret_str = temp_str;
705         }
706
707         // incorporate sensu tags from config if any
708         if ((sensu_tags != NULL) && (strlen(sensu_tags) != 0)) {
709                 res = asprintf(&temp_str, "%s, %s", ret_str, sensu_tags);
710                 free(ret_str);
711                 if (res == -1) {
712                         ERROR("write_sensu plugin: Unable to alloc memory");
713                         return NULL;
714                 }
715                 ret_str = temp_str;
716         }
717
718         // incorporate the service name
719         sensu_format_name2(service_buffer, sizeof(service_buffer),
720                                 /* host */ "", n->plugin, n->plugin_instance,
721                                 n->type, n->type_instance, host->separator);
722         // replace sensu event name chars that are considered illegal
723         in_place_replace_sensu_name_reserved(service_buffer);
724         res = asprintf(&temp_str, "%s, \"name\": \"%s\"", ret_str, &service_buffer[1]);
725         free(ret_str);
726         if (res == -1) {
727                 ERROR("write_sensu plugin: Unable to alloc memory");
728                 return NULL;
729         }
730         ret_str = temp_str;
731
732         // incorporate the check output
733         if (n->message[0] != 0) {
734                 char *msg = replace_json_reserved(n->message);
735                 if (msg == NULL) {
736                         ERROR("write_sensu plugin: Unable to alloc memory");
737                         free(ret_str);
738                         return NULL;
739                 }
740                 res = asprintf(&temp_str, "%s, \"output\": \"%s - %s\"", ret_str, severity, msg);
741                 free(ret_str);
742                 free(msg);
743                 if (res == -1) {
744                         ERROR("write_sensu plugin: Unable to alloc memory");
745                         return NULL;
746                 }
747                 ret_str = temp_str;
748         }
749
750         // Pull in values from threshold and add extra attributes
751         for (meta = n->meta; meta != NULL; meta = meta->next) {
752                 if (strcasecmp("CurrentValue", meta->name) == 0 && meta->type == NM_TYPE_DOUBLE) {
753                         res = asprintf(&temp_str, "%s, \"current_value\": \"%.8f\"", ret_str, meta->nm_value.nm_double);
754                         free(ret_str);
755                         if (res == -1) {
756                                 ERROR("write_sensu plugin: Unable to alloc memory");
757                                 return NULL;
758                         }
759                         ret_str = temp_str;
760                 }
761                 if (meta->type == NM_TYPE_STRING) {
762                         res = asprintf(&temp_str, "%s, \"%s\": \"%s\"", ret_str, meta->name, meta->nm_value.nm_string);
763                         free(ret_str);
764                         if (res == -1) {
765                                 ERROR("write_sensu plugin: Unable to alloc memory");
766                                 return NULL;
767                         }
768                         ret_str = temp_str;
769                 }
770         }
771
772         // close the curly bracket
773         res = asprintf(&temp_str, "%s}\n", ret_str);
774         free(ret_str);
775         if (res == -1) {
776                 ERROR("write_sensu plugin: Unable to alloc memory");
777                 return NULL;
778         }
779         ret_str = temp_str;
780
781         DEBUG("write_sensu plugin: Successfully created JSON for notification: "
782                                 "host = \"%s\", service = \"%s\", state = \"%s\"",
783                                 n->host, service_buffer, severity);
784         return ret_str;
785 } /* }}} char *sensu_notification_to_json */
786
787 static int sensu_send_msg(struct sensu_host *host, const char *msg) /* {{{ */
788 {
789         int status = 0;
790         size_t  buffer_len;
791
792         status = sensu_connect(host);
793         if (status != 0)
794                 return status;
795
796         buffer_len = strlen(msg);
797
798         status = (int) swrite(host->s, msg, buffer_len);
799         sensu_close_socket(host);
800
801         if (status != 0) {
802                 char errbuf[1024];
803                 ERROR("write_sensu plugin: Sending to Sensu at %s:%s failed: %s",
804                                 (host->node != NULL) ? host->node : SENSU_HOST,
805                                 (host->service != NULL) ? host->service : SENSU_PORT,
806                                 sstrerror(errno, errbuf, sizeof(errbuf)));
807                 return -1;
808         }
809
810         return 0;
811 } /* }}} int sensu_send_msg */
812
813
814 static int sensu_send(struct sensu_host *host, char const *msg) /* {{{ */
815 {
816         int status = 0;
817
818         status = sensu_send_msg(host, msg);
819         if (status != 0) {
820                 host->flags &= ~F_READY;
821                 if (host->res != NULL) {
822                         freeaddrinfo(host->res);
823                         host->res = NULL;
824                 }
825                 return status;
826         }
827
828         return 0;
829 } /* }}} int sensu_send */
830
831
832 static int sensu_write(const data_set_t *ds, /* {{{ */
833               const value_list_t *vl,
834               user_data_t *ud)
835 {
836         int status = 0;
837         int statuses[vl->values_len];
838         struct sensu_host       *host = ud->data;
839         gauge_t *rates = NULL;
840         size_t i;
841         char *msg;
842
843         pthread_mutex_lock(&host->lock);
844         memset(statuses, 0, vl->values_len * sizeof(*statuses));
845
846         if (host->store_rates) {
847                 rates = uc_get_rate(ds, vl);
848                 if (rates == NULL) {
849                         ERROR("write_sensu plugin: uc_get_rate failed.");
850                         pthread_mutex_unlock(&host->lock);
851                         return -1;
852                 }
853         }
854         for (i = 0; i < vl->values_len; i++) {
855                 msg = sensu_value_to_json(host, ds, vl, (int) i, rates, statuses[i]);
856                 if (msg == NULL) {
857                         sfree(rates);
858                         pthread_mutex_unlock(&host->lock);
859                         return -1;
860                 }
861                 status = sensu_send(host, msg);
862                 free(msg);
863                 if (status != 0) {
864                         ERROR("write_sensu plugin: sensu_send failed with status %i", status);
865                         pthread_mutex_unlock(&host->lock);
866                         sfree(rates);
867                         return status;
868                 }
869         }
870         sfree(rates);
871         pthread_mutex_unlock(&host->lock);
872         return status;
873 } /* }}} int sensu_write */
874
875 static int sensu_notification(const notification_t *n, user_data_t *ud) /* {{{ */
876 {
877         int     status;
878         struct sensu_host *host = ud->data;
879         char *msg;
880
881         pthread_mutex_lock(&host->lock);
882
883         msg = sensu_notification_to_json(host, n);
884         if (msg == NULL) {
885                 pthread_mutex_unlock(&host->lock);
886                 return -1;
887         }
888
889         status = sensu_send(host, msg);
890         free(msg);
891         if (status != 0)
892                 ERROR("write_sensu plugin: sensu_send failed with status %i", status);
893         pthread_mutex_unlock(&host->lock);
894
895         return status;
896 } /* }}} int sensu_notification */
897
898 static void sensu_free(void *p) /* {{{ */
899 {
900         struct sensu_host *host = p;
901
902         if (host == NULL)
903                 return;
904
905         pthread_mutex_lock(&host->lock);
906
907         host->reference_count--;
908         if (host->reference_count > 0) {
909                 pthread_mutex_unlock(&host->lock);
910                 return;
911         }
912
913         sensu_close_socket(host);
914         if (host->res != NULL) {
915                 freeaddrinfo(host->res);
916                 host->res = NULL;
917         }
918         sfree(host->service);
919         sfree(host->event_service_prefix);
920         sfree(host->name);
921         sfree(host->node);
922         sfree(host->separator);
923         free_str_list(&(host->metric_handlers));
924         free_str_list(&(host->notification_handlers));
925         pthread_mutex_destroy(&host->lock);
926         sfree(host);
927 } /* }}} void sensu_free */
928
929
930 static int sensu_config_node(oconfig_item_t *ci) /* {{{ */
931 {
932         struct sensu_host       *host = NULL;
933         int                                     status = 0;
934         int                                     i;
935         oconfig_item_t          *child;
936         char                            callback_name[DATA_MAX_NAME_LEN];
937         user_data_t                     ud;
938
939         if ((host = calloc(1, sizeof(*host))) == NULL) {
940                 ERROR("write_sensu plugin: calloc failed.");
941                 return ENOMEM;
942         }
943         pthread_mutex_init(&host->lock, NULL);
944         host->reference_count = 1;
945         host->node = NULL;
946         host->service = NULL;
947         host->notifications = 0;
948         host->metrics = 0;
949         host->store_rates = 1;
950         host->always_append_ds = 0;
951         host->metric_handlers.nb_strs = 0;
952         host->metric_handlers.strs = NULL;
953         host->notification_handlers.nb_strs = 0;
954         host->notification_handlers.strs = NULL;
955         host->separator = strdup("/");
956         if (host->separator == NULL) {
957                 ERROR("write_sensu plugin: Unable to alloc memory");
958                 sensu_free(host);
959                 return -1;
960         }
961
962         status = cf_util_get_string(ci, &host->name);
963         if (status != 0) {
964                 WARNING("write_sensu plugin: Required host name is missing.");
965                 sensu_free(host);
966                 return -1;
967         }
968
969         for (i = 0; i < ci->children_num; i++) {
970                 child = &ci->children[i];
971                 status = 0;
972
973                 if (strcasecmp("Host", child->key) == 0) {
974                         status = cf_util_get_string(child, &host->node);
975                         if (status != 0)
976                                 break;
977                 } else if (strcasecmp("Notifications", child->key) == 0) {
978                         status = cf_util_get_boolean(child, &host->notifications);
979                         if (status != 0)
980                                 break;
981                 } else if (strcasecmp("Metrics", child->key) == 0) {
982                                         status = cf_util_get_boolean(child, &host->metrics);
983                                         if (status != 0)
984                                                 break;
985                 } else if (strcasecmp("EventServicePrefix", child->key) == 0) {
986                         status = cf_util_get_string(child, &host->event_service_prefix);
987                         if (status != 0)
988                                 break;
989                 } else if (strcasecmp("Separator", child->key) == 0) {
990                                 status = cf_util_get_string(child, &host->separator);
991                                 if (status != 0)
992                                         break;
993                 } else if (strcasecmp("MetricHandler", child->key) == 0) {
994                         char *temp_str = NULL;
995                         status = cf_util_get_string(child, &temp_str);
996                         if (status != 0)
997                                 break;
998                         status = add_str_to_list(&(host->metric_handlers), temp_str);
999                         free(temp_str);
1000                         if (status != 0)
1001                                 break;
1002                 } else if (strcasecmp("NotificationHandler", child->key) == 0) {
1003                         char *temp_str = NULL;
1004                         status = cf_util_get_string(child, &temp_str);
1005                         if (status != 0)
1006                                 break;
1007                         status = add_str_to_list(&(host->notification_handlers), temp_str);
1008                         free(temp_str);
1009                         if (status != 0)
1010                                 break;
1011                 } else if (strcasecmp("Port", child->key) == 0) {
1012                         status = cf_util_get_service(child, &host->service);
1013                         if (status != 0) {
1014                                 ERROR("write_sensu plugin: Invalid argument "
1015                                                 "configured for the \"Port\" "
1016                                                 "option.");
1017                                 break;
1018                         }
1019                 } else if (strcasecmp("StoreRates", child->key) == 0) {
1020                         status = cf_util_get_boolean(child, &host->store_rates);
1021                         if (status != 0)
1022                                 break;
1023                 } else if (strcasecmp("AlwaysAppendDS", child->key) == 0) {
1024                         status = cf_util_get_boolean(child,
1025                                         &host->always_append_ds);
1026                         if (status != 0)
1027                                 break;
1028                 } else {
1029                         WARNING("write_sensu plugin: ignoring unknown config "
1030                                 "option: \"%s\"", child->key);
1031                 }
1032         }
1033         if (status != 0) {
1034                 sensu_free(host);
1035                 return status;
1036         }
1037
1038         if (host->metrics && (host->metric_handlers.nb_strs == 0)) {
1039                         sensu_free(host);
1040                         WARNING("write_sensu plugin: metrics enabled but no MetricHandler defined. Giving up.");
1041                         return -1;
1042                 }
1043
1044         if (host->notifications && (host->notification_handlers.nb_strs == 0)) {
1045                 sensu_free(host);
1046                 WARNING("write_sensu plugin: notifications enabled but no NotificationHandler defined. Giving up.");
1047                 return -1;
1048         }
1049
1050         if ((host->notification_handlers.nb_strs > 0) && (host->notifications == 0)) {
1051                 WARNING("write_sensu plugin: NotificationHandler given so forcing notifications to be enabled");
1052                 host->notifications = 1;
1053         }
1054
1055         if ((host->metric_handlers.nb_strs > 0) && (host->metrics == 0)) {
1056                 WARNING("write_sensu plugin: MetricHandler given so forcing metrics to be enabled");
1057                 host->metrics = 1;
1058         }
1059
1060         if (!(host->notifications || host->metrics)) {
1061                 WARNING("write_sensu plugin: neither metrics nor notifications enabled. Giving up.");
1062                 sensu_free(host);
1063                 return -1;
1064         }
1065
1066         ssnprintf(callback_name, sizeof(callback_name), "write_sensu/%s", host->name);
1067         ud.data = host;
1068         ud.free_func = sensu_free;
1069
1070         pthread_mutex_lock(&host->lock);
1071
1072         if (host->metrics) {
1073                 status = plugin_register_write(callback_name, sensu_write, &ud);
1074                 if (status != 0)
1075                         WARNING("write_sensu plugin: plugin_register_write (\"%s\") "
1076                                         "failed with status %i.",
1077                                         callback_name, status);
1078                 else /* success */
1079                         host->reference_count++;
1080         }
1081
1082         if (host->notifications) {
1083                 status = plugin_register_notification(callback_name, sensu_notification, &ud);
1084                 if (status != 0)
1085                         WARNING("write_sensu plugin: plugin_register_notification (\"%s\") "
1086                                         "failed with status %i.",
1087                                         callback_name, status);
1088                 else
1089                         host->reference_count++;
1090         }
1091
1092         if (host->reference_count <= 1) {
1093                 /* Both callbacks failed => free memory.
1094                  * We need to unlock here, because sensu_free() will lock.
1095                  * This is not a race condition, because we're the only one
1096                  * holding a reference. */
1097                 pthread_mutex_unlock(&host->lock);
1098                 sensu_free(host);
1099                 return -1;
1100         }
1101
1102         host->reference_count--;
1103         pthread_mutex_unlock(&host->lock);
1104
1105         return status;
1106 } /* }}} int sensu_config_node */
1107
1108 static int sensu_config(oconfig_item_t *ci) /* {{{ */
1109 {
1110         int              i;
1111         oconfig_item_t  *child;
1112         int              status;
1113         struct str_list sensu_tags_arr;
1114
1115         sensu_tags_arr.nb_strs = 0;
1116         sensu_tags_arr.strs = NULL;
1117
1118         for (i = 0; i < ci->children_num; i++)  {
1119                 child = &ci->children[i];
1120
1121                 if (strcasecmp("Node", child->key) == 0) {
1122                         sensu_config_node(child);
1123                 } else if (strcasecmp(child->key, "attribute") == 0) {
1124                         if (child->values_num != 2) {
1125                                 WARNING("sensu attributes need both a key and a value.");
1126                                 continue;
1127                         }
1128                         if (child->values[0].type != OCONFIG_TYPE_STRING ||
1129                                         child->values[1].type != OCONFIG_TYPE_STRING) {
1130                                 WARNING("sensu attribute needs string arguments.");
1131                                 continue;
1132                         }
1133
1134                         strarray_add(&sensu_attrs, &sensu_attrs_num, child->values[0].value.string);
1135                         strarray_add(&sensu_attrs, &sensu_attrs_num, child->values[1].value.string);
1136
1137                         DEBUG("write_sensu plugin: New attribute: %s => %s",
1138                                         child->values[0].value.string,
1139                                         child->values[1].value.string);
1140                 } else if (strcasecmp(child->key, "tag") == 0) {
1141                         char *tmp = NULL;
1142                         status = cf_util_get_string(child, &tmp);
1143                         if (status != 0)
1144                                 continue;
1145
1146                         status = add_str_to_list(&sensu_tags_arr, tmp);
1147                         sfree(tmp);
1148                         if (status != 0)
1149                                 continue;
1150                         DEBUG("write_sensu plugin: Got tag: %s", tmp);
1151                 } else {
1152                         WARNING("write_sensu plugin: Ignoring unknown "
1153                                  "configuration option \"%s\" at top level.",
1154                                  child->key);
1155                 }
1156         }
1157         if (sensu_tags_arr.nb_strs > 0) {
1158                 sfree (sensu_tags);
1159                 sensu_tags = build_json_str_list("tags", &sensu_tags_arr);
1160                 free_str_list(&sensu_tags_arr);
1161                 if (sensu_tags == NULL) {
1162                         ERROR("write_sensu plugin: Unable to alloc memory");
1163                         return -1;
1164                 }
1165         }
1166         return 0;
1167 } /* }}} int sensu_config */
1168
1169 void module_register(void)
1170 {
1171         plugin_register_complex_config("write_sensu", sensu_config);
1172 }
1173
1174 /* vim: set sw=8 sts=8 ts=8 noet : */