From 34ed76b1859d8e181af0195ee30dd197d1130564 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 21 Nov 2024 18:30:54 +0200 Subject: [PATCH] net: prometheus: Add way to format output by a metric Instead of requiring one big buffer for formatting the output, have a walk function that can be used to generate output by one metric at a time. Signed-off-by: Jukka Rissanen --- include/zephyr/net/prometheus/collector.h | 54 ++++ include/zephyr/net/prometheus/formatter.h | 18 +- subsys/net/lib/prometheus/collector.c | 75 +++++ subsys/net/lib/prometheus/formatter.c | 378 +++++++++++----------- 4 files changed, 342 insertions(+), 183 deletions(-) diff --git a/include/zephyr/net/prometheus/collector.h b/include/zephyr/net/prometheus/collector.h index 876ed706338..08d93cf48d9 100644 --- a/include/zephyr/net/prometheus/collector.h +++ b/include/zephyr/net/prometheus/collector.h @@ -116,6 +116,60 @@ int prometheus_collector_register_metric(struct prometheus_collector *collector, const void *prometheus_collector_get_metric(struct prometheus_collector *collector, const char *name); +/** @cond INTERNAL_HIDDEN */ + +enum prometheus_walk_state { + PROMETHEUS_WALK_START, + PROMETHEUS_WALK_CONTINUE, + PROMETHEUS_WALK_STOP, +}; + +struct prometheus_collector_walk_context { + struct prometheus_collector *collector; + struct prometheus_metric *metric; + struct prometheus_metric *tmp; + enum prometheus_walk_state state; +}; + +/** @endcond */ + +/** + * @brief Walk through all metrics in a Prometheus collector and format them + * into a buffer. + * + * @param ctx Pointer to the walker context. + * @param buffer Pointer to the buffer to store the formatted metrics. + * @param buffer_size Size of the buffer. + * @return 0 if successful and we went through all metrics, -EAGAIN if we + * need to call this function again, any other negative error code + * means an error occurred. + */ +int prometheus_collector_walk_metrics(struct prometheus_collector_walk_context *ctx, + uint8_t *buffer, size_t buffer_size); + +/** + * @brief Initialize the walker context to walk through all metrics. + * + * @param ctx Pointer to the walker context. + * @param collector Pointer to the collector to walk through. + * + * @return 0 if successful, otherwise a negative error code. + */ +static inline int prometheus_collector_walk_init(struct prometheus_collector_walk_context *ctx, + struct prometheus_collector *collector) +{ + if (collector == NULL) { + return -EINVAL; + } + + ctx->collector = collector; + ctx->state = PROMETHEUS_WALK_START; + ctx->metric = NULL; + ctx->tmp = NULL; + + return 0; +} + /** * @} */ diff --git a/include/zephyr/net/prometheus/formatter.h b/include/zephyr/net/prometheus/formatter.h index b88f1dcf028..393fe4c6689 100644 --- a/include/zephyr/net/prometheus/formatter.h +++ b/include/zephyr/net/prometheus/formatter.h @@ -22,7 +22,7 @@ * @brief Format exposition data for Prometheus * * Formats the exposition data collected by the specified collector into the provided buffer. - * Function to format metric data according to Prometheus text-based format + * Function will format metric data according to Prometheus text-based format * * @param collector Pointer to the collector containing the data to format. * @param buffer Pointer to the buffer where the formatted exposition data will be stored. @@ -33,6 +33,22 @@ int prometheus_format_exposition(struct prometheus_collector *collector, char *buffer, size_t buffer_size); +/** + * @brief Format exposition data for one metric for Prometheus + * + * Formats the exposition data of one specific metric into the provided buffer. + * Function will format metric data according to Prometheus text-based format. + * + * @param metric Pointer to the metric containing the data to format. + * @param buffer Pointer to the buffer where the formatted exposition data will be stored. + * @param buffer_size Size of the buffer. + * @param written How many bytes have been written to the buffer. + * + * @return 0 on success, negative errno on error. + */ +int prometheus_format_one_metric(struct prometheus_metric *metric, char *buffer, + size_t buffer_size, int *written); + /** * @} */ diff --git a/subsys/net/lib/prometheus/collector.c b/subsys/net/lib/prometheus/collector.c index cbfee1c6422..f2a0e2500f2 100644 --- a/subsys/net/lib/prometheus/collector.c +++ b/subsys/net/lib/prometheus/collector.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -157,3 +158,77 @@ const void *prometheus_collector_get_metric(struct prometheus_collector *collect out: return NULL; } + +int prometheus_collector_walk_metrics(struct prometheus_collector_walk_context *ctx, + uint8_t *buffer, size_t buffer_size) +{ + int ret = 0; + + if (ctx->collector == NULL) { + LOG_ERR("Invalid arguments"); + return -EINVAL; + } + + if (ctx->state == PROMETHEUS_WALK_START) { + k_mutex_lock(&ctx->collector->lock, K_FOREVER); + ctx->state = PROMETHEUS_WALK_CONTINUE; + + /* Start of the loop is taken from + * SYS_SLIST_FOR_EACH_CONTAINER_SAFE macro to simulate + * a loop. + */ + + ctx->metric = Z_GENLIST_PEEK_HEAD_CONTAINER(slist, + &ctx->collector->metrics, + ctx->metric, + node); + ctx->tmp = Z_GENLIST_PEEK_NEXT_CONTAINER(slist, + ctx->metric, + node); + } + + if (ctx->state == PROMETHEUS_WALK_CONTINUE) { + int len = 0; + + ctx->metric = ctx->tmp; + ctx->tmp = Z_GENLIST_PEEK_NEXT_CONTAINER(slist, + ctx->metric, + node); + + if (ctx->metric == NULL) { + ctx->state = PROMETHEUS_WALK_STOP; + goto out; + } + + /* If there is a user callback, use it to update the metric data. */ + if (ctx->collector->user_cb) { + ret = ctx->collector->user_cb(ctx->collector, ctx->metric, + ctx->collector->user_data); + if (ret < 0) { + if (ret != -EAGAIN) { + ctx->state = PROMETHEUS_WALK_STOP; + goto out; + } + + /* Skip this metric for now */ + goto out; + } + } + + ret = prometheus_format_one_metric(ctx->metric, buffer, buffer_size, &len); + if (ret < 0) { + ctx->state = PROMETHEUS_WALK_STOP; + goto out; + } + + ret = -EAGAIN; + } + +out: + if (ctx->state == PROMETHEUS_WALK_STOP) { + k_mutex_unlock(&ctx->collector->lock); + ret = 0; + } + + return ret; +} diff --git a/subsys/net/lib/prometheus/formatter.c b/subsys/net/lib/prometheus/formatter.c index 65a26366bb9..f517488dea7 100644 --- a/subsys/net/lib/prometheus/formatter.c +++ b/subsys/net/lib/prometheus/formatter.c @@ -46,6 +46,200 @@ static int write_metric_to_buffer(char *buffer, size_t buffer_size, const char * return 0; } +int prometheus_format_one_metric(struct prometheus_metric *metric, char *buffer, + size_t buffer_size, int *written) +{ + int ret = 0; + + /* write HELP line if available */ + if (metric->description[0] != '\0') { + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "# HELP %s %s\n", metric->name, + metric->description); + if (ret < 0) { + LOG_ERR("Error writing to buffer"); + goto out; + } + } + + /* write TYPE line */ + switch (metric->type) { + case PROMETHEUS_COUNTER: + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "# TYPE %s counter\n", metric->name); + if (ret < 0) { + LOG_ERR("Error writing counter"); + goto out; + } + + break; + + case PROMETHEUS_GAUGE: + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "# TYPE %s gauge\n", metric->name); + if (ret < 0) { + LOG_ERR("Error writing gauge"); + goto out; + } + + break; + + case PROMETHEUS_HISTOGRAM: + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "# TYPE %s histogram\n", metric->name); + if (ret < 0) { + LOG_ERR("Error writing histogram"); + goto out; + } + + break; + + case PROMETHEUS_SUMMARY: + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "# TYPE %s summary\n", metric->name); + if (ret < 0) { + LOG_ERR("Error writing summary"); + goto out; + } + + break; + + default: + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "# TYPE %s untyped\n", metric->name); + if (ret < 0) { + LOG_ERR("Error writing untyped"); + goto out; + } + + break; + } + + /* write metric-specific fields */ + switch (metric->type) { + case PROMETHEUS_COUNTER: { + const struct prometheus_counter *counter = + CONTAINER_OF(metric, struct prometheus_counter, base); + + LOG_DBG("counter->value: %llu", counter->value); + + for (int i = 0; i < metric->num_labels; ++i) { + ret = write_metric_to_buffer( + buffer + *written, buffer_size - *written, + "%s{%s=\"%s\"} %llu\n", metric->name, metric->labels[i].key, + metric->labels[i].value, counter->value); + if (ret < 0) { + LOG_ERR("Error writing counter"); + goto out; + } + } + + break; + } + + case PROMETHEUS_GAUGE: { + const struct prometheus_gauge *gauge = + CONTAINER_OF(metric, struct prometheus_gauge, base); + + LOG_DBG("gauge->value: %f", gauge->value); + + for (int i = 0; i < metric->num_labels; ++i) { + ret = write_metric_to_buffer( + buffer + *written, buffer_size - *written, + "%s{%s=\"%s\"} %f\n", metric->name, metric->labels[i].key, + metric->labels[i].value, gauge->value); + if (ret < 0) { + LOG_ERR("Error writing gauge"); + goto out; + } + } + + break; + } + + case PROMETHEUS_HISTOGRAM: { + const struct prometheus_histogram *histogram = + CONTAINER_OF(metric, struct prometheus_histogram, base); + + LOG_DBG("histogram->count: %lu", histogram->count); + + for (int i = 0; i < histogram->num_buckets; ++i) { + ret = write_metric_to_buffer( + buffer + *written, buffer_size - *written, + "%s_bucket{le=\"%f\"} %lu\n", metric->name, + histogram->buckets[i].upper_bound, + histogram->buckets[i].count); + if (ret < 0) { + LOG_ERR("Error writing histogram"); + goto out; + } + } + + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "%s_sum %f\n", metric->name, histogram->sum); + if (ret < 0) { + LOG_ERR("Error writing histogram"); + goto out; + } + + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "%s_count %lu\n", metric->name, + histogram->count); + if (ret < 0) { + LOG_ERR("Error writing histogram"); + goto out; + } + + break; + } + + case PROMETHEUS_SUMMARY: { + const struct prometheus_summary *summary = + CONTAINER_OF(metric, struct prometheus_summary, base); + + LOG_DBG("summary->count: %lu", summary->count); + + for (int i = 0; i < summary->num_quantiles; ++i) { + ret = write_metric_to_buffer( + buffer + *written, buffer_size - *written, + "%s{%s=\"%f\"} %f\n", metric->name, "quantile", + summary->quantiles[i].quantile, + summary->quantiles[i].value); + if (ret < 0) { + LOG_ERR("Error writing summary"); + goto out; + } + } + + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "%s_sum %f\n", metric->name, summary->sum); + if (ret < 0) { + LOG_ERR("Error writing summary"); + goto out; + } + + ret = write_metric_to_buffer(buffer + *written, buffer_size - *written, + "%s_count %lu\n", metric->name, + summary->count); + if (ret < 0) { + LOG_ERR("Error writing summary"); + goto out; + } + + break; + } + + default: + /* should not happen */ + LOG_ERR("Unsupported metric type %d", metric->type); + ret = -EINVAL; + goto out; + } + +out: + return ret; +} + int prometheus_format_exposition(struct prometheus_collector *collector, char *buffer, size_t buffer_size) { @@ -77,188 +271,8 @@ int prometheus_format_exposition(struct prometheus_collector *collector, char *b } } - /* write HELP line if available */ - if (metric->description[0] != '\0') { - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "# HELP %s %s\n", metric->name, - metric->description); - if (ret < 0) { - LOG_ERR("Error writing to buffer"); - goto out; - } - } - - /* write TYPE line */ - switch (metric->type) { - case PROMETHEUS_COUNTER: - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "# TYPE %s counter\n", metric->name); - if (ret < 0) { - LOG_ERR("Error writing counter"); - goto out; - } - - break; - - case PROMETHEUS_GAUGE: - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "# TYPE %s gauge\n", metric->name); - if (ret < 0) { - LOG_ERR("Error writing gauge"); - goto out; - } - - break; - - case PROMETHEUS_HISTOGRAM: - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "# TYPE %s histogram\n", metric->name); - if (ret < 0) { - LOG_ERR("Error writing histogram"); - goto out; - } - - break; - - case PROMETHEUS_SUMMARY: - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "# TYPE %s summary\n", metric->name); - if (ret < 0) { - LOG_ERR("Error writing summary"); - goto out; - } - - break; - - default: - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "# TYPE %s untyped\n", metric->name); - if (ret < 0) { - LOG_ERR("Error writing untyped"); - goto out; - } - - break; - } - - /* write metric-specific fields */ - switch (metric->type) { - case PROMETHEUS_COUNTER: { - const struct prometheus_counter *counter = - CONTAINER_OF(metric, struct prometheus_counter, base); - - LOG_DBG("counter->value: %llu", counter->value); - - for (int i = 0; i < metric->num_labels; ++i) { - ret = write_metric_to_buffer( - buffer + written, buffer_size - written, - "%s{%s=\"%s\"} %llu\n", metric->name, metric->labels[i].key, - metric->labels[i].value, counter->value); - if (ret < 0) { - LOG_ERR("Error writing counter"); - goto out; - } - } - - break; - } - - case PROMETHEUS_GAUGE: { - const struct prometheus_gauge *gauge = - CONTAINER_OF(metric, struct prometheus_gauge, base); - - LOG_DBG("gauge->value: %f", gauge->value); - - for (int i = 0; i < metric->num_labels; ++i) { - ret = write_metric_to_buffer( - buffer + written, buffer_size - written, - "%s{%s=\"%s\"} %f\n", metric->name, metric->labels[i].key, - metric->labels[i].value, gauge->value); - if (ret < 0) { - LOG_ERR("Error writing gauge"); - goto out; - } - } - - break; - } - - case PROMETHEUS_HISTOGRAM: { - const struct prometheus_histogram *histogram = - CONTAINER_OF(metric, struct prometheus_histogram, base); - - LOG_DBG("histogram->count: %lu", histogram->count); - - for (int i = 0; i < histogram->num_buckets; ++i) { - ret = write_metric_to_buffer( - buffer + written, buffer_size - written, - "%s_bucket{le=\"%f\"} %lu\n", metric->name, - histogram->buckets[i].upper_bound, - histogram->buckets[i].count); - if (ret < 0) { - LOG_ERR("Error writing histogram"); - goto out; - } - } - - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "%s_sum %f\n", metric->name, histogram->sum); - if (ret < 0) { - LOG_ERR("Error writing histogram"); - goto out; - } - - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "%s_count %lu\n", metric->name, - histogram->count); - if (ret < 0) { - LOG_ERR("Error writing histogram"); - goto out; - } - - break; - } - - case PROMETHEUS_SUMMARY: { - const struct prometheus_summary *summary = - CONTAINER_OF(metric, struct prometheus_summary, base); - - LOG_DBG("summary->count: %lu", summary->count); - - for (int i = 0; i < summary->num_quantiles; ++i) { - ret = write_metric_to_buffer( - buffer + written, buffer_size - written, - "%s{%s=\"%f\"} %f\n", metric->name, "quantile", - summary->quantiles[i].quantile, - summary->quantiles[i].value); - if (ret < 0) { - LOG_ERR("Error writing summary"); - goto out; - } - } - - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "%s_sum %f\n", metric->name, summary->sum); - if (ret < 0) { - LOG_ERR("Error writing summary"); - goto out; - } - - ret = write_metric_to_buffer(buffer + written, buffer_size - written, - "%s_count %lu\n", metric->name, - summary->count); - if (ret < 0) { - LOG_ERR("Error writing summary"); - goto out; - } - - break; - } - - default: - /* should not happen */ - LOG_ERR("Unsupported metric type %d", metric->type); - ret = -EINVAL; + ret = prometheus_format_one_metric(metric, buffer, buffer_size, &written); + if (ret < 0) { goto out; } }