Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 99 additions & 10 deletions sdk/include/opentelemetry/sdk/metrics/view/attributes_processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#pragma once

#include <cstring>
#include <string>
#include <unordered_map>
#include <utility>
Expand All @@ -18,8 +19,91 @@ namespace sdk
{
namespace metrics
{

using MetricAttributes = opentelemetry::sdk::metrics::FilteredOrderedAttributeMap;

/**
* Hash and equality for nostd::string_view, enabling safe use in unordered_map
* without requiring null termination.
*/
struct StringViewHash
{
#if __cplusplus >= 202002L
// enable heterogenous lookup in C++20+
using is_transparent = void;
#endif

std::size_t operator()(const std::string &s) const noexcept
{
return std::hash<std::string>{}(s);
}

std::size_t operator()(opentelemetry::nostd::string_view sv) const noexcept
{
#if __cplusplus >= 202002L
return std::hash<opentelemetry::nostd::string_view>{}(
opentelemetry::nostd::string_view{sv.data(), sv.size()});
Copy link

Copilot AI Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new string_view from an existing string_view is redundant and may not address the original issue. The problem is that string_view may not be null-terminated, but this code still uses the same data pointer. Consider using std::string construction instead: return std::hash<std::string>{}(std::string{sv.data(), sv.size()});

Suggested change
return std::hash<opentelemetry::nostd::string_view>{}(
opentelemetry::nostd::string_view{sv.data(), sv.size()});
// Materialize to std::string to ensure safe hashing (string_view may not be null-terminated)
return std::hash<std::string>{}(std::string{sv.data(), sv.size()});

Copilot uses AI. Check for mistakes.

#else
// pre-C++20 fallback: materialize to std::string
return std::hash<std::string>{}(std::string{sv.data(), sv.size()});
#endif
}
};

struct StringViewEqual
{
#if __cplusplus >= 202002L
using is_transparent = void;
#endif

bool operator()(const std::string &lhs, const std::string &rhs) const noexcept
{
return lhs == rhs;
}

bool operator()(const std::string &lhs, opentelemetry::nostd::string_view rhs) const noexcept
{
return lhs.size() == rhs.size() && std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}

bool operator()(opentelemetry::nostd::string_view lhs, const std::string &rhs) const noexcept
{
return lhs.size() == rhs.size() && std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}

bool operator()(opentelemetry::nostd::string_view lhs,
opentelemetry::nostd::string_view rhs) const noexcept
{
return lhs.size() == rhs.size() && std::memcmp(lhs.data(), rhs.data(), lhs.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).
*/
inline auto find_hetero(
const std::unordered_map<std::string, bool, StringViewHash, StringViewEqual> &map,
opentelemetry::nostd::string_view key)
{
#if defined(_LIBCPP_VERSION) || __cplusplus < 202002L
return map.find(std::string(key));
#else
return map.find(key);
#endif
}

inline auto find_hetero(std::unordered_map<std::string, bool, StringViewHash, StringViewEqual> &map,
opentelemetry::nostd::string_view key)
{
#if defined(_LIBCPP_VERSION) || __cplusplus < 202002L
return map.find(std::string(key));
#else
return map.find(key);
#endif
}

/**
* The AttributesProcessor is responsible for customizing which
* attribute(s) are to be reported as metrics dimension(s).
Expand Down Expand Up @@ -65,12 +149,15 @@ class DefaultAttributesProcessor : public AttributesProcessor
class FilteringAttributesProcessor : public AttributesProcessor
{
public:
FilteringAttributesProcessor(std::unordered_map<std::string, bool> &&allowed_attribute_keys = {})
FilteringAttributesProcessor(
std::unordered_map<std::string, bool, StringViewHash, StringViewEqual>
&&allowed_attribute_keys = {})
: allowed_attribute_keys_(std::move(allowed_attribute_keys))
{}

FilteringAttributesProcessor(
const std::unordered_map<std::string, bool> &allowed_attribute_keys = {})
const std::unordered_map<std::string, bool, StringViewHash, StringViewEqual>
&allowed_attribute_keys = {})
: allowed_attribute_keys_(allowed_attribute_keys)
{}

Expand All @@ -80,7 +167,7 @@ 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 (find_hetero(allowed_attribute_keys_, key) != allowed_attribute_keys_.end())
{
result.SetAttribute(key, value);
return true;
Expand All @@ -94,11 +181,11 @@ 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 (find_hetero(allowed_attribute_keys_, key) != allowed_attribute_keys_.end());
}

private:
std::unordered_map<std::string, bool> allowed_attribute_keys_;
std::unordered_map<std::string, bool, StringViewHash, StringViewEqual> allowed_attribute_keys_;
};

/**
Expand All @@ -109,12 +196,14 @@ class FilteringAttributesProcessor : public AttributesProcessor
class FilteringExcludeAttributesProcessor : public AttributesProcessor
{
public:
FilteringExcludeAttributesProcessor(std::unordered_map<std::string, bool> &&exclude_list = {})
FilteringExcludeAttributesProcessor(
std::unordered_map<std::string, bool, StringViewHash, StringViewEqual> &&exclude_list = {})
: exclude_list_(std::move(exclude_list))
{}

FilteringExcludeAttributesProcessor(
const std::unordered_map<std::string, bool> &exclude_list = {})
const std::unordered_map<std::string, bool, StringViewHash, StringViewEqual> &exclude_list =
{})
: exclude_list_(exclude_list)
{}

Expand All @@ -124,7 +213,7 @@ class FilteringExcludeAttributesProcessor : public AttributesProcessor
MetricAttributes result;
attributes.ForEachKeyValue(
[&](nostd::string_view key, opentelemetry::common::AttributeValue value) noexcept {
if (exclude_list_.find(std::string(key)) == exclude_list_.end())
if (find_hetero(exclude_list_, key) == exclude_list_.end())
{
result.SetAttribute(key, value);
return true;
Expand All @@ -138,11 +227,11 @@ class FilteringExcludeAttributesProcessor : public AttributesProcessor

bool isPresent(nostd::string_view key) const noexcept override
{
return (exclude_list_.find(std::string(key)) == exclude_list_.end());
return (find_hetero(exclude_list_, key) == exclude_list_.end());
}

private:
std::unordered_map<std::string, bool> exclude_list_;
std::unordered_map<std::string, bool, StringViewHash, StringViewEqual> exclude_list_;
};

} // namespace metrics
Expand Down
26 changes: 16 additions & 10 deletions sdk/test/metrics/attributes_processor_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ using namespace opentelemetry::sdk::common;

TEST(AttributesProcessor, FilteringAttributesProcessor)
{
const int kNumFilterAttributes = 3;
std::unordered_map<std::string, bool> filter = {
{"attr2", true}, {"attr4", true}, {"attr6", true}};
const int kNumFilterAttributes = 3;
std::unordered_map<std::string, bool, opentelemetry::sdk::metrics::StringViewHash,
opentelemetry::sdk::metrics::StringViewEqual>
filter = {{"attr2", true}, {"attr4", true}, {"attr6", true}};
const int kNumAttributes = 6;
std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4", "attr5", "attr6"};
int values[kNumAttributes] = {10, 20, 30, 40, 50, 60};
Expand All @@ -38,9 +39,11 @@ TEST(AttributesProcessor, FilteringAttributesProcessor)

TEST(AttributesProcessor, FilteringAllAttributesProcessor)
{
const int kNumFilterAttributes = 0;
std::unordered_map<std::string, bool> filter = {};
const int kNumAttributes = 6;
const int kNumFilterAttributes = 0;
std::unordered_map<std::string, bool, opentelemetry::sdk::metrics::StringViewHash,
opentelemetry::sdk::metrics::StringViewEqual>
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<std::string, int> attributes = {{keys[0], values[0]}, {keys[1], values[1]},
Expand All @@ -54,8 +57,9 @@ TEST(AttributesProcessor, FilteringAllAttributesProcessor)

TEST(AttributesProcessor, FilteringExcludeAttributesProcessor)
{
std::unordered_map<std::string, bool> filter = {
{"attr2", true}, {"attr4", true}, {"attr6", true}};
std::unordered_map<std::string, bool, opentelemetry::sdk::metrics::StringViewHash,
opentelemetry::sdk::metrics::StringViewEqual>
filter = {{"attr2", true}, {"attr4", true}, {"attr6", true}};
const int kNumAttributes = 7;
std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3", "attr4",
"attr5", "attr6", "attr7"};
Expand All @@ -76,8 +80,10 @@ TEST(AttributesProcessor, FilteringExcludeAttributesProcessor)

TEST(AttributesProcessor, FilteringExcludeAllAttributesProcessor)
{
std::unordered_map<std::string, bool> filter = {};
const int kNumAttributes = 6;
std::unordered_map<std::string, bool, opentelemetry::sdk::metrics::StringViewHash,
opentelemetry::sdk::metrics::StringViewEqual>
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<std::string, int> attributes = {{keys[0], values[0]}, {keys[1], values[1]},
Expand Down
16 changes: 12 additions & 4 deletions sdk/test/metrics/sum_aggregation_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ TEST(HistogramToSumFilterAttributes, Double)
std::string instrument_name = "histogram1";
std::string instrument_desc = "histogram metrics";

std::unordered_map<std::string, bool> allowedattr;
std::unordered_map<std::string, bool, opentelemetry::sdk::metrics::StringViewHash,
opentelemetry::sdk::metrics::StringViewEqual>
allowedattr;
allowedattr["attr1"] = true;
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attrproc{
new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};
Expand Down Expand Up @@ -154,7 +156,9 @@ TEST(HistogramToSumFilterAttributesWithCardinalityLimit, Double)
std::string instrument_desc = "histogram metrics";
size_t cardinality_limit = 10000;

std::unordered_map<std::string, bool> allowedattr;
std::unordered_map<std::string, bool, opentelemetry::sdk::metrics::StringViewHash,
opentelemetry::sdk::metrics::StringViewEqual>
allowedattr;
allowedattr["attr1"] = true;
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attrproc{
new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};
Expand Down Expand Up @@ -281,7 +285,9 @@ TEST(CounterToSumFilterAttributes, Double)
std::string instrument_name = "counter1";
std::string instrument_desc = "counter metrics";

std::unordered_map<std::string, bool> allowedattr;
std::unordered_map<std::string, bool, opentelemetry::sdk::metrics::StringViewHash,
opentelemetry::sdk::metrics::StringViewEqual>
allowedattr;
allowedattr["attr1"] = true;
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attrproc{
new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};
Expand Down Expand Up @@ -335,7 +341,9 @@ TEST(CounterToSumFilterAttributesWithCardinalityLimit, Double)
std::string instrument_desc = "counter metrics";
size_t cardinality_limit = 10000;

std::unordered_map<std::string, bool> allowedattr;
std::unordered_map<std::string, bool, opentelemetry::sdk::metrics::StringViewHash,
opentelemetry::sdk::metrics::StringViewEqual>
allowedattr;
allowedattr["attr1"] = true;
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attrproc{
new opentelemetry::sdk::metrics::FilteringAttributesProcessor(allowedattr)};
Expand Down
Loading