Skip to content

Commit 1f771a3

Browse files
committed
[Metrics SDK] Implement configurable cardinality limit
1 parent a56f0e2 commit 1f771a3

11 files changed

Lines changed: 221 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Increment the:
1515

1616
## [Unreleased]
1717

18+
* [METRICS SDK] Implement configurable cardinality limit at three priority levels
19+
[#3292](https://github.com/open-telemetry/opentelemetry-cpp/issues/3292)
20+
1821
* [SDK] Add `TracerProvider::UpdateTracerConfigurator()` and example
1922
[#4065](https://github.com/open-telemetry/opentelemetry-cpp/issues/4065)
2023

sdk/include/opentelemetry/sdk/metrics/meter_context.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <atomic>
77
#include <chrono>
8+
#include <cstddef>
89
#include <memory>
910
#include <vector>
1011

@@ -108,6 +109,15 @@ class MeterContext : public std::enable_shared_from_this<MeterContext>
108109
*/
109110
nostd::span<std::shared_ptr<CollectorHandle>> GetCollectors() noexcept;
110111

112+
/**
113+
* Get the cardinality limit for a given instrument type from configured readers.
114+
* Returns the maximum limit across all readers, or 0 if no reader specifies a limit.
115+
*
116+
* @param instrument_type The instrument type to get the limit for
117+
* @return The maximum cardinality limit, or 0 if not configured
118+
*/
119+
size_t GetReaderCardinalityLimit(InstrumentType instrument_type) noexcept;
120+
111121
/**
112122
* GET SDK Start time
113123
*

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <atomic>
77
#include <chrono>
8+
#include <cstddef>
89

910
#include "opentelemetry/nostd/function_ref.h"
1011
#include "opentelemetry/sdk/metrics/export/metric_producer.h"
@@ -16,6 +17,22 @@ namespace sdk
1617
{
1718
namespace metrics
1819
{
20+
/**
21+
* Configuration options for cardinality limits per instrument type.
22+
* A value of 0 means use the SDK default (2000).
23+
*/
24+
struct CardinalityLimitOptions
25+
{
26+
size_t default_limit = 0; // 0 means use SDK default (2000)
27+
size_t counter = 0;
28+
size_t gauge = 0;
29+
size_t histogram = 0;
30+
size_t observable_counter = 0;
31+
size_t observable_gauge = 0;
32+
size_t observable_up_down_counter = 0;
33+
size_t up_down_counter = 0;
34+
};
35+
1936
/**
2037
* MetricReader defines the interface to collect metrics from SDK
2138
*/
@@ -45,6 +62,22 @@ class MetricReader
4562
virtual AggregationTemporality GetAggregationTemporality(
4663
InstrumentType instrument_type) const noexcept = 0;
4764

65+
/**
66+
* Get the cardinality limit for a given instrument type.
67+
* Returns 0 if no limit is configured for this instrument type.
68+
*
69+
* @param instrument_type The instrument type to get the limit for
70+
* @return The cardinality limit, or 0 if not configured
71+
*/
72+
size_t GetCardinalityLimit(InstrumentType instrument_type) const noexcept;
73+
74+
/**
75+
* Set cardinality limit options for this reader.
76+
*
77+
* @param options The cardinality limit options
78+
*/
79+
void SetCardinalityLimitOptions(const CardinalityLimitOptions &options) noexcept;
80+
4881
/**
4982
* Shutdown the metric reader.
5083
*/
@@ -73,6 +106,7 @@ class MetricReader
73106
private:
74107
MetricProducer *metric_producer_{nullptr};
75108
std::atomic<bool> shutdown_{false};
109+
CardinalityLimitOptions cardinality_limit_options_;
76110
};
77111
} // namespace metrics
78112
} // namespace sdk

