Add attributes advice API (just `DoubleCounter` for now) (#5677)

This commit is contained in:
Mateusz Rzeszutek 2023-08-16 21:20:00 +02:00 committed by GitHub
parent ff79bb7094
commit 35b41ab484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 485 additions and 61 deletions

View File

@ -0,0 +1,17 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.extension.incubator.metrics;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.metrics.DoubleCounter;
import java.util.List;
/** Configure advice for implementation of {@link DoubleCounter}. */
public interface CounterAdviceConfigurer {
/** Specify the recommended set of attribute keys to be used for this counter. */
CounterAdviceConfigurer setAttributes(List<AttributeKey<?>> attributes);
}

View File

@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.extension.incubator.metrics;
import io.opentelemetry.api.metrics.DoubleCounterBuilder;
import java.util.function.Consumer;
/** Extended {@link DoubleCounterBuilder} with experimental APIs. */
public interface ExtendedDoubleCounterBuilder extends DoubleCounterBuilder {
/** Specify advice for counter implementations. */
default DoubleCounterBuilder setAdvice(Consumer<CounterAdviceConfigurer> adviceConsumer) {
return this;
}
}

View File

@ -28,10 +28,10 @@ abstract class AbstractInstrumentBuilder<BuilderT extends AbstractInstrumentBuil
private final InstrumentValueType valueType;
private String description;
private String unit;
private Advice advice;
protected final MeterSharedState meterSharedState;
protected final String instrumentName;
protected final Advice.AdviceBuilder adviceBuilder;
AbstractInstrumentBuilder(
MeterProviderSharedState meterProviderSharedState,
@ -49,7 +49,7 @@ abstract class AbstractInstrumentBuilder<BuilderT extends AbstractInstrumentBuil
name,
description,
unit,
Advice.empty());
Advice.builder());
}
AbstractInstrumentBuilder(
@ -60,7 +60,7 @@ abstract class AbstractInstrumentBuilder<BuilderT extends AbstractInstrumentBuil
String name,
String description,
String unit,
Advice advice) {
Advice.AdviceBuilder adviceBuilder) {
this.type = type;
this.valueType = valueType;
this.instrumentName = name;
@ -68,7 +68,7 @@ abstract class AbstractInstrumentBuilder<BuilderT extends AbstractInstrumentBuil
this.unit = unit;
this.meterProviderSharedState = meterProviderSharedState;
this.meterSharedState = meterSharedState;
this.advice = advice;
this.adviceBuilder = adviceBuilder;
}
protected abstract BuilderT getThis();
@ -85,17 +85,19 @@ abstract class AbstractInstrumentBuilder<BuilderT extends AbstractInstrumentBuil
protected <T> T swapBuilder(SwapBuilder<T> swapper) {
return swapper.newBuilder(
meterProviderSharedState, meterSharedState, instrumentName, description, unit, advice);
}
protected void setAdvice(Advice advice) {
this.advice = advice;
meterProviderSharedState,
meterSharedState,
instrumentName,
description,
unit,
adviceBuilder);
}
final <I extends AbstractInstrument> I buildSynchronousInstrument(
BiFunction<InstrumentDescriptor, WriteableMetricStorage, I> instrumentFactory) {
InstrumentDescriptor descriptor =
InstrumentDescriptor.create(instrumentName, description, unit, type, valueType, advice);
InstrumentDescriptor.create(
instrumentName, description, unit, type, valueType, adviceBuilder.build());
WriteableMetricStorage storage =
meterSharedState.registerSynchronousMetricStorage(descriptor, meterProviderSharedState);
return instrumentFactory.apply(descriptor, storage);
@ -123,7 +125,8 @@ abstract class AbstractInstrumentBuilder<BuilderT extends AbstractInstrumentBuil
final SdkObservableMeasurement buildObservableMeasurement(InstrumentType type) {
InstrumentDescriptor descriptor =
InstrumentDescriptor.create(instrumentName, description, unit, type, valueType, advice);
InstrumentDescriptor.create(
instrumentName, description, unit, type, valueType, adviceBuilder.build());
return meterSharedState.registerObservableMeasurement(descriptor);
}
@ -131,7 +134,8 @@ abstract class AbstractInstrumentBuilder<BuilderT extends AbstractInstrumentBuil
public String toString() {
return this.getClass().getSimpleName()
+ "{descriptor="
+ InstrumentDescriptor.create(instrumentName, description, unit, type, valueType, advice)
+ InstrumentDescriptor.create(
instrumentName, description, unit, type, valueType, adviceBuilder.build())
+ "}";
}
@ -143,6 +147,6 @@ abstract class AbstractInstrumentBuilder<BuilderT extends AbstractInstrumentBuil
String name,
String description,
String unit,
Advice advice);
Advice.AdviceBuilder adviceBuilder);
}
}

