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;
3940using namespace opentelemetry ::common;
4041namespace 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+
4259TEST (CardinalityLimit, AttributesHashMapBasicTests)
4360{
4461 AttributesHashMap hash_map (10 );
@@ -156,3 +173,81 @@ TEST_P(WritableMetricStorageCardinalityLimitTestFixture, LongCounterSumAggregati
156173INSTANTIATE_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