From e97294c7c928d9cfb6aa40eeda201ef67c14ad71 Mon Sep 17 00:00:00 2001 From: Henry Thasler Date: Sun, 2 Jun 2024 21:13:15 +0200 Subject: [PATCH 1/9] feat(rest api): Implement prometheus/openmetrics exporter --- .../jomjol_flowcontroll/ClassFlowControll.cpp | 9 ++ .../jomjol_flowcontroll/ClassFlowControll.h | 1 + .../jomjol_flowcontroll/MainFlowControl.cpp | 6 + .../jomjol_flowcontroll/MainFlowControl.h | 1 + code/components/jomjol_helper/Helper.cpp | 11 ++ code/components/jomjol_helper/Helper.h | 1 + code/components/openmetrics/CMakeLists.txt | 7 ++ code/components/openmetrics/openmetrics.cpp | 118 ++++++++++++++++++ code/components/openmetrics/openmetrics.h | 14 +++ code/main/main.cpp | 7 +- code/main/server_main.cpp | 2 +- .../openmetrics/test_openmetrics.cpp | 69 ++++++++++ code/test/test_suite_flowcontroll.cpp | 3 + 13 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 code/components/openmetrics/CMakeLists.txt create mode 100644 code/components/openmetrics/openmetrics.cpp create mode 100644 code/components/openmetrics/openmetrics.h create mode 100644 code/test/components/openmetrics/test_openmetrics.cpp diff --git a/code/components/jomjol_flowcontroll/ClassFlowControll.cpp b/code/components/jomjol_flowcontroll/ClassFlowControll.cpp index 90ed11119..14ce05f83 100644 --- a/code/components/jomjol_flowcontroll/ClassFlowControll.cpp +++ b/code/components/jomjol_flowcontroll/ClassFlowControll.cpp @@ -862,6 +862,15 @@ bool ClassFlowControll::StartMQTTService() #endif //ENABLE_MQTT +/** + * @returns a vector of all current sequences + **/ +const std::vector &ClassFlowControll::getNumbers() +{ + return *flowpostprocessing->GetNumbers(); +} + + /* Return all available numbers names (number sequences)*/ std::string ClassFlowControll::getNumbersName() { diff --git a/code/components/jomjol_flowcontroll/ClassFlowControll.h b/code/components/jomjol_flowcontroll/ClassFlowControll.h index 371845d0e..0584768ac 100644 --- a/code/components/jomjol_flowcontroll/ClassFlowControll.h +++ b/code/components/jomjol_flowcontroll/ClassFlowControll.h @@ -78,6 +78,7 @@ class ClassFlowControll : public ClassFlow std::string TranslateAktstatus(std::string _input); bool getStatusSetupModus() {return SetupModeActive;}; + const std::vector &getNumbers(); std::string getNumbersName(); std::string getNumbersName(int _number); int getNumbersSize(); diff --git a/code/components/jomjol_flowcontroll/MainFlowControl.cpp b/code/components/jomjol_flowcontroll/MainFlowControl.cpp index dc3c475fe..f8f4cccb0 100644 --- a/code/components/jomjol_flowcontroll/MainFlowControl.cpp +++ b/code/components/jomjol_flowcontroll/MainFlowControl.cpp @@ -1011,6 +1011,12 @@ void setTaskAutoFlowState(int _value) } +int getTaskAutoFlowState() +{ + return taskAutoFlowState; +} + + std::string getProcessStatus(void) { std::string process_status; diff --git a/code/components/jomjol_flowcontroll/MainFlowControl.h b/code/components/jomjol_flowcontroll/MainFlowControl.h index 2135e13ef..5b5436f20 100644 --- a/code/components/jomjol_flowcontroll/MainFlowControl.h +++ b/code/components/jomjol_flowcontroll/MainFlowControl.h @@ -19,6 +19,7 @@ esp_err_t triggerFlowStartByMqtt(std::string _topic); void triggerFlowStartByGpio(); void setTaskAutoFlowState(int _value); +int getTaskAutoFlowState(); std::string getProcessStatus(); int getFlowCycleCounter(); diff --git a/code/components/jomjol_helper/Helper.cpp b/code/components/jomjol_helper/Helper.cpp index f92243f3f..7d223e0dc 100644 --- a/code/components/jomjol_helper/Helper.cpp +++ b/code/components/jomjol_helper/Helper.cpp @@ -645,6 +645,17 @@ std::string UrlDecode(const std::string& value) } +// from https://stackoverflow.com/a/14678800 +void replaceAll(std::string& s, const std::string& toReplace, const std::string& replaceWith) +{ + size_t pos = 0; + while ((pos = s.find(toReplace, pos)) != std::string::npos) { + s.replace(pos, toReplace.length(), replaceWith); + pos += replaceWith.length(); + } +} + + bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith) { return replaceString(s, toReplace, replaceWith, true); diff --git a/code/components/jomjol_helper/Helper.h b/code/components/jomjol_helper/Helper.h index 97980de36..70651f5c0 100644 --- a/code/components/jomjol_helper/Helper.h +++ b/code/components/jomjol_helper/Helper.h @@ -47,6 +47,7 @@ const char* get404(void); std::string UrlDecode(const std::string& value); +void replaceAll(std::string& s, const std::string& toReplace, const std::string& replaceWith); bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith); bool replaceString(std::string& s, std::string const& toReplace, std::string const& replaceWith, bool logIt); bool isInString(std::string& s, std::string const& toFind); diff --git a/code/components/openmetrics/CMakeLists.txt b/code/components/openmetrics/CMakeLists.txt new file mode 100644 index 000000000..8470774ad --- /dev/null +++ b/code/components/openmetrics/CMakeLists.txt @@ -0,0 +1,7 @@ +FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*) + +idf_component_register(SRCS ${app_sources} + INCLUDE_DIRS "." + REQUIRES jomjol_helper) + + diff --git a/code/components/openmetrics/openmetrics.cpp b/code/components/openmetrics/openmetrics.cpp new file mode 100644 index 000000000..4ebadbe3d --- /dev/null +++ b/code/components/openmetrics/openmetrics.cpp @@ -0,0 +1,118 @@ +#include "openmetrics.h" +#include "../../include/defines.h" + +#include + +#include "MainFlowControl.h" +#include "system.h" +#include "connect_wlan.h" + + +static const char *TAG = "OPENMETRICS"; + + +/** + * create a singe metric from the given input + **/ +std::string createMetric(const std::string &metricName, const std::string &help, const std::string &type, const std::string &value) +{ + return "# HELP " + metricName + " " + help + "\n" + + "# TYPE " + metricName + " " + type + "\n" + + metricName + " " + value + "\n"; +} + + +/** + * Generate the MetricFamily from all available sequences + * @returns the string containing the text wire format of the MetricFamily + **/ +std::string createSequenceMetrics(std::string prefix, const std::vector &sequences) +{ + std::string res; + + for (const auto &sequence : sequences) { + std::string sequenceName = sequence->name; + + // except newline, double quote, and backslash (https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#abnf) + // to keep it simple, these characters are just removed from the label + replaceAll(sequenceName, "\\", ""); + replaceAll(sequenceName, "\"", ""); + replaceAll(sequenceName, "\n", ""); + res += prefix + "_flow_value{sequence=\"" + sequenceName + "\"} " + sequence->sActualValue + "\n"; + } + + // prepend metadata if a valid metric was created + if (res.length() > 0) { + res = "# HELP " + prefix + "_flow_value current value of meter readout\n# TYPE " + prefix + "_flow_value gauge\n" + res; + } + + return res; +} + + +/** + * 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) +{ + if (getTaskAutoFlowState() <= FLOW_TASK_STATE_INIT) { + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "E95: Request rejected, flow not initialized"); + return ESP_FAIL; + } + + 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 std::string metricNamePrefix = "ai_on_the_edge_device"; + + // get current measurement values + std::string response = createSequenceMetrics(metricNamePrefix, flowctrl.getNumbers()); + + // CPU temperature + response += createMetric(metricNamePrefix + "_cpu_temperature_celsius", "current cpu temperature in celsius", "gauge", std::to_string((int)getSOCTemperature())); + + // 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(getESPHeapSizeTotalFree())); + + // device uptime + response += createMetric(metricNamePrefix + "_uptime_seconds", "device uptime in seconds", "gauge", std::to_string((long)getUptime())); + + // data aquisition round + response += createMetric(metricNamePrefix + "_cycles_total", "data aquisition cycles since device startup", "counter", std::to_string(getFlowCycleCounter())); + + // 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()); + + return ESP_OK; +} + + +void register_openmetrics_uri(httpd_handle_t server) +{ + ESP_LOGI(TAG, "Registering URI handlers"); + + httpd_uri_t camuri = { }; + camuri.method = HTTP_GET; + + camuri.uri = "/metrics"; + camuri.handler = handler_openmetrics; + camuri.user_ctx = NULL; + httpd_register_uri_handler(server, &camuri); +} diff --git a/code/components/openmetrics/openmetrics.h b/code/components/openmetrics/openmetrics.h new file mode 100644 index 000000000..57da102d9 --- /dev/null +++ b/code/components/openmetrics/openmetrics.h @@ -0,0 +1,14 @@ +#ifndef OPENMETRICS_H +#define OPENMETRICS_H + +#include +#include + +#include "ClassFlowDefineTypes.h" + +std::string createMetric(const std::string &metricName, const std::string &help, const std::string &type, const std::string &value); +std::string createSequenceMetrics(std::string prefix, const std::vector &numbers); + +void register_openmetrics_uri(httpd_handle_t server); + +#endif // OPENMETRICS_H diff --git a/code/main/main.cpp b/code/main/main.cpp index 3918fe60c..f13462d27 100644 --- a/code/main/main.cpp +++ b/code/main/main.cpp @@ -45,6 +45,8 @@ #include "server_mqtt.h" #endif //ENABLE_MQTT +#include "openmetrics.h" + #include "Helper.h" #include "system.h" #include "statusled.h" @@ -341,10 +343,13 @@ extern "C" void app_main(void) register_server_main_flow_task_uri(server); register_server_file_uri(server, "/sdcard"); register_server_ota_sdcard_uri(server); + #ifdef ENABLE_MQTT - register_server_mqtt_uri(server); + register_server_mqtt_uri(server); #endif //ENABLE_MQTT + register_openmetrics_uri(server); + gpio_handler_create(server); ESP_LOGD(TAG, "Before reg server main"); diff --git a/code/main/server_main.cpp b/code/main/server_main.cpp index 145813f84..ea6bb5295 100644 --- a/code/main/server_main.cpp +++ b/code/main/server_main.cpp @@ -616,7 +616,7 @@ httpd_handle_t start_webserver(void) config.server_port = 80; config.ctrl_port = 32768; config.max_open_sockets = 5; //20210921 --> previously 7 - config.max_uri_handlers = 20; // previously 42 + config.max_uri_handlers = 21; config.max_resp_headers = 8; config.backlog_conn = 5; config.lru_purge_enable = true; // this cuts old connections if new ones are needed. diff --git a/code/test/components/openmetrics/test_openmetrics.cpp b/code/test/components/openmetrics/test_openmetrics.cpp new file mode 100644 index 000000000..259a71161 --- /dev/null +++ b/code/test/components/openmetrics/test_openmetrics.cpp @@ -0,0 +1,69 @@ +#include +#include "openmetrics.h" + + +void test_createMetric() +{ + // simple happy path + const char *expected = "# HELP metric_name short description\n# TYPE metric_name gauge\nmetric_name 123.456\n"; + std::string result = createMetric("metric_name", "short description", "gauge", "123.456"); + TEST_ASSERT_EQUAL_STRING(expected, result.c_str()); +} + + +/** + * test the replaceString function as it's a dependency to sanitize sequence names + */ +void test_replaceString() +{ + std::string sample = "hello\\world\\"; + replaceAll(sample, "\\", ""); + TEST_ASSERT_EQUAL_STRING("helloworld", sample.c_str()); + + sample = "hello\"world\""; + replaceAll(sample, "\"", ""); + TEST_ASSERT_EQUAL_STRING("helloworld", sample.c_str()); + + sample = "hello\nworld\n"; + replaceAll(sample, "\n", ""); + TEST_ASSERT_EQUAL_STRING("helloworld", sample.c_str()); + + sample = "\\\\\\\\\\\\\\\\\\hello\\world\\\\\\\\\\\\\\\\\\\\"; + replaceAll(sample, "\\", ""); + TEST_ASSERT_EQUAL_STRING("helloworld", sample.c_str()); +} + + +void test_createSequenceMetrics() +{ + std::vector sequences; + NumberPost *number_1 = new NumberPost; + number_1->name = "main"; + number_1->sActualValue = "123.456"; + sequences.push_back(number_1); + + const std::string metricNamePrefix = "ai_on_the_edge_device"; + const std::string metricName = metricNamePrefix + "_flow_value"; + + std::string expected1 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" + + metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->sActualValue + "\n"; + TEST_ASSERT_EQUAL_STRING(expected1.c_str(), createSequenceMetrics(metricNamePrefix, sequences).c_str()); + + NumberPost *number_2 = new NumberPost; + number_2->name = "secondary"; + number_2->sActualValue = "1.0"; + sequences.push_back(number_2); + + std::string expected2 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" + + metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->sActualValue + "\n" + + metricName + "{sequence=\"" + number_2->name + "\"} " + number_2->sActualValue + "\n"; + TEST_ASSERT_EQUAL_STRING(expected2.c_str(), createSequenceMetrics(metricNamePrefix, sequences).c_str()); +} + + +void test_openmetrics() +{ + test_createMetric(); + test_replaceString(); + test_createSequenceMetrics(); +} diff --git a/code/test/test_suite_flowcontroll.cpp b/code/test/test_suite_flowcontroll.cpp index c18d687f7..8fab4f48d 100644 --- a/code/test/test_suite_flowcontroll.cpp +++ b/code/test/test_suite_flowcontroll.cpp @@ -26,6 +26,7 @@ #include "components/jomjol-flowcontroll/test_flow_pp_negative.cpp" #include "components/jomjol-flowcontroll/test_PointerEvalAnalogToDigitNew.cpp" #include "components/jomjol-flowcontroll/test_getReadoutRawString.cpp" +#include "components/openmetrics/test_openmetrics.cpp" esp_err_t initNVSFlash(); esp_err_t initSDCard(); @@ -70,6 +71,8 @@ void task_UnityTesting(void *pvParameter) RUN_TEST(test_doFlowPP4); printf("---------------------------------------------------------------------------\n"); RUN_TEST(test_doFlowPP5); + printf("---------------------------------------------------------------------------\n"); + RUN_TEST(test_openmetrics); UNITY_END(); while(1); From 5422799c3bf015abe93eedacd02b09bd5d093e2f Mon Sep 17 00:00:00 2001 From: Slider0007 Date: Fri, 14 Jun 2024 13:29:26 +0200 Subject: [PATCH 2/9] Add more metrics --- code/components/openmetrics/openmetrics.cpp | 173 +++++++++++++++++--- code/components/openmetrics/openmetrics.h | 3 - 2 files changed, 149 insertions(+), 27 deletions(-) diff --git a/code/components/openmetrics/openmetrics.cpp b/code/components/openmetrics/openmetrics.cpp index 4ebadbe3d..feb2eeb14 100644 --- a/code/components/openmetrics/openmetrics.cpp +++ b/code/components/openmetrics/openmetrics.cpp @@ -2,17 +2,81 @@ #include "../../include/defines.h" #include +#include "esp_private/esp_clk.h" #include "MainFlowControl.h" #include "system.h" #include "connect_wlan.h" +extern std::string getFwVersion(void); + static const char *TAG = "OPENMETRICS"; /** - * create a singe metric from the given input + * Create a hardware info metric + **/ +std::string createHardwareInfoMetric(const std::string &metricNamePrefix) +{ + return "# HELP " + metricNamePrefix + "hardware_info Hardware info\n" + + "# TYPE " + metricNamePrefix + "hardware_info info\n" + + metricNamePrefix + "_hardware_info{board_type=\"" + getBoardType() + + "\",chip_model=\"" + getChipModel() + + "\",chip_cores=\"" + std::to_string(getChipCoreCount()) + + "\",chip_revision=\"" + getChipRevision() + + "\",chip_frequency=\"" + std::to_string(esp_clk_cpu_freq()/1000000) + + "\",camera_type=\"" + Camera.getCamType() + + "\",camera_frequency=\"" + std::to_string(Camera.getCamFrequencyMhz()) + + "\",sdcard_capacity=\"" + std::to_string(getSDCardCapacity()) + + "\",sdcard_partition_size=\"" + std::to_string(getSDCardPartitionSize()) + "\"} 1\n"; +} + + +/** + * Create a network info metric + **/ +std::string createNetworkInfoMetric(const std::string &metricNamePrefix) +{ + return "# HELP " + metricNamePrefix + "network_info Network info\n" + + "# TYPE " + metricNamePrefix + "network_info info\n" + + metricNamePrefix + "_network_info{hostname=\"" + getHostname() + + "\",ipv4_address=\"" + getIPAddress() + + "\",mac_address=\"" + getMac() + "\"} 1\n"; +} + + +/** + * Create a firmware info metric + **/ +std::string createFirmwareInfoMetric(const std::string &metricNamePrefix) +{ + return "# HELP " + metricNamePrefix + "firmware_info Firmware info\n" + + "# TYPE " + metricNamePrefix + "firmware_info info\n" + + metricNamePrefix + "_firmware_info{firmware_version=\"" + getFwVersion() + "\"} 1\n"; +} + + +/** + * Create heap data metrics + **/ +std::string createHeapDataMetric(const std::string &metricNamePrefix) +{ + return "# HELP " + metricNamePrefix + "heap_info_bytes Heap info\n" + + "# UNIT " + metricNamePrefix + "heap_info_bytes bytes\n" + + "# TYPE " + metricNamePrefix + "heap_info_bytes gauge\n" + + metricNamePrefix + "heap_info_bytes{heap_total_free=\"" + std::to_string(getESPHeapSizeTotalFree()) + "\"\n" + + metricNamePrefix + "heap_info_bytes{heap_internal_free=\"" + std::to_string(getESPHeapSizeInternalFree()) + "\"\n" + + metricNamePrefix + "heap_info_bytes{heap_internal_largest_free=\"" + std::to_string(getESPHeapSizeInternalLargestFree()) + "\"\n" + + metricNamePrefix + "heap_info_bytes{heap_internal_min_free=\"" + std::to_string(getESPHeapSizeInternalMinFree()) + "\"\n" + + metricNamePrefix + "heap_info_bytes{heap_spiram_free=\"" + std::to_string(getESPHeapSizeSPIRAMFree()) + "\"\n" + + metricNamePrefix + "heap_info_bytes{heap_spiram_largest_free=\"" + std::to_string(getESPHeapSizeSPIRAMLargestFree()) + "\"\n" + + metricNamePrefix + "heap_info_bytes{heap_spiram_min_free=\"" + std::to_string(getESPHeapSizeSPIRAMMinFree()) + "\"\n"; +} + + +/** + * Create a generic single metric **/ std::string createMetric(const std::string &metricName, const std::string &help, const std::string &type, const std::string &value) { @@ -22,13 +86,27 @@ std::string createMetric(const std::string &metricName, const std::string &help, } +/** + * Create a generic single metric with unit + **/ +std::string createMetricWithUnit(const std::string &metricName, const std::string &help, const std::string &type, + const std::string &unit, const std::string &value) +{ + return "# HELP " + metricName + "_" + unit + " " + help + "\n" + + "# UNIT " + metricName + "_" + unit + " " + unit + "\n" + + "# TYPE " + metricName + "_" + unit + " " + type + "\n" + + metricName + " " + value + "\n"; +} + + /** * Generate the MetricFamily from all available sequences * @returns the string containing the text wire format of the MetricFamily **/ -std::string createSequenceMetrics(std::string prefix, const std::vector &sequences) +std::string createSequenceMetrics(const std::string &metricNamePrefix, const std::vector &sequences) { - std::string res; + std::string response = "# HELP " + metricNamePrefix + "_actual_value actual value of meter\n" + + "# TYPE " + metricNamePrefix + "_actual_value gauge\n"; for (const auto &sequence : sequences) { std::string sequenceName = sequence->name; @@ -38,15 +116,24 @@ std::string createSequenceMetrics(std::string prefix, const std::vectorsActualValue + "\n"; + response += metricNamePrefix + "_actual_value{sequence=\"" + sequenceName + "\"} " + sequence->sActualValue + "\n"; } - // prepend metadata if a valid metric was created - if (res.length() > 0) { - res = "# HELP " + prefix + "_flow_value current value of meter readout\n# TYPE " + prefix + "_flow_value gauge\n" + res; + response += "# HELP " + metricNamePrefix + "_rate_per_minute rate per minute of meter\n" + + "# TYPE " + metricNamePrefix + "_rate_per_minute gauge\n"; + + for (const auto &sequence : sequences) { + std::string sequenceName = sequence->name; + + // except newline, double quote, and backslash (https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#abnf) + // to keep it simple, these characters are just removed from the label + replaceAll(sequenceName, "\\", ""); + replaceAll(sequenceName, "\"", ""); + replaceAll(sequenceName, "\n", ""); + response += metricNamePrefix + "_rate_per_minute{sequence=\"" + sequenceName + "\"} " + sequence->sRatePerMin + "\n"; } - return res; + return response; } @@ -59,11 +146,11 @@ std::string createSequenceMetrics(std::string prefix, const std::vector &numbers); - void register_openmetrics_uri(httpd_handle_t server); #endif // OPENMETRICS_H From 432bb72fff04809557c01f92ef34a083e02817d5 Mon Sep 17 00:00:00 2001 From: Slider0007 Date: Fri, 14 Jun 2024 21:09:46 +0200 Subject: [PATCH 3/9] Update --- code/components/openmetrics/openmetrics.cpp | 79 +++++++++++---------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/code/components/openmetrics/openmetrics.cpp b/code/components/openmetrics/openmetrics.cpp index feb2eeb14..b92f2d243 100644 --- a/code/components/openmetrics/openmetrics.cpp +++ b/code/components/openmetrics/openmetrics.cpp @@ -20,8 +20,8 @@ static const char *TAG = "OPENMETRICS"; std::string createHardwareInfoMetric(const std::string &metricNamePrefix) { return "# HELP " + metricNamePrefix + "hardware_info Hardware info\n" + - "# TYPE " + metricNamePrefix + "hardware_info info\n" + - metricNamePrefix + "_hardware_info{board_type=\"" + getBoardType() + + "# TYPE " + metricNamePrefix + "hardware_info gauge\n" + + metricNamePrefix + "hardware_info{board_type=\"" + getBoardType() + "\",chip_model=\"" + getChipModel() + "\",chip_cores=\"" + std::to_string(getChipCoreCount()) + "\",chip_revision=\"" + getChipRevision() + @@ -39,8 +39,8 @@ std::string createHardwareInfoMetric(const std::string &metricNamePrefix) std::string createNetworkInfoMetric(const std::string &metricNamePrefix) { return "# HELP " + metricNamePrefix + "network_info Network info\n" + - "# TYPE " + metricNamePrefix + "network_info info\n" + - metricNamePrefix + "_network_info{hostname=\"" + getHostname() + + "# TYPE " + metricNamePrefix + "network_info gauge\n" + + metricNamePrefix + "network_info{hostname=\"" + getHostname() + "\",ipv4_address=\"" + getIPAddress() + "\",mac_address=\"" + getMac() + "\"} 1\n"; } @@ -52,8 +52,8 @@ std::string createNetworkInfoMetric(const std::string &metricNamePrefix) std::string createFirmwareInfoMetric(const std::string &metricNamePrefix) { return "# HELP " + metricNamePrefix + "firmware_info Firmware info\n" + - "# TYPE " + metricNamePrefix + "firmware_info info\n" + - metricNamePrefix + "_firmware_info{firmware_version=\"" + getFwVersion() + "\"} 1\n"; + "# TYPE " + metricNamePrefix + "firmware_info gauge\n" + + metricNamePrefix + "firmware_info{firmware_version=\"" + getFwVersion() + "\"} 1\n"; } @@ -65,13 +65,13 @@ std::string createHeapDataMetric(const std::string &metricNamePrefix) return "# HELP " + metricNamePrefix + "heap_info_bytes Heap info\n" + "# UNIT " + metricNamePrefix + "heap_info_bytes bytes\n" + "# TYPE " + metricNamePrefix + "heap_info_bytes gauge\n" + - metricNamePrefix + "heap_info_bytes{heap_total_free=\"" + std::to_string(getESPHeapSizeTotalFree()) + "\"\n" + - metricNamePrefix + "heap_info_bytes{heap_internal_free=\"" + std::to_string(getESPHeapSizeInternalFree()) + "\"\n" + - metricNamePrefix + "heap_info_bytes{heap_internal_largest_free=\"" + std::to_string(getESPHeapSizeInternalLargestFree()) + "\"\n" + - metricNamePrefix + "heap_info_bytes{heap_internal_min_free=\"" + std::to_string(getESPHeapSizeInternalMinFree()) + "\"\n" + - metricNamePrefix + "heap_info_bytes{heap_spiram_free=\"" + std::to_string(getESPHeapSizeSPIRAMFree()) + "\"\n" + - metricNamePrefix + "heap_info_bytes{heap_spiram_largest_free=\"" + std::to_string(getESPHeapSizeSPIRAMLargestFree()) + "\"\n" + - metricNamePrefix + "heap_info_bytes{heap_spiram_min_free=\"" + std::to_string(getESPHeapSizeSPIRAMMinFree()) + "\"\n"; + metricNamePrefix + "heap_info_bytes{type=\"heap_total_free\"} " + std::to_string(getESPHeapSizeTotalFree()) + "\n" + + metricNamePrefix + "heap_info_bytes{type=\"heap_internal_free\"} " + std::to_string(getESPHeapSizeInternalFree()) + "\n" + + metricNamePrefix + "heap_info_bytes{type=\"heap_internal_largest_free\"} " + std::to_string(getESPHeapSizeInternalLargestFree()) + "\n" + + metricNamePrefix + "heap_info_bytes{type=\"heap_internal_min_free\"} " + std::to_string(getESPHeapSizeInternalMinFree()) + "\n" + + metricNamePrefix + "heap_info_bytes{type=\"heap_spiram_free\"} " + std::to_string(getESPHeapSizeSPIRAMFree()) + "\n" + + metricNamePrefix + "heap_info_bytes{type=\"heap_spiram_largest_free\"} " + std::to_string(getESPHeapSizeSPIRAMLargestFree()) + "\n" + + metricNamePrefix + "heap_info_bytes{type=\"heap_spiram_min_free\"} " + std::to_string(getESPHeapSizeSPIRAMMinFree()) + "\n"; } @@ -95,7 +95,7 @@ std::string createMetricWithUnit(const std::string &metricName, const std::strin return "# HELP " + metricName + "_" + unit + " " + help + "\n" + "# UNIT " + metricName + "_" + unit + " " + unit + "\n" + "# TYPE " + metricName + "_" + unit + " " + type + "\n" + - metricName + " " + value + "\n"; + metricName + "_" + unit + " " + value + "\n"; } @@ -105,8 +105,7 @@ std::string createMetricWithUnit(const std::string &metricName, const std::strin **/ std::string createSequenceMetrics(const std::string &metricNamePrefix, const std::vector &sequences) { - std::string response = "# HELP " + metricNamePrefix + "_actual_value actual value of meter\n" + - "# TYPE " + metricNamePrefix + "_actual_value gauge\n"; + std::string response; for (const auto &sequence : sequences) { std::string sequenceName = sequence->name; @@ -116,11 +115,21 @@ std::string createSequenceMetrics(const std::string &metricNamePrefix, const std replaceAll(sequenceName, "\\", ""); replaceAll(sequenceName, "\"", ""); replaceAll(sequenceName, "\n", ""); - response += metricNamePrefix + "_actual_value{sequence=\"" + sequenceName + "\"} " + sequence->sActualValue + "\n"; + + if (!sequence->sActualValue.empty()) + response += metricNamePrefix + "actual_value{sequence=\"" + sequenceName + "\"} " + sequence->sActualValue + "\n"; + } + + // Return if no valid value is available + if (response.empty()) { + return response; } - response += "# HELP " + metricNamePrefix + "_rate_per_minute rate per minute of meter\n" + - "# TYPE " + metricNamePrefix + "_rate_per_minute gauge\n"; + response += "# HELP " + metricNamePrefix + "actual_value Actual value of meter\n" + + "# TYPE " + metricNamePrefix + "actual_value gauge\n" + response; + + response += "# HELP " + metricNamePrefix + "rate_per_minute Rate per minute of meter\n" + + "# TYPE " + metricNamePrefix + "rate_per_minute gauge\n"; for (const auto &sequence : sequences) { std::string sequenceName = sequence->name; @@ -130,7 +139,7 @@ std::string createSequenceMetrics(const std::string &metricNamePrefix, const std replaceAll(sequenceName, "\\", ""); replaceAll(sequenceName, "\"", ""); replaceAll(sequenceName, "\n", ""); - response += metricNamePrefix + "_rate_per_minute{sequence=\"" + sequenceName + "\"} " + sequence->sRatePerMin + "\n"; + response += metricNamePrefix + "rate_per_minute{sequence=\"" + sequenceName + "\"} " + sequence->sRatePerMin + "\n"; } return response; @@ -177,7 +186,7 @@ esp_err_t handler_openmetrics(httpd_req_t *req) response += createFirmwareInfoMetric(metricNamePrefix); // Device uptime - response += createMetricWithUnit(metricNamePrefix + "device_uptime", "device uptime in seconds", + response += createMetricWithUnit(metricNamePrefix + "device_uptime", "Device uptime in seconds", "gauge", "seconds", std::to_string((long)getUptime())); // WLAN signal strength @@ -191,33 +200,25 @@ esp_err_t handler_openmetrics(httpd_req_t *req) // Heap data response += createHeapDataMetric(metricNamePrefix); - // SD card partition free - response += createMetricWithUnit(metricNamePrefix + "sd_partition_free", "Free SD partition size in bytes", - "gauge", "bytes", std::to_string(getSDCardFreePartitionSpace())); - - // Process status - response += createMetric(metricNamePrefix + "process_status", "Device process status", - "gauge", getProcessStatus()); - - // Process state - response += createMetric(metricNamePrefix + "process_state", "Actual processing step", - "gauge", flowctrl.getActStatus()); + // SD card partition free space + response += createMetricWithUnit(metricNamePrefix + "sd_partition_free", "Free SD partition space in MB", + "gauge", "MB", std::to_string(getSDCardFreePartitionSpace())); // Process error state response += createMetric(metricNamePrefix + "process_error", "Process error state", - "counter", std::to_string(flowctrl.getFlowStateErrorOrDeviation())); + "gauge", std::to_string(flowctrl.getFlowStateErrorOrDeviation())); // Processing interval - response += createMetricWithUnit(metricNamePrefix + "process_interval", "processing interval", - "counter", "seconds", to_stringWithPrecision(flowctrl.getProcessInterval(), 1)); + response += createMetricWithUnit(metricNamePrefix + "process_interval", "Processing interval", + "gauge", "minutes", to_stringWithPrecision(flowctrl.getProcessInterval(), 1)); // Processing time - response += createMetricWithUnit(metricNamePrefix + "process_time", "processing time of one cycle", - "counter", "seconds", std::to_string(getFlowProcessingTime())); + response += createMetricWithUnit(metricNamePrefix + "process_time", "Processing time of one cycle", + "gauge", "seconds", std::to_string(getFlowProcessingTime())); // Process cycles - response += createMetric(metricNamePrefix + "cycle_counter", "process cycles since device startup", - "counter", std::to_string(getFlowCycleCounter())); + response += createMetric(metricNamePrefix + "cycle_counter", "Process cycles since device startup", + "counter", std::to_string(getFlowCycleCounter())); // Actual measurement values response += createSequenceMetrics(metricNamePrefix, flowctrl.getNumbers()); From 4c077f87c7a13303f376dbeb7e90135f2d019206 Mon Sep 17 00:00:00 2001 From: Slider0007 Date: Sat, 15 Jun 2024 15:18:09 +0200 Subject: [PATCH 4/9] Update --- code/components/openmetrics/CMakeLists.txt | 2 +- code/components/openmetrics/openmetrics.cpp | 110 ++++++++++++-------- code/components/openmetrics/openmetrics.h | 4 +- 3 files changed, 66 insertions(+), 50 deletions(-) diff --git a/code/components/openmetrics/CMakeLists.txt b/code/components/openmetrics/CMakeLists.txt index 8470774ad..17ab47dd0 100644 --- a/code/components/openmetrics/CMakeLists.txt +++ b/code/components/openmetrics/CMakeLists.txt @@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*) idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "." - REQUIRES jomjol_helper) + REQUIRES jomjol_helper jomjol_flowcontroll jomjol_wlan) diff --git a/code/components/openmetrics/openmetrics.cpp b/code/components/openmetrics/openmetrics.cpp index b92f2d243..afb86c349 100644 --- a/code/components/openmetrics/openmetrics.cpp +++ b/code/components/openmetrics/openmetrics.cpp @@ -1,11 +1,15 @@ #include "openmetrics.h" #include "../../include/defines.h" +#include +#include + #include #include "esp_private/esp_clk.h" -#include "MainFlowControl.h" #include "system.h" +#include "ClassFlowDefineTypes.h" +#include "MainFlowControl.h" #include "connect_wlan.h" extern std::string getFwVersion(void); @@ -19,8 +23,8 @@ static const char *TAG = "OPENMETRICS"; **/ std::string createHardwareInfoMetric(const std::string &metricNamePrefix) { - return "# HELP " + metricNamePrefix + "hardware_info Hardware info\n" + - "# TYPE " + metricNamePrefix + "hardware_info gauge\n" + + return "# TYPE " + metricNamePrefix + "hardware_info gauge\n" + + "# HELP " + metricNamePrefix + "hardware_info Hardware info\n" + metricNamePrefix + "hardware_info{board_type=\"" + getBoardType() + "\",chip_model=\"" + getChipModel() + "\",chip_cores=\"" + std::to_string(getChipCoreCount()) + @@ -38,8 +42,8 @@ std::string createHardwareInfoMetric(const std::string &metricNamePrefix) **/ std::string createNetworkInfoMetric(const std::string &metricNamePrefix) { - return "# HELP " + metricNamePrefix + "network_info Network info\n" + - "# TYPE " + metricNamePrefix + "network_info gauge\n" + + return "# TYPE " + metricNamePrefix + "network_info gauge\n" + + "# HELP " + metricNamePrefix + "network_info Network info\n" + metricNamePrefix + "network_info{hostname=\"" + getHostname() + "\",ipv4_address=\"" + getIPAddress() + "\",mac_address=\"" + getMac() + "\"} 1\n"; @@ -51,8 +55,8 @@ std::string createNetworkInfoMetric(const std::string &metricNamePrefix) **/ std::string createFirmwareInfoMetric(const std::string &metricNamePrefix) { - return "# HELP " + metricNamePrefix + "firmware_info Firmware info\n" + - "# TYPE " + metricNamePrefix + "firmware_info gauge\n" + + return "# TYPE " + metricNamePrefix + "firmware_info gauge\n" + + "# HELP " + metricNamePrefix + "firmware_info Firmware info\n" + metricNamePrefix + "firmware_info{firmware_version=\"" + getFwVersion() + "\"} 1\n"; } @@ -62,26 +66,32 @@ std::string createFirmwareInfoMetric(const std::string &metricNamePrefix) **/ std::string createHeapDataMetric(const std::string &metricNamePrefix) { - return "# HELP " + metricNamePrefix + "heap_info_bytes Heap info\n" + - "# UNIT " + metricNamePrefix + "heap_info_bytes bytes\n" + - "# TYPE " + metricNamePrefix + "heap_info_bytes gauge\n" + - metricNamePrefix + "heap_info_bytes{type=\"heap_total_free\"} " + std::to_string(getESPHeapSizeTotalFree()) + "\n" + - metricNamePrefix + "heap_info_bytes{type=\"heap_internal_free\"} " + std::to_string(getESPHeapSizeInternalFree()) + "\n" + - metricNamePrefix + "heap_info_bytes{type=\"heap_internal_largest_free\"} " + std::to_string(getESPHeapSizeInternalLargestFree()) + "\n" + - metricNamePrefix + "heap_info_bytes{type=\"heap_internal_min_free\"} " + std::to_string(getESPHeapSizeInternalMinFree()) + "\n" + - metricNamePrefix + "heap_info_bytes{type=\"heap_spiram_free\"} " + std::to_string(getESPHeapSizeSPIRAMFree()) + "\n" + - metricNamePrefix + "heap_info_bytes{type=\"heap_spiram_largest_free\"} " + std::to_string(getESPHeapSizeSPIRAMLargestFree()) + "\n" + - metricNamePrefix + "heap_info_bytes{type=\"heap_spiram_min_free\"} " + std::to_string(getESPHeapSizeSPIRAMMinFree()) + "\n"; + return "# TYPE " + metricNamePrefix + "heap_data_bytes gauge\n" + + "# UNIT " + metricNamePrefix + "heap_data_bytes bytes\n" + + "# HELP " + metricNamePrefix + "heap_data_bytes Heap data\n" + + metricNamePrefix + "heap_data_bytes{heap_data=\"heap_total_free\"} " + std::to_string(getESPHeapSizeTotalFree()) + "\n" + + metricNamePrefix + "heap_data_bytes{heap_data=\"heap_internal_free\"} " + std::to_string(getESPHeapSizeInternalFree()) + "\n" + + metricNamePrefix + "heap_data_bytes{heap_data=\"heap_internal_largest_free\"} " + std::to_string(getESPHeapSizeInternalLargestFree()) + "\n" + + metricNamePrefix + "heap_data_bytes{heap_data=\"heap_internal_min_free\"} " + std::to_string(getESPHeapSizeInternalMinFree()) + "\n" + + metricNamePrefix + "heap_data_bytes{heap_data=\"heap_spiram_free\"} " + std::to_string(getESPHeapSizeSPIRAMFree()) + "\n" + + metricNamePrefix + "heap_data_bytes{heap_data=\"heap_spiram_largest_free\"} " + std::to_string(getESPHeapSizeSPIRAMLargestFree()) + "\n" + + metricNamePrefix + "heap_data_bytes{heap_data=\"heap_spiram_min_free\"} " + std::to_string(getESPHeapSizeSPIRAMMinFree()) + "\n"; } /** * Create a generic single metric **/ -std::string createMetric(const std::string &metricName, const std::string &help, const std::string &type, const std::string &value) +std::string createMetric(const std::string &metricName, const std::string &type, const std::string &help, const std::string &value) { - return "# HELP " + metricName + " " + help + "\n" + - "# TYPE " + metricName + " " + type + "\n" + + if (type == "counter") { + return "# TYPE " + metricName + " " + type + "\n" + + "# HELP " + metricName + " " + help + "\n" + + metricName + "_total " + value + "\n"; + } + + return "# TYPE " + metricName + " " + type + "\n" + + "# HELP " + metricName + " " + help + "\n" + metricName + " " + value + "\n"; } @@ -89,12 +99,19 @@ std::string createMetric(const std::string &metricName, const std::string &help, /** * Create a generic single metric with unit **/ -std::string createMetricWithUnit(const std::string &metricName, const std::string &help, const std::string &type, - const std::string &unit, const std::string &value) +std::string createMetricWithUnit(const std::string &metricName, const std::string &type, const std::string &unit, + const std::string &help, const std::string &value) { - return "# HELP " + metricName + "_" + unit + " " + help + "\n" + + if (type == "counter") { + return "# TYPE " + metricName + "_" + unit + " " + type + "\n" + + "# UNIT " + metricName + "_" + unit + " " + unit + "\n" + + "# HELP " + metricName + "_" + unit + " " + help + "\n" + + metricName + "_" + unit + "_total " + value + "\n"; + } + + return "# TYPE " + metricName + "_" + unit + " " + type + "\n" + "# UNIT " + metricName + "_" + unit + " " + unit + "\n" + - "# TYPE " + metricName + "_" + unit + " " + type + "\n" + + "# HELP " + metricName + "_" + unit + " " + help + "\n" + metricName + "_" + unit + " " + value + "\n"; } @@ -121,15 +138,16 @@ std::string createSequenceMetrics(const std::string &metricNamePrefix, const std } // Return if no valid value is available - if (response.empty()) { + if (response.empty()) return response; - } - response += "# HELP " + metricNamePrefix + "actual_value Actual value of meter\n" + - "# TYPE " + metricNamePrefix + "actual_value gauge\n" + response; + // Add metadata to value data if values are avialbale + response = "# TYPE " + metricNamePrefix + "actual_value gauge\n" + + "# HELP " + metricNamePrefix + "actual_value Actual value of meter\n" + response; - response += "# HELP " + metricNamePrefix + "rate_per_minute Rate per minute of meter\n" + - "# TYPE " + metricNamePrefix + "rate_per_minute gauge\n"; + // Add rate per minute + response += "# TYPE " + metricNamePrefix + "rate_per_minute gauge\n" + + "# HELP " + metricNamePrefix + "rate_per_minute Rate per minute of meter\n"; for (const auto &sequence : sequences) { std::string sequenceName = sequence->name; @@ -186,39 +204,39 @@ esp_err_t handler_openmetrics(httpd_req_t *req) response += createFirmwareInfoMetric(metricNamePrefix); // Device uptime - response += createMetricWithUnit(metricNamePrefix + "device_uptime", "Device uptime in seconds", - "gauge", "seconds", std::to_string((long)getUptime())); + response += createMetricWithUnit(metricNamePrefix + "device_uptime", "gauge", "seconds", + "Device uptime in seconds", std::to_string((long)getUptime())); // WLAN signal strength - response += createMetricWithUnit(metricNamePrefix + "wlan_rssi", "WLAN signal strength in dBm", - "gauge", "dBm", std::to_string(get_WIFI_RSSI())); + response += createMetricWithUnit(metricNamePrefix + "wlan_rssi", "gauge", "dBm", + "WLAN signal strength in dBm", std::to_string(get_WIFI_RSSI())); // CPU temperature - response += createMetricWithUnit(metricNamePrefix + "chip_temp", "CPU temperature in celsius", - "gauge", "celsius", std::to_string((int)getSOCTemperature())); + response += createMetricWithUnit(metricNamePrefix + "chip_temp", "gauge", "celsius", + "CPU temperature in celsius", std::to_string((int)getSOCTemperature())); // Heap data response += createHeapDataMetric(metricNamePrefix); // SD card partition free space - response += createMetricWithUnit(metricNamePrefix + "sd_partition_free", "Free SD partition space in MB", - "gauge", "MB", std::to_string(getSDCardFreePartitionSpace())); + response += createMetricWithUnit(metricNamePrefix + "sd_partition_free", "gauge", "megabytes", + "Free SD partition space in megabytes", std::to_string(getSDCardFreePartitionSpace())); // Process error state - response += createMetric(metricNamePrefix + "process_error", "Process error state", - "gauge", std::to_string(flowctrl.getFlowStateErrorOrDeviation())); + response += createMetric(metricNamePrefix + "process_error", "gauge", + "Process error state", std::to_string(flowctrl.getFlowStateErrorOrDeviation())); // Processing interval - response += createMetricWithUnit(metricNamePrefix + "process_interval", "Processing interval", - "gauge", "minutes", to_stringWithPrecision(flowctrl.getProcessInterval(), 1)); + response += createMetricWithUnit(metricNamePrefix + "process_interval", "gauge", "minutes", + "Processing interval", to_stringWithPrecision(flowctrl.getProcessInterval(), 1)); // Processing time - response += createMetricWithUnit(metricNamePrefix + "process_time", "Processing time of one cycle", - "gauge", "seconds", std::to_string(getFlowProcessingTime())); + response += createMetricWithUnit(metricNamePrefix + "process_time", "gauge", "seconds", + "Processing time of one cycle", std::to_string(getFlowProcessingTime())); // Process cycles - response += createMetric(metricNamePrefix + "cycle_counter", "Process cycles since device startup", - "counter", std::to_string(getFlowCycleCounter())); + response += createMetric(metricNamePrefix + "cycle_counter", "counter", + "Process cycles since device startup", std::to_string(getFlowCycleCounter())); // Actual measurement values response += createSequenceMetrics(metricNamePrefix, flowctrl.getNumbers()); diff --git a/code/components/openmetrics/openmetrics.h b/code/components/openmetrics/openmetrics.h index 8915cdd7b..273ceab0d 100644 --- a/code/components/openmetrics/openmetrics.h +++ b/code/components/openmetrics/openmetrics.h @@ -1,10 +1,8 @@ #ifndef OPENMETRICS_H #define OPENMETRICS_H -#include -#include +#include -#include "ClassFlowDefineTypes.h" void register_openmetrics_uri(httpd_handle_t server); From 92f049479a6a77ec6a2b5c45f56d52cbe586e9b9 Mon Sep 17 00:00:00 2001 From: Slider0007 Date: Sat, 15 Jun 2024 21:19:53 +0200 Subject: [PATCH 5/9] Add docs --- code/components/openmetrics/openmetrics.cpp | 2 +- docs/API/Prometheus-OpenMetrics/_OVERVIEW.md | 81 ++++++++ docs/API/REST/_OVERVIEW.md | 2 + docs/API/REST/metrics.md | 179 ++++++++++++++++++ sd-card/html/doc_api_prometheus.html | 19 ++ sd-card/html/doc_api_prometheus.md | 7 + sd-card/html/index.html | 1 + .../generate-api-docs-localbuild.py | 18 +- tools/docs-generator/generate-api-docs.py | 18 +- 9 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 docs/API/Prometheus-OpenMetrics/_OVERVIEW.md create mode 100644 docs/API/REST/metrics.md create mode 100644 sd-card/html/doc_api_prometheus.html create mode 100644 sd-card/html/doc_api_prometheus.md diff --git a/code/components/openmetrics/openmetrics.cpp b/code/components/openmetrics/openmetrics.cpp index afb86c349..6fb73db57 100644 --- a/code/components/openmetrics/openmetrics.cpp +++ b/code/components/openmetrics/openmetrics.cpp @@ -143,7 +143,7 @@ std::string createSequenceMetrics(const std::string &metricNamePrefix, const std // Add metadata to value data if values are avialbale response = "# TYPE " + metricNamePrefix + "actual_value gauge\n" + - "# HELP " + metricNamePrefix + "actual_value Actual value of meter\n" + response; + "# HELP " + metricNamePrefix + "actual_value Actual value of meter\n" + response; // Add rate per minute response += "# TYPE " + metricNamePrefix + "rate_per_minute gauge\n" + diff --git a/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md b/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md new file mode 100644 index 000000000..3e2d0dbac --- /dev/null +++ b/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md @@ -0,0 +1,81 @@ +## Overview: Prometheus API +### Prometheus / OpenMetrics telemetry data + +A set of metrics is exported via the `/metrics` REST API endpoint (see REST API description for more detailed metric infos). +The metrics can be scraped by Prometheus or any OpenMetrics specification compatilble software.
+ +The metrics are provided in text wire format based on [OpenMetrics specification](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md) +which is backward-compatible with [Prometheus text-based exposition format](https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md). + +### Prometheus Scrape Config + +The following scrape config (add to `prometheus.yml`) can be used as an example to ingest available metrics with prometheus: +``` +scrape_configs: + - job_name: watermeter + scrape_interval: 300s + metrics_path: /metrics + static_configs: + - targets: ['192.168.1.4'] +``` + +Example response of REST API `/metrics`: +``` +# TYPE ai_on_the_edge_device_hardware_info gauge +# HELP ai_on_the_edge_device_hardware_info Hardware info +ai_on_the_edge_device_hardware_info{board_type="ESP32CAM",chip_model="ESP32",chip_cores="2",chip_revision="1.0",chip_frequency="160",camera_type="OV2640",camera_frequency="20",sdcard_capacity="29580",sdcard_partition_size="29560"} 1 +# TYPE ai_on_the_edge_device_network_info gauge +# HELP ai_on_the_edge_device_network_info Network info +ai_on_the_edge_device_network_info{hostname="watermeter",ipv4_address="192.168.2.68",mac_address="40:22:D8:03:5F:AC"} 1 +# TYPE ai_on_the_edge_device_firmware_info gauge +# HELP ai_on_the_edge_device_firmware_info Firmware info +ai_on_the_edge_device_firmware_info{firmware_version="Develop: openmetrics-exporter (Commit: 432bb72)"} 1 +# TYPE ai_on_the_edge_device_device_uptime_seconds gauge +# UNIT ai_on_the_edge_device_device_uptime_seconds seconds +# HELP ai_on_the_edge_device_device_uptime_seconds Device uptime in seconds +ai_on_the_edge_device_device_uptime_seconds 109 +# TYPE ai_on_the_edge_device_wlan_rssi_dBm gauge +# UNIT ai_on_the_edge_device_wlan_rssi_dBm dBm +# HELP ai_on_the_edge_device_wlan_rssi_dBm WLAN signal strength in dBm +ai_on_the_edge_device_wlan_rssi_dBm -60 +# TYPE ai_on_the_edge_device_chip_temp_celsius gauge +# UNIT ai_on_the_edge_device_chip_temp_celsius celsius +# HELP ai_on_the_edge_device_chip_temp_celsius CPU temperature in celsius +ai_on_the_edge_device_chip_temp_celsius 40 +# TYPE ai_on_the_edge_device_heap_info_bytes gauge +# UNIT ai_on_the_edge_device_heap_info_bytes bytes +# HELP ai_on_the_edge_device_heap_info_bytes Heap info +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_total_free"} 2381099 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_internal_free"} 69159 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_internal_largest_free"} 65536 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_internal_min_free"} 57971 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_spiram_free"} 2311700 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_spiram_largest_free"} 2293760 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_spiram_min_free"} 1261940 +# TYPE ai_on_the_edge_device_sd_partition_free_megabytes gauge +# UNIT ai_on_the_edge_device_sd_partition_free_megabytes megabytes +# HELP ai_on_the_edge_device_sd_partition_free_megabytes Free SD partition space in MB +ai_on_the_edge_device_sd_partition_free_megabytes 28410 +# TYPE ai_on_the_edge_device_process_error gauge +# HELP ai_on_the_edge_device_process_error Process error state +ai_on_the_edge_device_process_error 0 +# TYPE ai_on_the_edge_device_process_interval_minutes gauge +# UNIT ai_on_the_edge_device_process_interval_minutes minutes +# HELP ai_on_the_edge_device_process_interval_minutes Processing interval +ai_on_the_edge_device_process_interval_minutes 2.0 +# TYPE ai_on_the_edge_device_process_time_seconds gauge +# UNIT ai_on_the_edge_device_process_time_seconds seconds +# HELP ai_on_the_edge_device_process_time_seconds Processing time of one cycle +ai_on_the_edge_device_process_time_seconds 24 +# TYPE ai_on_the_edge_device_cycle_counter counter +# HELP ai_on_the_edge_device_cycle_counter Process cycles since device startup +ai_on_the_edge_device_cycle_counter_total 2 +# TYPE ai_on_the_edge_device_actual_value gauge +# HELP ai_on_the_edge_device_actual_value Actual value of meter +ai_on_the_edge_device_actual_value{sequence="main"} 530.01083 +ai_on_the_edge_device_actual_value{sequence="test"} 3 +# TYPE ai_on_the_edge_device_rate_per_minute gauge +# HELP ai_on_the_edge_device_rate_per_minute Rate per minute of meter +ai_on_the_edge_device_rate_per_minute{sequence="main"} 0.000000 +ai_on_the_edge_device_rate_per_minute{sequence="test"} 0.0 +``` diff --git a/docs/API/REST/_OVERVIEW.md b/docs/API/REST/_OVERVIEW.md index 9f595bbac..3afde77ee 100644 --- a/docs/API/REST/_OVERVIEW.md +++ b/docs/API/REST/_OVERVIEW.md @@ -12,6 +12,8 @@ Further details can be found in the respective REST API endpoint description. |:-------------------------------------|:---------------------------------------------------|:------------|:----------- | [/process_data](process_data.md) | Process Data | JSON + HTML | | [/info](info.md) | Device Info + Process Status | JSON + HTML | +| [/info](info.md) | Device Info + Process Status | JSON + HTML | +| [/metrics](metrics.md) | Prometheus / OpenMetrics Data | HTML | | [/cycle_start](cycle_start.md) | Trigger Cycle (Flow) Start | HTML | | [/reload_config](reload_config.md) | Reload Configuration | HTML | | [/set_fallbackvalue](set_fallbackvalue.md) | Set Fallback Value | HTML | diff --git a/docs/API/REST/metrics.md b/docs/API/REST/metrics.md new file mode 100644 index 000000000..631e03018 --- /dev/null +++ b/docs/API/REST/metrics.md @@ -0,0 +1,179 @@ +[Overview](_OVERVIEW.md) + +## REST API endpoint: metrics + +`http://IP-ADDRESS/metrics` + + +Provides a set of metrics that can be scraped by prometheus or any OpenMetrics compatilble software.
+ +The metrics are provided in text wire format based on [OpenMetrics specification](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md) +which is backward-compatible with [Prometheus text-based exposition format](https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md). + + +### Metric Name Design Approach + +The MetricPrefix is hard-coded: `ai_on_the_edge_device` + +Generic metric name: `metricPrefix` + `metricName` + `_unit` (and/or `_total` for counter metric) +Example: `ai_on_the_edge_device_uptime_seconds` + + +#### Hardware Info Metric `ai_on_the_edge_device_hardware_info` + +All information are static and provided by labels. The metric value is set to `1` + +| Metric label | Description | Output +|:--------------------|:----------------------------|:-------------- +| `board_type` | Board Type | `ESP32CAM` +| `chip_model` | Device SOC Model | `ESP32` +| `chip_cores` | Device SOC Cores | `2` +| `chip_revision` | Device SOC Silicon Revision | `1.00` +| `chip_frequency` | Device SOC CPU Frequency | `160` +| `type` | Camera Type | `OV2640` +| `frequency` | Camera Frequency [Mhz] | `20` +| `capacity` | SD card capacity [MB] | `29580` +| `partition_size` | SD card partition size [MB] | `29560` + + +#### Network Info Metric `ai_on_the_edge_device_network_info` + +All information are static and provided by labels. The metric value is set to `1` + +| Metric label | Description | Output +|:--------------------|:----------------------------|:-------------- +| `hostname` | Device Hostname | `watermetter` +| `ipv4_address` | Device IPv4 Address | `192.168.1.x` +| `mac_address` | Device MAC Address | `44:21:D8:04:DF:A8` + + +#### Firmware Info Metric `ai_on_the_edge_device_firmware_info` + +All information are static and provided by labels. The metric value is set to `1` + +| Metric Label | Description | Output +|:--------------------|:----------------------------|:-------------- +| `firmware_version` | Firmware Version (MCU) | `v17.0.0 (1234567)` + + +#### Heap Data Metric `ai_on_the_edge_device_heap_data_bytes` + +All data are provided by labels. The metric label is called `heap_data`. + +Example: `ai_on_the_edge_device_heap_data_bytes{heap_data="heap_total_free"}` + +| Metric Label Values | Description | Output +|:-----------------------------|:----------------------------|:-------------- +| `heap_total_free` | Memory: Total Free (Int. + Ext.) [kB] | `3058639` +| `heap_internal_free` | Memory: Internal Free [kB] | `75079` +| `heap_internal_largest_free` | Memory: Internal Largest Free Block [kB] | `65536` +| `heap_internal_min_free` | Memory: Internal Minimum Free [kB] | `57647` +| `heap_spiram_free` | Memory: External Free [kB] | `2409076` +| `heap_spiram_largest_free` | Memory: External Largest Free Block [kB] | `2359296` +| `heap_spiram_min_free` | Memory: External Minimum Free [kB] | `1359460` + + +#### Further Device Status Metrics + +| Metric Name | Description | Output +|:-------------------------------------------------|:----------------------------|:-------------- +| `ai_on_the_edge_device_device_uptime_seconds ` | Device Uptime [s] | `147` +| `ai_on_the_edge_device_wlan_rssi_dBm` | WLAN Signal Strength [dBm] | `-54` +| `ai_on_the_edge_device_chip_temp_celsius` | Device CPU Temperature (°C) | `45` +| `ai_on_the_edge_device_sd_partition_free_megabytes`| SD Card: Free Partition Space | `29016` + + +#### Process Status Metrics + +| Metric Name | Description | Output +|:-------------------------------------------------|:----------------------------|:-------------- +| `ai_on_the_edge_device_process_interval_minutes` | Automatic Process Interval [min] | `2.0` +| `ai_on_the_edge_device_process_time_seconds` | Process Time [sec] | `25` +| `ai_on_the_edge_device_process_error` | Process Error State
- Error definition: Process error with cycle abortion, e.g. alignment failed
- Deviation definition: Process deviation with cycle continuation, e.g. rate limit exceeded

