Skip to content
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;
Copy link
Contributor

Choose a reason for hiding this comment

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

explicit not required for default constructor. Here it can also be removed (rule of 0)


~ObserverResultT() override = default;

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

Choose a reason for hiding this comment

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

Can be replaced with new type MeasurementsKeyType

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;
Copy link
Contributor

Choose a reason for hiding this comment

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

replace with new type MeasurementsKeyType

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
}
};

// 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;
}
};

// Hash function for unordered_map of key-value pairs
// This Custom Hash is only applied to strings and int for now
struct AttributeMapHash
Copy link
Member

Choose a reason for hiding this comment

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

Please see sdk/common/attributemap_hash.h -> GetHash and GetHashForAttributeValueVisitor. Consider if these can be used.

{
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
Copy link
Contributor

Choose a reason for hiding this comment

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

add another type here for use in AsyncMetricStorage:
using MeasurementsKeyType = typename Measurements::key_type;

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure that it's worth, adding another runtime expensive associative container just for a list of some attributes. Unordered_map gets efficient with thousands and thousands of entries. But still very slow in comparison to a boost::flat_map or absl::flat_hash_map or a simple std::vector.

Copy link
Contributor

@Reneg973 Reneg973 Oct 25, 2025

Choose a reason for hiding this comment

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

with the type std::unordered_map<nostd::string_view, opentelemetry::common::AttributeValue> it will also lead to dangling references. It will work when the string_view is created with "abcd", because this will stay in memory, but putting in a std::string... > crash

} // 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