View File

@ -5,18 +5,22 @@
package io.opentelemetry.sdk.metrics;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleCounter;
import io.opentelemetry.api.metrics.DoubleCounterBuilder;
import io.opentelemetry.api.metrics.ObservableDoubleCounter;
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
import io.opentelemetry.context.Context;
import io.opentelemetry.extension.incubator.metrics.CounterAdviceConfigurer;
import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleCounterBuilder;
import io.opentelemetry.sdk.internal.ThrottlingLogger;
import io.opentelemetry.sdk.metrics.internal.descriptor.Advice;
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState;
import io.opentelemetry.sdk.metrics.internal.state.MeterSharedState;
import io.opentelemetry.sdk.metrics.internal.state.WriteableMetricStorage;
import java.util.List;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -56,7 +60,8 @@ final class SdkDoubleCounter extends AbstractInstrument implements DoubleCounter
}
static final class SdkDoubleCounterBuilder
extends AbstractInstrumentBuilder<SdkDoubleCounterBuilder> implements DoubleCounterBuilder {
extends AbstractInstrumentBuilder<SdkDoubleCounterBuilder>
implements ExtendedDoubleCounterBuilder, CounterAdviceConfigurer {
SdkDoubleCounterBuilder(
MeterProviderSharedState meterProviderSharedState,
@ -64,7 +69,7 @@ final class SdkDoubleCounter extends AbstractInstrument implements DoubleCounter
String name,
String description,
String unit,
Advice advice) {
Advice.AdviceBuilder adviceBuilder) {
super(
meterProviderSharedState,
sharedState,
@ -73,7 +78,7 @@ final class SdkDoubleCounter extends AbstractInstrument implements DoubleCounter
name,
description,
unit,
advice);
adviceBuilder);
}
@Override
@ -81,6 +86,12 @@ final class SdkDoubleCounter extends AbstractInstrument implements DoubleCounter
return this;
}
@Override
public DoubleCounterBuilder setAdvice(Consumer<CounterAdviceConfigurer> adviceConsumer) {
adviceConsumer.accept(this);
return this;
}
@Override
public SdkDoubleCounter build() {
return buildSynchronousInstrument(SdkDoubleCounter::new);
@ -96,5 +107,11 @@ final class SdkDoubleCounter extends AbstractInstrument implements DoubleCounter
public ObservableDoubleMeasurement buildObserver() {
return buildObservableMeasurement(InstrumentType.OBSERVABLE_COUNTER);
}
@Override
public CounterAdviceConfigurer setAttributes(List<AttributeKey<?>> attributes) {
adviceBuilder.setAttributes(attributes);
return this;
}
}
}

View File

