#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 <yajl/yajl_common.h>
#include <yajl/yajl_gen.h>
#if HAVE_YAJL_YAJL_VERSION_H
#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)
}
/* 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 */
/* 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) /* {{{ */
{
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
#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;
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;
}
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);
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\"}"
"]}]",
{
.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);
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;
}