From 7f14d89bc013f6db145eac343d90b4b457ae11b3 Mon Sep 17 00:00:00 2001 From: Slider0007 <115730895+Slider0007@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:07:51 +0200 Subject: [PATCH] feat(rest api): Prometheus (OpenMetrics) exporter (/metrics) (#163) Co-authored-by: Henry Thasler --- README.md | 1 + .../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 | 262 ++++++++++++++++++ code/components/openmetrics/openmetrics.h | 9 + code/main/main.cpp | 7 +- code/main/server_main.cpp | 2 +- .../test_flow_postrocess_helper.cpp | 18 +- .../openmetrics/test_openmetrics.cpp | 79 ++++++ code/test/test_suite_flowcontroll.cpp | 3 + docs/API/Prometheus-OpenMetrics/_OVERVIEW.md | 174 ++++++++++++ docs/API/REST/_OVERVIEW.md | 2 + docs/API/REST/metrics.md | 76 +++++ 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 +- 23 files changed, 719 insertions(+), 13 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 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/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 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..17ab47dd0 --- /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 jomjol_flowcontroll jomjol_wlan) + + diff --git a/code/components/openmetrics/openmetrics.cpp b/code/components/openmetrics/openmetrics.cpp new file mode 100644 index 000000000..6fb73db57 --- /dev/null +++ b/code/components/openmetrics/openmetrics.cpp @@ -0,0 +1,262 @@ +#include "openmetrics.h" +#include "../../include/defines.h" + +#include +#include + +#include +#include "esp_private/esp_clk.h" + +#include "system.h" +#include "ClassFlowDefineTypes.h" +#include "MainFlowControl.h" +#include "connect_wlan.h" + +extern std::string getFwVersion(void); + + +static const char *TAG = "OPENMETRICS"; + + +/** + * Create a hardware info metric + **/ +std::string createHardwareInfoMetric(const std::string &metricNamePrefix) +{ + 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()) + + "\",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 "# 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"; +} + + +/** + * Create a firmware info metric + **/ +std::string createFirmwareInfoMetric(const std::string &metricNamePrefix) +{ + return "# TYPE " + metricNamePrefix + "firmware_info gauge\n" + + "# HELP " + metricNamePrefix + "firmware_info Firmware info\n" + + metricNamePrefix + "firmware_info{firmware_version=\"" + getFwVersion() + "\"} 1\n"; +} + + +/** + * Create heap data metrics + **/ +std::string createHeapDataMetric(const std::string &metricNamePrefix) +{ + 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 &type, const std::string &help, const std::string &value) +{ + 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"; +} + + +/** + * Create a generic single metric with unit + **/ +std::string createMetricWithUnit(const std::string &metricName, const std::string &type, const std::string &unit, + const std::string &help, const std::string &value) +{ + 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" + + "# HELP " + metricName + "_" + unit + " " + help + "\n" + + metricName + "_" + unit + " " + value + "\n"; +} + + +/** + * Generate the MetricFamily from all available sequences + * @returns the string containing the text wire format of the MetricFamily + **/ +std::string createSequenceMetrics(const std::string &metricNamePrefix, const std::vector &sequences) +{ + std::string response; + + 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", ""); + + 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; + + // 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; + + // 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; + + // 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 response; +} + + +/** + * 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: ['192.168.1.4'] + * +*/ +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 + + // Metric name prefix + const std::string metricNamePrefix = "ai_on_the_edge_device_"; + + // Hardware (board, camera, sd-card) info + std::string response = createHardwareInfoMetric(metricNamePrefix); + + // Network info + response += createNetworkInfoMetric(metricNamePrefix); + + // Firmware info + response += createFirmwareInfoMetric(metricNamePrefix); + + // Device uptime + response += createMetricWithUnit(metricNamePrefix + "device_uptime", "gauge", "seconds", + "Device uptime in seconds", std::to_string((long)getUptime())); + + // WLAN signal strength + 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", "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", "gauge", "megabytes", + "Free SD partition space in megabytes", std::to_string(getSDCardFreePartitionSpace())); + + // Process error state + response += createMetric(metricNamePrefix + "process_error", "gauge", + "Process error state", std::to_string(flowctrl.getFlowStateErrorOrDeviation())); + + // Processing interval + response += createMetricWithUnit(metricNamePrefix + "process_interval", "gauge", "minutes", + "Processing interval", to_stringWithPrecision(flowctrl.getProcessInterval(), 1)); + + // Processing time + response += createMetricWithUnit(metricNamePrefix + "process_time", "gauge", "seconds", + "Processing time of one cycle", std::to_string(getFlowProcessingTime())); + + // Process cycles + response += createMetric(metricNamePrefix + "cycle_counter", "counter", + "Process cycles since device startup", std::to_string(getFlowCycleCounter())); + + // Actual measurement values + response += createSequenceMetrics(metricNamePrefix, flowctrl.getNumbers()); + + // The response always contains at least the metadata (HELP, TYPE) for the MetricFamily so no length check is needed + httpd_resp_sendstr(req, response.c_str()); + + 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..273ceab0d --- /dev/null +++ b/code/components/openmetrics/openmetrics.h @@ -0,0 +1,9 @@ +#ifndef OPENMETRICS_H +#define OPENMETRICS_H + +#include + + +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/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 new file mode 100644 index 000000000..e43c7ab40 --- /dev/null +++ b/code/test/components/openmetrics/test_openmetrics.cpp @@ -0,0 +1,79 @@ +#include +#include "openmetrics.cpp" + + +void test_createMetric() +{ + // simple happy path + 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()); +} + + +/** + * 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"; + number_1->sRatePerMin = "0.001"; + sequences.push_back(number_1); + + const std::string metricNamePrefix = "ai_on_the_edge_device_"; + + 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 = "# 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()); +} + + +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); diff --git a/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md b/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md new file mode 100644 index 000000000..1826eba3f --- /dev/null +++ b/docs/API/Prometheus-OpenMetrics/_OVERVIEW.md @@ -0,0 +1,174 @@ +## Overview: Prometheus API +### Prometheus / OpenMetrics telemetry data + +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) +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` +| `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` + +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: +``` +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..d2ad08cb6 --- /dev/null +++ b/docs/API/REST/metrics.md @@ -0,0 +1,76 @@ +[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). + + +### Metrics +Metrics description > see Prometheus API description (docs/API/Prometheus-OpenMetrics) + +### Example output + +``` +# 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