Possible States:
- `0`: No error/deviation
- `-1`: One error occured
- `-2`: Multiple process errors in a row
- `1`: One process deviation occured
- `2`: Multiple process deviations in a row | `0` +| `ai_on_the_edge_device_cycle_counter_total` | Process Cycle Counter | `64` + + +#### Process Data Metrics + +Muliple sequence data is provided separately by label `sequence`. + +| Topic | Description | Output +|:--------------------------|:----------------------------|:-------------- +| `ai_on_the_edge_device_actual_value{sequence="[sequenceName]"}` | Actual value of [sequenceName] | `146.540` +| `ai_on_the_edge_device_rate_per_minute{sequence="[sequenceName]"}`| Rate per minute
(Delta of actual and last valid processed cycle + normalized to minute) | `0.000` + + +### Prometheus Scrape Config + +The following scrape config (add to `prometheus.yml`) can be used as an example to ingest available metrics with prometheus: + +``` +scrape_configs: + - job_name: watermeter + scrape_interval: 300s + metrics_path: /metrics + static_configs: + - targets: ['192.168.1.4'] +``` + +Example: + +``` +# TYPE ai_on_the_edge_device_hardware_info gauge +# HELP ai_on_the_edge_device_hardware_info Hardware info +ai_on_the_edge_device_hardware_info{board_type="ESP32CAM",chip_model="ESP32",chip_cores="2",chip_revision="1.0",chip_frequency="160",camera_type="OV2640",camera_frequency="20",sdcard_capacity="29580",sdcard_partition_size="29560"} 1 +# TYPE ai_on_the_edge_device_network_info gauge +# HELP ai_on_the_edge_device_network_info Network info +ai_on_the_edge_device_network_info{hostname="watermeter",ipv4_address="192.168.2.68",mac_address="40:22:D8:03:5F:AC"} 1 +# TYPE ai_on_the_edge_device_firmware_info gauge +# HELP ai_on_the_edge_device_firmware_info Firmware info +ai_on_the_edge_device_firmware_info{firmware_version="Develop: openmetrics-exporter (Commit: 432bb72)"} 1 +# TYPE ai_on_the_edge_device_device_uptime_seconds gauge +# UNIT ai_on_the_edge_device_device_uptime_seconds seconds +# HELP ai_on_the_edge_device_device_uptime_seconds Device uptime in seconds +ai_on_the_edge_device_device_uptime_seconds 109 +# TYPE ai_on_the_edge_device_wlan_rssi_dBm gauge +# UNIT ai_on_the_edge_device_wlan_rssi_dBm dBm +# HELP ai_on_the_edge_device_wlan_rssi_dBm WLAN signal strength in dBm +ai_on_the_edge_device_wlan_rssi_dBm -60 +# TYPE ai_on_the_edge_device_chip_temp_celsius gauge +# UNIT ai_on_the_edge_device_chip_temp_celsius celsius +# HELP ai_on_the_edge_device_chip_temp_celsius CPU temperature in celsius +ai_on_the_edge_device_chip_temp_celsius 40 +# TYPE ai_on_the_edge_device_heap_info_bytes gauge +# UNIT ai_on_the_edge_device_heap_info_bytes bytes +# HELP ai_on_the_edge_device_heap_info_bytes Heap info +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_total_free"} 2381099 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_internal_free"} 69159 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_internal_largest_free"} 65536 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_internal_min_free"} 57971 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_spiram_free"} 2311700 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_spiram_largest_free"} 2293760 +ai_on_the_edge_device_heap_info_bytes{heap_type="heap_spiram_min_free"} 1261940 +# TYPE ai_on_the_edge_device_sd_partition_free_megabytes gauge +# UNIT ai_on_the_edge_device_sd_partition_free_megabytes megabytes +# HELP ai_on_the_edge_device_sd_partition_free_megabytes Free SD partition space in MB +ai_on_the_edge_device_sd_partition_free_megabytes 28410 +# TYPE ai_on_the_edge_device_process_error gauge +# HELP ai_on_the_edge_device_process_error Process error state +ai_on_the_edge_device_process_error 0 +# TYPE ai_on_the_edge_device_process_interval_minutes gauge +# UNIT ai_on_the_edge_device_process_interval_minutes minutes +# HELP ai_on_the_edge_device_process_interval_minutes Processing interval +ai_on_the_edge_device_process_interval_minutes 1.0 +# TYPE ai_on_the_edge_device_process_time_seconds gauge +# UNIT ai_on_the_edge_device_process_time_seconds seconds +# HELP ai_on_the_edge_device_process_time_seconds Processing time of one cycle +ai_on_the_edge_device_process_time_seconds 21 +# TYPE ai_on_the_edge_device_cycle_counter counter +# HELP ai_on_the_edge_device_cycle_counter Process cycles since device startup +ai_on_the_edge_device_cycle_counter_total 2 +# TYPE ai_on_the_edge_device_actual_value gauge +# HELP ai_on_the_edge_device_actual_value Actual value of meter +ai_on_the_edge_device_actual_value{sequence="main"} 530.01083 +ai_on_the_edge_device_actual_value{sequence="name"} 3 +# TYPE ai_on_the_edge_device_rate_per_minute gauge +# HELP ai_on_the_edge_device_rate_per_minute Rate per minute of meter +ai_on_the_edge_device_rate_per_minute{sequence="main"} 0.000000 +ai_on_the_edge_device_rate_per_minute{sequence="name"} 0.0 +``` diff --git a/sd-card/html/doc_api_prometheus.html b/sd-card/html/doc_api_prometheus.html new file mode 100644 index 000000000..2f39ecae7 --- /dev/null +++ b/sd-card/html/doc_api_prometheus.html @@ -0,0 +1,19 @@ + + + + + Documentation Prometheus API + + + + + + + + diff --git a/sd-card/html/doc_api_prometheus.md b/sd-card/html/doc_api_prometheus.md new file mode 100644 index 000000000..dcd9d044e --- /dev/null +++ b/sd-card/html/doc_api_prometheus.md @@ -0,0 +1,7 @@ +## Overview: Prometheus API +### Prometheus / OpenMetrics telemetry data + +Offline data view is not built in. Use correct WebUI package or check online: +Prometheus / OpenMetrics API Docs
+
+NOTE: Please make sure using matching doumentation of version in use. \ No newline at end of file diff --git a/sd-card/html/index.html b/sd-card/html/index.html index cec79525c..50d1d7d79 100644 --- a/sd-card/html/index.html +++ b/sd-card/html/index.html @@ -121,6 +121,7 @@

