blob: f517488dea7fa7625cd04b9e3b4b94388590a628 [file] [log] [blame]
/*
* Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology
* Copyright (c) 2024 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/net/prometheus/formatter.h>
#include <zephyr/net/prometheus/collector.h>
#include <zephyr/net/prometheus/metric.h>
#include <zephyr/net/prometheus/histogram.h>
#include <zephyr/net/prometheus/summary.h>
#include <zephyr/net/prometheus/gauge.h>
#include <zephyr/net/prometheus/counter.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(pm_formatter, CONFIG_PROMETHEUS_LOG_LEVEL);
static int write_metric_to_buffer(char *buffer, size_t buffer_size, const char *format, ...)
{
/* helper function to write formatted metric to buffer */
va_list args;
size_t len = strlen(buffer);
if (len >= buffer_size) {
return -ENOMEM;
}
buffer += len;
buffer_size -= len;
va_start(args, format);
len = vsnprintf(buffer, buffer_size, format, args);
va_end(args);
if (len >= buffer_size) {
return -ENOMEM;
}
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)
{
struct prometheus_metric *metric;
struct prometheus_metric *tmp;
int written = 0;
int ret = 0;
if (collector == NULL || buffer == NULL || buffer_size == 0) {
LOG_ERR("Invalid arguments");
return -EINVAL;
}
k_mutex_lock(&collector->lock, K_FOREVER);
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&collector->metrics, metric, tmp, node) {
/* If there is a user callback, use it to update the metric data. */
if (collector->user_cb) {
ret = collector->user_cb(collector, metric, collector->user_data);
if (ret < 0) {
if (ret == -EAGAIN) {
/* Skip this metric for now */
continue;
}
LOG_ERR("Error in user callback (%d)", ret);
goto out;
}
}
ret = prometheus_format_one_metric(metric, buffer, buffer_size, &written);
if (ret < 0) {
goto out;
}
}
out:
k_mutex_unlock(&collector->lock);
return ret;
}