diff --git a/src/core/emsdevice.cpp b/src/core/emsdevice.cpp index fdf87b43d..baa5d6a23 100644 --- a/src/core/emsdevice.cpp +++ b/src/core/emsdevice.cpp @@ -1699,14 +1699,33 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) { std::string result; std::unordered_map 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_) { if (tag >= 0 && tag != dv.tag) { continue; } - // only process number and boolean types for now - if (dv.type != DeviceValueType::BOOL && dv.type != DeviceValueType::UINT8 && dv.type != DeviceValueType::INT8 && dv.type != DeviceValueType::UINT16 - && dv.type != DeviceValueType::INT16 && dv.type != DeviceValueType::UINT24 && dv.type != DeviceValueType::UINT32 && dv.type != DeviceValueType::TIME) { + // only process number, boolean and enum types + if (!dv.hasValue() || !is_supported_type(dv.type)) { continue; } @@ -1752,6 +1771,12 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) { metric_value = *(uint32_t *)(dv.value_p); } 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: break; } @@ -1794,8 +1819,19 @@ std::string EMSdevice::get_metrics_prometheus(const int8_t tag) { } std::string uom_str; + std::string enum_mapping; if (dv.type == DeviceValueType::BOOL) { 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) { 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()) { 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 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)); - 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); } else { 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.shrink_to_fit(); + return result; } diff --git a/src/core/system.cpp b/src/core/system.cpp index 8501f34f1..5fc3862cd 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1568,6 +1568,8 @@ std::string System::get_metrics_prometheus() { std::string result; std::unordered_map seen_metrics; + result.reserve(16000); + // get system data JsonDocument doc; JsonObject root = doc.to(); @@ -1633,7 +1635,7 @@ std::string System::get_metrics_prometheus() { }; // helper function to process a JSON object recursively - std::function process_object = [&](const JsonObject & obj, const std::string & prefix) { + std::function process_object = [&](const JsonObject obj, const std::string & prefix) { std::vector> local_info_labels; 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) std::string val = p.value().as(); 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_object(root, ""); + result.shrink_to_fit(); + return result; } diff --git a/test/test_api/test_api.cpp b/test/test_api/test_api.cpp index 2482f1dfb..a003ecde1 100644 --- a/test/test_api/test_api.cpp +++ b/test/test_api/test_api.cpp @@ -299,6 +299,10 @@ void manual_test8() { TEST_ASSERT_TRUE(strstr(response, "emsesp_") != 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 || strstr(response, "emsesp_curflowtemp") != nullptr); } @@ -313,6 +317,10 @@ void manual_test9() { TEST_ASSERT_TRUE(strstr(response, "# TYPE") != 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) { 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, " 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 TEST_ASSERT_TRUE(strstr(response, "emsesp_system_") != nullptr || strstr(response, "emsesp_network_") != nullptr || strstr(response, "emsesp_api_") != nullptr);