@ -12,7 +12,6 @@ import io.opentelemetry.context.Context;
import io.opentelemetry.extension.incubator.metrics.DoubleHistogramAdviceConfigurer;
import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleHistogramBuilder;
import io.opentelemetry.sdk.internal.ThrottlingLogger;
import io.opentelemetry.sdk.metrics.internal.descriptor.Advice;
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState;
import io.opentelemetry.sdk.metrics.internal.state.MeterSharedState;
@ -99,7 +98,7 @@ final class SdkDoubleHistogram extends AbstractInstrument implements DoubleHisto
@Override
public DoubleHistogramAdviceConfigurer setExplicitBucketBoundaries(
List<Double> bucketBoundaries) {
setAdvice(Advice.create(bucketBoundaries));
adviceBuilder.setExplicitBucketBoundaries(bucketBoundaries);
return this;
}
}

View File

@ -52,7 +52,7 @@ final class SdkDoubleUpDownCounter extends AbstractInstrument implements DoubleU
String name,
String description,
String unit,
Advice advice) {
Advice.AdviceBuilder adviceBuilder) {
super(
meterProviderSharedState,
sharedState,
@ -61,7 +61,7 @@ final class SdkDoubleUpDownCounter extends AbstractInstrument implements DoubleU
name,
description,
unit,
advice);
adviceBuilder);
}
@Override

View File

@ -22,7 +22,7 @@ final class SdkLongGaugeBuilder extends AbstractInstrumentBuilder<SdkLongGaugeBu
String name,
String description,
String unit,
Advice advice) {
Advice.AdviceBuilder adviceBuilder) {
super(
meterProviderSharedState,
sharedState,
@ -31,7 +31,7 @@ final class SdkLongGaugeBuilder extends AbstractInstrumentBuilder<SdkLongGaugeBu
name,
description,
unit,
advice);
adviceBuilder);
}
@Override

View File

@ -66,7 +66,7 @@ final class SdkLongHistogram extends AbstractInstrument implements LongHistogram
String name,
String description,
String unit,
Advice advice) {
Advice.AdviceBuilder adviceBuilder) {
super(
meterProviderSharedState,
sharedState,
@ -75,7 +75,7 @@ final class SdkLongHistogram extends AbstractInstrument implements LongHistogram
name,
description,
unit,
advice);
adviceBuilder);
}
@Override
@ -99,7 +99,7 @@ final class SdkLongHistogram extends AbstractInstrument implements LongHistogram
public LongHistogramAdviceConfigurer setExplicitBucketBoundaries(List<Long> bucketBoundaries) {
List<Double> doubleBoundaries =
bucketBoundaries.stream().map(Long::doubleValue).collect(Collectors.toList());
setAdvice(Advice.create(doubleBoundaries));
adviceBuilder.setExplicitBucketBoundaries(doubleBoundaries);
return this;
}
}

View File

