1 /**
2 * collectd - src/utils_format_json.c
3 * Copyright (C) 2009-2015 Florian octo Forster
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 * Florian octo Forster <octo at collectd.org>
25 **/
27 #include "collectd.h"
29 #include "utils_format_json.h"
31 #include "common.h"
32 #include "plugin.h"
33 #include "utils_cache.h"
35 #if HAVE_LIBYAJL
36 #include <yajl/yajl_common.h>
37 #include <yajl/yajl_gen.h>
38 #if HAVE_YAJL_YAJL_VERSION_H
39 #include <yajl/yajl_version.h>
40 #endif
41 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
42 #define HAVE_YAJL_V2 1
43 #endif
44 #endif
46 static int json_escape_string(char *buffer, size_t buffer_size, /* {{{ */
47 const char *string) {
48 size_t dst_pos;
50 if ((buffer == NULL) || (string == NULL))
51 return (-EINVAL);
53 if (buffer_size < 3)
54 return (-ENOMEM);
56 dst_pos = 0;
58 #define BUFFER_ADD(c) \
59 do { \
60 if (dst_pos >= (buffer_size - 1)) { \
61 buffer[buffer_size - 1] = 0; \
62 return (-ENOMEM); \
63 } \
64 buffer[dst_pos] = (c); \
65 dst_pos++; \
66 } while (0)
68 /* Escape special characters */
69 BUFFER_ADD('"');
70 for (size_t src_pos = 0; string[src_pos] != 0; src_pos++) {
71 if ((string[src_pos] == '"') || (string[src_pos] == '\\')) {
72 BUFFER_ADD('\\');
73 BUFFER_ADD(string[src_pos]);
74 } else if (string[src_pos] <= 0x001F)
75 BUFFER_ADD('?');
76 else
77 BUFFER_ADD(string[src_pos]);
78 } /* for */
79 BUFFER_ADD('"');
80 buffer[dst_pos] = 0;
82 #undef BUFFER_ADD
84 return (0);
85 } /* }}} int json_escape_string */
87 static int values_to_json(char *buffer, size_t buffer_size, /* {{{ */
88 const data_set_t *ds, const value_list_t *vl,
89 int store_rates) {
90 size_t offset = 0;
91 gauge_t *rates = NULL;
93 memset(buffer, 0, buffer_size);
95 #define BUFFER_ADD(...) \
96 do { \
97 int status; \
98 status = ssnprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \
99 if (status < 1) { \
100 sfree(rates); \
101 return (-1); \
102 } else if (((size_t)status) >= (buffer_size - offset)) { \
103 sfree(rates); \
104 return (-ENOMEM); \
105 } else \
106 offset += ((size_t)status); \
107 } while (0)
109 BUFFER_ADD("[");
110 for (size_t i = 0; i < ds->ds_num; i++) {
111 if (i > 0)
112 BUFFER_ADD(",");
114 if (ds->ds[i].type == DS_TYPE_GAUGE) {
115 if (isfinite(vl->values[i].gauge))
116 BUFFER_ADD(JSON_GAUGE_FORMAT, vl->values[i].gauge);
117 else
118 BUFFER_ADD("null");
119 } else if (store_rates) {
120 if (rates == NULL)
121 rates = uc_get_rate(ds, vl);
122 if (rates == NULL) {
123 WARNING("utils_format_json: uc_get_rate failed.");
124 sfree(rates);
125 return (-1);
126 }
128 if (isfinite(rates[i]))
129 BUFFER_ADD(JSON_GAUGE_FORMAT, rates[i]);
130 else
131 BUFFER_ADD("null");
132 } else if (ds->ds[i].type == DS_TYPE_COUNTER)
133 BUFFER_ADD("%llu", vl->values[i].counter);
134 else if (ds->ds[i].type == DS_TYPE_DERIVE)
135 BUFFER_ADD("%" PRIi64, vl->values[i].derive);
136 else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
137 BUFFER_ADD("%" PRIu64, vl->values[i].absolute);
138 else {
139 ERROR("format_json: Unknown data source type: %i", ds->ds[i].type);
140 sfree(rates);
141 return (-1);
142 }
143 } /* for ds->ds_num */
144 BUFFER_ADD("]");
146 #undef BUFFER_ADD
148 DEBUG("format_json: values_to_json: buffer = %s;", buffer);
149 sfree(rates);
150 return (0);
151 } /* }}} int values_to_json */
153 static int dstypes_to_json(char *buffer, size_t buffer_size, /* {{{ */
154 const data_set_t *ds) {
155 size_t offset = 0;
157 memset(buffer, 0, buffer_size);
159 #define BUFFER_ADD(...) \
160 do { \
161 int status; \
162 status = ssnprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \
163 if (status < 1) \
164 return (-1); \
165 else if (((size_t)status) >= (buffer_size - offset)) \
166 return (-ENOMEM); \
167 else \
168 offset += ((size_t)status); \
169 } while (0)
171 BUFFER_ADD("[");
172 for (size_t i = 0; i < ds->ds_num; i++) {
173 if (i > 0)
174 BUFFER_ADD(",");
176 BUFFER_ADD("\"%s\"", DS_TYPE_TO_STRING(ds->ds[i].type));
177 } /* for ds->ds_num */
178 BUFFER_ADD("]");
180 #undef BUFFER_ADD
182 DEBUG("format_json: dstypes_to_json: buffer = %s;", buffer);
184 return (0);
185 } /* }}} int dstypes_to_json */
187 static int dsnames_to_json(char *buffer, size_t buffer_size, /* {{{ */
188 const data_set_t *ds) {
189 size_t offset = 0;
191 memset(buffer, 0, buffer_size);
193 #define BUFFER_ADD(...) \
194 do { \
195 int status; \
196 status = ssnprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \
197 if (status < 1) \
198 return (-1); \
199 else if (((size_t)status) >= (buffer_size - offset)) \
200 return (-ENOMEM); \
201 else \
202 offset += ((size_t)status); \
203 } while (0)
205 BUFFER_ADD("[");
206 for (size_t i = 0; i < ds->ds_num; i++) {
207 if (i > 0)
208 BUFFER_ADD(",");
210 BUFFER_ADD("\"%s\"", ds->ds[i].name);
211 } /* for ds->ds_num */
212 BUFFER_ADD("]");
214 #undef BUFFER_ADD
216 DEBUG("format_json: dsnames_to_json: buffer = %s;", buffer);
218 return (0);
219 } /* }}} int dsnames_to_json */
221 static int meta_data_keys_to_json(char *buffer, size_t buffer_size, /* {{{ */
222 meta_data_t *meta, char **keys,
223 size_t keys_num) {
224 size_t offset = 0;
225 int status;
227 buffer[0] = 0;
229 #define BUFFER_ADD(...) \
230 do { \
231 status = ssnprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \
232 if (status < 1) \
233 return (-1); \
234 else if (((size_t)status) >= (buffer_size - offset)) \
235 return (-ENOMEM); \
236 else \
237 offset += ((size_t)status); \
238 } while (0)
240 for (size_t i = 0; i < keys_num; ++i) {
241 int type;
242 char *key = keys[i];
244 type = meta_data_type(meta, key);
245 if (type == MD_TYPE_STRING) {
246 char *value = NULL;
247 if (meta_data_get_string(meta, key, &value) == 0) {
248 char temp[512] = "";
250 status = json_escape_string(temp, sizeof(temp), value);
251 sfree(value);
252 if (status != 0)
253 return status;
255 BUFFER_ADD(",\"%s\":%s", key, temp);
256 }
257 } else if (type == MD_TYPE_SIGNED_INT) {
258 int64_t value = 0;
259 if (meta_data_get_signed_int(meta, key, &value) == 0)
260 BUFFER_ADD(",\"%s\":%" PRIi64, key, value);
261 } else if (type == MD_TYPE_UNSIGNED_INT) {
262 uint64_t value = 0;
263 if (meta_data_get_unsigned_int(meta, key, &value) == 0)
264 BUFFER_ADD(",\"%s\":%" PRIu64, key, value);
265 } else if (type == MD_TYPE_DOUBLE) {
266 double value = 0.0;
267 if (meta_data_get_double(meta, key, &value) == 0)
268 BUFFER_ADD(",\"%s\":%f", key, value);
269 } else if (type == MD_TYPE_BOOLEAN) {
270 _Bool value = 0;
271 if (meta_data_get_boolean(meta, key, &value) == 0)
272 BUFFER_ADD(",\"%s\":%s", key, value ? "true" : "false");
273 }
274 } /* for (keys) */
276 if (offset == 0)
277 return (ENOENT);
279 buffer[0] = '{'; /* replace leading ',' */
280 BUFFER_ADD("}");
282 #undef BUFFER_ADD
284 return (0);
285 } /* }}} int meta_data_keys_to_json */
287 static int meta_data_to_json(char *buffer, size_t buffer_size, /* {{{ */
288 meta_data_t *meta) {
289 char **keys = NULL;
290 size_t keys_num;
291 int status;
293 if ((buffer == NULL) || (buffer_size == 0) || (meta == NULL))
294 return (EINVAL);
296 status = meta_data_toc(meta, &keys);
297 if (status <= 0)
298 return (status);
299 keys_num = (size_t)status;
301 status = meta_data_keys_to_json(buffer, buffer_size, meta, keys, keys_num);
303 for (size_t i = 0; i < keys_num; ++i)
304 sfree(keys[i]);
305 sfree(keys);
307 return status;
308 } /* }}} int meta_data_to_json */
310 static int value_list_to_json(char *buffer, size_t buffer_size, /* {{{ */
311 const data_set_t *ds, const value_list_t *vl,
312 int store_rates) {
313 char temp[512];
314 size_t offset = 0;
315 int status;
317 memset(buffer, 0, buffer_size);
319 #define BUFFER_ADD(...) \
320 do { \
321 status = ssnprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \
322 if (status < 1) \
323 return (-1); \
324 else if (((size_t)status) >= (buffer_size - offset)) \
325 return (-ENOMEM); \
326 else \
327 offset += ((size_t)status); \
328 } while (0)
330 /* All value lists have a leading comma. The first one will be replaced with
331 * a square bracket in `format_json_finalize'. */
332 BUFFER_ADD(",{");
334 status = values_to_json(temp, sizeof(temp), ds, vl, store_rates);
335 if (status != 0)
336 return (status);
337 BUFFER_ADD("\"values\":%s", temp);
339 status = dstypes_to_json(temp, sizeof(temp), ds);
340 if (status != 0)
341 return (status);
342 BUFFER_ADD(",\"dstypes\":%s", temp);
344 status = dsnames_to_json(temp, sizeof(temp), ds);
345 if (status != 0)
346 return (status);
347 BUFFER_ADD(",\"dsnames\":%s", temp);
349 BUFFER_ADD(",\"time\":%.3f", CDTIME_T_TO_DOUBLE(vl->time));
350 BUFFER_ADD(",\"interval\":%.3f", CDTIME_T_TO_DOUBLE(vl->interval));
352 #define BUFFER_ADD_KEYVAL(key, value) \
353 do { \
354 status = json_escape_string(temp, sizeof(temp), (value)); \
355 if (status != 0) \
356 return (status); \
357 BUFFER_ADD(",\"%s\":%s", (key), temp); \
358 } while (0)
360 BUFFER_ADD_KEYVAL("host", vl->host);
361 BUFFER_ADD_KEYVAL("plugin", vl->plugin);
362 BUFFER_ADD_KEYVAL("plugin_instance", vl->plugin_instance);
363 BUFFER_ADD_KEYVAL("type", vl->type);
364 BUFFER_ADD_KEYVAL("type_instance", vl->type_instance);
366 if (vl->meta != NULL) {
367 char meta_buffer[buffer_size];
368 memset(meta_buffer, 0, sizeof(meta_buffer));
369 status = meta_data_to_json(meta_buffer, sizeof(meta_buffer), vl->meta);
370 if (status != 0)
371 return (status);
373 BUFFER_ADD(",\"meta\":%s", meta_buffer);
374 } /* if (vl->meta != NULL) */
376 BUFFER_ADD("}");
378 #undef BUFFER_ADD_KEYVAL
379 #undef BUFFER_ADD
381 DEBUG("format_json: value_list_to_json: buffer = %s;", buffer);
383 return (0);
384 } /* }}} int value_list_to_json */
386 static int format_json_value_list_nocheck(char *buffer, /* {{{ */
387 size_t *ret_buffer_fill,
388 size_t *ret_buffer_free,
389 const data_set_t *ds,
390 const value_list_t *vl,
391 int store_rates, size_t temp_size) {
392 char temp[temp_size];
393 int status;
395 status = value_list_to_json(temp, sizeof(temp), ds, vl, store_rates);
396 if (status != 0)
397 return (status);
398 temp_size = strlen(temp);
400 memcpy(buffer + (*ret_buffer_fill), temp, temp_size + 1);
401 (*ret_buffer_fill) += temp_size;
402 (*ret_buffer_free) -= temp_size;
404 return (0);
405 } /* }}} int format_json_value_list_nocheck */
407 int format_json_initialize(char *buffer, /* {{{ */
408 size_t *ret_buffer_fill, size_t *ret_buffer_free) {
409 size_t buffer_fill;
410 size_t buffer_free;
412 if ((buffer == NULL) || (ret_buffer_fill == NULL) ||
413 (ret_buffer_free == NULL))
414 return (-EINVAL);
416 buffer_fill = *ret_buffer_fill;
417 buffer_free = *ret_buffer_free;
419 buffer_free = buffer_fill + buffer_free;
420 buffer_fill = 0;
422 if (buffer_free < 3)
423 return (-ENOMEM);
425 memset(buffer, 0, buffer_free);
426 *ret_buffer_fill = buffer_fill;
427 *ret_buffer_free = buffer_free;
429 return (0);
430 } /* }}} int format_json_initialize */
432 int format_json_finalize(char *buffer, /* {{{ */
433 size_t *ret_buffer_fill, size_t *ret_buffer_free) {
434 size_t pos;
436 if ((buffer == NULL) || (ret_buffer_fill == NULL) ||
437 (ret_buffer_free == NULL))
438 return (-EINVAL);
440 if (*ret_buffer_free < 2)
441 return (-ENOMEM);
443 /* Replace the leading comma added in `value_list_to_json' with a square
444 * bracket. */
445 if (buffer[0] != ',')
446 return (-EINVAL);
447 buffer[0] = '[';
449 pos = *ret_buffer_fill;
450 buffer[pos] = ']';
451 buffer[pos + 1] = 0;
453 (*ret_buffer_fill)++;
454 (*ret_buffer_free)--;
456 return (0);
457 } /* }}} int format_json_finalize */
459 int format_json_value_list(char *buffer, /* {{{ */
460 size_t *ret_buffer_fill, size_t *ret_buffer_free,
461 const data_set_t *ds, const value_list_t *vl,
462 int store_rates) {
463 if ((buffer == NULL) || (ret_buffer_fill == NULL) ||
464 (ret_buffer_free == NULL) || (ds == NULL) || (vl == NULL))
465 return (-EINVAL);
467 if (*ret_buffer_free < 3)
468 return (-ENOMEM);
470 return (format_json_value_list_nocheck(buffer, ret_buffer_fill,
471 ret_buffer_free, ds, vl, store_rates,
472 (*ret_buffer_free) - 2));
473 } /* }}} int format_json_value_list */
475 #if HAVE_LIBYAJL
476 static int json_add_string(yajl_gen g, char const *str) /* {{{ */
477 {
478 if (str == NULL)
479 return (int)yajl_gen_null(g);
481 return (int)yajl_gen_string(g, (unsigned char const *)str,
482 (unsigned int)strlen(str));
483 } /* }}} int json_add_string */
485 #define JSON_ADD(g, str) \
486 do { \
487 yajl_gen_status status = json_add_string(g, str); \
488 if (status != yajl_gen_status_ok) { \
489 return -1; \
490 } \
491 } while (0)
493 #define JSON_ADDF(g, format, ...) \
494 do { \
495 char *str = ssnprintf_alloc(format, __VA_ARGS__); \
496 yajl_gen_status status = json_add_string(g, str); \
497 free(str); \
498 if (status != yajl_gen_status_ok) { \
499 return -1; \
500 } \
501 } while (0)
503 static int format_json_meta(yajl_gen g, notification_meta_t *meta) /* {{{ */
504 {
505 if (meta == NULL)
506 return 0;
508 JSON_ADD(g, meta->name);
509 switch (meta->type) {
510 case NM_TYPE_STRING:
511 JSON_ADD(g, meta->nm_value.nm_string);
512 break;
513 case NM_TYPE_SIGNED_INT:
514 JSON_ADDF(g, "%" PRIi64, meta->nm_value.nm_signed_int);
515 break;
516 case NM_TYPE_UNSIGNED_INT:
517 JSON_ADDF(g, "%" PRIu64, meta->nm_value.nm_unsigned_int);
518 break;
519 case NM_TYPE_DOUBLE:
520 JSON_ADDF(g, JSON_GAUGE_FORMAT, meta->nm_value.nm_double);
521 break;
522 case NM_TYPE_BOOLEAN:
523 JSON_ADD(g, meta->nm_value.nm_boolean ? "true" : "false");
524 break;
525 default:
526 ERROR("format_json_meta: unknown meta data type %d (name \"%s\")",
527 meta->type, meta->name);
528 yajl_gen_null(g);
529 }
531 return format_json_meta(g, meta->next);
532 } /* }}} int format_json_meta */
534 static int format_time(yajl_gen g, cdtime_t t) /* {{{ */
535 {
536 char buffer[RFC3339NANO_SIZE] = "";
538 if (rfc3339nano(buffer, sizeof(buffer), t) != 0)
539 return -1;
541 JSON_ADD(g, buffer);
542 return 0;
543 } /* }}} int format_time */
545 static int format_alert(yajl_gen g, notification_t const *n) /* {{{ */
546 {
547 yajl_gen_array_open(g);
548 yajl_gen_map_open(g); /* BEGIN alert */
550 /*
551 * labels
552 */
553 JSON_ADD(g, "labels");
554 yajl_gen_map_open(g); /* BEGIN labels */
556 JSON_ADD(g, "alertname");
557 if (strncmp(n->plugin, n->type, strlen(n->plugin)) == 0)
558 JSON_ADDF(g, "collectd_%s", n->type);
559 else
560 JSON_ADDF(g, "collectd_%s_%s", n->plugin, n->type);
562 JSON_ADD(g, "instance");
563 JSON_ADD(g, n->host);
565 /* mangling of plugin instance and type instance into labels is copied from
566 * the Prometheus collectd exporter. */
567 if (strlen(n->plugin_instance) > 0) {
568 JSON_ADD(g, n->plugin);
569 JSON_ADD(g, n->plugin_instance);
570 }
571 if (strlen(n->type_instance) > 0) {
572 if (strlen(n->plugin_instance) > 0)
573 JSON_ADD(g, "type");
574 else
575 JSON_ADD(g, n->plugin);
576 JSON_ADD(g, n->type_instance);
577 }
579 JSON_ADD(g, "severity");
580 JSON_ADD(g, (n->severity == NOTIF_FAILURE)
581 ? "FAILURE"
582 : (n->severity == NOTIF_WARNING)
583 ? "WARNING"
584 : (n->severity == NOTIF_OKAY) ? "OKAY" : "UNKNOWN");
586 JSON_ADD(g, "service");
587 JSON_ADD(g, "collectd");
589 yajl_gen_map_close(g); /* END labels */
591 /*
592 * annotations
593 */
594 JSON_ADD(g, "annotations");
595 yajl_gen_map_open(g); /* BEGIN annotations */
597 JSON_ADD(g, "summary");
598 JSON_ADD(g, n->message);
600 if (format_json_meta(g, n->meta) != 0)
601 return -1;
603 yajl_gen_map_close(g); /* END annotations */
605 JSON_ADD(g, "startsAt");
606 format_time(g, n->time);
608 yajl_gen_map_close(g); /* END alert */
609 yajl_gen_array_close(g);
611 return 0;
612 } /* }}} format_alert */
614 /*
615 * Format (prometheus/alertmanager v1):
616 *
617 * [{
618 * "labels": {
619 * "alertname": "collectd_cpu",
620 * "instance": "host.example.com",
621 * "severity": "FAILURE",
622 * "service": "collectd",
623 * "cpu": "0",
624 * "type": "wait"
625 * },
626 * "annotations": {
627 * "summary": "...",
628 * // meta
629 * },
630 * "startsAt": <rfc3339 time>,
631 * "endsAt": <rfc3339 time>, // not used
632 * }]
633 */
634 int format_json_notification(char *buffer, size_t buffer_size, /* {{{ */
635 notification_t const *n) {
636 yajl_gen g;
637 unsigned char const *out;
638 #if HAVE_YAJL_V2
639 size_t unused_out_len;
640 #else
641 unsigned int unused_out_len;
642 #endif
644 if ((buffer == NULL) || (n == NULL))
645 return EINVAL;
647 #if HAVE_YAJL_V2
648 g = yajl_gen_alloc(NULL);
649 if (g == NULL)
650 return -1;
651 #if COLLECT_DEBUG
652 yajl_gen_config(g, yajl_gen_beautify, 1);
653 yajl_gen_config(g, yajl_gen_validate_utf8, 1);
654 #endif
656 #else /* !HAVE_YAJL_V2 */
657 yajl_gen_config conf = {0};
658 #if COLLECT_DEBUG
659 conf.beautify = 1;
660 conf.indentString = " ";
661 #endif
662 g = yajl_gen_alloc(&conf, NULL);
663 if (g == NULL)
664 return -1;
665 #endif
667 if (format_alert(g, n) != 0) {
668 yajl_gen_clear(g);
669 yajl_gen_free(g);
670 return -1;
671 }
673 /* copy to output buffer */
674 yajl_gen_get_buf(g, &out, &unused_out_len);
675 sstrncpy(buffer, (void *)out, buffer_size);
677 yajl_gen_clear(g);
678 yajl_gen_free(g);
679 return 0;
680 } /* }}} format_json_notification */
681 #else
682 int format_json_notification(char *buffer, size_t buffer_size, /* {{{ */
683 notification_t const *n) {
684 ERROR("format_json_notification: Not available (requires libyajl).");
685 return ENOTSUP;
686 } /* }}} int format_json_notification */
687 #endif
689 /* vim: set sw=2 sts=2 et fdm=marker : */