A Neural Network Recognition Sy diff --git a/tools/docs-generator/generate-api-docs-localbuild.py b/tools/docs-generator/generate-api-docs-localbuild.py index 62d6b3b0d..5d9bd4707 100644 --- a/tools/docs-generator/generate-api-docs-localbuild.py +++ b/tools/docs-generator/generate-api-docs-localbuild.py @@ -25,6 +25,7 @@ htmlFolder = rootPath + "/sd-card/html" docAPIRest = "doc_api_rest.md" docAPIMqtt = "doc_api_mqtt.md" +docAPIPrometheus = "doc_api_prometheus.md" # Generate REST API doc markdown file for offline usage @@ -88,6 +89,14 @@ def prepareMqttApiMarkdown(markdownFile): return markdownFileContent +# Generate Prometheus API doc markdown file for offline usage +def preparePrometheusApiMarkdown(markdownFile): + with open(markdownFile, 'r') as markdownFileHandle: + markdownFileContent = markdownFileHandle.read() + + return markdownFileContent + + ########################################################################################## # Generate API docs for offline usage in WebUI ########################################################################################## @@ -97,6 +106,7 @@ def prepareMqttApiMarkdown(markdownFile): markdownRestApi = '' markdownMqttApi = '' +markdownPrometheusApi = '' # Create a combined markdown file for folder in folders: @@ -113,6 +123,8 @@ def prepareMqttApiMarkdown(markdownFile): elif (folder == "MQTT"): markdownMqttApi += prepareMqttApiMarkdown(file) # Merge files markdownMqttApi += "\n\n---\n" # Add a divider line + elif (folder == "Prometheus-OpenMetrics"): + markdownPrometheusApi += preparePrometheusApiMarkdown(file) # Read content # Copy in API doc linked images to HTMl folder if os.path.exists(docsAPIRootFolder + "/" + folder + "/img"): @@ -126,4 +138,8 @@ def prepareMqttApiMarkdown(markdownFile): # Write MQTT API markdown file with open(htmlFolder + "/" + docAPIMqtt, 'w') as docAPIMqttHandle: - docAPIMqttHandle.write(markdownMqttApi) \ No newline at end of file + docAPIMqttHandle.write(markdownMqttApi) + +# Write Prometheus API markdown file +with open(htmlFolder + "/" + docAPIPrometheus, 'w') as docAPIPrometheusHandle: + docAPIPrometheusHandle.write(markdownPrometheusApi) \ No newline at end of file diff --git a/tools/docs-generator/generate-api-docs.py b/tools/docs-generator/generate-api-docs.py index e49748897..bdbf4680f 100644 --- a/tools/docs-generator/generate-api-docs.py +++ b/tools/docs-generator/generate-api-docs.py @@ -10,6 +10,7 @@ htmlFolder = "./sd-card/html" docAPIRest = "doc_api_rest.md" docAPIMqtt = "doc_api_mqtt.md" +docAPIPrometheus = "doc_api_prometheus.md" # Generate REST API doc markdown file for offline usage @@ -73,6 +74,14 @@ def prepareMqttApiMarkdown(markdownFile): return markdownFileContent +# Generate Prometheus API doc markdown file for offline usage +def preparePrometheusApiMarkdown(markdownFile): + with open(markdownFile, 'r') as markdownFileHandle: + markdownFileContent = markdownFileHandle.read() + + return markdownFileContent + + ########################################################################################## # Generate API docs for offline usage in WebUI ########################################################################################## @@ -82,6 +91,7 @@ def prepareMqttApiMarkdown(markdownFile): markdownRestApi = '' markdownMqttApi = '' +markdownPrometheusApi = '' # Create a combined markdown file for folder in folders: @@ -98,6 +108,8 @@ def prepareMqttApiMarkdown(markdownFile): elif (folder == "MQTT"): markdownMqttApi += prepareMqttApiMarkdown(file) # Merge files markdownMqttApi += "\n\n---\n" # Add a divider line + elif (folder == "Prometheus-OpenMetrics"): + markdownPrometheusApi += preparePrometheusApiMarkdown(file) # Read content # Copy in API doc linked images to HTMl folder if os.path.exists(docsAPIRootFolder + "/" + folder + "/img"): @@ -111,4 +123,8 @@ def prepareMqttApiMarkdown(markdownFile): # Write MQTT API markdown file with open(htmlFolder + "/" + docAPIMqtt, 'w') as docAPIMqttHandle: - docAPIMqttHandle.write(markdownMqttApi) \ No newline at end of file + docAPIMqttHandle.write(markdownMqttApi) + +# Write Prometheus API markdown file +with open(htmlFolder + "/" + docAPIPrometheus, 'w') as docAPIPrometheusHandle: + docAPIPrometheusHandle.write(markdownPrometheusApi) \ No newline at end of file From c239c3bbbdd78bf64bbf13620587b2b06935efb0 Mon Sep 17 00:00:00 2001 From: Slider0007 Date: Sun, 16 Jun 2024 13:36:35 +0200 Subject: [PATCH 6/9] Update test cases --- .../test_flow_postrocess_helper.cpp | 18 +++++------ .../openmetrics/test_openmetrics.cpp | 30 ++++++++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/code/test/components/jomjol-flowcontroll/test_flow_postrocess_helper.cpp b/code/test/components/jomjol-flowcontroll/test_flow_postrocess_helper.cpp index 07f2eedaa..2b31bfe27 100644 --- a/code/test/components/jomjol-flowcontroll/test_flow_postrocess_helper.cpp +++ b/code/test/components/jomjol-flowcontroll/test_flow_postrocess_helper.cpp @@ -3,7 +3,7 @@ #include "esp_log.h" -static const char *TAG = "POSTPROC_TEST"; +static const char *TAG_PPTEST = "POSTPROC_TEST"; UnderTestPost* setUpClassFlowPostprocessing(t_CNNType digType, t_CNNType anaType) @@ -30,7 +30,7 @@ UnderTestPost* setUpClassFlowPostprocessing(t_CNNType digType, t_CNNType anaType std::string process_doFlow(UnderTestPost* _underTestPost) { - std::string time; + std::string time; // run test TEST_ASSERT_TRUE(_underTestPost->doFlow(time)); @@ -44,7 +44,7 @@ std::string process_doFlow(std::vector analog, std::vector digits, { // setup the classundertest UnderTestPost* _undertestPost = init_do_flow(analog, digits, digType, checkConsistency, extendedResolution, decimal_shift); - ESP_LOGD(TAG, "SetupClassFlowPostprocessing completed."); + ESP_LOGD(TAG_PPTEST, "SetupClassFlowPostprocessing completed."); std::string time; // run test @@ -94,7 +94,7 @@ UnderTestPost* init_do_flow(std::vector analog, std::vector digits } else { _undertestPost->flowAnalog = NULL; } - ESP_LOGD(TAG, "Setting up of ROIs completed."); + ESP_LOGD(TAG_PPTEST, "Setting up of ROIs completed."); _undertestPost->InitNUMBERS(); @@ -111,7 +111,7 @@ UnderTestPost* init_do_flow(std::vector analog, std::vector digits void SetFallbackValue(UnderTestPost* _underTestPost, double _fallbackValue) { if (_fallbackValue > 0) { - ESP_LOGD(TAG, "fallbackValue=%f", _fallbackValue); + ESP_LOGD(TAG_PPTEST, "fallbackValue=%f", _fallbackValue); std::vector* NUMBERS = _underTestPost->GetNumbers(); for (int _n = 0; _n < (*NUMBERS).size(); ++_n) { (*NUMBERS)[_n]->fallbackValue = _fallbackValue; @@ -124,7 +124,7 @@ void SetFallbackValue(UnderTestPost* _underTestPost, double _fallbackValue) void setAllowNegatives(UnderTestPost* _underTestPost, bool _allowNegatives) { - ESP_LOGD(TAG, "checkConsistency=true"); + ESP_LOGD(TAG_PPTEST, "checkConsistency=true"); std::vector* NUMBERS = _underTestPost->GetNumbers(); for (int _n = 0; _n < (*NUMBERS).size(); ++_n) { (*NUMBERS)[_n]->allowNegativeRates = _allowNegatives; @@ -136,7 +136,7 @@ void setAllowNegatives(UnderTestPost* _underTestPost, bool _allowNegatives) void setConsitencyCheck(UnderTestPost* _underTestPost, bool _checkConsistency) { if (_checkConsistency) { - ESP_LOGD(TAG, "checkConsistency=true"); + ESP_LOGD(TAG_PPTEST, "checkConsistency=true"); std::vector* NUMBERS = _underTestPost->GetNumbers(); for (int _n = 0; _n < (*NUMBERS).size(); ++_n) { (*NUMBERS)[_n]->checkDigitIncreaseConsistency = true; @@ -161,7 +161,7 @@ void setDecimalShift(UnderTestPost* _underTestPost, int _decimal_shift) if (_decimal_shift!=0) { std::vector* NUMBERS = _underTestPost->GetNumbers(); for (int _n = 0; _n < (*NUMBERS).size(); ++_n) { - ESP_LOGD(TAG, "Setting decimal shift on number: %d to %d", _n, _decimal_shift); + ESP_LOGD(TAG_PPTEST, "Setting decimal shift on number: %d to %d", _n, _decimal_shift); (*NUMBERS)[_n]->decimalShift = _decimal_shift; } } @@ -173,7 +173,7 @@ void setAnalogdigitTransistionStart(UnderTestPost* _underTestPost, float _analog if (_analogdigitTransistionStart!=0) { std::vector* NUMBERS = _underTestPost->GetNumbers(); for (int _n = 0; _n < (*NUMBERS).size(); ++_n) { - ESP_LOGD(TAG, "Setting decimal shift on number: %d to %f", _n, _analogdigitTransistionStart); + ESP_LOGD(TAG_PPTEST, "Setting decimal shift on number: %d to %f", _n, _analogdigitTransistionStart); (*NUMBERS)[_n]->analogDigitalTransitionStart = _analogdigitTransistionStart; } } diff --git a/code/test/components/openmetrics/test_openmetrics.cpp b/code/test/components/openmetrics/test_openmetrics.cpp index 259a71161..e43c7ab40 100644 --- a/code/test/components/openmetrics/test_openmetrics.cpp +++ b/code/test/components/openmetrics/test_openmetrics.cpp @@ -1,12 +1,12 @@ #include -#include "openmetrics.h" +#include "openmetrics.cpp" void test_createMetric() { // simple happy path - const char *expected = "# HELP metric_name short description\n# TYPE metric_name gauge\nmetric_name 123.456\n"; - std::string result = createMetric("metric_name", "short description", "gauge", "123.456"); + const char *expected = "# TYPE metric_name gauge\n# HELP metric_name short description\nmetric_name 123.456\n"; + std::string result = createMetric("metric_name", "gauge", "short description", "123.456"); TEST_ASSERT_EQUAL_STRING(expected, result.c_str()); } @@ -40,23 +40,33 @@ void test_createSequenceMetrics() NumberPost *number_1 = new NumberPost; number_1->name = "main"; number_1->sActualValue = "123.456"; + number_1->sRatePerMin = "0.001"; sequences.push_back(number_1); - const std::string metricNamePrefix = "ai_on_the_edge_device"; - const std::string metricName = metricNamePrefix + "_flow_value"; + const std::string metricNamePrefix = "ai_on_the_edge_device_"; - std::string expected1 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" + - metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->sActualValue + "\n"; + std::string expected1 = "# TYPE " + metricNamePrefix + "actual_value gauge\n# HELP " + metricNamePrefix + + "actual_value Actual value of meter\n" + metricNamePrefix + "actual_value{sequence=\"" + + number_1->name + "\"} " + number_1->sActualValue + "\n" + + "# TYPE " + metricNamePrefix + "rate_per_minute gauge\n# HELP " + metricNamePrefix + + "rate_per_minute Rate per minute of meter\n" + metricNamePrefix + "rate_per_minute{sequence=\"" + + number_1->name + "\"} " + number_1->sRatePerMin + "\n"; TEST_ASSERT_EQUAL_STRING(expected1.c_str(), createSequenceMetrics(metricNamePrefix, sequences).c_str()); NumberPost *number_2 = new NumberPost; number_2->name = "secondary"; number_2->sActualValue = "1.0"; + number_2->sRatePerMin = "0.0"; sequences.push_back(number_2); - std::string expected2 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" + - metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->sActualValue + "\n" + - metricName + "{sequence=\"" + number_2->name + "\"} " + number_2->sActualValue + "\n"; + std::string expected2 = "# TYPE " + metricNamePrefix + "actual_value gauge\n# HELP " + metricNamePrefix + + "actual_value Actual value of meter\n" + metricNamePrefix + "actual_value{sequence=\"" + + number_1->name + "\"} " + number_1->sActualValue + "\n" + metricNamePrefix + "actual_value{sequence=\"" + + number_2->name + "\"} " + number_2->sActualValue + "\n" + + "# TYPE " + metricNamePrefix + "rate_per_minute gauge\n# HELP " + metricNamePrefix + + "rate_per_minute Rate per minute of meter\n" + metricNamePrefix + "rate_per_minute{sequence=\"" + + number_1->name + "\"} " + number_1->sRatePerMin + "\n" + metricNamePrefix + "rate_per_minute{sequence=\"" + + number_2->name + "\"} " + number_2->sRatePerMin + "\n"; TEST_ASSERT_EQUAL_STRING(expected2.c_str(), createSequenceMetrics(metricNamePrefix, sequences).c_str()); } From b046399a0e3c3ae2bc7395abdd469ffb53e59378 Mon Sep 17 00:00:00 2001 From: Slider0007 Date: Sun, 16 Jun 2024 13:45:33 +0200 Subject: [PATCH 7/9] Update docs --- docs/API/Prometheus-OpenMetrics/_OVERVIEW.md | 93 ++++++++++++++++ docs/API/REST/metrics.md | 111 +------------------ 2 files changed, 97 insertions(+), 107 deletions(-) diff --git a/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md b/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md index 3e2d0dbac..8bddfae7d 100644 --- a/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md +++ b/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md @@ -7,6 +7,99 @@ The metrics can be scraped by Prometheus or any OpenMetrics specification compat The metrics are provided in text wire format based on [OpenMetrics specification](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md) which is backward-compatible with [Prometheus text-based exposition format](https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md). +### Metric Name Design Approach + +The MetricPrefix is hard-coded: `ai_on_the_edge_device` + +Generic metric name: `metricPrefix` + `metricName` + `_unit` (and/or `_total` for counter metric)
+Example: `ai_on_the_edge_device_uptime_seconds` + + +### Metrics +#### 1. Hardware Info Metric `ai_on_the_edge_device_hardware_info` + +All information are static and provided by labels. The metric value is set to `1` + +| Metric label | Description | Output +|:--------------------|:----------------------------|:-------------- +| `board_type` | Board Type | `ESP32CAM` +| `chip_model` | Device SOC Model | `ESP32` +| `chip_cores` | Device SOC Cores | `2` +| `chip_revision` | Device SOC Silicon Revision | `1.00` +| `chip_frequency` | Device SOC CPU Frequency | `160` +| `type` | Camera Type | `OV2640` +| `frequency` | Camera Frequency [Mhz] | `20` +| `capacity` | SD card capacity [MB] | `29580` +| `partition_size` | SD card partition size [MB] | `29560` + + +#### 2. Network Info Metric `ai_on_the_edge_device_network_info` + +All information are static and provided by labels. The metric value is set to `1` + +| Metric label | Description | Output +|:--------------------|:----------------------------|:-------------- +| `hostname` | Device Hostname | `watermetter` +| `ipv4_address` | Device IPv4 Address | `192.168.1.x` +| `mac_address` | Device MAC Address | `44:21:D8:04:DF:A8` + + +#### 3. Firmware Info Metric `ai_on_the_edge_device_firmware_info` + +All information are static and provided by labels. The metric value is set to `1` + +| Metric Label | Description | Output +|:--------------------|:----------------------------|:-------------- +| `firmware_version` | Firmware Version (MCU) | `v17.0.0 (1234567)` + + +#### 4. Heap Data Metric `ai_on_the_edge_device_heap_data_bytes` + +All data are provided by labels. The metric label is called `heap_data`. + +Example: `ai_on_the_edge_device_heap_data_bytes{heap_data="heap_total_free"}` + +| Metric Label Values | Description | Output +|:-----------------------------|:----------------------------|:-------------- +| `heap_total_free` | Memory: Total Free (Int. + Ext.) [kB] | `3058639` +| `heap_internal_free` | Memory: Internal Free [kB] | `75079` +| `heap_internal_largest_free` | Memory: Internal Largest Free Block [kB] | `65536` +| `heap_internal_min_free` | Memory: Internal Minimum Free [kB] | `57647` +| `heap_spiram_free` | Memory: External Free [kB] | `2409076` +| `heap_spiram_largest_free` | Memory: External Largest Free Block [kB] | `2359296` +| `heap_spiram_min_free` | Memory: External Minimum Free [kB] | `1359460` + + +#### 5. Further Device Status Metrics + +| Metric Name | Description | Output +|:-------------------------------------------------|:----------------------------|:-------------- +| `ai_on_the_edge_device_device_uptime_seconds ` | Device Uptime [s] | `147` +| `ai_on_the_edge_device_wlan_rssi_dBm` | WLAN Signal Strength [dBm] | `-54` +| `ai_on_the_edge_device_chip_temp_celsius` | Device CPU Temperature (°C) | `45` +| `ai_on_the_edge_device_sd_partition_free_megabytes`| SD Card: Free Partition Space | `29016` + + +#### 6. Process Status Metrics + +| Metric Name | Description | Output +|:-------------------------------------------------|:----------------------------|:-------------- +| `ai_on_the_edge_device_process_interval_minutes` | Automatic Process Interval [min] | `2.0` +| `ai_on_the_edge_device_process_time_seconds` | Process Time [sec] | `25` +| `ai_on_the_edge_device_process_error` | Process Error State
- Error definition: Process error with cycle abortion, e.g. alignment failed
- Deviation definition: Process deviation with cycle continuation, e.g. rate limit exceeded