@ -6,6 +6,7 @@
package io.opentelemetry.sdk.metrics.internal.descriptor;
import com.google.auto.value.AutoValue;
import io.opentelemetry.api.common.AttributeKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -16,28 +17,64 @@ import javax.annotation.concurrent.Immutable;
@Immutable
public abstract class Advice {
private static final Advice EMPTY_ADVICE = create(null);
private static final Advice EMPTY_ADVICE = builder().build();
public static Advice empty() {
return EMPTY_ADVICE;
}
/**
* Creates a new {@link Advice} with the given explicit bucket histogram boundaries.
*
* @param explicitBucketBoundaries the explicit bucket histogram boundaries.
* @return a new {@link Advice} with the given bucket boundaries.
*/
public static Advice create(@Nullable List<Double> explicitBucketBoundaries) {
if (explicitBucketBoundaries != null) {
explicitBucketBoundaries =
Collections.unmodifiableList(new ArrayList<>(explicitBucketBoundaries));
}
return new AutoValue_Advice(explicitBucketBoundaries);
public static AdviceBuilder builder() {
return new AutoValue_Advice.Builder();
}
Advice() {}
@Nullable
public abstract List<Double> getExplicitBucketBoundaries();
@Nullable
public abstract List<AttributeKey<?>> getAttributes();
public boolean hasAttributes() {
return getAttributes() != null;
}
@AutoValue.Builder
public abstract static class AdviceBuilder {
AdviceBuilder() {}
abstract AdviceBuilder explicitBucketBoundaries(
@Nullable List<Double> explicitBucketBoundaries);
/**
* Sets the explicit bucket histogram boundaries.
*
* @param explicitBucketBoundaries the explicit bucket histogram boundaries.
*/
public AdviceBuilder setExplicitBucketBoundaries(
@Nullable List<Double> explicitBucketBoundaries) {
if (explicitBucketBoundaries != null) {
explicitBucketBoundaries =
Collections.unmodifiableList(new ArrayList<>(explicitBucketBoundaries));
}
return explicitBucketBoundaries(explicitBucketBoundaries);
}
abstract AdviceBuilder attributes(@Nullable List<AttributeKey<?>> attributes);
/**
* Sets the list of the attribute keys to be used for the resulting instrument.
*
* @param attributes the list of the attribute keys.
*/
public AdviceBuilder setAttributes(@Nullable List<AttributeKey<?>> attributes) {
if (attributes != null) {
attributes = Collections.unmodifiableList(new ArrayList<>(attributes));
}
return attributes(attributes);
}
public abstract Advice build();
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics.internal.view;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
final class AdviceAttributesProcessor extends AttributesProcessor {
private final Set<AttributeKey<?>> attributeKeys;
AdviceAttributesProcessor(List<AttributeKey<?>> adviceAttributeKeys) {
this.attributeKeys = new HashSet<>(adviceAttributeKeys);
}
@Override
public Attributes process(Attributes incoming, Context context) {
AttributesBuilder builder = incoming.toBuilder();
builder.removeIf(key -> !attributeKeys.contains(key));
return builder.build();
}
@Override
public boolean usesContext() {
return false;
}
@Override
public String toString() {
return "AdviceAttributesProcessor{attributeKeys=" + attributeKeys + '}';
}
}

View File

@ -6,6 +6,7 @@
package io.opentelemetry.sdk.metrics.internal.view;
import static io.opentelemetry.sdk.metrics.internal.view.NoopAttributesProcessor.NOOP;
import static java.util.Objects.requireNonNull;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.metrics.Aggregation;
@ -17,6 +18,7 @@ import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorFactory;
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
import io.opentelemetry.sdk.metrics.internal.descriptor.Advice;
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
@ -25,7 +27,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -127,27 +128,32 @@ public final class ViewRegistry {
// No views matched, use default view
RegisteredView instrumentDefaultView =
Objects.requireNonNull(instrumentDefaultRegisteredView.get(descriptor.getType()));
requireNonNull(instrumentDefaultRegisteredView.get(descriptor.getType()));
AggregatorFactory viewAggregatorFactory =
(AggregatorFactory) instrumentDefaultView.getView().getAggregation();
// If the aggregation from default aggregation selector is compatible with the instrument, use
// it
if (viewAggregatorFactory.isCompatibleWithInstrument(descriptor)) {
return Collections.singletonList(instrumentDefaultView);
if (!viewAggregatorFactory.isCompatibleWithInstrument(descriptor)) {
// The aggregation from default aggregation selector was incompatible with instrument, use
// default aggregation instead
logger.log(
Level.WARNING,
"Instrument default aggregation "
+ AggregationUtil.aggregationName(instrumentDefaultView.getView().getAggregation())
+ " is incompatible with instrument "
+ descriptor.getName()
+ " of type "
+ descriptor.getType());
instrumentDefaultView = DEFAULT_REGISTERED_VIEW;
}
// The aggregation from default aggregation selector was incompatible with instrument, use
// default aggregation instead
logger.log(
Level.WARNING,
"Instrument default aggregation "
+ AggregationUtil.aggregationName(instrumentDefaultView.getView().getAggregation())
+ " is incompatible with instrument "
+ descriptor.getName()
+ " of type "
+ descriptor.getType());
return Collections.singletonList(DEFAULT_REGISTERED_VIEW);
// if the user defined an attributes advice, use it
if (descriptor.getAdvice().hasAttributes()) {
instrumentDefaultView =
applyAdviceToDefaultView(instrumentDefaultView, descriptor.getAdvice());
}
return Collections.singletonList(instrumentDefaultView);
}
// Matches an instrument selector against an instrument + meter.
@ -246,4 +252,14 @@ public final class ViewRegistry {
}
return Pattern.compile(patternBuilder.toString());
}
private static RegisteredView applyAdviceToDefaultView(
RegisteredView instrumentDefaultView, Advice advice) {
return RegisteredView.create(
instrumentDefaultView.getInstrumentSelector(),
instrumentDefaultView.getView(),
new AdviceAttributesProcessor(requireNonNull(advice.getAttributes())),
instrumentDefaultView.getCardinalityLimit(),
instrumentDefaultView.getViewSourceInfo());
}
}

View File

@ -41,7 +41,7 @@ class AbstractInstrumentBuilderTest {
+ "unit=instrument-unit, "
+ "type=COUNTER, "
+ "valueType=LONG, "
+ "advice=Advice{explicitBucketBoundaries=null}"
+ "advice=Advice{explicitBucketBoundaries=null, attributes=null}"
+ "}}");
}

View File

@ -0,0 +1,111 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static java.util.Arrays.asList;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleCounter;
import io.opentelemetry.api.metrics.DoubleCounterBuilder;
import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleCounterBuilder;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
class AttributesAdviceTest {
private static final Attributes ATTRIBUTES =
Attributes.builder()
.put(stringKey("key1"), "1")
.put(stringKey("key2"), "2")
.put(stringKey("key3"), "3")
.build();
private SdkMeterProvider meterProvider = SdkMeterProvider.builder().build();
@AfterEach
void cleanup() {
meterProvider.close();
}
@Test
void counterWithoutAdvice() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
meterProvider = SdkMeterProvider.builder().registerMetricReader(reader).build();
DoubleCounter counter =
meterProvider.get("meter").counterBuilder("counter").ofDoubles().build();
counter.add(1, ATTRIBUTES);
assertThat(reader.collectAllMetrics())
.satisfiesExactly(
metric ->
assertThat(metric)
.hasDoubleSumSatisfying(
sum -> sum.hasPointsSatisfying(point -> point.hasAttributes(ATTRIBUTES))));
}
@Test
void counterWithAdvice() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
meterProvider = SdkMeterProvider.builder().registerMetricReader(reader).build();
DoubleCounterBuilder doubleCounterBuilder =
meterProvider.get("meter").counterBuilder("counter").ofDoubles();
((ExtendedDoubleCounterBuilder) doubleCounterBuilder)
.setAdvice(advice -> advice.setAttributes(asList(stringKey("key1"), stringKey("key2"))));
DoubleCounter counter = doubleCounterBuilder.build();
counter.add(1, ATTRIBUTES);
assertThat(reader.collectAllMetrics())
.satisfiesExactly(
metric ->
assertThat(metric)
.hasDoubleSumSatisfying(
sum ->
sum.hasPointsSatisfying(
point ->
point.hasAttributesSatisfyingExactly(
equalTo(stringKey("key1"), "1"),
equalTo(stringKey("key2"), "2")))));
}
@Test
void counterWithAdviceAndViews() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
meterProvider =
SdkMeterProvider.builder()
.registerMetricReader(reader)
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder()
.setAttributeFilter(key -> "key2".equals(key) || "key3".equals(key))
.build())
.build();
DoubleCounterBuilder doubleCounterBuilder =
meterProvider.get("meter").counterBuilder("counter").ofDoubles();
((ExtendedDoubleCounterBuilder) doubleCounterBuilder)
.setAdvice(advice -> advice.setAttributes(asList(stringKey("key1"), stringKey("key2"))));
DoubleCounter counter = doubleCounterBuilder.build();
counter.add(1, ATTRIBUTES);
assertThat(reader.collectAllMetrics())
.satisfiesExactly(
metric ->
assertThat(metric)
.hasDoubleSumSatisfying(
sum ->
sum.hasPointsSatisfying(
point ->
point.hasAttributesSatisfyingExactly(
equalTo(stringKey("key2"), "2"),
equalTo(stringKey("key3"), "3")))));
}
}

