Experimental metric reader and view cardinality limits (#5494)
This commit is contained in:
parent
4d034b08e8
commit
331c6af8d6
|
|
@ -421,7 +421,7 @@ class OpenTelemetrySdkTest {
|
||||||
+ "clock=SystemClock{}, "
|
+ "clock=SystemClock{}, "
|
||||||
+ "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, "
|
+ "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, "
|
||||||
+ "metricReaders=[PeriodicMetricReader{exporter=MockMetricExporter{}, intervalNanos=60000000000}], "
|
+ "metricReaders=[PeriodicMetricReader{exporter=MockMetricExporter{}, intervalNanos=60000000000}], "
|
||||||
+ "views=[RegisteredView{instrumentSelector=InstrumentSelector{instrumentName=instrument}, view=View{name=new-instrument, aggregation=DefaultAggregation, attributesProcessor=NoopAttributesProcessor{}}}]"
|
+ "views=[RegisteredView{instrumentSelector=InstrumentSelector{instrumentName=instrument}, view=View{name=new-instrument, aggregation=DefaultAggregation, attributesProcessor=NoopAttributesProcessor{}, cardinalityLimit=2000}}]"
|
||||||
+ "}, "
|
+ "}, "
|
||||||
+ "loggerProvider=SdkLoggerProvider{"
|
+ "loggerProvider=SdkLoggerProvider{"
|
||||||
+ "clock=SystemClock{}, "
|
+ "clock=SystemClock{}, "
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import io.opentelemetry.sdk.metrics.export.MetricReader;
|
import io.opentelemetry.sdk.metrics.export.MetricReader;
|
||||||
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
|
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
|
||||||
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
|
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
|
||||||
import io.opentelemetry.sdk.metrics.internal.export.MetricProducer;
|
import io.opentelemetry.sdk.metrics.internal.export.MetricProducer;
|
||||||
import io.opentelemetry.sdk.metrics.internal.export.RegisteredReader;
|
import io.opentelemetry.sdk.metrics.internal.export.RegisteredReader;
|
||||||
import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState;
|
import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState;
|
||||||
|
|
@ -26,6 +27,7 @@ import java.io.Closeable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
@ -54,17 +56,19 @@ public final class SdkMeterProvider implements MeterProvider, Closeable {
|
||||||
|
|
||||||
SdkMeterProvider(
|
SdkMeterProvider(
|
||||||
List<RegisteredView> registeredViews,
|
List<RegisteredView> registeredViews,
|
||||||
List<MetricReader> metricReaders,
|
IdentityHashMap<MetricReader, CardinalityLimitSelector> metricReaders,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
Resource resource,
|
Resource resource,
|
||||||
ExemplarFilter exemplarFilter) {
|
ExemplarFilter exemplarFilter) {
|
||||||
long startEpochNanos = clock.now();
|
long startEpochNanos = clock.now();
|
||||||
this.registeredViews = registeredViews;
|
this.registeredViews = registeredViews;
|
||||||
this.registeredReaders =
|
this.registeredReaders =
|
||||||
metricReaders.stream()
|
metricReaders.entrySet().stream()
|
||||||
.map(
|
.map(
|
||||||
reader ->
|
entry ->
|
||||||
RegisteredReader.create(reader, ViewRegistry.create(reader, registeredViews)))
|
RegisteredReader.create(
|
||||||
|
entry.getKey(),
|
||||||
|
ViewRegistry.create(entry.getKey(), entry.getValue(), registeredViews)))
|
||||||
.collect(toList());
|
.collect(toList());
|
||||||
this.sharedState =
|
this.sharedState =
|
||||||
MeterProviderSharedState.create(clock, resource, exemplarFilter, startEpochNanos);
|
MeterProviderSharedState.create(clock, resource, exemplarFilter, startEpochNanos);
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ import io.opentelemetry.sdk.metrics.export.MetricReader;
|
||||||
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
|
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
|
||||||
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
|
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
|
||||||
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
|
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
|
||||||
import io.opentelemetry.sdk.metrics.internal.view.RegisteredView;
|
import io.opentelemetry.sdk.metrics.internal.view.RegisteredView;
|
||||||
import io.opentelemetry.sdk.resources.Resource;
|
import io.opentelemetry.sdk.resources.Resource;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
@ -32,7 +34,8 @@ public final class SdkMeterProviderBuilder {
|
||||||
|
|
||||||
private Clock clock = Clock.getDefault();
|
private Clock clock = Clock.getDefault();
|
||||||
private Resource resource = Resource.getDefault();
|
private Resource resource = Resource.getDefault();
|
||||||
private final List<MetricReader> metricReaders = new ArrayList<>();
|
private final IdentityHashMap<MetricReader, CardinalityLimitSelector> metricReaders =
|
||||||
|
new IdentityHashMap<>();
|
||||||
private final List<RegisteredView> registeredViews = new ArrayList<>();
|
private final List<RegisteredView> registeredViews = new ArrayList<>();
|
||||||
private ExemplarFilter exemplarFilter = DEFAULT_EXEMPLAR_FILTER;
|
private ExemplarFilter exemplarFilter = DEFAULT_EXEMPLAR_FILTER;
|
||||||
|
|
||||||
|
|
@ -96,7 +99,11 @@ public final class SdkMeterProviderBuilder {
|
||||||
Objects.requireNonNull(view, "view");
|
Objects.requireNonNull(view, "view");
|
||||||
registeredViews.add(
|
registeredViews.add(
|
||||||
RegisteredView.create(
|
RegisteredView.create(
|
||||||
selector, view, view.getAttributesProcessor(), SourceInfo.fromCurrentStack()));
|
selector,
|
||||||
|
view,
|
||||||
|
view.getAttributesProcessor(),
|
||||||
|
view.getCardinalityLimit(),
|
||||||
|
SourceInfo.fromCurrentStack()));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +113,20 @@ public final class SdkMeterProviderBuilder {
|
||||||
* <p>Note: custom implementations of {@link MetricReader} are not currently supported.
|
* <p>Note: custom implementations of {@link MetricReader} are not currently supported.
|
||||||
*/
|
*/
|
||||||
public SdkMeterProviderBuilder registerMetricReader(MetricReader reader) {
|
public SdkMeterProviderBuilder registerMetricReader(MetricReader reader) {
|
||||||
metricReaders.add(reader);
|
metricReaders.put(reader, CardinalityLimitSelector.defaultCardinalityLimitSelector());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a {@link MetricReader} with a {@link CardinalityLimitSelector}.
|
||||||
|
*
|
||||||
|
* <p>Note: not currently stable but available for experimental use via {@link
|
||||||
|
* SdkMeterProviderUtil#registerMetricReaderWithCardinalitySelector(SdkMeterProviderBuilder,
|
||||||
|
* MetricReader, CardinalityLimitSelector)}.
|
||||||
|
*/
|
||||||
|
SdkMeterProviderBuilder registerMetricReader(
|
||||||
|
MetricReader reader, CardinalityLimitSelector cardinalityLimitSelector) {
|
||||||
|
metricReaders.put(reader, cardinalityLimitSelector);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,10 @@ public abstract class View {
|
||||||
@Nullable String name,
|
@Nullable String name,
|
||||||
@Nullable String description,
|
@Nullable String description,
|
||||||
Aggregation aggregation,
|
Aggregation aggregation,
|
||||||
AttributesProcessor attributesProcessor) {
|
AttributesProcessor attributesProcessor,
|
||||||
return new AutoValue_View(name, description, aggregation, attributesProcessor);
|
int cardinalityLimit) {
|
||||||
|
return new AutoValue_View(
|
||||||
|
name, description, aggregation, attributesProcessor, cardinalityLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
View() {}
|
View() {}
|
||||||
|
|
@ -58,6 +60,9 @@ public abstract class View {
|
||||||
/** Returns the attribute processor used for this view. */
|
/** Returns the attribute processor used for this view. */
|
||||||
abstract AttributesProcessor getAttributesProcessor();
|
abstract AttributesProcessor getAttributesProcessor();
|
||||||
|
|
||||||
|
/** Returns the cardinality limit for this view. */
|
||||||
|
abstract int getCardinalityLimit();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final String toString() {
|
public final String toString() {
|
||||||
StringJoiner joiner = new StringJoiner(", ", "View{", "}");
|
StringJoiner joiner = new StringJoiner(", ", "View{", "}");
|
||||||
|
|
@ -69,6 +74,7 @@ public abstract class View {
|
||||||
}
|
}
|
||||||
joiner.add("aggregation=" + getAggregation());
|
joiner.add("aggregation=" + getAggregation());
|
||||||
joiner.add("attributesProcessor=" + getAttributesProcessor());
|
joiner.add("attributesProcessor=" + getAttributesProcessor());
|
||||||
|
joiner.add("cardinalityLimit=" + getCardinalityLimit());
|
||||||
return joiner.toString();
|
return joiner.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ package io.opentelemetry.sdk.metrics;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
|
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
|
||||||
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorFactory;
|
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorFactory;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
|
||||||
import io.opentelemetry.sdk.metrics.internal.view.AttributesProcessor;
|
import io.opentelemetry.sdk.metrics.internal.view.AttributesProcessor;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
@ -23,6 +24,7 @@ public final class ViewBuilder {
|
||||||
@Nullable private String description;
|
@Nullable private String description;
|
||||||
private Aggregation aggregation = Aggregation.defaultAggregation();
|
private Aggregation aggregation = Aggregation.defaultAggregation();
|
||||||
private AttributesProcessor processor = AttributesProcessor.noop();
|
private AttributesProcessor processor = AttributesProcessor.noop();
|
||||||
|
private int cardinalityLimit = MetricStorage.DEFAULT_MAX_CARDINALITY;
|
||||||
|
|
||||||
ViewBuilder() {}
|
ViewBuilder() {}
|
||||||
|
|
||||||
|
|
@ -85,8 +87,24 @@ public final class ViewBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cardinality limit.
|
||||||
|
*
|
||||||
|
* <p>Note: not currently stable but cardinality limit can be configured via
|
||||||
|
* SdkMeterProviderUtil#setCardinalityLimit(ViewBuilder, int)}.
|
||||||
|
*
|
||||||
|
* @param cardinalityLimit the maximum number of series for a metric
|
||||||
|
*/
|
||||||
|
ViewBuilder setCardinalityLimit(int cardinalityLimit) {
|
||||||
|
if (cardinalityLimit <= 0) {
|
||||||
|
throw new IllegalArgumentException("cardinalityLimit must be > 0");
|
||||||
|
}
|
||||||
|
this.cardinalityLimit = cardinalityLimit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns a {@link View} with the configuration of this builder. */
|
/** Returns a {@link View} with the configuration of this builder. */
|
||||||
public View build() {
|
public View build() {
|
||||||
return View.create(name, description, aggregation, processor);
|
return View.create(name, description, aggregation, processor, cardinalityLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ package io.opentelemetry.sdk.metrics.internal;
|
||||||
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
|
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
|
||||||
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
|
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
|
||||||
import io.opentelemetry.sdk.metrics.ViewBuilder;
|
import io.opentelemetry.sdk.metrics.ViewBuilder;
|
||||||
|
import io.opentelemetry.sdk.metrics.export.MetricReader;
|
||||||
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
|
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
|
||||||
import io.opentelemetry.sdk.metrics.internal.view.AttributesProcessor;
|
import io.opentelemetry.sdk.metrics.internal.view.AttributesProcessor;
|
||||||
import io.opentelemetry.sdk.metrics.internal.view.StringPredicates;
|
import io.opentelemetry.sdk.metrics.internal.view.StringPredicates;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
@ -26,7 +28,7 @@ public final class SdkMeterProviderUtil {
|
||||||
/**
|
/**
|
||||||
* Reflectively assign the {@link ExemplarFilter} to the {@link SdkMeterProviderBuilder}.
|
* Reflectively assign the {@link ExemplarFilter} to the {@link SdkMeterProviderBuilder}.
|
||||||
*
|
*
|
||||||
* @param sdkMeterProviderBuilder the
|
* @param sdkMeterProviderBuilder the builder
|
||||||
*/
|
*/
|
||||||
public static void setExemplarFilter(
|
public static void setExemplarFilter(
|
||||||
SdkMeterProviderBuilder sdkMeterProviderBuilder, ExemplarFilter exemplarFilter) {
|
SdkMeterProviderBuilder sdkMeterProviderBuilder, ExemplarFilter exemplarFilter) {
|
||||||
|
|
@ -42,6 +44,28 @@ public final class SdkMeterProviderUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflectively add a {@link MetricReader} with the {@link CardinalityLimitSelector} to the {@link
|
||||||
|
* SdkMeterProviderBuilder}.
|
||||||
|
*
|
||||||
|
* @param sdkMeterProviderBuilder the builder
|
||||||
|
*/
|
||||||
|
public static void registerMetricReaderWithCardinalitySelector(
|
||||||
|
SdkMeterProviderBuilder sdkMeterProviderBuilder,
|
||||||
|
MetricReader metricReader,
|
||||||
|
CardinalityLimitSelector cardinalityLimitSelector) {
|
||||||
|
try {
|
||||||
|
Method method =
|
||||||
|
SdkMeterProviderBuilder.class.getDeclaredMethod(
|
||||||
|
"registerMetricReader", MetricReader.class, CardinalityLimitSelector.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
method.invoke(sdkMeterProviderBuilder, metricReader, cardinalityLimitSelector);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Error calling addMetricReader on SdkMeterProviderBuilder", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reflectively add an {@link AttributesProcessor} to the {@link ViewBuilder} which appends
|
* Reflectively add an {@link AttributesProcessor} to the {@link ViewBuilder} which appends
|
||||||
* key-values from baggage to all measurements.
|
* key-values from baggage to all measurements.
|
||||||
|
|
@ -81,6 +105,21 @@ public final class SdkMeterProviderUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflectively set the {@code cardinalityLimit} on the {@link ViewBuilder}.
|
||||||
|
*
|
||||||
|
* @param viewBuilder the builder
|
||||||
|
*/
|
||||||
|
public static void setCardinalityLimit(ViewBuilder viewBuilder, int cardinalityLimit) {
|
||||||
|
try {
|
||||||
|
Method method = ViewBuilder.class.getDeclaredMethod("setCardinalityLimit", int.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
method.invoke(viewBuilder, cardinalityLimit);
|
||||||
|
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new IllegalStateException("Error setting cardinalityLimit on ViewBuilder", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Reflectively reset the {@link SdkMeterProvider}, clearing all registered instruments. */
|
/** Reflectively reset the {@link SdkMeterProvider}, clearing all registered instruments. */
|
||||||
public static void resetForTest(SdkMeterProvider sdkMeterProvider) {
|
public static void resetForTest(SdkMeterProvider sdkMeterProvider) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.sdk.metrics.internal.export;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.metrics.InstrumentType;
|
||||||
|
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
|
||||||
|
import io.opentelemetry.sdk.metrics.export.MetricReader;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize the {@link io.opentelemetry.sdk.metrics.export.MetricReader} cardinality limit as a
|
||||||
|
* function of {@link InstrumentType}. Register via {@link
|
||||||
|
* SdkMeterProviderUtil#registerMetricReaderWithCardinalitySelector(SdkMeterProviderBuilder,
|
||||||
|
* MetricReader, CardinalityLimitSelector)}.
|
||||||
|
*
|
||||||
|
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
|
||||||
|
* at any time.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface CardinalityLimitSelector {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default {@link CardinalityLimitSelector}, allowing each metric to have {@code 2000} points.
|
||||||
|
*/
|
||||||
|
static CardinalityLimitSelector defaultCardinalityLimitSelector() {
|
||||||
|
return unused -> MetricStorage.DEFAULT_MAX_CARDINALITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the default cardinality limit for metrics from instruments of type {@code
|
||||||
|
* instrumentType}. The cardinality limit dictates the maximum number of distinct points (or time
|
||||||
|
* series) for the metric.
|
||||||
|
*/
|
||||||
|
int getCardinalityLimit(InstrumentType instrumentType);
|
||||||
|
}
|
||||||
|
|
@ -46,6 +46,7 @@ final class AsynchronousMetricStorage<T extends PointData, U extends ExemplarDat
|
||||||
private final AggregationTemporality aggregationTemporality;
|
private final AggregationTemporality aggregationTemporality;
|
||||||
private final Aggregator<T, U> aggregator;
|
private final Aggregator<T, U> aggregator;
|
||||||
private final AttributesProcessor attributesProcessor;
|
private final AttributesProcessor attributesProcessor;
|
||||||
|
private final int maxCardinality;
|
||||||
private Map<Attributes, T> points = new HashMap<>();
|
private Map<Attributes, T> points = new HashMap<>();
|
||||||
private Map<Attributes, T> lastPoints =
|
private Map<Attributes, T> lastPoints =
|
||||||
new HashMap<>(); // Only populated if aggregationTemporality == DELTA
|
new HashMap<>(); // Only populated if aggregationTemporality == DELTA
|
||||||
|
|
@ -54,7 +55,8 @@ final class AsynchronousMetricStorage<T extends PointData, U extends ExemplarDat
|
||||||
RegisteredReader registeredReader,
|
RegisteredReader registeredReader,
|
||||||
MetricDescriptor metricDescriptor,
|
MetricDescriptor metricDescriptor,
|
||||||
Aggregator<T, U> aggregator,
|
Aggregator<T, U> aggregator,
|
||||||
AttributesProcessor attributesProcessor) {
|
AttributesProcessor attributesProcessor,
|
||||||
|
int maxCardinality) {
|
||||||
this.registeredReader = registeredReader;
|
this.registeredReader = registeredReader;
|
||||||
this.metricDescriptor = metricDescriptor;
|
this.metricDescriptor = metricDescriptor;
|
||||||
this.aggregationTemporality =
|
this.aggregationTemporality =
|
||||||
|
|
@ -63,6 +65,7 @@ final class AsynchronousMetricStorage<T extends PointData, U extends ExemplarDat
|
||||||
.getAggregationTemporality(metricDescriptor.getSourceInstrument().getType());
|
.getAggregationTemporality(metricDescriptor.getSourceInstrument().getType());
|
||||||
this.aggregator = aggregator;
|
this.aggregator = aggregator;
|
||||||
this.attributesProcessor = attributesProcessor;
|
this.attributesProcessor = attributesProcessor;
|
||||||
|
this.maxCardinality = maxCardinality;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -83,7 +86,8 @@ final class AsynchronousMetricStorage<T extends PointData, U extends ExemplarDat
|
||||||
registeredReader,
|
registeredReader,
|
||||||
metricDescriptor,
|
metricDescriptor,
|
||||||
aggregator,
|
aggregator,
|
||||||
registeredView.getViewAttributesProcessor());
|
registeredView.getViewAttributesProcessor(),
|
||||||
|
registeredView.getCardinalityLimit());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -109,13 +113,13 @@ final class AsynchronousMetricStorage<T extends PointData, U extends ExemplarDat
|
||||||
private void recordPoint(T point) {
|
private void recordPoint(T point) {
|
||||||
Attributes attributes = point.getAttributes();
|
Attributes attributes = point.getAttributes();
|
||||||
|
|
||||||
if (points.size() >= MetricStorage.MAX_CARDINALITY) {
|
if (points.size() >= maxCardinality) {
|
||||||
throttlingLogger.log(
|
throttlingLogger.log(
|
||||||
Level.WARNING,
|
Level.WARNING,
|
||||||
"Instrument "
|
"Instrument "
|
||||||
+ metricDescriptor.getSourceInstrument().getName()
|
+ metricDescriptor.getSourceInstrument().getName()
|
||||||
+ " has exceeded the maximum allowed cardinality ("
|
+ " has exceeded the maximum allowed cardinality ("
|
||||||
+ MetricStorage.MAX_CARDINALITY
|
+ maxCardinality
|
||||||
+ ").");
|
+ ").");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ public final class DefaultSynchronousMetricStorage<T extends PointData, U extend
|
||||||
private final ConcurrentHashMap<Attributes, AggregatorHandle<T, U>> aggregatorHandles =
|
private final ConcurrentHashMap<Attributes, AggregatorHandle<T, U>> aggregatorHandles =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
private final AttributesProcessor attributesProcessor;
|
private final AttributesProcessor attributesProcessor;
|
||||||
|
private final int maxCardinality;
|
||||||
private final ConcurrentLinkedQueue<AggregatorHandle<T, U>> aggregatorHandlePool =
|
private final ConcurrentLinkedQueue<AggregatorHandle<T, U>> aggregatorHandlePool =
|
||||||
new ConcurrentLinkedQueue<>();
|
new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
|
@ -57,7 +58,8 @@ public final class DefaultSynchronousMetricStorage<T extends PointData, U extend
|
||||||
RegisteredReader registeredReader,
|
RegisteredReader registeredReader,
|
||||||
MetricDescriptor metricDescriptor,
|
MetricDescriptor metricDescriptor,
|
||||||
Aggregator<T, U> aggregator,
|
Aggregator<T, U> aggregator,
|
||||||
AttributesProcessor attributesProcessor) {
|
AttributesProcessor attributesProcessor,
|
||||||
|
int maxCardinality) {
|
||||||
this.registeredReader = registeredReader;
|
this.registeredReader = registeredReader;
|
||||||
this.metricDescriptor = metricDescriptor;
|
this.metricDescriptor = metricDescriptor;
|
||||||
this.aggregationTemporality =
|
this.aggregationTemporality =
|
||||||
|
|
@ -66,6 +68,7 @@ public final class DefaultSynchronousMetricStorage<T extends PointData, U extend
|
||||||
.getAggregationTemporality(metricDescriptor.getSourceInstrument().getType());
|
.getAggregationTemporality(metricDescriptor.getSourceInstrument().getType());
|
||||||
this.aggregator = aggregator;
|
this.aggregator = aggregator;
|
||||||
this.attributesProcessor = attributesProcessor;
|
this.attributesProcessor = attributesProcessor;
|
||||||
|
this.maxCardinality = maxCardinality;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing
|
// Visible for testing
|
||||||
|
|
@ -97,13 +100,13 @@ public final class DefaultSynchronousMetricStorage<T extends PointData, U extend
|
||||||
if (handle != null) {
|
if (handle != null) {
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
if (aggregatorHandles.size() >= MAX_CARDINALITY) {
|
if (aggregatorHandles.size() >= maxCardinality) {
|
||||||
logger.log(
|
logger.log(
|
||||||
Level.WARNING,
|
Level.WARNING,
|
||||||
"Instrument "
|
"Instrument "
|
||||||
+ metricDescriptor.getSourceInstrument().getName()
|
+ metricDescriptor.getSourceInstrument().getName()
|
||||||
+ " has exceeded the maximum allowed cardinality ("
|
+ " has exceeded the maximum allowed cardinality ("
|
||||||
+ MAX_CARDINALITY
|
+ maxCardinality
|
||||||
+ ").");
|
+ ").");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -143,9 +146,9 @@ public final class DefaultSynchronousMetricStorage<T extends PointData, U extend
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trim pool down if needed. pool.size() will only exceed MAX_CARDINALITY if new handles are
|
// Trim pool down if needed. pool.size() will only exceed maxCardinality if new handles are
|
||||||
// created during collection.
|
// created during collection.
|
||||||
int toDelete = aggregatorHandlePool.size() - MAX_CARDINALITY;
|
int toDelete = aggregatorHandlePool.size() - maxCardinality;
|
||||||
for (int i = 0; i < toDelete; i++) {
|
for (int i = 0; i < toDelete; i++) {
|
||||||
aggregatorHandlePool.poll();
|
aggregatorHandlePool.poll();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import io.opentelemetry.sdk.resources.Resource;
|
||||||
*/
|
*/
|
||||||
public interface MetricStorage {
|
public interface MetricStorage {
|
||||||
|
|
||||||
/** The max number of distinct metric points for a particular {@link MetricStorage}. */
|
/** The default max number of distinct metric points for a particular {@link MetricStorage}. */
|
||||||
int MAX_CARDINALITY = 2000;
|
int DEFAULT_MAX_CARDINALITY = 2000;
|
||||||
|
|
||||||
/** Returns a description of the metric produced in this storage. */
|
/** Returns a description of the metric produced in this storage. */
|
||||||
MetricDescriptor getMetricDescriptor();
|
MetricDescriptor getMetricDescriptor();
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ public interface SynchronousMetricStorage extends MetricStorage, WriteableMetric
|
||||||
registeredReader,
|
registeredReader,
|
||||||
metricDescriptor,
|
metricDescriptor,
|
||||||
aggregator,
|
aggregator,
|
||||||
registeredView.getViewAttributesProcessor());
|
registeredView.getViewAttributesProcessor(),
|
||||||
|
registeredView.getCardinalityLimit());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,10 @@ public abstract class RegisteredView {
|
||||||
InstrumentSelector selector,
|
InstrumentSelector selector,
|
||||||
View view,
|
View view,
|
||||||
AttributesProcessor viewAttributesProcessor,
|
AttributesProcessor viewAttributesProcessor,
|
||||||
|
int cardinalityLimit,
|
||||||
SourceInfo viewSourceInfo) {
|
SourceInfo viewSourceInfo) {
|
||||||
return new AutoValue_RegisteredView(selector, view, viewAttributesProcessor, viewSourceInfo);
|
return new AutoValue_RegisteredView(
|
||||||
|
selector, view, viewAttributesProcessor, cardinalityLimit, viewSourceInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisteredView() {}
|
RegisteredView() {}
|
||||||
|
|
@ -40,6 +42,9 @@ public abstract class RegisteredView {
|
||||||
/** The view's {@link AttributesProcessor}. */
|
/** The view's {@link AttributesProcessor}. */
|
||||||
public abstract AttributesProcessor getViewAttributesProcessor();
|
public abstract AttributesProcessor getViewAttributesProcessor();
|
||||||
|
|
||||||
|
/** The view's cardinality limit. */
|
||||||
|
public abstract int getCardinalityLimit();
|
||||||
|
|
||||||
/** The {@link SourceInfo} from where the view was registered. */
|
/** The {@link SourceInfo} from where the view was registered. */
|
||||||
public abstract SourceInfo getViewSourceInfo();
|
public abstract SourceInfo getViewSourceInfo();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;
|
||||||
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorFactory;
|
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorFactory;
|
||||||
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
|
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
|
||||||
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
|
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -45,6 +47,7 @@ public final class ViewRegistry {
|
||||||
InstrumentSelector.builder().setName("*").build(),
|
InstrumentSelector.builder().setName("*").build(),
|
||||||
DEFAULT_VIEW,
|
DEFAULT_VIEW,
|
||||||
NOOP,
|
NOOP,
|
||||||
|
MetricStorage.DEFAULT_MAX_CARDINALITY,
|
||||||
SourceInfo.noSourceInfo());
|
SourceInfo.noSourceInfo());
|
||||||
private static final Logger logger = Logger.getLogger(ViewRegistry.class.getName());
|
private static final Logger logger = Logger.getLogger(ViewRegistry.class.getName());
|
||||||
|
|
||||||
|
|
@ -52,7 +55,9 @@ public final class ViewRegistry {
|
||||||
private final List<RegisteredView> registeredViews;
|
private final List<RegisteredView> registeredViews;
|
||||||
|
|
||||||
ViewRegistry(
|
ViewRegistry(
|
||||||
DefaultAggregationSelector defaultAggregationSelector, List<RegisteredView> registeredViews) {
|
DefaultAggregationSelector defaultAggregationSelector,
|
||||||
|
CardinalityLimitSelector cardinalityLimitSelector,
|
||||||
|
List<RegisteredView> registeredViews) {
|
||||||
instrumentDefaultRegisteredView = new HashMap<>();
|
instrumentDefaultRegisteredView = new HashMap<>();
|
||||||
for (InstrumentType instrumentType : InstrumentType.values()) {
|
for (InstrumentType instrumentType : InstrumentType.values()) {
|
||||||
instrumentDefaultRegisteredView.put(
|
instrumentDefaultRegisteredView.put(
|
||||||
|
|
@ -63,6 +68,7 @@ public final class ViewRegistry {
|
||||||
.setAggregation(defaultAggregationSelector.getDefaultAggregation(instrumentType))
|
.setAggregation(defaultAggregationSelector.getDefaultAggregation(instrumentType))
|
||||||
.build(),
|
.build(),
|
||||||
AttributesProcessor.noop(),
|
AttributesProcessor.noop(),
|
||||||
|
cardinalityLimitSelector.getCardinalityLimit(instrumentType),
|
||||||
SourceInfo.noSourceInfo()));
|
SourceInfo.noSourceInfo()));
|
||||||
}
|
}
|
||||||
this.registeredViews = registeredViews;
|
this.registeredViews = registeredViews;
|
||||||
|
|
@ -70,13 +76,19 @@ public final class ViewRegistry {
|
||||||
|
|
||||||
/** Returns a {@link ViewRegistry}. */
|
/** Returns a {@link ViewRegistry}. */
|
||||||
public static ViewRegistry create(
|
public static ViewRegistry create(
|
||||||
DefaultAggregationSelector defaultAggregationSelector, List<RegisteredView> registeredViews) {
|
DefaultAggregationSelector defaultAggregationSelector,
|
||||||
return new ViewRegistry(defaultAggregationSelector, new ArrayList<>(registeredViews));
|
CardinalityLimitSelector cardinalityLimitSelector,
|
||||||
|
List<RegisteredView> registeredViews) {
|
||||||
|
return new ViewRegistry(
|
||||||
|
defaultAggregationSelector, cardinalityLimitSelector, new ArrayList<>(registeredViews));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return a {@link ViewRegistry} using the default aggregation and no views registered. */
|
/** Return a {@link ViewRegistry} using the default aggregation and no views registered. */
|
||||||
public static ViewRegistry create() {
|
public static ViewRegistry create() {
|
||||||
return create(unused -> Aggregation.defaultAggregation(), Collections.emptyList());
|
return create(
|
||||||
|
unused -> Aggregation.defaultAggregation(),
|
||||||
|
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
|
||||||
|
Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -113,7 +125,7 @@ public final class ViewRegistry {
|
||||||
return Collections.unmodifiableList(result);
|
return Collections.unmodifiableList(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not views matched, use default view
|
// No views matched, use default view
|
||||||
RegisteredView instrumentDefaultView =
|
RegisteredView instrumentDefaultView =
|
||||||
Objects.requireNonNull(instrumentDefaultRegisteredView.get(descriptor.getType()));
|
Objects.requireNonNull(instrumentDefaultRegisteredView.get(descriptor.getType()));
|
||||||
AggregatorFactory viewAggregatorFactory =
|
AggregatorFactory viewAggregatorFactory =
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,24 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.metrics;
|
package io.opentelemetry.sdk.metrics;
|
||||||
|
|
||||||
|
import static io.opentelemetry.sdk.metrics.internal.state.MetricStorage.DEFAULT_MAX_CARDINALITY;
|
||||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
|
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.common.AttributeKey;
|
||||||
import io.opentelemetry.api.common.Attributes;
|
import io.opentelemetry.api.common.Attributes;
|
||||||
import io.opentelemetry.api.metrics.LongCounter;
|
import io.opentelemetry.api.metrics.LongCounter;
|
||||||
|
import io.opentelemetry.api.metrics.LongHistogram;
|
||||||
import io.opentelemetry.api.metrics.Meter;
|
import io.opentelemetry.api.metrics.Meter;
|
||||||
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
|
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
|
||||||
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
|
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
|
||||||
|
import io.opentelemetry.sdk.metrics.data.Data;
|
||||||
import io.opentelemetry.sdk.metrics.data.LongPointData;
|
import io.opentelemetry.sdk.metrics.data.LongPointData;
|
||||||
import io.opentelemetry.sdk.metrics.data.SumData;
|
import io.opentelemetry.sdk.metrics.data.SumData;
|
||||||
|
import io.opentelemetry.sdk.metrics.export.MetricReader;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
|
||||||
import io.opentelemetry.sdk.metrics.internal.state.DefaultSynchronousMetricStorage;
|
import io.opentelemetry.sdk.metrics.internal.state.DefaultSynchronousMetricStorage;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
|
||||||
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
|
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
@ -26,9 +34,6 @@ import org.junit.jupiter.api.Test;
|
||||||
@SuppressLogger(DefaultSynchronousMetricStorage.class)
|
@SuppressLogger(DefaultSynchronousMetricStorage.class)
|
||||||
class CardinalityTest {
|
class CardinalityTest {
|
||||||
|
|
||||||
/** Traces {@code MetricStorageUtils#MAX_CARDINALITY}. */
|
|
||||||
private static final int MAX_CARDINALITY = 2000;
|
|
||||||
|
|
||||||
private InMemoryMetricReader deltaReader;
|
private InMemoryMetricReader deltaReader;
|
||||||
private InMemoryMetricReader cumulativeReader;
|
private InMemoryMetricReader cumulativeReader;
|
||||||
private Meter meter;
|
private Meter meter;
|
||||||
|
|
@ -50,14 +55,14 @@ class CardinalityTest {
|
||||||
* are dropped for delta and cumulative readers. Stale metrics are those with attributes that did
|
* are dropped for delta and cumulative readers. Stale metrics are those with attributes that did
|
||||||
* not receive recordings in the most recent collection.
|
* not receive recordings in the most recent collection.
|
||||||
*
|
*
|
||||||
* <p>Effectively, we make sure we cap-out at attribute size = 2000 (constant in
|
* <p>Effectively, we make sure we cap-out at attribute size = {@link
|
||||||
* MetricStorageutils).
|
* MetricStorage#DEFAULT_MAX_CARDINALITY}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void staleMetricsDropped_synchronousInstrument() {
|
void staleMetricsDropped_synchronousInstrument() {
|
||||||
LongCounter syncCounter = meter.counterBuilder("sync-counter").build();
|
LongCounter syncCounter = meter.counterBuilder("sync-counter").build();
|
||||||
// Note: This constant comes from MetricStorageUtils, but it's package-private.
|
// Note: This constant comes from MetricStorageUtils, but it's package-private.
|
||||||
for (int i = 1; i <= 2000; i++) {
|
for (int i = 1; i <= DEFAULT_MAX_CARDINALITY; i++) {
|
||||||
syncCounter.add(1, Attributes.builder().put("key", "num_" + i).build());
|
syncCounter.add(1, Attributes.builder().put("key", "num_" + i).build());
|
||||||
|
|
||||||
// DELTA reader only has latest
|
// DELTA reader only has latest
|
||||||
|
|
@ -87,7 +92,7 @@ class CardinalityTest {
|
||||||
.isEqualTo(currentSize))));
|
.isEqualTo(currentSize))));
|
||||||
}
|
}
|
||||||
// Now punch the limit and ONLY metrics we just recorded stay, due to simplistic GC.
|
// Now punch the limit and ONLY metrics we just recorded stay, due to simplistic GC.
|
||||||
for (int i = 2001; i <= 2010; i++) {
|
for (int i = DEFAULT_MAX_CARDINALITY + 1; i <= DEFAULT_MAX_CARDINALITY + 10; i++) {
|
||||||
syncCounter.add(1, Attributes.builder().put("key", "num_" + i).build());
|
syncCounter.add(1, Attributes.builder().put("key", "num_" + i).build());
|
||||||
}
|
}
|
||||||
assertThat(deltaReader.collectAllMetrics())
|
assertThat(deltaReader.collectAllMetrics())
|
||||||
|
|
@ -118,7 +123,7 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(2000))));
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -165,7 +170,7 @@ class CardinalityTest {
|
||||||
void cardinalityLimits_synchronousInstrument() {
|
void cardinalityLimits_synchronousInstrument() {
|
||||||
LongCounter syncCounter1 = meter.counterBuilder("sync-counter1").build();
|
LongCounter syncCounter1 = meter.counterBuilder("sync-counter1").build();
|
||||||
LongCounter syncCounter2 = meter.counterBuilder("sync-counter2").build();
|
LongCounter syncCounter2 = meter.counterBuilder("sync-counter2").build();
|
||||||
for (int i = 0; i < MAX_CARDINALITY + 1; i++) {
|
for (int i = 0; i < DEFAULT_MAX_CARDINALITY + 1; i++) {
|
||||||
syncCounter1.add(1, Attributes.builder().put("key", "value" + i).build());
|
syncCounter1.add(1, Attributes.builder().put("key", "value" + i).build());
|
||||||
syncCounter2.add(1, Attributes.builder().put("key", "value" + i).build());
|
syncCounter2.add(1, Attributes.builder().put("key", "value" + i).build());
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +188,7 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(MAX_CARDINALITY))),
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))),
|
||||||
metricData ->
|
metricData ->
|
||||||
assertThat(metricData)
|
assertThat(metricData)
|
||||||
.hasName("sync-counter2")
|
.hasName("sync-counter2")
|
||||||
|
|
@ -194,7 +199,7 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(MAX_CARDINALITY))));
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))));
|
||||||
|
|
||||||
assertThat(cumulativeReader.collectAllMetrics())
|
assertThat(cumulativeReader.collectAllMetrics())
|
||||||
.as("Cumulative collection")
|
.as("Cumulative collection")
|
||||||
|
|
@ -209,7 +214,7 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(MAX_CARDINALITY))),
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))),
|
||||||
metricData ->
|
metricData ->
|
||||||
assertThat(metricData)
|
assertThat(metricData)
|
||||||
.hasName("sync-counter2")
|
.hasName("sync-counter2")
|
||||||
|
|
@ -220,7 +225,7 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(MAX_CARDINALITY))));
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -231,7 +236,7 @@ class CardinalityTest {
|
||||||
void cardinalityLimits_asynchronousInstrument() {
|
void cardinalityLimits_asynchronousInstrument() {
|
||||||
Consumer<ObservableLongMeasurement> callback =
|
Consumer<ObservableLongMeasurement> callback =
|
||||||
measurement -> {
|
measurement -> {
|
||||||
for (int i = 0; i < MAX_CARDINALITY + 1; i++) {
|
for (int i = 0; i < DEFAULT_MAX_CARDINALITY + 1; i++) {
|
||||||
measurement.record(1, Attributes.builder().put("key", "value" + i).build());
|
measurement.record(1, Attributes.builder().put("key", "value" + i).build());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -251,7 +256,7 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(MAX_CARDINALITY))),
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))),
|
||||||
metricData ->
|
metricData ->
|
||||||
assertThat(metricData)
|
assertThat(metricData)
|
||||||
.hasName("async-counter2")
|
.hasName("async-counter2")
|
||||||
|
|
@ -262,7 +267,7 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(MAX_CARDINALITY))));
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))));
|
||||||
|
|
||||||
assertThat(cumulativeReader.collectAllMetrics())
|
assertThat(cumulativeReader.collectAllMetrics())
|
||||||
.as("Cumulative collection")
|
.as("Cumulative collection")
|
||||||
|
|
@ -277,7 +282,7 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(MAX_CARDINALITY))),
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))),
|
||||||
metricData ->
|
metricData ->
|
||||||
assertThat(metricData)
|
assertThat(metricData)
|
||||||
.hasName("async-counter2")
|
.hasName("async-counter2")
|
||||||
|
|
@ -288,6 +293,213 @@ class CardinalityTest {
|
||||||
(Consumer<SumData<LongPointData>>)
|
(Consumer<SumData<LongPointData>>)
|
||||||
sumPointData ->
|
sumPointData ->
|
||||||
assertThat(sumPointData.getPoints().size())
|
assertThat(sumPointData.getPoints().size())
|
||||||
.isEqualTo(MAX_CARDINALITY))));
|
.isEqualTo(DEFAULT_MAX_CARDINALITY))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate ability to customize metric reader cardinality limits via {@link
|
||||||
|
* SdkMeterProviderBuilder#registerMetricReader(MetricReader, CardinalityLimitSelector)}, and view
|
||||||
|
* cardinality limits via {@link ViewBuilder#setCardinalityLimit(int)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void readerAndViewCardinalityConfiguration() {
|
||||||
|
int counterLimit = 10;
|
||||||
|
int generalLimit = 20;
|
||||||
|
int counter2Limit = 30;
|
||||||
|
|
||||||
|
// Define a cardinality selector which has one limit for counters, and another general limit for
|
||||||
|
// other instrument kinds
|
||||||
|
CardinalityLimitSelector cardinalityLimitSelector =
|
||||||
|
instrumentType -> instrumentType == InstrumentType.COUNTER ? counterLimit : generalLimit;
|
||||||
|
SdkMeterProviderBuilder builder = SdkMeterProvider.builder();
|
||||||
|
|
||||||
|
// Register both the delta and cumulative reader with the customized cardinality selector
|
||||||
|
SdkMeterProviderUtil.registerMetricReaderWithCardinalitySelector(
|
||||||
|
builder, deltaReader, cardinalityLimitSelector);
|
||||||
|
SdkMeterProviderUtil.registerMetricReaderWithCardinalitySelector(
|
||||||
|
builder, cumulativeReader, cardinalityLimitSelector);
|
||||||
|
|
||||||
|
// Register a view which defines a custom cardinality limit for instrumented named "counter2"
|
||||||
|
ViewBuilder viewBuilder = View.builder();
|
||||||
|
SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, counter2Limit);
|
||||||
|
builder.registerView(
|
||||||
|
InstrumentSelector.builder().setName("counter2").build(), viewBuilder.build());
|
||||||
|
|
||||||
|
SdkMeterProvider sdkMeterProvider = builder.build();
|
||||||
|
meter = sdkMeterProvider.get(CardinalityTest.class.getName());
|
||||||
|
|
||||||
|
LongCounter counter1 = meter.counterBuilder("counter1").build();
|
||||||
|
LongCounter counter2 = meter.counterBuilder("counter2").build();
|
||||||
|
LongHistogram histogram = meter.histogramBuilder("histogram").ofLongs().build();
|
||||||
|
|
||||||
|
// Record enough measurements to exceed cardinality threshold
|
||||||
|
for (int i = 0; i < DEFAULT_MAX_CARDINALITY; i++) {
|
||||||
|
counter1.add(1, Attributes.builder().put("key", i).build());
|
||||||
|
counter2.add(1, Attributes.builder().put("key", i).build());
|
||||||
|
histogram.record(1, Attributes.builder().put("key", i).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that each instrument has the appropriate number of points based on cardinality limits:
|
||||||
|
// - counter1 should have counterLimit points
|
||||||
|
// - counter2 should have counter2Limit points
|
||||||
|
// - histogram should have generalLimit points
|
||||||
|
assertThat(deltaReader.collectAllMetrics())
|
||||||
|
.as("delta collection")
|
||||||
|
.satisfiesExactlyInAnyOrder(
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("counter1")
|
||||||
|
.hasLongSumSatisfying(
|
||||||
|
sum ->
|
||||||
|
sum.isDelta()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, counterLimit, 0, counterLimit))),
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("counter2")
|
||||||
|
.hasLongSumSatisfying(
|
||||||
|
sum ->
|
||||||
|
sum.isDelta()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, counter2Limit, 0, counter2Limit))),
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("histogram")
|
||||||
|
.hasHistogramSatisfying(
|
||||||
|
histogramMetric ->
|
||||||
|
histogramMetric
|
||||||
|
.isDelta()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, generalLimit, 0, generalLimit))));
|
||||||
|
assertThat(cumulativeReader.collectAllMetrics())
|
||||||
|
.as("cumulative collection")
|
||||||
|
.satisfiesExactlyInAnyOrder(
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("counter1")
|
||||||
|
.hasLongSumSatisfying(
|
||||||
|
sum ->
|
||||||
|
sum.isCumulative()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, counterLimit, 0, counterLimit))),
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("counter2")
|
||||||
|
.hasLongSumSatisfying(
|
||||||
|
sum ->
|
||||||
|
sum.isCumulative()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, counter2Limit, 0, counter2Limit))),
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("histogram")
|
||||||
|
.hasHistogramSatisfying(
|
||||||
|
histogramMetric ->
|
||||||
|
histogramMetric
|
||||||
|
.isCumulative()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, generalLimit, 0, generalLimit))));
|
||||||
|
|
||||||
|
// Record another round of measurements, again exceeding cardinality limits
|
||||||
|
for (int i = DEFAULT_MAX_CARDINALITY; i < DEFAULT_MAX_CARDINALITY * 2; i++) {
|
||||||
|
counter1.add(1, Attributes.builder().put("key", i).build());
|
||||||
|
counter2.add(1, Attributes.builder().put("key", i).build());
|
||||||
|
histogram.record(1, Attributes.builder().put("key", i).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delta reader should have new points, forgetting the points with measurements recorded prior
|
||||||
|
// to last collection
|
||||||
|
assertThat(deltaReader.collectAllMetrics())
|
||||||
|
.as("delta collection")
|
||||||
|
.satisfiesExactlyInAnyOrder(
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("counter1")
|
||||||
|
.hasLongSumSatisfying(
|
||||||
|
sum ->
|
||||||
|
sum.isDelta()
|
||||||
|
.satisfies(
|
||||||
|
data ->
|
||||||
|
pointsAssert(
|
||||||
|
data,
|
||||||
|
counterLimit,
|
||||||
|
DEFAULT_MAX_CARDINALITY,
|
||||||
|
DEFAULT_MAX_CARDINALITY + counterLimit))),
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("counter2")
|
||||||
|
.hasLongSumSatisfying(
|
||||||
|
sum ->
|
||||||
|
sum.isDelta()
|
||||||
|
.satisfies(
|
||||||
|
data ->
|
||||||
|
pointsAssert(
|
||||||
|
data,
|
||||||
|
counter2Limit,
|
||||||
|
DEFAULT_MAX_CARDINALITY,
|
||||||
|
DEFAULT_MAX_CARDINALITY + counter2Limit))),
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("histogram")
|
||||||
|
.hasHistogramSatisfying(
|
||||||
|
histogramMetric ->
|
||||||
|
histogramMetric
|
||||||
|
.isDelta()
|
||||||
|
.satisfies(
|
||||||
|
data ->
|
||||||
|
pointsAssert(
|
||||||
|
data,
|
||||||
|
generalLimit,
|
||||||
|
DEFAULT_MAX_CARDINALITY,
|
||||||
|
DEFAULT_MAX_CARDINALITY + generalLimit))));
|
||||||
|
|
||||||
|
// Cumulative reader should retain old points, dropping the new measurements
|
||||||
|
assertThat(cumulativeReader.collectAllMetrics())
|
||||||
|
.as("cumulative collection")
|
||||||
|
.satisfiesExactlyInAnyOrder(
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("counter1")
|
||||||
|
.hasLongSumSatisfying(
|
||||||
|
sum ->
|
||||||
|
sum.isCumulative()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, counterLimit, 0, counterLimit))),
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("counter2")
|
||||||
|
.hasLongSumSatisfying(
|
||||||
|
sum ->
|
||||||
|
sum.isCumulative()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, counter2Limit, 0, counter2Limit))),
|
||||||
|
metricData ->
|
||||||
|
assertThat(metricData)
|
||||||
|
.hasName("histogram")
|
||||||
|
.hasHistogramSatisfying(
|
||||||
|
histogramMetric ->
|
||||||
|
histogramMetric
|
||||||
|
.isCumulative()
|
||||||
|
.satisfies(
|
||||||
|
data -> pointsAssert(data, generalLimit, 0, generalLimit))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for {@link #readerAndViewCardinalityConfiguration()}. Asserts that the {@code
|
||||||
|
* data} contains the {@code expectedNumPoints}, and has the attribute "key" values in the range
|
||||||
|
* [{@code minAttributeValueInclusive}, {@code maxAttributeValueExclusive}).
|
||||||
|
*/
|
||||||
|
private static void pointsAssert(
|
||||||
|
Data<?> data,
|
||||||
|
int expectedNumPoints,
|
||||||
|
int minAttributeValueInclusive,
|
||||||
|
int maxAttributeValueExclusive) {
|
||||||
|
assertThat(data.getPoints())
|
||||||
|
.hasSize(expectedNumPoints)
|
||||||
|
.allSatisfy(
|
||||||
|
point ->
|
||||||
|
assertThat(point.getAttributes().get(AttributeKey.longKey("key")))
|
||||||
|
.isGreaterThanOrEqualTo(minAttributeValueInclusive)
|
||||||
|
.isLessThan(maxAttributeValueExclusive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,17 @@ class ViewTest {
|
||||||
void stringRepresentation() {
|
void stringRepresentation() {
|
||||||
assertThat(View.builder().build().toString())
|
assertThat(View.builder().build().toString())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
"View{aggregation=DefaultAggregation, attributesProcessor=NoopAttributesProcessor{}}");
|
"View{"
|
||||||
|
+ "aggregation=DefaultAggregation, "
|
||||||
|
+ "attributesProcessor=NoopAttributesProcessor{}, "
|
||||||
|
+ "cardinalityLimit=2000"
|
||||||
|
+ "}");
|
||||||
assertThat(
|
assertThat(
|
||||||
View.builder()
|
View.builder()
|
||||||
.setName("name")
|
.setName("name")
|
||||||
.setDescription("description")
|
.setDescription("description")
|
||||||
.setAggregation(Aggregation.sum())
|
.setAggregation(Aggregation.sum())
|
||||||
|
.setCardinalityLimit(10)
|
||||||
.build()
|
.build()
|
||||||
.toString())
|
.toString())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
|
|
@ -28,7 +33,8 @@ class ViewTest {
|
||||||
+ "name=name, "
|
+ "name=name, "
|
||||||
+ "description=description, "
|
+ "description=description, "
|
||||||
+ "aggregation=SumAggregation, "
|
+ "aggregation=SumAggregation, "
|
||||||
+ "attributesProcessor=NoopAttributesProcessor{}"
|
+ "attributesProcessor=NoopAttributesProcessor{}, "
|
||||||
|
+ "cardinalityLimit=10"
|
||||||
+ "}");
|
+ "}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class AsynchronousMetricStorageTest {
|
class AsynchronousMetricStorageTest {
|
||||||
|
|
||||||
|
private static final int CARDINALITY_LIMIT = 25;
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
LogCapturer logs = LogCapturer.create().captureForType(AsynchronousMetricStorage.class);
|
LogCapturer logs = LogCapturer.create().captureForType(AsynchronousMetricStorage.class);
|
||||||
|
|
||||||
|
|
@ -51,7 +53,11 @@ class AsynchronousMetricStorageTest {
|
||||||
private final InstrumentSelector selector = InstrumentSelector.builder().setName("*").build();
|
private final InstrumentSelector selector = InstrumentSelector.builder().setName("*").build();
|
||||||
private final RegisteredView registeredView =
|
private final RegisteredView registeredView =
|
||||||
RegisteredView.create(
|
RegisteredView.create(
|
||||||
selector, View.builder().build(), AttributesProcessor.noop(), SourceInfo.noSourceInfo());
|
selector,
|
||||||
|
View.builder().build(),
|
||||||
|
AttributesProcessor.noop(),
|
||||||
|
CARDINALITY_LIMIT,
|
||||||
|
SourceInfo.noSourceInfo());
|
||||||
|
|
||||||
@Mock private MetricReader reader;
|
@Mock private MetricReader reader;
|
||||||
private RegisteredReader registeredReader;
|
private RegisteredReader registeredReader;
|
||||||
|
|
@ -149,6 +155,7 @@ class AsynchronousMetricStorageTest {
|
||||||
selector,
|
selector,
|
||||||
View.builder().build(),
|
View.builder().build(),
|
||||||
AttributesProcessor.filterByKeyName(key -> key.equals("key1")),
|
AttributesProcessor.filterByKeyName(key -> key.equals("key1")),
|
||||||
|
CARDINALITY_LIMIT,
|
||||||
SourceInfo.noSourceInfo()),
|
SourceInfo.noSourceInfo()),
|
||||||
InstrumentDescriptor.create(
|
InstrumentDescriptor.create(
|
||||||
"name",
|
"name",
|
||||||
|
|
@ -175,7 +182,7 @@ class AsynchronousMetricStorageTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void record_MaxCardinality() {
|
void record_MaxCardinality() {
|
||||||
for (int i = 0; i <= MetricStorage.MAX_CARDINALITY + 1; i++) {
|
for (int i = 0; i <= CARDINALITY_LIMIT + 1; i++) {
|
||||||
longCounterStorage.record(
|
longCounterStorage.record(
|
||||||
longMeasurement(0, 1, 1, Attributes.builder().put("key" + i, "val").build()));
|
longMeasurement(0, 1, 1, Attributes.builder().put("key" + i, "val").build()));
|
||||||
}
|
}
|
||||||
|
|
@ -183,8 +190,7 @@ class AsynchronousMetricStorageTest {
|
||||||
assertThat(longCounterStorage.collect(resource, scope, 0, testClock.nanoTime()))
|
assertThat(longCounterStorage.collect(resource, scope, 0, testClock.nanoTime()))
|
||||||
.satisfies(
|
.satisfies(
|
||||||
metricData ->
|
metricData ->
|
||||||
assertThat(metricData.getLongSumData().getPoints())
|
assertThat(metricData.getLongSumData().getPoints()).hasSize(CARDINALITY_LIMIT));
|
||||||
.hasSize(MetricStorage.MAX_CARDINALITY));
|
|
||||||
logs.assertContains("Instrument long-counter has exceeded the maximum allowed cardinality");
|
logs.assertContains("Instrument long-counter has exceeded the maximum allowed cardinality");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ public class SynchronousMetricStorageTest {
|
||||||
Advice.empty());
|
Advice.empty());
|
||||||
private static final MetricDescriptor METRIC_DESCRIPTOR =
|
private static final MetricDescriptor METRIC_DESCRIPTOR =
|
||||||
MetricDescriptor.create("name", "description", "unit");
|
MetricDescriptor.create("name", "description", "unit");
|
||||||
|
private static final int CARDINALITY_LIMIT = 25;
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
LogCapturer logs = LogCapturer.create().captureForType(DefaultSynchronousMetricStorage.class);
|
LogCapturer logs = LogCapturer.create().captureForType(DefaultSynchronousMetricStorage.class);
|
||||||
|
|
@ -76,7 +77,11 @@ public class SynchronousMetricStorageTest {
|
||||||
AttributesProcessor spyAttributesProcessor = spy(attributesProcessor);
|
AttributesProcessor spyAttributesProcessor = spy(attributesProcessor);
|
||||||
SynchronousMetricStorage storage =
|
SynchronousMetricStorage storage =
|
||||||
new DefaultSynchronousMetricStorage<>(
|
new DefaultSynchronousMetricStorage<>(
|
||||||
cumulativeReader, METRIC_DESCRIPTOR, aggregator, spyAttributesProcessor);
|
cumulativeReader,
|
||||||
|
METRIC_DESCRIPTOR,
|
||||||
|
aggregator,
|
||||||
|
spyAttributesProcessor,
|
||||||
|
CARDINALITY_LIMIT);
|
||||||
storage.recordDouble(1, attributes, Context.root());
|
storage.recordDouble(1, attributes, Context.root());
|
||||||
MetricData md = storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, testClock.now());
|
MetricData md = storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, testClock.now());
|
||||||
assertThat(md)
|
assertThat(md)
|
||||||
|
|
@ -92,7 +97,11 @@ public class SynchronousMetricStorageTest {
|
||||||
void recordAndCollect_CumulativeDoesNotReset() {
|
void recordAndCollect_CumulativeDoesNotReset() {
|
||||||
DefaultSynchronousMetricStorage<?, ?> storage =
|
DefaultSynchronousMetricStorage<?, ?> storage =
|
||||||
new DefaultSynchronousMetricStorage<>(
|
new DefaultSynchronousMetricStorage<>(
|
||||||
cumulativeReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor);
|
cumulativeReader,
|
||||||
|
METRIC_DESCRIPTOR,
|
||||||
|
aggregator,
|
||||||
|
attributesProcessor,
|
||||||
|
CARDINALITY_LIMIT);
|
||||||
|
|
||||||
// Record measurement and collect at time 10
|
// Record measurement and collect at time 10
|
||||||
storage.recordDouble(3, Attributes.empty(), Context.current());
|
storage.recordDouble(3, Attributes.empty(), Context.current());
|
||||||
|
|
@ -134,7 +143,7 @@ public class SynchronousMetricStorageTest {
|
||||||
void recordAndCollect_DeltaResets() {
|
void recordAndCollect_DeltaResets() {
|
||||||
DefaultSynchronousMetricStorage<?, ?> storage =
|
DefaultSynchronousMetricStorage<?, ?> storage =
|
||||||
new DefaultSynchronousMetricStorage<>(
|
new DefaultSynchronousMetricStorage<>(
|
||||||
deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor);
|
deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor, CARDINALITY_LIMIT);
|
||||||
|
|
||||||
// Record measurement and collect at time 10
|
// Record measurement and collect at time 10
|
||||||
storage.recordDouble(3, Attributes.empty(), Context.current());
|
storage.recordDouble(3, Attributes.empty(), Context.current());
|
||||||
|
|
@ -181,14 +190,18 @@ public class SynchronousMetricStorageTest {
|
||||||
void recordAndCollect_CumulativeAtLimit() {
|
void recordAndCollect_CumulativeAtLimit() {
|
||||||
DefaultSynchronousMetricStorage<?, ?> storage =
|
DefaultSynchronousMetricStorage<?, ?> storage =
|
||||||
new DefaultSynchronousMetricStorage<>(
|
new DefaultSynchronousMetricStorage<>(
|
||||||
cumulativeReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor);
|
cumulativeReader,
|
||||||
|
METRIC_DESCRIPTOR,
|
||||||
|
aggregator,
|
||||||
|
attributesProcessor,
|
||||||
|
CARDINALITY_LIMIT);
|
||||||
|
|
||||||
// Record measurements for max number of attributes
|
// Record measurements for max number of attributes
|
||||||
for (int i = 0; i < MetricStorage.MAX_CARDINALITY; i++) {
|
for (int i = 0; i < CARDINALITY_LIMIT; i++) {
|
||||||
storage.recordDouble(
|
storage.recordDouble(
|
||||||
3, Attributes.builder().put("key", "value" + i).build(), Context.current());
|
3, Attributes.builder().put("key", "value" + i).build(), Context.current());
|
||||||
}
|
}
|
||||||
verify(aggregator, times(MetricStorage.MAX_CARDINALITY)).createHandle();
|
verify(aggregator, times(CARDINALITY_LIMIT)).createHandle();
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
||||||
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 10))
|
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 10))
|
||||||
.hasDoubleSumSatisfying(
|
.hasDoubleSumSatisfying(
|
||||||
|
|
@ -196,7 +209,7 @@ public class SynchronousMetricStorageTest {
|
||||||
sum.satisfies(
|
sum.satisfies(
|
||||||
sumData ->
|
sumData ->
|
||||||
assertThat(sumData.getPoints())
|
assertThat(sumData.getPoints())
|
||||||
.hasSize(MetricStorage.MAX_CARDINALITY)
|
.hasSize(CARDINALITY_LIMIT)
|
||||||
.allSatisfy(
|
.allSatisfy(
|
||||||
point -> {
|
point -> {
|
||||||
assertThat(point.getStartEpochNanos()).isEqualTo(0);
|
assertThat(point.getStartEpochNanos()).isEqualTo(0);
|
||||||
|
|
@ -210,10 +223,10 @@ public class SynchronousMetricStorageTest {
|
||||||
// Record measurement for additional attribute, exceeding limit
|
// Record measurement for additional attribute, exceeding limit
|
||||||
storage.recordDouble(
|
storage.recordDouble(
|
||||||
3,
|
3,
|
||||||
Attributes.builder().put("key", "value" + MetricStorage.MAX_CARDINALITY + 1).build(),
|
Attributes.builder().put("key", "value" + CARDINALITY_LIMIT + 1).build(),
|
||||||
Context.current());
|
Context.current());
|
||||||
// Should not create additional handles after MAX_CARDINALITY is reached
|
// Should not create additional handles after CARDINALITY_LIMIT is reached
|
||||||
verify(aggregator, times(MetricStorage.MAX_CARDINALITY)).createHandle();
|
verify(aggregator, times(CARDINALITY_LIMIT)).createHandle();
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
||||||
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 20))
|
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 20))
|
||||||
.hasDoubleSumSatisfying(
|
.hasDoubleSumSatisfying(
|
||||||
|
|
@ -221,7 +234,7 @@ public class SynchronousMetricStorageTest {
|
||||||
sum.satisfies(
|
sum.satisfies(
|
||||||
sumData ->
|
sumData ->
|
||||||
assertThat(sumData.getPoints())
|
assertThat(sumData.getPoints())
|
||||||
.hasSize(MetricStorage.MAX_CARDINALITY)
|
.hasSize(CARDINALITY_LIMIT)
|
||||||
.allSatisfy(
|
.allSatisfy(
|
||||||
point -> {
|
point -> {
|
||||||
assertThat(point.getStartEpochNanos()).isEqualTo(0);
|
assertThat(point.getStartEpochNanos()).isEqualTo(0);
|
||||||
|
|
@ -233,7 +246,7 @@ public class SynchronousMetricStorageTest {
|
||||||
point
|
point
|
||||||
.getAttributes()
|
.getAttributes()
|
||||||
.get(AttributeKey.stringKey("key"))
|
.get(AttributeKey.stringKey("key"))
|
||||||
.equals("value" + MetricStorage.MAX_CARDINALITY + 1))));
|
.equals("value" + CARDINALITY_LIMIT + 1))));
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
||||||
logs.assertContains("Instrument name has exceeded the maximum allowed cardinality");
|
logs.assertContains("Instrument name has exceeded the maximum allowed cardinality");
|
||||||
}
|
}
|
||||||
|
|
@ -242,14 +255,14 @@ public class SynchronousMetricStorageTest {
|
||||||
void recordAndCollect_DeltaAtLimit() {
|
void recordAndCollect_DeltaAtLimit() {
|
||||||
DefaultSynchronousMetricStorage<?, ?> storage =
|
DefaultSynchronousMetricStorage<?, ?> storage =
|
||||||
new DefaultSynchronousMetricStorage<>(
|
new DefaultSynchronousMetricStorage<>(
|
||||||
deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor);
|
deltaReader, METRIC_DESCRIPTOR, aggregator, attributesProcessor, CARDINALITY_LIMIT);
|
||||||
|
|
||||||
// Record measurements for max number of attributes
|
// Record measurements for max number of attributes
|
||||||
for (int i = 0; i < MetricStorage.MAX_CARDINALITY; i++) {
|
for (int i = 0; i < CARDINALITY_LIMIT; i++) {
|
||||||
storage.recordDouble(
|
storage.recordDouble(
|
||||||
3, Attributes.builder().put("key", "value" + i).build(), Context.current());
|
3, Attributes.builder().put("key", "value" + i).build(), Context.current());
|
||||||
}
|
}
|
||||||
verify(aggregator, times(MetricStorage.MAX_CARDINALITY)).createHandle();
|
verify(aggregator, times(CARDINALITY_LIMIT)).createHandle();
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
||||||
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 10))
|
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 10))
|
||||||
.hasDoubleSumSatisfying(
|
.hasDoubleSumSatisfying(
|
||||||
|
|
@ -257,25 +270,25 @@ public class SynchronousMetricStorageTest {
|
||||||
sum.satisfies(
|
sum.satisfies(
|
||||||
sumData ->
|
sumData ->
|
||||||
assertThat(sumData.getPoints())
|
assertThat(sumData.getPoints())
|
||||||
.hasSize(MetricStorage.MAX_CARDINALITY)
|
.hasSize(CARDINALITY_LIMIT)
|
||||||
.allSatisfy(
|
.allSatisfy(
|
||||||
point -> {
|
point -> {
|
||||||
assertThat(point.getStartEpochNanos()).isEqualTo(0);
|
assertThat(point.getStartEpochNanos()).isEqualTo(0);
|
||||||
assertThat(point.getEpochNanos()).isEqualTo(10);
|
assertThat(point.getEpochNanos()).isEqualTo(10);
|
||||||
assertThat(point.getValue()).isEqualTo(3);
|
assertThat(point.getValue()).isEqualTo(3);
|
||||||
})));
|
})));
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(MetricStorage.MAX_CARDINALITY);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(CARDINALITY_LIMIT);
|
||||||
assertThat(logs.getEvents()).isEmpty();
|
assertThat(logs.getEvents()).isEmpty();
|
||||||
deltaReader.setLastCollectEpochNanos(10);
|
deltaReader.setLastCollectEpochNanos(10);
|
||||||
|
|
||||||
// Record measurement for additional attribute, should not exceed limit due to reset
|
// Record measurement for additional attribute, should not exceed limit due to reset
|
||||||
storage.recordDouble(
|
storage.recordDouble(
|
||||||
3,
|
3,
|
||||||
Attributes.builder().put("key", "value" + MetricStorage.MAX_CARDINALITY + 1).build(),
|
Attributes.builder().put("key", "value" + CARDINALITY_LIMIT + 1).build(),
|
||||||
Context.current());
|
Context.current());
|
||||||
// Should use handle returned to pool instead of creating new ones
|
// Should use handle returned to pool instead of creating new ones
|
||||||
verify(aggregator, times(MetricStorage.MAX_CARDINALITY)).createHandle();
|
verify(aggregator, times(CARDINALITY_LIMIT)).createHandle();
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(MetricStorage.MAX_CARDINALITY - 1);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(CARDINALITY_LIMIT - 1);
|
||||||
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 20))
|
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 20))
|
||||||
.hasDoubleSumSatisfying(
|
.hasDoubleSumSatisfying(
|
||||||
sum ->
|
sum ->
|
||||||
|
|
@ -288,19 +301,19 @@ public class SynchronousMetricStorageTest {
|
||||||
.hasValue(3)
|
.hasValue(3)
|
||||||
.hasAttributes(
|
.hasAttributes(
|
||||||
Attributes.builder()
|
Attributes.builder()
|
||||||
.put("key", "value" + MetricStorage.MAX_CARDINALITY + 1)
|
.put("key", "value" + CARDINALITY_LIMIT + 1)
|
||||||
.build())));
|
.build())));
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(MetricStorage.MAX_CARDINALITY);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(CARDINALITY_LIMIT);
|
||||||
assertThat(logs.getEvents()).isEmpty();
|
assertThat(logs.getEvents()).isEmpty();
|
||||||
deltaReader.setLastCollectEpochNanos(20);
|
deltaReader.setLastCollectEpochNanos(20);
|
||||||
|
|
||||||
// Record measurements exceeding max number of attributes. Last measurement should be dropped
|
// Record measurements exceeding max number of attributes. Last measurement should be dropped
|
||||||
for (int i = 0; i < MetricStorage.MAX_CARDINALITY + 1; i++) {
|
for (int i = 0; i < CARDINALITY_LIMIT + 1; i++) {
|
||||||
storage.recordDouble(
|
storage.recordDouble(
|
||||||
3, Attributes.builder().put("key", "value" + i).build(), Context.current());
|
3, Attributes.builder().put("key", "value" + i).build(), Context.current());
|
||||||
}
|
}
|
||||||
// Should use handles returned to pool instead of creating new ones
|
// Should use handles returned to pool instead of creating new ones
|
||||||
verify(aggregator, times(MetricStorage.MAX_CARDINALITY)).createHandle();
|
verify(aggregator, times(CARDINALITY_LIMIT)).createHandle();
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(0);
|
||||||
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 30))
|
assertThat(storage.collect(RESOURCE, INSTRUMENTATION_SCOPE_INFO, 0, 30))
|
||||||
.hasDoubleSumSatisfying(
|
.hasDoubleSumSatisfying(
|
||||||
|
|
@ -308,7 +321,7 @@ public class SynchronousMetricStorageTest {
|
||||||
sum.satisfies(
|
sum.satisfies(
|
||||||
sumData ->
|
sumData ->
|
||||||
assertThat(sumData.getPoints())
|
assertThat(sumData.getPoints())
|
||||||
.hasSize(MetricStorage.MAX_CARDINALITY)
|
.hasSize(CARDINALITY_LIMIT)
|
||||||
.allSatisfy(
|
.allSatisfy(
|
||||||
point -> {
|
point -> {
|
||||||
assertThat(point.getStartEpochNanos()).isEqualTo(20);
|
assertThat(point.getStartEpochNanos()).isEqualTo(20);
|
||||||
|
|
@ -320,8 +333,8 @@ public class SynchronousMetricStorageTest {
|
||||||
point
|
point
|
||||||
.getAttributes()
|
.getAttributes()
|
||||||
.get(AttributeKey.stringKey("key"))
|
.get(AttributeKey.stringKey("key"))
|
||||||
.equals("value" + MetricStorage.MAX_CARDINALITY + 1))));
|
.equals("value" + CARDINALITY_LIMIT + 1))));
|
||||||
assertThat(storage.getAggregatorHandlePool()).hasSize(MetricStorage.MAX_CARDINALITY);
|
assertThat(storage.getAggregatorHandlePool()).hasSize(CARDINALITY_LIMIT);
|
||||||
logs.assertContains("Instrument name has exceeded the maximum allowed cardinality");
|
logs.assertContains("Instrument name has exceeded the maximum allowed cardinality");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import io.opentelemetry.sdk.metrics.InstrumentSelector;
|
||||||
import io.opentelemetry.sdk.metrics.InstrumentType;
|
import io.opentelemetry.sdk.metrics.InstrumentType;
|
||||||
import io.opentelemetry.sdk.metrics.View;
|
import io.opentelemetry.sdk.metrics.View;
|
||||||
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
|
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class RegisteredViewTest {
|
class RegisteredViewTest {
|
||||||
|
|
@ -33,12 +34,13 @@ class RegisteredViewTest {
|
||||||
.setAggregation(Aggregation.sum())
|
.setAggregation(Aggregation.sum())
|
||||||
.build(),
|
.build(),
|
||||||
AttributesProcessor.noop(),
|
AttributesProcessor.noop(),
|
||||||
|
MetricStorage.DEFAULT_MAX_CARDINALITY,
|
||||||
SourceInfo.fromCurrentStack())
|
SourceInfo.fromCurrentStack())
|
||||||
.toString())
|
.toString())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
"RegisteredView{"
|
"RegisteredView{"
|
||||||
+ "instrumentSelector=InstrumentSelector{instrumentType=COUNTER, instrumentName=name, meterName=meter-name, meterVersion=meter-version, meterSchemaUrl=meter-schema-url}, "
|
+ "instrumentSelector=InstrumentSelector{instrumentType=COUNTER, instrumentName=name, meterName=meter-name, meterVersion=meter-version, meterSchemaUrl=meter-schema-url}, "
|
||||||
+ "view=View{name=name, description=description, aggregation=SumAggregation, attributesProcessor=NoopAttributesProcessor{}}"
|
+ "view=View{name=name, description=description, aggregation=SumAggregation, attributesProcessor=NoopAttributesProcessor{}, cardinalityLimit=2000}"
|
||||||
+ "}");
|
+ "}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
|
||||||
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
|
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
|
||||||
import io.opentelemetry.sdk.metrics.internal.descriptor.Advice;
|
import io.opentelemetry.sdk.metrics.internal.descriptor.Advice;
|
||||||
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
|
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
|
||||||
|
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -38,7 +40,11 @@ class ViewRegistryTest {
|
||||||
|
|
||||||
private static RegisteredView registeredView(InstrumentSelector instrumentSelector, View view) {
|
private static RegisteredView registeredView(InstrumentSelector instrumentSelector, View view) {
|
||||||
return RegisteredView.create(
|
return RegisteredView.create(
|
||||||
instrumentSelector, view, AttributesProcessor.noop(), SourceInfo.fromCurrentStack());
|
instrumentSelector,
|
||||||
|
view,
|
||||||
|
AttributesProcessor.noop(),
|
||||||
|
MetricStorage.DEFAULT_MAX_CARDINALITY,
|
||||||
|
SourceInfo.fromCurrentStack());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -49,7 +55,9 @@ class ViewRegistryTest {
|
||||||
View.builder().setDescription("description").build());
|
View.builder().setDescription("description").build());
|
||||||
ViewRegistry viewRegistry =
|
ViewRegistry viewRegistry =
|
||||||
ViewRegistry.create(
|
ViewRegistry.create(
|
||||||
DefaultAggregationSelector.getDefault(), Collections.singletonList(registeredView));
|
DefaultAggregationSelector.getDefault(),
|
||||||
|
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
|
||||||
|
Collections.singletonList(registeredView));
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
viewRegistry.findViews(
|
viewRegistry.findViews(
|
||||||
|
|
@ -81,7 +89,9 @@ class ViewRegistryTest {
|
||||||
View.builder().setDescription("description").build());
|
View.builder().setDescription("description").build());
|
||||||
ViewRegistry viewRegistry =
|
ViewRegistry viewRegistry =
|
||||||
ViewRegistry.create(
|
ViewRegistry.create(
|
||||||
DefaultAggregationSelector.getDefault(), Collections.singletonList(registeredView));
|
DefaultAggregationSelector.getDefault(),
|
||||||
|
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
|
||||||
|
Collections.singletonList(registeredView));
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
viewRegistry.findViews(
|
viewRegistry.findViews(
|
||||||
|
|
@ -113,7 +123,9 @@ class ViewRegistryTest {
|
||||||
View.builder().setDescription("description").build());
|
View.builder().setDescription("description").build());
|
||||||
ViewRegistry viewRegistry =
|
ViewRegistry viewRegistry =
|
||||||
ViewRegistry.create(
|
ViewRegistry.create(
|
||||||
DefaultAggregationSelector.getDefault(), Collections.singletonList(registeredView));
|
DefaultAggregationSelector.getDefault(),
|
||||||
|
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
|
||||||
|
Collections.singletonList(registeredView));
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
viewRegistry.findViews(
|
viewRegistry.findViews(
|
||||||
|
|
@ -156,6 +168,7 @@ class ViewRegistryTest {
|
||||||
ViewRegistry viewRegistry =
|
ViewRegistry viewRegistry =
|
||||||
ViewRegistry.create(
|
ViewRegistry.create(
|
||||||
DefaultAggregationSelector.getDefault(),
|
DefaultAggregationSelector.getDefault(),
|
||||||
|
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
|
||||||
Arrays.asList(registeredView1, registeredView2));
|
Arrays.asList(registeredView1, registeredView2));
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
|
|
@ -195,7 +208,9 @@ class ViewRegistryTest {
|
||||||
View.builder().setAggregation(Aggregation.explicitBucketHistogram()).build());
|
View.builder().setAggregation(Aggregation.explicitBucketHistogram()).build());
|
||||||
ViewRegistry viewRegistry =
|
ViewRegistry viewRegistry =
|
||||||
ViewRegistry.create(
|
ViewRegistry.create(
|
||||||
DefaultAggregationSelector.getDefault(), Collections.singletonList(registeredView));
|
DefaultAggregationSelector.getDefault(),
|
||||||
|
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
|
||||||
|
Collections.singletonList(registeredView));
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
viewRegistry.findViews(
|
viewRegistry.findViews(
|
||||||
|
|
@ -254,7 +269,10 @@ class ViewRegistryTest {
|
||||||
: Aggregation.defaultAggregation();
|
: Aggregation.defaultAggregation();
|
||||||
|
|
||||||
ViewRegistry viewRegistry =
|
ViewRegistry viewRegistry =
|
||||||
ViewRegistry.create(defaultAggregationSelector, Collections.singletonList(registeredView));
|
ViewRegistry.create(
|
||||||
|
defaultAggregationSelector,
|
||||||
|
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
|
||||||
|
Collections.singletonList(registeredView));
|
||||||
|
|
||||||
// Counter instrument should result in default view
|
// Counter instrument should result in default view
|
||||||
assertThat(
|
assertThat(
|
||||||
|
|
@ -330,7 +348,9 @@ class ViewRegistryTest {
|
||||||
View.builder().setAggregation(Aggregation.explicitBucketHistogram()).build());
|
View.builder().setAggregation(Aggregation.explicitBucketHistogram()).build());
|
||||||
ViewRegistry viewRegistry =
|
ViewRegistry viewRegistry =
|
||||||
ViewRegistry.create(
|
ViewRegistry.create(
|
||||||
DefaultAggregationSelector.getDefault(), Collections.singletonList(registeredView));
|
DefaultAggregationSelector.getDefault(),
|
||||||
|
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
|
||||||
|
Collections.singletonList(registeredView));
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
viewRegistry.findViews(
|
viewRegistry.findViews(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue