Skip to content

Commit

Permalink
format_json: Reimplement format_json_metric().
Browse files Browse the repository at this point in the history
This new implementation uses the new format of the metric_t type.
It produces output that is compatible with the prometheus/prom2json
project.
  • Loading branch information
octo committed Jul 29, 2020
1 parent bff9b02 commit eeb6ffa
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 129 deletions.
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ libformat_json_la_SOURCES = \
src/utils/format_json/format_json.h
libformat_json_la_CPPFLAGS = $(AM_CPPFLAGS)
libformat_json_la_LDFLAGS = $(AM_LDFLAGS)
libformat_json_la_LIBADD =
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)
Expand Down
18 changes: 18 additions & 0 deletions src/daemon/plugin_mock.c
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,21 @@ int plugin_thread_create(__attribute__((unused)) pthread_t *thread,
* would be to hard-code the top-level config keys in daemon/collectd.c to avoid
* having these references in daemon/configfile.c. */
int fc_configure(const oconfig_item_t *ci) { return ENOTSUP; }

int plugin_convert_values_to_metrics(__attribute__((unused))
value_list_t const *vl,
__attribute__((unused))
metrics_list_t **ml) {
return ENOTSUP;
}

void destroy_metrics_list(metrics_list_t *ml) {
while (ml != NULL) {
identity_destroy(ml->metric.identity);
meta_data_destroy(ml->metric.meta);

metrics_list_t *next = ml->next_p;
free(ml);
ml = next;
}
}
261 changes: 136 additions & 125 deletions src/utils/format_json/format_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -376,131 +376,6 @@ static int value_list_to_json(char *buffer, size_t buffer_size, /* {{{ */
return 0;
} /* }}} int value_list_to_json */


int format_json_metric(char *buffer_p, size_t *ret_buffer_fill, /* {{{ */
size_t *ret_buffer_free, const metric_t *metric_p,
int store_rates) {
char temp[1024];
gauge_t rate = -1;
int status = 0;

if ((buffer_p == NULL) || (ret_buffer_fill == NULL) ||
(ret_buffer_free == NULL) || (metric_p == NULL) ||
(metric_p->identity == NULL) || (metric_p->ds == NULL))
return -EINVAL;

if (*ret_buffer_free < 3)
return -ENOMEM;

/* Respect high water marks and fere size */
char *buffer = buffer_p + (*ret_buffer_fill);
size_t buffer_size = *ret_buffer_free;
size_t offset = 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)

#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)

/* Designed to be called multiple times, as when adding metrics from a
metrics_lisat_t object. When finalizing, the initial leading comma
will be replaced by a [ */
BUFFER_ADD(",{");
BUFFER_ADD_KEYVAL("name", metric_p->identity->name);
if (metric_p->value_type == DS_TYPE_GAUGE) {
if (isfinite(metric_p->value.gauge))
BUFFER_ADD(JSON_GAUGE_FORMAT, metric_p->value.gauge);
else
BUFFER_ADD("null");
} else if (store_rates) {
if (rate == -1)
status = uc_get_rate(metric_p, &rate);
if (status != 0) {
WARNING("utils_format_json: uc_get_rate failed.");
buffer_p[*ret_buffer_fill] = '0';
return -1;
}

if (isfinite(rate))
BUFFER_ADD(JSON_GAUGE_FORMAT, rate);
else
BUFFER_ADD("null");
} else if (metric_p->value_type == DS_TYPE_COUNTER)
BUFFER_ADD("%" PRIu64, (uint64_t)metric_p->value.counter);
else if (metric_p->value_type == DS_TYPE_DERIVE)
BUFFER_ADD("%" PRIi64, metric_p->value.derive);
else if (metric_p->value_type == DS_TYPE_ABSOLUTE)
BUFFER_ADD("%" PRIu64, metric_p->value.absolute);
else {
ERROR("format_json: Unknown data source type: %i", metric_p->value_type);
buffer_p[*ret_buffer_fill] = '0';
return -1;
}

BUFFER_ADD(",\"time\":%.3f", CDTIME_T_TO_DOUBLE(metric_p->time));
BUFFER_ADD(",\"interval\":%.3f", CDTIME_T_TO_DOUBLE(metric_p->interval));
BUFFER_ADD_KEYVAL("plugin", metric_p->plugin);
BUFFER_ADD_KEYVAL("type", metric_p->type);
BUFFER_ADD_KEYVAL("dsname", metric_p->ds->name);
BUFFER_ADD_KEYVAL("dstype", DS_TYPE_TO_STRING(metric_p->value_type));

if (metric_p->identity->root_p != NULL) {
c_avl_iterator_t *iter_p = c_avl_get_iterator(metric_p->identity->root_p);
if (iter_p != NULL) {
char *key_p = NULL;
char *value_p = NULL;
BUFFER_ADD("\"labels\":{");
while ((c_avl_iterator_next(iter_p, (void **)&key_p,
(void **)&value_p)) == 0) {
if ((key_p != NULL) && (value_p != NULL)) {
BUFFER_ADD_KEYVAL(key_p, value_p);
}
}
BUFFER_ADD("},");
c_avl_iterator_destroy(iter_p);
}
}

if (metric_p->meta != NULL) {
char meta_buffer[buffer_size];
memset(meta_buffer, 0, sizeof(meta_buffer));
status = meta_data_to_json(meta_buffer, sizeof(meta_buffer),
metric_p->meta->meta);
if (status != 0) {
buffer_p[*ret_buffer_fill] = '0';
return status;
}

BUFFER_ADD(",\"meta\":%s", meta_buffer);
} /* if (vl->meta != NULL) */

BUFFER_ADD("}");

#undef BUFFER_ADD_KEYVAL
#undef BUFFER_ADD

/* Update hihg water mark and free size */
(*ret_buffer_fill) += offset;
(*ret_buffer_free) -= offset;

return 0;
} /* }}} */


