2 * collectd - src/utils_format_stackdriver.c
5 * Copyright (C) 2017 Florian Forster
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 * Florian Forster <octo at collectd.org>
25 #include "utils_format_stackdriver.h"
29 #include "utils_avltree.h"
30 #include "utils_cache.h"
31 #include "utils_time.h"
33 #include <yajl/yajl_gen.h>
34 #include <yajl/yajl_parse.h>
35 #if HAVE_YAJL_YAJL_VERSION_H
36 #include <yajl/yajl_version.h>
43 c_avl_tree_t *metric_descriptors;
50 typedef struct sd_label_s sd_label_t;
52 struct sd_resource_s {
59 static int json_string(yajl_gen gen, char const *s) /* {{{ */
61 yajl_gen_status status =
62 yajl_gen_string(gen, (unsigned char const *)s, strlen(s));
63 if (status != yajl_gen_status_ok)
67 } /* }}} int json_string */
69 static int json_time(yajl_gen gen, cdtime_t t) {
72 size_t status = rfc3339(buffer, sizeof(buffer), t);
77 return json_string(gen, buffer);
78 } /* }}} int json_time */
83 * "type": "library.googleapis.com/book",
85 * "/genre": "fiction",
87 * "/title": "The Old Man and the Sea"
91 static int format_gcm_resource(yajl_gen gen, sd_resource_t *res) /* {{{ */
93 yajl_gen_map_open(gen);
95 int status = json_string(gen, "type") || json_string(gen, res->type);
99 if (res->labels_num != 0) {
100 status = json_string(gen, "labels");
104 yajl_gen_map_open(gen);
105 for (size_t i = 0; i < res->labels_num; i++) {
106 status = json_string(gen, res->labels[i].key) ||
107 json_string(gen, res->labels[i].value);
111 yajl_gen_map_close(gen);
114 yajl_gen_map_close(gen);
116 } /* }}} int format_gcm_resource */
121 * // Union field, only one of the following:
122 * "int64Value": string,
123 * "doubleValue": number,
126 static int format_gcm_typed_value(yajl_gen gen, int ds_type,
131 yajl_gen_map_open(gen);
132 if (ds_type == DS_TYPE_GAUGE) {
133 int status = json_string(gen, "doubleValue");
137 status = (int)yajl_gen_double(gen, (double)v.gauge);
138 if (status != yajl_gen_status_ok)
142 case DS_TYPE_COUNTER:
143 snprintf(integer, sizeof(integer), "%llu", v.counter);
146 snprintf(integer, sizeof(integer), "%" PRIi64, v.derive);
148 case DS_TYPE_ABSOLUTE:
149 snprintf(integer, sizeof(integer), "%" PRIu64, v.derive);
152 ERROR("format_gcm_typed_value: unknown value type %d.", ds_type);
156 int status = json_string(gen, "int64Value") || json_string(gen, integer);
161 yajl_gen_map_close(gen);
164 } /* }}} int format_gcm_typed_value */
173 static int format_metric_kind(yajl_gen gen, int ds_type) {
174 return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "GAUGE" : "CUMULATIVE");
184 static int format_value_type(yajl_gen gen, int ds_type) {
185 return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "DOUBLE" : "INT64");
188 static int metric_type(char *buffer, size_t buffer_size, data_set_t const *ds,
189 value_list_t const *vl, int ds_index) {
191 char const *ds_name = ds->ds[ds_index].name;
193 #define GCM_PREFIX "custom.googleapis.com/collectd/"
194 if ((ds_index != 0) || strcmp("value", ds_name) != 0) {
195 snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s_%s", vl->plugin, vl->type,
198 snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s", vl->plugin, vl->type);
201 char const *whitelist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
202 "abcdefghijklmnopqrstuvwxyz"
204 char *ptr = buffer + strlen(GCM_PREFIX);
206 while ((ok_len = strspn(ptr, whitelist)) != strlen(ptr)) {
212 } /* }}} int metric_type */
214 /* The metric type, including its DNS name prefix. The type is not URL-encoded.
215 * All user-defined custom metric types have the DNS name custom.googleapis.com.
216 * Metric types should use a natural hierarchical grouping. */
217 static int format_metric_type(yajl_gen gen, data_set_t const *ds,
218 value_list_t const *vl, int ds_index) {
220 char buffer[4 * DATA_MAX_NAME_LEN];
221 metric_type(buffer, sizeof(buffer), ds, vl, ds_index);
223 return json_string(gen, buffer);
224 } /* }}} int format_metric_type */
230 * "startTime": string,
233 static int format_time_interval(yajl_gen gen, int ds_type,
234 value_list_t const *vl) {
236 yajl_gen_map_open(gen);
238 int status = json_string(gen, "endTime") || json_time(gen, vl->time);
242 if (ds_type != DS_TYPE_GAUGE) {
243 cdtime_t start_time = 0;
244 if (uc_meta_data_get_unsigned_int(vl, "gcm:start_time", &start_time) != 0) {
245 start_time = vl->time;
246 uc_meta_data_add_unsigned_int(vl, "gcm:start_time", start_time);
249 int status = json_string(gen, "startTime") || json_time(gen, start_time);
254 yajl_gen_map_close(gen);
256 } /* }}} int format_time_interval */
262 * object(TimeInterval)
269 static int format_point(yajl_gen gen, data_set_t const *ds,
270 value_list_t const *vl, int ds_index) {
272 yajl_gen_map_open(gen);
274 int ds_type = ds->ds[ds_index].type;
276 int status = json_string(gen, "interval") ||
277 format_time_interval(gen, ds_type, vl) ||
278 json_string(gen, "value") ||
279 format_gcm_typed_value(gen, ds_type, vl->values[ds_index]);
283 yajl_gen_map_close(gen);
285 } /* }}} int format_point */
297 static int format_metric(yajl_gen gen, data_set_t const *ds,
298 value_list_t const *vl, int ds_index) {
300 yajl_gen_map_open(gen);
302 int status = json_string(gen, "type") ||
303 format_metric_type(gen, ds, vl, ds_index) ||
304 json_string(gen, "labels");
309 yajl_gen_map_open(gen);
310 status = json_string(gen, "host") || json_string(gen, vl->host) ||
311 json_string(gen, "plugin_instance") ||
312 json_string(gen, vl->plugin_instance) ||
313 json_string(gen, "type_instance") ||
314 json_string(gen, vl->type_instance);
318 yajl_gen_map_close(gen);
320 yajl_gen_map_close(gen);
322 } /* }}} int format_metric */
331 * object(MonitoredResource)
333 * "metricKind": enum(MetricKind),
334 * "valueType": enum(ValueType),
342 static int format_time_series(yajl_gen gen, data_set_t const *ds,
343 value_list_t const *vl, int ds_index,
344 sd_resource_t *res) {
346 yajl_gen_map_open(gen);
348 int ds_type = ds->ds[ds_index].type;
351 json_string(gen, "metric") || format_metric(gen, ds, vl, ds_index) ||
352 json_string(gen, "resource") || format_gcm_resource(gen, res) ||
353 json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) ||
354 json_string(gen, "valueType") || format_value_type(gen, ds_type) ||
355 json_string(gen, "points");
359 yajl_gen_array_open(gen);
360 if ((status = format_point(gen, ds, vl, ds_index)) != 0) {
363 yajl_gen_array_close(gen);
365 yajl_gen_map_close(gen);
367 } /* }}} int format_time_series */
379 static int sd_output_initialize(sd_output_t *out) /* {{{ */
381 yajl_gen_map_open(out->gen);
383 int status = json_string(out->gen, "timeSeries");
388 yajl_gen_array_open(out->gen);
390 } /* }}} int sd_output_initialize */
392 static int sd_output_finalize(sd_output_t *out) /* {{{ */
394 yajl_gen_array_close(out->gen);
395 yajl_gen_map_close(out->gen);
398 } /* }}} int sd_output_finalize */
400 static void sd_output_reset_staged(sd_output_t *out) /* {{{ */
404 while (c_avl_pick(out->staged, &key, &(void *){NULL}) == 0)
406 } /* }}} void sd_output_reset_staged */
408 sd_output_t *sd_output_create(sd_resource_t *res) /* {{{ */
410 sd_output_t *out = calloc(1, sizeof(*out));
416 out->gen = yajl_gen_alloc(/* funcs = */ NULL);
417 if (out->gen == NULL) {
418 sd_output_destroy(out);
422 out->staged = c_avl_create((void *)strcmp);
423 if (out->staged == NULL) {
424 sd_output_destroy(out);
428 out->metric_descriptors = c_avl_create((void *)strcmp);
429 if (out->metric_descriptors == NULL) {
430 sd_output_destroy(out);
434 sd_output_initialize(out);
437 } /* }}} sd_output_t *sd_output_create */
439 void sd_output_destroy(sd_output_t *out) /* {{{ */
444 if (out->metric_descriptors != NULL) {
446 while (c_avl_pick(out->metric_descriptors, &key, &(void *){NULL}) == 0) {
449 c_avl_destroy(out->metric_descriptors);
450 out->metric_descriptors = NULL;
453 if (out->staged != NULL) {
454 sd_output_reset_staged(out);
455 c_avl_destroy(out->staged);
459 if (out->gen != NULL) {
460 yajl_gen_free(out->gen);
464 if (out->res != NULL) {
465 sd_resource_destroy(out->res);
470 } /* }}} void sd_output_destroy */
472 int sd_output_add(sd_output_t *out, data_set_t const *ds,
473 value_list_t const *vl) /* {{{ */
475 /* first, check that we have all appropriate metric descriptors. */
476 for (size_t i = 0; i < ds->ds_num; i++) {
477 char buffer[4 * DATA_MAX_NAME_LEN];
478 metric_type(buffer, sizeof(buffer), ds, vl, i);
480 if (c_avl_get(out->metric_descriptors, buffer, NULL) != 0) {
485 char key[6 * DATA_MAX_NAME_LEN];
486 int status = FORMAT_VL(key, sizeof(key), vl);
488 ERROR("sd_output_add: FORMAT_VL failed with status %d.", status);
492 if (c_avl_get(out->staged, key, NULL) == 0) {
496 for (size_t i = 0; i < ds->ds_num; i++) {
497 int status = format_time_series(out->gen, ds, vl, i, out->res);
499 ERROR("sd_output_add: format_time_series failed with status %d.", status);
504 c_avl_insert(out->staged, strdup(key), NULL);
506 size_t json_buffer_size = 0;
507 yajl_gen_get_buf(out->gen, &(unsigned char const *){NULL}, &json_buffer_size);
508 if (json_buffer_size > 65535)
512 } /* }}} int sd_output_add */
514 int sd_output_register_metric(sd_output_t *out, data_set_t const *ds,
515 value_list_t const *vl) {
517 for (size_t i = 0; i < ds->ds_num; i++) {
518 char buffer[4 * DATA_MAX_NAME_LEN];
519 metric_type(buffer, sizeof(buffer), ds, vl, i);
521 char *key = strdup(buffer);
522 int status = c_avl_insert(out->metric_descriptors, key, NULL);
530 } /* }}} int sd_output_register_metric */
532 char *sd_output_reset(sd_output_t *out) /* {{{ */
534 sd_output_finalize(out);
536 unsigned char const *json_buffer = NULL;
537 yajl_gen_get_buf(out->gen, &json_buffer, &(size_t){0});
538 char *ret = strdup((void const *)json_buffer);
540 sd_output_reset_staged(out);
542 yajl_gen_free(out->gen);
543 out->gen = yajl_gen_alloc(/* funcs = */ NULL);
545 sd_output_initialize(out);
548 } /* }}} char *sd_output_reset */
550 sd_resource_t *sd_resource_create(char const *type) /* {{{ */
552 sd_resource_t *res = malloc(sizeof(*res));
555 memset(res, 0, sizeof(*res));
557 res->type = strdup(type);
558 if (res->type == NULL) {
567 } /* }}} sd_resource_t *sd_resource_create */
569 void sd_resource_destroy(sd_resource_t *res) /* {{{ */
574 for (size_t i = 0; i < res->labels_num; i++) {
575 sfree(res->labels[i].key);
576 sfree(res->labels[i].value);
581 } /* }}} void sd_resource_destroy */
583 int sd_resource_add_label(sd_resource_t *res, char const *key,
584 char const *value) /* {{{ */
586 if ((res == NULL) || (key == NULL) || (value == NULL))
590 realloc(res->labels, sizeof(*res->labels) * (res->labels_num + 1));
595 l = res->labels + res->labels_num;
597 l->key = strdup(key);
598 l->value = strdup(value);
599 if ((l->key == NULL) || (l->value == NULL)) {
607 } /* }}} int sd_resource_add_label */
613 * "valueType": enum(ValueType),
614 * "description": string,
617 static int format_label_descriptor(yajl_gen gen, char const *key) {
619 yajl_gen_map_open(gen);
621 int status = json_string(gen, "key") || json_string(gen, key) ||
622 json_string(gen, "valueType") || json_string(gen, "STRING");
627 yajl_gen_map_close(gen);
629 } /* }}} int format_label_descriptor */
638 * object(LabelDescriptor)
641 * "metricKind": enum(MetricKind),
642 * "valueType": enum(ValueType),
644 * "description": string,
645 * "displayName": string,
648 int sd_format_metric_descriptor(char *buffer, size_t buffer_size,
649 data_set_t const *ds, value_list_t const *vl,
652 yajl_gen gen = yajl_gen_alloc(/* funcs = */ NULL);
657 int ds_type = ds->ds[ds_index].type;
659 yajl_gen_map_open(gen);
662 json_string(gen, "type") || format_metric_type(gen, ds, vl, ds_index) ||
663 json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) ||
664 json_string(gen, "valueType") || format_value_type(gen, ds_type) ||
665 json_string(gen, "labels");
671 char const *labels[] = {"host", "plugin_instance", "type_instance"};
672 yajl_gen_array_open(gen);
674 for (size_t i = 0; i < STATIC_ARRAY_SIZE(labels); i++) {
675 int status = format_label_descriptor(gen, labels[i]);
682 yajl_gen_array_close(gen);
683 yajl_gen_map_close(gen);
685 unsigned char const *tmp = NULL;
686 yajl_gen_get_buf(gen, &tmp, &(size_t){0});
687 sstrncpy(buffer, (void const *)tmp, buffer_size);
691 } /* }}} int sd_format_metric_descriptor */