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 "plugin.h"
32 #include "common.h"
33 #include "utils_cache.h"
35 #if HAVE_LIBYAJL
36 #include <yajl/yajl_gen.h>
37 #endif
39 static int json_escape_string (char *buffer, size_t buffer_size, /* {{{ */
40 const char *string)
41 {
42 size_t dst_pos;
44 if ((buffer == NULL) || (string == NULL))
45 return (-EINVAL);
47 if (buffer_size < 3)
48 return (-ENOMEM);
50 dst_pos = 0;
52 #define BUFFER_ADD(c) do { \
53 if (dst_pos >= (buffer_size - 1)) { \
54 buffer[buffer_size - 1] = 0; \
55 return (-ENOMEM); \
56 } \
57 buffer[dst_pos] = (c); \
58 dst_pos++; \
59 } while (0)
61 /* Escape special characters */
62 BUFFER_ADD ('"');
63 for (size_t src_pos = 0; string[src_pos] != 0; src_pos++)
64 {
65 if ((string[src_pos] == '"')
66 || (string[src_pos] == '\\'))
67 {
68 BUFFER_ADD ('\\');
69 BUFFER_ADD (string[src_pos]);
70 }
71 else if (string[src_pos] <= 0x001F)
72 BUFFER_ADD ('?');
73 else
74 BUFFER_ADD (string[src_pos]);
75 } /* for */
76 BUFFER_ADD ('"');
77 buffer[dst_pos] = 0;
79 #undef BUFFER_ADD
81 return (0);
82 } /* }}} int json_escape_string */
84 static int values_to_json (char *buffer, size_t buffer_size, /* {{{ */
85 const data_set_t *ds, const value_list_t *vl, int store_rates)
86 {
87 size_t offset = 0;
88 gauge_t *rates = NULL;
90 memset (buffer, 0, buffer_size);
92 #define BUFFER_ADD(...) do { \
93 int status; \
94 status = ssnprintf (buffer + offset, buffer_size - offset, \
95 __VA_ARGS__); \
96 if (status < 1) \
97 { \
98 sfree(rates); \
99 return (-1); \
100 } \
101 else if (((size_t) status) >= (buffer_size - offset)) \
102 { \
103 sfree(rates); \
104 return (-ENOMEM); \
105 } \
106 else \
107 offset += ((size_t) status); \
108 } while (0)
110 BUFFER_ADD ("[");
111 for (size_t i = 0; i < ds->ds_num; i++)
112 {
113 if (i > 0)
114 BUFFER_ADD (",");
116 if (ds->ds[i].type == DS_TYPE_GAUGE)
117 {
118 if(isfinite (vl->values[i].gauge))
119 BUFFER_ADD (JSON_GAUGE_FORMAT, vl->values[i].gauge);
120 else
121 BUFFER_ADD ("null");
122 }
123 else if (store_rates)
124 {
125 if (rates == NULL)
126 rates = uc_get_rate (ds, vl);
127 if (rates == NULL)
128 {
129 WARNING ("utils_format_json: uc_get_rate failed.");
130 sfree(rates);
131 return (-1);
132 }
134 if(isfinite (rates[i]))
135 BUFFER_ADD (JSON_GAUGE_FORMAT, rates[i]);
136 else
137 BUFFER_ADD ("null");
138 }
139 else if (ds->ds[i].type == DS_TYPE_COUNTER)
140 BUFFER_ADD ("%llu", vl->values[i].counter);
141 else if (ds->ds[i].type == DS_TYPE_DERIVE)
142 BUFFER_ADD ("%"PRIi64, vl->values[i].derive);
143 else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
144 BUFFER_ADD ("%"PRIu64, vl->values[i].absolute);
145 else
146 {
147 ERROR ("format_json: Unknown data source type: %i",
148 ds->ds[i].type);
149 sfree (rates);
150 return (-1);
151 }
152 } /* for ds->ds_num */
153 BUFFER_ADD ("]");
155 #undef BUFFER_ADD
157 DEBUG ("format_json: values_to_json: buffer = %s;", buffer);
158 sfree(rates);
159 return (0);
160 } /* }}} int values_to_json */
162 static int dstypes_to_json (char *buffer, size_t buffer_size, /* {{{ */
163 const data_set_t *ds)
164 {
165 size_t offset = 0;
167 memset (buffer, 0, buffer_size);
169 #define BUFFER_ADD(...) do { \
170 int status; \
171 status = ssnprintf (buffer + offset, buffer_size - offset, \
172 __VA_ARGS__); \
173 if (status < 1) \
174 return (-1); \
175 else if (((size_t) status) >= (buffer_size - offset)) \
176 return (-ENOMEM); \
177 else \
178 offset += ((size_t) status); \
179 } while (0)
181 BUFFER_ADD ("[");
182 for (size_t i = 0; i < ds->ds_num; i++)
183 {
184 if (i > 0)
185 BUFFER_ADD (",");
187 BUFFER_ADD ("\"%s\"", DS_TYPE_TO_STRING (ds->ds[i].type));
188 } /* for ds->ds_num */
189 BUFFER_ADD ("]");
191 #undef BUFFER_ADD
193 DEBUG ("format_json: dstypes_to_json: buffer = %s;", buffer);
195 return (0);
196 } /* }}} int dstypes_to_json */
198 static int dsnames_to_json (char *buffer, size_t buffer_size, /* {{{ */
199 const data_set_t *ds)
200 {
201 size_t offset = 0;
203 memset (buffer, 0, buffer_size);
205 #define BUFFER_ADD(...) do { \
206 int status; \
207 status = ssnprintf (buffer + offset, buffer_size - offset, \
208 __VA_ARGS__); \
209 if (status < 1) \
210 return (-1); \
211 else if (((size_t) status) >= (buffer_size - offset)) \
212 return (-ENOMEM); \
213 else \
214 offset += ((size_t) status); \
215 } while (0)
217 BUFFER_ADD ("[");
218 for (size_t i = 0; i < ds->ds_num; i++)
219 {
220 if (i > 0)
221 BUFFER_ADD (",");
223 BUFFER_ADD ("\"%s\"", ds->ds[i].name);
224 } /* for ds->ds_num */
225 BUFFER_ADD ("]");
227 #undef BUFFER_ADD
229 DEBUG ("format_json: dsnames_to_json: buffer = %s;", buffer);
231 return (0);
232 } /* }}} int dsnames_to_json */
234 static int meta_data_keys_to_json (char *buffer, size_t buffer_size, /* {{{ */
235 meta_data_t *meta, char **keys, size_t keys_num)
236 {
237 size_t offset = 0;
238 int status;
240 buffer[0] = 0;
242 #define BUFFER_ADD(...) do { \
243 status = ssnprintf (buffer + offset, buffer_size - offset, \
244 __VA_ARGS__); \
245 if (status < 1) \
246 return (-1); \
247 else if (((size_t) status) >= (buffer_size - offset)) \
248 return (-ENOMEM); \
249 else \
250 offset += ((size_t) status); \
251 } while (0)
253 for (size_t i = 0; i < keys_num; ++i)
254 {
255 int type;
256 char *key = keys[i];
258 type = meta_data_type (meta, key);
259 if (type == MD_TYPE_STRING)
260 {
261 char *value = NULL;
262 if (meta_data_get_string (meta, key, &value) == 0)
263 {
264 char temp[512] = "";
266 status = json_escape_string (temp, sizeof (temp), value);
267 sfree (value);
268 if (status != 0)
269 return status;
271 BUFFER_ADD (",\"%s\":%s", key, temp);
272 }
273 }
274 else if (type == MD_TYPE_SIGNED_INT)
275 {
276 int64_t value = 0;
277 if (meta_data_get_signed_int (meta, key, &value) == 0)
278 BUFFER_ADD (",\"%s\":%"PRIi64, key, value);
279 }
280 else if (type == MD_TYPE_UNSIGNED_INT)
281 {
282 uint64_t value = 0;
283 if (meta_data_get_unsigned_int (meta, key, &value) == 0)
284 BUFFER_ADD (",\"%s\":%"PRIu64, key, value);
285 }
286 else if (type == MD_TYPE_DOUBLE)
287 {
288 double value = 0.0;
289 if (meta_data_get_double (meta, key, &value) == 0)
290 BUFFER_ADD (",\"%s\":%f", key, value);
291 }
292 else if (type == MD_TYPE_BOOLEAN)
293 {
294 _Bool value = 0;
295 if (meta_data_get_boolean (meta, key, &value) == 0)
296 BUFFER_ADD (",\"%s\":%s", key, value ? "true" : "false");
297 }
298 } /* for (keys) */
300 if (offset == 0)
301 return (ENOENT);
303 buffer[0] = '{'; /* replace leading ',' */
304 BUFFER_ADD ("}");
306 #undef BUFFER_ADD
308 return (0);
309 } /* }}} int meta_data_keys_to_json */
311 static int meta_data_to_json (char *buffer, size_t buffer_size, /* {{{ */
312 meta_data_t *meta)
313 {
314 char **keys = NULL;
315 size_t keys_num;
316 int status;
318 if ((buffer == NULL) || (buffer_size == 0) || (meta == NULL))
319 return (EINVAL);
321 status = meta_data_toc (meta, &keys);
322 if (status <= 0)
323 return (status);
324 keys_num = (size_t) status;
326 status = meta_data_keys_to_json (buffer, buffer_size, meta, keys, keys_num);
328 for (size_t i = 0; i < keys_num; ++i)
329 sfree (keys[i]);
330 sfree (keys);
332 return status;
333 } /* }}} int meta_data_to_json */
335 static int value_list_to_json (char *buffer, size_t buffer_size, /* {{{ */
336 const data_set_t *ds, const value_list_t *vl, int store_rates)
337 {
338 char temp[512];
339 size_t offset = 0;
340 int status;
342 memset (buffer, 0, buffer_size);
344 #define BUFFER_ADD(...) do { \
345 status = ssnprintf (buffer + offset, buffer_size - offset, \
346 __VA_ARGS__); \
347 if (status < 1) \
348 return (-1); \
349 else if (((size_t) status) >= (buffer_size - offset)) \
350 return (-ENOMEM); \
351 else \
352 offset += ((size_t) status); \
353 } while (0)
355 /* All value lists have a leading comma. The first one will be replaced with
356 * a square bracket in `format_json_finalize'. */
357 BUFFER_ADD (",{");
359 status = values_to_json (temp, sizeof (temp), ds, vl, store_rates);
360 if (status != 0)
361 return (status);
362 BUFFER_ADD ("\"values\":%s", temp);
364 status = dstypes_to_json (temp, sizeof (temp), ds);
365 if (status != 0)
366 return (status);
367 BUFFER_ADD (",\"dstypes\":%s", temp);
369 status = dsnames_to_json (temp, sizeof (temp), ds);
370 if (status != 0)
371 return (status);
372 BUFFER_ADD (",\"dsnames\":%s", temp);
374 BUFFER_ADD (",\"time\":%.3f", CDTIME_T_TO_DOUBLE (vl->time));
375 BUFFER_ADD (",\"interval\":%.3f", CDTIME_T_TO_DOUBLE (vl->interval));
377 #define BUFFER_ADD_KEYVAL(key, value) do { \
378 status = json_escape_string (temp, sizeof (temp), (value)); \
379 if (status != 0) \
380 return (status); \
381 BUFFER_ADD (",\"%s\":%s", (key), temp); \
382 } while (0)
384 BUFFER_ADD_KEYVAL ("host", vl->host);
385 BUFFER_ADD_KEYVAL ("plugin", vl->plugin);
386 BUFFER_ADD_KEYVAL ("plugin_instance", vl->plugin_instance);
387 BUFFER_ADD_KEYVAL ("type", vl->type);
388 BUFFER_ADD_KEYVAL ("type_instance", vl->type_instance);
390 if (vl->meta != NULL)
391 {
392 char meta_buffer[buffer_size];
393 memset (meta_buffer, 0, sizeof (meta_buffer));
394 status = meta_data_to_json (meta_buffer, sizeof (meta_buffer), vl->meta);
395 if (status != 0)
396 return (status);
398 BUFFER_ADD (",\"meta\":%s", meta_buffer);
399 } /* if (vl->meta != NULL) */
401 BUFFER_ADD ("}");
403 #undef BUFFER_ADD_KEYVAL
404 #undef BUFFER_ADD
406 DEBUG ("format_json: value_list_to_json: buffer = %s;", buffer);
408 return (0);
409 } /* }}} int value_list_to_json */
411 static int format_json_value_list_nocheck (char *buffer, /* {{{ */
412 size_t *ret_buffer_fill, size_t *ret_buffer_free,
413 const data_set_t *ds, const value_list_t *vl,
414 int store_rates, size_t temp_size)
415 {
416 char temp[temp_size];
417 int status;
419 status = value_list_to_json (temp, sizeof (temp), ds, vl, store_rates);
420 if (status != 0)
421 return (status);
422 temp_size = strlen (temp);
424 memcpy (buffer + (*ret_buffer_fill), temp, temp_size + 1);
425 (*ret_buffer_fill) += temp_size;
426 (*ret_buffer_free) -= temp_size;
428 return (0);
429 } /* }}} int format_json_value_list_nocheck */
431 int format_json_initialize (char *buffer, /* {{{ */
432 size_t *ret_buffer_fill, size_t *ret_buffer_free)
433 {
434 size_t buffer_fill;
435 size_t buffer_free;
437 if ((buffer == NULL) || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL))
438 return (-EINVAL);
440 buffer_fill = *ret_buffer_fill;
441 buffer_free = *ret_buffer_free;
443 buffer_free = buffer_fill + buffer_free;
444 buffer_fill = 0;
446 if (buffer_free < 3)
447 return (-ENOMEM);
449 memset (buffer, 0, buffer_free);
450 *ret_buffer_fill = buffer_fill;
451 *ret_buffer_free = buffer_free;
453 return (0);
454 } /* }}} int format_json_initialize */
456 int format_json_finalize (char *buffer, /* {{{ */
457 size_t *ret_buffer_fill, size_t *ret_buffer_free)
458 {
459 size_t pos;
461 if ((buffer == NULL) || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL))
462 return (-EINVAL);
464 if (*ret_buffer_free < 2)
465 return (-ENOMEM);
467 /* Replace the leading comma added in `value_list_to_json' with a square
468 * bracket. */
469 if (buffer[0] != ',')
470 return (-EINVAL);
471 buffer[0] = '[';
473 pos = *ret_buffer_fill;
474 buffer[pos] = ']';
475 buffer[pos+1] = 0;
477 (*ret_buffer_fill)++;
478 (*ret_buffer_free)--;
480 return (0);
481 } /* }}} int format_json_finalize */
483 int format_json_value_list (char *buffer, /* {{{ */
484 size_t *ret_buffer_fill, size_t *ret_buffer_free,
485 const data_set_t *ds, const value_list_t *vl, int store_rates)
486 {
487 if ((buffer == NULL)
488 || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL)
489 || (ds == NULL) || (vl == NULL))
490 return (-EINVAL);
492 if (*ret_buffer_free < 3)
493 return (-ENOMEM);
495 return (format_json_value_list_nocheck (buffer,
496 ret_buffer_fill, ret_buffer_free, ds, vl,
497 store_rates, (*ret_buffer_free) - 2));
498 } /* }}} int format_json_value_list */
500 #if HAVE_LIBYAJL
501 static int json_add_string (yajl_gen g, char const *str) /* {{{ */
502 {
503 if (str == NULL)
504 return (int) yajl_gen_null (g);
506 return (int) yajl_gen_string (g, (unsigned char const *) str, (unsigned int) strlen (str));
507 } /* }}} int json_add_string */
509 #define JSON_ADD(g, str) do { \
510 yajl_gen_status status = json_add_string (g, str); \
511 if (status != yajl_gen_status_ok) { return -1; } \
512 } while (0)
514 #define JSON_ADDF(g, format, ...) do { \
515 char *str = ssnprintf_alloc (format, __VA_ARGS__); \
516 yajl_gen_status status = json_add_string (g, str); \
517 free (str); \
518 if (status != yajl_gen_status_ok) { return -1; } \
519 } while (0)
521 static int format_json_meta (yajl_gen g, notification_meta_t *meta) /* {{{ */
522 {
523 if (meta == NULL)
524 return 0;
526 JSON_ADD (g, meta->name);
527 switch (meta->type)
528 {
529 case NM_TYPE_STRING:
530 JSON_ADD (g, meta->nm_value.nm_string);
531 break;
532 case NM_TYPE_SIGNED_INT:
533 JSON_ADDF (g, "%"PRIi64, meta->nm_value.nm_signed_int);
534 break;
535 case NM_TYPE_UNSIGNED_INT:
536 JSON_ADDF (g, "%"PRIu64, meta->nm_value.nm_unsigned_int);
537 break;
538 case NM_TYPE_DOUBLE:
539 JSON_ADDF (g, JSON_GAUGE_FORMAT, meta->nm_value.nm_double);
540 break;
541 case NM_TYPE_BOOLEAN:
542 JSON_ADD (g, meta->nm_value.nm_boolean ? "true" : "false");
543 break;
544 default:
545 ERROR ("format_json_meta: unknown meta data type %d (name \"%s\")", meta->type, meta->name);
546 yajl_gen_null (g);
547 }
549 return format_json_meta (g, meta->next);
550 } /* }}} int format_json_meta */
552 static int format_time (yajl_gen g, cdtime_t t) /* {{{ */
553 {
554 char buffer[RFC3339NANO_SIZE] = "";
556 if (rfc3339nano (buffer, sizeof (buffer), t) != 0)
557 return -1;
559 JSON_ADD (g, buffer);
560 return 0;
561 } /* }}} int format_time */
563 static int format_alert (yajl_gen g, notification_t const *n) /* {{{ */
564 {
565 yajl_gen_array_open (g);
566 yajl_gen_map_open (g); /* BEGIN alert */
568 /*
569 * labels
570 */
571 JSON_ADD (g, "labels");
572 yajl_gen_map_open (g); /* BEGIN labels */
574 JSON_ADD (g, "alertname");
575 if (strncmp (n->plugin, n->type, strlen (n->plugin)) == 0)
576 JSON_ADDF (g, "collectd_%s", n->type);
577 else
578 JSON_ADDF (g, "collectd_%s_%s", n->plugin, n->type);
580 JSON_ADD (g, "instance");
581 JSON_ADD (g, n->host);
583 /* mangling of plugin instance and type instance into labels is copied from
584 * the Prometheus collectd exporter. */
585 if (strlen (n->plugin_instance) > 0)
586 {
587 JSON_ADD (g, n->plugin);
588 JSON_ADD (g, n->plugin_instance);
589 }
590 if (strlen (n->type_instance) > 0)
591 {
592 if (strlen (n->plugin_instance) > 0)
593 JSON_ADD (g, "type");
594 else
595 JSON_ADD (g, n->plugin);
596 JSON_ADD (g, n->type_instance);
597 }
599 JSON_ADD (g, "severity");
600 JSON_ADD (g, (n->severity == NOTIF_FAILURE) ? "FAILURE"
601 : (n->severity == NOTIF_WARNING) ? "WARNING"
602 : (n->severity == NOTIF_OKAY) ? "OKAY"
603 : "UNKNOWN");
605 JSON_ADD (g, "service");
606 JSON_ADD (g, "collectd");
608 yajl_gen_map_close (g); /* END labels */
610 /*
611 * annotations
612 */
613 JSON_ADD (g, "annotations");
614 yajl_gen_map_open (g); /* BEGIN annotations */
616 JSON_ADD (g, "summary");
617 JSON_ADD (g, n->message);
619 if (format_json_meta (g, n->meta) != 0)
620 return -1;
622 yajl_gen_map_close (g); /* END annotations */
624 JSON_ADD (g, "startsAt");
625 format_time (g, n->time);
627 yajl_gen_map_close (g); /* END alert */
628 yajl_gen_array_close (g);
630 return 0;
631 } /* }}} format_alert */
633 /*
634 * Format (prometheus/alertmanager v1):
635 *
636 * [{
637 * "labels": {
638 * "alertname": "collectd_cpu",
639 * "instance": "host.example.com",
640 * "severity": "FAILURE",
641 * "service": "collectd",
642 * "cpu": "0",
643 * "type": "wait"
644 * },
645 * "annotations": {
646 * "summary": "...",
647 * // meta
648 * },
649 * "startsAt": <rfc3339 time>,
650 * "endsAt": <rfc3339 time>, // not used
651 * }]
652 */
653 int format_json_notification (char *buffer, size_t buffer_size, /* {{{ */
654 notification_t const *n)
655 {
656 yajl_gen g;
657 unsigned char const *out;
658 size_t unused_out_len;
660 if ((buffer == NULL) || (n == NULL))
661 return EINVAL;
663 g = yajl_gen_alloc (NULL);
664 if (g == NULL)
665 return -1;
667 #if COLLECT_DEBUG
668 yajl_gen_config (g, yajl_gen_beautify);
669 yajl_gen_config (g, yajl_gen_validate_utf8);
670 #endif
672 if (format_alert (g, n) != 0)
673 {
674 yajl_gen_clear (g);
675 yajl_gen_free (g);
676 return -1;
677 }
679 /* copy to output buffer */
680 yajl_gen_get_buf (g, &out, &unused_out_len);
681 sstrncpy (buffer, (void *) out, buffer_size);
683 yajl_gen_clear (g);
684 yajl_gen_free (g);
685 return 0;
686 } /* }}} format_json_notification */
687 #else
688 int format_json_notification (char *buffer, size_t buffer_size, /* {{{ */
689 notification_t const *n)
690 {
691 ERROR ("format_json_notification: Not available (requires libyajl).");
692 return ENOTSUP;
693 } /* }}} int format_json_notification */
694 #endif
696 /* vim: set sw=2 sts=2 et fdm=marker : */