Skip to content

Commit 92bf8da

Browse files
authored
[SDK] Fix include instrumentation scope attributes in equal method (open-telemetry#3214)
1 parent 0b94d71 commit 92bf8da

File tree

11 files changed

+528
-7
lines changed

11 files changed

+528
-7
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ Increment the:
1818
* [SDK] Do not frequently create and destroy http client threads
1919
[#3198](https://github.com/open-telemetry/opentelemetry-cpp/pull/3198)
2020

21+
* [SDK] Fix instrumentation scope attributes evaluated in equal method
22+
[#3214](https://github.com/open-telemetry/opentelemetry-cpp/pull/3214)
23+
2124
## [1.18 2024-11-25]
2225

2326
* [EXPORTER] Fix crash in ElasticsearchLogRecordExporter

sdk/include/opentelemetry/sdk/common/attribute_utils.h

+83
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,60 @@ struct AttributeConverter
105105
}
106106
};
107107

108+
/**
109+
* Evaluates if an owned value (from an OwnedAttributeValue) is equal to another value (from a
110+
* non-owning AttributeValue). This only supports the checking equality with
111+
* nostd::visit(AttributeEqualToVisitor, OwnedAttributeValue, AttributeValue).
112+
*/
113+
struct AttributeEqualToVisitor
114+
{
115+
// Different types are not equal including containers of different element types
116+
template <typename T, typename U>
117+
bool operator()(const T &, const U &) const noexcept
118+
{
119+
return false;
120+
}
121+
122+
// Compare the same arithmetic types
123+
template <typename T>
124+
bool operator()(const T &owned_value, const T &value) const noexcept
125+
{
126+
return owned_value == value;
127+
}
128+
129+
// Compare std::string and const char*
130+
bool operator()(const std::string &owned_value, const char *value) const noexcept
131+
{
132+
return owned_value == value;
133+
}
134+
135+
// Compare std::string and nostd::string_view
136+
bool operator()(const std::string &owned_value, nostd::string_view value) const noexcept
137+
{
138+
return owned_value == value;
139+
}
140+
141+
// Compare std::vector<std::string> and nostd::span<const nostd::string_view>
142+
bool operator()(const std::vector<std::string> &owned_value,
143+
const nostd::span<const nostd::string_view> &value) const noexcept
144+
{
145+
return owned_value.size() == value.size() &&
146+
std::equal(owned_value.begin(), owned_value.end(), value.begin(),
147+
[](const std::string &owned_element, nostd::string_view element) {
148+
return owned_element == element;
149+
});
150+
}
151+
152+
// Compare nostd::span<const T> and std::vector<T> for arithmetic types
153+
template <typename T>
154+
bool operator()(const std::vector<T> &owned_value,
155+
const nostd::span<const T> &value) const noexcept
156+
{
157+
return owned_value.size() == value.size() &&
158+
std::equal(owned_value.begin(), owned_value.end(), value.begin());
159+
}
160+
};
161+
108162
/**
109163
* Class for storing attributes.
110164
*/
@@ -162,8 +216,37 @@ class AttributeMap : public std::unordered_map<std::string, OwnedAttributeValue>
162216
(*this)[std::string(key)] = nostd::visit(converter_, value);
163217
}
164218

219+
// Compare the attributes of this map with another KeyValueIterable
220+
bool EqualTo(const opentelemetry::common::KeyValueIterable &attributes) const noexcept
221+
{
222+
if (attributes.size() != this->size())
223+
{
224+
return false;
225+
}
226+
227+
const bool is_equal = attributes.ForEachKeyValue(
228+
[this](nostd::string_view key,
229+
const opentelemetry::common::AttributeValue &value) noexcept {
230+
// Perform a linear search to find the key assuming the map is small
231+
// This avoids temporary string creation from this->find(std::string(key))
232+
for (const auto &kv : *this)
233+
{
234+
if (kv.first == key)
235+
{
236+
// Order of arguments is important here. OwnedAttributeValue is first then
237+
// AttributeValue AttributeEqualToVisitor does not support the reverse order
238+
return nostd::visit(equal_to_visitor_, kv.second, value);
239+
}
240+
}
241+
return false;
242+
});
243+
244+
return is_equal;
245+
}
246+
165247
private:
166248
AttributeConverter converter_;
249+
AttributeEqualToVisitor equal_to_visitor_;
167250
};
168251

169252
/**

sdk/include/opentelemetry/sdk/instrumentationscope/instrumentation_scope.h

+21-3
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ class InstrumentationScope
103103
*/
104104
bool operator==(const InstrumentationScope &other) const noexcept
105105
{
106-
return equal(other.name_, other.version_, other.schema_url_);
106+
return this->name_ == other.name_ && this->version_ == other.version_ &&
107+
this->schema_url_ == other.schema_url_ && this->attributes_ == other.attributes_;
107108
}
108109

