From: Florian Forster Date: Wed, 8 Jul 2020 20:18:04 +0000 (+0200) Subject: format_json: Add support for appending metric_family_t's to the buffer. X-Git-Tag: 6.0.0-rc0~144^2~46 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c5aa6700766f38b55a0f11c1941e88a25fd8f560;p=thirdparty%2Fcollectd.git format_json: Add support for appending metric_family_t's to the buffer. With this change, multiple metric_family_t's can be added to a buffer sequentially. The implementation that did not use libyajl has been removed because it was unused (it only implemented marshalling of value_list_t). --- diff --git a/Makefile.am b/Makefile.am index fd9b7dd74..01c5bb773 100644 --- a/Makefile.am +++ b/Makefile.am @@ -132,7 +132,6 @@ noinst_LTLIBRARIES = \ libcmds.la \ libcommon.la \ libformat_graphite.la \ - libformat_json.la \ libheap.la \ libidentity.la \ libignorelist.la \ @@ -492,13 +491,14 @@ test_format_graphite_LDADD = \ libstrbuf.la \ -lm +if BUILD_WITH_LIBYAJL +noinst_LTLIBRARIES += libformat_json.la libformat_json_la_SOURCES = \ src/utils/format_json/format_json.c \ src/utils/format_json/format_json.h libformat_json_la_CPPFLAGS = $(AM_CPPFLAGS) libformat_json_la_LDFLAGS = $(AM_LDFLAGS) libformat_json_la_LIBADD = libmetric.la -if BUILD_WITH_LIBYAJL libformat_json_la_CPPFLAGS += $(BUILD_WITH_LIBYAJL_CPPFLAGS) libformat_json_la_LDFLAGS += $(BUILD_WITH_LIBYAJL_LDFLAGS) libformat_json_la_LIBADD += $(BUILD_WITH_LIBYAJL_LIBS) diff --git a/src/utils/format_json/format_json.c b/src/utils/format_json/format_json.c index 23b95aabf..5958b12d7 100644 --- a/src/utils/format_json/format_json.c +++ b/src/utils/format_json/format_json.c @@ -30,11 +30,9 @@ #include "utils/format_json/format_json.h" #include "plugin.h" -#include "utils/avltree/avltree.h" #include "utils/common/common.h" #include "utils_cache.h" -#if HAVE_LIBYAJL #include #include #if HAVE_YAJL_YAJL_VERSION_H @@ -43,429 +41,7 @@ #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1) #define HAVE_YAJL_V2 1 #endif -#endif - -static int json_escape_string(char *buffer, size_t buffer_size, /* {{{ */ - const char *string) { - size_t dst_pos; - - if ((buffer == NULL) || (string == NULL)) - return -EINVAL; - - if (buffer_size < 3) - return -ENOMEM; - - dst_pos = 0; - -#define BUFFER_ADD(c) \ - do { \ - if (dst_pos >= (buffer_size - 1)) { \ - buffer[buffer_size - 1] = '\0'; \ - return -ENOMEM; \ - } \ - buffer[dst_pos] = (c); \ - dst_pos++; \ - } while (0) - - /* Escape special characters */ - BUFFER_ADD('"'); - for (size_t src_pos = 0; string[src_pos] != 0; src_pos++) { - if ((string[src_pos] == '"') || (string[src_pos] == '\\')) { - BUFFER_ADD('\\'); - BUFFER_ADD(string[src_pos]); - } else if (string[src_pos] <= 0x001F) - BUFFER_ADD('?'); - else - BUFFER_ADD(string[src_pos]); - } /* for */ - BUFFER_ADD('"'); - buffer[dst_pos] = 0; - -#undef BUFFER_ADD - - return 0; -} /* }}} int json_escape_string */ - -static int values_to_json(char *buffer, size_t buffer_size, /* {{{ */ - const data_set_t *ds, const value_list_t *vl, - int store_rates) { - size_t offset = 0; - gauge_t *rates = NULL; - - memset(buffer, 0, buffer_size); - -#define BUFFER_ADD(...) \ - do { \ - int status; \ - status = snprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \ - if (status < 1) { \ - sfree(rates); \ - return -1; \ - } else if (((size_t)status) >= (buffer_size - offset)) { \ - sfree(rates); \ - return -ENOMEM; \ - } else \ - offset += ((size_t)status); \ - } while (0) - - BUFFER_ADD("["); - for (size_t i = 0; i < ds->ds_num; i++) { - if (i > 0) - BUFFER_ADD(","); - - if (ds->ds[i].type == DS_TYPE_GAUGE) { - if (isfinite(vl->values[i].gauge)) - BUFFER_ADD(JSON_GAUGE_FORMAT, vl->values[i].gauge); - else - BUFFER_ADD("null"); - } else if (store_rates) { - if (rates == NULL) - rates = uc_get_rate_vl(ds, vl); - if (rates == NULL) { - WARNING("utils_format_json: uc_get_rate_vl failed."); - sfree(rates); - return -1; - } - - if (isfinite(rates[i])) - BUFFER_ADD(JSON_GAUGE_FORMAT, rates[i]); - else - BUFFER_ADD("null"); - } else if (ds->ds[i].type == DS_TYPE_COUNTER) - BUFFER_ADD("%" PRIu64, (uint64_t)vl->values[i].counter); - else if (ds->ds[i].type == DS_TYPE_DERIVE) - BUFFER_ADD("%" PRIi64, vl->values[i].derive); - else { - ERROR("format_json: Unknown data source type: %i", ds->ds[i].type); - sfree(rates); - return -1; - } - } /* for ds->ds_num */ - BUFFER_ADD("]"); - -#undef BUFFER_ADD - - sfree(rates); - return 0; -} /* }}} int values_to_json */ - -static int dstypes_to_json(char *buffer, size_t buffer_size, /* {{{ */ - const data_set_t *ds) { - size_t offset = 0; - - memset(buffer, 0, buffer_size); - -#define BUFFER_ADD(...) \ - do { \ - int status; \ - status = snprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \ - if (status < 1) \ - return -1; \ - else if (((size_t)status) >= (buffer_size - offset)) \ - return -ENOMEM; \ - else \ - offset += ((size_t)status); \ - } while (0) - - BUFFER_ADD("["); - for (size_t i = 0; i < ds->ds_num; i++) { - if (i > 0) - BUFFER_ADD(","); - - BUFFER_ADD("\"%s\"", DS_TYPE_TO_STRING(ds->ds[i].type)); - } /* for ds->ds_num */ - BUFFER_ADD("]"); - -#undef BUFFER_ADD - - return 0; -} /* }}} int dstypes_to_json */ - -static int dsnames_to_json(char *buffer, size_t buffer_size, /* {{{ */ - const data_set_t *ds) { - size_t offset = 0; - - memset(buffer, 0, buffer_size); - -#define BUFFER_ADD(...) \ - do { \ - int status; \ - status = snprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \ - if (status < 1) \ - return -1; \ - else if (((size_t)status) >= (buffer_size - offset)) \ - return -ENOMEM; \ - else \ - offset += ((size_t)status); \ - } while (0) - - BUFFER_ADD("["); - for (size_t i = 0; i < ds->ds_num; i++) { - if (i > 0) - BUFFER_ADD(","); - - BUFFER_ADD("\"%s\"", ds->ds[i].name); - } /* for ds->ds_num */ - BUFFER_ADD("]"); - -#undef BUFFER_ADD - - return 0; -} /* }}} int dsnames_to_json */ - -static int meta_data_keys_to_json(char *buffer, size_t buffer_size, /* {{{ */ - meta_data_t *meta, char **keys, - size_t keys_num) { - size_t offset = 0; - int status; - - buffer[0] = 0; - -#define BUFFER_ADD(...) \ - do { \ - status = snprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \ - if (status < 1) \ - return -1; \ - else if (((size_t)status) >= (buffer_size - offset)) \ - return -ENOMEM; \ - else \ - offset += ((size_t)status); \ - } while (0) - - for (size_t i = 0; i < keys_num; ++i) { - int type; - char *key = keys[i]; - - type = meta_data_type(meta, key); - if (type == MD_TYPE_STRING) { - char *value = NULL; - if (meta_data_get_string(meta, key, &value) == 0) { - char temp[512] = ""; - - status = json_escape_string(temp, sizeof(temp), value); - sfree(value); - if (status != 0) - return status; - - BUFFER_ADD(",\"%s\":%s", key, temp); - } - } else if (type == MD_TYPE_SIGNED_INT) { - int64_t value = 0; - if (meta_data_get_signed_int(meta, key, &value) == 0) - BUFFER_ADD(",\"%s\":%" PRIi64, key, value); - } else if (type == MD_TYPE_UNSIGNED_INT) { - uint64_t value = 0; - if (meta_data_get_unsigned_int(meta, key, &value) == 0) - BUFFER_ADD(",\"%s\":%" PRIu64, key, value); - } else if (type == MD_TYPE_DOUBLE) { - double value = 0.0; - if (meta_data_get_double(meta, key, &value) == 0) - BUFFER_ADD(",\"%s\":%f", key, value); - } else if (type == MD_TYPE_BOOLEAN) { - bool value = false; - if (meta_data_get_boolean(meta, key, &value) == 0) - BUFFER_ADD(",\"%s\":%s", key, value ? "true" : "false"); - } - } /* for (keys) */ - - if (offset == 0) - return ENOENT; - - buffer[0] = '{'; /* replace leading ',' */ - BUFFER_ADD("}"); - -#undef BUFFER_ADD - - return 0; -} /* }}} int meta_data_keys_to_json */ - -static int meta_data_to_json(char *buffer, size_t buffer_size, /* {{{ */ - meta_data_t *meta) { - char **keys = NULL; - size_t keys_num; - int status; - - if ((buffer == NULL) || (buffer_size == 0) || (meta == NULL)) - return EINVAL; - - status = meta_data_toc(meta, &keys); - if (status <= 0) - return status; - keys_num = (size_t)status; - status = meta_data_keys_to_json(buffer, buffer_size, meta, keys, keys_num); - - for (size_t i = 0; i < keys_num; ++i) - sfree(keys[i]); - sfree(keys); - - return status; -} /* }}} int meta_data_to_json */ - -static int value_list_to_json(char *buffer, size_t buffer_size, /* {{{ */ - const data_set_t *ds, const value_list_t *vl, - int store_rates) { - char temp[512]; - size_t offset = 0; - int status; - - memset(buffer, 0, buffer_size); - -#define BUFFER_ADD(...) \ - do { \ - status = snprintf(buffer + offset, buffer_size - offset, __VA_ARGS__); \ - if (status < 1) \ - return -1; \ - else if (((size_t)status) >= (buffer_size - offset)) \ - return -ENOMEM; \ - else \ - offset += ((size_t)status); \ - } while (0) - - /* All value lists have a leading comma. The first one will be replaced with - * a square bracket in `format_json_finalize'. */ - BUFFER_ADD(",{"); - - status = values_to_json(temp, sizeof(temp), ds, vl, store_rates); - if (status != 0) - return status; - BUFFER_ADD("\"values\":%s", temp); - - status = dstypes_to_json(temp, sizeof(temp), ds); - if (status != 0) - return status; - BUFFER_ADD(",\"dstypes\":%s", temp); - - status = dsnames_to_json(temp, sizeof(temp), ds); - if (status != 0) - return status; - BUFFER_ADD(",\"dsnames\":%s", temp); - - BUFFER_ADD(",\"time\":%.3f", CDTIME_T_TO_DOUBLE(vl->time)); - BUFFER_ADD(",\"interval\":%.3f", CDTIME_T_TO_DOUBLE(vl->interval)); - -#define BUFFER_ADD_KEYVAL(key, value) \ - do { \ - status = json_escape_string(temp, sizeof(temp), (value)); \ - if (status != 0) \ - return status; \ - BUFFER_ADD(",\"%s\":%s", (key), temp); \ - } while (0) - - BUFFER_ADD_KEYVAL("host", vl->host); - BUFFER_ADD_KEYVAL("plugin", vl->plugin); - BUFFER_ADD_KEYVAL("plugin_instance", vl->plugin_instance); - BUFFER_ADD_KEYVAL("type", vl->type); - BUFFER_ADD_KEYVAL("type_instance", vl->type_instance); - - if (vl->meta != NULL) { - char meta_buffer[buffer_size]; - memset(meta_buffer, 0, sizeof(meta_buffer)); - status = meta_data_to_json(meta_buffer, sizeof(meta_buffer), vl->meta); - if (status != 0) - return status; - - BUFFER_ADD(",\"meta\":%s", meta_buffer); - } /* if (vl->meta != NULL) */ - - BUFFER_ADD("}"); - -#undef BUFFER_ADD_KEYVAL -#undef BUFFER_ADD - - return 0; -} /* }}} int value_list_to_json */ - -static int format_json_value_list_nocheck(char *buffer, /* {{{ */ - size_t *ret_buffer_fill, - size_t *ret_buffer_free, - const data_set_t *ds, - const value_list_t *vl, - int store_rates, size_t temp_size) { - char temp[temp_size]; - int status; - - status = value_list_to_json(temp, sizeof(temp), ds, vl, store_rates); - if (status != 0) - return status; - temp_size = strlen(temp); - - memcpy(buffer + (*ret_buffer_fill), temp, temp_size + 1); - (*ret_buffer_fill) += temp_size; - (*ret_buffer_free) -= temp_size; - - return 0; -} /* }}} int format_json_value_list_nocheck */ - -int format_json_initialize(char *buffer, /* {{{ */ - size_t *ret_buffer_fill, size_t *ret_buffer_free) { - size_t buffer_fill; - size_t buffer_free; - - if ((buffer == NULL) || (ret_buffer_fill == NULL) || - (ret_buffer_free == NULL)) - return -EINVAL; - - buffer_fill = *ret_buffer_fill; - buffer_free = *ret_buffer_free; - - buffer_free = buffer_fill + buffer_free; - buffer_fill = 0; - - if (buffer_free < 3) - return -ENOMEM; - - memset(buffer, 0, buffer_free); - *ret_buffer_fill = buffer_fill; - *ret_buffer_free = buffer_free; - - return 0; -} /* }}} int format_json_initialize */ - -int format_json_finalize(char *buffer, /* {{{ */ - size_t *ret_buffer_fill, size_t *ret_buffer_free) { - size_t pos; - - if ((buffer == NULL) || (ret_buffer_fill == NULL) || - (ret_buffer_free == NULL)) - return -EINVAL; - - if (*ret_buffer_free < 2) - return -ENOMEM; - - /* Replace the leading comma added in `value_list_to_json' with a square - * bracket. */ - if (buffer[0] != ',') - return -EINVAL; - buffer[0] = '['; - - pos = *ret_buffer_fill; - buffer[pos] = ']'; - buffer[pos + 1] = 0; - - (*ret_buffer_fill)++; - (*ret_buffer_free)--; - - return 0; -} /* }}} int format_json_finalize */ - -int format_json_value_list(char *buffer, /* {{{ */ - size_t *ret_buffer_fill, size_t *ret_buffer_free, - const data_set_t *ds, const value_list_t *vl, - int store_rates) { - if ((buffer == NULL) || (ret_buffer_fill == NULL) || - (ret_buffer_free == NULL) || (ds == NULL) || (vl == NULL)) - return -EINVAL; - - if (*ret_buffer_free < 3) - return -ENOMEM; - - return format_json_value_list_nocheck(buffer, ret_buffer_fill, - ret_buffer_free, ds, vl, store_rates, - (*ret_buffer_free) - 2); -} /* }}} int format_json_value_list */ - -#if HAVE_LIBYAJL static int json_add_string(yajl_gen g, char const *str) /* {{{ */ { if (str == NULL) @@ -582,6 +158,20 @@ static int format_metric(yajl_gen g, metric_t const *m) { } /* json_metric_family that all metrics in ml have the same name and value_type. + * + * Example: + [ + { + "name": "roshi_select_call_count", + "help": "How many select calls have been made.", + "type": "COUNTER", + "metrics": [ + { + "value": "1063110" + } + ] + } + ] */ static int json_metric_family(yajl_gen g, metric_family_t const *fam) { CHECK_SUCCESS(yajl_gen_map_open(g)); /* BEGIN metric family */ @@ -663,21 +253,41 @@ int format_json_metric_family(strbuf_t *buf, metric_family_t const *fam, /* copy to output buffer */ unsigned char const *out = NULL; #if HAVE_YAJL_V2 - size_t unused_out_len = 0; + size_t out_len = 0; #else - unsigned int unused_out_len = 0; + unsigned int out_len = 0; #endif - if (yajl_gen_get_buf(g, &out, &unused_out_len) != yajl_gen_status_ok) { + if (yajl_gen_get_buf(g, &out, &out_len) != yajl_gen_status_ok) { yajl_gen_clear(g); yajl_gen_free(g); return -1; } + + if (buf->fixed) { + size_t avail = (buf->size == 0) ? 0 : buf->size - (buf->pos + 1); + if (avail < out_len) { + yajl_gen_clear(g); + yajl_gen_free(g); + return ENOBUFS; + } + } + + /* If the buffer is not empty, append by converting the closing ']' of "buf" + * to a comma and skip the opening '[' of "out". */ + if (buf->pos != 0) { + assert(buf->ptr[buf->pos - 1] == ']'); + buf->ptr[buf->pos - 1] = ','; + + assert(out[0] == '['); + out++; + } + status = strbuf_print(buf, (void *)out); yajl_gen_clear(g); yajl_gen_free(g); return status; -} /* }}} format_json_metric */ +} /* }}} format_json_metric_family */ static int format_alert(yajl_gen g, notification_t const *n) /* {{{ */ { @@ -822,10 +432,3 @@ int format_json_notification(char *buffer, size_t buffer_size, /* {{{ */ yajl_gen_free(g); return 0; } /* }}} format_json_notification */ -#else -int format_json_notification(char *buffer, size_t buffer_size, /* {{{ */ - notification_t const *n) { - ERROR("format_json_notification: Not available (requires libyajl)."); - return ENOTSUP; -} /* }}} int format_json_notification */ -#endif diff --git a/src/utils/format_json/format_json.h b/src/utils/format_json/format_json.h index eb05d1065..12cc969f0 100644 --- a/src/utils/format_json/format_json.h +++ b/src/utils/format_json/format_json.h @@ -1,6 +1,6 @@ /** * collectd - src/utils_format_json.h - * Copyright (C) 2009 Florian octo Forster + * Copyright (C) 2009-2020 Florian octo Forster * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -36,16 +36,12 @@ #define JSON_GAUGE_FORMAT GAUGE_FORMAT #endif -int format_json_initialize(char *buffer, size_t *ret_buffer_fill, - size_t *ret_buffer_free); -int format_json_value_list(char *buffer, size_t *ret_buffer_fill, - size_t *ret_buffer_free, const data_set_t *ds, - const value_list_t *vl, int store_rates); -int format_json_finalize(char *buffer, size_t *ret_buffer_fill, - size_t *ret_buffer_free); - -/* format_json_metric writes m to buf in JSON format. The format produces is - * compatible to the "prometheus/prom2json" project. */ +/* format_json_metric_family adds the metric family "fam" to the buffer "buf" + * in JSON format. The format produced is compatible to the + * "prometheus/prom2json" project. Calling this function repeatedly with the + * same buffer will append additional metric families to the buffer. If the + * buffer has fixed size and the serialized metric family exceeds the buffer + * length, the buffer is unmodified and ENOBUFS is returned. */ int format_json_metric_family(strbuf_t *buf, metric_family_t const *fam, bool store_rates); diff --git a/src/utils/format_json/format_json_test.c b/src/utils/format_json/format_json_test.c index 3185b725e..6251ecd25 100644 --- a/src/utils/format_json/format_json_test.c +++ b/src/utils/format_json/format_json_test.c @@ -40,12 +40,7 @@ #endif typedef struct { - char const *key; - char const *value; -} keyval_t; - -typedef struct { - keyval_t *expected_labels; + label_t *expected_labels; size_t expected_labels_num; keyval_t *current_label; @@ -63,9 +58,9 @@ static int test_map_key(void *ctx, unsigned char const *key, c->current_label = NULL; for (i = 0; i < c->expected_labels_num; i++) { - keyval_t *l = c->expected_labels + i; - if ((strlen(l->key) == key_len) && - (strncmp(l->key, (char const *)key, key_len) == 0)) { + label_t *l = c->expected_labels + i; + if ((strlen(l->name) == key_len) && + (strncmp(l->name, (char const *)key, key_len) == 0)) { c->current_label = l; break; } @@ -106,7 +101,7 @@ static int test_string(void *ctx, unsigned char const *value, memmove(got, value, value_len); got[value_len] = 0; - status = expect_label(l->key, got, l->value); + status = expect_label(l->name, got, l->value); free(got); @@ -165,49 +160,49 @@ DEF_TEST(notification) { return expect_json_labels(got, labels, STATIC_ARRAY_SIZE(labels)); } -DEF_TEST(metric) { +DEF_TEST(metric_family) { struct { char const *identity; + metric_type_t type; value_t value; - int value_type; cdtime_t time; cdtime_t interval; - meta_data_t *meta; char const *want; } cases[] = { - { - .identity = "metric_name", - .value = (value_t){.gauge = 42}, - .value_type = VALUE_TYPE_GAUGE, - .want = "[{\"name\":\"metric_name\",\"type\":\"GAUGE\",\"metrics\":[" - "{\"value\":\"42\"}" - "]}]", - }, - { - .identity = - "metric_with_labels{sorted=\"true\",alphabetically=\"yes\"}", - .value = (value_t){.gauge = 42}, - .value_type = VALUE_TYPE_GAUGE, - .want = "[{\"name\":\"metric_with_labels\",\"type\":\"GAUGE\"," - "\"metrics\":[" - "{\"labels\":{\"alphabetically\":\"yes\",\"sorted\":\"true\"}" - ",\"value\":\"42\"}" - "]}]", - }, - { - .identity = "metric_with_time", - .value = (value_t){.gauge = 42}, - .value_type = VALUE_TYPE_GAUGE, - .time = MS_TO_CDTIME_T(1592987324125), - .want = - "[{\"name\":\"metric_with_time\",\"type\":\"GAUGE\",\"metrics\":[" - "{\"timestamp_ms\":\"1592987324125\",\"value\":\"42\"}" - "]}]", - }, + { + .identity = "metric_name", + .value.gauge = 42, + .type = METRIC_TYPE_GAUGE, + .want = "[{\"name\":\"metric_name\",\"type\":\"GAUGE\",\"metrics\":[" + "{\"value\":\"42\"}" + "]}]", + }, + { + .identity = + "metric_with_labels{sorted=\"true\",alphabetically=\"yes\"}", + .value.gauge = 42, + .type = METRIC_TYPE_GAUGE, + .want = "[{\"name\":\"metric_with_labels\",\"type\":\"GAUGE\"," + "\"metrics\":[" + "{\"labels\":{\"alphabetically\":\"yes\",\"sorted\":\"true\"}" + ",\"value\":\"42\"}" + "]}]", + }, + { + .identity = "metric_with_time", + .value.gauge = 42, + .type = METRIC_TYPE_GAUGE, + .time = MS_TO_CDTIME_T(1592987324125), + .want = + "[{\"name\":\"metric_with_time\",\"type\":\"GAUGE\",\"metrics\":[" + "{\"timestamp_ms\":\"1592987324125\",\"value\":\"42\"}" + "]}]", + }, +#if 0 { .identity = "derive_max", - .value = (value_t){.derive = INT64_MAX}, - .value_type = VALUE_TYPE_DERIVE, + .value.derive = INT64_MAX, + .type = METRIC_TYPE_COUNTER, .want = "[{\"name\":\"derive_max\",\"type\":\"COUNTER\",\"metrics\":[" "{\"value\":\"9223372036854775807\"}" "]}]", @@ -215,37 +210,35 @@ DEF_TEST(metric) { { .identity = "derive_min", .value = (value_t){.derive = INT64_MIN}, - .value_type = VALUE_TYPE_DERIVE, + .type = METRIC_TYPE_COUNTER, .want = "[{\"name\":\"derive_min\",\"type\":\"COUNTER\",\"metrics\":[" "{\"value\":\"-9223372036854775808\"}" "]}]", }, - { - .identity = "counter_max", - .value = (value_t){.counter = UINT64_MAX}, - .value_type = DS_TYPE_COUNTER, - .want = - "[{\"name\":\"counter_max\",\"type\":\"COUNTER\",\"metrics\":[" - "{\"value\":\"18446744073709551615\"}" - "]}]", - }, +#endif + { + .identity = "counter_max", + .value = (value_t){.counter = UINT64_MAX}, + .type = METRIC_TYPE_COUNTER, + .want = "[{\"name\":\"counter_max\",\"type\":\"COUNTER\",\"metrics\":[" + "{\"value\":\"18446744073709551615\"}" + "]}]", + }, }; for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { - identity_t *id; - CHECK_NOT_NULL(id = identity_unmarshal_text(cases[i].identity)); - - metric_single_t m = { - .identity = id, - .value = cases[i].value, - .value_type = cases[i].value_type, - .time = cases[i].time, - .interval = cases[i].interval, - .meta = cases[i].meta, - }; + metric_family_t *fam; + CHECK_NOT_NULL( + fam = metric_family_unmarshal_text(cases[i].identity, cases[i].type)); + + metric_t *m = fam->metric.ptr; + + m->value = cases[i].value; + m->time = cases[i].time; + m->interval = cases[i].interval; strbuf_t buf = STRBUF_CREATE; - CHECK_ZERO(format_json_metric(&buf, &m, false)); + CHECK_ZERO(format_json_metric_family(&buf, fam, false)); EXPECT_EQ_STR(cases[i].want, buf.ptr); STRBUF_DESTROY(buf); @@ -254,9 +247,50 @@ DEF_TEST(metric) { return 0; } +DEF_TEST(metric_family_append) { + strbuf_t buf = STRBUF_CREATE; + + metric_family_t fam = { + .name = "first", + .type = METRIC_TYPE_UNTYPED, + }; + metric_family_metric_append(&fam, (metric_t){ + .value.gauge = 0, + }); + metric_family_metric_append(&fam, (metric_t){ + .value.gauge = 1, + }); + CHECK_ZERO(format_json_metric_family(&buf, &fam, false)); + EXPECT_EQ_STR("[{\"name\":\"first\",\"type\":\"UNTYPED\",\"metrics\":[{" + "\"value\":\"0\"},{\"value\":\"1\"}]}]", + buf.ptr); + + metric_family_metric_reset(&fam); + + fam = (metric_family_t){ + .name = "second", + .type = METRIC_TYPE_GAUGE, + }; + metric_family_metric_append(&fam, (metric_t){ + .value.gauge = 2, + }); + + CHECK_ZERO(format_json_metric_family(&buf, &fam, false)); + EXPECT_EQ_STR("[{\"name\":\"first\",\"type\":\"UNTYPED\",\"metrics\":[{" + "\"value\":\"0\"},{\"value\":\"1\"}]},{\"name\":\"second\"," + "\"type\":\"GAUGE\",\"metrics\":[{\"value\":\"2\"}]}]", + buf.ptr); + + metric_family_metric_reset(&fam); + STRBUF_DESTROY(buf); + + return 0; +} + int main(void) { RUN_TEST(notification); - RUN_TEST(metric); + RUN_TEST(metric_family); + RUN_TEST(metric_family_append); END_TEST; }