sdk/include/opentelemetry/sdk/metrics/state/async_metric_storage.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora
4040
ExemplarFilterType exempler_filter_type,
4141
nostd::shared_ptr<ExemplarReservoir> &&exemplar_reservoir,
4242
#endif
43-
const AggregationConfig *aggregation_config)
44-
: instrument_descriptor_(instrument_descriptor),
43+
const AggregationConfig *aggregation_config,
44+
std::shared_ptr<AggregationConfig> owned_aggregation_config = nullptr)
45+
: owned_aggregation_config_(std::move(owned_aggregation_config)),
46+
instrument_descriptor_(instrument_descriptor),
4547
aggregation_type_{aggregation_type},
4648
aggregation_config_{AggregationConfig::GetOrDefault(aggregation_config)},
4749
cumulative_hash_map_(
@@ -139,6 +141,8 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora
139141
}
140142

141143
private:
144+
// Keep owned config alive if created from reader-level defaults
145+
std::shared_ptr<AggregationConfig> owned_aggregation_config_;
142146
InstrumentDescriptor instrument_descriptor_;
143147
AggregationType aggregation_type_;
144148
const AggregationConfig *aggregation_config_;

sdk/include/opentelemetry/sdk/metrics/state/metric_collector.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma once
55

66
#include <chrono>
7+
#include <cstddef>
78
#include <memory>
89

910
#include "opentelemetry/nostd/function_ref.h"
@@ -36,6 +37,19 @@ class CollectorHandle
3637

3738
virtual AggregationTemporality GetAggregationTemporality(
3839
InstrumentType instrument_type) noexcept = 0;
40+
41+
/**
42+
* Get the cardinality limit for a given instrument type.
43+
* Returns 0 if no limit is configured.
44+
*
45+
* @param instrument_type The instrument type to get the limit for
46+
* @return The cardinality limit, or 0 if not configured
47+
*/
48+
virtual size_t GetCardinalityLimit(InstrumentType instrument_type) noexcept
49+
{
50+
(void)instrument_type;
51+
return 0;
52+
}
3953
};
4054

