Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
30 changes: 15 additions & 15 deletions sdk/include/opentelemetry/sdk/metrics/observer_result.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@

#pragma once

#include <unordered_map>
#include <memory>

#include "opentelemetry/common/attribute_value.h"
#include "opentelemetry/common/key_value_iterable.h"
#include "opentelemetry/metrics/observer_result.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/sdk/metrics/state/attributes_hashmap.h"
#include "opentelemetry/sdk/metrics/view/attributes_processor.h"
#include "opentelemetry/sdk/metrics/state/measurement_attributes_map.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
Expand All @@ -22,32 +21,33 @@ template <class T>
class ObserverResultT final : public opentelemetry::metrics::ObserverResultT<T>
{
public:
explicit ObserverResultT(const AttributesProcessor *attributes_processor = nullptr)
: attributes_processor_(attributes_processor)
{}
explicit ObserverResultT() = default;

~ObserverResultT() override = default;

void Observe(T value) noexcept override
{
data_[MetricAttributes{{}, attributes_processor_}] = value;
std::unordered_map<nostd::string_view, opentelemetry::common::AttributeValue> empty;
data_[empty] += value;
}

void Observe(T value, const opentelemetry::common::KeyValueIterable &attributes) noexcept override
{
data_[MetricAttributes{attributes, attributes_processor_}] =
value; // overwrites the previous value if present
std::unordered_map<nostd::string_view, opentelemetry::common::AttributeValue> attr_map;
attributes.ForEachKeyValue(
[&](nostd::string_view key, opentelemetry::common::AttributeValue val) noexcept {
attr_map.emplace(key, val);
return true;
});
data_[attr_map] += value;
}

const std::unordered_map<MetricAttributes, T, AttributeHashGenerator> &GetMeasurements()
{
return data_;
}
const MeasurementAttributes<T> &GetMeasurements() { return data_; }

private:
std::unordered_map<MetricAttributes, T, AttributeHashGenerator> data_;
const AttributesProcessor *attributes_processor_;
MeasurementAttributes<T> data_;
};

} // namespace metrics
} // namespace sdk

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
# include "opentelemetry/sdk/metrics/exemplar/reservoir.h"
#endif

#include "opentelemetry/common/key_value_iterable_view.h"
#include "opentelemetry/sdk/metrics/instruments.h"
#include "opentelemetry/sdk/metrics/observer_result.h"
#include "opentelemetry/sdk/metrics/state/attributes_hashmap.h"
#include "opentelemetry/sdk/metrics/state/measurement_attributes_map.h"
#include "opentelemetry/sdk/metrics/state/metric_collector.h"
#include "opentelemetry/sdk/metrics/state/metric_storage.h"
#include "opentelemetry/sdk/metrics/state/temporal_metric_storage.h"
Expand All @@ -36,6 +38,7 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora
public:
AsyncMetricStorage(InstrumentDescriptor instrument_descriptor,
const AggregationType aggregation_type,
std::shared_ptr<const AttributesProcessor> attributes_processor,
#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
ExemplarFilterType exempler_filter_type,
nostd::shared_ptr<ExemplarReservoir> &&exemplar_reservoir,
Expand All @@ -48,6 +51,7 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora
std::make_unique<AttributesHashMap>(aggregation_config_->cardinality_limit_)),
delta_hash_map_(
std::make_unique<AttributesHashMap>(aggregation_config_->cardinality_limit_)),
attributes_processor_(std::move(attributes_processor)),
#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
exemplar_filter_type_(exempler_filter_type),
exemplar_reservoir_(exemplar_reservoir),
Expand Down Expand Up @@ -95,26 +99,45 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora
}
}

