add Prometheus/OpenMetrics exporter (#3081)

* add prometheus endpoint

* refine metrics implementation

* move metrics generator to ClassFlowControll

* add more metrics
align prefix

* add more metrics
clean up

* refine documentation

* revert dependencies change

* sanitize labels

* create separate module for openmetrics

* move openmetrics to separate folder

* clean up

* add basic unit-tests

* work with const numbers
add replaceAll for string replacement
avoid opening std namespace
adapt unit-tests

* Update code/main/server_main.cpp

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
This commit is contained in:
Henry Thasler
2024-06-02 21:13:15 +02:00
committed by GitHub
parent 1300242d4a
commit 1a76ae121c
13 changed files with 228 additions and 2 deletions

View File

@@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*)
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "."
REQUIRES esp_timer esp_wifi jomjol_tfliteclass jomjol_helper jomjol_controlcamera jomjol_mqtt jomjol_influxdb jomjol_fileserver_ota jomjol_image_proc jomjol_wlan)
REQUIRES esp_timer esp_wifi jomjol_tfliteclass jomjol_helper jomjol_controlcamera jomjol_mqtt jomjol_influxdb jomjol_fileserver_ota jomjol_image_proc jomjol_wlan openmetrics)

View File

@@ -927,3 +927,11 @@ string ClassFlowControll::getJSON()
{
return flowpostprocessing->GetJSON();
}
/**
* @returns a vector of all current sequences
**/
const std::vector<NumberPost*> &ClassFlowControll::getNumbers()
{
return *flowpostprocessing->GetNumbers();
}

View File

@@ -52,6 +52,7 @@ public:
string GetPrevalue(std::string _number = "");
bool ReadParameter(FILE* pfile, string& aktparamgraph);
string getJSON();
const std::vector<NumberPost*> &getNumbers();
string getNumbersName();
string TranslateAktstatus(std::string _input);

View File

@@ -474,6 +474,71 @@ esp_err_t handler_json(httpd_req_t *req)
return ESP_OK;
}
/**
* Generates a http response containing the OpenMetrics (https://openmetrics.io/) text wire format
* according to https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#text-format.
*
* A MetricFamily with a Metric for each Sequence is provided. If no valid value is available, the metric is not provided.
* MetricPoints are provided without a timestamp. Additional metrics with some device information is also provided.
*
* The metric name prefix is 'ai_on_the_edge_device_'.
*
* example configuration for Prometheus (`prometheus.yml`):
*
* - job_name: watermeter
* static_configs:
* - targets: ['watermeter.fritz.box']
*
*/
esp_err_t handler_openmetrics(httpd_req_t *req)
{
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("handler_openmetrics - Start");
#endif
ESP_LOGD(TAG, "handler_openmetrics uri: %s", req->uri);
if (bTaskAutoFlowCreated)
{
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_type(req, "text/plain"); // application/openmetrics-text is not yet supported by prometheus so we use text/plain for now
const string metricNamePrefix = "ai_on_the_edge_device";
// get current measurement (flow)
string response = createSequenceMetrics(metricNamePrefix, flowctrl.getNumbers());
// CPU Temperature
response += createMetric(metricNamePrefix + "_cpu_temperature_celsius", "current cpu temperature in celsius", "gauge", std::to_string((int)temperatureRead()));
// WiFi signal strength
response += createMetric(metricNamePrefix + "_rssi_dbm", "current WiFi signal strength in dBm", "gauge", std::to_string(get_WIFI_RSSI()));
// memory info
response += createMetric(metricNamePrefix + "_memory_heap_free_bytes", "available heap memory", "gauge", std::to_string(getESPHeapSize()));
// device uptime
response += createMetric(metricNamePrefix + "_uptime_seconds", "device uptime in seconds", "gauge", std::to_string((long)getUpTime()));
// data aquisition round
response += createMetric(metricNamePrefix + "_rounds_total", "data aquisition rounds since device startup", "counter", std::to_string(countRounds));
// the response always contains at least the metadata (HELP, TYPE) for the MetricFamily so no length check is needed
httpd_resp_send(req, response.c_str(), response.length());
}
else
{
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Flow not (yet) started: REST API /metrics not yet available!");
return ESP_ERR_NOT_FOUND;
}
#ifdef DEBUG_DETAIL_ON
LogFile.WriteHeapInfo("handler_openmetrics - Done");
#endif
return ESP_OK;
}
esp_err_t handler_wasserzaehler(httpd_req_t *req)
{
#ifdef DEBUG_DETAIL_ON
@@ -1650,4 +1715,12 @@ void register_server_main_flow_task_uri(httpd_handle_t server)
camuri.handler = handler_stream;
camuri.user_ctx = (void *)"stream";
httpd_register_uri_handler(server, &camuri);
/** will handle metrics requests */
camuri.uri = "/metrics";
camuri.handler = handler_openmetrics;
camuri.user_ctx = (void *)"metrics";
httpd_register_uri_handler(server, &camuri);
/** when adding a new handler, make sure to increment the value for config.max_uri_handlers in `main/server_main.cpp` */
}

View File

@@ -9,6 +9,7 @@
#include <esp_http_server.h>
#include "CImageBasis.h"
#include "ClassFlowControll.h"
#include "openmetrics.h"
typedef struct
{