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) {
73 status = rfc3339(buffer, sizeof(buffer), t);
78 return json_string(gen, buffer);
79 } /* }}} int json_time */
84 * "type": "library.googleapis.com/book",
86 * "/genre": "fiction",
88 * "/title": "The Old Man and the Sea"
92 static int format_gcm_resource(yajl_gen gen, sd_resource_t *res) /* {{{ */
96 yajl_gen_map_open(gen);
98 status = json_string(gen, "type") || json_string(gen, res->type);
102 if (res->labels_num != 0) {
105 status = json_string(gen, "labels");
109 yajl_gen_map_open(gen);
110 for (i = 0; i < res->labels_num; i++) {
111 status = json_string(gen, res->labels[i].key) ||
112 json_string(gen, res->labels[i].value);
116 yajl_gen_map_close(gen);
119 yajl_gen_map_close(gen);
121 } /* }}} int format_gcm_resource */
126 * // Union field, only one of the following:
127 * "int64Value": string,
128 * "doubleValue": number,
131 static int format_gcm_typed_value(yajl_gen gen, int ds_type,
136 yajl_gen_map_open(gen);
137 if (ds_type == DS_TYPE_GAUGE) {
140 status = json_string(gen, "doubleValue");
144 status = (int)yajl_gen_double(gen, (double)v.gauge);
145 if (status != yajl_gen_status_ok)
149 case DS_TYPE_COUNTER:
150 snprintf(integer, sizeof(integer), "%llu", v.counter);
153 snprintf(integer, sizeof(integer), "%" PRIi64, v.derive);
155 case DS_TYPE_ABSOLUTE:
156 snprintf(integer, sizeof(integer), "%" PRIu64, v.derive);
159 ERROR("format_gcm_typed_value: unknown value type %d.", ds_type);
163 int status = json_string(gen, "int64Value") || json_string(gen, integer);
168 yajl_gen_map_close(gen);
171 } /* }}} int format_gcm_typed_value */
180 static int format_metric_kind(yajl_gen gen, int ds_type) {
181 return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "GAUGE" : "CUMULATIVE");
191 static int format_value_type(yajl_gen gen, int ds_type) {
192 return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "DOUBLE" : "INT64");
195 static int metric_type(char *buffer, size_t buffer_size, data_set_t const *ds,
196 value_list_t const *vl, int ds_index) {
198 char const *ds_name = ds->ds[ds_index].name;
200 #define GCM_PREFIX "custom.googleapis.com/collectd/"
201 if ((ds_index != 0) || strcmp("value", ds_name) != 0) {
202 snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s_%s", vl->plugin, vl->type,
205 snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s", vl->plugin, vl->type);
208 char const *whitelist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
209 "abcdefghijklmnopqrstuvwxyz"
211 char *ptr = buffer + strlen(GCM_PREFIX);
213 while ((ok_len = strspn(ptr, whitelist)) != strlen(ptr)) {
219 } /* }}} int metric_type */
221 /* The metric type, including its DNS name prefix. The type is not URL-encoded.
222 * All user-defined custom metric types have the DNS name custom.googleapis.com.
223 * Metric types should use a natural hierarchical grouping. */
224 static int format_metric_type(yajl_gen gen, data_set_t const *ds,
225 value_list_t const *vl, int ds_index) {
227 char buffer[4 * DATA_MAX_NAME_LEN];
228 metric_type(buffer, sizeof(buffer), ds, vl, ds_index);
230 return json_string(gen, buffer);
231 } /* }}} int format_metric_type */
237 * "startTime": string,
240 static int format_time_interval(yajl_gen gen, int ds_type,
241 value_list_t const *vl) {
243 yajl_gen_map_open(gen);
245 int status = json_string(gen, "endTime") || json_time(gen, vl->time);
249 if (ds_type != DS_TYPE_GAUGE) {
250 cdtime_t start_time = 0;
251 if (uc_meta_data_get_unsigned_int(vl, "gcm:start_time", &start_time) != 0) {
252 start_time = vl->time;
253 uc_meta_data_add_unsigned_int(vl, "gcm:start_time", start_time);
256 int status = json_string(gen, "startTime") || json_time(gen, start_time);
261 yajl_gen_map_close(gen);
263 } /* }}} int format_time_interval */
269 * object(TimeInterval)
276 static int format_point(yajl_gen gen, data_set_t const *ds,
277 value_list_t const *vl, int ds_index) {
279 yajl_gen_map_open(gen);
281 int ds_type = ds->ds[ds_index].type;
283 int status = json_string(gen, "interval") ||
284 format_time_interval(gen, ds_type, vl) ||
285 json_string(gen, "value") ||
286 format_gcm_typed_value(gen, ds_type, vl->values[ds_index]);
290 yajl_gen_map_close(gen);
292 } /* }}} int format_point */
304 static int format_metric(yajl_gen gen, data_set_t const *ds,
305 value_list_t const *vl, int ds_index) {
307 yajl_gen_map_open(gen);
309 int status = json_string(gen, "type") ||
310 format_metric_type(gen, ds, vl, ds_index) ||
311 json_string(gen, "labels");
316 yajl_gen_map_open(gen);
317 status = json_string(gen, "host") || json_string(gen, vl->host) ||
318 json_string(gen, "plugin_instance") ||
319 json_string(gen, vl->plugin_instance) ||
320 json_string(gen, "type_instance") ||
321 json_string(gen, vl->type_instance);
325 yajl_gen_map_close(gen);
327 yajl_gen_map_close(gen);
329 } /* }}} int format_metric */
338 * object(MonitoredResource)
340 * "metricKind": enum(MetricKind),
341 * "valueType": enum(ValueType),
349 static int format_time_series(yajl_gen gen, data_set_t const *ds,
350 value_list_t const *vl, int ds_index,
351 sd_resource_t *res) {
353 yajl_gen_map_open(gen);
355 int ds_type = ds->ds[ds_index].type;
358 json_string(gen, "metric") || format_metric(gen, ds, vl, ds_index) ||
359 json_string(gen, "resource") || format_gcm_resource(gen, res) ||
360 json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) ||
361 json_string(gen, "valueType") || format_value_type(gen, ds_type) ||
362 json_string(gen, "points");
366 yajl_gen_array_open(gen);
367 if ((status = format_point(gen, ds, vl, ds_index)) != 0) {
370 yajl_gen_array_close(gen);
372 yajl_gen_map_close(gen);
374 } /* }}} int format_time_series */
386 static int sd_output_initialize(sd_output_t *out) /* {{{ */
388 yajl_gen_map_open(out->gen);
390 int status = json_string(out->gen, "timeSeries");
395 yajl_gen_array_open(out->gen);
397 } /* }}} int sd_output_initialize */
399 static int sd_output_finalize(sd_output_t *out) /* {{{ */
401 yajl_gen_array_close(out->gen);
402 yajl_gen_map_close(out->gen);
405 } /* }}} int sd_output_finalize */
407 static void sd_output_reset_staged(sd_output_t *out) /* {{{ */
411 while (c_avl_pick(out->staged, &key, &(void *){NULL}) == 0)
413 } /* }}} void sd_output_reset_staged */
415 sd_output_t *sd_output_create(sd_resource_t *res) /* {{{ */
417 sd_output_t *out = calloc(1, sizeof(*out));
423 out->gen = yajl_gen_alloc(/* funcs = */ NULL);
424 if (out->gen == NULL) {
425 sd_output_destroy(out);
429 out->staged = c_avl_create((void *)strcmp);
430 if (out->staged == NULL) {
431 sd_output_destroy(out);
435 out->metric_descriptors = c_avl_create((void *)strcmp);
436 if (out->metric_descriptors == NULL) {
437 sd_output_destroy(out);
441 sd_output_initialize(out);
444 } /* }}} sd_output_t *sd_output_create */
446 void sd_output_destroy(sd_output_t *out) /* {{{ */
451 if (out->metric_descriptors != NULL) {
453 while (c_avl_pick(out->metric_descriptors, &key, &(void *){NULL}) == 0) {
456 c_avl_destroy(out->metric_descriptors);
457 out->metric_descriptors = NULL;
460 if (out->staged != NULL) {
461 sd_output_reset_staged(out);
462 c_avl_destroy(out->staged);
466 if (out->gen != NULL) {
467 yajl_gen_free(out->gen);
471 if (out->res != NULL) {
472 sd_resource_destroy(out->res);
477 } /* }}} void sd_output_destroy */
479 int sd_output_add(sd_output_t *out, data_set_t const *ds,
480 value_list_t const *vl) /* {{{ */
482 char key[6 * DATA_MAX_NAME_LEN];
485 /* first, check that we have all appropriate metric descriptors. */
486 for (size_t i = 0; i < ds->ds_num; i++) {
487 char buffer[4 * DATA_MAX_NAME_LEN];
488 metric_type(buffer, sizeof(buffer), ds, vl, i);
490 if (c_avl_get(out->metric_descriptors, buffer, NULL) != 0) {
495 status = FORMAT_VL(key, sizeof(key), vl);
497 ERROR("sd_output_add: FORMAT_VL failed with status %d.", status);
501 if (c_avl_get(out->staged, key, NULL) == 0) {
505 for (size_t i = 0; i < ds->ds_num; i++) {
506 int status = format_time_series(out->gen, ds, vl, i, out->res);
508 ERROR("sd_output_add: format_time_series failed with status %d.", status);
513 c_avl_insert(out->staged, strdup(key), NULL);
515 size_t json_buffer_size = 0;
516 yajl_gen_get_buf(out->gen, &(unsigned char const *){NULL}, &json_buffer_size);
517 if (json_buffer_size > 65535)
521 } /* }}} int sd_output_add */
523 int sd_output_register_metric(sd_output_t *out, data_set_t const *ds,
524 value_list_t const *vl) {
526 for (size_t i = 0; i < ds->ds_num; i++) {
527 char buffer[4 * DATA_MAX_NAME_LEN];
528 metric_type(buffer, sizeof(buffer), ds, vl, i);
530 char *key = strdup(buffer);
531 int status = c_avl_insert(out->metric_descriptors, key, NULL);
539 } /* }}} int sd_output_register_metric */
541 char *sd_output_reset(sd_output_t *out) /* {{{ */
543 unsigned char const *json_buffer = NULL;
546 sd_output_finalize(out);
548 yajl_gen_get_buf(out->gen, &json_buffer, &(size_t){0});
549 ret = strdup((void const *)json_buffer);
551 sd_output_reset_staged(out);
553 yajl_gen_free(out->gen);
554 out->gen = yajl_gen_alloc(/* funcs = */ NULL);
556 sd_output_initialize(out);
559 } /* }}} char *sd_output_reset */
561 sd_resource_t *sd_resource_create(char const *type) /* {{{ */
565 res = malloc(sizeof(*res));
568 memset(res, 0, sizeof(*res));
570 res->type = strdup(type);
571 if (res->type == NULL) {
580 } /* }}} sd_resource_t *sd_resource_create */
582 void sd_resource_destroy(sd_resource_t *res) /* {{{ */
589 for (i = 0; i < res->labels_num; i++) {
590 sfree(res->labels[i].key);
591 sfree(res->labels[i].value);
596 } /* }}} void sd_resource_destroy */
598 int sd_resource_add_label(sd_resource_t *res, char const *key,
599 char const *value) /* {{{ */
603 if ((res == NULL) || (key == NULL) || (value == NULL))
606 l = realloc(res->labels, sizeof(*res->labels) * (res->labels_num + 1));
611 l = res->labels + res->labels_num;
613 l->key = strdup(key);
614 l->value = strdup(value);
615 if ((l->key == NULL) || (l->value == NULL)) {
623 } /* }}} int sd_resource_add_label */
629 * "valueType": enum(ValueType),
630 * "description": string,
633 static int format_label_descriptor(yajl_gen gen, char const *key) {
635 yajl_gen_map_open(gen);
637 int status = json_string(gen, "key") || json_string(gen, key) ||
638 json_string(gen, "valueType") || json_string(gen, "STRING");
643 yajl_gen_map_close(gen);
645 } /* }}} int format_label_descriptor */
654 * object(LabelDescriptor)
657 * "metricKind": enum(MetricKind),
658 * "valueType": enum(ValueType),
660 * "description": string,
661 * "displayName": string,
664 int sd_format_metric_descriptor(char *buffer, size_t buffer_size,
665 data_set_t const *ds, value_list_t const *vl,
668 yajl_gen gen = yajl_gen_alloc(/* funcs = */ NULL);
673 int ds_type = ds->ds[ds_index].type;
675 yajl_gen_map_open(gen);
678 json_string(gen, "type") || format_metric_type(gen, ds, vl, ds_index) ||
679 json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) ||
680 json_string(gen, "valueType") || format_value_type(gen, ds_type) ||
681 json_string(gen, "labels");
687 char const *labels[] = {"host", "plugin_instance", "type_instance"};
688 yajl_gen_array_open(gen);
690 for (size_t i = 0; i < STATIC_ARRAY_SIZE(labels); i++) {
691 int status = format_label_descriptor(gen, labels[i]);
698 yajl_gen_array_close(gen);
699 yajl_gen_map_close(gen);
701 unsigned char const *tmp = NULL;
702 yajl_gen_get_buf(gen, &tmp, &(size_t){0});
703 sstrncpy(buffer, (void const *)tmp, buffer_size);
707 } /* }}} int sd_format_metric_descriptor */
709 /* vim: set sw=2 sts=2 et fdm=marker : */