forked from jomjol/AI-on-the-edge-device
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rest api): Implement prometheus/openmetrics exporter
- Loading branch information
1 parent
49e123e
commit e97294c
Showing
13 changed files
with
247 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*) | ||
|
||
idf_component_register(SRCS ${app_sources} | ||
INCLUDE_DIRS "." | ||
REQUIRES jomjol_helper) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
#include "openmetrics.h" | ||
#include "../../include/defines.h" | ||
|
||
#include <esp_log.h> | ||
|
||
#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<NumberPost *> &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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#ifndef OPENMETRICS_H | ||
#define OPENMETRICS_H | ||
|
||
#include <string> | ||
#include <vector> | ||
|
||
#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<NumberPost *> &numbers); | ||
|
||
void register_openmetrics_uri(httpd_handle_t server); | ||
|
||
#endif // OPENMETRICS_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#include <unity.h> | ||
#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<NumberPost *> 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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters