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