Skip to content

Commit b26221c

Browse files
Copilotlalitb
andcommitted
Add configurable cardinality limit support to View class
Co-authored-by: lalitb <[email protected]>
1 parent 5d68878 commit b26221c

File tree

4 files changed

+130
-3
lines changed

4 files changed

+130
-3
lines changed

sdk/include/opentelemetry/sdk/metrics/metric_reader.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ class MetricReader
4141
virtual AggregationTemporality GetAggregationTemporality(
4242
InstrumentType instrument_type) const noexcept = 0;
4343

44+
/**
45+
* Get the default cardinality limit for given Instrument Type for this reader.
46+
*
47+
* @param instrument_type The instrument type to get the cardinality limit for
48+
* @return The cardinality limit, or 0 if no limit is set
49+
*/
50+
virtual size_t GetDefaultCardinalityLimit(InstrumentType instrument_type) const noexcept
51+
{
52+
// Default implementation returns no limit
53+
(void)instrument_type;
54+
return 0;
55+
}
56+
4457
/**
4558
* Shutdown the metric reader.
4659
*/

sdk/include/opentelemetry/sdk/metrics/view/view.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ class View
3131
std::shared_ptr<AggregationConfig> aggregation_config = nullptr,
3232
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> attributes_processor =
3333
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor>(
34-
new opentelemetry::sdk::metrics::DefaultAttributesProcessor()))
34+
new opentelemetry::sdk::metrics::DefaultAttributesProcessor()),
35+
size_t aggregation_cardinality_limit = 0)
3536
: name_(name),
3637
description_(description),
3738
unit_(unit),
3839
aggregation_type_{aggregation_type},
3940
aggregation_config_{aggregation_config},
40-
attributes_processor_{std::move(attributes_processor)}
41+
attributes_processor_{std::move(attributes_processor)},
42+
aggregation_cardinality_limit_{aggregation_cardinality_limit}
4143
{}
4244

4345
virtual ~View() = default;
@@ -59,13 +61,24 @@ class View
5961
return attributes_processor_;
6062
}
6163

64+
virtual size_t GetAggregationCardinalityLimit() const noexcept
65+
{
66+
return aggregation_cardinality_limit_;
67+
}
68+
69+
virtual bool HasAggregationCardinalityLimit() const noexcept
70+
{
71+
return aggregation_cardinality_limit_ > 0;
72+
}
73+
6274
private:
6375
std::string name_;
6476
std::string description_;
6577
std::string unit_;
6678
AggregationType aggregation_type_;
6779
std::shared_ptr<AggregationConfig> aggregation_config_;
6880
std::shared_ptr<AttributesProcessor> attributes_processor_;
81+
size_t aggregation_cardinality_limit_;
6982
};
7083
} // namespace metrics
7184
} // namespace sdk

sdk/src/metrics/meter.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,14 +538,20 @@ std::unique_ptr<SyncWritableMetricStorage> Meter::RegisterSyncMetricStorage(
538538
else
539539
{
540540
WarnOnDuplicateInstrument(GetInstrumentationScope(), storage_registry_, view_instr_desc);
541+
// Calculate cardinality limit based on specification priority:
542+
// 1. View-specific cardinality limit (if set)
543+
// 2. Default value of 2000
544+
size_t cardinality_limit = view.HasAggregationCardinalityLimit()
545+
? view.GetAggregationCardinalityLimit()
546+
: kAggregationCardinalityLimit;
541547
sync_storage = std::shared_ptr<SyncMetricStorage>(new SyncMetricStorage(
542548
view_instr_desc, view.GetAggregationType(), view.GetAttributesProcessor(),
543549
#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
544550
exemplar_filter_type,
545551
GetExemplarReservoir(view.GetAggregationType(), view.GetAggregationConfig(),
546552
view_instr_desc),
547553
#endif
548-
view.GetAggregationConfig()));
554+
view.GetAggregationConfig(), cardinality_limit));
549555
storage_registry_.insert({view_instr_desc, sync_storage});
550556
}
551557
auto sync_multi_storage = static_cast<SyncMultiMetricStorage *>(storages.get());

sdk/test/metrics/cardinality_limit_test.cc

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "opentelemetry/sdk/metrics/state/metric_collector.h"
3030
#include "opentelemetry/sdk/metrics/state/sync_metric_storage.h"
3131
#include "opentelemetry/sdk/metrics/view/attributes_processor.h"
32+
#include "opentelemetry/sdk/metrics/view/view.h"
3233

3334
#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
3435
# include "opentelemetry/sdk/metrics/exemplar/filter_type.h"
@@ -39,6 +40,22 @@ using namespace opentelemetry::sdk::metrics;
3940
using namespace opentelemetry::common;
4041
namespace nostd = opentelemetry::nostd;
4142

