net: prometheus: Add more parameters to metric macros

Add collector parameter to metric creation macros so that it
is possible to bind the metric to collector already at built
time.

Also add optional user_data to metric macro calls so that user
can add optional data there. This will be used by network statistics
Prometheus support in subsequent commits.

Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
This commit is contained in:
Jukka Rissanen 2024-11-20 18:31:22 +02:00 committed by Anas Nashif
commit c7989b5747
13 changed files with 117 additions and 17 deletions

View file

@ -26,6 +26,22 @@
#include <stddef.h>
struct prometheus_collector;
/**
* @typedef prometheus_scrape_cb_t
* @brief Callback used to scrape a collector for a specific metric.
*
* @param collector A valid pointer on the collector to scrape
* @param metric A valid pointer on the metric to scrape
* @param user_data A valid pointer to a user data or NULL
*
* @return 0 if successful, otherwise a negative error code.
*/
typedef int (*prometheus_scrape_cb_t)(struct prometheus_collector *collector,
struct prometheus_metric *metric,
void *user_data);
/**
* @brief Prometheus collector definition
*
@ -38,6 +54,12 @@ struct prometheus_collector {
sys_slist_t metrics;
/** Mutex to protect the metrics list manipulation */
struct k_mutex lock;
/** User callback function. If set, then the metric data is fetched
* via the function callback.
*/
prometheus_scrape_cb_t user_cb;
/** User data */
void *user_data;
};
/**
@ -46,12 +68,25 @@ struct prometheus_collector {
* This macro defines a Collector.
*
* @param _name The collector's name.
* @param ... Optional user callback function. If set, this function is called
* when the collector is scraped. The function should be of type
* prometheus_scrape_cb_t.
* Optional user data to pass to the user callback function.
*/
#define PROMETHEUS_COLLECTOR_DEFINE(_name) \
#define PROMETHEUS_COLLECTOR_DEFINE(_name, ...) \
STRUCT_SECTION_ITERABLE(prometheus_collector, _name) = { \
.name = STRINGIFY(_name), \
.metrics = SYS_SLIST_STATIC_INIT(&_name.metrics), \
.lock = Z_MUTEX_INITIALIZER(_name.lock), \
.user_cb = COND_CODE_0( \
NUM_VA_ARGS_LESS_1( \
LIST_DROP_EMPTY(__VA_ARGS__, _)), \
(NULL), \
(GET_ARG_N(1, __VA_ARGS__))), \
.user_data = COND_CODE_0( \
NUM_VA_ARGS_LESS_1(__VA_ARGS__), (NULL), \
(GET_ARG_N(1, \
GET_ARGS_LESS_N(1, __VA_ARGS__)))), \
}
/**

View file

@ -33,6 +33,8 @@ struct prometheus_counter {
struct prometheus_metric base;
/** Value of the Prometheus counter metric */
uint64_t value;
/** User data */
void *user_data;
};
/**
@ -44,22 +46,30 @@ struct prometheus_counter {
* @param _name The counter metric name
* @param _desc Counter description
* @param _label Label for the metric. Additional labels can be added at runtime.
* @param _collector Collector to map this metric. Can be set to NULL if it not yet known.
* @param ... Optional user data specific to this metric instance.
*
* Example usage:
* @code{.c}
*
* PROMETHEUS_COUNTER_DEFINE(http_request_counter, "HTTP request counter",
* ({ .key = "http_request", .value = "request_count" }));
* ({ .key = "http_request", .value = "request_count" }),
* NULL);
* @endcode
*/
#define PROMETHEUS_COUNTER_DEFINE(_name, _desc, _label) \
#define PROMETHEUS_COUNTER_DEFINE(_name, _desc, _label, _collector, ...) \
STRUCT_SECTION_ITERABLE(prometheus_counter, _name) = { \
.base.name = STRINGIFY(_name), \
.base.type = PROMETHEUS_COUNTER, \
.base.description = _desc, \
.base.labels[0] = __DEBRACKET _label, \
.base.num_labels = 1, \
.base.collector = _collector, \
.value = 0ULL, \
.user_data = COND_CODE_0( \
NUM_VA_ARGS_LESS_1(LIST_DROP_EMPTY(__VA_ARGS__, _)), \
(NULL), \
(GET_ARG_N(1, __VA_ARGS__))), \
}
/**

View file

@ -31,6 +31,8 @@ struct prometheus_gauge {
struct prometheus_metric base;
/** Value of the Prometheus gauge metric */
double value;
/** User data */
void *user_data;
};
/**
@ -42,23 +44,31 @@ struct prometheus_gauge {
* @param _name The gauge metric name.
* @param _desc Gauge description
* @param _label Label for the metric. Additional labels can be added at runtime.
* @param _collector Collector to map this metric. Can be set to NULL if it not yet known.
* @param ... Optional user data specific to this metric instance.
*
* Example usage:
* @code{.c}
*
* PROMETHEUS_GAUGE_DEFINE(http_request_gauge, "HTTP request gauge",
* ({ .key = "http_request", .value = "request_count" }));
* ({ .key = "http_request", .value = "request_count" }),
* NULL);
*
* @endcode
*/
#define PROMETHEUS_GAUGE_DEFINE(_name, _desc, _label) \
#define PROMETHEUS_GAUGE_DEFINE(_name, _desc, _label, _collector, ...) \
STRUCT_SECTION_ITERABLE(prometheus_gauge, _name) = { \
.base.name = STRINGIFY(_name), \
.base.type = PROMETHEUS_GAUGE, \
.base.description = _desc, \
.base.labels[0] = __DEBRACKET _label, \
.base.num_labels = 1, \
.base.collector = _collector, \
.value = 0.0, \
.user_data = COND_CODE_0( \
NUM_VA_ARGS_LESS_1(LIST_DROP_EMPTY(__VA_ARGS__, _)), \
(NULL), \
(GET_ARG_N(1, __VA_ARGS__))), \
}
/**

View file

@ -51,6 +51,8 @@ struct prometheus_histogram {
double sum;
/** Total count of observations in the histogram */
unsigned long count;
/** User data */
void *user_data;
};
/**
@ -62,26 +64,34 @@ struct prometheus_histogram {
* @param _name The histogram metric name.
* @param _desc Histogram description
* @param _label Label for the metric. Additional labels can be added at runtime.
* @param _collector Collector to map this metric. Can be set to NULL if it not yet known.
* @param ... Optional user data specific to this metric instance.
*
* Example usage:
* @code{.c}
*
* PROMETHEUS_HISTOGRAM_DEFINE(http_request_histogram, "HTTP request histogram",
* ({ .key = "request_latency", .value = "request_latency_seconds" }));
* ({ .key = "request_latency", .value = "request_latency_seconds" }),
* NULL);
*
* @endcode
*/
#define PROMETHEUS_HISTOGRAM_DEFINE(_name, _desc, _label) \
#define PROMETHEUS_HISTOGRAM_DEFINE(_name, _desc, _label, _collector, ...) \
STRUCT_SECTION_ITERABLE(prometheus_histogram, _name) = { \
.base.name = STRINGIFY(_name), \
.base.type = PROMETHEUS_HISTOGRAM, \
.base.description = _desc, \
.base.labels[0] = __DEBRACKET _label, \
.base.num_labels = 1, \
.base.collector = _collector, \
.buckets = NULL, \
.num_buckets = 0, \
.sum = 0.0, \
.count = 0U, \
.user_data = COND_CODE_0( \
NUM_VA_ARGS_LESS_1(LIST_DROP_EMPTY(__VA_ARGS__, _)), \
(NULL), \
(GET_ARG_N(1, __VA_ARGS__))), \
}
/**

View file

@ -49,6 +49,11 @@ struct prometheus_metric {
sys_snode_t node;
/** Back pointer to the collector that this metric belongs to */
struct prometheus_collector *collector;
/** Back pointer to the actual metric (counter, gauge, etc.).
* This is just a temporary solution, ultimate goal is to place
* this generic metrict struct into the actual metric struct.
*/
void *metric;
/** Type of the Prometheus metric. */
enum prometheus_metric_type type;
/** Name of the Prometheus metric. */
@ -59,6 +64,8 @@ struct prometheus_metric {
struct prometheus_label labels[MAX_PROMETHEUS_LABELS_PER_METRIC];
/** Number of labels associated with the Prometheus metric. */
int num_labels;
/** User defined data */
void *user_data;
/* Add any other necessary fields */
};

