diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java index c0f1476077a..b31b3721428 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeter.java @@ -88,7 +88,8 @@ final class SdkMeter implements Meter { private final MeterProviderSharedState meterProviderSharedState; private final InstrumentationScopeInfo instrumentationScopeInfo; private final Map readerStorageRegistries; - private final boolean meterEnabled; + + private boolean meterEnabled; SdkMeter( MeterProviderSharedState meterProviderSharedState, @@ -103,6 +104,18 @@ final class SdkMeter implements Meter { this.meterEnabled = meterConfig.isEnabled(); } + void updateMeterConfig(MeterConfig meterConfig) { + meterEnabled = meterConfig.isEnabled(); + + for (RegisteredReader registeredReader : readerStorageRegistries.keySet()) { + Collection storages = + Objects.requireNonNull(readerStorageRegistries.get(registeredReader)).getStorages(); + for (MetricStorage storage : storages) { + storage.setEnabled(meterEnabled); + } + } + } + // Visible for testing InstrumentationScopeInfo getInstrumentationScopeInfo() { return instrumentationScopeInfo; @@ -110,21 +123,22 @@ InstrumentationScopeInfo getInstrumentationScopeInfo() { /** Collect all metrics for the meter. */ Collection collectAll(RegisteredReader registeredReader, long epochNanos) { - // Short circuit collection process if meter is disabled - if (!meterEnabled) { - return Collections.emptyList(); - } List currentRegisteredCallbacks; synchronized (callbackLock) { currentRegisteredCallbacks = new ArrayList<>(callbackRegistrations); } // Collections across all readers are sequential synchronized (collectLock) { - for (CallbackRegistration callbackRegistration : currentRegisteredCallbacks) { - callbackRegistration.invokeCallback( - registeredReader, meterProviderSharedState.getStartEpochNanos(), epochNanos); + // Only invoke callbacks if meter is enabled + if (meterEnabled) { + for (CallbackRegistration callbackRegistration : currentRegisteredCallbacks) { + callbackRegistration.invokeCallback( + registeredReader, meterProviderSharedState.getStartEpochNanos(), epochNanos); + } } + // Collect even if meter is disabled. Storage is responsible for managing state and returning + // empty metric if disabled. Collection storages = Objects.requireNonNull(readerStorageRegistries.get(registeredReader)).getStorages(); List result = new ArrayList<>(storages.size()); @@ -275,7 +289,8 @@ WriteableMetricStorage registerSynchronousMetricStorage(InstrumentDescriptor ins reader, registeredView, instrument, - meterProviderSharedState.getExemplarFilter()))); + meterProviderSharedState.getExemplarFilter(), + meterEnabled))); } } @@ -301,7 +316,8 @@ SdkObservableMeasurement registerObservableMeasurement( } registeredStorages.add( registry.register( - AsynchronousMetricStorage.create(reader, registeredView, instrumentDescriptor))); + AsynchronousMetricStorage.create( + reader, registeredView, instrumentDescriptor, meterEnabled))); } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java index 30ae0b1da5a..a2a076fd9a0 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java @@ -52,9 +52,10 @@ public final class SdkMeterProvider implements MeterProvider, Closeable { private final List metricProducers; private final MeterProviderSharedState sharedState; private final ComponentRegistry registry; - private final ScopeConfigurator meterConfigurator; private final AtomicBoolean isClosed = new AtomicBoolean(false); + private ScopeConfigurator meterConfigurator; + /** Returns a new {@link SdkMeterProviderBuilder} for {@link SdkMeterProvider}. */ public static SdkMeterProviderBuilder builder() { return new SdkMeterProviderBuilder(); @@ -105,6 +106,15 @@ private MeterConfig getMeterConfig(InstrumentationScopeInfo instrumentationScope return meterConfig == null ? MeterConfig.defaultConfig() : meterConfig; } + void setMeterConfigurator(ScopeConfigurator meterConfigurator) { + this.meterConfigurator = meterConfigurator; + this.registry + .getComponents() + .forEach( + sdkMeter -> + sdkMeter.updateMeterConfig(getMeterConfig(sdkMeter.getInstrumentationScopeInfo()))); + } + @Override public MeterBuilder meterBuilder(String instrumentationScopeName) { if (registeredReaders.isEmpty()) { diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java index 9fc690366ed..c324900ab7d 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java @@ -49,6 +49,19 @@ public static SdkMeterProviderBuilder setExemplarFilter( return sdkMeterProviderBuilder; } + /** Reflectively set the {@link ScopeConfigurator} to the {@link SdkMeterProvider}. */ + public static void setMeterConfigurator( + SdkMeterProvider sdkMeterProvider, ScopeConfigurator scopeConfigurator) { + try { + Method method = + SdkMeterProvider.class.getDeclaredMethod("setMeterConfigurator", ScopeConfigurator.class); + method.setAccessible(true); + method.invoke(sdkMeterProvider, scopeConfigurator); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException("Error calling setMeterConfigurator on SdkMeterProvider", e); + } + } + /** Reflectively set the {@link ScopeConfigurator} to the {@link SdkMeterProviderBuilder}. */ public static SdkMeterProviderBuilder setMeterConfigurator( SdkMeterProviderBuilder sdkMeterProviderBuilder, diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorage.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorage.java index ad68fdd3ec7..6463226f8bc 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorage.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorage.java @@ -22,6 +22,7 @@ import io.opentelemetry.sdk.metrics.internal.aggregator.Aggregator; import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorFactory; import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorHandle; +import io.opentelemetry.sdk.metrics.internal.aggregator.EmptyMetricData; import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor; import io.opentelemetry.sdk.metrics.internal.descriptor.MetricDescriptor; import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter; @@ -86,12 +87,15 @@ public final class AsynchronousMetricStorage aggregator, AttributesProcessor attributesProcessor, - int maxCardinality) { + int maxCardinality, + boolean enabled) { this.registeredReader = registeredReader; this.metricDescriptor = metricDescriptor; this.aggregationTemporality = @@ -102,6 +106,7 @@ private AsynchronousMetricStorage( this.aggregator = aggregator; this.attributesProcessor = attributesProcessor; this.maxCardinality = maxCardinality - 1; + this.enabled = enabled; this.reusablePointsPool = new ObjectPool<>(aggregator::createReusablePoint); this.reusableHandlesPool = new ObjectPool<>(aggregator::createHandle); this.handleBuilder = ignored -> reusableHandlesPool.borrowObject(); @@ -125,7 +130,8 @@ private AsynchronousMetricStorage( AsynchronousMetricStorage create( RegisteredReader registeredReader, RegisteredView registeredView, - InstrumentDescriptor instrumentDescriptor) { + InstrumentDescriptor instrumentDescriptor, + boolean enabled) { View view = registeredView.getView(); MetricDescriptor metricDescriptor = MetricDescriptor.create(view, registeredView.getViewSourceInfo(), instrumentDescriptor); @@ -140,7 +146,8 @@ AsynchronousMetricStorage create( metricDescriptor, aggregator, registeredView.getViewAttributesProcessor(), - registeredView.getCardinalityLimit()); + registeredView.getCardinalityLimit(), + enabled); } /** Record callback measurement from {@link ObservableLongMeasurement}. */ @@ -207,8 +214,10 @@ public MetricData collect( aggregatorHandles.forEach(handleReleaser); aggregatorHandles.clear(); - return aggregator.toMetricData( - resource, instrumentationScopeInfo, metricDescriptor, result, aggregationTemporality); + return enabled + ? aggregator.toMetricData( + resource, instrumentationScopeInfo, metricDescriptor, result, aggregationTemporality) + : EmptyMetricData.getInstance(); } private Collection collectWithDeltaAggregationTemporality() { @@ -310,7 +319,7 @@ private Collection collectWithCumulativeAggregationTemporality() { } @Override - public boolean isEmpty() { - return aggregator == Aggregator.drop(); + public void setEnabled(boolean enabled) { + this.enabled = enabled; } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/DefaultSynchronousMetricStorage.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/DefaultSynchronousMetricStorage.java index c0deda9d068..77070ace40d 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/DefaultSynchronousMetricStorage.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/DefaultSynchronousMetricStorage.java @@ -74,12 +74,15 @@ public final class DefaultSynchronousMetricStorage> aggregatorHandlePool = new ConcurrentLinkedQueue<>(); + private boolean enabled; + DefaultSynchronousMetricStorage( RegisteredReader registeredReader, MetricDescriptor metricDescriptor, Aggregator aggregator, AttributesProcessor attributesProcessor, - int maxCardinality) { + int maxCardinality, + boolean enabled) { this.registeredReader = registeredReader; this.metricDescriptor = metricDescriptor; this.aggregationTemporality = @@ -90,6 +93,7 @@ public final class DefaultSynchronousMetricStorage> getAggregatorHandlePool() { @Override public void recordLong(long value, Attributes attributes, Context context) { + if (!enabled) { + return; + } AggregatorHolder aggregatorHolder = getHolderForRecord(); try { AggregatorHandle handle = @@ -111,6 +118,9 @@ public void recordLong(long value, Attributes attributes, Context context) { @Override public void recordDouble(double value, Attributes attributes, Context context) { + if (!enabled) { + return; + } if (Double.isNaN(value)) { logger.log( Level.FINE, @@ -131,9 +141,14 @@ public void recordDouble(double value, Attributes attributes, Context context) { } } + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + @Override public boolean isEnabled() { - return true; + return enabled; } /** @@ -299,7 +314,7 @@ public MetricData collect( previousCollectionAggregatorHandles = aggregatorHandles; } - if (points.isEmpty()) { + if (points.isEmpty() || !enabled) { return EmptyMetricData.getInstance(); } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/EmptyMetricStorage.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/EmptyMetricStorage.java index faaa7087c76..9a0ed207877 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/EmptyMetricStorage.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/EmptyMetricStorage.java @@ -44,4 +44,9 @@ public void recordDouble(double value, Attributes attributes, Context context) { public boolean isEnabled() { return false; } + + @Override + public void setEnabled(boolean enabled) { + // do nothing + } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MetricStorage.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MetricStorage.java index b31852cda9c..6bada056d8a 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MetricStorage.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MetricStorage.java @@ -48,14 +48,5 @@ MetricData collect( long startEpochNanos, long epochNanos); - /** - * Determines whether this storage is an empty metric storage. - * - *

Uses the reference comparison since {@link EmptyMetricStorage} is singleton. - * - * @return true if is empty. - */ - default boolean isEmpty() { - return this == EmptyMetricStorage.INSTANCE; - } + void setEnabled(boolean enabled); } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/SynchronousMetricStorage.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/SynchronousMetricStorage.java index f743c26cbc7..efb49692563 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/SynchronousMetricStorage.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/SynchronousMetricStorage.java @@ -40,7 +40,8 @@ static SynchronousMetricStorage cr RegisteredReader registeredReader, RegisteredView registeredView, InstrumentDescriptor instrumentDescriptor, - ExemplarFilter exemplarFilter) { + ExemplarFilter exemplarFilter, + boolean enabled) { View view = registeredView.getView(); MetricDescriptor metricDescriptor = MetricDescriptor.create(view, registeredView.getViewSourceInfo(), instrumentDescriptor); @@ -57,6 +58,7 @@ static SynchronousMetricStorage cr metricDescriptor, aggregator, registeredView.getViewAttributesProcessor(), - registeredView.getCardinalityLimit()); + registeredView.getCardinalityLimit(), + enabled); } } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java index 479dcd62088..571c1496148 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterProviderTest.java @@ -28,8 +28,10 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.metrics.internal.MeterConfig; import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; import io.opentelemetry.sdk.metrics.internal.view.ViewRegistry; import io.opentelemetry.sdk.resources.Resource; @@ -47,6 +49,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; @@ -1030,6 +1033,34 @@ void resetForTest() { sum -> sum.isCumulative().hasPointsSatisfying(point -> point.hasValue(1)))); } + private static ScopeConfigurator flipConfigurator(boolean enabled) { + return scopeInfo -> enabled ? MeterConfig.disabled() : MeterConfig.enabled(); + } + + @Test + void propagatesEnablementToMeterDirectly() { + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(InMemoryMetricReader.create()).build(); + SdkMeter meter = (SdkMeter) meterProvider.get("test"); + boolean isEnabled = meter.isMeterEnabled(); + + meterProvider.setMeterConfigurator(flipConfigurator(isEnabled)); + + Assertions.assertThat(meter.isMeterEnabled()).isEqualTo(!isEnabled); + } + + @Test + void propagatesEnablementToMeterByUtil() { + SdkMeterProvider sdkMeterProvider = + SdkMeterProvider.builder().registerMetricReader(InMemoryMetricReader.create()).build(); + SdkMeter sdkMeter = (SdkMeter) sdkMeterProvider.get("test"); + boolean isEnabled = sdkMeter.isMeterEnabled(); + + SdkMeterProviderUtil.setMeterConfigurator(sdkMeterProvider, flipConfigurator(isEnabled)); + + Assertions.assertThat(sdkMeter.isMeterEnabled()).isEqualTo(!isEnabled); + } + private static void registerViewForAllTypes( SdkMeterProviderBuilder meterProviderBuilder, Aggregation aggregation) { for (InstrumentType instrumentType : InstrumentType.values()) { diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterTest.java index 468d0121e2b..e38d3180bd4 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/SdkMeterTest.java @@ -18,6 +18,7 @@ import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.internal.testing.slf4j.SuppressLogger; +import io.opentelemetry.sdk.metrics.internal.MeterConfig; import io.opentelemetry.sdk.metrics.internal.state.MetricStorageRegistry; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import java.util.Locale; @@ -481,4 +482,16 @@ void stringRepresentation() { + "attributes={}" + "}}"); } + + @Test + void updateEnabled() { + SdkMeterProvider sdkMeterProvider = + SdkMeterProvider.builder().registerMetricReader(InMemoryMetricReader.create()).build(); + SdkMeter meter = (SdkMeter) sdkMeterProvider.get("test"); + + meter.updateMeterConfig(MeterConfig.disabled()); + assertThat(meter.isMeterEnabled()).isFalse(); + meter.updateMeterConfig(MeterConfig.enabled()); + assertThat(meter.isMeterEnabled()).isTrue(); + } } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorageTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorageTest.java index e33e3d71800..d744a22c2db 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorageTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/AsynchronousMetricStorageTest.java @@ -86,7 +86,8 @@ void setup(MemoryMode memoryMode) { "unit", InstrumentType.COUNTER, InstrumentValueType.LONG, - Advice.empty())); + Advice.empty()), + /* enabled= */ true); doubleCounterStorage = AsynchronousMetricStorage.create( registeredReader, @@ -97,7 +98,8 @@ void setup(MemoryMode memoryMode) { "unit", InstrumentType.COUNTER, InstrumentValueType.DOUBLE, - Advice.empty())); + Advice.empty()), + /* enabled= */ true); } @ParameterizedTest @@ -174,7 +176,8 @@ void record_ProcessesAttributes(MemoryMode memoryMode) { "unit", InstrumentType.COUNTER, InstrumentValueType.LONG, - Advice.empty())); + Advice.empty()), + /* enabled= */ true); storage.setEpochInformation(0, 1); storage.record(Attributes.builder().put("key1", "a").put("key2", "b").build(), 1); @@ -342,7 +345,8 @@ void collect_DeltaComputesDiff(MemoryMode memoryMode) { "unit", InstrumentType.COUNTER, InstrumentValueType.LONG, - Advice.empty())); + Advice.empty()), + /* enabled= */ true); // Record measurement and collect at time 10 longCounterStorage.setEpochInformation(0, 10); @@ -456,4 +460,29 @@ void collect_reusableData_reusedObjectsAreReturnedOnSecondCall() { .anySatisfy(point -> assertThat(point).isSameAs(firstCollectionPoint)); } } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void enabledThenDisable_recordAndCollect(MemoryMode memoryMode) { + setup(memoryMode); + + longCounterStorage.setEnabled(false); + + longCounterStorage.record(Attributes.empty(), 10); + + assertThat(longCounterStorage.collect(resource, scope, 0, 0).isEmpty()).isTrue(); + } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void enabledThenDisableThenEnable_recordAndCollect(MemoryMode memoryMode) { + setup(memoryMode); + + longCounterStorage.setEnabled(false); + longCounterStorage.setEnabled(true); + + longCounterStorage.record(Attributes.empty(), 10); + + assertThat(longCounterStorage.collect(resource, scope, 0, 0).isEmpty()).isFalse(); + } } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/MetricStorageRegistryTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/MetricStorageRegistryTest.java index 99a26106a78..9f91d13fe24 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/MetricStorageRegistryTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/MetricStorageRegistryTest.java @@ -119,5 +119,10 @@ public void recordDouble(double value, Attributes attributes, Context context) { public boolean isEnabled() { return true; } + + @Override + public void setEnabled(boolean enabled) { + throw new UnsupportedOperationException("Not implemented"); + } } } diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/SynchronousMetricStorageTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/SynchronousMetricStorageTest.java index 0117af20b59..bc1f5acffdd 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/SynchronousMetricStorageTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/internal/state/SynchronousMetricStorageTest.java @@ -117,7 +117,8 @@ void recordDouble_NaN(MemoryMode memoryMode) { METRIC_DESCRIPTOR, aggregator, attributesProcessor, - CARDINALITY_LIMIT); + CARDINALITY_LIMIT, + /* enabled= */ true); storage.recordDouble(Double.NaN, Attributes.empty(), Context.current()); @@ -143,7 +144,8 @@ void attributesProcessor_applied(MemoryMode memoryMode) { METRIC_DESCRIPTOR, aggregator, spyAttributesProcessor, - CARDINALITY_LIMIT); + CARDINALITY_LIMIT, + /* enabled= */ true); storage.recordDouble(1, attributes, Context.root()); MetricData md = storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, testClock.now()); assertThat(md) @@ -166,7 +168,8 @@ void recordAndCollect_CumulativeDoesNotReset(MemoryMode memoryMode) { METRIC_DESCRIPTOR, aggregator, attributesProcessor, - CARDINALITY_LIMIT); + CARDINALITY_LIMIT, + /* enabled= */ true); // Record measurement and collect at time 10 storage.recordDouble(3, Attributes.empty(), Context.current()); @@ -210,7 +213,12 @@ void recordAndCollect_DeltaResets_ImmutableData() { DefaultSynchronousMetricStorage storage = new DefaultSynchronousMetricStorage<>( - deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor, CARDINALITY_LIMIT); + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); // Record measurement and collect at time 10 storage.recordDouble(3, Attributes.empty(), Context.current()); @@ -259,7 +267,12 @@ void recordAndCollect_DeltaResets_ReusableData() { DefaultSynchronousMetricStorage storage = new DefaultSynchronousMetricStorage<>( - deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor, CARDINALITY_LIMIT); + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); // Record measurement and collect at time 10 storage.recordDouble(3, Attributes.empty(), Context.current()); @@ -361,7 +374,8 @@ void recordAndCollect_CumulativeAtLimit(MemoryMode memoryMode) { METRIC_DESCRIPTOR, aggregator, attributesProcessor, - CARDINALITY_LIMIT); + CARDINALITY_LIMIT, + /* enabled= */ true); // Record measurements for CARDINALITY_LIMIT - 1, since 1 slot is reserved for the overflow // series @@ -428,7 +442,12 @@ void recordAndCollect_DeltaAtLimit_ImmutableDataMemoryMode() { DefaultSynchronousMetricStorage storage = new DefaultSynchronousMetricStorage<>( - deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor, CARDINALITY_LIMIT); + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); // Record measurements for CARDINALITY_LIMIT - 1, since 1 slot is reserved for the overflow // series @@ -524,7 +543,12 @@ void recordAndCollect_DeltaAtLimit_ReusableDataMemoryMode() { DefaultSynchronousMetricStorage storage = new DefaultSynchronousMetricStorage<>( - deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor, CARDINALITY_LIMIT); + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); // Record measurements for CARDINALITY_LIMIT - 1, since 1 slot is reserved for the overflow // series @@ -606,7 +630,12 @@ void recordAndCollect_DeltaAtLimit_ReusableDataMemoryMode_ExpireUnused() { DefaultSynchronousMetricStorage storage = new DefaultSynchronousMetricStorage<>( - deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor, CARDINALITY_LIMIT); + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); // 1st recording: Recording goes to active map for (int i = 0; i < CARDINALITY_LIMIT - 1; i++) { @@ -814,7 +843,8 @@ private static Stream concurrentStressTestArguments() { METRIC_DESCRIPTOR, aggregator, AttributesProcessor.noop(), - CARDINALITY_LIMIT), + CARDINALITY_LIMIT, + /* enabled= */ true), (BiConsumer) (value, cumulativeCount) -> cumulativeCount.addAndGet(value))); @@ -828,11 +858,94 @@ private static Stream concurrentStressTestArguments() { METRIC_DESCRIPTOR, aggregator, AttributesProcessor.noop(), - CARDINALITY_LIMIT), + CARDINALITY_LIMIT, + /* enabled= */ true), (BiConsumer) (value, cumulativeCount) -> cumulativeCount.set(value))); } return argumentsList.stream(); } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void enabledThenDisable_isEnabled(MemoryMode memoryMode) { + initialize(memoryMode); + + DefaultSynchronousMetricStorage storage = + new DefaultSynchronousMetricStorage<>( + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); + + storage.setEnabled(false); + + assertThat(storage.isEnabled()).isFalse(); + } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void enabledThenDisableThenEnable_isEnabled(MemoryMode memoryMode) { + initialize(memoryMode); + + DefaultSynchronousMetricStorage storage = + new DefaultSynchronousMetricStorage<>( + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); + + storage.setEnabled(false); + storage.setEnabled(true); + + assertThat(storage.isEnabled()).isTrue(); + } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void enabledThenDisable_recordAndCollect(MemoryMode memoryMode) { + initialize(memoryMode); + + DefaultSynchronousMetricStorage storage = + new DefaultSynchronousMetricStorage<>( + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); + + storage.setEnabled(false); + + storage.recordDouble(10d, Attributes.empty(), Context.current()); + + assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 10).isEmpty()).isTrue(); + } + + @ParameterizedTest + @EnumSource(MemoryMode.class) + void enabledThenDisableThenEnable_recordAndCollect(MemoryMode memoryMode) { + initialize(memoryMode); + + DefaultSynchronousMetricStorage storage = + new DefaultSynchronousMetricStorage<>( + deltaReader, + METRIC_DESCRIPTOR, + aggregator, + attributesProcessor, + CARDINALITY_LIMIT, + /* enabled= */ true); + + storage.setEnabled(false); + storage.setEnabled(true); + + storage.recordDouble(10d, Attributes.empty(), Context.current()); + + assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 10).isEmpty()).isFalse(); + } } diff --git a/sdk/metrics/src/testIncubating/java/io/opentelemetry/sdk/metrics/MeterConfigTest.java b/sdk/metrics/src/testIncubating/java/io/opentelemetry/sdk/metrics/MeterConfigTest.java index aaaeaf5f85d..f09a7f058be 100644 --- a/sdk/metrics/src/testIncubating/java/io/opentelemetry/sdk/metrics/MeterConfigTest.java +++ b/sdk/metrics/src/testIncubating/java/io/opentelemetry/sdk/metrics/MeterConfigTest.java @@ -240,4 +240,68 @@ private static Stream meterConfiguratorArgs() { Arguments.of(enableStartsWithD, scopeDog, enabled()), Arguments.of(enableStartsWithD, scopeDuck, enabled())); } + + @Test + void setScopeConfigurator() { + // 1. Initially, configure all meters to be enabled except meterB + InMemoryMetricReader reader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder() + .addMeterConfiguratorCondition(nameEquals("meterB"), disabled()) + .registerMetricReader(reader) + .build(); + + SdkMeter meterA = (SdkMeter) meterProvider.get("meterA"); + SdkMeter meterB = (SdkMeter) meterProvider.get("meterB"); + SdkMeter meterC = (SdkMeter) meterProvider.get("meterC"); + + // verify isMeterEnabled() + assertThat(meterA.isMeterEnabled()).isTrue(); + assertThat(meterB.isMeterEnabled()).isFalse(); + assertThat(meterC.isMeterEnabled()).isTrue(); + + // verify metrics are emitted as expected + meterA.counterBuilder("meterA").build().add(1); + meterB.counterBuilder("meterB").build().add(2); + meterC.counterBuilder("meterC").build().add(3); + assertThat(reader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + metricData -> assertThat(metricData).hasName("meterA"), + metricData -> assertThat(metricData).hasName("meterC")); + + // 2. Update config to disable all meters + meterProvider.setMeterConfigurator( + ScopeConfigurator.builder().setDefault(disabled()).build()); + + // verify isEnabled() + assertThat(meterA.isMeterEnabled()).isFalse(); + assertThat(meterB.isMeterEnabled()).isFalse(); + assertThat(meterC.isMeterEnabled()).isFalse(); + + // verify metrics are emitted as expected + meterA.counterBuilder("meterA").build().add(1); + meterB.counterBuilder("meterB").build().add(2); + meterC.counterBuilder("meterC").build().add(3); + assertThat(reader.collectAllMetrics()).isEmpty(); + + // 3. Update config to restore original + meterProvider.setMeterConfigurator( + ScopeConfigurator.builder() + .addCondition(nameEquals("meterB"), disabled()) + .build()); + + // verify isEnabled() + assertThat(meterA.isMeterEnabled()).isTrue(); + assertThat(meterB.isMeterEnabled()).isFalse(); + assertThat(meterC.isMeterEnabled()).isTrue(); + + // verify metrics are emitted as expected + meterA.counterBuilder("meterA").build().add(1); + meterB.counterBuilder("meterB").build().add(2); + meterC.counterBuilder("meterC").build().add(3); + assertThat(reader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + metricData -> assertThat(metricData).hasName("meterA"), + metricData -> assertThat(metricData).hasName("meterC")); + } }