From: Florian Forster Date: Sat, 20 Jun 2015 13:54:54 +0000 (+0200) Subject: src/utils_format_gcm.[ch]: Implementation of the Stackdriver Monitoring API v3. X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=949a964dc749cf2fdd2cfff4026a5c8ad5007c45;p=collectd.git src/utils_format_gcm.[ch]: Implementation of the Stackdriver Monitoring API v3. Docs: https://cloud.google.com/monitoring/api/ref_v3/rest/ --- diff --git a/Makefile.am b/Makefile.am index a7e2f4d5..aa8c59f9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -611,6 +611,29 @@ libgce_la_LIBADD = \ $(BUILD_WITH_LIBCURL_LIBS) endif +if BUILD_WITH_LIBYAJL +noinst_LTLIBRARIES += libformat_gcm.la +libformat_gcm_la_SOURCES = \ + src/utils_format_gcm.c \ + src/utils_format_gcm.h +libformat_gcm_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(BUILD_WITH_LIBYAJL_CPPFLAGS) +libformat_gcm_la_LIBADD = \ + libavltree.la \ + $(BUILD_WITH_LIBSSL_LIBS) \ + $(BUILD_WITH_LIBYAJL_LIBS) + +check_PROGRAMS += test_format_gcm +TESTS += test_format_gcm +test_format_gcm_SOURCES = \ + utils_format_gcm_test.c \ + testing.h +test_format_gcm_LDADD = \ + libformat_gcm.la \ + daemon/libplugin_mock.la \ + -lm +endif if BUILD_PLUGIN_AGGREGATION pkglib_LTLIBRARIES += aggregation.la diff --git a/src/daemon/utils_cache_mock.c b/src/daemon/utils_cache_mock.c index 1495a803..0b8c997b 100644 --- a/src/daemon/utils_cache_mock.c +++ b/src/daemon/utils_cache_mock.c @@ -27,6 +27,8 @@ #include "utils_cache.h" #include +#include + gauge_t *uc_get_rate(__attribute__((unused)) data_set_t const *ds, __attribute__((unused)) value_list_t const *vl) { errno = ENOTSUP; @@ -46,3 +48,13 @@ int uc_get_value_by_name(const char *name, value_t **ret_values, size_t *ret_values_num) { return ENOTSUP; } + +int uc_meta_data_get_unsigned_int(const value_list_t *vl, const char *key, + uint64_t *value) { + return -ENOENT; +} + +int uc_meta_data_add_unsigned_int(const value_list_t *vl, const char *key, + uint64_t value) { + return 0; +} diff --git a/src/utils_format_gcm.c b/src/utils_format_gcm.c new file mode 100644 index 00000000..0c60ef0c --- /dev/null +++ b/src/utils_format_gcm.c @@ -0,0 +1,710 @@ +/** + * collectd - src/utils_format_gcm.c + * ISC license + * + * Copyright (C) 2017 Florian Forster + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: + * Florian Forster + **/ + +#include "collectd.h" + +#include "utils_format_gcm.h" + +#include "common.h" +#include "plugin.h" +#include "utils_avltree.h" +#include "utils_cache.h" +#include "utils_time.h" + +#include +#include +#if HAVE_YAJL_YAJL_VERSION_H +#include +#endif + +struct gcm_output_s { + gcm_resource_t *res; + yajl_gen gen; + c_avl_tree_t *staged; + c_avl_tree_t *metric_descriptors; +}; + +struct gcm_label_s { + char *key; + char *value; +}; +typedef struct gcm_label_s gcm_label_t; + +struct gcm_resource_s { + char *type; + + gcm_label_t *labels; + size_t labels_num; +}; + +static int json_string(yajl_gen gen, char const *s) /* {{{ */ +{ + yajl_gen_status status = + yajl_gen_string(gen, (unsigned char const *)s, strlen(s)); + if (status != yajl_gen_status_ok) + return (int)status; + + return 0; +} /* }}} int json_string */ + +static int json_time(yajl_gen gen, cdtime_t t) { + char buffer[64]; + size_t status; + + status = rfc3339(buffer, sizeof(buffer), t); + if (status != 0) { + return status; + } + + return json_string(gen, buffer); +} /* }}} int json_time */ + +/* MonitoredResource + * + * { + * "type": "library.googleapis.com/book", + * "labels": { + * "/genre": "fiction", + * "/media": "paper" + * "/title": "The Old Man and the Sea" + * } + * } + */ +static int format_gcm_resource(yajl_gen gen, gcm_resource_t *res) /* {{{ */ +{ + int status; + + yajl_gen_map_open(gen); + + status = json_string(gen, "type") || json_string(gen, res->type); + if (status != 0) + return status; + + if (res->labels_num != 0) { + size_t i; + + status = json_string(gen, "labels"); + if (status != 0) + return status; + + yajl_gen_map_open(gen); + for (i = 0; i < res->labels_num; i++) { + status = json_string(gen, res->labels[i].key) || + json_string(gen, res->labels[i].value); + if (status != 0) + return status; + } + yajl_gen_map_close(gen); + } + + yajl_gen_map_close(gen); + return 0; +} /* }}} int format_gcm_resource */ + +/* TypedValue + * + * { + * // Union field, only one of the following: + * "int64Value": string, + * "doubleValue": number, + * } + */ +static int format_gcm_typed_value(yajl_gen gen, int ds_type, + value_t v) /* {{{ */ +{ + char integer[21]; + + yajl_gen_map_open(gen); + if (ds_type == DS_TYPE_GAUGE) { + int status; + + status = json_string(gen, "doubleValue"); + if (status != 0) + return status; + + status = (int)yajl_gen_double(gen, (double)v.gauge); + if (status != yajl_gen_status_ok) + return status; + } else { + switch (ds_type) { + case DS_TYPE_COUNTER: + snprintf(integer, sizeof(integer), "%llu", v.counter); + break; + case DS_TYPE_DERIVE: + snprintf(integer, sizeof(integer), "%" PRIi64, v.derive); + break; + case DS_TYPE_ABSOLUTE: + snprintf(integer, sizeof(integer), "%" PRIu64, v.derive); + break; + default: + ERROR("format_gcm_typed_value: unknown value type %d.", ds_type); + return EINVAL; + } + + int status = json_string(gen, "int64Value") || json_string(gen, integer); + if (status != 0) { + return status; + } + } + yajl_gen_map_close(gen); + + return 0; +} /* }}} int format_gcm_typed_value */ + +/* MetricKind + * + * enum( + * "CUMULATIVE", + * "GAUGE" + * ) +*/ +static int format_metric_kind(yajl_gen gen, int ds_type) { + return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "GAUGE" : "CUMULATIVE"); +} + +/* ValueType + * + * enum( + * "DOUBLE", + * "INT64" + * ) +*/ +static int format_value_type(yajl_gen gen, int ds_type) { + return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "DOUBLE" : "INT64"); +} + +static int metric_type(char *buffer, size_t buffer_size, data_set_t const *ds, + value_list_t const *vl, int ds_index) { + /* {{{ */ + char const *ds_name = ds->ds[ds_index].name; + +#define GCM_PREFIX "custom.googleapis.com/collectd/" + if ((ds_index != 0) || strcmp("value", ds_name) != 0) { + snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s_%s", vl->plugin, vl->type, + ds_name); + } else { + snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s", vl->plugin, vl->type); + } + + char const *whitelist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789_/"; + char *ptr = buffer + strlen(GCM_PREFIX); + size_t ok_len; + while ((ok_len = strspn(ptr, whitelist)) != strlen(ptr)) { + ptr[ok_len] = '_'; + ptr += ok_len; + } + + return 0; +} /* }}} int metric_type */ + +/* The metric type, including its DNS name prefix. The type is not URL-encoded. + * All user-defined custom metric types have the DNS name custom.googleapis.com. + * Metric types should use a natural hierarchical grouping. */ +static int format_metric_type(yajl_gen gen, data_set_t const *ds, + value_list_t const *vl, int ds_index) { + /* {{{ */ + char buffer[4 * DATA_MAX_NAME_LEN]; + metric_type(buffer, sizeof(buffer), ds, vl, ds_index); + + return json_string(gen, buffer); +} /* }}} int format_metric_type */ + +/* TimeInterval + * + * { + * "endTime": string, + * "startTime": string, + * } + */ +static int format_time_interval(yajl_gen gen, int ds_type, + value_list_t const *vl) { + /* {{{ */ + yajl_gen_map_open(gen); + + int status = json_string(gen, "endTime") || json_time(gen, vl->time); + if (status != 0) + return status; + + if (ds_type != DS_TYPE_GAUGE) { + cdtime_t start_time = 0; + if (uc_meta_data_get_unsigned_int(vl, "gcm:start_time", &start_time) != 0) { + start_time = vl->time; + uc_meta_data_add_unsigned_int(vl, "gcm:start_time", start_time); + } + + int status = json_string(gen, "startTime") || json_time(gen, start_time); + if (status != 0) + return status; + } + + yajl_gen_map_close(gen); + return 0; +} /* }}} int format_time_interval */ + +/* Point + * + * { + * "interval": { + * object(TimeInterval) + * }, + * "value": { + * object(TypedValue) + * }, + * } + */ +static int format_point(yajl_gen gen, data_set_t const *ds, + value_list_t const *vl, int ds_index) { + /* {{{ */ + yajl_gen_map_open(gen); + + int ds_type = ds->ds[ds_index].type; + + int status = json_string(gen, "interval") || + format_time_interval(gen, ds_type, vl) || + json_string(gen, "value") || + format_gcm_typed_value(gen, ds_type, vl->values[ds_index]); + if (status != 0) + return status; + + yajl_gen_map_close(gen); + return 0; +} /* }}} int format_point */ + +/* Metric + * + * { + * "type": string, + * "labels": { + * string: string, + * ... + * }, + * } + */ +static int format_metric(yajl_gen gen, data_set_t const *ds, + value_list_t const *vl, int ds_index) { + /* {{{ */ + yajl_gen_map_open(gen); + + int status = json_string(gen, "type") || + format_metric_type(gen, ds, vl, ds_index) || + json_string(gen, "labels"); + if (status != 0) { + return status; + } + + yajl_gen_map_open(gen); + status = json_string(gen, "host") || json_string(gen, vl->host) || + json_string(gen, "plugin_instance") || + json_string(gen, vl->plugin_instance) || + json_string(gen, "type_instance") || + json_string(gen, vl->type_instance); + if (status != 0) { + return status; + } + yajl_gen_map_close(gen); + + yajl_gen_map_close(gen); + return 0; +} /* }}} int format_metric */ + +/* TimeSeries + * + * { + * "metric": { + * object(Metric) + * }, + * "resource": { + * object(MonitoredResource) + * }, + * "metricKind": enum(MetricKind), + * "valueType": enum(ValueType), + * "points": [ + * { + * object(Point) + * } + * ], + * } + */ +static int format_time_series(yajl_gen gen, data_set_t const *ds, + value_list_t const *vl, int ds_index, + gcm_resource_t *res) { + /* {{{ */ + yajl_gen_map_open(gen); + + int ds_type = ds->ds[ds_index].type; + + int status = + json_string(gen, "metric") || format_metric(gen, ds, vl, ds_index) || + json_string(gen, "resource") || format_gcm_resource(gen, res) || + json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) || + json_string(gen, "valueType") || format_value_type(gen, ds_type) || + json_string(gen, "points"); + if (status != 0) + return status; + + yajl_gen_array_open(gen); + if ((status = format_point(gen, ds, vl, ds_index)) != 0) { + return status; + } + yajl_gen_array_close(gen); + + yajl_gen_map_close(gen); + return 0; +} /* }}} int format_time_series */ + +/* Request body + * + * { + * "timeSeries": [ + * { + * object(TimeSeries) + * } + * ], + * } + */ +static int gcm_output_initialize(gcm_output_t *out) /* {{{ */ +{ + yajl_gen_map_open(out->gen); + + int status = json_string(out->gen, "timeSeries"); + if (status != 0) { + return status; + } + + yajl_gen_array_open(out->gen); + return 0; +} /* }}} int gcm_output_initialize */ + +static int gcm_output_finalize(gcm_output_t *out) /* {{{ */ +{ + yajl_gen_array_close(out->gen); + yajl_gen_map_close(out->gen); + + return 0; +} /* }}} int gcm_output_finalize */ + +static void gcm_output_reset_staged(gcm_output_t *out) /* {{{ */ +{ + void *key = NULL; + + while (c_avl_pick(out->staged, &key, &(void *){NULL}) == 0) + sfree(key); +} /* }}} void gcm_output_reset_staged */ + +gcm_output_t *gcm_output_create(gcm_resource_t *res) /* {{{ */ +{ + gcm_output_t *out = calloc(1, sizeof(*out)); + if (out == NULL) + return NULL; + + out->res = res; + + out->gen = yajl_gen_alloc(/* funcs = */ NULL); + if (out->gen == NULL) { + gcm_output_destroy(out); + return NULL; + } + + out->staged = c_avl_create((void *)strcmp); + if (out->staged == NULL) { + gcm_output_destroy(out); + return NULL; + } + + out->metric_descriptors = c_avl_create((void *)strcmp); + if (out->metric_descriptors == NULL) { + gcm_output_destroy(out); + return NULL; + } + + gcm_output_initialize(out); + + return out; +} /* }}} gcm_output_t *gcm_output_create */ + +void gcm_output_destroy(gcm_output_t *out) /* {{{ */ +{ + if (out == NULL) + return; + + if (out->metric_descriptors != NULL) { + void *key = NULL; + while (c_avl_pick(out->metric_descriptors, &key, &(void *){NULL}) == 0) { + sfree(key); + } + c_avl_destroy(out->metric_descriptors); + out->metric_descriptors = NULL; + } + + if (out->staged != NULL) { + gcm_output_reset_staged(out); + c_avl_destroy(out->staged); + out->staged = NULL; + } + + if (out->gen != NULL) { + yajl_gen_free(out->gen); + out->gen = NULL; + } + + if (out->res != NULL) { + gcm_resource_destroy(out->res); + out->res = NULL; + } + + sfree(out); +} /* }}} void gcm_output_destroy */ + +int gcm_output_add(gcm_output_t *out, data_set_t const *ds, + value_list_t const *vl) /* {{{ */ +{ + char key[6 * DATA_MAX_NAME_LEN]; + int status; + + /* first, check that we have all appropriate metric descriptors. */ + for (size_t i = 0; i < ds->ds_num; i++) { + char buffer[4 * DATA_MAX_NAME_LEN]; + metric_type(buffer, sizeof(buffer), ds, vl, i); + + if (c_avl_get(out->metric_descriptors, buffer, NULL) != 0) { + return ENOENT; + } + } + + status = FORMAT_VL(key, sizeof(key), vl); + if (status != 0) { + ERROR("gcm_output_add: FORMAT_VL failed with status %d.", status); + return status; + } + + if (c_avl_get(out->staged, key, NULL) == 0) { + return EEXIST; + } + + for (size_t i = 0; i < ds->ds_num; i++) { + int status = format_time_series(out->gen, ds, vl, i, out->res); + if (status != 0) { + ERROR("gcm_output_add: format_time_series failed with status %d.", + status); + return status; + } + } + + c_avl_insert(out->staged, strdup(key), NULL); + + size_t json_buffer_size = 0; + yajl_gen_get_buf(out->gen, &(unsigned char const *){NULL}, &json_buffer_size); + if (json_buffer_size > 65535) + return ENOBUFS; + + return 0; +} /* }}} int gcm_output_add */ + +int gcm_output_register_metric(gcm_output_t *out, data_set_t const *ds, + value_list_t const *vl) { + /* {{{ */ + for (size_t i = 0; i < ds->ds_num; i++) { + char buffer[4 * DATA_MAX_NAME_LEN]; + metric_type(buffer, sizeof(buffer), ds, vl, i); + + char *key = strdup(buffer); + int status = c_avl_insert(out->metric_descriptors, key, NULL); + if (status != 0) { + sfree(key); + return status; + } + } + + return 0; +} /* }}} int gcm_output_register_metric */ + +char *gcm_output_reset(gcm_output_t *out) /* {{{ */ +{ + unsigned char const *json_buffer = NULL; + char *ret; + + gcm_output_finalize(out); + + yajl_gen_get_buf(out->gen, &json_buffer, &(size_t){0}); + ret = strdup((void const *)json_buffer); + + gcm_output_reset_staged(out); + + yajl_gen_free(out->gen); + out->gen = yajl_gen_alloc(/* funcs = */ NULL); + + gcm_output_initialize(out); + + return ret; +} /* }}} char *gcm_output_reset */ + +gcm_resource_t *gcm_resource_create(char const *type) /* {{{ */ +{ + gcm_resource_t *res; + + res = malloc(sizeof(*res)); + if (res == NULL) + return NULL; + memset(res, 0, sizeof(*res)); + + res->type = strdup(type); + if (res->type == NULL) { + sfree(res); + return NULL; + } + + res->labels = NULL; + res->labels_num = 0; + + return res; +} /* }}} gcm_resource_t *gcm_resource_create */ + +void gcm_resource_destroy(gcm_resource_t *res) /* {{{ */ +{ + size_t i; + + if (res == NULL) + return; + + for (i = 0; i < res->labels_num; i++) { + sfree(res->labels[i].key); + sfree(res->labels[i].value); + } + sfree(res->labels); + sfree(res->type); + sfree(res); +} /* }}} void gcm_resource_destroy */ + +int gcm_resource_add_label(gcm_resource_t *res, char const *key, + char const *value) /* {{{ */ +{ + gcm_label_t *l; + + if ((res == NULL) || (key == NULL) || (value == NULL)) + return EINVAL; + + l = realloc(res->labels, sizeof(*res->labels) * (res->labels_num + 1)); + if (l == NULL) + return ENOMEM; + + res->labels = l; + l = res->labels + res->labels_num; + + l->key = strdup(key); + l->value = strdup(value); + if ((l->key == NULL) || (l->value == NULL)) { + sfree(l->key); + sfree(l->value); + return ENOMEM; + } + + res->labels_num++; + return 0; +} /* }}} int gcm_resource_add_label */ + +/* LabelDescriptor + * + * { + * "key": string, + * "valueType": enum(ValueType), + * "description": string, + * } + */ +static int format_label_descriptor(yajl_gen gen, char const *key) { + /* {{{ */ + yajl_gen_map_open(gen); + + int status = json_string(gen, "key") || json_string(gen, key) || + json_string(gen, "valueType") || json_string(gen, "STRING"); + if (status != 0) { + return status; + } + + yajl_gen_map_close(gen); + return 0; +} /* }}} int format_label_descriptor */ + +/* MetricDescriptor + * + * { + * "name": string, + * "type": string, + * "labels": [ + * { + * object(LabelDescriptor) + * } + * ], + * "metricKind": enum(MetricKind), + * "valueType": enum(ValueType), + * "unit": string, + * "description": string, + * "displayName": string, + * } + */ +int gcm_format_metric_descriptor(char *buffer, size_t buffer_size, + data_set_t const *ds, value_list_t const *vl, + int ds_index) { + /* {{{ */ + yajl_gen gen = yajl_gen_alloc(/* funcs = */ NULL); + if (gen == NULL) { + return ENOMEM; + } + + int ds_type = ds->ds[ds_index].type; + + yajl_gen_map_open(gen); + + int status = + json_string(gen, "type") || format_metric_type(gen, ds, vl, ds_index) || + json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) || + json_string(gen, "valueType") || format_value_type(gen, ds_type) || + json_string(gen, "labels"); + if (status != 0) { + yajl_gen_free(gen); + return status; + } + + char const *labels[] = {"host", "plugin_instance", "type_instance"}; + yajl_gen_array_open(gen); + + for (size_t i = 0; i < STATIC_ARRAY_SIZE(labels); i++) { + int status = format_label_descriptor(gen, labels[i]); + if (status != 0) { + yajl_gen_free(gen); + return status; + } + } + + yajl_gen_array_close(gen); + yajl_gen_map_close(gen); + + unsigned char const *tmp = NULL; + yajl_gen_get_buf(gen, &tmp, &(size_t){0}); + sstrncpy(buffer, (void const *)tmp, buffer_size); + + yajl_gen_free(gen); + return 0; +} /* }}} int gcm_format_metric_descriptor */ + +/* vim: set sw=2 sts=2 et fdm=marker : */ diff --git a/src/utils_format_gcm.h b/src/utils_format_gcm.h new file mode 100644 index 00000000..a43812c5 --- /dev/null +++ b/src/utils_format_gcm.h @@ -0,0 +1,78 @@ +/** + * collectd - src/utils_format_gcm.h + * ISC license + * + * Copyright (C) 2017 Florian Forster + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: + * Florian Forster + **/ + +#ifndef UTILS_FORMAT_GCM_H +#define UTILS_FORMAT_GCM_H 1 + +#include "collectd.h" +#include "plugin.h" + +/* gcm_output_t is a buffer to which value_list_t* can be added and from which + * an appropriately formatted char* can be read. */ +struct gcm_output_s; +typedef struct gcm_output_s gcm_output_t; + +/* gcm_resource_t represents a MonitoredResource. */ +struct gcm_resource_s; +typedef struct gcm_resource_s gcm_resource_t; + +gcm_output_t *gcm_output_create(gcm_resource_t *res); + +/* gcm_output_destroy frees all memory used by out, including the + * gcm_resource_t* passed to gcm_output_create. */ +void gcm_output_destroy(gcm_output_t *out); + +/* gcm_output_add adds a value_list_t* to "out". + * + * Return values: + * - 0 Success + * - ENOBUFS Success, but the buffer should be flushed soon. + * - EEXIST The value list is already encoded in the buffer. + * Flush the buffer, then call gcm_output_add again. + * - ENOENT First time we encounter this metric. Create a metric descriptor + * using the GCM API and then call gcm_output_register_metric. + */ +int gcm_output_add(gcm_output_t *out, data_set_t const *ds, + value_list_t const *vl); + +/* gcm_output_register_metric adds the metric descriptor which vl maps to, to + * the list of known metric descriptors. */ +int gcm_output_register_metric(gcm_output_t *out, data_set_t const *ds, + value_list_t const *vl); + +/* gcm_output_reset resets the output and returns the previous content of the + * buffer. It is the caller's responsibility to call free() with the returned + * pointer. */ +char *gcm_output_reset(gcm_output_t *out); + +gcm_resource_t *gcm_resource_create(char const *type); +void gcm_resource_destroy(gcm_resource_t *res); +int gcm_resource_add_label(gcm_resource_t *res, char const *key, + char const *value); + +/* gcm_format_metric_descriptor creates the payload for a + * projects.metricDescriptors.create() request. */ +int gcm_format_metric_descriptor(char *buffer, size_t buffer_size, + data_set_t const *ds, value_list_t const *vl, + int ds_index); + +#endif /* UTILS_FORMAT_GCM_H */ diff --git a/src/utils_format_gcm_test.c b/src/utils_format_gcm_test.c new file mode 100644 index 00000000..52d6dc9b --- /dev/null +++ b/src/utils_format_gcm_test.c @@ -0,0 +1,75 @@ +/** + * collectd - src/utils_format_gcm_test.c + * ISC license + * + * Copyright (C) 2017 Florian Forster + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: + * Florian Forster + **/ + +#include "utils_format_gcm.h" +#include "testing.h" + +DEF_TEST(gcm_format_metric_descriptor) { + value_list_t vl = { + .host = "example.com", .plugin = "unit-test", .type = "example", + }; + char got[1024]; + + data_set_t ds_single = { + .type = "example", + .ds_num = 1, + .ds = + &(data_source_t){ + .name = "value", .type = DS_TYPE_GAUGE, .min = NAN, .max = NAN, + }, + }; + EXPECT_EQ_INT( + 0, gcm_format_metric_descriptor(got, sizeof(got), &ds_single, &vl, 0)); + char const *want_single = + "{\"type\":\"custom.googleapis.com/collectd/unit_test/" + "example\",\"metricKind\":\"GAUGE\",\"valueType\":\"DOUBLE\",\"labels\":[" + "{\"key\":\"host\",\"valueType\":\"STRING\"},{\"key\":\"plugin_" + "instance\",\"valueType\":\"STRING\"},{\"key\":\"type_instance\"," + "\"valueType\":\"STRING\"}]}"; + EXPECT_EQ_STR(want_single, got); + + data_set_t ds_double = { + .type = "example", + .ds_num = 2, + .ds = + (data_source_t[]){ + {.name = "one", .type = DS_TYPE_DERIVE, .min = 0, .max = NAN}, + {.name = "two", .type = DS_TYPE_DERIVE, .min = 0, .max = NAN}, + }, + }; + EXPECT_EQ_INT( + 0, gcm_format_metric_descriptor(got, sizeof(got), &ds_double, &vl, 0)); + char const *want_double = + "{\"type\":\"custom.googleapis.com/collectd/unit_test/" + "example_one\",\"metricKind\":\"CUMULATIVE\",\"valueType\":\"INT64\"," + "\"labels\":[{\"key\":\"host\",\"valueType\":\"STRING\"},{\"key\":" + "\"plugin_instance\",\"valueType\":\"STRING\"},{\"key\":\"type_" + "instance\",\"valueType\":\"STRING\"}]}"; + EXPECT_EQ_STR(want_double, got); + return 0; +} + +int main(int argc, char **argv) { + RUN_TEST(gcm_format_metric_descriptor); + + END_TEST; +}