From 11636e5bcdbb177f818f95b7ff62f3de559ec6a9 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Thu, 27 Jul 2023 16:14:37 -0500 Subject: [PATCH] Add experimental autoconfigure support for customizing cardinality limit --- sdk-extensions/autoconfigure/README.md | 6 + .../MeterProviderConfiguration.java | 13 ++- .../MeterProviderConfigurationTest.java | 104 ++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java diff --git a/sdk-extensions/autoconfigure/README.md b/sdk-extensions/autoconfigure/README.md index 3611975b35d..76ac4826c28 100644 --- a/sdk-extensions/autoconfigure/README.md +++ b/sdk-extensions/autoconfigure/README.md @@ -292,6 +292,12 @@ These properties can be used to control the maximum size of spans by placing lim |------------------------------|------------------------------|-----------------------------------------------------------------------------------------------------------------| | otel.metrics.exemplar.filter | OTEL_METRICS_EXEMPLAR_FILTER | The filter for exemplar sampling. Can be `ALWAYS_OFF`, `ALWAYS_ON` or `TRACE_BASED`. Default is `TRACE_BASED`. | +## Cardinality Limits + +| System property | Environment variable | Description | +|---------------------------------------------|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------| +| otel.experimental.metrics.cardinality.limit | OTEL_EXPERIMENTAL_METRICS_CARDINALITY_LIMIT | If set, configure experimental cardinality limit. The value dictates the maximum number of distinct points per metric. Default is `2000`. | + ## Periodic Metric Reader | System property | Environment variable | Description | diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfiguration.java index aaab88488f5..7f7465b603d 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfiguration.java @@ -13,6 +13,7 @@ import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter; +import io.opentelemetry.sdk.metrics.internal.state.MetricStorage; import java.io.Closeable; import java.util.Collections; import java.util.List; @@ -48,8 +49,18 @@ static void configureMeterProvider( break; } + int cardinalityLimit = + config.getInt( + "otel.experimental.metrics.cardinality.limit", MetricStorage.DEFAULT_MAX_CARDINALITY); + if (cardinalityLimit < 1) { + throw new ConfigurationException("otel.experimental.metrics.cardinality.limit must be >= 1"); + } + configureMetricReaders(config, serviceClassLoader, metricExporterCustomizer, closeables) - .forEach(meterProviderBuilder::registerMetricReader); + .forEach( + reader -> + SdkMeterProviderUtil.registerMetricReaderWithCardinalitySelector( + meterProviderBuilder, reader, instrumentType -> cardinalityLimit)); } static List configureMetricReaders( diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java new file mode 100644 index 00000000000..43b76a48d5e --- /dev/null +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/MeterProviderConfigurationTest.java @@ -0,0 +1,104 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure; + +import static org.assertj.core.api.Assertions.as; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.internal.testing.CleanupExtension; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector; +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class MeterProviderConfigurationTest { + + @RegisterExtension CleanupExtension cleanup = new CleanupExtension(); + + @Test + void configureMeterProvider_InvalidCardinalityLimit() { + List closeables = new ArrayList<>(); + + assertThatThrownBy( + () -> { + MeterProviderConfiguration.configureMeterProvider( + SdkMeterProvider.builder(), + DefaultConfigProperties.createForTest( + ImmutableMap.of( + "otel.metrics.exporter", + "logging", + "otel.experimental.metrics.cardinality.limit", + "0")), + MeterProviderConfigurationTest.class.getClassLoader(), + (a, b) -> a, + closeables); + }) + .isInstanceOf(ConfigurationException.class) + .hasMessage("otel.experimental.metrics.cardinality.limit must be >= 1"); + cleanup.addCloseables(closeables); + } + + @Test + void configureMeterProvider_ConfiguresCardinalityLimit() { + List closeables = new ArrayList<>(); + + // Verify default cardinality + SdkMeterProviderBuilder builder = SdkMeterProvider.builder(); + MeterProviderConfiguration.configureMeterProvider( + builder, + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.metrics.exporter", "logging")), + MeterProviderConfigurationTest.class.getClassLoader(), + (a, b) -> a, + closeables); + cleanup.addCloseables(closeables); + assertCardinalityLimit(builder, 2000); + + // Customized limit cardinality limit to 100 + builder = SdkMeterProvider.builder(); + MeterProviderConfiguration.configureMeterProvider( + builder, + DefaultConfigProperties.createForTest( + ImmutableMap.of( + "otel.metrics.exporter", + "logging", + "otel.experimental.metrics.cardinality.limit", + "100")), + MeterProviderConfigurationTest.class.getClassLoader(), + (a, b) -> a, + closeables); + cleanup.addCloseables(closeables); + assertCardinalityLimit(builder, 100); + } + + private static void assertCardinalityLimit( + SdkMeterProviderBuilder builder, int expectedCardinalityLimit) { + assertThat(builder) + .extracting( + "metricReaders", + as(InstanceOfAssertFactories.map(MetricReader.class, CardinalityLimitSelector.class))) + .hasSizeGreaterThan(0) + .allSatisfy( + (metricReader, cardinalityLimitSelector) -> { + for (InstrumentType instrumentType : InstrumentType.values()) { + assertThat(cardinalityLimitSelector.getCardinalityLimit(instrumentType)) + .isEqualTo(expectedCardinalityLimit); + } + }); + } +}