static int format_json_value_list_nocheck(char *buffer, /* {{{ */
size_t *ret_buffer_fill,
size_t *ret_buffer_free,
Expand Down Expand Up @@ -668,6 +543,142 @@ static int format_time(yajl_gen g, cdtime_t t) /* {{{ */
return 0;
} /* }}} int format_time */

/* TODO(octo): format_metric should export the interval, too. */
/* TODO(octo): Decide whether format_metric should export meta data. */
static int format_metric(yajl_gen g, metric_t const *m) {
CHECK_SUCCESS(yajl_gen_map_open(g)); /* BEGIN metric */

if (c_avl_size(m->identity->labels) != 0) {
JSON_ADD(g, "labels");
CHECK_SUCCESS(yajl_gen_map_open(g)); /* BEGIN labels */

c_avl_iterator_t *iter = c_avl_get_iterator(m->identity->labels);
char *k = NULL, *v = NULL;
while (c_avl_iterator_next(iter, (void *)&k, (void *)&v) == 0) {
JSON_ADD(g, k);
JSON_ADD(g, v);
}
c_avl_iterator_destroy(iter);

CHECK_SUCCESS(yajl_gen_map_close(g)); /* END labels */
}

if (m->time != 0) {
JSON_ADD(g, "timestamp_ms");
JSON_ADDF(g, "%" PRIu64, CDTIME_T_TO_MS(m->time));
}

strbuf_t buf = STRBUF_CREATE;
int status = value_marshal_text(&buf, m->value, m->value_type);
if (status != 0) {
STRBUF_DESTROY(buf);
return status;
}
JSON_ADD(g, "value");
JSON_ADD(g, buf.ptr);
STRBUF_DESTROY(buf);

CHECK_SUCCESS(yajl_gen_map_close(g)); /* END metric */

return 0;
}