View file

@ -32,6 +32,8 @@ struct prometheus_summary_quantile {
double quantile;
/** Value of the quantile */
double value;
/** User data */
void *user_data;
};
/**
@ -51,6 +53,8 @@ struct prometheus_summary {
double sum;
/** Total count of observations in the summary metric */
unsigned long count;
/** User data */
void *user_data;
};
/**
@ -62,6 +66,8 @@ struct prometheus_summary {
* @param _name The summary metric name.
* @param _desc Summary description
* @param _label Label for the metric. Additional labels can be added at runtime.
* @param _collector Collector to map this metric. Can be set to NULL if it not yet known.
* @param ... Optional user data specific to this metric instance.
*
*
* Example usage:
@ -69,22 +75,27 @@ struct prometheus_summary {
*
* PROMETHEUS_SUMMARY_DEFINE(http_request_summary, "HTTP request summary",
* ({ .key = "request_latency",
* .value = "request_latency_seconds" }));
* .value = "request_latency_seconds" }), NULL);
*
* @endcode
*/
#define PROMETHEUS_SUMMARY_DEFINE(_name, _desc, _label) \
#define PROMETHEUS_SUMMARY_DEFINE(_name, _desc, _label, _collector, ...) \
STRUCT_SECTION_ITERABLE(prometheus_summary, _name) = { \
.base.name = STRINGIFY(_name), \
.base.type = PROMETHEUS_SUMMARY, \
.base.description = _desc, \
.base.labels[0] = __DEBRACKET _label, \
.base.num_labels = 1, \
.base.collector = _collector, \
.quantiles = NULL, \
.num_quantiles = 0, \
.sum = 0.0, \
.count = 0U, \
.user_data = COND_CODE_0( \
NUM_VA_ARGS_LESS_1(LIST_DROP_EMPTY(__VA_ARGS__, _)), \
(NULL), \
(GET_ARG_N(1, __VA_ARGS__))), \
}
/**

View file

@ -146,7 +146,7 @@ static void setup_tls(void)
}
PROMETHEUS_COUNTER_DEFINE(http_request_counter, "HTTP request counter",
({ .key = "http_request", .value = "request_count" }));
({ .key = "http_request", .value = "request_count" }), NULL);
PROMETHEUS_COLLECTOR_DEFINE(test_collector);

View file

@ -63,6 +63,20 @@ int prometheus_format_exposition(struct prometheus_collector *collector, char *b
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;
}
}
/* write HELP line if available */
if (metric->description[0] != '\0') {
ret = write_metric_to_buffer(buffer + written, buffer_size - written,

View file

@ -10,7 +10,7 @@
#include <zephyr/net/prometheus/collector.h>
PROMETHEUS_COUNTER_DEFINE(test_counter_m, "Test counter",
({ .key = "test_counter", .value = "test" }));
({ .key = "test_counter", .value = "test" }), NULL);
PROMETHEUS_COLLECTOR_DEFINE(test_custom_collector);
@ -31,9 +31,11 @@ ZTEST(test_collector, test_prometheus_collector_register)
prometheus_collector_register_metric(&test_custom_collector, &test_counter_m.base);
counter = (struct prometheus_counter *)prometheus_collector_get_metric(
&test_custom_collector, "test_counter");
&test_custom_collector, "test_counter_m");
zassert_equal(counter, &test_counter_m, "Counter not found in collector");
zassert_equal_ptr(counter, &test_counter_m,
"Counter not found in collector (expected %p, got %p)",
&test_counter_m, counter);
zassert_equal(test_counter_m.value, 0, "Counter value is not 0");