View File

@ -25,7 +25,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class AdviceTest {
class ExplicitBucketBoundariesAdviceTest {
private SdkMeterProvider meterProvider = SdkMeterProvider.builder().build();

View File

@ -83,7 +83,7 @@ class SdkObservableInstrumentTest {
+ "unit=unit, "
+ "type=COUNTER, "
+ "valueType=DOUBLE, "
+ "advice=Advice{explicitBucketBoundaries=null}}"
+ "advice=Advice{explicitBucketBoundaries=null, attributes=null}}"
+ "]}}");
}
}

View File

@ -106,7 +106,7 @@ class CallbackRegistrationTest {
+ "unit=unit, "
+ "type=OBSERVABLE_COUNTER, "
+ "valueType=LONG, "
+ "advice=Advice{explicitBucketBoundaries=null}"
+ "advice=Advice{explicitBucketBoundaries=null, attributes=null}"
+ "}]}");
assertThat(
CallbackRegistration.create(Arrays.asList(measurement1, measurement2), callback)
@ -120,14 +120,14 @@ class CallbackRegistrationTest {
+ "unit=unit, "
+ "type=OBSERVABLE_COUNTER, "
+ "valueType=LONG, "
+ "advice=Advice{explicitBucketBoundaries=null}}, "
+ "advice=Advice{explicitBucketBoundaries=null, attributes=null}}, "
+ "InstrumentDescriptor{"
+ "name=long-counter, "
+ "description=description, "
+ "unit=unit, "
+ "type=OBSERVABLE_COUNTER, "
+ "valueType=LONG, "
+ "advice=Advice{explicitBucketBoundaries=null}"
+ "advice=Advice{explicitBucketBoundaries=null, attributes=null}"
+ "}]}");
}