void RecordLong(
const std::unordered_map<MetricAttributes, int64_t, AttributeHashGenerator> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept override
void RecordLong(const opentelemetry::sdk::metrics::MeasurementAttributes<int64_t> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept override
{
if (instrument_descriptor_.value_type_ != InstrumentValueType::kLong)
{
return;
}
Record<int64_t>(measurements, observation_time);
std::unordered_map<MetricAttributes, int64_t, AttributeHashGenerator> mp =
ConvertMeasurementsToMetricAttributes<int64_t>(measurements, observation_time);
Record<int64_t>(mp, observation_time);
}

void RecordDouble(
const std::unordered_map<MetricAttributes, double, AttributeHashGenerator> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept override
void RecordDouble(const opentelemetry::sdk::metrics::MeasurementAttributes<double> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept override
{
if (instrument_descriptor_.value_type_ != InstrumentValueType::kDouble)
{
return;
}
Record<double>(measurements, observation_time);
std::unordered_map<MetricAttributes, double, AttributeHashGenerator> mp =
ConvertMeasurementsToMetricAttributes<double>(measurements, observation_time);
Record<double>(mp, observation_time);
}

template <class T>
std::unordered_map<MetricAttributes, T, AttributeHashGenerator>
ConvertMeasurementsToMetricAttributes(
const opentelemetry::sdk::metrics::MeasurementAttributes<T> &measurements,
opentelemetry::common::SystemTimestamp /* observation_time */) noexcept
{
std::unordered_map<MetricAttributes, T, AttributeHashGenerator> mp;
for (auto &measurement : measurements)
{
opentelemetry::common::KeyValueIterableView<
std::unordered_map<nostd::string_view, opentelemetry::common::AttributeValue>>
kv_view{measurement.first};
mp[MetricAttributes{kv_view, attributes_processor_.get()}] += measurement.second;
}
return mp;
}

bool Collect(CollectorHandle *collector,
Expand Down Expand Up @@ -144,6 +167,7 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora
const AggregationConfig *aggregation_config_;
std::unique_ptr<AttributesHashMap> cumulative_hash_map_;
std::unique_ptr<AttributesHashMap> delta_hash_map_;
std::shared_ptr<const AttributesProcessor> attributes_processor_;
opentelemetry::common::SpinLockMutex hashmap_lock_;
#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
ExemplarFilterType exemplar_filter_type_;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <cstring>
#include <string>
#include <unordered_map>
#include <utility>

#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"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace metrics
{

// Hash function for AttributeValue (int and string only)
struct AttributeValueHash
{
size_t operator()(const opentelemetry::common::AttributeValue &value) const noexcept
{
if (nostd::holds_alternative<int32_t>(value))
{
return std::hash<int32_t>{}(nostd::get<int32_t>(value));
}
else if (nostd::holds_alternative<int64_t>(value))
{
return std::hash<int64_t>{}(nostd::get<int64_t>(value));
}
else if (nostd::holds_alternative<const char *>(value))
{
return std::hash<nostd::string_view>{}(nostd::string_view(nostd::get<const char *>(value)));
}
else if (nostd::holds_alternative<nostd::string_view>(value))
{
return std::hash<nostd::string_view>{}(nostd::get<nostd::string_view>(value));
}

return 0; // fallback for other types
Copy link

Copilot AI Oct 13, 2025

Choose a reason for hiding this comment

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

The fallback hash value of 0 for unsupported types can cause hash collisions. Consider using a more distinctive hash value or throwing an exception for unsupported types to make the limitation explicit.

Suggested change
return 0; // fallback for other types
// Fallback for other types: use the index of the variant as a distinctive hash value
return std::hash<size_t>{}(value.index());

Copilot uses AI. Check for mistakes.

}
};

// Equality function for AttributeValue (int and string only)
struct AttributeValueEqual
{
bool operator()(const opentelemetry::common::AttributeValue &a,
const opentelemetry::common::AttributeValue &b) const noexcept
{
if (a.index() != b.index())
{
return false;
}

// Compare int32_t
if (nostd::holds_alternative<int32_t>(a))
{
return nostd::get<int32_t>(a) == nostd::get<int32_t>(b);
}
// Compare int64_t
else if (nostd::holds_alternative<int64_t>(a))
{
return nostd::get<int64_t>(a) == nostd::get<int64_t>(b);
}
// Compare const char*
else if (nostd::holds_alternative<const char *>(a))
{
return nostd::string_view(nostd::get<const char *>(a)) ==
nostd::string_view(nostd::get<const char *>(b));
}
// Compare string_view
else if (nostd::holds_alternative<nostd::string_view>(a))
{
return nostd::get<nostd::string_view>(a) == nostd::get<nostd::string_view>(b);
}

return false;
Copy link

Copilot AI Oct 13, 2025

Choose a reason for hiding this comment

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

The fallback return value of false for unsupported types in equality comparison can lead to incorrect behavior. Consider throwing an exception for unsupported types to make the limitation explicit, similar to the hash function concern.

Copilot uses AI. Check for mistakes.

}
};

// Hash function for unordered_map of key-value pairs
// This Custom Hash is only applied to strings and int for now
struct AttributeMapHash
{
size_t operator()(
const std::unordered_map<nostd::string_view, opentelemetry::common::AttributeValue> &map)
const noexcept
{
size_t hash = 0;
AttributeValueHash value_hasher;

for (const auto &pair : map)
{
// Combine key hash
size_t key_hash = std::hash<nostd::string_view>{}(pair.first);
hash ^= key_hash + 0x9e3779b9 + (hash << 6) + (hash >> 2);

// Combine value hash
size_t value_hash = value_hasher(pair.second);
hash ^= value_hash + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}

return hash;
}
};

// Equality function for unordered_map of key-value pairs
// This Custom Equal is only applied to strings and int for now
struct AttributeMapEqual
{
bool operator()(
const std::unordered_map<nostd::string_view, opentelemetry::common::AttributeValue> &a,
const std::unordered_map<nostd::string_view, opentelemetry::common::AttributeValue> &b)
const noexcept
{
if (a.size() != b.size())
{
return false;
}

AttributeValueEqual value_equal;

for (const auto &pair_a : a)
{
auto it = b.find(pair_a.first);
if (it == b.end())
{
return false; // key not found in b
}

// Compare values
if (!value_equal(pair_a.second, it->second))
{
return false;
}
}

return true;
}
};

template <class T>
using MeasurementAttributes = std::unordered_map<
std::unordered_map<nostd::string_view, opentelemetry::common::AttributeValue>,
T,
AttributeMapHash,
AttributeMapEqual>;

} // namespace metrics
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
17 changes: 9 additions & 8 deletions sdk/include/opentelemetry/sdk/metrics/state/metric_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "opentelemetry/sdk/metrics/data/metric_data.h"
#include "opentelemetry/sdk/metrics/instruments.h"
#include "opentelemetry/sdk/metrics/state/attributes_hashmap.h"
#include "opentelemetry/sdk/metrics/state/measurement_attributes_map.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
Expand Down Expand Up @@ -73,11 +74,11 @@ class AsyncWritableMetricStorage

/* Records a batch of measurements */
virtual void RecordLong(
const std::unordered_map<MetricAttributes, int64_t, AttributeHashGenerator> &measurements,
const opentelemetry::sdk::metrics::MeasurementAttributes<int64_t> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept = 0;

virtual void RecordDouble(
const std::unordered_map<MetricAttributes, double, AttributeHashGenerator> &measurements,
const opentelemetry::sdk::metrics::MeasurementAttributes<double> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept = 0;
};

Expand Down Expand Up @@ -119,14 +120,14 @@ class NoopWritableMetricStorage : public SyncWritableMetricStorage
class NoopAsyncWritableMetricStorage : public AsyncWritableMetricStorage
{
public:
void RecordLong(const std::unordered_map<MetricAttributes, int64_t, AttributeHashGenerator>
& /* measurements */,
opentelemetry::common::SystemTimestamp /* observation_time */) noexcept override
void RecordLong(
const opentelemetry::sdk::metrics::MeasurementAttributes<int64_t> & /* measurements */,
opentelemetry::common::SystemTimestamp /* observation_time */) noexcept override
{}

void RecordDouble(const std::unordered_map<MetricAttributes, double, AttributeHashGenerator>
& /* measurements */,
opentelemetry::common::SystemTimestamp /* observation_time */) noexcept override
void RecordDouble(
const opentelemetry::sdk::metrics::MeasurementAttributes<double> & /* measurements */,
opentelemetry::common::SystemTimestamp /* observation_time */) noexcept override
{}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "opentelemetry/common/timestamp.h"
#include "opentelemetry/context/context.h"
#include "opentelemetry/sdk/metrics/state/attributes_hashmap.h"
#include "opentelemetry/sdk/metrics/state/measurement_attributes_map.h"
#include "opentelemetry/sdk/metrics/state/metric_storage.h"
#include "opentelemetry/sdk/metrics/view/attributes_processor.h"
#include "opentelemetry/version.h"
Expand Down Expand Up @@ -80,19 +81,17 @@ class AsyncMultiMetricStorage : public AsyncWritableMetricStorage
storages_.push_back(storage);
}

void RecordLong(
const std::unordered_map<MetricAttributes, int64_t, AttributeHashGenerator> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept override
void RecordLong(const opentelemetry::sdk::metrics::MeasurementAttributes<int64_t> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept override
{
for (auto &s : storages_)
{
s->RecordLong(measurements, observation_time);
}
}

void RecordDouble(
const std::unordered_map<MetricAttributes, double, AttributeHashGenerator> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept override
void RecordDouble(const opentelemetry::sdk::metrics::MeasurementAttributes<double> &measurements,
opentelemetry::common::SystemTimestamp observation_time) noexcept override
{
for (auto &s : storages_)
{
Expand Down
Loading
Loading