Possible States:
- `0`: No error/deviation
- `-1`: One error occured
- `-2`: Multiple process errors in a row
- `1`: One process deviation occured
- `2`: Multiple process deviations in a row | `0` +| `ai_on_the_edge_device_cycle_counter_total` | Process Cycle Counter | `64` + + +#### 7. Process Data Metrics + +Muliple sequence data is provided separately by label `sequence`. + +| Topic | Description | Output +|:--------------------------|:----------------------------|:-------------- +| `ai_on_the_edge_device_actual_value{sequence="[sequenceName]"}` | Actual value of [sequenceName] | `146.540` +| `ai_on_the_edge_device_rate_per_minute{sequence="[sequenceName]"}`| Rate per minute
(Delta of actual and last valid processed cycle + normalized to minute) | `0.000` + + ### Prometheus Scrape Config The following scrape config (add to `prometheus.yml`) can be used as an example to ingest available metrics with prometheus: diff --git a/docs/API/REST/metrics.md b/docs/API/REST/metrics.md index 631e03018..d2ad08cb6 100644 --- a/docs/API/REST/metrics.md +++ b/docs/API/REST/metrics.md @@ -5,118 +5,15 @@ `http://IP-ADDRESS/metrics` -Provides a set of metrics that can be scraped by prometheus or any OpenMetrics compatilble software.
- +Provides a set of metrics that can be scraped by prometheus or any OpenMetrics compatilble software. The metrics are provided in text wire format based on [OpenMetrics specification](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md) which is backward-compatible with [Prometheus text-based exposition format](https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md). -### Metric Name Design Approach - -The MetricPrefix is hard-coded: `ai_on_the_edge_device` - -Generic metric name: `metricPrefix` + `metricName` + `_unit` (and/or `_total` for counter metric) -Example: `ai_on_the_edge_device_uptime_seconds` - - -#### Hardware Info Metric `ai_on_the_edge_device_hardware_info` - -All information are static and provided by labels. The metric value is set to `1` - -| Metric label | Description | Output -|:--------------------|:----------------------------|:-------------- -| `board_type` | Board Type | `ESP32CAM` -| `chip_model` | Device SOC Model | `ESP32` -| `chip_cores` | Device SOC Cores | `2` -| `chip_revision` | Device SOC Silicon Revision | `1.00` -| `chip_frequency` | Device SOC CPU Frequency | `160` -| `type` | Camera Type | `OV2640` -| `frequency` | Camera Frequency [Mhz] | `20` -| `capacity` | SD card capacity [MB] | `29580` -| `partition_size` | SD card partition size [MB] | `29560` - - -#### Network Info Metric `ai_on_the_edge_device_network_info` - -All information are static and provided by labels. The metric value is set to `1` - -| Metric label | Description | Output -|:--------------------|:----------------------------|:-------------- -| `hostname` | Device Hostname | `watermetter` -| `ipv4_address` | Device IPv4 Address | `192.168.1.x` -| `mac_address` | Device MAC Address | `44:21:D8:04:DF:A8` - - -#### Firmware Info Metric `ai_on_the_edge_device_firmware_info` - -All information are static and provided by labels. The metric value is set to `1` - -| Metric Label | Description | Output -|:--------------------|:----------------------------|:-------------- -| `firmware_version` | Firmware Version (MCU) | `v17.0.0 (1234567)` - - -#### Heap Data Metric `ai_on_the_edge_device_heap_data_bytes` - -All data are provided by labels. The metric label is called `heap_data`. - -Example: `ai_on_the_edge_device_heap_data_bytes{heap_data="heap_total_free"}` - -| Metric Label Values | Description | Output -|:-----------------------------|:----------------------------|:-------------- -| `heap_total_free` | Memory: Total Free (Int. + Ext.) [kB] | `3058639` -| `heap_internal_free` | Memory: Internal Free [kB] | `75079` -| `heap_internal_largest_free` | Memory: Internal Largest Free Block [kB] | `65536` -| `heap_internal_min_free` | Memory: Internal Minimum Free [kB] | `57647` -| `heap_spiram_free` | Memory: External Free [kB] | `2409076` -| `heap_spiram_largest_free` | Memory: External Largest Free Block [kB] | `2359296` -| `heap_spiram_min_free` | Memory: External Minimum Free [kB] | `1359460` - - -#### Further Device Status Metrics - -| Metric Name | Description | Output -|:-------------------------------------------------|:----------------------------|:-------------- -| `ai_on_the_edge_device_device_uptime_seconds ` | Device Uptime [s] | `147` -| `ai_on_the_edge_device_wlan_rssi_dBm` | WLAN Signal Strength [dBm] | `-54` -| `ai_on_the_edge_device_chip_temp_celsius` | Device CPU Temperature (°C) | `45` -| `ai_on_the_edge_device_sd_partition_free_megabytes`| SD Card: Free Partition Space | `29016` - - -#### Process Status Metrics - -| Metric Name | Description | Output -|:-------------------------------------------------|:----------------------------|:-------------- -| `ai_on_the_edge_device_process_interval_minutes` | Automatic Process Interval [min] | `2.0` -| `ai_on_the_edge_device_process_time_seconds` | Process Time [sec] | `25` -| `ai_on_the_edge_device_process_error` | Process Error State
- Error definition: Process error with cycle abortion, e.g. alignment failed
- Deviation definition: Process deviation with cycle continuation, e.g. rate limit exceeded