109110
/**
@@ -112,14 +113,31 @@ class InstrumentationScope
112113
* @param name name of the instrumentation scope to compare.
113114
* @param version version of the instrumentation scope to compare.
114115
* @param schema_url schema url of the telemetry emitted by the scope.
116+
* @param attributes attributes of the instrumentation scope to compare.
115117
* @returns true if name and version in this instrumentation scope are equal with the given name
116118
* and version.
117119
*/
118120
bool equal(const nostd::string_view name,
119121
const nostd::string_view version,
120-
const nostd::string_view schema_url = "") const noexcept
122+
const nostd::string_view schema_url = "",
123+
const opentelemetry::common::KeyValueIterable *attributes = nullptr) const noexcept
121124
{
122-
return this->name_ == name && this->version_ == version && this->schema_url_ == schema_url;
125+
126+
if (this->name_ != name || this->version_ != version || this->schema_url_ != schema_url)
127+
{
128+
return false;
129+
}
130+
131+
if (attributes == nullptr)
132+
{
133+
if (attributes_.empty())
134+
{
135+
return true;
136+
}
137+
return false;
138+
}
139+
140+
return attributes_.EqualTo(*attributes);
123141
}
124142

125143
const std::string &GetName() const noexcept { return name_; }

sdk/src/logs/logger_provider.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ opentelemetry::nostd::shared_ptr<opentelemetry::logs::Logger> LoggerProvider::Ge
8080
{
8181
auto &logger_lib = logger->GetInstrumentationScope();
8282
if (logger->GetName() == logger_name &&
83-
logger_lib.equal(library_name, library_version, schema_url))
83+
logger_lib.equal(library_name, library_version, schema_url, &attributes))
8484
{
8585
return opentelemetry::nostd::shared_ptr<opentelemetry::logs::Logger>{logger};
8686
}