View file

@ -9,7 +9,8 @@
#include <zephyr/net/prometheus/counter.h>
PROMETHEUS_COUNTER_DEFINE(test_counter_m, "Test counter",
({ .key = "test_counter", .value = "test" }));
({ .key = "test_counter", .value = "test" }),
NULL);
/**
* @brief Test prometheus_counter_inc

View file

@ -9,7 +9,7 @@
#include <zephyr/net/prometheus/gauge.h>
PROMETHEUS_GAUGE_DEFINE(test_gauge_m, "Test gauge",
({ .key = "test", .value = "gauge" }));
({ .key = "test", .value = "gauge" }), NULL);
/**
* @brief Test prometheus_gauge_set

View file

@ -9,7 +9,7 @@
#include <zephyr/net/prometheus/histogram.h>
PROMETHEUS_HISTOGRAM_DEFINE(test_histogram_m, "Test histogram",
({ .key = "test", .value = "histogram" }));
({ .key = "test", .value = "histogram" }), NULL);
/**
* @brief Test prometheus_histogram_observe

View file

@ -9,7 +9,7 @@
#include <zephyr/net/prometheus/summary.h>
PROMETHEUS_SUMMARY_DEFINE(test_summary_m, "Test summary",
({ .key = "test", .value = "summary" }));
({ .key = "test", .value = "summary" }), NULL);
/**
* @brief Test prometheus_summary_observe