samples: net: add prometheus library sample application

The sample uses the Zephyr HTTP server library
and demonstrates the Prometheus metric
server node. Prometheus client library
runs as a pull method. The sample contains
the HTTP request counter and increases
when refresh path of '/metrics' from the browser.

Signed-off-by: Mustafa Abdullah Kus <mustafa.kus@sparsetechnology.com>
This commit is contained in:
Mustafa Abdullah Kus 2024-10-12 23:16:51 +03:00 committed by Carles Cufí
commit 316e823178
14 changed files with 535 additions and 0 deletions

View file

@ -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()

View file

@ -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"

View file

@ -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 <https://docs.zephyrproject.org/latest/connectivity/networking/qemu_setup.html#networking-with-qemu>`_
Building and running the server
-------------------------------
To build and run the application:
.. zephyr-app-commands::
:zephyr-app: samples/net/prometheus
:board: <board to use>
:conf: <config file to use>
: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
<https://prometheus.io/docs/instrumenting/clientlibs/>`_.
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
<https://prometheus.io/docs/prometheus/latest/configuration/configuration>`_.

View file

@ -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

View file

@ -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: {}

View file

@ -0,0 +1,4 @@
#include <zephyr/linker/iterable_sections.h>
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)

Binary file not shown.

View file

@ -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__ */

View file

@ -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__ */

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,178 @@
/*
* Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/net/tls_credentials.h>
#include <zephyr/net/http/server.h>
#include <zephyr/net/http/service.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/prometheus/formatter.h>
#include <zephyr/net/prometheus/collector.h>
#include <zephyr/net/prometheus/counter.h>
#include <zephyr/net/prometheus/gauge.h>
#include <zephyr/net/prometheus/histogram.h>
#include <zephyr/net/prometheus/summary.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/logging/log.h>
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;
}

Binary file not shown.

Binary file not shown.