sdk/src/metrics/meter_provider.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ nostd::shared_ptr<metrics_api::Meter> MeterProvider::GetMeter(
7171
for (auto &meter : context_->GetMeters())
7272
{
7373
auto meter_lib = meter->GetInstrumentationScope();
74-
if (meter_lib->equal(name, version, schema_url))
74+
if (meter_lib->equal(name, version, schema_url, attributes))
7575
{
7676
return nostd::shared_ptr<metrics_api::Meter>{meter};
7777
}

sdk/src/trace/tracer_provider.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ nostd::shared_ptr<trace_api::Tracer> TracerProvider::GetTracer(
101101
for (auto &tracer : tracers_)
102102
{
103103
auto &tracer_scope = tracer->GetInstrumentationScope();
104-
if (tracer_scope.equal(name, version, schema_url))
104+
if (tracer_scope.equal(name, version, schema_url, attributes))
105105
{
106106
return nostd::shared_ptr<trace_api::Tracer>{tracer};
107107
}

sdk/test/common/attribute_utils_test.cc

+132
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
#include <gtest/gtest.h>
5+
#include <array>
56
#include <map>
67
#include <string>
78
#include <unordered_map>
89

910
#include "opentelemetry/common/key_value_iterable_view.h"
11+
#include "opentelemetry/nostd/span.h"
12+
#include "opentelemetry/nostd/string_view.h"
1013
#include "opentelemetry/nostd/variant.h"
1114
#include "opentelemetry/sdk/common/attribute_utils.h"
1215

@@ -55,3 +58,132 @@ TEST(OrderedAttributeMapTest, AttributesConstruction)
5558
EXPECT_EQ(opentelemetry::nostd::get<int>(attribute_map.GetAttributes().at(keys[i])), values[i]);
5659
}
5760
}
61+
62+
TEST(AttributeEqualToVisitorTest, AttributeValueEqualTo)
63+
{
64+
namespace sdk = opentelemetry::sdk::common;
65+
namespace api = opentelemetry::common;
66+
namespace nostd = opentelemetry::nostd;
67+
68+
using AV = api::AttributeValue;
69+
using OV = sdk::OwnedAttributeValue;
70+
71+
sdk::AttributeEqualToVisitor equal_to_visitor;
72+
73+
// arithmetic types
74+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{bool(true)}, AV{bool(true)}));
75+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{int32_t(22)}, AV{int32_t(22)}));
76+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{int64_t(22)}, AV{int64_t(22)}));
77+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{uint32_t(22)}, AV{uint32_t(22)}));
78+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{uint64_t(22)}, AV{uint64_t(22)}));
79+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{double(22.0)}, AV{double(22.0)}));
80+
81+
// string types
82+
EXPECT_TRUE(opentelemetry::nostd::visit(
83+
equal_to_visitor, OV{std::string("string to const char*")}, AV{"string to const char*"}));
84+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor,
85+
OV{std::string("string to string_view")},
86+
AV{nostd::string_view("string to string_view")}));
87+
88+
// container types
89+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<bool>{true, false}},
90+
AV{std::array<const bool, 2>{true, false}}));
91+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<uint8_t>{33, 44}},
92+
AV{std::array<const uint8_t, 2>{33, 44}}));
93+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<int32_t>{33, 44}},
94+
AV{std::array<const int32_t, 2>{33, 44}}));
95+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<int64_t>{33, 44}},
96+
AV{std::array<const int64_t, 2>{33, 44}}));
97+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<uint32_t>{33, 44}},
98+
AV{std::array<const uint32_t, 2>{33, 44}}));
99+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<uint64_t>{33, 44}},
100+
AV{std::array<const uint64_t, 2>{33, 44}}));
101+
EXPECT_TRUE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<double>{33.0, 44.0}},
102+
AV{std::array<const double, 2>{33.0, 44.0}}));
103+
EXPECT_TRUE(opentelemetry::nostd::visit(
104+
equal_to_visitor, OV{std::vector<std::string>{"a string", "another string"}},
105+
AV{std::array<const nostd::string_view, 2>{"a string", "another string"}}));
106+
}
107+
108+
TEST(AttributeEqualToVisitorTest, AttributeValueNotEqualTo)
109+
{
110+
namespace sdk = opentelemetry::sdk::common;
111+
namespace api = opentelemetry::common;
112+
namespace nostd = opentelemetry::nostd;
113+
114+
using AV = api::AttributeValue;
115+
using OV = sdk::OwnedAttributeValue;
116+
117+
sdk::AttributeEqualToVisitor equal_to_visitor;
118+
119+
// check different values of the same type
120+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{bool(true)}, AV{bool(false)}));
121+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{int32_t(22)}, AV{int32_t(33)}));
122+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{int64_t(22)}, AV{int64_t(33)}));
123+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{uint32_t(22)}, AV{uint32_t(33)}));
124+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{double(22.2)}, AV{double(33.3)}));
125+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::string("string one")},
126+
AV{"another string"}));
127+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::string("string one")},
128+
AV{nostd::string_view("another string")}));
129+
130+
// check different value types
131+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{bool(true)}, AV{uint32_t(1)}));
132+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{int32_t(22)}, AV{uint32_t(22)}));
133+
134+
// check containers of different element values
135+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<bool>{true, false}},
136+
AV{std::array<const bool, 2>{false, true}}));
137+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<int32_t>{22, 33}},
138+
AV{std::array<const int32_t, 2>{33, 44}}));
139+
EXPECT_FALSE(opentelemetry::nostd::visit(
140+
equal_to_visitor, OV{std::vector<std::string>{"a string", "another string"}},
141+
AV{std::array<const nostd::string_view, 2>{"a string", "a really different string"}}));
142+
143+
// check containers of different element types
144+
EXPECT_FALSE(opentelemetry::nostd::visit(equal_to_visitor, OV{std::vector<int32_t>{22, 33}},
145+
AV{std::array<const uint32_t, 2>{22, 33}}));
146+
}
147+
148+
TEST(AttributeMapTest, EqualTo)
149+
{
150+
using Attributes = std::initializer_list<
151+
std::pair<opentelemetry::nostd::string_view, opentelemetry::common::AttributeValue>>;
152+
153+
// check for case where both are empty
154+
Attributes attributes_empty = {};
155+
auto kv_iterable_empty =
156+
opentelemetry::common::MakeKeyValueIterableView<Attributes>(attributes_empty);
157+
opentelemetry::sdk::common::AttributeMap attribute_map_empty(kv_iterable_empty);
158+
EXPECT_TRUE(attribute_map_empty.EqualTo(kv_iterable_empty));
159+
160+
// check for equality with a range of attributes and types
161+
Attributes attributes = {{"key0", "some value"}, {"key1", 1}, {"key2", 2.0}, {"key3", true}};
162+
auto kv_iterable_match = opentelemetry::common::MakeKeyValueIterableView<Attributes>(attributes);
163+
opentelemetry::sdk::common::AttributeMap attribute_map(attributes);
164+
EXPECT_TRUE(attribute_map.EqualTo(kv_iterable_match));
165+
166+
// check for several cases where the attributes are different
167+
Attributes attributes_different_value = {
168+
{"key0", "some value"}, {"key1", 1}, {"key2", 2.0}, {"key3", false}};
169+
Attributes attributes_different_type = {
170+
{"key0", "some value"}, {"key1", 1.0}, {"key2", 2.0}, {"key3", true}};
171+
Attributes attributes_different_size = {{"key0", "some value"}};
172+
173+
// check for the case where the number of attributes is the same but all keys are different
174+
Attributes attributes_different_all = {{"a", "b"}, {"c", "d"}, {"e", 4.0}, {"f", uint8_t(5)}};
175+
176+
auto kv_iterable_different_value =
177+
opentelemetry::common::MakeKeyValueIterableView<Attributes>(attributes_different_value);
178+
auto kv_iterable_different_type =
179+
opentelemetry::common::MakeKeyValueIterableView<Attributes>(attributes_different_type);
180+
auto kv_iterable_different_size =
181+
opentelemetry::common::MakeKeyValueIterableView<Attributes>(attributes_different_size);
182+
auto kv_iterable_different_all =
183+
opentelemetry::common::MakeKeyValueIterableView<Attributes>(attributes_different_all);
184+
185+
EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_value));
186+
EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_type));
187+
EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_size));
188+
EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_all));
189+
}

0 commit comments

Comments
 (0)