Skip to content

Commit 661a58a

Browse files
[SDK] custom hash and equality for attribute processor (open-telemetry#3643)
1 parent 74d348a commit 661a58a

File tree

5 files changed

+200
-34
lines changed

5 files changed

+200
-34
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#pragma once
5+
6+
#include <cstddef>
7+
#include <cstring>
8+
#include <string>
9+
10+
#include "opentelemetry/nostd/string_view.h"
11+
#include "opentelemetry/version.h"
12+
13+
OPENTELEMETRY_BEGIN_NAMESPACE
14+
namespace sdk
15+
{
16+
namespace common
17+
{
18+
/**
19+
*
20+
* FNV Hashing Utilities
21+
*
22+
* Implements FNV-1a hashing algorithm for 32-bit and 64-bit types.
23+
*
24+
* - FNV (Fowler–Noll–Vo) is a simple, fast, and widely used non-cryptographic hash.
25+
* - FNV-1a is the recommended variant because it mixes input bits better.
26+
* - We parameterize by type size (4 = 32-bit, 8 = 64-bit).
27+
*/
28+
29+
// Forward declaration for FNV prime constants
30+
template <typename Ty, size_t Size>
31+
struct FnvPrime;
32+
33+
// Specialization for 32-bit
34+
template <typename Ty>
35+
struct FnvPrime<Ty, 4>
36+
{
37+
static constexpr Ty value = static_cast<Ty>(0x01000193U);
38+
};
39+
40+
// Specialization for 64-bit
41+
template <typename Ty>
42+
struct FnvPrime<Ty, 8>
43+
{
44+
static constexpr Ty value = static_cast<Ty>(0x100000001b3ULL);
45+
};
46+
47+
// Forward declaration for FNV offset basis constants
48+
template <typename Ty, size_t Size>
49+
struct FnvOffset;
50+
51+
// Specialization for 32-bit
52+
template <typename Ty>
53+
struct FnvOffset<Ty, 4>
54+
{
55+
// 32-bit offset basis
56+
static constexpr Ty value = static_cast<Ty>(0x811C9DC5U);
57+
58+
static constexpr Ty Fix(Ty hval) noexcept { return hval; }
59+
};
60+
61+
// Specialization for 64-bit
62+
template <typename Ty>
63+
struct FnvOffset<Ty, 8>
64+
{
65+
// 64-bit offset basis
66+
static constexpr Ty value = static_cast<Ty>(0xCBF29CE484222325ULL);
67+
68+
// Fix function: mix upper and lower bits for better distribution
69+
static constexpr Ty Fix(Ty hval) noexcept { return hval ^ (hval >> 32); }
70+
};
71+
72+
/**
73+
* FNV-1a hash function
74+
*
75+
* @tparam Ty Hash integer type (std::size_t, uint32_t, uint64_t, etc.)
76+
* @param buf Pointer to the input buffer
77+
* @param len Length of the buffer
78+
* @param hval Starting hash value (defaults to offset basis)
79+
* @return Computed hash
80+
*/
81+
template <typename Ty>
82+
inline Ty Fnv1a(const void *buf, size_t len, Ty hval = FnvOffset<Ty, sizeof(Ty)>::value) noexcept
83+
{
84+
const unsigned char *bp = reinterpret_cast<const unsigned char *>(buf);
85+
const unsigned char *be = bp + len;
86+
Ty prime = FnvPrime<Ty, sizeof(Ty)>::value;
87+
88+
while (bp < be)
89+
{
90+
hval ^= static_cast<Ty>(*bp++);
91+
hval *= prime;
92+
}
93+
return FnvOffset<Ty, sizeof(Ty)>::Fix(hval);
94+
}
95+
96+
/**
97+
* Hash and equality for nostd::string_view, enabling safe use in unordered_map
98+
* without requiring null termination.
99+
*/
100+
101+
struct StringViewHash
102+
{
103+
#if defined(OPENTELEMETRY_STL_VERSION)
104+
# if OPENTELEMETRY_STL_VERSION >= 2020
105+
using is_transparent = void;
106+
# endif
107+
#endif
108+
109+
std::size_t operator()(const std::string &s) const noexcept
110+
{
111+
return Fnv1a<std::size_t>(s.data(), s.size());
112+
}
113+
114+
std::size_t operator()(opentelemetry::nostd::string_view sv) const noexcept
115+
{
116+
return Fnv1a<std::size_t>(sv.data(), sv.size());
117+
}
118+
};
119+
120+
struct StringViewEqual
121+
{
122+
#if defined(OPENTELEMETRY_STL_VERSION)
123+
# if OPENTELEMETRY_STL_VERSION >= 2020
124+
using is_transparent = void;
125+
# endif
126+
#endif
127+
128+
template <typename Lhs, typename Rhs>
129+
bool operator()(const Lhs &lhs, const Rhs &rhs) const noexcept
130+
{
131+
opentelemetry::nostd::string_view lsv(lhs);
132+
opentelemetry::nostd::string_view rsv(rhs);
133+
134+
return lsv.size() == rsv.size() && std::memcmp(lsv.data(), rsv.data(), lsv.size()) == 0;
135+
}
136+
};
137+
138+
/**
139+
* Cross-platform heterogeneous lookup wrapper.
140+
* Falls back to std::string construction on libc++ (macOS) and pre-c++20,
141+
* but uses direct lookup on libstdc++ (Linux).
142+
*/
143+
144+
template <typename MapType>
145+
inline auto find_heterogeneous(MapType &&map, opentelemetry::nostd::string_view key)
146+
{
147+
#if defined(_LIBCPP_VERSION) || \
148+
(!defined(OPENTELEMETRY_STL_VERSION) || OPENTELEMETRY_STL_VERSION < 2020)
149+
return map.find(std::string(key));
150+
#else
151+
// libstdc++ + C++20: heterogeneous lookup works
152+
return map.find(key);
153+
#endif
154+
}
155+
} // namespace common
156+
} // namespace sdk
157+
OPENTELEMETRY_END_NAMESPACE

sdk/include/opentelemetry/sdk/metrics/view/attributes_processor.h

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
#pragma once
55

6+
#include <cstring>
67
#include <string>
78
#include <unordered_map>
89
#include <utility>
910

1011
#include "opentelemetry/common/attribute_value.h"
1112
#include "opentelemetry/common/key_value_iterable.h"
1213
#include "opentelemetry/nostd/string_view.h"
14+
#include "opentelemetry/sdk/common/custom_hash_equality.h"
1315
#include "opentelemetry/sdk/metrics/state/filtered_ordered_attribute_map.h"
1416
#include "opentelemetry/version.h"
1517

@@ -18,8 +20,15 @@ namespace sdk
1820
{
1921
namespace metrics
2022
{
23+
2124
using MetricAttributes = opentelemetry::sdk::metrics::FilteredOrderedAttributeMap;
2225

26+
typedef std::unordered_map<std::string,
27+
bool,
28+
opentelemetry::sdk::common::StringViewHash,
29+
opentelemetry::sdk::common::StringViewEqual>
30+
FilterAttributeMap;
31+
2332
/**
2433
* The AttributesProcessor is responsible for customizing which
2534
* attribute(s) are to be reported as metrics dimension(s).
@@ -65,12 +74,11 @@ class DefaultAttributesProcessor : public AttributesProcessor
6574
class FilteringAttributesProcessor : public AttributesProcessor
6675
{
6776
public:
68-
FilteringAttributesProcessor(std::unordered_map<std::string, bool> &&allowed_attribute_keys = {})
77+
FilteringAttributesProcessor(FilterAttributeMap &&allowed_attribute_keys = {})
6978
: allowed_attribute_keys_(std::move(allowed_attribute_keys))
7079
{}
7180

72-
FilteringAttributesProcessor(
73-
const std::unordered_map<std::string, bool> &allowed_attribute_keys = {})
81+
FilteringAttributesProcessor(const FilterAttributeMap &allowed_attribute_keys = {})
7482
: allowed_attribute_keys_(allowed_attribute_keys)
7583
{}
7684

@@ -80,7 +88,8 @@ class FilteringAttributesProcessor : public AttributesProcessor
8088
MetricAttributes result;
8189
attributes.ForEachKeyValue(
8290
[&](nostd::string_view key, opentelemetry::common::AttributeValue value) noexcept {
83-
if (allowed_attribute_keys_.find(std::string(key)) != allowed_attribute_keys_.end())
91+
if (opentelemetry::sdk::common::find_heterogeneous(allowed_attribute_keys_, key) !=
92+
allowed_attribute_keys_.end())
8493
{
8594
result.SetAttribute(key, value);
8695
return true;
@@ -94,11 +103,12 @@ class FilteringAttributesProcessor : public AttributesProcessor
94103

95104
bool isPresent(nostd::string_view key) const noexcept override
96105
{
97-
return (allowed_attribute_keys_.find(std::string(key)) != allowed_attribute_keys_.end());
106+
return (opentelemetry::sdk::common::find_heterogeneous(allowed_attribute_keys_, key) !=
107+
allowed_attribute_keys_.end());
98108
}
99109

100110
private:
101-
std::unordered_map<std::string, bool> allowed_attribute_keys_;
111+
FilterAttributeMap allowed_attribute_keys_;
102112
};
103113

104114
/**
@@ -109,40 +119,40 @@ class FilteringAttributesProcessor : public AttributesProcessor
109119
class FilteringExcludeAttributesProcessor : public AttributesProcessor
110120
{
111121
public:
112-
FilteringExcludeAttributesProcessor(std::unordered_map<std::string, bool> &&exclude_list = {})
122+
FilteringExcludeAttributesProcessor(FilterAttributeMap &&exclude_list = {})
113123
: exclude_list_(std::move(exclude_list))
114124
{}
115125

116-
FilteringExcludeAttributesProcessor(
117-
const std::unordered_map<std::string, bool> &exclude_list = {})
126+
FilteringExcludeAttributesProcessor(const FilterAttributeMap &exclude_list = {})
118127
: exclude_list_(exclude_list)
119128
{}
120129

121130
MetricAttributes process(
122131
const opentelemetry::common::KeyValueIterable &attributes) const noexcept override
123132
{
124133
MetricAttributes result;
125-
attributes.ForEachKeyValue(
126-
[&](nostd::string_view key, opentelemetry::common::AttributeValue value) noexcept {
127-
if (exclude_list_.find(std::string(key)) == exclude_list_.end())
128-
{
129-
result.SetAttribute(key, value);
130-
return true;
131-
}
132-
return true;
133-
});
134+
attributes.ForEachKeyValue([&](nostd::string_view key,
135+
opentelemetry::common::AttributeValue value) noexcept {
136+
if (opentelemetry::sdk::common::find_heterogeneous(exclude_list_, key) == exclude_list_.end())
137+
{
138+
result.SetAttribute(key, value);
139+
return true;
140+
}
141+
return true;
142+
});
134143

135144
result.UpdateHash();
136145
return result;
137146
}
138147

139148
bool isPresent(nostd::string_view key) const noexcept override
140149
{
141-
return (exclude_list_.find(std::string(key)) == exclude_list_.end());
150+
return (opentelemetry::sdk::common::find_heterogeneous(exclude_list_, key) ==
151+
exclude_list_.end());
142152
}
143153

144154
private:
145-
std::unordered_map<std::string, bool> exclude_list_;
155+
FilterAttributeMap exclude_list_;
146156
};
147157

148158
} // namespace metrics

sdk/test/metrics/attributes_processor_benchmark.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include <benchmark/benchmark.h>
55
#include <map>
66
#include <string>
7-
#include <unordered_map>
87
#include <utility>
98

109
#include "opentelemetry/common/key_value_iterable_view.h"

sdk/test/metrics/attributes_processor_test.cc

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include <utility>
99

1010
#include "opentelemetry/common/key_value_iterable_view.h"
11-
#include "opentelemetry/sdk/common/attribute_utils.h"
11+
#include "opentelemetry/sdk/common/custom_hash_equality.h"
1212
#include "opentelemetry/sdk/metrics/view/attributes_processor.h"
1313

1414
using namespace opentelemetry::sdk::metrics;
@@ -17,8 +17,8 @@ using namespace opentelemetry::sdk::common;
1717

1818
TEST(AttributesProcessor, FilteringAttributesProcessor)
1919
{
20-
const int kNumFilterAttributes = 3;
21-
std::unordered_map<std::string, bool> filter = {
20+
const int kNumFilterAttributes = 3;
21+
opentelemetry::sdk::metrics::FilterAttributeMap filter = {
2222
{"attr2", true}, {"attr4", true}, {"attr6", true}};
2323
const int kNumAttributes = 6;
2424
std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"};
@@ -38,9 +38,9 @@ TEST(AttributesProcessor, FilteringAttributesProcessor)
3838

3939
TEST(AttributesProcessor, FilteringAllAttributesProcessor)
4040
{
41-
const int kNumFilterAttributes = 0;
42-
std::unordered_map<std::string, bool> filter = {};
43-
const int kNumAttributes = 6;
41+
const int kNumFilterAttributes = 0;
42+
opentelemetry::sdk::metrics::FilterAttributeMap filter = {};
43+
const int kNumAttributes = 6;
4444
std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"};
4545
int values[kNumAttributes] = {10, 20, 30, 40, 50, 60};
4646
std::map<std::string, int> attributes = {{keys[0], values[0]}, {keys[1], values[1]},
@@ -54,7 +54,7 @@ TEST(AttributesProcessor, FilteringAllAttributesProcessor)
5454

5555
TEST(AttributesProcessor, FilteringExcludeAttributesProcessor)
5656
{
57-
std::unordered_map<std::string, bool> filter = {
57+
opentelemetry::sdk::metrics::FilterAttributeMap filter = {
5858
{"attr2", true}, {"attr4", true}, {"attr6", true}};
5959
const int kNumAttributes = 7;
6060
std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4",
@@ -76,8 +76,8 @@ TEST(AttributesProcessor, FilteringExcludeAttributesProcessor)
7676

7777
TEST(AttributesProcessor, FilteringExcludeAllAttributesProcessor)
7878
{
79-
std::unordered_map<std::string, bool> filter = {};
80-
const int kNumAttributes = 6;
79+
opentelemetry::sdk::metrics::FilterAttributeMap filter = {};
80+
const int kNumAttributes = 6;
8181
std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"};
8282
int values[kNumAttributes] = {10, 20, 30, 40, 50, 60};
8383
std::map<std::string, int> attributes = {{keys[0], values[0]}, {keys[1], values[1]},

sdk/test/metrics/sum_aggregation_test.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ TEST(HistogramToSumFilterAttributes, Double)
100100
std::string instrument_name = "histogram1";
101101
std::string instrument_desc = "histogram metrics";
102102

103-
std::unordered_map<std::string, bool> allowedattr;
103+
opentelemetry::sdk::metrics::FilterAttributeMap allowedattr;
104104
allowedattr["attr1"] = true;
105105
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attrproc{
106106
new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};
@@ -154,7 +154,7 @@ TEST(HistogramToSumFilterAttributesWithCardinalityLimit, Double)
154154
std::string instrument_desc = "histogram metrics";
155155
size_t cardinality_limit = 10000;
156156

157-
std::unordered_map<std::string, bool> allowedattr;
157+
opentelemetry::sdk::metrics::FilterAttributeMap allowedattr;
158158
allowedattr["attr1"] = true;
159159
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attrproc{
160160
new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};
@@ -281,7 +281,7 @@ TEST(CounterToSumFilterAttributes, Double)
281281
std::string instrument_name = "counter1";
282282
std::string instrument_desc = "counter metrics";
283283

284-
std::unordered_map<std::string, bool> allowedattr;
284+
opentelemetry::sdk::metrics::FilterAttributeMap allowedattr;
285285
allowedattr["attr1"] = true;
286286
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attrproc{
287287
new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};
@@ -335,7 +335,7 @@ TEST(CounterToSumFilterAttributesWithCardinalityLimit, Double)
335335
std::string instrument_desc = "counter metrics";
336336
size_t cardinality_limit = 10000;
337337

338-
std::unordered_map<std::string, bool> allowedattr;
338+
opentelemetry::sdk::metrics::FilterAttributeMap allowedattr;
339339
allowedattr["attr1"] = true;
340340
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attrproc{
341341
new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};

0 commit comments

Comments
 (0)