-
Notifications
You must be signed in to change notification settings - Fork 591
[Metrics SDK] Fix histogram views rejected when only aggregation_cardinality_limit is set #4314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6f17c56
6b87ec4
2ae3755
c313e7f
5a3eba8
39edf35
3484667
25115b2
9b644c1
725fd5e
509f4b0
d98acee
d207474
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
|
|
||
| #include <gtest/gtest.h> | ||
|
|
||
| #include <cstddef> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <utility> | ||
|
|
@@ -16,6 +17,7 @@ | |
| #include "opentelemetry/sdk/configuration/always_on_sampler_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/extension_push_metric_exporter_builder.h" | ||
| #include "opentelemetry/sdk/configuration/extension_push_metric_exporter_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/instrument_type.h" | ||
| #include "opentelemetry/sdk/configuration/logger_config_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/logger_configurator_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/logger_matcher_and_config_configuration.h" | ||
|
|
@@ -30,11 +32,23 @@ | |
| #include "opentelemetry/sdk/configuration/span_limits_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/trace_id_ratio_based_sampler_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/tracer_provider_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/view_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/view_selector_configuration.h" | ||
| #include "opentelemetry/sdk/configuration/view_stream_configuration.h" | ||
|
|
||
| #include "opentelemetry/nostd/function_ref.h" | ||
| #include "opentelemetry/nostd/variant.h" | ||
| #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" | ||
| #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" | ||
| #include "opentelemetry/sdk/logs/logger_config.h" | ||
| #include "opentelemetry/sdk/metrics/aggregation/aggregation.h" | ||
| #include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" | ||
| #include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h" | ||
| #include "opentelemetry/sdk/metrics/data/point_data.h" | ||
| #include "opentelemetry/sdk/metrics/instruments.h" | ||
| #include "opentelemetry/sdk/metrics/metric_reader.h" | ||
| #include "opentelemetry/sdk/metrics/view/view.h" | ||
| #include "opentelemetry/sdk/metrics/view/view_registry.h" | ||
| #include "opentelemetry/sdk/resource/resource.h" | ||
| #include "opentelemetry/sdk/trace/sampler.h" | ||
| #include "opentelemetry/sdk/trace/span_limits.h" | ||
|
|
@@ -260,3 +274,110 @@ TEST(SdkBuilder, CreatePeriodicMetricReader) | |
| EXPECT_EQ(captured->timeout, model.timeout); | ||
| EXPECT_TRUE(captured->exporter != nullptr); | ||
| } | ||
|
|
||
| namespace | ||
| { | ||
|
|
||
| // Builds a ViewConfiguration selecting the given instrument type, with only | ||
| // aggregation_cardinality_limit set on the stream (no explicit `aggregation` block). | ||
| std::unique_ptr<config_sdk::ViewConfiguration> MakeCardinalityOnlyViewConfig( | ||
| config_sdk::InstrumentType instrument_type, | ||
| std::size_t cardinality_limit) | ||
| { | ||
| auto model = std::make_unique<config_sdk::ViewConfiguration>(); | ||
| model->selector = std::make_unique<config_sdk::ViewSelectorConfiguration>(); | ||
| model->selector->instrument_type = instrument_type; | ||
|
|
||
| model->stream = std::make_unique<config_sdk::ViewStreamConfiguration>(); | ||
| model->stream->aggregation_cardinality_limit = cardinality_limit; | ||
|
|
||
| return model; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| TEST(SdkBuilder, AddViewHistogramCardinalityLimitOnly) | ||
| { | ||
| namespace metrics_sdk = opentelemetry::sdk::metrics; | ||
|
|
||
| auto model = MakeCardinalityOnlyViewConfig(config_sdk::InstrumentType::histogram, 42); | ||
|
|
||
| auto registry = std::make_shared<config_sdk::Registry>(); | ||
| config_sdk::SdkBuilder builder(registry); | ||
|
|
||
| metrics_sdk::ViewRegistry view_registry; | ||
| builder.AddView(&view_registry, model); | ||
|
|
||
| metrics_sdk::InstrumentDescriptor instrument_descriptor{ | ||
| "", "", "", metrics_sdk::InstrumentType::kHistogram, metrics_sdk::InstrumentValueType::kLong}; | ||
| auto instrumentation_scope = scope_sdk::InstrumentationScope::Create(""); | ||
|
|
||
| int matched = 0; | ||
| view_registry.FindViews( | ||
| instrument_descriptor, *instrumentation_scope, [&](const metrics_sdk::View &view) { | ||
| matched++; | ||
| // The view must not be rejected: it should carry a | ||
| // HistogramAggregationConfig (not a plain AggregationConfig), since | ||
| // the instrument's default aggregation for kHistogram is kHistogram. | ||
| auto *aggregation_config = view.GetAggregationConfig(); | ||
| EXPECT_NE(aggregation_config, nullptr); | ||
| if (aggregation_config) | ||
| { | ||
| EXPECT_EQ(aggregation_config->GetType(), metrics_sdk::AggregationType::kHistogram); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new regression test proves only that the view registers with a So the suite stays green (and codecov reports the changed lines fully covered) with zero boundaries and a single bucket. Please make this a behavioral assertion: call
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, understood. The test was checking the shape of the code, not what a user actually gets. Updated AddViewHistogramCardinalityLimitOnly to call DefaultAggregation::CreateAggregation() with the config the view ends up holding, then assert on the resulting HistogramPointData: 15 boundaries, 16 buckets. I confirmed this assertion actually catches the regression; reverting the boundaries fix locally makes it fail with boundaries_.size() == 0 / counts_.size() == 1, which is exactly the degenerate case you described. |
||
| EXPECT_EQ(aggregation_config->cardinality_limit_, 42u); | ||
|
|
||
| // Pin what users actually receive: building the aggregation from this config | ||
| // must keep the SDK's default bucket boundaries, not silently collapse to a | ||
| // single bucket. A default-constructed HistogramAggregationConfig has empty | ||
| // boundaries_, which the aggregation takes literally (as opposed to a null | ||
| // config pointer, which falls back to the default boundaries), so AddView() | ||
| // must populate boundaries_ explicitly. | ||
| auto aggregation = metrics_sdk::DefaultAggregation::CreateAggregation( | ||
| metrics_sdk::AggregationType::kHistogram, instrument_descriptor, aggregation_config); | ||
| EXPECT_NE(aggregation, nullptr); | ||
| if (aggregation) | ||
| { | ||
| auto histogram_data = | ||
| opentelemetry::nostd::get<metrics_sdk::HistogramPointData>(aggregation->ToPoint()); | ||
| EXPECT_EQ(histogram_data.boundaries_.size(), 15u); | ||
| EXPECT_EQ(histogram_data.counts_.size(), 16u); | ||
| } | ||
| } | ||
| return true; | ||
| }); | ||
|
|
||
| EXPECT_EQ(matched, 1); | ||
| } | ||
|
|
||
| TEST(SdkBuilder, AddViewCounterCardinalityLimitOnly) | ||
| { | ||
| namespace metrics_sdk = opentelemetry::sdk::metrics; | ||
|
|
||
| auto model = MakeCardinalityOnlyViewConfig(config_sdk::InstrumentType::counter, 7); | ||
|
|
||
| auto registry = std::make_shared<config_sdk::Registry>(); | ||
| config_sdk::SdkBuilder builder(registry); | ||
|
|
||
| metrics_sdk::ViewRegistry view_registry; | ||
| builder.AddView(&view_registry, model); | ||
|
|
||
| metrics_sdk::InstrumentDescriptor instrument_descriptor{ | ||
| "", "", "", metrics_sdk::InstrumentType::kCounter, metrics_sdk::InstrumentValueType::kLong}; | ||
| auto instrumentation_scope = scope_sdk::InstrumentationScope::Create(""); | ||
|
|
||
| int matched = 0; | ||
| view_registry.FindViews( | ||
| instrument_descriptor, *instrumentation_scope, [&](const metrics_sdk::View &view) { | ||
| matched++; | ||
| auto *aggregation_config = view.GetAggregationConfig(); | ||
| EXPECT_NE(aggregation_config, nullptr); | ||
| if (aggregation_config) | ||
| { | ||
| EXPECT_EQ(aggregation_config->GetType(), metrics_sdk::AggregationType::kDefault); | ||
| EXPECT_EQ(aggregation_config->cardinality_limit_, 7u); | ||
| } | ||
| return true; | ||
| }); | ||
|
|
||
| EXPECT_EQ(matched, 1); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes the view registration, but it appears to silently collapse the histogram to a single bucket.
HistogramAggregationConfigdefault-constructsboundaries_as an empty vector, while the histogram constructor chooses between configured and default boundaries based only on whether the config pointer is non-null:In other words, "no config" previously selected the SDK's 15 default boundaries, whereas the synthesized config now means "explicitly use zero boundaries," producing one bucket. Could we preserve the default boundaries when creating this config and add a test that asserts the resulting histogram still has 15 boundaries and 16 buckets?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ThomsonTan Thank you for pointing it out. HistogramAggregationConfig default-constructs with an empty boundaries_, and the histogram aggregation constructors only fall back to the SDK defaults when the config pointer itself is nullptr, not when it's non-null-but-empty. Since this fallback path synthesizes the config rather than getting it from an explicit aggregation block, it was accidentally saying "use zero boundaries" instead of "use the defaults."
Fixed in 3484667, added HistogramAggregationConfig::DefaultBoundaries() as a single source of truth for the SDK's 15-boundary default list, and pointed the synthesized config at it. While I was in there, I also deduped the two other spots (LongHistogramAggregation/DoubleHistogramAggregation) that had this same literal hardcoded, so there will be no chance of the three copies drifting apart in the future.