Possible States:
- `0`: No error/deviation
- `-1`: One error occured
- `-2`: Multiple process errors in a row
- `1`: One process deviation occured
- `2`: Multiple process deviations in a row | `0` -| `ai_on_the_edge_device_cycle_counter_total` | Process Cycle Counter | `64` - - -#### Process Data Metrics - -Muliple sequence data is provided separately by label `sequence`. - -| Topic | Description | Output -|:--------------------------|:----------------------------|:-------------- -| `ai_on_the_edge_device_actual_value{sequence="[sequenceName]"}` | Actual value of [sequenceName] | `146.540` -| `ai_on_the_edge_device_rate_per_minute{sequence="[sequenceName]"}`| Rate per minute
(Delta of actual and last valid processed cycle + normalized to minute) | `0.000` - - -### Prometheus Scrape Config - -The following scrape config (add to `prometheus.yml`) can be used as an example to ingest available metrics with prometheus: - -``` -scrape_configs: - - job_name: watermeter - scrape_interval: 300s - metrics_path: /metrics - static_configs: - - targets: ['192.168.1.4'] -``` +### Metrics +Metrics description > see Prometheus API description (docs/API/Prometheus-OpenMetrics) -Example: +### Example output ``` # TYPE ai_on_the_edge_device_hardware_info gauge From 31398bd647e614a987087a6ffd51b6e885ffcd38 Mon Sep 17 00:00:00 2001 From: Slider0007 Date: Mon, 17 Jun 2024 20:34:05 +0200 Subject: [PATCH 8/9] Update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 406c0cdd6..39c924f4c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This project allows you to digitize your **analog** water, gas, power and other - InfluxDB v1.x + v2.x - [MQTT v3.x](docs/API/MQTT/_OVERVIEW.md) - [REST API](docs/API/REST/_OVERVIEW.md) +- [Prometheus/OpenMetrics exporter](docs/API/Prometheus-OpenMetrics/_OVERVIEW.md) ## Workflow From a0daa4d79fc488ad70de1996df7a2cabc2539954 Mon Sep 17 00:00:00 2001 From: Slider0007 <115730895+Slider0007@users.noreply.github.com> Date: Wed, 26 Jun 2024 08:24:55 +0200 Subject: [PATCH 9/9] Update docs --- docs/API/Prometheus-OpenMetrics/_OVERVIEW.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md b/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md index 8bddfae7d..1826eba3f 100644 --- a/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md +++ b/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md @@ -1,7 +1,7 @@ ## Overview: Prometheus API ### Prometheus / OpenMetrics telemetry data -A set of metrics is exported via the `/metrics` REST API endpoint (see REST API description for more detailed metric infos). +A set of metrics is exported via the `/metrics` REST API endpoint (see also REST API description). The metrics can be scraped by Prometheus or any OpenMetrics specification compatilble software.
The metrics are provided in text wire format based on [OpenMetrics specification](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md) @@ -27,10 +27,10 @@ All information are static and provided by labels. The metric value is set to `1 | `chip_cores` | Device SOC Cores | `2` | `chip_revision` | Device SOC Silicon Revision | `1.00` | `chip_frequency` | Device SOC CPU Frequency | `160` -| `type` | Camera Type | `OV2640` -| `frequency` | Camera Frequency [Mhz] | `20` -| `capacity` | SD card capacity [MB] | `29580` -| `partition_size` | SD card partition size [MB] | `29560` +| `camera_type` | Camera Type | `OV2640` +| `camera_frequency` | Camera Frequency [Mhz] | `20` +| `sdcard_capacity` | SD card capacity [MB] | `29580` +| `sdcard_partition_size` | SD card partition size [MB] | `29560` #### 2. Network Info Metric `ai_on_the_edge_device_network_info`