mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-14 19:59:53 +03:00
Merge pull request #2805 from gr3enk/dev
Fix system/metrics endpoint duplicate labels & add enum support for metrics
This commit is contained in:
@@ -1699,14 +1699,33 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
|
|||||||
std::string result;
|
std::string result;
|
||||||
std::unordered_map<std::string, bool> seen_metrics;
|
std::unordered_map<std::string, bool> seen_metrics;
|
||||||
|
|
||||||
|
// Helper function to check if a device value type is supported for Prometheus metrics
|
||||||
|
auto is_supported_type = [](uint8_t type) -> bool {
|
||||||
|
return type == DeviceValueType::BOOL || type == DeviceValueType::UINT8 || type == DeviceValueType::INT8
|
||||||
|
|| type == DeviceValueType::UINT16 || type == DeviceValueType::INT16 || type == DeviceValueType::UINT24
|
||||||
|
|| type == DeviceValueType::UINT32 || type == DeviceValueType::TIME || type == DeviceValueType::ENUM;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamically reserve memory for the result
|
||||||
|
size_t entity_count = 0;
|
||||||
|
for (const auto & dv : devicevalues_) {
|
||||||
|
if (tag >= 0 && tag != dv.tag) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// only count supported types
|
||||||
|
if (dv.hasValue() && is_supported_type(dv.type)) {
|
||||||
|
entity_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.reserve(160 * entity_count);
|
||||||
|
|
||||||
for (auto & dv : devicevalues_) {
|
for (auto & dv : devicevalues_) {
|
||||||
if (tag >= 0 && tag != dv.tag) {
|
if (tag >= 0 && tag != dv.tag) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only process number and boolean types for now
|
// only process number, boolean and enum types
|
||||||
if (dv.type != DeviceValueType::BOOL && dv.type != DeviceValueType::UINT8 && dv.type != DeviceValueType::INT8 && dv.type != DeviceValueType::UINT16
|
if (!dv.hasValue() || !is_supported_type(dv.type)) {
|
||||||
&& dv.type != DeviceValueType::INT16 && dv.type != DeviceValueType::UINT24 && dv.type != DeviceValueType::UINT32 && dv.type != DeviceValueType::TIME) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1752,6 +1771,12 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
|
|||||||
metric_value = *(uint32_t *)(dv.value_p);
|
metric_value = *(uint32_t *)(dv.value_p);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case DeviceValueType::ENUM:
|
||||||
|
if (*(uint8_t *)(dv.value_p) < dv.options_size) {
|
||||||
|
has_value = true;
|
||||||
|
metric_value = *(uint8_t *)(dv.value_p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1794,8 +1819,19 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string uom_str;
|
std::string uom_str;
|
||||||
|
std::string enum_mapping;
|
||||||
if (dv.type == DeviceValueType::BOOL) {
|
if (dv.type == DeviceValueType::BOOL) {
|
||||||
uom_str = "boolean";
|
uom_str = "boolean";
|
||||||
|
} else if (dv.type == DeviceValueType::ENUM) {
|
||||||
|
// build enum mapping string: "(0: Wert1; 1: Wert2; ...)"
|
||||||
|
enum_mapping = "(";
|
||||||
|
for (uint8_t i = 0; i < dv.options_size; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
enum_mapping += "; ";
|
||||||
|
}
|
||||||
|
enum_mapping += std::to_string(i) + ": " + std::string(Helpers::translated_word(dv.options[i]));
|
||||||
|
}
|
||||||
|
enum_mapping += ")";
|
||||||
} else if (dv.uom != DeviceValueUOM::NONE) {
|
} else if (dv.uom != DeviceValueUOM::NONE) {
|
||||||
uom_str = uom_to_string(dv.uom);
|
uom_str = uom_to_string(dv.uom);
|
||||||
}
|
}
|
||||||
@@ -1804,6 +1840,9 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
|
|||||||
if (!uom_str.empty()) {
|
if (!uom_str.empty()) {
|
||||||
help_line += ", " + uom_str;
|
help_line += ", " + uom_str;
|
||||||
}
|
}
|
||||||
|
if (!enum_mapping.empty()) {
|
||||||
|
help_line += ", enum, " + enum_mapping;
|
||||||
|
}
|
||||||
|
|
||||||
bool readable = dv.type != DeviceValueType::CMD && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
|
bool readable = dv.type != DeviceValueType::CMD && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE);
|
||||||
bool writeable = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
|
bool writeable = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY);
|
||||||
@@ -1854,7 +1893,7 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double rounded = (final_value >= 0) ? (double)((int64_t)(final_value + 0.5)) : (double)((int64_t)(final_value - 0.5));
|
double rounded = (final_value >= 0) ? (double)((int64_t)(final_value + 0.5)) : (double)((int64_t)(final_value - 0.5));
|
||||||
if (dv.type == DeviceValueType::BOOL || (final_value == rounded)) {
|
if (dv.type == DeviceValueType::BOOL || dv.type == DeviceValueType::ENUM || (final_value == rounded)) {
|
||||||
snprintf(val_str, sizeof(val_str), "%.0f", final_value);
|
snprintf(val_str, sizeof(val_str), "%.0f", final_value);
|
||||||
} else {
|
} else {
|
||||||
snprintf(val_str, sizeof(val_str), "%.2f", final_value);
|
snprintf(val_str, sizeof(val_str), "%.2f", final_value);
|
||||||
@@ -1863,6 +1902,8 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) {
|
|||||||
result += "\n";
|
result += "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.shrink_to_fit();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1568,6 +1568,8 @@ std::string System::get_metrics_prometheus() {
|
|||||||
std::string result;
|
std::string result;
|
||||||
std::unordered_map<std::string, bool> seen_metrics;
|
std::unordered_map<std::string, bool> seen_metrics;
|
||||||
|
|
||||||
|
result.reserve(16000);
|
||||||
|
|
||||||
// get system data
|
// get system data
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
JsonObject root = doc.to<JsonObject>();
|
JsonObject root = doc.to<JsonObject>();
|
||||||
@@ -1633,7 +1635,7 @@ std::string System::get_metrics_prometheus() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// helper function to process a JSON object recursively
|
// helper function to process a JSON object recursively
|
||||||
std::function<void(const JsonObject &, const std::string &)> process_object = [&](const JsonObject & obj, const std::string & prefix) {
|
std::function<void(const JsonObject, const std::string &)> process_object = [&](const JsonObject obj, const std::string & prefix) {
|
||||||
std::vector<std::pair<std::string, std::string>> local_info_labels;
|
std::vector<std::pair<std::string, std::string>> local_info_labels;
|
||||||
bool has_nested_objects = false;
|
bool has_nested_objects = false;
|
||||||
|
|
||||||
@@ -1751,7 +1753,18 @@ std::string System::get_metrics_prometheus() {
|
|||||||
// collect string for info metric (skip dynamic strings like uptime and timestamp)
|
// collect string for info metric (skip dynamic strings like uptime and timestamp)
|
||||||
std::string val = p.value().as<const char *>();
|
std::string val = p.value().as<const char *>();
|
||||||
if (!val.empty() && key != "uptime" && key != "timestamp") {
|
if (!val.empty() && key != "uptime" && key != "timestamp") {
|
||||||
local_info_labels.push_back({to_lowercase(key), val});
|
std::string lower_key = to_lowercase(key);
|
||||||
|
// check if key already exists in local_info_labels
|
||||||
|
bool key_exists = false;
|
||||||
|
for (const auto & label : local_info_labels) {
|
||||||
|
if (label.first == lower_key) {
|
||||||
|
key_exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!key_exists) {
|
||||||
|
local_info_labels.push_back({lower_key, val});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1786,6 +1799,8 @@ std::string System::get_metrics_prometheus() {
|
|||||||
// process root object
|
// process root object
|
||||||
process_object(root, "");
|
process_object(root, "");
|
||||||
|
|
||||||
|
result.shrink_to_fit();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -299,6 +299,10 @@ void manual_test8() {
|
|||||||
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
||||||
TEST_ASSERT_TRUE(strstr(response, " gauge") != nullptr);
|
TEST_ASSERT_TRUE(strstr(response, " gauge") != nullptr);
|
||||||
|
|
||||||
|
if (strstr(response, ", enum, (") != nullptr) {
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, ", enum, (") != nullptr);
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, ")") != nullptr);
|
||||||
|
}
|
||||||
TEST_ASSERT_TRUE(strstr(response, "emsesp_tapwateractive") != nullptr || strstr(response, "emsesp_selflowtemp") != nullptr
|
TEST_ASSERT_TRUE(strstr(response, "emsesp_tapwateractive") != nullptr || strstr(response, "emsesp_selflowtemp") != nullptr
|
||||||
|| strstr(response, "emsesp_curflowtemp") != nullptr);
|
|| strstr(response, "emsesp_curflowtemp") != nullptr);
|
||||||
}
|
}
|
||||||
@@ -313,6 +317,10 @@ void manual_test9() {
|
|||||||
TEST_ASSERT_TRUE(strstr(response, "# TYPE") != nullptr);
|
TEST_ASSERT_TRUE(strstr(response, "# TYPE") != nullptr);
|
||||||
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
||||||
|
|
||||||
|
if (strstr(response, ", enum, (") != nullptr) {
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, ", enum, (") != nullptr);
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, ")") != nullptr);
|
||||||
|
}
|
||||||
if (strstr(response, "circuit=") != nullptr) {
|
if (strstr(response, "circuit=") != nullptr) {
|
||||||
TEST_ASSERT_TRUE(strstr(response, "{circuit=") != nullptr);
|
TEST_ASSERT_TRUE(strstr(response, "{circuit=") != nullptr);
|
||||||
}
|
}
|
||||||
@@ -329,6 +337,10 @@ void manual_test10() {
|
|||||||
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
TEST_ASSERT_TRUE(strstr(response, "emsesp_") != nullptr);
|
||||||
TEST_ASSERT_TRUE(strstr(response, " gauge") != nullptr);
|
TEST_ASSERT_TRUE(strstr(response, " gauge") != nullptr);
|
||||||
|
|
||||||
|
if (strstr(response, ", enum, (") != nullptr) {
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, ", enum, (") != nullptr);
|
||||||
|
TEST_ASSERT_TRUE(strstr(response, ")") != nullptr);
|
||||||
|
}
|
||||||
// Check for some expected system metrics
|
// Check for some expected system metrics
|
||||||
TEST_ASSERT_TRUE(strstr(response, "emsesp_system_") != nullptr || strstr(response, "emsesp_network_") != nullptr
|
TEST_ASSERT_TRUE(strstr(response, "emsesp_system_") != nullptr || strstr(response, "emsesp_network_") != nullptr
|
||||||
|| strstr(response, "emsesp_api_") != nullptr);
|
|| strstr(response, "emsesp_api_") != nullptr);
|
||||||
|
|||||||
Reference in New Issue
Block a user