/* format_metrics_list that all metrics in ml have the same name and value_type.
*/
static int format_metrics_list(yajl_gen g, metrics_list_t const *ml) {
CHECK_SUCCESS(yajl_gen_map_open(g)); /* BEGIN metric family */

metric_t const *m = &ml->metric;

JSON_ADD(g, "name");
JSON_ADD(g, m->identity->name);

char const *type = NULL;
switch (m->value_type) {
/* TODO(octo): handle store_rates. */
case VALUE_TYPE_GAUGE:
type = "GAUGE";
break;
case VALUE_TYPE_DERIVE:
case DS_TYPE_COUNTER:
type = "COUNTER";
break;
default:
ERROR("format_json_metric: Unknown value type: %d", m->value_type);
return EINVAL;
}
JSON_ADD(g, "type");
JSON_ADD(g, type);

JSON_ADD(g, "metrics");
CHECK_SUCCESS(yajl_gen_array_open(g));
for (metrics_list_t const *ptr = ml; ptr != NULL; ptr = ptr->next_p) {
int status = format_metric(g, &ptr->metric);
if (status != 0) {
return status;
}
}
CHECK_SUCCESS(yajl_gen_array_close(g));

CHECK_SUCCESS(yajl_gen_map_close(g)); /* END metric family */

return 0;
}

int format_json_metric(strbuf_t *buf, metric_t const *m, bool store_rates) {
if ((buf == NULL) || (m == NULL))
return EINVAL;

#if HAVE_YAJL_V2
yajl_gen g = yajl_gen_alloc(NULL);
if (g == NULL)
return -1;
#if COLLECT_DEBUG
yajl_gen_config(g, yajl_gen_beautify, 1);
yajl_gen_config(g, yajl_gen_validate_utf8, 1);
#endif

#else /* !HAVE_YAJL_V2 */
yajl_gen_config conf = {0};
#if COLLECT_DEBUG
conf.beautify = 1;
conf.indentString = " ";
#endif
yajl_gen g = yajl_gen_alloc(&conf, NULL);
if (g == NULL)
return -1;
#endif

yajl_gen_array_open(g);

int status = format_metrics_list(g, &(metrics_list_t){.metric = *m});
if (status != 0) {
yajl_gen_clear(g);
yajl_gen_free(g);
return status;
}

yajl_gen_array_close(g);

/* copy to output buffer */
unsigned char const *out = NULL;
#if HAVE_YAJL_V2
size_t unused_out_len = 0;
#else
unsigned int unused_out_len = 0;
#endif
if (yajl_gen_get_buf(g, &out, &unused_out_len) != yajl_gen_status_ok) {
yajl_gen_clear(g);
yajl_gen_free(g);
return -1;
}
status = strbuf_print(buf, (void *)out);

yajl_gen_clear(g);
yajl_gen_free(g);
return status;
} /* }}} format_json_metric */

static int format_alert(yajl_gen g, notification_t const *n) /* {{{ */
{
CHECK_SUCCESS(yajl_gen_array_open(g)); /* BEGIN array */
Expand Down
9 changes: 6 additions & 3 deletions src/utils/format_json/format_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "collectd.h"

#include "plugin.h"
#include "utils/strbuf/strbuf.h"

#ifndef JSON_GAUGE_FORMAT
#define JSON_GAUGE_FORMAT GAUGE_FORMAT
Expand All @@ -40,11 +41,13 @@ int format_json_initialize(char *buffer, size_t *ret_buffer_fill,
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_metric(char *buffer_p, size_t *ret_buffer_fill,
size_t *ret_buffer_free, const metric_t *metric_p,
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. */
int format_json_metric(strbuf_t *buf, metric_t const *m, bool store_rates);

int format_json_notification(char *buffer, size_t buffer_size,
notification_t const *n);

Expand Down
Loading

0 comments on commit eeb6ffa

Please sign in to comment.