43+
TEST(CardinalityLimit, ViewCardinalityLimitConfiguration)
44+
{
45+
// Test View without cardinality limit
46+
View view_no_limit("test_view_no_limit");
47+
EXPECT_FALSE(view_no_limit.HasAggregationCardinalityLimit());
48+
EXPECT_EQ(view_no_limit.GetAggregationCardinalityLimit(), 0);
49+
50+
// Test View with cardinality limit
51+
View view_with_limit("test_view_with_limit", "", "", AggregationType::kDefault, nullptr,
52+
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor>(
53+
new opentelemetry::sdk::metrics::DefaultAttributesProcessor()),
54+
500);
55+
EXPECT_TRUE(view_with_limit.HasAggregationCardinalityLimit());
56+
EXPECT_EQ(view_with_limit.GetAggregationCardinalityLimit(), 500);
57+
}
58+
4259
TEST(CardinalityLimit, AttributesHashMapBasicTests)
4360
{
4461
AttributesHashMap hash_map(10);
@@ -156,3 +173,81 @@ TEST_P(WritableMetricStorageCardinalityLimitTestFixture, LongCounterSumAggregati
156173
INSTANTIATE_TEST_SUITE_P(All,
157174
WritableMetricStorageCardinalityLimitTestFixture,
158175
::testing::Values(AggregationTemporality::kDelta));
176+
177+
TEST(CardinalityLimit, SyncMetricStorageWithViewCardinalityLimit)
178+
{
179+
auto sdk_start_ts = std::chrono::system_clock::now();
180+
InstrumentDescriptor instr_desc = {"name", "desc", "1unit", InstrumentType::kCounter,
181+
InstrumentValueType::kLong};
182+
std::shared_ptr<DefaultAttributesProcessor> default_attributes_processor{
183+
new DefaultAttributesProcessor{}};
184+
185+
// Create a view with a cardinality limit of 5
186+
View view_with_limit("test_view", "", "", AggregationType::kSum, nullptr,
187+
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor>(
188+
new opentelemetry::sdk::metrics::DefaultAttributesProcessor()),
189+
5);
190+
191+
// Test that the view has the cardinality limit
192+
EXPECT_TRUE(view_with_limit.HasAggregationCardinalityLimit());
193+
EXPECT_EQ(view_with_limit.GetAggregationCardinalityLimit(), 5);
194+
195+
// Create SyncMetricStorage using the cardinality limit from the view
196+
size_t cardinality_limit = view_with_limit.HasAggregationCardinalityLimit()
197+
? view_with_limit.GetAggregationCardinalityLimit()
198+
: kAggregationCardinalityLimit;
199+
SyncMetricStorage storage(instr_desc, AggregationType::kSum, default_attributes_processor,
200+
#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
201+
ExemplarFilterType::kAlwaysOff,
202+
ExemplarReservoir::GetNoExemplarReservoir(),
203+
#endif
204+
nullptr, cardinality_limit);
205+
206+
int64_t record_value = 100;
207+
// Add 5 unique metric points (should all fit within limit)
208+
for (auto i = 0; i < 5; i++)
209+
{
210+
std::map<std::string, std::string> attributes = {{"key", std::to_string(i)}};
211+
storage.RecordLong(record_value,
212+
KeyValueIterableView<std::map<std::string, std::string>>(attributes),
213+
opentelemetry::context::Context{});
214+
}
215+
216+
// Add 3 more unique metric points (should trigger overflow behavior)
217+
for (auto i = 5; i < 8; i++)
218+
{
219+
std::map<std::string, std::string> attributes = {{"key", std::to_string(i)}};
220+
storage.RecordLong(record_value,
221+
KeyValueIterableView<std::map<std::string, std::string>>(attributes),
222+
opentelemetry::context::Context{});
223+
}
224+
225+
AggregationTemporality temporality = AggregationTemporality::kDelta;
226+
std::shared_ptr<CollectorHandle> collector(new MockCollectorHandle(temporality));
227+
std::vector<std::shared_ptr<CollectorHandle>> collectors;
228+
collectors.push_back(collector);
229+
auto collection_ts = std::chrono::system_clock::now();
230+
size_t count_attributes = 0;
231+
bool overflow_present = false;
232+
233+
storage.Collect(
234+
collector.get(), collectors, sdk_start_ts, collection_ts, [&](const MetricData &metric_data) {
235+
for (const auto &data_attr : metric_data.point_data_attr_)
236+
{
237+
count_attributes++;
238+
if (data_attr.attributes.begin()->first == kAttributesLimitOverflowKey)
239+
{
240+
// The overflow attribute should contain the aggregated values from the 3 excess metrics
241+
const auto &data = opentelemetry::nostd::get<SumPointData>(data_attr.point_data);
242+
EXPECT_EQ(nostd::get<int64_t>(data.value_), record_value * 3);
243+
overflow_present = true;
244+
}
245+
}
246+
return true;
247+
});
248+
249+
// We should have exactly 5 attributes (the cardinality limit)
250+
EXPECT_EQ(count_attributes, 5);
251+
// And there should be an overflow attribute
252+
EXPECT_TRUE(overflow_present);
253+
}

0 commit comments

Comments
 (0)