Implement metric identity specification (#4222)

* Implement metric identity specification

* PR feedback

* Fix build
This commit is contained in:
jack-berg 2022-03-15 12:46:27 -05:00 committed by GitHub
parent ef99593d4f
commit ccfa2dcbe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1375 additions and 290 deletions

View File

@ -11,6 +11,7 @@ import static java.util.stream.Collectors.toList;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.common.InstrumentType;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;
import io.opentelemetry.sdk.metrics.view.Aggregation;
import io.opentelemetry.sdk.metrics.view.InstrumentSelector;
import io.opentelemetry.sdk.metrics.view.InstrumentSelectorBuilder;
@ -182,17 +183,10 @@ public final class ViewConfig {
// Visible for testing
static Aggregation toAggregation(String aggregation) {
switch (aggregation) {
case "sum":
return Aggregation.sum();
case "last_value":
return Aggregation.lastValue();
case "drop":
return Aggregation.drop();
case "histogram":
return Aggregation.explicitBucketHistogram();
default:
throw new ConfigurationException("Unrecognized aggregation " + aggregation);
try {
return AggregationUtil.forName(aggregation);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Error creating aggregation", e);
}
}

View File

@ -132,14 +132,9 @@ class ViewConfigTest {
@Test
void toAggregation() {
assertThat(ViewConfig.toAggregation("sum")).isEqualTo(Aggregation.sum());
assertThat(ViewConfig.toAggregation("last_value")).isEqualTo(Aggregation.lastValue());
assertThat(ViewConfig.toAggregation("histogram"))
.isEqualTo(Aggregation.explicitBucketHistogram());
assertThat(ViewConfig.toAggregation("drop")).isEqualTo(Aggregation.drop());
assertThatThrownBy(() -> ViewConfig.toAggregation("foo"))
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining("Unrecognized aggregation foo");
.hasMessageContaining("Error creating aggregation");
}
@Test

View File

@ -0,0 +1,65 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics.internal.descriptor;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.sdk.metrics.common.InstrumentType;
import io.opentelemetry.sdk.metrics.common.InstrumentValueType;
import org.junit.jupiter.api.Test;
/**
* {@link InstrumentDescriptor#equals(Object)} must ignore {@link
* InstrumentDescriptor#getSourceInfo()}, which only returns a meaningful value when {@code
* otel.experimental.sdk.metrics.debug=true}.
*/
class InstrumentDescriptorTest {
@Test
void equals() {
InstrumentDescriptor descriptor =
InstrumentDescriptor.create(
"name", "description", "unit", InstrumentType.COUNTER, InstrumentValueType.LONG);
assertThat(descriptor)
.isEqualTo(
InstrumentDescriptor.create(
"name", "description", "unit", InstrumentType.COUNTER, InstrumentValueType.LONG));
// Validate getSourceInfo() is not equal for otherwise equal descriptors
assertThat(descriptor.getSourceInfo())
.isNotEqualTo(
InstrumentDescriptor.create(
"name", "description", "unit", InstrumentType.COUNTER, InstrumentValueType.LONG)
.getSourceInfo());
// Validate that name, description, unit, type, and value type are considered in equals
assertThat(descriptor)
.isNotEqualTo(
InstrumentDescriptor.create(
"foo", "description", "unit", InstrumentType.COUNTER, InstrumentValueType.LONG));
assertThat(descriptor)
.isNotEqualTo(
InstrumentDescriptor.create(
"name", "foo", "unit", InstrumentType.COUNTER, InstrumentValueType.LONG));
assertThat(descriptor)
.isNotEqualTo(
InstrumentDescriptor.create(
"name", "description", "foo", InstrumentType.COUNTER, InstrumentValueType.LONG));
assertThat(descriptor)
.isNotEqualTo(
InstrumentDescriptor.create(
"name",
"description",
"unit",
InstrumentType.UP_DOWN_COUNTER,
InstrumentValueType.LONG));
assertThat(descriptor)
.isNotEqualTo(
InstrumentDescriptor.create(
"name", "description", "unit", InstrumentType.COUNTER, InstrumentValueType.DOUBLE));
}
}

View File

@ -45,11 +45,16 @@ class SourceInfoTest {
MetricDescriptor.create(
View.builder().build(),
InstrumentDescriptor.create(
"name", "description2", "unit2", InstrumentType.COUNTER, InstrumentValueType.LONG));
"name2",
"description2",
"unit2",
InstrumentType.COUNTER,
InstrumentValueType.LONG));
assertThat(DebugUtils.duplicateMetricErrorMessage(simple, simpleWithNewDescription))
.contains("Found duplicate metric definition: name")
.contains("- Unit [unit2] does not match [unit]")
.contains("- Description [description2] does not match [description]")
.contains("- InstrumentDescription [description2] does not match [description]")
.contains("- InstrumentName [name2] does not match [name]")
.contains("- InstrumentUnit [unit2] does not match [unit]")
.contains("- InstrumentType [COUNTER] does not match [OBSERVABLE_COUNTER]")
.contains("- InstrumentValueType [LONG] does not match [DOUBLE]")
.contains(simple.getSourceInstrument().getSourceInfo().multiLineDebugString())
@ -60,10 +65,9 @@ class SourceInfoTest {
@Test
void testDuplicateExceptionMessage_viewBasedConflict() {
View problemView = View.builder().setName("name2").build();
MetricDescriptor simple =
MetricDescriptor.create(
problemView,
View.builder().setName("name2").build(),
InstrumentDescriptor.create(
"name",
"description",
@ -82,9 +86,9 @@ class SourceInfoTest {
assertThat(DebugUtils.duplicateMetricErrorMessage(simple, simpleWithNewDescription))
.contains("Found duplicate metric definition: name2")
.contains(simple.getSourceInstrument().getSourceInfo().multiLineDebugString())
.contains("- Description [description2] does not match [description]")
.contains("- InstrumentDescription [description2] does not match [description]")
.contains("Conflicting view registered")
.contains(ImmutableView.getSourceInfo(problemView).multiLineDebugString())
.contains(ImmutableView.getSourceInfo(simple.getSourceView()).multiLineDebugString())
.contains("FROM instrument name")
.contains(
simpleWithNewDescription.getSourceInstrument().getSourceInfo().multiLineDebugString());
@ -117,7 +121,7 @@ class SourceInfoTest {
.contains(ImmutableView.getSourceInfo(problemView).multiLineDebugString())
.contains("FROM instrument name2")
.contains(simple.getSourceInstrument().getSourceInfo().multiLineDebugString())
.contains("- Unit [unit] does not match [unit2]")
.contains("- InstrumentUnit [unit] does not match [unit2]")
.contains("Original instrument registered with same name but is incompatible.")
.contains(
simpleWithNewDescription.getSourceInstrument().getSourceInfo().multiLineDebugString());

View File

@ -0,0 +1,77 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics.internal.aggregator;
import io.opentelemetry.sdk.metrics.view.Aggregation;
import java.util.HashMap;
import java.util.Map;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public class AggregationUtil {
private static final Map<String, Aggregation> aggregationByName;
private static final Map<Class<? extends Aggregation>, String> nameByAggregation;
private static final String AGGREGATION_DEFAULT = "default";
private static final String AGGREGATION_SUM = "sum";
private static final String AGGREGATION_LAST_VALUE = "last_value";
private static final String AGGREGATION_DROP = "drop";
private static final String AGGREGATION_EXPLICIT_BUCKET_HISTOGRAM = "explicit_bucket_histogram";
static {
aggregationByName = new HashMap<>();
aggregationByName.put(AGGREGATION_DEFAULT, Aggregation.defaultAggregation());
aggregationByName.put(AGGREGATION_SUM, Aggregation.sum());
aggregationByName.put(AGGREGATION_LAST_VALUE, Aggregation.lastValue());
aggregationByName.put(AGGREGATION_DROP, Aggregation.drop());
aggregationByName.put(
AGGREGATION_EXPLICIT_BUCKET_HISTOGRAM, Aggregation.explicitBucketHistogram());
nameByAggregation = new HashMap<>();
nameByAggregation.put(Aggregation.defaultAggregation().getClass(), AGGREGATION_DEFAULT);
nameByAggregation.put(Aggregation.sum().getClass(), AGGREGATION_SUM);
nameByAggregation.put(Aggregation.lastValue().getClass(), AGGREGATION_LAST_VALUE);
nameByAggregation.put(Aggregation.drop().getClass(), AGGREGATION_DROP);
nameByAggregation.put(
Aggregation.explicitBucketHistogram().getClass(), AGGREGATION_EXPLICIT_BUCKET_HISTOGRAM);
}
private AggregationUtil() {}
/**
* Return the aggregation for the human-readable {@code name}.
*
* <p>The inverse of {@link #aggregationName(Aggregation)}.
*
* @throws IllegalArgumentException if the name is not recognized
*/
public static Aggregation forName(String name) {
Aggregation aggregation = aggregationByName.get(name);
if (aggregation == null) {
throw new IllegalArgumentException("Unrecognized aggregation name " + name);
}
return aggregation;
}
/**
* Return the human-readable name of the {@code aggregation}.
*
* <p>The inverse of {@link #forName(String)}.
*
* @throws IllegalArgumentException if the aggregation is not recognized
*/
public static String aggregationName(Aggregation aggregation) {
String name = nameByAggregation.get(aggregation.getClass());
if (name == null) {
throw new IllegalStateException(
"Unrecognized aggregation " + aggregation.getClass().getName());
}
return name;
}
}

View File

@ -132,7 +132,7 @@ final class DoubleExponentialHistogramAggregator
instrumentationScopeInfo,
metricDescriptor.getName(),
metricDescriptor.getDescription(),
metricDescriptor.getUnit(),
metricDescriptor.getSourceInstrument().getUnit(),
ExponentialHistogramData.create(
temporality,
MetricDataUtils.toExponentialHistogramPointList(

View File

@ -104,7 +104,7 @@ public final class DoubleHistogramAggregator implements Aggregator<HistogramAccu
instrumentationScopeInfo,
metricDescriptor.getName(),
metricDescriptor.getDescription(),
metricDescriptor.getUnit(),
metricDescriptor.getSourceInstrument().getUnit(),
ImmutableHistogramData.create(
temporality,
MetricDataUtils.toDoubleHistogramPointList(

View File

@ -73,7 +73,7 @@ public final class DoubleLastValueAggregator implements Aggregator<DoubleAccumul
instrumentationScopeInfo,
descriptor.getName(),
descriptor.getDescription(),
descriptor.getUnit(),
descriptor.getSourceInstrument().getUnit(),
ImmutableGaugeData.create(
MetricDataUtils.toDoublePointList(
accumulationByLabels,

View File

@ -85,7 +85,7 @@ public final class DoubleSumAggregator extends AbstractSumAggregator<DoubleAccum
instrumentationScopeInfo,
descriptor.getName(),
descriptor.getDescription(),
descriptor.getUnit(),
descriptor.getSourceInstrument().getUnit(),
ImmutableSumData.create(
isMonotonic(),
temporality,

View File

@ -70,7 +70,7 @@ public final class LongLastValueAggregator implements Aggregator<LongAccumulatio
instrumentationScopeInfo,
descriptor.getName(),
descriptor.getDescription(),
descriptor.getUnit(),
descriptor.getSourceInstrument().getUnit(),
ImmutableGaugeData.create(
MetricDataUtils.toLongPointList(
accumulationByLabels,

View File

@ -72,7 +72,7 @@ public final class LongSumAggregator extends AbstractSumAggregator<LongAccumulat
instrumentationScopeInfo,
descriptor.getName(),
descriptor.getDescription(),
descriptor.getUnit(),
descriptor.getSourceInstrument().getUnit(),
ImmutableSumData.create(
isMonotonic(),
temporality,

View File

@ -21,14 +21,16 @@ import javax.annotation.concurrent.Immutable;
@AutoValue
@Immutable
public abstract class InstrumentDescriptor {
private final SourceInfo sourceInfo = SourceInfo.fromCurrentStack();
public static InstrumentDescriptor create(
String name,
String description,
String unit,
InstrumentType type,
InstrumentValueType valueType) {
return new AutoValue_InstrumentDescriptor(
name, description, unit, type, valueType, SourceInfo.fromCurrentStack());
return new AutoValue_InstrumentDescriptor(name, description, unit, type, valueType);
}
public abstract String getName();
@ -41,8 +43,13 @@ public abstract class InstrumentDescriptor {
public abstract InstrumentValueType getValueType();
/** Debugging information for this instrument. */
public abstract SourceInfo getSourceInfo();
/**
* Debugging information for this instrument. Ignored from {@link #equals(Object)} and {@link
* #toString()}
*/
public final SourceInfo getSourceInfo() {
return sourceInfo;
}
@Memoized
@Override

View File

@ -9,9 +9,9 @@ import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import io.opentelemetry.sdk.metrics.common.InstrumentType;
import io.opentelemetry.sdk.metrics.common.InstrumentValueType;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;
import io.opentelemetry.sdk.metrics.view.Aggregation;
import io.opentelemetry.sdk.metrics.view.View;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.concurrent.Immutable;
/**
@ -27,7 +27,7 @@ import javax.annotation.concurrent.Immutable;
public abstract class MetricDescriptor {
/**
* Constructs a metric descriptor with no source instrument/view.
* Constructs a metric descriptor with no instrument and default view.
*
* <p>Used for testing + empty-storage only.
*/
@ -35,8 +35,7 @@ public abstract class MetricDescriptor {
return new AutoValue_MetricDescriptor(
name,
description,
unit,
Optional.empty(),
View.builder().build(),
InstrumentDescriptor.create(
name, description, unit, InstrumentType.OBSERVABLE_GAUGE, InstrumentValueType.DOUBLE));
}
@ -46,21 +45,32 @@ public abstract class MetricDescriptor {
String name = (view.getName() == null) ? instrument.getName() : view.getName();
String description =
(view.getDescription() == null) ? instrument.getDescription() : view.getDescription();
return new AutoValue_MetricDescriptor(
name, description, instrument.getUnit(), Optional.of(view), instrument);
return new AutoValue_MetricDescriptor(name, description, view, instrument);
}
/**
* The name of the descriptor, equal to {@link View#getName()} if not null, else {@link
* InstrumentDescriptor#getName()}.
*/
public abstract String getName();
/**
* The description of the descriptor, equal to {@link View#getDescription()} if not null, else
* {@link InstrumentDescriptor#getDescription()}.
*/
public abstract String getDescription();
public abstract String getUnit();
/** The view that lead to the creation of this metric. */
public abstract View getSourceView();
/** The view that lead to the creation of this metric, if applicable. */
public abstract Optional<View> getSourceView();
/** The instrument which lead to the creation of this metric. */
public abstract InstrumentDescriptor getSourceInstrument();
/** The {@link AggregationUtil#aggregationName(Aggregation)} of the view aggregation. */
public String getAggregationName() {
return AggregationUtil.aggregationName(getSourceView().getAggregation());
}
@Memoized
@Override
public abstract int hashCode();
@ -73,17 +83,24 @@ public abstract class MetricDescriptor {
* <ul>
* <li>{@link #getName()} is equal
* <li>{@link #getDescription()} is equal
* <li>{@link #getUnit()} is equal
* <li>{@link #getAggregationName()} is equal
* <li>{@link InstrumentDescriptor#getName()} is equal
* <li>{@link InstrumentDescriptor#getDescription()} is equal
* <li>{@link InstrumentDescriptor#getUnit()} is equal
* <li>{@link InstrumentDescriptor#getType()} is equal
* <li>{@link InstrumentDescriptor#getValueType()} is equal
* </ul>
*/
public boolean isCompatibleWith(MetricDescriptor other) {
return Objects.equals(getName(), other.getName())
&& Objects.equals(getDescription(), other.getDescription())
&& Objects.equals(getUnit(), other.getUnit())
&& Objects.equals(getSourceInstrument().getType(), other.getSourceInstrument().getType())
&& Objects.equals(
getSourceInstrument().getValueType(), other.getSourceInstrument().getValueType());
return getName().equals(other.getName())
&& getDescription().equals(other.getDescription())
&& getAggregationName().equals(other.getAggregationName())
&& getSourceInstrument().getName().equals(other.getSourceInstrument().getName())
&& getSourceInstrument()
.getDescription()
.equals(other.getSourceInstrument().getDescription())
&& getSourceInstrument().getUnit().equals(other.getSourceInstrument().getUnit())
&& getSourceInstrument().getType().equals(other.getSourceInstrument().getType())
&& getSourceInstrument().getValueType().equals(other.getSourceInstrument().getValueType());
}
}

View File

@ -19,12 +19,11 @@ import io.opentelemetry.sdk.metrics.internal.view.ImmutableView;
public final class DebugUtils {
private DebugUtils() {}
static String duplicateMetricErrorMessage(DuplicateMetricStorageException ex) {
return duplicateMetricErrorMessage(ex.getExisting(), ex.getConflict());
}
/**
* Creates a detailed error message comparing two MetricDescriptors.
* Creates a detailed error message comparing two {@link MetricDescriptor}s.
*
* <p>Called when the metrics with the descriptors have the same name, but {@link
* MetricDescriptor#isCompatibleWith(MetricDescriptor)} is {@code false}.
*
* <p>This should identify all issues between the descriptor and log information on where they are
* defined. Users should be able to find/fix issues based on this error.
@ -43,11 +42,9 @@ public final class DebugUtils {
// or a view on a raw instrument.
if (!conflict.getName().equals(conflict.getSourceInstrument().getName())) {
// Record the source view.
result.append("\tVIEW defined\n");
conflict
.getSourceView()
.ifPresent(v -> result.append(ImmutableView.getSourceInfo(v).multiLineDebugString()));
result
.append("\tVIEW defined\n")
.append(ImmutableView.getSourceInfo(conflict.getSourceView()).multiLineDebugString())
.append("\tFROM instrument ")
.append(conflict.getSourceInstrument().getName())
.append("\n")
@ -59,6 +56,14 @@ public final class DebugUtils {
}
// Add information on what's at conflict.
result.append("Causes\n");
if (!existing.getName().equals(conflict.getName())) {
result
.append("- Name [")
.append(conflict.getName())
.append("] does not match [")
.append(existing.getName())
.append("]\n");
}
if (!existing.getDescription().equals(conflict.getDescription())) {
result
.append("- Description [")
@ -67,12 +72,45 @@ public final class DebugUtils {
.append(existing.getDescription())
.append("]\n");
}
if (!existing.getUnit().equals(conflict.getUnit())) {
if (!existing.getAggregationName().equals(conflict.getAggregationName())) {
result
.append("- Unit [")
.append(conflict.getUnit())
.append("- Aggregation [")
.append(conflict.getAggregationName())
.append("] does not match [")
.append(existing.getUnit())
.append(existing.getAggregationName())
.append("]\n");
}
if (!existing
.getSourceInstrument()
.getName()
.equals(conflict.getSourceInstrument().getName())) {
result
.append("- InstrumentName [")
.append(conflict.getSourceInstrument().getName())
.append("] does not match [")
.append(existing.getSourceInstrument().getName())
.append("]\n");
}
if (!existing
.getSourceInstrument()
.getDescription()
.equals(conflict.getSourceInstrument().getDescription())) {
result
.append("- InstrumentDescription [")
.append(conflict.getSourceInstrument().getDescription())
.append("] does not match [")
.append(existing.getSourceInstrument().getDescription())
.append("]\n");
}
if (!existing
.getSourceInstrument()
.getUnit()
.equals(conflict.getSourceInstrument().getUnit())) {
result
.append("- InstrumentUnit [")
.append(conflict.getSourceInstrument().getUnit())
.append("] does not match [")
.append(existing.getSourceInstrument().getUnit())
.append("]\n");
}
if (!existing
@ -107,12 +145,9 @@ public final class DebugUtils {
.append("\n");
} else {
// Log that the view changed the name.
result.append("Conflicting view registered.\n");
existing
.getSourceView()
.ifPresent(
view -> result.append(ImmutableView.getSourceInfo(view).multiLineDebugString()));
result
.append("Conflicting view registered.\n")
.append(ImmutableView.getSourceInfo(existing.getSourceView()).multiLineDebugString())
.append("FROM instrument ")
.append(existing.getSourceInstrument().getName())
.append("\n")

View File

@ -17,11 +17,7 @@ import io.opentelemetry.sdk.metrics.internal.export.CollectionInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
@ -34,8 +30,6 @@ import javax.annotation.concurrent.Immutable;
@Immutable
public abstract class MeterSharedState {
private static final Logger logger = Logger.getLogger(MeterSharedState.class.getName());
public static MeterSharedState create(InstrumentationScopeInfo instrumentationScopeInfo) {
return new AutoValue_MeterSharedState(instrumentationScopeInfo, new MetricStorageRegistry());
}
@ -77,7 +71,7 @@ public abstract class MeterSharedState {
public final WriteableMetricStorage registerSynchronousMetricStorage(
InstrumentDescriptor instrument, MeterProviderSharedState meterProviderSharedState) {
List<WriteableMetricStorage> storage =
List<SynchronousMetricStorage> storages =
meterProviderSharedState
.getViewRegistry()
.findViews(instrument, getInstrumentationScopeInfo())
@ -87,15 +81,17 @@ public abstract class MeterSharedState {
SynchronousMetricStorage.create(
view, instrument, meterProviderSharedState.getExemplarFilter()))
.filter(m -> !m.isEmpty())
.map(this::register)
.filter(Objects::nonNull)
.collect(toList());
if (storage.size() == 1) {
return storage.get(0);
List<SynchronousMetricStorage> registeredStorages = new ArrayList<>(storages.size());
for (SynchronousMetricStorage storage : storages) {
registeredStorages.add(getMetricStorageRegistry().register(storage));
}
// If the size is 0, we return an, effectively, no-op writer.
return new MultiWritableMetricStorage(storage);
if (registeredStorages.size() == 1) {
return registeredStorages.get(0);
}
return new MultiWritableMetricStorage(registeredStorages);
}
/** Registers new asynchronous storage associated with a given {@code long} instrument. */
@ -117,11 +113,10 @@ public abstract class MeterSharedState {
List<AsynchronousMetricStorage<?, ObservableLongMeasurement>> registeredStorages =
new ArrayList<>();
for (AsynchronousMetricStorage<?, ObservableLongMeasurement> storage : storages) {
AsynchronousMetricStorage<?, ObservableLongMeasurement> registeredStorage = register(storage);
if (registeredStorage != null) {
registeredStorage.addCallback(callback);
registeredStorages.add(registeredStorage);
}
AsynchronousMetricStorage<?, ObservableLongMeasurement> registeredStorage =
getMetricStorageRegistry().register(storage);
registeredStorage.addCallback(callback);
registeredStorages.add(registeredStorage);
}
return registeredStorages;
}
@ -146,24 +141,10 @@ public abstract class MeterSharedState {
new ArrayList<>();
for (AsynchronousMetricStorage<?, ObservableDoubleMeasurement> storage : storages) {
AsynchronousMetricStorage<?, ObservableDoubleMeasurement> registeredStorage =
register(storage);
if (registeredStorage != null) {
registeredStorage.addCallback(callback);
registeredStorages.add(registeredStorage);
}
getMetricStorageRegistry().register(storage);
registeredStorage.addCallback(callback);
registeredStorages.add(registeredStorage);
}
return registeredStorages;
}
@Nullable
private <S extends MetricStorage> S register(S storage) {
try {
return getMetricStorageRegistry().register(storage);
} catch (DuplicateMetricStorageException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, DebugUtils.duplicateMetricErrorMessage(e), e);
}
}
return null;
}
}

View File

@ -5,71 +5,84 @@
package io.opentelemetry.sdk.metrics.internal.state;
import io.opentelemetry.api.internal.GuardedBy;
import io.opentelemetry.sdk.metrics.internal.descriptor.MetricDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Responsible for storing metrics (by name) and returning access to input pipeline for instrument
* wiring.
* Responsible for storing metrics by {@link MetricDescriptor} and returning access to input
* pipeline for instrument measurements.
*
* <p>The rules of the registry:
*
* <ul>
* <li>Only one storage type may be registered per-name. Repeated look-ups per-name will return
* the same storage.
* <li>The metric descriptor should be "compatible", when returning an existing metric storage,
* i.e. same type of metric, same name, description etc.
* <li>The registered storage type MUST be either always Asynchronous or always Synchronous. No
* mixing and matching.
* </ul>
* <p>Each descriptor in the registry results in an exported metric stream. Under normal
* circumstances each descriptor shares a unique {@link MetricDescriptor#getName()}. When multiple
* descriptors share the same name, an identity conflict has occurred. The registry detects identity
* conflicts on {@link #register(MetricStorage)} and logs diagnostic information when they occur.
* See {@link MetricDescriptor#isCompatibleWith(MetricDescriptor)} for definition of compatibility.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public class MetricStorageRegistry {
// TODO: Maybe we store metrics *and* instrument interfaces separately here...
private final ConcurrentMap<String, MetricStorage> registry = new ConcurrentHashMap<>();
private static final Logger logger = Logger.getLogger(MetricStorageRegistry.class.getName());
/**
* Returns a {@code Collection} view of the registered {@link MetricStorage}.
*
* @return a {@code Collection} view of the registered {@link MetricStorage}.
*/
private final Object lock = new Object();
@GuardedBy("lock")
private final Map<MetricDescriptor, MetricStorage> registry = new HashMap<>();
/** Returns a {@link Collection} of the registered {@link MetricStorage}. */
public Collection<MetricStorage> getMetrics() {
return Collections.unmodifiableCollection(new ArrayList<>(registry.values()));
synchronized (lock) {
return Collections.unmodifiableCollection(new ArrayList<>(registry.values()));
}
}
/**
* Registers the given {@code Metric} to this registry. Returns the registered storage if no other
* metric with the same name is registered or a previously registered metric with same name and
* equal with the current metric, otherwise throws an exception.
* Registers the metric {@code newStorage} to this registry. If a metric with compatible identity
* was previously registered, returns the previously registered storage. If a metric with the same
* name (case-insensitive) but incompatible {@link MetricDescriptor} was previously registered,
* logs a diagnostic warning and returns the {@code newStorage}.
*
* @param storage the metric storage to use or discard.
* @return the given metric storage if no metric with same name already registered, otherwise the
* previous registered instrument.
* @throws IllegalArgumentException if instrument cannot be registered.
* @param newStorage the metric storage to use or discard.
* @return the {@code newStorage} if no compatible metric is already registered, otherwise the
* previously registered storage.
*/
@SuppressWarnings("unchecked")
public <I extends MetricStorage> I register(I storage) {
MetricDescriptor descriptor = storage.getMetricDescriptor();
MetricStorage oldOrNewStorage =
registry.computeIfAbsent(descriptor.getName().toLowerCase(), key -> storage);
// Metric didn't already exist in registry, return.
if (storage == oldOrNewStorage) {
return (I) oldOrNewStorage;
public <I extends MetricStorage> I register(I newStorage) {
MetricDescriptor descriptor = newStorage.getMetricDescriptor();
I oldOrNewStorage;
List<MetricStorage> storages;
synchronized (lock) {
oldOrNewStorage = (I) registry.computeIfAbsent(descriptor, key -> newStorage);
// If storage was NOT added to the registry, its description was a perfect match to one
// previously registered and we can skip detecting identity conflicts
if (newStorage != oldOrNewStorage || !logger.isLoggable(Level.WARNING)) {
return oldOrNewStorage;
}
storages = new ArrayList<>(registry.values());
}
// Make sure the storage is compatible.
if (!oldOrNewStorage.getMetricDescriptor().isCompatibleWith(descriptor)) {
throw new DuplicateMetricStorageException(
oldOrNewStorage.getMetricDescriptor(),
descriptor,
"Metric with same name and different descriptor already created.");
// Else, we need to look for identity conflicts with previously registered storages
for (MetricStorage storage : storages) {
// Skip the newly registered storage
if (storage == newStorage) {
continue;
}
MetricDescriptor existing = storage.getMetricDescriptor();
// Check compatibility of metrics which share the same case-insensitive name
if (existing.getName().equalsIgnoreCase(descriptor.getName())
&& !existing.isCompatibleWith(descriptor)) {
logger.log(Level.WARNING, DebugUtils.duplicateMetricErrorMessage(existing, descriptor));
break; // Only log information about the first conflict found to reduce noise
}
}
// Metric already existed, and is compatible with new storage.
return (I) oldOrNewStorage;
// Finally, return the storage
return oldOrNewStorage;
}
}

View File

@ -10,9 +10,9 @@ import java.util.ArrayList;
import java.util.List;
class MultiWritableMetricStorage implements WriteableMetricStorage {
private final List<WriteableMetricStorage> underlyingMetrics;
private final List<? extends WriteableMetricStorage> underlyingMetrics;
MultiWritableMetricStorage(List<WriteableMetricStorage> metrics) {
MultiWritableMetricStorage(List<? extends WriteableMetricStorage> metrics) {
this.underlyingMetrics = metrics;
}

View File

@ -0,0 +1,925 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics;
import static io.opentelemetry.sdk.testing.assertj.MetricAssertions.assertThat;
import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.metrics.common.InstrumentType;
import io.opentelemetry.sdk.metrics.internal.state.MetricStorageRegistry;
import io.opentelemetry.sdk.metrics.view.Aggregation;
import io.opentelemetry.sdk.metrics.view.InstrumentSelector;
import io.opentelemetry.sdk.metrics.view.View;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import java.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class IdentityTest {
@RegisterExtension
LogCapturer logs = LogCapturer.create().captureForType(MetricStorageRegistry.class);
private InMemoryMetricReader reader;
private SdkMeterProviderBuilder builder;
@BeforeEach
void setup() {
reader = InMemoryMetricReader.createDelta();
builder =
SdkMeterProvider.builder()
.registerMetricReader(reader)
.setMinimumCollectionInterval(Duration.ZERO);
}
@Test
void sameMeterSameInstrumentNoViews() {
// Instruments are the same if their name, type, value type, description, and unit are all
// equal.
SdkMeterProvider meterProvider = builder.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)));
meterProvider.get("meter2").counterBuilder("counter2").ofDoubles().build().add(10);
meterProvider.get("meter2").counterBuilder("counter2").ofDoubles().build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter2"))
.hasName("counter2")
.hasDoubleSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)));
meterProvider
.get("meter3")
.counterBuilder("counter3")
.setDescription("description3")
.build()
.add(10);
meterProvider
.get("meter3")
.counterBuilder("counter3")
.setDescription("description3")
.build()
.add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter3"))
.hasName("counter3")
.hasDescription("description3")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)));
meterProvider.get("meter4").counterBuilder("counter4").setUnit("unit4").build().add(10);
meterProvider.get("meter4").counterBuilder("counter4").setUnit("unit4").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter4"))
.hasName("counter4")
.hasUnit("unit4")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)));
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void sameMeterDifferentInstrumentNoViews() {
SdkMeterProvider meterProvider = builder.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter2").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter2")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void differentMeterSameInstrumentNoViews() {
// Meters are the same if their name, version, and scope are all equals
SdkMeterProvider meterProvider = builder.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter2").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter2"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider
.meterBuilder("meter1")
.setInstrumentationVersion("version1")
.build()
.counterBuilder("counter1")
.build()
.add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(
InstrumentationScopeInfo.create("meter1", "version1", null))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
meterProvider
.meterBuilder("meter1")
.setInstrumentationVersion("version1")
.build()
.counterBuilder("counter1")
.build()
.add(10);
meterProvider
.meterBuilder("meter1")
.setInstrumentationVersion("version1")
.setSchemaUrl("schema1")
.build()
.counterBuilder("counter1")
.build()
.add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(
InstrumentationScopeInfo.create("meter1", "version1", null))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(
InstrumentationScopeInfo.create("meter1", "version1", "schema1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents()).hasSize(0);
}
@Test
@SuppressLogger(MetricStorageRegistry.class)
void sameMeterConflictingInstrumentDescriptionNoViews() {
// Instruments with the same name but different descriptions are in conflict.
SdkMeterProvider meterProvider = builder.build();
meterProvider
.get("meter1")
.counterBuilder("counter1")
.setDescription("description1")
.build()
.add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getDescription()).isBlank();
});
assertThat(logs.getEvents())
.allSatisfy(
logEvent ->
assertThat(logEvent.getMessage()).contains("Found duplicate metric definition"))
.hasSize(1);
}
@Test
@SuppressLogger(MetricStorageRegistry.class)
void sameMeterConflictingInstrumentUnitNoViews() {
// Instruments with the same name but different units are in conflict.
SdkMeterProvider meterProvider = builder.build();
meterProvider.get("meter1").counterBuilder("counter1").setUnit("unit1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasUnit("unit1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getUnit()).isEqualTo("1");
});
assertThat(logs.getEvents())
.allSatisfy(
logEvent ->
assertThat(logEvent.getMessage()).contains("Found duplicate metric definition"))
.hasSize(1);
}
@Test
@SuppressLogger(MetricStorageRegistry.class)
void sameMeterConflictingInstrumentTypeNoViews() {
// Instruments with the same name but different types are in conflict.
SdkMeterProvider meterProvider = builder.build();
meterProvider.get("meter1").upDownCounterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.isNotMonotonic()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.isMonotonic()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents())
.allSatisfy(
logEvent ->
assertThat(logEvent.getMessage()).contains("Found duplicate metric definition"))
.hasSize(1);
}
@Test
@SuppressLogger(MetricStorageRegistry.class)
void sameMeterConflictingInstrumentValueTypeNoViews() {
// Instruments with the same name but different instrument value types are in conflict.
SdkMeterProvider meterProvider = builder.build();
meterProvider.get("meter1").counterBuilder("counter1").ofDoubles().build().add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDoubleSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents())
.allSatisfy(
logEvent ->
assertThat(logEvent.getMessage()).contains("Found duplicate metric definition"))
.hasSize(1);
}
@Test
void sameMeterSameInstrumentSingleView() {
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setDescription("description1").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)));
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void differentMeterSameInstrumentSingleView() {
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setDescription("description1").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter2").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter2"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void sameMeterDifferentInstrumentSingleView() {
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setDescription("description1").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter2").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter2")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void sameMeterDifferentInstrumentViewSelectingInstrumentName() {
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setName("counter1").build(),
View.builder().setDescription("description1").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter2").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter2")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getDescription()).isBlank();
});
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void sameMeterDifferentInstrumentViewSelectingInstrumentType() {
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setDescription("description1").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").upDownCounterBuilder("counter2").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter2")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getDescription()).isBlank();
});
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void differentMeterSameInstrumentViewSelectingMeterName() {
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setMeterName("meter1").build(),
View.builder().setDescription("description1").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter2").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter2"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getDescription()).isBlank();
});
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void differentMeterSameInstrumentViewSelectingMeterVersion() {
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setMeterVersion("version1").build(),
View.builder().setDescription("description1").build())
.build();
meterProvider
.meterBuilder("meter1")
.setInstrumentationVersion("version1")
.build()
.counterBuilder("counter1")
.build()
.add(10);
meterProvider
.meterBuilder("meter1")
.setInstrumentationVersion("version2")
.build()
.counterBuilder("counter1")
.build()
.add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(
InstrumentationScopeInfo.create("meter1", "version1", null))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(
InstrumentationScopeInfo.create("meter1", "version2", null))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getDescription()).isBlank();
});
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void differentMeterSameInstrumentViewSelectingMeterSchema() {
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setMeterSchemaUrl("schema1").build(),
View.builder().setDescription("description1").build())
.build();
meterProvider
.meterBuilder("meter1")
.setSchemaUrl("schema1")
.build()
.counterBuilder("counter1")
.build()
.add(10);
meterProvider
.meterBuilder("meter1")
.setSchemaUrl("schema2")
.build()
.counterBuilder("counter1")
.build()
.add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(
InstrumentationScopeInfo.create("meter1", null, "schema1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(
InstrumentationScopeInfo.create("meter1", null, "schema2"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getDescription()).isBlank();
});
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void differentMeterDifferentInstrumentViewSelectingInstrumentNameAndMeterName() {
// A view selecting based on meter name and instrument name should not affect different
// instruments in the selected meter, or the same instrument in a different meter.
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setMeterName("meter1").setName("counter1").build(),
View.builder().setDescription("description1").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter2").build().add(10);
meterProvider.get("meter2").upDownCounterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter2")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getDescription()).isBlank();
},
metricData -> {
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter2"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10));
assertThat(metricData.getDescription()).isBlank();
});
assertThat(logs.getEvents()).hasSize(0);
}
@Test
@SuppressLogger(MetricStorageRegistry.class)
void sameMeterSameInstrumentConflictingViewDescriptions() {
// Registering multiple views that select the same instrument(s) and change the description
// produces an identity conflict as description is part of instrument identity.
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setDescription("description1").build())
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setDescription("description2").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description2")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)));
assertThat(logs.getEvents())
.allSatisfy(
logEvent ->
assertThat(logEvent.getMessage()).contains("Found duplicate metric definition"))
.hasSize(1);
}
@Test
@SuppressLogger(MetricStorageRegistry.class)
void sameMeterSameInstrumentConflictingViewAggregations() {
// Registering multiple views that select the same instrument(s) and change the aggregation
// produces an identity conflict as aggregation is part of instrument identity.
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setAggregation(Aggregation.defaultAggregation()).build())
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setAggregation(Aggregation.lastValue()).build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasLongGauge()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents())
.allSatisfy(
logEvent ->
assertThat(logEvent.getMessage()).contains("Found duplicate metric definition"))
.hasSize(1);
}
@Test
@SuppressLogger(MetricStorageRegistry.class)
void sameMeterDifferentInstrumentConflictingViewName() {
// A view that selects multiple instruments and sets the name produces an identity conflict. If
// it could, views could be used to merge compatible instruments, which is out of scope.
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setName("counter-new").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter2").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter-new")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter-new")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents())
.allSatisfy(
logEvent ->
assertThat(logEvent.getMessage()).contains("Found duplicate metric definition"))
.hasSize(1);
}
@Test
void differentMeterDifferentInstrumentViewSetsName() {
// A view can select multiple instruments and set the name without producing a conflict if the
// instruments belong to different meters.
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setName("counter-new").build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter2").counterBuilder("counter2").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter-new")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter2"))
.hasName("counter-new")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents()).hasSize(0);
}
@Test
void sameMeterDifferentInstrumentCompatibleViews() {
// Multiple views can select the same instrument(s) without conflict if they produce instruments
// with unique identities. For example, one view might change part of the instrument identity
// (description, aggregation) and another might change the aggregation and name to avoid
// identity conflicts.
SdkMeterProvider meterProvider =
builder
.registerView(
InstrumentSelector.builder().setType(InstrumentType.COUNTER).build(),
View.builder().setDescription("description1").build())
.registerView(
InstrumentSelector.builder().setName("counter1").build(),
View.builder()
.setName("counter1-gauge")
.setAggregation(Aggregation.lastValue())
.build())
.build();
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
meterProvider.get("meter1").counterBuilder("counter1").build().add(10);
assertThat(reader.collectAllMetrics())
.satisfiesExactlyInAnyOrder(
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1")
.hasDescription("description1")
.hasLongSum()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(20)),
metricData ->
assertThat(metricData)
.hasInstrumentationScope(forMeter("meter1"))
.hasName("counter1-gauge")
.hasLongGauge()
.points()
.satisfiesExactly(point -> assertThat(point).hasValue(10)));
assertThat(logs.getEvents()).hasSize(0);
}
private static InstrumentationScopeInfo forMeter(String meterName) {
return InstrumentationScopeInfo.create(meterName);
}
}