4155
/**
@@ -61,6 +75,8 @@ class MetricCollector : public MetricProducer, public CollectorHandle
6175
AggregationTemporality GetAggregationTemporality(
6276
InstrumentType instrument_type) noexcept override;
6377

78+
size_t GetCardinalityLimit(InstrumentType instrument_type) noexcept override;
79+
6480
/**
6581
* The callback to be called for each metric exporter. This will only be those
6682
* metrics that have been produced since the last time this method was called.

sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage
6969
ExemplarFilterType exempler_filter_type,
7070
nostd::shared_ptr<ExemplarReservoir> &&exemplar_reservoir,
7171
#endif
72-
const AggregationConfig *aggregation_config)
73-
: instrument_descriptor_(instrument_descriptor),
72+
const AggregationConfig *aggregation_config,
73+
std::shared_ptr<AggregationConfig> owned_aggregation_config = nullptr)
74+
: owned_aggregation_config_(std::move(owned_aggregation_config)),
75+
instrument_descriptor_(instrument_descriptor),
7476
aggregation_config_(AggregationConfig::GetOrDefault(aggregation_config)),
7577
attributes_hashmap_(
7678
std::make_unique<AttributesHashMap>(aggregation_config_->cardinality_limit_)),
@@ -291,6 +293,8 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage
291293
}
292294
#endif
293295

296+
// Keep owned config alive if created from reader-level defaults
297+
std::shared_ptr<AggregationConfig> owned_aggregation_config_;
294298
InstrumentDescriptor instrument_descriptor_;
295299
// hashmap to maintain the metrics for delta collection (i.e, collection since last Collect call)
296300
const AggregationConfig *aggregation_config_;

sdk/src/configuration/sdk_builder.cc

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,14 +1471,24 @@ std::unique_ptr<opentelemetry::sdk::metrics::MetricReader> SdkBuilder::CreatePer
14711471
OTEL_INTERNAL_LOG_WARN("metric producer not supported, ignoring");
14721472
}
14731473

1474+
sdk = opentelemetry::sdk::metrics::PeriodicExportingMetricReaderFactory::Create(
1475+
std::move(exporter_sdk), options);
1476+
14741477
if (model->cardinality_limits != nullptr)
14751478
{
1476-
OTEL_INTERNAL_LOG_WARN("cardinality limits not supported, ignoring");
1479+
opentelemetry::sdk::metrics::CardinalityLimitOptions cardinality_options;
1480+
cardinality_options.default_limit = model->cardinality_limits->default_limit;
1481+
cardinality_options.counter = model->cardinality_limits->counter;
1482+
cardinality_options.gauge = model->cardinality_limits->gauge;
1483+
cardinality_options.histogram = model->cardinality_limits->histogram;
1484+
cardinality_options.observable_counter = model->cardinality_limits->observable_counter;
1485+
cardinality_options.observable_gauge = model->cardinality_limits->observable_gauge;
1486+
cardinality_options.observable_up_down_counter =
1487+
model->cardinality_limits->observable_up_down_counter;
1488+
cardinality_options.up_down_counter = model->cardinality_limits->up_down_counter;
1489+
sdk->SetCardinalityLimitOptions(cardinality_options);
14771490
}
14781491

1479-
sdk = opentelemetry::sdk::metrics::PeriodicExportingMetricReaderFactory::Create(
1480-
std::move(exporter_sdk), options);
1481-
14821492
return sdk;
14831493
}
14841494

@@ -1496,7 +1506,17 @@ std::unique_ptr<opentelemetry::sdk::metrics::MetricReader> SdkBuilder::CreatePul
14961506

14971507
if (model->cardinality_limits != nullptr)
14981508
{
1499-
OTEL_INTERNAL_LOG_WARN("cardinality limits not supported, ignoring");
1509+
opentelemetry::sdk::metrics::CardinalityLimitOptions cardinality_options;
1510+
cardinality_options.default_limit = model->cardinality_limits->default_limit;
1511+
cardinality_options.counter = model->cardinality_limits->counter;
1512+
cardinality_options.gauge = model->cardinality_limits->gauge;
1513+
cardinality_options.histogram = model->cardinality_limits->histogram;
1514+
cardinality_options.observable_counter = model->cardinality_limits->observable_counter;
1515+
cardinality_options.observable_gauge = model->cardinality_limits->observable_gauge;
1516+
cardinality_options.observable_up_down_counter =
1517+
model->cardinality_limits->observable_up_down_counter;
1518+
cardinality_options.up_down_counter = model->cardinality_limits->up_down_counter;
1519+
sdk->SetCardinalityLimitOptions(cardinality_options);
15001520
}
15011521

15021522
return sdk;
@@ -1600,6 +1620,20 @@ void SdkBuilder::AddView(
16001620

16011621
sdk_aggregation_config = CreateAggregationConfig(stream->aggregation, sdk_aggregation_type);
16021622

1623+
// Apply aggregation_cardinality_limit from the view stream configuration
1624+
if (stream->aggregation_cardinality_limit != 0)
1625+
{
1626+
if (sdk_aggregation_config)
1627+
{
1628+
sdk_aggregation_config->cardinality_limit_ = stream->aggregation_cardinality_limit;
1629+
}
1630+
else
1631+
{
1632+
sdk_aggregation_config = std::make_shared<opentelemetry::sdk::metrics::AggregationConfig>(
1633+
stream->aggregation_cardinality_limit);
1634+
}
1635+
}
1636+
16031637
std::unique_ptr<opentelemetry::sdk::metrics::AttributesProcessor> sdk_attribute_processor;
16041638

16051639
if (stream->attribute_keys != nullptr)

sdk/src/metrics/meter.cc

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
#include <cstddef>
45
#include <cstdint>
56
#include <functional>
7+
#include <memory>
68
#include <mutex>
79
#include <ostream>
810
#include <string>
@@ -542,14 +544,34 @@ std::unique_ptr<SyncWritableMetricStorage> Meter::RegisterSyncMetricStorage(
542544
else
543545
{
544546
WarnOnDuplicateInstrument(GetInstrumentationScope(), storage_registry_, view_instr_desc);
547+
548+
// Determine effective aggregation config with reader-level fallback
549+
const AggregationConfig *effective_config = view.GetAggregationConfig();
550+
std::shared_ptr<AggregationConfig> reader_config;
551+
552+
// If the view does not specify a cardinality limit, use the MetricReader-level default
553+
if (!effective_config)
554+
{
555+
auto ctx_ptr = meter_context_.lock();
556+
if (ctx_ptr)
557+
{
558+
size_t reader_limit = ctx_ptr->GetReaderCardinalityLimit(instrument_descriptor.type_);
559+
if (reader_limit != 0)
560+
{
561+
reader_config = std::make_shared<AggregationConfig>(reader_limit);
562+
effective_config = reader_config.get();
563+
}
564+
}
565+
}
566+
545567
sync_storage = std::shared_ptr<SyncMetricStorage>(new SyncMetricStorage(
546568
view_instr_desc, view.GetAggregationType(), view.GetAttributesProcessor(),
547569
#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
548570
exemplar_filter_type,
549571
GetExemplarReservoir(view.GetAggregationType(), view.GetAggregationConfig(),
550572
view_instr_desc),
551573
#endif
552-
view.GetAggregationConfig()));
574+
effective_config, std::move(reader_config)));
553575
storage_registry_.insert({view_instr_desc, sync_storage});
554576
}
555577
auto sync_multi_storage = static_cast<SyncMultiMetricStorage *>(storages.get());
@@ -615,14 +637,34 @@ std::unique_ptr<AsyncWritableMetricStorage> Meter::RegisterAsyncMetricStorage(
615637
else
616638
{
617639
WarnOnDuplicateInstrument(GetInstrumentationScope(), storage_registry_, view_instr_desc);
640+
641+
// Determine effective aggregation config with reader-level fallback
642+
const AggregationConfig *effective_config = view.GetAggregationConfig();
643+
std::shared_ptr<AggregationConfig> reader_config;
644+
645+
// If the view does not specify a cardinality limit, use the MetricReader-level default
646+
if (!effective_config)
647+
{
648+
auto ctx_ptr = meter_context_.lock();
649+
if (ctx_ptr)
650+
{
651+
size_t reader_limit = ctx_ptr->GetReaderCardinalityLimit(instrument_descriptor.type_);
652+
if (reader_limit != 0)
653+
{
654+
reader_config = std::make_shared<AggregationConfig>(reader_limit);
655+
effective_config = reader_config.get();
656+
}
657+
}
658+
}
659+
618660
async_storage = std::shared_ptr<AsyncMetricStorage>(new AsyncMetricStorage(
619661
view_instr_desc, view.GetAggregationType(),
620662
#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW
621663
exemplar_filter_type,
622664
GetExemplarReservoir(view.GetAggregationType(), view.GetAggregationConfig(),
623665
view_instr_desc),
624666
#endif
625-
view.GetAggregationConfig()));
667+
effective_config, std::move(reader_config)));
626668
storage_registry_.insert({view_instr_desc, async_storage});
627669
}
628670
auto async_multi_storage = static_cast<AsyncMultiMetricStorage *>(storages.get());

sdk/src/metrics/meter_context.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <atomic>
55
#include <chrono>
6+
#include <cstddef>
67
#include <memory>
78
#include <mutex>
89
#include <ostream>
@@ -91,6 +92,20 @@ nostd::span<std::shared_ptr<CollectorHandle>> MeterContext::GetCollectors() noex
9192
return nostd::span<std::shared_ptr<CollectorHandle>>(collectors_.data(), collectors_.size());
9293
}
9394

95+
size_t MeterContext::GetReaderCardinalityLimit(InstrumentType instrument_type) noexcept
96+
{
97+
size_t max_limit = 0;
98+
for (const auto &collector : collectors_)
99+
{
100+
size_t limit = collector->GetCardinalityLimit(instrument_type);
101+
if (limit > max_limit)
102+
{
103+
max_limit = limit;
104+
}
105+
}
106+
return max_limit;
107+
}
108+
94109
opentelemetry::common::SystemTimestamp MeterContext::GetSDKStartTime() noexcept
95110
{
96111
return sdk_start_ts_;

0 commit comments

Comments
 (0)