diff --git a/sdk/include/opentelemetry/sdk/common/custom_hash_equality.h b/sdk/include/opentelemetry/sdk/common/custom_hash_equality.h new file mode 100644 index 0000000000..d1b5555b3a --- /dev/null +++ b/sdk/include/opentelemetry/sdk/common/custom_hash_equality.h @@ -0,0 +1,157 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace common +{ +/** + * + * FNV Hashing Utilities + * + * Implements FNV-1a hashing algorithm for 32-bit and 64-bit types. + * + * - FNV (Fowler–Noll–Vo) is a simple, fast, and widely used non-cryptographic hash. + * - FNV-1a is the recommended variant because it mixes input bits better. + * - We parameterize by type size (4 = 32-bit, 8 = 64-bit). + */ + +// Forward declaration for FNV prime constants +template +struct FnvPrime; + +// Specialization for 32-bit +template +struct FnvPrime +{ + static constexpr Ty value = static_cast(0x01000193U); +}; + +// Specialization for 64-bit +template +struct FnvPrime +{ + static constexpr Ty value = static_cast(0x100000001b3ULL); +}; + +// Forward declaration for FNV offset basis constants +template +struct FnvOffset; + +// Specialization for 32-bit +template +struct FnvOffset +{ + // 32-bit offset basis + static constexpr Ty value = static_cast(0x811C9DC5U); + + static constexpr Ty Fix(Ty hval) noexcept { return hval; } +}; + +// Specialization for 64-bit +template +struct FnvOffset +{ + // 64-bit offset basis + static constexpr Ty value = static_cast(0xCBF29CE484222325ULL); + + // Fix function: mix upper and lower bits for better distribution + static constexpr Ty Fix(Ty hval) noexcept { return hval ^ (hval >> 32); } +}; + +/** + * FNV-1a hash function + * + * @tparam Ty Hash integer type (std::size_t, uint32_t, uint64_t, etc.) + * @param buf Pointer to the input buffer + * @param len Length of the buffer + * @param hval Starting hash value (defaults to offset basis) + * @return Computed hash + */ +template +inline Ty Fnv1a(const void *buf, size_t len, Ty hval = FnvOffset::value) noexcept +{ + const unsigned char *bp = reinterpret_cast(buf); + const unsigned char *be = bp + len; + Ty prime = FnvPrime::value; + + while (bp < be) + { + hval ^= static_cast(*bp++); + hval *= prime; + } + return FnvOffset::Fix(hval); +} + +/** + * Hash and equality for nostd::string_view, enabling safe use in unordered_map + * without requiring null termination. + */ + +struct StringViewHash +{ +#if defined(OPENTELEMETRY_STL_VERSION) +# if OPENTELEMETRY_STL_VERSION >= 2020 + using is_transparent = void; +# endif +#endif + + std::size_t operator()(const std::string &s) const noexcept + { + return Fnv1a(s.data(), s.size()); + } + + std::size_t operator()(opentelemetry::nostd::string_view sv) const noexcept + { + return Fnv1a(sv.data(), sv.size()); + } +}; + +struct StringViewEqual +{ +#if defined(OPENTELEMETRY_STL_VERSION) +# if OPENTELEMETRY_STL_VERSION >= 2020 + using is_transparent = void; +# endif +#endif + + template + bool operator()(const Lhs &lhs, const Rhs &rhs) const noexcept + { + opentelemetry::nostd::string_view lsv(lhs); + opentelemetry::nostd::string_view rsv(rhs); + + return lsv.size() == rsv.size() && std::memcmp(lsv.data(), rsv.data(), lsv.size()) == 0; + } +}; + +/** + * Cross-platform heterogeneous lookup wrapper. + * Falls back to std::string construction on libc++ (macOS) and pre-c++20, + * but uses direct lookup on libstdc++ (Linux). + */ + +template +inline auto find_heterogeneous(MapType &&map, opentelemetry::nostd::string_view key) +{ +#if defined(_LIBCPP_VERSION) || \ + (!defined(OPENTELEMETRY_STL_VERSION) || OPENTELEMETRY_STL_VERSION < 2020) + return map.find(std::string(key)); +#else + // libstdc++ + C++20: heterogeneous lookup works + return map.find(key); +#endif +} +} // namespace common +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/view/attributes_processor.h b/sdk/include/opentelemetry/sdk/metrics/view/attributes_processor.h index 7b320d8d6a..b00687a5c1 100644 --- a/sdk/include/opentelemetry/sdk/metrics/view/attributes_processor.h +++ b/sdk/include/opentelemetry/sdk/metrics/view/attributes_processor.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -10,6 +11,7 @@ #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/common/custom_hash_equality.h" #include "opentelemetry/sdk/metrics/state/filtered_ordered_attribute_map.h" #include "opentelemetry/version.h" @@ -18,8 +20,15 @@ namespace sdk { namespace metrics { + using MetricAttributes = opentelemetry::sdk::metrics::FilteredOrderedAttributeMap; +typedef std::unordered_map + FilterAttributeMap; + /** * The AttributesProcessor is responsible for customizing which * attribute(s) are to be reported as metrics dimension(s). @@ -65,12 +74,11 @@ class DefaultAttributesProcessor : public AttributesProcessor class FilteringAttributesProcessor : public AttributesProcessor { public: - FilteringAttributesProcessor(std::unordered_map &&allowed_attribute_keys = {}) + FilteringAttributesProcessor(FilterAttributeMap &&allowed_attribute_keys = {}) : allowed_attribute_keys_(std::move(allowed_attribute_keys)) {} - FilteringAttributesProcessor( - const std::unordered_map &allowed_attribute_keys = {}) + FilteringAttributesProcessor(const FilterAttributeMap &allowed_attribute_keys = {}) : allowed_attribute_keys_(allowed_attribute_keys) {} @@ -80,7 +88,8 @@ class FilteringAttributesProcessor : public AttributesProcessor MetricAttributes result; attributes.ForEachKeyValue( [&](nostd::string_view key, opentelemetry::common::AttributeValue value) noexcept { - if (allowed_attribute_keys_.find(std::string(key)) != allowed_attribute_keys_.end()) + if (opentelemetry::sdk::common::find_heterogeneous(allowed_attribute_keys_, key) != + allowed_attribute_keys_.end()) { result.SetAttribute(key, value); return true; @@ -94,11 +103,12 @@ class FilteringAttributesProcessor : public AttributesProcessor bool isPresent(nostd::string_view key) const noexcept override { - return (allowed_attribute_keys_.find(std::string(key)) != allowed_attribute_keys_.end()); + return (opentelemetry::sdk::common::find_heterogeneous(allowed_attribute_keys_, key) != + allowed_attribute_keys_.end()); } private: - std::unordered_map allowed_attribute_keys_; + FilterAttributeMap allowed_attribute_keys_; }; /** @@ -109,12 +119,11 @@ class FilteringAttributesProcessor : public AttributesProcessor class FilteringExcludeAttributesProcessor : public AttributesProcessor { public: - FilteringExcludeAttributesProcessor(std::unordered_map &&exclude_list = {}) + FilteringExcludeAttributesProcessor(FilterAttributeMap &&exclude_list = {}) : exclude_list_(std::move(exclude_list)) {} - FilteringExcludeAttributesProcessor( - const std::unordered_map &exclude_list = {}) + FilteringExcludeAttributesProcessor(const FilterAttributeMap &exclude_list = {}) : exclude_list_(exclude_list) {} @@ -122,15 +131,15 @@ class FilteringExcludeAttributesProcessor : public AttributesProcessor const opentelemetry::common::KeyValueIterable &attributes) const noexcept override { MetricAttributes result; - attributes.ForEachKeyValue( - [&](nostd::string_view key, opentelemetry::common::AttributeValue value) noexcept { - if (exclude_list_.find(std::string(key)) == exclude_list_.end()) - { - result.SetAttribute(key, value); - return true; - } - return true; - }); + attributes.ForEachKeyValue([&](nostd::string_view key, + opentelemetry::common::AttributeValue value) noexcept { + if (opentelemetry::sdk::common::find_heterogeneous(exclude_list_, key) == exclude_list_.end()) + { + result.SetAttribute(key, value); + return true; + } + return true; + }); result.UpdateHash(); return result; @@ -138,11 +147,12 @@ class FilteringExcludeAttributesProcessor : public AttributesProcessor bool isPresent(nostd::string_view key) const noexcept override { - return (exclude_list_.find(std::string(key)) == exclude_list_.end()); + return (opentelemetry::sdk::common::find_heterogeneous(exclude_list_, key) == + exclude_list_.end()); } private: - std::unordered_map exclude_list_; + FilterAttributeMap exclude_list_; }; } // namespace metrics diff --git a/sdk/test/metrics/attributes_processor_benchmark.cc b/sdk/test/metrics/attributes_processor_benchmark.cc index 02923c5642..c64aa6a77c 100644 --- a/sdk/test/metrics/attributes_processor_benchmark.cc +++ b/sdk/test/metrics/attributes_processor_benchmark.cc @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "opentelemetry/common/key_value_iterable_view.h" diff --git a/sdk/test/metrics/attributes_processor_test.cc b/sdk/test/metrics/attributes_processor_test.cc index 8de97cc6df..3e13f0e101 100644 --- a/sdk/test/metrics/attributes_processor_test.cc +++ b/sdk/test/metrics/attributes_processor_test.cc @@ -8,7 +8,7 @@ #include #include "opentelemetry/common/key_value_iterable_view.h" -#include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/common/custom_hash_equality.h" #include "opentelemetry/sdk/metrics/view/attributes_processor.h" using namespace opentelemetry::sdk::metrics; @@ -17,8 +17,8 @@ using namespace opentelemetry::sdk::common; TEST(AttributesProcessor, FilteringAttributesProcessor) { - const int kNumFilterAttributes = 3; - std::unordered_map filter = { + const int kNumFilterAttributes = 3; + opentelemetry::sdk::metrics::FilterAttributeMap filter = { {"attr2", true}, {"attr4", true}, {"attr6", true}}; const int kNumAttributes = 6; std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"}; @@ -38,9 +38,9 @@ TEST(AttributesProcessor, FilteringAttributesProcessor) TEST(AttributesProcessor, FilteringAllAttributesProcessor) { - const int kNumFilterAttributes = 0; - std::unordered_map filter = {}; - const int kNumAttributes = 6; + const int kNumFilterAttributes = 0; + opentelemetry::sdk::metrics::FilterAttributeMap filter = {}; + const int kNumAttributes = 6; std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"}; int values[kNumAttributes] = {10, 20, 30, 40, 50, 60}; std::map attributes = {{keys[0], values[0]}, {keys[1], values[1]}, @@ -54,7 +54,7 @@ TEST(AttributesProcessor, FilteringAllAttributesProcessor) TEST(AttributesProcessor, FilteringExcludeAttributesProcessor) { - std::unordered_map filter = { + opentelemetry::sdk::metrics::FilterAttributeMap filter = { {"attr2", true}, {"attr4", true}, {"attr6", true}}; const int kNumAttributes = 7; std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", @@ -76,8 +76,8 @@ TEST(AttributesProcessor, FilteringExcludeAttributesProcessor) TEST(AttributesProcessor, FilteringExcludeAllAttributesProcessor) { - std::unordered_map filter = {}; - const int kNumAttributes = 6; + opentelemetry::sdk::metrics::FilterAttributeMap filter = {}; + const int kNumAttributes = 6; std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"}; int values[kNumAttributes] = {10, 20, 30, 40, 50, 60}; std::map attributes = {{keys[0], values[0]}, {keys[1], values[1]}, diff --git a/sdk/test/metrics/sum_aggregation_test.cc b/sdk/test/metrics/sum_aggregation_test.cc index 7722db1afa..02421a9e26 100644 --- a/sdk/test/metrics/sum_aggregation_test.cc +++ b/sdk/test/metrics/sum_aggregation_test.cc @@ -100,7 +100,7 @@ TEST(HistogramToSumFilterAttributes, Double) std::string instrument_name = "histogram1"; std::string instrument_desc = "histogram metrics"; - std::unordered_map allowedattr; + opentelemetry::sdk::metrics::FilterAttributeMap allowedattr; allowedattr["attr1"] = true; std::unique_ptr attrproc{ new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)}; @@ -154,7 +154,7 @@ TEST(HistogramToSumFilterAttributesWithCardinalityLimit, Double) std::string instrument_desc = "histogram metrics"; size_t cardinality_limit = 10000; - std::unordered_map allowedattr; + opentelemetry::sdk::metrics::FilterAttributeMap allowedattr; allowedattr["attr1"] = true; std::unique_ptr attrproc{ new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)}; @@ -281,7 +281,7 @@ TEST(CounterToSumFilterAttributes, Double) std::string instrument_name = "counter1"; std::string instrument_desc = "counter metrics"; - std::unordered_map allowedattr; + opentelemetry::sdk::metrics::FilterAttributeMap allowedattr; allowedattr["attr1"] = true; std::unique_ptr attrproc{ new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)}; @@ -335,7 +335,7 @@ TEST(CounterToSumFilterAttributesWithCardinalityLimit, Double) std::string instrument_desc = "counter metrics"; size_t cardinality_limit = 10000; - std::unordered_map allowedattr; + opentelemetry::sdk::metrics::FilterAttributeMap allowedattr; allowedattr["attr1"] = true; std::unique_ptr attrproc{ new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};