View File

@ -0,0 +1,51 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics.internal.view;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.entry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.Context;
import org.junit.jupiter.api.Test;
class AdviceAttributesProcessorTest {
@Test
void doesNotUseContext() {
assertThat(new AdviceAttributesProcessor(emptyList()).usesContext()).isFalse();
}
@Test
void removeUnwantedAttributes() {
AttributesProcessor processor =
new AdviceAttributesProcessor(asList(stringKey("abc"), stringKey("def"), stringKey("ghi")));
Attributes result =
processor.process(
Attributes.builder()
.put(stringKey("abc"), "abc")
.put(stringKey("def"), "def")
.put(longKey("ghi"), 42L)
.put(stringKey("xyz"), "xyz")
.build(),
Context.root());
// does not contain key "ghi" because the type is different (stringKey != longKey)
assertThat(result).containsOnly(entry(stringKey("abc"), "abc"), entry(stringKey("def"), "def"));
}
@Test
void stringRepresentation() {
assertThat(new AdviceAttributesProcessor(singletonList(stringKey("abc"))).toString())
.isEqualTo("AdviceAttributesProcessor{attributeKeys=[abc]}");
}
}

View File

@ -5,11 +5,13 @@
package io.opentelemetry.sdk.metrics.internal.view;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.metrics.internal.view.ViewRegistry.DEFAULT_REGISTERED_VIEW;
import static io.opentelemetry.sdk.metrics.internal.view.ViewRegistry.toGlobPatternPredicate;
import static org.assertj.core.api.Assertions.assertThat;
import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.metrics.Aggregation;
@ -25,6 +27,7 @@ import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
import io.opentelemetry.sdk.metrics.internal.state.MetricStorage;
import java.util.Arrays;
import java.util.Collections;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@ -431,6 +434,117 @@ class ViewRegistryTest {
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void findViews_ApplyAdvice() {
// use incompatible aggregation for histogram
DefaultAggregationSelector aggregationSelector =
instrumentType ->
instrumentType == InstrumentType.HISTOGRAM
? Aggregation.lastValue()
: Aggregation.defaultAggregation();
RegisteredView registeredView =
registeredView(
InstrumentSelector.builder().setName("test").build(),
View.builder().setDescription("view applied").build());
ViewRegistry viewRegistry =
ViewRegistry.create(
aggregationSelector,
CardinalityLimitSelector.defaultCardinalityLimitSelector(),
Collections.singletonList(registeredView));
// If a view matches the descriptor, use it and ignore the advice
assertThat(
viewRegistry.findViews(
InstrumentDescriptor.create(
"test",
"",
"",
InstrumentType.COUNTER,
InstrumentValueType.DOUBLE,
Advice.builder()
.setAttributes(Arrays.asList(stringKey("key1"), stringKey("key2")))
.build()),
INSTRUMENTATION_SCOPE_INFO))
.isEqualTo(Collections.singletonList(registeredView));
// If there is no matching view and attributes advice was defined, use it
assertThat(
viewRegistry.findViews(
InstrumentDescriptor.create(
"advice",
"",
"",
InstrumentType.COUNTER,
InstrumentValueType.DOUBLE,
Advice.builder()
.setAttributes(Arrays.asList(stringKey("key1"), stringKey("key2")))
.build()),
INSTRUMENTATION_SCOPE_INFO))
.hasSize(1)
.element(0)
.satisfies(
view -> {
assertThat(view)
.as("is the same as the default view, except the attributes processor")
.usingRecursiveComparison()
.ignoringFields("viewAttributesProcessor")
.isEqualTo(DEFAULT_REGISTERED_VIEW);
assertThat(view)
.as("has the advice attributes processor")
.extracting("viewAttributesProcessor")
.isInstanceOf(AdviceAttributesProcessor.class)
.extracting(
"attributeKeys", InstanceOfAssertFactories.collection(AttributeKey.class))
.containsExactlyInAnyOrder(stringKey("key1"), stringKey("key2"));
});
// If there is no matching view and attributes advice was defined, use it - incompatible
// aggregation case
assertThat(
viewRegistry.findViews(
InstrumentDescriptor.create(
"histogram_advice",
"",
"",
InstrumentType.HISTOGRAM,
InstrumentValueType.DOUBLE,
Advice.builder()
.setAttributes(Arrays.asList(stringKey("key1"), stringKey("key2")))
.build()),
INSTRUMENTATION_SCOPE_INFO))
.hasSize(1)
.element(0)
.satisfies(
view -> {
assertThat(view)
.as("is the same as the default view, except the attributes processor")
.usingRecursiveComparison()
.ignoringFields("viewAttributesProcessor")
.isEqualTo(DEFAULT_REGISTERED_VIEW);
assertThat(view)
.as("has the advice attributes processor")
.extracting("viewAttributesProcessor")
.isInstanceOf(AdviceAttributesProcessor.class)
.extracting(
"attributeKeys", InstanceOfAssertFactories.collection(AttributeKey.class))
.containsExactlyInAnyOrder(stringKey("key1"), stringKey("key2"));
});
// if advice is not defined, use the default view
assertThat(
viewRegistry.findViews(
InstrumentDescriptor.create(
"advice",
"",
"",
InstrumentType.COUNTER,
InstrumentValueType.DOUBLE,
Advice.empty()),
INSTRUMENTATION_SCOPE_INFO))
.isEqualTo(Collections.singletonList(DEFAULT_REGISTERED_VIEW));
}
@Test
void matchesName() {
assertThat(toGlobPatternPredicate("foo").test("foo")).isTrue();