View File

@ -6,7 +6,6 @@
package io.opentelemetry.sdk.metrics;
import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.event.Level.WARN;
import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.api.metrics.DoubleCounter;
@ -17,19 +16,20 @@ import io.opentelemetry.api.metrics.LongHistogram;
import io.opentelemetry.api.metrics.LongUpDownCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.metrics.internal.state.MeterSharedState;
import io.opentelemetry.sdk.metrics.internal.state.MetricStorageRegistry;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@SuppressLogger(MeterSharedState.class)
@SuppressLogger(MetricStorageRegistry.class)
class SdkMeterTest {
// Meter must have an exporter configured to actual run.
private final SdkMeterProvider testMeterProvider =
SdkMeterProvider.builder().registerMetricReader(InMemoryMetricReader.create()).build();
private final Meter sdkMeter = testMeterProvider.get(getClass().getName());
@RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(MeterSharedState.class);
@RegisterExtension
LogCapturer logs = LogCapturer.create().captureForType(MetricStorageRegistry.class);
@Test
void testLongCounter() {
@ -51,12 +51,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.counterBuilder("testLongCounter").build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -69,12 +64,7 @@ class SdkMeterTest {
.build();
assertThat(longCounter).isNotNull();
sdkMeter.counterBuilder("testLongCounter".toUpperCase()).build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -98,12 +88,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.upDownCounterBuilder("testLongUpDownCounter").build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -117,12 +102,7 @@ class SdkMeterTest {
assertThat(longUpDownCounter).isNotNull();
assertThat(logs.getEvents()).isEmpty();
sdkMeter.upDownCounterBuilder("testLongUpDownCounter".toUpperCase()).build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -148,12 +128,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.histogramBuilder("testLongValueRecorder").ofLongs().build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -169,12 +144,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.histogramBuilder("testLongValueRecorder".toUpperCase()).ofLongs().build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -188,12 +158,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.gaugeBuilder("longValueObserver").ofLongs().buildWithCallback(x -> {});
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -207,12 +172,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.gaugeBuilder("longValueObserver".toUpperCase()).ofLongs().buildWithCallback(x -> {});
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -225,12 +185,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.counterBuilder("testLongSumObserver").buildWithCallback(x -> {});
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -243,12 +198,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.counterBuilder("testLongSumObserver".toUpperCase()).buildWithCallback(x -> {});
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -261,12 +211,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.upDownCounterBuilder("testLongUpDownSumObserver").buildWithCallback(x -> {});
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -281,12 +226,7 @@ class SdkMeterTest {
sdkMeter
.upDownCounterBuilder("testLongUpDownSumObserver".toUpperCase())
.buildWithCallback(x -> {});
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -311,12 +251,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.counterBuilder("testDoubleCounter").ofDoubles().build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -341,12 +276,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.upDownCounterBuilder("testDoubleUpDownCounter").ofDoubles().build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -369,12 +299,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.histogramBuilder("testDoubleValueRecorder").build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -388,12 +313,7 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.counterBuilder("testDoubleSumObserver").ofDoubles().buildWithCallback(x -> {});
sdkMeter.histogramBuilder("testDoubleValueRecorder").build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -411,12 +331,7 @@ class SdkMeterTest {
.ofDoubles()
.buildWithCallback(x -> {});
sdkMeter.histogramBuilder("testDoubleValueRecorder").build();
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -429,11 +344,6 @@ class SdkMeterTest {
assertThat(logs.getEvents()).isEmpty();
sdkMeter.gaugeBuilder("doubleValueObserver").buildWithCallback(x -> {});
assertThat(
logs.assertContains(
loggingEvent -> loggingEvent.getLevel().equals(WARN),
"Failed to register metric.")
.getThrowable())
.hasMessageContaining("Metric with same name and different descriptor already created.");
logs.assertContains("Found duplicate metric definition");
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics.internal.aggregator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import io.opentelemetry.sdk.metrics.view.Aggregation;
import org.junit.jupiter.api.Test;
class AggregationUtilTest {
@Test
void forName() {
assertThat(AggregationUtil.forName("sum")).isEqualTo(Aggregation.sum());
assertThat(AggregationUtil.forName("last_value")).isEqualTo(Aggregation.lastValue());
assertThat(AggregationUtil.forName("explicit_bucket_histogram"))
.isEqualTo(Aggregation.explicitBucketHistogram());
assertThat(AggregationUtil.forName("drop")).isEqualTo(Aggregation.drop());
assertThatThrownBy(() -> AggregationUtil.forName("foo"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Unrecognized aggregation name foo");
}
@Test
void aggregationName() {
assertThat(AggregationUtil.aggregationName(Aggregation.defaultAggregation()))
.isEqualTo("default");
assertThat(AggregationUtil.aggregationName(Aggregation.sum())).isEqualTo("sum");
assertThat(AggregationUtil.aggregationName(Aggregation.lastValue())).isEqualTo("last_value");
assertThat(AggregationUtil.aggregationName(Aggregation.drop())).isEqualTo("drop");
assertThat(AggregationUtil.aggregationName(Aggregation.explicitBucketHistogram()))
.isEqualTo("explicit_bucket_histogram");
assertThatThrownBy(() -> AggregationUtil.aggregationName(new Aggregation() {}))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Unrecognized aggregation");
}
}

View File

@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.sdk.metrics.common.InstrumentType;
import io.opentelemetry.sdk.metrics.common.InstrumentValueType;
import io.opentelemetry.sdk.metrics.view.Aggregation;
import io.opentelemetry.sdk.metrics.view.View;
import org.junit.jupiter.api.Test;
@ -23,9 +24,9 @@ class MetricDescriptorTest {
MetricDescriptor simple = MetricDescriptor.create(view, instrument);
assertThat(simple.getName()).isEqualTo("name");
assertThat(simple.getDescription()).isEqualTo("description");
assertThat(simple.getUnit()).isEqualTo("unit");
assertThat(simple.getSourceView()).contains(view);
assertThat(simple.getSourceView()).isEqualTo(view);
assertThat(simple.getSourceInstrument()).isEqualTo(instrument);
assertThat(simple.getAggregationName()).isEqualTo("default");
}
@Test
@ -37,24 +38,24 @@ class MetricDescriptorTest {
MetricDescriptor simple = MetricDescriptor.create(view, instrument);
assertThat(simple.getName()).isEqualTo("new_name");
assertThat(simple.getDescription()).isEqualTo("new_description");
assertThat(simple.getUnit()).isEqualTo("unit");
assertThat(simple.getSourceInstrument()).isEqualTo(instrument);
assertThat(simple.getSourceView()).contains(view);
assertThat(simple.getSourceView()).isEqualTo(view);
assertThat(simple.getAggregationName()).isEqualTo("default");
}
@Test
void metricDescriptor_isCompatible() {
View view = View.builder().build();
MetricDescriptor descriptor =
MetricDescriptor.create(
view,
InstrumentDescriptor.create(
"name", "description", "unit", InstrumentType.COUNTER, InstrumentValueType.DOUBLE));
// Same name, description, unit, instrument type, and value type is compatible
InstrumentDescriptor instrument =
InstrumentDescriptor.create(
"name", "description", "unit", InstrumentType.COUNTER, InstrumentValueType.DOUBLE);
MetricDescriptor descriptor = MetricDescriptor.create(view, instrument);
// Same name, description, source name, source description, source unit, source type, and source
// value type is compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(
view,
View.builder().build(),
InstrumentDescriptor.create(
"name",
"description",
@ -62,7 +63,23 @@ class MetricDescriptorTest {
InstrumentType.COUNTER,
InstrumentValueType.DOUBLE))))
.isTrue();
// Different name is not compatible
// Different name overridden by view is not compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(View.builder().setName("bar").build(), instrument)))
.isFalse();
// Different description overridden by view is not compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(View.builder().setDescription("foo").build(), instrument)))
.isFalse();
// Different aggregation overridden by view is not compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(
View.builder().setAggregation(Aggregation.lastValue()).build(), instrument)))
.isFalse();
// Different instrument source name is not compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(
@ -74,7 +91,7 @@ class MetricDescriptorTest {
InstrumentType.COUNTER,
InstrumentValueType.DOUBLE))))
.isFalse();
// Different description is not compatible
// Different instrument source description is not compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(
@ -86,7 +103,7 @@ class MetricDescriptorTest {
InstrumentType.COUNTER,
InstrumentValueType.DOUBLE))))
.isFalse();
// Different unit is not compatible
// Different instrument source unit is not compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(
@ -98,7 +115,7 @@ class MetricDescriptorTest {
InstrumentType.COUNTER,
InstrumentValueType.DOUBLE))))
.isFalse();
// Different instrument type is not compatible
// Different instrument source type is not compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(
@ -110,7 +127,7 @@ class MetricDescriptorTest {
InstrumentType.HISTOGRAM,
InstrumentValueType.DOUBLE))))
.isFalse();
// Different instrument value type is not compatible
// Different instrument source value type is not compatible
assertThat(
descriptor.isCompatibleWith(
MetricDescriptor.create(

View File

@ -6,9 +6,10 @@
package io.opentelemetry.sdk.metrics.internal.state;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.metrics.common.InstrumentType;
import io.opentelemetry.sdk.metrics.common.InstrumentValueType;
@ -19,8 +20,10 @@ import io.opentelemetry.sdk.metrics.internal.export.CollectionInfo;
import io.opentelemetry.sdk.metrics.view.View;
import io.opentelemetry.sdk.resources.Resource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link MetricStorageRegistry}. */
@SuppressLogger(MetricStorageRegistry.class)
class MetricStorageRegistryTest {
private static final MetricDescriptor SYNC_DESCRIPTOR =
descriptor("sync", "description", InstrumentType.COUNTER);
@ -31,6 +34,9 @@ class MetricStorageRegistryTest {
private static final MetricDescriptor OTHER_ASYNC_DESCRIPTOR =
descriptor("async", "other_description", InstrumentType.OBSERVABLE_GAUGE);
@RegisterExtension
LogCapturer logs = LogCapturer.create().captureForType(MetricStorageRegistry.class);
private final MetricStorageRegistry metricStorageRegistry = new MetricStorageRegistry();
@Test
@ -46,11 +52,10 @@ class MetricStorageRegistryTest {
void register_SyncIncompatibleDescriptor() {
TestMetricStorage storage = new TestMetricStorage(SYNC_DESCRIPTOR);
assertThat(metricStorageRegistry.register(storage)).isSameAs(storage);
assertThatThrownBy(
() -> metricStorageRegistry.register(new TestMetricStorage(OTHER_SYNC_DESCRIPTOR)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Metric with same name and different descriptor already created.");
assertThat(logs.getEvents()).isEmpty();
assertThat(metricStorageRegistry.register(new TestMetricStorage(OTHER_SYNC_DESCRIPTOR)))
.isNotSameAs(storage);
logs.assertContains("Found duplicate metric definition");
}
@Test
@ -66,11 +71,10 @@ class MetricStorageRegistryTest {
void register_AsyncIncompatibleDescriptor() {
TestMetricStorage storage = new TestMetricStorage(ASYNC_DESCRIPTOR);
assertThat(metricStorageRegistry.register(storage)).isSameAs(storage);
assertThatThrownBy(
() -> metricStorageRegistry.register(new TestMetricStorage(OTHER_ASYNC_DESCRIPTOR)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Metric with same name and different descriptor already created.");
assertThat(logs.getEvents()).isEmpty();
assertThat(metricStorageRegistry.register(new TestMetricStorage(OTHER_ASYNC_DESCRIPTOR)))
.isNotSameAs(storage);
logs.assertContains("Found duplicate metric definition");
}
private static MetricDescriptor descriptor(