diff --git a/include/zephyr/net/prometheus/collector.h b/include/zephyr/net/prometheus/collector.h new file mode 100644 index 0000000000000..906eb8b161bec --- /dev/null +++ b/include/zephyr/net/prometheus/collector.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PROMETHEUS_COLLECTOR_H_ +#define ZEPHYR_INCLUDE_PROMETHEUS_COLLECTOR_H_ + +/** + * @file + * + * @brief Prometheus collector APIs. + * + * @defgroup prometheus Prometheus API + * @since 4.0 + * @version 0.1.0 + * @ingroup networking + * @{ + */ + +#include +#include + +#include + +/** + * @brief Prometheus collector definition + * + * This structure defines a Prometheus collector. + */ +struct prometheus_collector { + /** Name of the collector */ + const char *name; + /** Array of metrics associated with the collector */ + struct prometheus_metric *metric[CONFIG_PROMETHEUS_MAX_METRICS]; + /** Number of metrics associated with the collector */ + size_t size; +}; + +/** + * @brief Prometheus Collector definition. + * + * This macro defines a Collector. + * + * @param _name The collector's name. + */ +#define PROMETHEUS_COLLECTOR_DEFINE(_name) \ + static STRUCT_SECTION_ITERABLE(prometheus_collector, _name) = { \ + .name = STRINGIFY(_name), .size = 0, .metric = {0}} + +/** + * @brief Register a metric with a Prometheus collector + * + * Registers the specified metric with the given collector. + * + * @param collector Pointer to the collector to register the metric with. + * @param metric Pointer to the metric to register. + * + * @return 0 if successful, otherwise a negative error code. + * @retval -EINVAL Invalid arguments. + * @retval -ENOMEM Not enough memory to register the metric. + */ +int prometheus_collector_register_metric(struct prometheus_collector *collector, + struct prometheus_metric *metric); + +/** + * @brief Get a metric from a Prometheus collector + * + * Retrieves the metric with the specified name from the given collector. + * + * @param collector Pointer to the collector to retrieve the metric from. + * @param name Name of the metric to retrieve. + * @return Pointer to the retrieved metric, or NULL if not found. + */ +const void *prometheus_collector_get_metric(const struct prometheus_collector *collector, + const char *name); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_PROMETHEUS_COLLECTOR_H_ */ diff --git a/include/zephyr/net/prometheus/counter.h b/include/zephyr/net/prometheus/counter.h new file mode 100644 index 0000000000000..2b25743961f34 --- /dev/null +++ b/include/zephyr/net/prometheus/counter.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PROMETHEUS_COUNTER_H_ +#define ZEPHYR_INCLUDE_PROMETHEUS_COUNTER_H_ + +/** + * @file + * + * @brief Prometheus counter APIs. + * + * @addtogroup prometheus + * @{ + */ + +#include + +#include +#include + +/** + * @brief Type used to represent a Prometheus counter metric. + * + * * References + * * See https://prometheus.io/docs/concepts/metric_types/#counter + */ +struct prometheus_counter { + /** Base of the Prometheus counter metric */ + struct prometheus_metric *base; + /** Value of the Prometheus counter metric */ + uint64_t value; +}; + +/** + * @brief Prometheus Counter definition. + * + * This macro defines a Counter metric. + * + * @param _name The channel's name. + * @param _detail The metric base. + * + * Example usage: + * @code{.c} + * + * struct prometheus_metric http_request_counter = { + * .type = PROMETHEUS_COUNTER, + * .name = "http_request_counter", + * .description = "HTTP request counter", + * .num_labels = 1, + * .labels = { + * { .key = "http_request", .value = "request_count",} + * }, + *}; + * + * PROMETHEUS_COUNTER_DEFINE(test_counter, &test_counter_metric); + * @endcode + */ +#define PROMETHEUS_COUNTER_DEFINE(_name, _detail) \ + static STRUCT_SECTION_ITERABLE(prometheus_counter, _name) = {.base = (void *)(_detail), \ + .value = 0} + +/** + * @brief Increment the value of a Prometheus counter metric + * Increments the value of the specified counter metric by one. + * @param counter Pointer to the counter metric to increment. + * @return 0 on success, negative errno on error. + */ +int prometheus_counter_inc(struct prometheus_counter *counter); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_PROMETHEUS_COUNTER_H_ */ diff --git a/include/zephyr/net/prometheus/formatter.h b/include/zephyr/net/prometheus/formatter.h new file mode 100644 index 0000000000000..0d5dafeb7019a --- /dev/null +++ b/include/zephyr/net/prometheus/formatter.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PROMETHEUS_FORMATTER_H_ +#define ZEPHYR_INCLUDE_PROMETHEUS_FORMATTER_H_ + +/** + * @file + * + * @brief Prometheus formatter APIs. + * + * @addtogroup prometheus + * @{ + */ + +#include + +/** + * @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 + * + * @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. + * @param buffer_size Size of the buffer. + * + * @return 0 on success, negative errno on error. + */ +int prometheus_format_exposition(const struct prometheus_collector *collector, char *buffer, + size_t buffer_size); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_PROMETHEUS_FORMATTER_H_ */ diff --git a/include/zephyr/net/prometheus/gauge.h b/include/zephyr/net/prometheus/gauge.h new file mode 100644 index 0000000000000..d82b0a126faf2 --- /dev/null +++ b/include/zephyr/net/prometheus/gauge.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PROMETHEUS_GAUGE_H_ +#define ZEPHYR_INCLUDE_PROMETHEUS_GAUGE_H_ + +/** + * @file + * + * @brief Prometheus gauge APIs. + * + * @addtogroup prometheus + * @{ + */ + +#include +#include + +/** + * @brief Type used to represent a Prometheus gauge metric. + * + * * References + * * See https://prometheus.io/docs/concepts/metric_types/#gauge + */ +struct prometheus_gauge { + /** Base of the Prometheus gauge metric */ + struct prometheus_metric *base; + /** Value of the Prometheus gauge metric */ + double value; +}; + +/** + * @brief Prometheus Gauge definition. + * + * This macro defines a Gauge metric. + * + * @param _name The channel's name. + * @param _detail The metric base. + * + * Example usage: + * @code{.c} + * + * struct prometheus_metric http_request_gauge = { + * .type = PROMETHEUS_GAUGE, + * .name = "http_request_gauge", + * .description = "HTTP request gauge", + * .num_labels = 1, + * .labels = { + * { .key = "http_request", .value = "request_count",} + * }, + * }; + * + * PROMETHEUS_GAUGE_DEFINE(test_gauge, &test_gauge_metric); + * + * @endcode + */ +#define PROMETHEUS_GAUGE_DEFINE(_name, _detail) \ + static STRUCT_SECTION_ITERABLE(prometheus_gauge, _name) = {.base = (void *)(_detail), \ + .value = 0} + +/** + * @brief Set the value of a Prometheus gauge metric + * + * Sets the value of the specified gauge metric to the given value. + * + * @param gauge Pointer to the gauge metric to set. + * @param value Value to set the gauge metric to. + * + * @return 0 on success, -EINVAL if the value is negative. + */ +int prometheus_gauge_set(struct prometheus_gauge *gauge, double value); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_PROMETHEUS_GAUGE_H_ */ diff --git a/include/zephyr/net/prometheus/histogram.h b/include/zephyr/net/prometheus/histogram.h new file mode 100644 index 0000000000000..ec77e100e054d --- /dev/null +++ b/include/zephyr/net/prometheus/histogram.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PROMETHEUS_HISTOGRAM_H_ +#define ZEPHYR_INCLUDE_PROMETHEUS_HISTOGRAM_H_ + +/** + * @file + * + * @brief Prometheus histogram APIs. + * + * @addtogroup prometheus + * @{ + */ + +#include +#include + +#include + +/** + * @brief Prometheus histogram bucket definition. + * + * This structure defines a Prometheus histogram bucket. + */ +struct prometheus_histogram_bucket { + /** Upper bound value of bucket */ + double upper_bound; + /** Cumulative count of observations in the bucket */ + unsigned long count; +}; + +/** + * @brief Type used to represent a Prometheus histogram metric. + * + * * References + * * See https://prometheus.io/docs/concepts/metric_types/#histogram + */ +struct prometheus_histogram { + /** Base of the Prometheus histogram metric */ + struct prometheus_metric *base; + /** Array of buckets in the histogram */ + struct prometheus_histogram_bucket *buckets; + /** Number of buckets in the histogram */ + size_t num_buckets; + /** Sum of all observed values in the histogram */ + double sum; + /** Total count of observations in the histogram */ + unsigned long count; +}; + +/** + * @brief Prometheus Histogram definition. + * + * This macro defines a Histogram metric. + * + * @param _name The channel's name. + * @param _detail The metric base. + * + * Example usage: + * @code{.c} + * + * struct prometheus_metric http_request_gauge = { + * .type = PROMETHEUS_HISTOGRAM, + * .name = "http_request_histograms", + * .description = "HTTP request histogram", + * .num_labels = 1, + * .labels = { + * { .key = "request_latency", .value = "request_latency_seconds",} + * }, + * }; + * + * PROMETHEUS_HISTOGRAM_DEFINE(test_histogram, &test_histogram_metric); + * + * @endcode + */ +#define PROMETHEUS_HISTOGRAM_DEFINE(_name, _detail) \ + static STRUCT_SECTION_ITERABLE(prometheus_histogram, _name) = {.base = (void *)(_detail), \ + .buckets = NULL, \ + .num_buckets = 0, \ + .sum = 0, \ + .count = 0} + +/** + * @brief Observe a value in a Prometheus histogram metric + * + * Observes the specified value in the given histogram metric. + * + * @param histogram Pointer to the histogram metric to observe. + * @param value Value to observe in the histogram metric. + * @return 0 on success, -EINVAL if the value is negative. + */ +int prometheus_histogram_observe(struct prometheus_histogram *histogram, double value); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_PROMETHEUS_HISTOGRAM_H_ */ diff --git a/include/zephyr/net/prometheus/label.h b/include/zephyr/net/prometheus/label.h new file mode 100644 index 0000000000000..798470147cef1 --- /dev/null +++ b/include/zephyr/net/prometheus/label.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PROMETHEUS_LABEL_H_ +#define ZEPHYR_INCLUDE_PROMETHEUS_LABEL_H_ + +/** + * @file + * + * @brief Prometheus label interface. + * + * @addtogroup prometheus + * @{ + */ + +/* maximum length of label key */ +#define MAX_PROMETHEUS_LABEL_KEY_LENGTH 16 +/* maximum length of label value */ +#define MAX_PROMETHEUS_LABEL_VALUE_LENGTH 16 +/* maximum namber of labels per metric */ +#define MAX_PROMETHEUS_LABELS_PER_METRIC 5 + +/** + * @brief Prometheus label definition. + * + * This structure defines a Prometheus label. + */ +struct prometheus_label { + /** Prometheus metric label key */ + char key[MAX_PROMETHEUS_LABEL_KEY_LENGTH]; + /** Prometheus metric label value */ + char value[MAX_PROMETHEUS_LABEL_VALUE_LENGTH]; +}; + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_PROMETHEUS_LABEL_H_ */ diff --git a/include/zephyr/net/prometheus/metric.h b/include/zephyr/net/prometheus/metric.h new file mode 100644 index 0000000000000..18cc4e29aff16 --- /dev/null +++ b/include/zephyr/net/prometheus/metric.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PROMETHEUS_METRIC_H_ +#define ZEPHYR_INCLUDE_PROMETHEUS_METRIC_H_ + +/** + * @file + * + * @brief Prometheus metric interface. + * + * @addtogroup prometheus + * @{ + */ + +#include +#include + +/** + * @brief Prometheus metric types. + * + * * References + * * See https://prometheus.io/docs/concepts/metric_types + */ +enum prometheus_metric_type { + /** Prometheus Counter */ + PROMETHEUS_COUNTER = 0, + /** Prometheus Gauge */ + PROMETHEUS_GAUGE, + /** Prometheus Summary */ + PROMETHEUS_SUMMARY, + /** Prometheus Histogram */ + PROMETHEUS_HISTOGRAM, +}; + +#define MAX_METRIC_NAME_LENGTH 32 +#define MAX_METRIC_DESCRIPTION_LENGTH 64 + +/** + * @brief Type used to represent a Prometheus metric base. + * + * Every metric has a prometheus_metric structure associated used + * to control the metric access and usage. + */ +struct prometheus_metric { + /** Type of the Prometheus metric. */ + enum prometheus_metric_type type; + /** Name of the Prometheus metric. */ + char name[MAX_METRIC_NAME_LENGTH]; + /** Description of the Prometheus metric. */ + char description[MAX_METRIC_DESCRIPTION_LENGTH]; + /** Labels associated with the Prometheus metric. */ + struct prometheus_label labels[MAX_PROMETHEUS_LABELS_PER_METRIC]; + /** Number of labels associated with the Prometheus metric. */ + int num_labels; + /* Add any other necessary fields */ +}; + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_PROMETHEUS_METRIC_H_ */ diff --git a/include/zephyr/net/prometheus/summary.h b/include/zephyr/net/prometheus/summary.h new file mode 100644 index 0000000000000..78947dd560a2a --- /dev/null +++ b/include/zephyr/net/prometheus/summary.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_PROMETHEUS_SUMMARY_H_ +#define ZEPHYR_INCLUDE_PROMETHEUS_SUMMARY_H_ + +/** + * @file + * + * @brief Prometheus summary APIs. + * + * @addtogroup prometheus + * @{ + */ + +#include +#include + +#include + +/** + * @brief Prometheus summary quantile definition. + * + * This structure defines a Prometheus summary quantile. + */ +struct prometheus_summary_quantile { + /** Quantile of the summary */ + double quantile; + /** Value of the quantile */ + double value; +}; + +/** + * @brief Type used to represent a Prometheus summary metric. + * + * * References + * * See https://prometheus.io/docs/concepts/metric_types/#summary + */ +struct prometheus_summary { + /** Base of the Prometheus summary metric */ + struct prometheus_metric *base; + /** Array of quantiles associated with the Prometheus summary metric */ + struct prometheus_summary_quantile *quantiles; + /** Number of quantiles associated with the Prometheus summary metric */ + size_t num_quantiles; + /** Sum of all observed values in the summary metric */ + double sum; + /** Total count of observations in the summary metric */ + unsigned long count; +}; + +/** + * @brief Prometheus Summary definition. + * + * This macro defines a Summary metric. + * + * @param _name The channel's name. + * @param _detail The metric base. + * + * Example usage: + * @code{.c} + * + * struct prometheus_metric http_request_gauge = { + * .type = PROMETHEUS_SUMMARY, + * .name = "http_request_summary", + * .description = "HTTP request summary", + * .num_labels = 1, + * .labels = { + * { .key = "request_latency", .value = "request_latency_seconds",} + * }, + * }; + * + * PROMETHEUS_SUMMARY_DEFINE(test_summary, &test_summary_metric); + * + * @endcode + */ + +#define PROMETHEUS_SUMMARY_DEFINE(_name, _detail) \ + static STRUCT_SECTION_ITERABLE(prometheus_summary, _name) = {.base = (void *)(_detail), \ + .quantiles = NULL, \ + .num_quantiles = 0, \ + .sum = 0, \ + .count = 0} + +/** + * @brief Observes a value in a Prometheus summary metric + * + * Observes the specified value in the given summary metric. + * + * @param summary Pointer to the summary metric to observe. + * @param value Value to observe in the summary metric. + * @return 0 on success, -EINVAL if the value is negative. + */ +int prometheus_summary_observe(struct prometheus_summary *summary, double value); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_PROMETHEUS_SUMMARY_H_ */ diff --git a/samples/net/prometheus/CMakeLists.txt b/samples/net/prometheus/CMakeLists.txt new file mode 100644 index 0000000000000..1a9d783f35bc0 --- /dev/null +++ b/samples/net/prometheus/CMakeLists.txt @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +find_package(Python REQUIRED COMPONENTS Interpreter) + +project(prometheus_sample) + +if(CONFIG_NET_SOCKETS_SOCKOPT_TLS AND + CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED AND + (CONFIG_NET_SAMPLE_PSK_HEADER_FILE STREQUAL "dummy_psk.h")) + add_custom_target(development_psk + COMMAND ${CMAKE_COMMAND} -E echo "----------------------------------------------------------" + COMMAND ${CMAKE_COMMAND} -E echo "--- WARNING: Using dummy PSK! Only suitable for ---" + COMMAND ${CMAKE_COMMAND} -E echo "--- development. Set NET_SAMPLE_PSK_HEADER_FILE to use ---" + COMMAND ${CMAKE_COMMAND} -E echo "--- own pre-shared key. ---" + COMMAND ${CMAKE_COMMAND} -E echo "----------------------------------------------------------" + ) + add_dependencies(app development_psk) +endif() + + +target_sources(app PRIVATE src/main.c) + +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) + +target_link_libraries(app PRIVATE zephyr_interface zephyr) + +zephyr_linker_sources(SECTIONS sections-rom.ld) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTPS_SERVICE NAME + http_resource_desc_test_https_service + KVMA RAM_REGION GROUP RODATA_REGION + SUBALIGN Z_LINK_ITERABLE_SUBALIGN) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTP_SERVICE NAME + http_resource_desc_test_http_service + KVMA RAM_REGION GROUP RODATA_REGION + SUBALIGN Z_LINK_ITERABLE_SUBALIGN) + +foreach(inc_file + ca.der + server.der + server_privkey.der + https-server-cert.der + https-server-key.der + ) + generate_inc_file_for_target( + app + src/${inc_file} + ${gen_dir}/${inc_file}.inc + ) +endforeach() diff --git a/samples/net/prometheus/Kconfig b/samples/net/prometheus/Kconfig new file mode 100644 index 0000000000000..2426dd9fd5a65 --- /dev/null +++ b/samples/net/prometheus/Kconfig @@ -0,0 +1,41 @@ +# Config options for prometheus sample application + +# Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "Prometheus sample application" + +config NET_SAMPLE_HTTP_SERVICE + bool "Enable HTTP service" + default y + +config NET_SAMPLE_HTTP_SERVER_SERVICE_PORT + int "Port number for HTTP service" + default 80 + depends on NET_SAMPLE_HTTP_SERVICE + +config NET_SAMPLE_HTTPS_SERVICE + bool "Enable HTTPS service" + depends on NET_SOCKETS_SOCKOPT_TLS || TLS_CREDENTIALS + +config NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT + int "Port number for HTTPS service" + default 443 + depends on NET_SAMPLE_HTTPS_SERVICE + +config NET_SAMPLE_PSK_HEADER_FILE + string "Header file containing PSK" + default "dummy_psk.h" + depends on MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + help + Name of a header file containing a + pre-shared key. + +config NET_SAMPLE_CERTS_WITH_SC + bool "Signed certificates" + depends on NET_SOCKETS_SOCKOPT_TLS + help + Enable this flag, if you are interested to run this + application with signed certificates and keys. + +source "Kconfig.zephyr" diff --git a/samples/net/prometheus/README.rst b/samples/net/prometheus/README.rst new file mode 100644 index 0000000000000..ba0b64d426fde --- /dev/null +++ b/samples/net/prometheus/README.rst @@ -0,0 +1,93 @@ +.. zephyr:code-sample:: prometheus + :name: Prometheus Sample + :relevant-api: http_service http_server tls_credentials prometheus + + Implement a Prometheus Metric Server demonstrating various metric types. + +Overview +-------- + +This sample application demonstrates the use of the ``prometheus`` library. +This library provides prometheus client library(pull method) implementation. +By integrating this library into your code, you can expose internal metrics +via an HTTP endpoint on your application's instance, enabling Prometheus to +scrape and collect the metrics. + +Requirement +----------- + +`QEMU Networking `_ + +Building and running the server +------------------------------- + +To build and run the application: + +.. zephyr-app-commands:: + :zephyr-app: samples/net/prometheus + :board: + :conf: + :goals: build + :compact: + +When the server is up, we can make requests to the server using HTTP/1.1. + +**With HTTP/1.1:** + +- Using a browser: ``http://192.0.2.1/metrics`` + +See `Prometheus client library documentation +`_. + +Metric Server Customization +--------------------------- + +The server sample contains several parameters that can be customized based on +the requirements. These are the configurable parameters: + +- ``CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT``: Configures the service port. + +- ``CONFIG_HTTP_SERVER_MAX_CLIENTS``: Defines the maximum number of HTTP/2 + clients that the server can handle simultaneously. + +- ``CONFIG_HTTP_SERVER_MAX_STREAMS``: Specifies the maximum number of HTTP/2 + streams that can be established per client. + +- ``CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE``: Defines the buffer size allocated + for each client. This limits the maximum length of an individual HTTP header + supported. + +- ``CONFIG_HTTP_SERVER_MAX_URL_LENGTH``: Specifies the maximum length of an HTTP + URL that the server can process. + +To customize these options, we can run ``west build -t menuconfig``, which provides +us with an interactive configuration interface. Then we could navigate from the top-level +menu to: ``-> Subsystems and OS Services -> Networking -> Network Protocols``. + + +Prometheus Configuration +------------------------ + +.. code-block:: yaml + + scrape_configs: + - job_name: 'your_server_metrics' + static_configs: + - targets: ['your_server_ip:your_server_port'] + # Optional: Configure scrape interval + # scrape_interval: 15s + +Replace ``'your_server_metrics'`` with a descriptive name for your job, +``'your_server_ip'`` with the IP address or hostname of your server, and +``'your_server_port'`` with the port number where your server exposes Prometheus metrics. + +Make sure to adjust the configuration according to your server's setup and requirements. + +After updating the configuration, save the file and restart the Prometheus server. +Once restarted, Prometheus will start scraping metrics from your server according +to the defined scrape configuration. You can verify that your server's metrics are +being scraped by checking the Prometheus targets page or querying Prometheus for +metrics from your server. + +See `Prometheus configuration docs +`_. diff --git a/samples/net/prometheus/prj.conf b/samples/net/prometheus/prj.conf new file mode 100644 index 0000000000000..3851f584a58b5 --- /dev/null +++ b/samples/net/prometheus/prj.conf @@ -0,0 +1,86 @@ +# General config +CONFIG_MAIN_STACK_SIZE=3072 +CONFIG_SHELL=y +CONFIG_LOG=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_POSIX_MAX_FDS=32 +CONFIG_POSIX_API=y +CONFIG_FDTABLE=y +CONFIG_NET_SOCKETS_POLL_MAX=32 +CONFIG_REQUIRES_FULL_LIBC=y +CONFIG_HEAP_MEM_POOL_SIZE=2048 +CONFIG_ZVFS_OPEN_MAX=32 + +# Prometheus +CONFIG_PROMETHEUS=y + +# Eventfd +CONFIG_EVENTFD=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_SHELL=y +CONFIG_NET_LOG=y +CONFIG_NET_DHCPV4=y + +# JSON +CONFIG_JSON_LIBRARY=y + +# HTTP parser +CONFIG_HTTP_PARSER_URL=y +CONFIG_HTTP_PARSER=y +CONFIG_HTTP_SERVER=y +CONFIG_HTTP_SERVER_WEBSOCKET=y +CONFIG_HTTP_SERVER_MAX_CONTENT_TYPE_LENGTH=128 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +# IP address options +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_MAX_CONTEXTS=32 +CONFIG_NET_MAX_CONN=32 + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_NEED_IPV6=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2" + +# TLS configuration +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=60000 +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048 +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y +CONFIG_NET_SOCKETS_TLS_MAX_CONTEXTS=6 +CONFIG_TLS_CREDENTIALS=y +CONFIG_TLS_MAX_CREDENTIALS_NUMBER=5 + +# Networking tweaks +# Required to handle large number of consecutive connections, +# e.g. when testing with ApacheBench. +CONFIG_NET_TCP_TIME_WAIT_DELAY=0 + +# Network debug config +CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=n +CONFIG_NET_HTTP_LOG_LEVEL_DBG=n +CONFIG_NET_IPV6_LOG_LEVEL_DBG=n +CONFIG_NET_IPV6_ND_LOG_LEVEL_DBG=n diff --git a/samples/net/prometheus/sample.yaml b/samples/net/prometheus/sample.yaml new file mode 100644 index 0000000000000..c50834045898f --- /dev/null +++ b/samples/net/prometheus/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: Prometheus Client Sample + name: prometheus_client_sample +common: + harness: net + min_ram: 192 + tags: + - http + - net + - server + - socket + - prometheus + platform_exclude: + - native_posix + - native_posix/native/64 +tests: + sample.net.prometheus: {} diff --git a/samples/net/prometheus/sections-rom.ld b/samples/net/prometheus/sections-rom.ld new file mode 100644 index 0000000000000..d51cad087f3e9 --- /dev/null +++ b/samples/net/prometheus/sections-rom.ld @@ -0,0 +1,4 @@ +#include + +ITERABLE_SECTION_ROM(http_resource_desc_test_http_service, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_ROM(http_resource_desc_test_https_service, Z_LINK_ITERABLE_SUBALIGN) diff --git a/samples/net/prometheus/src/ca.der b/samples/net/prometheus/src/ca.der new file mode 100644 index 0000000000000..b1d3e097cadce Binary files /dev/null and b/samples/net/prometheus/src/ca.der differ diff --git a/samples/net/prometheus/src/certificate.h b/samples/net/prometheus/src/certificate.h new file mode 100644 index 0000000000000..52a3fa9c8ea18 --- /dev/null +++ b/samples/net/prometheus/src/certificate.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __CERTIFICATE_H__ +#define __CERTIFICATE_H__ + +enum tls_tag { + /** The Certificate Authority public key */ + HTTP_SERVER_CA_CERTIFICATE_TAG, + /** Used for both the public and private server keys */ + HTTP_SERVER_CERTIFICATE_TAG, + /** Used for both the public and private client keys */ + HTTP_SERVER_CLIENT_CERTIFICATE_TAG, + PSK_TAG, +}; + +#if !defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) +static const unsigned char server_certificate[] = { +#include "https-server-cert.der.inc" +}; + +/* This is the private key in pkcs#8 format. */ +static const unsigned char private_key[] = { +#include "https-server-key.der.inc" +}; + +#else + +static const unsigned char ca_certificate[] = { +#include "ca.der.inc" +}; + +static const unsigned char server_certificate[] = { +#include "server.der.inc" +}; + +/* This is the private key in pkcs#8 format. */ +static const unsigned char private_key[] = { +#include "server_privkey.der.inc" +}; +#endif + +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) +#include CONFIG_NET_SAMPLE_PSK_HEADER_FILE +#endif + +#endif /* __CERTIFICATE_H__ */ diff --git a/samples/net/prometheus/src/dummy_psk.h b/samples/net/prometheus/src/dummy_psk.h new file mode 100644 index 0000000000000..1a9a2736d151d --- /dev/null +++ b/samples/net/prometheus/src/dummy_psk.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __DUMMY_PSK_H__ +#define __DUMMY_PSK_H__ + +static const unsigned char psk[] = {0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; +static const char psk_id[] = "PSK_identity"; + +#endif /* __DUMMY_PSK_H__ */ diff --git a/samples/net/prometheus/src/https-server-cert.der b/samples/net/prometheus/src/https-server-cert.der new file mode 100644 index 0000000000000..bfcb335e31c8c Binary files /dev/null and b/samples/net/prometheus/src/https-server-cert.der differ diff --git a/samples/net/prometheus/src/https-server-key.der b/samples/net/prometheus/src/https-server-key.der new file mode 100644 index 0000000000000..5a4d67372ea41 Binary files /dev/null and b/samples/net/prometheus/src/https-server-key.der differ diff --git a/samples/net/prometheus/src/main.c b/samples/net/prometheus/src/main.c new file mode 100644 index 0000000000000..3acd12ef94ca5 --- /dev/null +++ b/samples/net/prometheus/src/main.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); + +struct { + + struct prometheus_collector *collector; + + struct prometheus_counter *counter; + +} prom_context; + +#if defined(CONFIG_NET_SAMPLE_HTTP_SERVICE) +static uint16_t test_http_service_port = CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT; +HTTP_SERVICE_DEFINE(test_http_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_http_service_port, 1, + 10, NULL); + +static int dyn_handler(struct http_client_ctx *client, enum http_data_status status, + uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, + void *user_data) +{ + int ret; + static uint8_t prom_buffer[256]; + + if (status == HTTP_SERVER_DATA_FINAL) { + + /* incrase counter per request */ + prometheus_counter_inc(prom_context.counter); + + /* clear buffer */ + (void)memset(prom_buffer, 0, sizeof(prom_buffer)); + + /* format exposition data */ + ret = prometheus_format_exposition(prom_context.collector, prom_buffer, + sizeof(prom_buffer)); + if (ret < 0) { + LOG_ERR("Cannot format exposition data (%d)", ret); + return ret; + } + + response_ctx->body = prom_buffer; + response_ctx->body_len = strlen(prom_buffer); + response_ctx->final_chunk = true; + } + + return 0; +} + +struct http_resource_detail_dynamic dyn_resource_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_DYNAMIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + .content_type = "text/plain", + }, + .cb = dyn_handler, + .user_data = NULL, +}; + +HTTP_RESOURCE_DEFINE(dyn_resource, test_http_service, "/metrics", &dyn_resource_detail); + +#endif /* CONFIG_NET_SAMPLE_HTTP_SERVICE */ + +#if defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) +#include "certificate.h" + +static const sec_tag_t sec_tag_list_verify_none[] = { + HTTP_SERVER_CERTIFICATE_TAG, +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) + PSK_TAG, +#endif +}; + +static uint16_t test_https_service_port = CONFIG_NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT; +HTTPS_SERVICE_DEFINE(test_https_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_https_service_port, + 1, 10, NULL, sec_tag_list_verify_none, sizeof(sec_tag_list_verify_none)); + +HTTP_RESOURCE_DEFINE(index_html_gz_resource_https, test_https_service, "/metrics", + &dyn_resource_detail); + +#endif /* CONFIG_NET_SAMPLE_HTTPS_SERVICE */ + +static void setup_tls(void) +{ +#if defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) + int err; + +#if defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE, + ca_certificate, sizeof(ca_certificate)); + if (err < 0) { + LOG_ERR("Failed to register CA certificate: %d", err); + } +#endif /* defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) */ + + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, TLS_CREDENTIAL_SERVER_CERTIFICATE, + server_certificate, sizeof(server_certificate)); + if (err < 0) { + LOG_ERR("Failed to register public certificate: %d", err); + } + + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, TLS_CREDENTIAL_PRIVATE_KEY, + private_key, sizeof(private_key)); + if (err < 0) { + LOG_ERR("Failed to register private key: %d", err); + } + +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) + err = tls_credential_add(PSK_TAG, TLS_CREDENTIAL_PSK, psk, sizeof(psk)); + if (err < 0) { + LOG_ERR("Failed to register PSK: %d", err); + } + + err = tls_credential_add(PSK_TAG, TLS_CREDENTIAL_PSK_ID, psk_id, sizeof(psk_id) - 1); + if (err < 0) { + LOG_ERR("Failed to register PSK ID: %d", err); + } +#endif /* defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) */ +#endif /* defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) */ +#endif /* defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) */ +} + +struct prometheus_metric http_request_counter = { + .type = PROMETHEUS_COUNTER, + .name = "http_request_counter", + .description = "HTTP request counter", + .num_labels = 1, + .labels = {{ + .key = "http_request", + .value = "request_count", + }}, +}; + +PROMETHEUS_COUNTER_DEFINE(test_counter, &http_request_counter); + +PROMETHEUS_COLLECTOR_DEFINE(test_collector); + +int main(void) +{ + /* Create a mock collector with different types of metrics */ + prom_context.collector = &test_collector; + + prom_context.counter = &test_counter; + prometheus_counter_inc(prom_context.counter); + + prometheus_collector_register_metric(prom_context.collector, prom_context.counter->base); + + setup_tls(); + + http_server_start(); + + return 0; +} diff --git a/samples/net/prometheus/src/server.der b/samples/net/prometheus/src/server.der new file mode 100644 index 0000000000000..2b664a4bdb2ce Binary files /dev/null and b/samples/net/prometheus/src/server.der differ diff --git a/samples/net/prometheus/src/server_privkey.der b/samples/net/prometheus/src/server_privkey.der new file mode 100644 index 0000000000000..2269293fe790f Binary files /dev/null and b/samples/net/prometheus/src/server_privkey.der differ diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index ad195333b0bf3..6dbd0048ae5c1 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory_ifdef(CONFIG_NET_ZPERF zperf) add_subdirectory_ifdef(CONFIG_NET_SHELL shell) add_subdirectory_ifdef(CONFIG_NET_TRICKLE trickle) add_subdirectory_ifdef(CONFIG_NET_DHCPV6 dhcpv6) +add_subdirectory_ifdef(CONFIG_PROMETHEUS prometheus) if (CONFIG_NET_DHCPV4 OR CONFIG_NET_DHCPV4_SERVER) add_subdirectory(dhcpv4) diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index 917bbd40b6eab..aa770cc6722ba 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -49,4 +49,6 @@ source "subsys/net/lib/trickle/Kconfig" source "subsys/net/lib/zperf/Kconfig" +source "subsys/net/lib/prometheus/Kconfig" + endmenu diff --git a/subsys/net/lib/prometheus/CMakeLists.txt b/subsys/net/lib/prometheus/CMakeLists.txt new file mode 100644 index 0000000000000..39c0cc47680e6 --- /dev/null +++ b/subsys/net/lib/prometheus/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright(c) 2024 Sparse Technology, Mustafa Abdullah Kus +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories(.) + +zephyr_library_sources( + collector.c + counter.c + formatter.c + gauge.c + histogram.c + summary.c +) + +zephyr_linker_sources(DATA_SECTIONS prometheus.ld) diff --git a/subsys/net/lib/prometheus/Kconfig b/subsys/net/lib/prometheus/Kconfig new file mode 100644 index 0000000000000..a34a54f327aca --- /dev/null +++ b/subsys/net/lib/prometheus/Kconfig @@ -0,0 +1,25 @@ +# Copyright(c) 2024 Sparse Technology, Mustafa Abdullah Kus +# SPDX-License-Identifier: Apache-2.0 + +menuconfig PROMETHEUS + bool "Prometheus Client Library (Pull Method)" + depends on NET_SOCKETS + depends on HTTP_SERVER + help + Enable Prometheus client library + +if PROMETHEUS + +module = PROMETHEUS +module-dep = NET_LOG +module-str = Log level for PROMETHEUS +module-help = Enable debug message of PROMETHEUS client library. +source "subsys/net/Kconfig.template.log_config.net" + +config PROMETHEUS_MAX_METRICS + int "Maximum number of metrics" + default 10 + help + Maximum number of metrics that can be registered. + +endif # PROMETHEUS diff --git a/subsys/net/lib/prometheus/collector.c b/subsys/net/lib/prometheus/collector.c new file mode 100644 index 0000000000000..d5354a523b723 --- /dev/null +++ b/subsys/net/lib/prometheus/collector.c @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +LOG_MODULE_REGISTER(pm_collector, CONFIG_PROMETHEUS_LOG_LEVEL); + +int prometheus_collector_register_metric(struct prometheus_collector *collector, + struct prometheus_metric *metric) +{ + if (!collector || !metric) { + LOG_ERR("Invalid arguments"); + return -EINVAL; + } + + LOG_DBG("Registering metric type=%d", metric->type); + + for (int i = 0; i < CONFIG_PROMETHEUS_MAX_METRICS; i++) { + if (!collector->metric[i]) { + collector->metric[i] = metric; + collector->size++; + return 0; + } + } + + return -ENOMEM; +} + +const struct prometheus_counter *prometheus_get_counter_metric(const char *name) +{ + STRUCT_SECTION_FOREACH(prometheus_counter, entry) { + LOG_DBG("entry->name: %s", entry->base->name); + + if (strncmp(entry->base->name, name, sizeof(entry->base->name)) == 0) { + LOG_DBG("Counter found %s=%s", entry->base->name, name); + return entry; + } + } + + LOG_DBG("%s %s not found", "Counter", name); + + return NULL; +} + +const struct prometheus_gauge *prometheus_get_gauge_metric(const char *name) +{ + STRUCT_SECTION_FOREACH(prometheus_gauge, entry) { + LOG_DBG("entry->name: %s", entry->base->name); + + if (strncmp(entry->base->name, name, sizeof(entry->base->name)) == 0) { + LOG_DBG("Counter found %s=%s", entry->base->name, name); + return entry; + } + } + + LOG_DBG("%s %s not found", "Gauge", name); + + return NULL; +} + +const struct prometheus_histogram *prometheus_get_histogram_metric(const char *name) +{ + STRUCT_SECTION_FOREACH(prometheus_histogram, entry) { + LOG_DBG("entry->name: %s", entry->base->name); + + if (strncmp(entry->base->name, name, sizeof(entry->base->name)) == 0) { + LOG_DBG("Counter found %s=%s", entry->base->name, name); + return entry; + } + } + + LOG_DBG("%s %s not found", "Histogram", name); + + return NULL; +} + +const struct prometheus_summary *prometheus_get_summary_metric(const char *name) +{ + STRUCT_SECTION_FOREACH(prometheus_summary, entry) { + LOG_DBG("entry->name: %s", entry->base->name); + + if (strncmp(entry->base->name, name, sizeof(entry->base->name)) == 0) { + LOG_DBG("Counter found %s=%s", entry->base->name, name); + return entry; + } + } + + LOG_DBG("%s %s not found", "Summary", name); + + return NULL; +} + +const void *prometheus_collector_get_metric(const struct prometheus_collector *collector, + const char *name) +{ + bool is_found = false; + enum prometheus_metric_type type = 0; + + for (size_t i = 0; i < collector->size; ++i) { + if (strncmp(collector->metric[i]->name, name, sizeof(collector->metric[i]->name)) == + 0) { + type = collector->metric[i]->type; + is_found = true; + + LOG_DBG("metric found: %s", collector->metric[i]->name); + } + } + + if (!is_found) { + LOG_ERR("Metric not found"); + return NULL; + } + + switch (type) { + case PROMETHEUS_COUNTER: + return prometheus_get_counter_metric(name); + case PROMETHEUS_GAUGE: + return prometheus_get_gauge_metric(name); + case PROMETHEUS_HISTOGRAM: + return prometheus_get_histogram_metric(name); + case PROMETHEUS_SUMMARY: + return prometheus_get_summary_metric(name); + default: + LOG_ERR("Invalid metric type"); + return NULL; + } + + return NULL; +} diff --git a/subsys/net/lib/prometheus/counter.c b/subsys/net/lib/prometheus/counter.c new file mode 100644 index 0000000000000..b1ef683e4d7ef --- /dev/null +++ b/subsys/net/lib/prometheus/counter.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(pm_counter, CONFIG_PROMETHEUS_LOG_LEVEL); + +int prometheus_counter_inc(struct prometheus_counter *counter) +{ + if (!counter) { + return -EINVAL; + } + + if (counter) { + counter->value++; + } + + return 0; +} diff --git a/subsys/net/lib/prometheus/formatter.c b/subsys/net/lib/prometheus/formatter.c new file mode 100644 index 0000000000000..978f50d88d9ad --- /dev/null +++ b/subsys/net/lib/prometheus/formatter.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +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_exposition(const struct prometheus_collector *collector, char *buffer, + size_t buffer_size) +{ + int ret; + int written = 0; + + if (collector == NULL || buffer == NULL || buffer_size == 0) { + LOG_ERR("Invalid arguments"); + return -EINVAL; + } + + /* iterate through each metric in the collector */ + for (size_t ind = 0; ind < collector->size; ind++) { + + const struct prometheus_metric *metric = collector->metric[ind]; + + /* 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"); + return ret; + } + } + + /* 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"); + return ret; + } + 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"); + return ret; + } + 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"); + return ret; + } + 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"); + return ret; + } + 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"); + return ret; + } + break; + } + + /* write metric-specific fields */ + switch (metric->type) { + case PROMETHEUS_COUNTER: { + const struct prometheus_counter *counter = + (const struct prometheus_counter *)prometheus_collector_get_metric( + collector, metric->name); + + 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"); + return ret; + } + } + break; + } + case PROMETHEUS_GAUGE: { + const struct prometheus_gauge *gauge = + (const struct prometheus_gauge *)prometheus_collector_get_metric( + collector, metric->name); + + 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"); + return ret; + } + } + break; + } + case PROMETHEUS_HISTOGRAM: { + const struct prometheus_histogram *histogram = + (const struct prometheus_histogram *) + prometheus_collector_get_metric(collector, metric->name); + + 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"); + return ret; + } + } + + 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"); + return ret; + } + + 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"); + return ret; + } + break; + } + case PROMETHEUS_SUMMARY: { + const struct prometheus_summary *summary = + (const struct prometheus_summary *)prometheus_collector_get_metric( + collector, metric->name); + + 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"); + return ret; + } + } + + 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"); + return ret; + } + + 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"); + return ret; + } + break; + } + default: + /* should not happen */ + LOG_ERR("Unsupported metric type %d", metric->type); + return -EINVAL; + } + } + + return 0; +} diff --git a/subsys/net/lib/prometheus/gauge.c b/subsys/net/lib/prometheus/gauge.c new file mode 100644 index 0000000000000..f2d39685455d2 --- /dev/null +++ b/subsys/net/lib/prometheus/gauge.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(pm_gauge, CONFIG_PROMETHEUS_LOG_LEVEL); + +int prometheus_gauge_set(struct prometheus_gauge *gauge, double value) +{ + if (value < 0) { + LOG_ERR("Invalid value"); + return -EINVAL; + } + + if (gauge) { + gauge->value = value; + } + + return 0; +} diff --git a/subsys/net/lib/prometheus/histogram.c b/subsys/net/lib/prometheus/histogram.c new file mode 100644 index 0000000000000..161e0d499d784 --- /dev/null +++ b/subsys/net/lib/prometheus/histogram.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(pm_histogram, CONFIG_PROMETHEUS_LOG_LEVEL); + +int prometheus_histogram_observe(struct prometheus_histogram *histogram, double value) +{ + if (!histogram) { + return -EINVAL; + } + + /* increment count */ + histogram->count++; + + /* update sum */ + histogram->sum += value; + + /* find appropriate bucket */ + for (size_t i = 0; i < histogram->num_buckets; ++i) { + if (value <= histogram->buckets[i].upper_bound) { + /* increment count for the bucket */ + histogram->buckets[i].count++; + + LOG_DBG("value: %f, bucket: %f, count: %lu", value, + histogram->buckets[i].upper_bound, histogram->buckets[i].count); + + break; + } + } + + return 0; +} diff --git a/subsys/net/lib/prometheus/prometheus.ld b/subsys/net/lib/prometheus/prometheus.ld new file mode 100644 index 0000000000000..1dbab6aeb3704 --- /dev/null +++ b/subsys/net/lib/prometheus/prometheus.ld @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +ITERABLE_SECTION_RAM(prometheus_counter, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_RAM(prometheus_gauge, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_RAM(prometheus_histogram, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_RAM(prometheus_summary, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_RAM(prometheus_collector, Z_LINK_ITERABLE_SUBALIGN) diff --git a/subsys/net/lib/prometheus/summary.c b/subsys/net/lib/prometheus/summary.c new file mode 100644 index 0000000000000..abf3caa3cdb47 --- /dev/null +++ b/subsys/net/lib/prometheus/summary.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(pm_summary, CONFIG_PROMETHEUS_LOG_LEVEL); + +int prometheus_summary_observe(struct prometheus_summary *summary, double value) +{ + if (!summary) { + return -EINVAL; + } + + /* increment count */ + summary->count++; + + /* update sum */ + summary->sum += value; + + return 0; +} diff --git a/tests/net/lib/prometheus/collector/CMakeLists.txt b/tests/net/lib/prometheus/collector/CMakeLists.txt new file mode 100644 index 0000000000000..2df012069a61b --- /dev/null +++ b/tests/net/lib/prometheus/collector/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(test_prometheus_collector) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/net/lib/prometheus/collector/prj.conf b/tests/net/lib/prometheus/collector/prj.conf new file mode 100644 index 0000000000000..6a5b7ce43c082 --- /dev/null +++ b/tests/net/lib/prometheus/collector/prj.conf @@ -0,0 +1,12 @@ +CONFIG_LOG=y +CONFIG_NET_LOG=y +CONFIG_ZTEST=y +CONFIG_ZTEST_STACK_SIZE=1024 +CONFIG_PROMETHEUS=y +CONFIG_POSIX_API=y +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_HTTP_SERVER=y +CONFIG_NET_TEST=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y diff --git a/tests/net/lib/prometheus/collector/src/main.c b/tests/net/lib/prometheus/collector/src/main.c new file mode 100644 index 0000000000000..e480b084152de --- /dev/null +++ b/tests/net/lib/prometheus/collector/src/main.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +struct prometheus_metric test_counter_metric = { + .type = PROMETHEUS_COUNTER, + .name = "test_counter", + .description = "Test counter", + .num_labels = 1, + .labels = {{ + .key = "test", + .value = "counter", + }}, +}; + +PROMETHEUS_COUNTER_DEFINE(test_counter_m, &test_counter_metric); + +PROMETHEUS_COLLECTOR_DEFINE(test_custom_collector); + +/** + * @brief Test prometheus_counter_inc + * + * @details The test shall increment the counter value by 1 and check if the + * value is incremented correctly. + * + * @details The test shall register the counter to the collector and check if the + * counter is found in the collector. + */ +ZTEST(test_collector, test_prometheus_collector_register) +{ + int ret; + struct prometheus_counter *counter; + + prometheus_collector_register_metric(&test_custom_collector, test_counter_m.base); + + counter = (struct prometheus_counter *)prometheus_collector_get_metric( + &test_custom_collector, "test_counter"); + + zassert_equal(counter, &test_counter_m, "Counter not found in collector"); + + zassert_equal(test_counter_m.value, 0, "Counter value is not 0"); + + ret = prometheus_counter_inc(counter); + zassert_ok(ret, "Error incrementing counter"); + + zassert_equal(counter->value, 1, "Counter value is not 1"); +} + +ZTEST_SUITE(test_collector, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/net/lib/prometheus/collector/testcase.yaml b/tests/net/lib/prometheus/collector/testcase.yaml new file mode 100644 index 0000000000000..b4b4ed288a618 --- /dev/null +++ b/tests/net/lib/prometheus/collector/testcase.yaml @@ -0,0 +1,11 @@ +tests: + # section.subsection + prometheus.collector: + depends_on: netif + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 + tags: prometheus diff --git a/tests/net/lib/prometheus/counter/CMakeLists.txt b/tests/net/lib/prometheus/counter/CMakeLists.txt new file mode 100644 index 0000000000000..f5a7ce321e244 --- /dev/null +++ b/tests/net/lib/prometheus/counter/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(test_prometheus_counter) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/net/lib/prometheus/counter/prj.conf b/tests/net/lib/prometheus/counter/prj.conf new file mode 100644 index 0000000000000..547832cdf8ed1 --- /dev/null +++ b/tests/net/lib/prometheus/counter/prj.conf @@ -0,0 +1,11 @@ +CONFIG_LOG=y +CONFIG_ZTEST=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_ZTEST_STACK_SIZE=1024 +CONFIG_PROMETHEUS=y +CONFIG_POSIX_API=y +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_LOG=y +CONFIG_HTTP_SERVER=y +CONFIG_NET_TEST=y diff --git a/tests/net/lib/prometheus/counter/src/main.c b/tests/net/lib/prometheus/counter/src/main.c new file mode 100644 index 0000000000000..63c29d649ac9d --- /dev/null +++ b/tests/net/lib/prometheus/counter/src/main.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +struct prometheus_metric test_counter_metric = { + .type = PROMETHEUS_COUNTER, + .name = "test_counter", + .description = "Test counter", + .num_labels = 1, + .labels = {{ + .key = "test", + .value = "counter", + }}, +}; + +PROMETHEUS_COUNTER_DEFINE(test_counter_m, &test_counter_metric); + +/** + * @brief Test prometheus_counter_inc + * @details The test shall increment the counter value by 1 and check if the + * value is incremented correctly. + */ +ZTEST(test_counter, test_prometheus_counter_inc) +{ + int ret; + + zassert_equal(test_counter_m.value, 0, "Counter value is not 0"); + + ret = prometheus_counter_inc(&test_counter_m); + zassert_ok(ret, "Error incrementing counter"); + + zassert_equal(test_counter_m.value, 1, "Counter value is not 1"); + + ret = prometheus_counter_inc(&test_counter_m); + zassert_ok(ret, "Error incrementing counter"); + + zassert_equal(test_counter_m.value, 2, "Counter value is not 2"); +} + +ZTEST_SUITE(test_counter, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/net/lib/prometheus/counter/testcase.yaml b/tests/net/lib/prometheus/counter/testcase.yaml new file mode 100644 index 0000000000000..fba6efe3157e3 --- /dev/null +++ b/tests/net/lib/prometheus/counter/testcase.yaml @@ -0,0 +1,11 @@ +tests: + # section.subsection + prometheus.counter: + depends_on: netif + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 + tags: prometheus diff --git a/tests/net/lib/prometheus/formatter/CMakeLists.txt b/tests/net/lib/prometheus/formatter/CMakeLists.txt new file mode 100644 index 0000000000000..0df1dbff54699 --- /dev/null +++ b/tests/net/lib/prometheus/formatter/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(test_prometheus_formatter) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/net/lib/prometheus/formatter/prj.conf b/tests/net/lib/prometheus/formatter/prj.conf new file mode 100644 index 0000000000000..6a5b7ce43c082 --- /dev/null +++ b/tests/net/lib/prometheus/formatter/prj.conf @@ -0,0 +1,12 @@ +CONFIG_LOG=y +CONFIG_NET_LOG=y +CONFIG_ZTEST=y +CONFIG_ZTEST_STACK_SIZE=1024 +CONFIG_PROMETHEUS=y +CONFIG_POSIX_API=y +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_HTTP_SERVER=y +CONFIG_NET_TEST=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y diff --git a/tests/net/lib/prometheus/formatter/src/main.c b/tests/net/lib/prometheus/formatter/src/main.c new file mode 100644 index 0000000000000..12af8153cc6e8 --- /dev/null +++ b/tests/net/lib/prometheus/formatter/src/main.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#define MAX_BUFFER_SIZE 256 + +struct prometheus_metric test_counter_metric = { + .type = PROMETHEUS_COUNTER, + .name = "test_counter", + .description = "Test counter", + .num_labels = 1, + .labels = {{ + .key = "test", + .value = "counter", + }}, +}; + +PROMETHEUS_COUNTER_DEFINE(test_counter_m, &test_counter_metric); + +PROMETHEUS_COLLECTOR_DEFINE(test_custom_collector); + +/** + * @brief Test Prometheus formatter + * @details The test shall increment the counter value by 1 and check if the + * value is incremented correctly. It shall then format the metric and compare + * it with the expected output. + */ +ZTEST(test_formatter, test_prometheus_formatter_simple) +{ + int ret; + char formatted[MAX_BUFFER_SIZE]; + struct prometheus_counter *counter; + char exposed[] = "# HELP test_counter Test counter\n" + "# TYPE test_counter counter\n" + "test_counter{test=\"counter\"} 1\n"; + + prometheus_collector_register_metric(&test_custom_collector, test_counter_m.base); + + counter = (struct prometheus_counter *)prometheus_collector_get_metric( + &test_custom_collector, "test_counter"); + + zassert_equal(counter, &test_counter_m, "Counter not found in collector"); + + zassert_equal(test_counter_m.value, 0, "Counter value is not 0"); + + ret = prometheus_counter_inc(&test_counter_m); + zassert_ok(ret, "Error incrementing counter"); + + zassert_equal(counter->value, 1, "Counter value is not 1"); + + ret = prometheus_format_exposition(&test_custom_collector, formatted, sizeof(formatted)); + zassert_ok(ret, "Error formatting exposition data"); + + zassert_equal(strcmp(formatted, exposed), 0, "Exposition format is not as expected"); +} + +ZTEST_SUITE(test_formatter, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/net/lib/prometheus/formatter/testcase.yaml b/tests/net/lib/prometheus/formatter/testcase.yaml new file mode 100644 index 0000000000000..57a5e6307ddcd --- /dev/null +++ b/tests/net/lib/prometheus/formatter/testcase.yaml @@ -0,0 +1,11 @@ +tests: + # section.subsection + prometheus.formatter: + depends_on: netif + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 + tags: prometheus diff --git a/tests/net/lib/prometheus/gauge/CMakeLists.txt b/tests/net/lib/prometheus/gauge/CMakeLists.txt new file mode 100644 index 0000000000000..0b33896929d5e --- /dev/null +++ b/tests/net/lib/prometheus/gauge/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(test_prometheus_gauge) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/net/lib/prometheus/gauge/prj.conf b/tests/net/lib/prometheus/gauge/prj.conf new file mode 100644 index 0000000000000..547832cdf8ed1 --- /dev/null +++ b/tests/net/lib/prometheus/gauge/prj.conf @@ -0,0 +1,11 @@ +CONFIG_LOG=y +CONFIG_ZTEST=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_ZTEST_STACK_SIZE=1024 +CONFIG_PROMETHEUS=y +CONFIG_POSIX_API=y +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_LOG=y +CONFIG_HTTP_SERVER=y +CONFIG_NET_TEST=y diff --git a/tests/net/lib/prometheus/gauge/src/main.c b/tests/net/lib/prometheus/gauge/src/main.c new file mode 100644 index 0000000000000..82602c00960d4 --- /dev/null +++ b/tests/net/lib/prometheus/gauge/src/main.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +struct prometheus_metric test_gauge_metric = { + .type = PROMETHEUS_GAUGE, + .name = "test_gauge", + .description = "Test gauge", + .num_labels = 1, + .labels = {{ + .key = "test", + .value = "gauge", + }}, +}; + +PROMETHEUS_GAUGE_DEFINE(test_gauge_m, &test_gauge_metric); + +/** + * @brief Test prometheus_gauge_set + * + * @details The test shall set the gauge value to 1 and check if the + * value is set correctly. + * + * @details The test shall set the gauge value to 2 and check if the + * value is set correctly. + */ +ZTEST(test_gauge, test_gauge_set) +{ + int ret; + + zassert_equal(test_gauge_m.value, 0, "Gauge value is not 0"); + + ret = prometheus_gauge_set(&test_gauge_m, 1); + zassert_ok(ret, "Error setting gauge"); + + zassert_equal(test_gauge_m.value, 1, "Gauge value is not 1"); + + ret = prometheus_gauge_set(&test_gauge_m, 2); + zassert_ok(ret, "Error setting gauge"); + + zassert_equal(test_gauge_m.value, 2, "Gauge value is not 2"); +} + +ZTEST_SUITE(test_gauge, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/net/lib/prometheus/gauge/testcase.yaml b/tests/net/lib/prometheus/gauge/testcase.yaml new file mode 100644 index 0000000000000..4a807ccd55fc9 --- /dev/null +++ b/tests/net/lib/prometheus/gauge/testcase.yaml @@ -0,0 +1,11 @@ +tests: + # section.subsection + prometheus.gauge: + depends_on: netif + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 + tags: prometheus diff --git a/tests/net/lib/prometheus/histogram/CMakeLists.txt b/tests/net/lib/prometheus/histogram/CMakeLists.txt new file mode 100644 index 0000000000000..63e058bc29e2d --- /dev/null +++ b/tests/net/lib/prometheus/histogram/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(test_prometheus_histogram) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/net/lib/prometheus/histogram/prj.conf b/tests/net/lib/prometheus/histogram/prj.conf new file mode 100644 index 0000000000000..5e13db5a5ab3e --- /dev/null +++ b/tests/net/lib/prometheus/histogram/prj.conf @@ -0,0 +1,12 @@ +CONFIG_LOG=y +CONFIG_ZTEST=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_ZTEST_STACK_SIZE=1024 +CONFIG_PROMETHEUS=y +CONFIG_POSIX_API=y +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_LOG=y +CONFIG_HTTP_SERVER=y +CONFIG_NET_TEST=y +CONFIG_FPU=y diff --git a/tests/net/lib/prometheus/histogram/src/main.c b/tests/net/lib/prometheus/histogram/src/main.c new file mode 100644 index 0000000000000..1c4c7a5846096 --- /dev/null +++ b/tests/net/lib/prometheus/histogram/src/main.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +struct prometheus_metric test_histogram_metric = { + .type = PROMETHEUS_HISTOGRAM, + .name = "test_histogram", + .description = "Test histogram", + .num_labels = 1, + .labels = {{ + .key = "test", + .value = "histogram", + }}, +}; + +PROMETHEUS_HISTOGRAM_DEFINE(test_histogram_m, &test_histogram_metric); + +/** + * @brief Test prometheus_histogram_observe + * + * @details The test shall observe the histogram value by 1 and check if the + * value is incremented correctly. + * + * @details The test shall observe the histogram value by 2 and check if the + * value is incremented correctly. + */ +ZTEST(test_histogram, test_histogram_observe) +{ + int ret; + + zassert_equal(test_histogram_m.sum, 0, "Histogram value is not 0"); + + ret = prometheus_histogram_observe(&test_histogram_m, 1); + zassert_ok(ret, "Error observing histogram"); + + zassert_equal(test_histogram_m.sum, 1.0, "Histogram value is not 1"); + + ret = prometheus_histogram_observe(&test_histogram_m, 2); + zassert_ok(ret, "Error observing histogram"); + + zassert_equal(test_histogram_m.sum, 3.0, "Histogram value is not 2"); +} + +ZTEST_SUITE(test_histogram, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/net/lib/prometheus/histogram/testcase.yaml b/tests/net/lib/prometheus/histogram/testcase.yaml new file mode 100644 index 0000000000000..1aaca343571c4 --- /dev/null +++ b/tests/net/lib/prometheus/histogram/testcase.yaml @@ -0,0 +1,11 @@ +tests: + # section.subsection + prometheus.histogram: + depends_on: netif + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 + tags: prometheus diff --git a/tests/net/lib/prometheus/summary/CMakeLists.txt b/tests/net/lib/prometheus/summary/CMakeLists.txt new file mode 100644 index 0000000000000..d432bca777f7d --- /dev/null +++ b/tests/net/lib/prometheus/summary/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(test_prometheus_summary) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/net/lib/prometheus/summary/prj.conf b/tests/net/lib/prometheus/summary/prj.conf new file mode 100644 index 0000000000000..547832cdf8ed1 --- /dev/null +++ b/tests/net/lib/prometheus/summary/prj.conf @@ -0,0 +1,11 @@ +CONFIG_LOG=y +CONFIG_ZTEST=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_ZTEST_STACK_SIZE=1024 +CONFIG_PROMETHEUS=y +CONFIG_POSIX_API=y +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_LOG=y +CONFIG_HTTP_SERVER=y +CONFIG_NET_TEST=y diff --git a/tests/net/lib/prometheus/summary/src/main.c b/tests/net/lib/prometheus/summary/src/main.c new file mode 100644 index 0000000000000..6dc30f43fc013 --- /dev/null +++ b/tests/net/lib/prometheus/summary/src/main.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +struct prometheus_metric test_summary_metric = { + .type = PROMETHEUS_SUMMARY, + .name = "test_summary", + .description = "Test summary", + .num_labels = 1, + .labels = {{ + .key = "test", + .value = "summary", + }}, +}; + +PROMETHEUS_SUMMARY_DEFINE(test_summary_m, &test_summary_metric); + +/** + * @brief Test prometheus_summary_observe + * + * @details The test shall observe the histogram value by 1 and check if the + * value is incremented correctly. + * + * @details The test shall observe the histogram value by 2 and check if the + * value is incremented correctly. + */ +ZTEST(test_summary, test_summary_observe) +{ + int ret; + + zassert_equal(test_summary_m.sum, 0, "Histogram value is not 0"); + + ret = prometheus_summary_observe(&test_summary_m, 1); + zassert_ok(ret, "Error observing histogram"); + + zassert_equal(test_summary_m.sum, 1, "Histogram value is not 1"); + + ret = prometheus_summary_observe(&test_summary_m, 2); + zassert_ok(ret, "Error observing histogram"); + + zassert_equal(test_summary_m.sum, 3, "Histogram value is not 3"); +} + +ZTEST_SUITE(test_summary, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/net/lib/prometheus/summary/testcase.yaml b/tests/net/lib/prometheus/summary/testcase.yaml new file mode 100644 index 0000000000000..c094209d5944b --- /dev/null +++ b/tests/net/lib/prometheus/summary/testcase.yaml @@ -0,0 +1,11 @@ +tests: + # section.subsection + prometheus.summary: + depends_on: netif + integration_platforms: + - native_sim + - qemu_x86 + platform_exclude: + - native_posix + - native_posix/native/64 + tags: prometheus