diff --git a/include/zephyr/net/prometheus/collector.h b/include/zephyr/net/prometheus/collector.h index e63dc7aa827..876ed706338 100644 --- a/include/zephyr/net/prometheus/collector.h +++ b/include/zephyr/net/prometheus/collector.h @@ -26,6 +26,22 @@ #include +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__)))), \ } /** diff --git a/include/zephyr/net/prometheus/counter.h b/include/zephyr/net/prometheus/counter.h index 89c1ff123d3..591e7ec25c5 100644 --- a/include/zephyr/net/prometheus/counter.h +++ b/include/zephyr/net/prometheus/counter.h @@ -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__))), \ } /** diff --git a/include/zephyr/net/prometheus/gauge.h b/include/zephyr/net/prometheus/gauge.h index dd0f4dee4da..0703e3818a7 100644 --- a/include/zephyr/net/prometheus/gauge.h +++ b/include/zephyr/net/prometheus/gauge.h @@ -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__))), \ } /** diff --git a/include/zephyr/net/prometheus/histogram.h b/include/zephyr/net/prometheus/histogram.h index 54c70632305..dbf5538f96e 100644 --- a/include/zephyr/net/prometheus/histogram.h +++ b/include/zephyr/net/prometheus/histogram.h @@ -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__))), \ } /** diff --git a/include/zephyr/net/prometheus/metric.h b/include/zephyr/net/prometheus/metric.h index c9f329e5082..bbe3a0e3eb7 100644 --- a/include/zephyr/net/prometheus/metric.h +++ b/include/zephyr/net/prometheus/metric.h @@ -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 */ }; diff --git a/include/zephyr/net/prometheus/summary.h b/include/zephyr/net/prometheus/summary.h index ea1d5f1d087..d36b34c643c 100644 --- a/include/zephyr/net/prometheus/summary.h +++ b/include/zephyr/net/prometheus/summary.h @@ -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__))), \ } /** diff --git a/samples/net/prometheus/src/main.c b/samples/net/prometheus/src/main.c index e554279cc83..161368bd072 100644 --- a/samples/net/prometheus/src/main.c +++ b/samples/net/prometheus/src/main.c @@ -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); diff --git a/subsys/net/lib/prometheus/formatter.c b/subsys/net/lib/prometheus/formatter.c index dd29e3d3cac..65a26366bb9 100644 --- a/subsys/net/lib/prometheus/formatter.c +++ b/subsys/net/lib/prometheus/formatter.c @@ -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, diff --git a/tests/net/lib/prometheus/collector/src/main.c b/tests/net/lib/prometheus/collector/src/main.c index 613ae3bb004..f005d9dd860 100644 --- a/tests/net/lib/prometheus/collector/src/main.c +++ b/tests/net/lib/prometheus/collector/src/main.c @@ -10,7 +10,7 @@ #include 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"); diff --git a/tests/net/lib/prometheus/counter/src/main.c b/tests/net/lib/prometheus/counter/src/main.c index 5830c8da973..c869daf8f3e 100644 --- a/tests/net/lib/prometheus/counter/src/main.c +++ b/tests/net/lib/prometheus/counter/src/main.c @@ -9,7 +9,8 @@ #include PROMETHEUS_COUNTER_DEFINE(test_counter_m, "Test counter", - ({ .key = "test_counter", .value = "test" })); + ({ .key = "test_counter", .value = "test" }), + NULL); /** * @brief Test prometheus_counter_inc diff --git a/tests/net/lib/prometheus/gauge/src/main.c b/tests/net/lib/prometheus/gauge/src/main.c index 08ee1b74966..acf8481ed72 100644 --- a/tests/net/lib/prometheus/gauge/src/main.c +++ b/tests/net/lib/prometheus/gauge/src/main.c @@ -9,7 +9,7 @@ #include PROMETHEUS_GAUGE_DEFINE(test_gauge_m, "Test gauge", - ({ .key = "test", .value = "gauge" })); + ({ .key = "test", .value = "gauge" }), NULL); /** * @brief Test prometheus_gauge_set diff --git a/tests/net/lib/prometheus/histogram/src/main.c b/tests/net/lib/prometheus/histogram/src/main.c index 934dfe08e79..8be4d0c8dc4 100644 --- a/tests/net/lib/prometheus/histogram/src/main.c +++ b/tests/net/lib/prometheus/histogram/src/main.c @@ -9,7 +9,7 @@ #include PROMETHEUS_HISTOGRAM_DEFINE(test_histogram_m, "Test histogram", - ({ .key = "test", .value = "histogram" })); + ({ .key = "test", .value = "histogram" }), NULL); /** * @brief Test prometheus_histogram_observe diff --git a/tests/net/lib/prometheus/summary/src/main.c b/tests/net/lib/prometheus/summary/src/main.c index b8be3f3dd12..e1246b04dab 100644 --- a/tests/net/lib/prometheus/summary/src/main.c +++ b/tests/net/lib/prometheus/summary/src/main.c @@ -9,7 +9,7 @@ #include PROMETHEUS_SUMMARY_DEFINE(test_summary_m, "Test summary", - ({ .key = "test", .value = "summary" })); + ({ .key = "test", .value = "summary" }), NULL); /** * @brief Test prometheus_summary_observe