[Metrics SDK] Fix hash calculation for nostd::string (#2999)
* add test to validate cardinaity limit * format * warning * Format * maint mode CI * modify test to reproduce the issue * Format * remove vscode settings * remove redundant test * remove unused code * fix to calculate hash on string_view * remove iostream * template specialization for const char *, and varous string type tests * unused variable warning * more warnings * format * fix use-after-stack-scope * format * another try * format --------- Co-authored-by: Tom Tan <Tom.Tan@microsoft.com>
This commit is contained in:
parent
323a279ec5
commit
d8ae09e53d
|
@ -35,6 +35,15 @@ inline void GetHash(size_t &seed, const std::vector<T> &arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Specialization for const char*
|
||||||
|
// this creates an intermediate string.
|
||||||
|
template <>
|
||||||
|
inline void GetHash<const char *>(size_t &seed, const char *const &arg)
|
||||||
|
{
|
||||||
|
std::hash<std::string> hasher;
|
||||||
|
seed ^= hasher(std::string(arg)) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||||
|
}
|
||||||
|
|
||||||
struct GetHashForAttributeValueVisitor
|
struct GetHashForAttributeValueVisitor
|
||||||
{
|
{
|
||||||
GetHashForAttributeValueVisitor(size_t &seed) : seed_(seed) {}
|
GetHashForAttributeValueVisitor(size_t &seed) : seed_(seed) {}
|
||||||
|
@ -71,7 +80,7 @@ inline size_t GetHashForAttributeMap(
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
GetHash(seed, key.data());
|
GetHash(seed, key);
|
||||||
auto attr_val = nostd::visit(converter, value);
|
auto attr_val = nostd::visit(converter, value);
|
||||||
nostd::visit(GetHashForAttributeValueVisitor(seed), attr_val);
|
nostd::visit(GetHashForAttributeValueVisitor(seed), attr_val);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace nostd = opentelemetry::nostd;
|
||||||
|
|
||||||
TEST(AttributesHashMap, BasicTests)
|
TEST(AttributesHashMap, BasicTests)
|
||||||
{
|
{
|
||||||
|
|
||||||
// Empty map
|
// Empty map
|
||||||
AttributesHashMap hash_map;
|
AttributesHashMap hash_map;
|
||||||
EXPECT_EQ(hash_map.Size(), 0);
|
EXPECT_EQ(hash_map.Size(), 0);
|
||||||
|
@ -73,3 +72,78 @@ TEST(AttributesHashMap, BasicTests)
|
||||||
});
|
});
|
||||||
EXPECT_EQ(count, hash_map.Size());
|
EXPECT_EQ(count, hash_map.Size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string make_unique_string(const char *str)
|
||||||
|
{
|
||||||
|
return std::string(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttributesHashMap, HashWithKeyValueIterable)
|
||||||
|
{
|
||||||
|
std::string key1 = make_unique_string("k1");
|
||||||
|
std::string value1 = make_unique_string("v1");
|
||||||
|
std::string key2 = make_unique_string("k2");
|
||||||
|
std::string value2 = make_unique_string("v2");
|
||||||
|
std::string key3 = make_unique_string("k3");
|
||||||
|
std::string value3 = make_unique_string("v3");
|
||||||
|
|
||||||
|
// Create mock KeyValueIterable instances with the same content but different variables
|
||||||
|
std::map<std::string, std::string> attributes1({{key1, value1}, {key2, value2}});
|
||||||
|
std::map<std::string, std::string> attributes2({{key1, value1}, {key2, value2}});
|
||||||
|
std::map<std::string, std::string> attributes3({{key1, value1}, {key2, value2}, {key3, value3}});
|
||||||
|
|
||||||
|
// Create a callback that filters "k3" key
|
||||||
|
auto is_key_filter_k3_callback = [](nostd::string_view key) {
|
||||||
|
if (key == "k3")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
// Calculate hash
|
||||||
|
size_t hash1 = opentelemetry::sdk::common::GetHashForAttributeMap(
|
||||||
|
opentelemetry::common::KeyValueIterableView<std::map<std::string, std::string>>(attributes1),
|
||||||
|
is_key_filter_k3_callback);
|
||||||
|
size_t hash2 = opentelemetry::sdk::common::GetHashForAttributeMap(
|
||||||
|
opentelemetry::common::KeyValueIterableView<std::map<std::string, std::string>>(attributes2),
|
||||||
|
is_key_filter_k3_callback);
|
||||||
|
|
||||||
|
size_t hash3 = opentelemetry::sdk::common::GetHashForAttributeMap(
|
||||||
|
opentelemetry::common::KeyValueIterableView<std::map<std::string, std::string>>(attributes3),
|
||||||
|
is_key_filter_k3_callback);
|
||||||
|
|
||||||
|
// Expect the hashes to be the same because the content is the same
|
||||||
|
EXPECT_EQ(hash1, hash2);
|
||||||
|
// Expect the hashes to be the same because the content is the same
|
||||||
|
EXPECT_EQ(hash1, hash3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttributesHashMap, HashConsistencyAcrossStringTypes)
|
||||||
|
{
|
||||||
|
const char *c_str = "teststring";
|
||||||
|
std::string std_str = "teststring";
|
||||||
|
nostd::string_view nostd_str_view = "teststring";
|
||||||
|
#if __cplusplus >= 201703L
|
||||||
|
std::string_view std_str_view = "teststring";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t hash_c_str = 0;
|
||||||
|
size_t hash_std_str = 0;
|
||||||
|
size_t hash_nostd_str_view = 0;
|
||||||
|
#if __cplusplus >= 201703L
|
||||||
|
size_t hash_std_str_view = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
opentelemetry::sdk::common::GetHash(hash_c_str, c_str);
|
||||||
|
opentelemetry::sdk::common::GetHash(hash_std_str, std_str);
|
||||||
|
opentelemetry::sdk::common::GetHash(hash_nostd_str_view, nostd_str_view);
|
||||||
|
#if __cplusplus >= 201703L
|
||||||
|
opentelemetry::sdk::common::GetHash(hash_std_str_view, std_str_view);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
EXPECT_EQ(hash_c_str, hash_std_str);
|
||||||
|
EXPECT_EQ(hash_c_str, hash_nostd_str_view);
|
||||||
|
#if __cplusplus >= 201703L
|
||||||
|
EXPECT_EQ(hash_c_str, hash_std_str_view);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
@ -47,14 +47,47 @@ TEST(CardinalityLimit, AttributesHashMapBasicTests)
|
||||||
->Aggregate(record_value);
|
->Aggregate(record_value);
|
||||||
}
|
}
|
||||||
EXPECT_EQ(hash_map.Size(), 10); // only one more metric point should be added as overflow.
|
EXPECT_EQ(hash_map.Size(), 10); // only one more metric point should be added as overflow.
|
||||||
|
// record 5 more measurements to already existing (and not-overflow) metric points. They
|
||||||
|
// should get aggregated to these existing metric points.
|
||||||
|
for (auto i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
FilteredOrderedAttributeMap attributes = {{"key", std::to_string(i)}};
|
||||||
|
auto hash = opentelemetry::sdk::common::GetHashForAttributeMap(attributes);
|
||||||
|
static_cast<LongSumAggregation *>(
|
||||||
|
hash_map.GetOrSetDefault(attributes, aggregation_callback, hash))
|
||||||
|
->Aggregate(record_value);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(hash_map.Size(), 10); // no new metric point added
|
||||||
|
|
||||||
// get the overflow metric point
|
// get the overflow metric point
|
||||||
auto agg = hash_map.GetOrSetDefault(
|
auto agg1 = hash_map.GetOrSetDefault(
|
||||||
FilteredOrderedAttributeMap({{kAttributesLimitOverflowKey, kAttributesLimitOverflowValue}}),
|
FilteredOrderedAttributeMap({{kAttributesLimitOverflowKey, kAttributesLimitOverflowValue}}),
|
||||||
aggregation_callback, kOverflowAttributesHash);
|
aggregation_callback, kOverflowAttributesHash);
|
||||||
EXPECT_NE(agg, nullptr);
|
EXPECT_NE(agg1, nullptr);
|
||||||
auto sum_agg = static_cast<LongSumAggregation *>(agg);
|
auto sum_agg1 = static_cast<LongSumAggregation *>(agg1);
|
||||||
EXPECT_EQ(nostd::get<int64_t>(nostd::get<SumPointData>(sum_agg->ToPoint()).value_),
|
EXPECT_EQ(nostd::get<int64_t>(nostd::get<SumPointData>(sum_agg1->ToPoint()).value_),
|
||||||
record_value * 6); // 1 from previous 10, 5 from current 5.
|
record_value * 6); // 1 from previous 10, 5 from current 5.
|
||||||
|
// get remaining metric points
|
||||||
|
for (auto i = 0; i < 9; i++)
|
||||||
|
{
|
||||||
|
FilteredOrderedAttributeMap attributes = {{"key", std::to_string(i)}};
|
||||||
|
auto hash = opentelemetry::sdk::common::GetHashForAttributeMap(attributes);
|
||||||
|
auto agg2 = hash_map.GetOrSetDefault(
|
||||||
|
FilteredOrderedAttributeMap({{kAttributesLimitOverflowKey, kAttributesLimitOverflowValue}}),
|
||||||
|
aggregation_callback, hash);
|
||||||
|
EXPECT_NE(agg2, nullptr);
|
||||||
|
auto sum_agg2 = static_cast<LongSumAggregation *>(agg2);
|
||||||
|
if (i < 5)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(nostd::get<int64_t>(nostd::get<SumPointData>(sum_agg2->ToPoint()).value_),
|
||||||
|
record_value * 2); // 1 from first recording, 1 from third recording
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EXPECT_EQ(nostd::get<int64_t>(nostd::get<SumPointData>(sum_agg2->ToPoint()).value_),
|
||||||
|
record_value); // 1 from first recording
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WritableMetricStorageCardinalityLimitTestFixture
|
class WritableMetricStorageCardinalityLimitTestFixture
|
||||||
|
|
Loading…
Reference in New Issue