diff --git a/instrumentation/micrometer/micrometer-1.5/library/build.gradle.kts b/instrumentation/micrometer/micrometer-1.5/library/build.gradle.kts new file mode 100644 index 000000000000..3cc08bafa0a2 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.library-instrumentation") +} + +dependencies { + library("io.micrometer:micrometer-core:1.5.0") + + testImplementation(project(":instrumentation:micrometer:micrometer-1.5:testing")) +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/Bridging.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/Bridging.java new file mode 100644 index 000000000000..8ff9abd223ea --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/Bridging.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.config.NamingConvention; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +final class Bridging { + + private static final ConcurrentMap descriptionsCache = new ConcurrentHashMap<>(); + + static Attributes tagsAsAttributes(Meter.Id id, NamingConvention namingConvention) { + Iterable tags = id.getTagsAsIterable(); + if (!tags.iterator().hasNext()) { + return Attributes.empty(); + } + AttributesBuilder builder = Attributes.builder(); + for (Tag tag : tags) { + String tagKey = namingConvention.tagKey(tag.getKey()); + String tagValue = namingConvention.tagValue(tag.getValue()); + builder.put(tagKey, tagValue); + } + return builder.build(); + } + + static String name(Meter.Id id, NamingConvention namingConvention) { + return namingConvention.name(id.getName(), id.getType(), id.getBaseUnit()); + } + + static String description(Meter.Id id) { + return descriptionsCache.computeIfAbsent( + id.getName(), + n -> { + String description = id.getDescription(); + return description != null ? description : ""; + }); + } + + static String baseUnit(Meter.Id id) { + String baseUnit = id.getBaseUnit(); + return baseUnit == null ? "1" : baseUnit; + } + + static String statisticInstrumentName( + Meter.Id id, Statistic statistic, NamingConvention namingConvention) { + String prefix = id.getName() + "."; + // use "total_time" instead of "total" to avoid clashing with Statistic.TOTAL + String statisticStr = + statistic == Statistic.TOTAL_TIME ? "total_time" : statistic.getTagValueRepresentation(); + return namingConvention.name(prefix + statisticStr, id.getType(), id.getBaseUnit()); + } + + private Bridging() {} +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/DoubleMeasurementRecorder.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/DoubleMeasurementRecorder.java new file mode 100644 index 000000000000..5eddf0473a5c --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/DoubleMeasurementRecorder.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; +import java.lang.ref.WeakReference; +import java.util.function.Consumer; +import java.util.function.ToDoubleFunction; +import javax.annotation.Nullable; + +final class DoubleMeasurementRecorder implements Consumer { + + // using a weak reference here so that the existence of the micrometer Meter does not block the + // measured object from being GC'd; e.g. a Gauge (or any other async instrument) must not block + // garbage collection of the object that it measures + private final WeakReference objWeakRef; + private final ToDoubleFunction metricFunction; + private final Attributes attributes; + + DoubleMeasurementRecorder( + @Nullable T obj, ToDoubleFunction metricFunction, Attributes attributes) { + this.objWeakRef = new WeakReference<>(obj); + this.metricFunction = metricFunction; + this.attributes = attributes; + } + + @Override + public void accept(ObservableDoubleMeasurement measurement) { + T obj = objWeakRef.get(); + if (obj != null) { + measurement.record(metricFunction.applyAsDouble(obj), attributes); + } + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongMeasurementRecorder.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongMeasurementRecorder.java new file mode 100644 index 000000000000..75ecd5f031c4 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongMeasurementRecorder.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import java.lang.ref.WeakReference; +import java.util.function.Consumer; +import java.util.function.ToLongFunction; +import javax.annotation.Nullable; + +final class LongMeasurementRecorder implements Consumer { + + // using a weak reference here so that the existence of the micrometer Meter does not block the + // measured object from being GC'd; e.g. a Gauge (or any other async instrument) must not block + // garbage collection of the object that it measures + private final WeakReference objWeakRef; + private final ToLongFunction metricFunction; + private final Attributes attributes; + + LongMeasurementRecorder( + @Nullable T obj, ToLongFunction metricFunction, Attributes attributes) { + this.objWeakRef = new WeakReference<>(obj); + this.metricFunction = metricFunction; + this.attributes = attributes; + } + + @Override + public void accept(ObservableLongMeasurement measurement) { + T obj = objWeakRef.get(); + if (obj != null) { + measurement.record(metricFunction.applyAsLong(obj), attributes); + } + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryCounter.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryCounter.java new file mode 100644 index 000000000000..a21ab4b6a9f4 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryCounter.java @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.baseUnit; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.config.NamingConvention; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleCounter; +import io.opentelemetry.api.metrics.Meter; +import java.util.Collections; + +final class OpenTelemetryCounter extends AbstractMeter implements Counter, RemovableMeter { + + // TODO: use bound instruments when they're available + private final DoubleCounter otelCounter; + private final Attributes attributes; + + private volatile boolean removed = false; + + OpenTelemetryCounter(Id id, NamingConvention namingConvention, Meter otelMeter) { + super(id); + + this.attributes = tagsAsAttributes(id, namingConvention); + String conventionName = name(id, namingConvention); + this.otelCounter = + otelMeter + .counterBuilder(conventionName) + .setDescription(Bridging.description(id)) + .setUnit(baseUnit(id)) + .ofDoubles() + .build(); + } + + @Override + public void increment(double v) { + if (removed) { + return; + } + otelCounter.add(v, attributes); + } + + @Override + public double count() { + UnsupportedReadLogger.logWarning(); + return Double.NaN; + } + + @Override + public Iterable measure() { + UnsupportedReadLogger.logWarning(); + return Collections.emptyList(); + } + + @Override + public void onRemove() { + removed = true; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryDistributionSummary.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryDistributionSummary.java new file mode 100644 index 000000000000..5b58713b8670 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryDistributionSummary.java @@ -0,0 +1,165 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.baseUnit; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; + +import io.micrometer.core.instrument.AbstractDistributionSummary; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.config.NamingConvention; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.instrument.distribution.NoopHistogram; +import io.micrometer.core.instrument.distribution.TimeWindowMax; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableDoubleGauge; +import java.util.Collections; +import java.util.concurrent.atomic.DoubleAdder; +import java.util.concurrent.atomic.LongAdder; + +final class OpenTelemetryDistributionSummary extends AbstractDistributionSummary + implements RemovableMeter { + + private final Measurements measurements; + private final TimeWindowMax max; + // TODO: use bound instruments when they're available + private final DoubleHistogram otelHistogram; + private final Attributes attributes; + private final ObservableDoubleGauge observableMax; + + private volatile boolean removed = false; + + OpenTelemetryDistributionSummary( + Id id, + NamingConvention namingConvention, + Clock clock, + DistributionStatisticConfig distributionStatisticConfig, + double scale, + Meter otelMeter) { + super(id, clock, distributionStatisticConfig, scale, false); + + if (isUsingMicrometerHistograms()) { + measurements = new MicrometerHistogramMeasurements(); + } else { + measurements = NoopMeasurements.INSTANCE; + } + max = new TimeWindowMax(clock, distributionStatisticConfig); + + this.attributes = tagsAsAttributes(id, namingConvention); + + String name = name(id, namingConvention); + this.otelHistogram = + otelMeter + .histogramBuilder(name) + .setDescription(Bridging.description(id)) + .setUnit(baseUnit(id)) + .build(); + this.observableMax = + otelMeter + .gaugeBuilder(name + ".max") + .setDescription(Bridging.description(id)) + .setUnit(baseUnit(id)) + .buildWithCallback( + new DoubleMeasurementRecorder<>(max, TimeWindowMax::poll, attributes)); + } + + boolean isUsingMicrometerHistograms() { + return histogram != NoopHistogram.INSTANCE; + } + + @Override + protected void recordNonNegative(double amount) { + if (!removed) { + otelHistogram.record(amount, attributes); + measurements.record(amount); + max.record(amount); + } + } + + @Override + public long count() { + return measurements.count(); + } + + @Override + public double totalAmount() { + return measurements.totalAmount(); + } + + @Override + public double max() { + return max.poll(); + } + + @Override + public Iterable measure() { + UnsupportedReadLogger.logWarning(); + return Collections.emptyList(); + } + + @Override + public void onRemove() { + removed = true; + observableMax.close(); + } + + private interface Measurements { + void record(double amount); + + long count(); + + double totalAmount(); + } + + // if micrometer histograms are not being used then there's no need to keep any local state + // OpenTelemetry metrics bridge does not support reading measurements + enum NoopMeasurements implements Measurements { + INSTANCE; + + @Override + public void record(double amount) {} + + @Override + public long count() { + UnsupportedReadLogger.logWarning(); + return 0; + } + + @Override + public double totalAmount() { + UnsupportedReadLogger.logWarning(); + return Double.NaN; + } + } + + // calculate count and totalAmount value for the use of micrometer histograms + // kinda similar to how DropwizardDistributionSummary does that + private static final class MicrometerHistogramMeasurements implements Measurements { + + private final LongAdder count = new LongAdder(); + private final DoubleAdder totalAmount = new DoubleAdder(); + + @Override + public void record(double amount) { + count.increment(); + totalAmount.add(amount); + } + + @Override + public long count() { + return count.sum(); + } + + @Override + public double totalAmount() { + return totalAmount.sum(); + } + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryFunctionCounter.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryFunctionCounter.java new file mode 100644 index 000000000000..352009c3a5b3 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryFunctionCounter.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.baseUnit; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.FunctionCounter; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.config.NamingConvention; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableDoubleCounter; +import java.util.Collections; +import java.util.function.ToDoubleFunction; + +final class OpenTelemetryFunctionCounter extends AbstractMeter + implements FunctionCounter, RemovableMeter { + + private final ObservableDoubleCounter observableCount; + + OpenTelemetryFunctionCounter( + Id id, + NamingConvention namingConvention, + T obj, + ToDoubleFunction countFunction, + Meter otelMeter) { + super(id); + + String name = name(id, namingConvention); + observableCount = + otelMeter + .counterBuilder(name) + .ofDoubles() + .setDescription(Bridging.description(id)) + .setUnit(baseUnit(id)) + .buildWithCallback( + new DoubleMeasurementRecorder<>( + obj, countFunction, tagsAsAttributes(id, namingConvention))); + } + + @Override + public double count() { + UnsupportedReadLogger.logWarning(); + return Double.NaN; + } + + @Override + public Iterable measure() { + UnsupportedReadLogger.logWarning(); + return Collections.emptyList(); + } + + @Override + public void onRemove() { + observableCount.close(); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryFunctionTimer.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryFunctionTimer.java new file mode 100644 index 000000000000..91fb153f577a --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryFunctionTimer.java @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.FunctionTimer; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.config.NamingConvention; +import io.micrometer.core.instrument.util.TimeUtils; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableDoubleCounter; +import io.opentelemetry.api.metrics.ObservableLongCounter; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.function.ToDoubleFunction; +import java.util.function.ToLongFunction; + +final class OpenTelemetryFunctionTimer extends AbstractMeter + implements FunctionTimer, RemovableMeter { + private final TimeUnit baseTimeUnit; + private final ObservableLongCounter observableCount; + private final ObservableDoubleCounter observableTotalTime; + + OpenTelemetryFunctionTimer( + Id id, + NamingConvention namingConvention, + T obj, + ToLongFunction countFunction, + ToDoubleFunction totalTimeFunction, + TimeUnit totalTimeFunctionUnit, + TimeUnit baseTimeUnit, + Meter otelMeter) { + super(id); + this.baseTimeUnit = baseTimeUnit; + + String name = Bridging.name(id, namingConvention); + Attributes attributes = Bridging.tagsAsAttributes(id, namingConvention); + + this.observableCount = + otelMeter + .counterBuilder(name + ".count") + .setDescription(Bridging.description(id)) + .setUnit("1") + .buildWithCallback(new LongMeasurementRecorder<>(obj, countFunction, attributes)); + + this.observableTotalTime = + otelMeter + .counterBuilder(name + ".sum") + .ofDoubles() + .setDescription(Bridging.description(id)) + .setUnit(TimeUnitHelper.getUnitString(baseTimeUnit)) + .buildWithCallback( + new DoubleMeasurementRecorder<>( + obj, + val -> + TimeUtils.convert( + totalTimeFunction.applyAsDouble(val), + totalTimeFunctionUnit, + baseTimeUnit), + attributes)); + } + + @Override + public double count() { + UnsupportedReadLogger.logWarning(); + return Double.NaN; + } + + @Override + public double totalTime(TimeUnit unit) { + UnsupportedReadLogger.logWarning(); + return Double.NaN; + } + + @Override + public double mean(TimeUnit unit) { + UnsupportedReadLogger.logWarning(); + return Double.NaN; + } + + @Override + public TimeUnit baseTimeUnit() { + return baseTimeUnit; + } + + @Override + public Iterable measure() { + UnsupportedReadLogger.logWarning(); + return Collections.emptyList(); + } + + @Override + public void onRemove() { + observableCount.close(); + observableTotalTime.close(); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryGauge.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryGauge.java new file mode 100644 index 000000000000..ce9efa803aca --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryGauge.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.baseUnit; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.config.NamingConvention; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableDoubleGauge; +import java.util.Collections; +import java.util.function.ToDoubleFunction; +import javax.annotation.Nullable; + +final class OpenTelemetryGauge extends AbstractMeter implements Gauge, RemovableMeter { + + private final ObservableDoubleGauge observableGauge; + + OpenTelemetryGauge( + Id id, + NamingConvention namingConvention, + @Nullable T obj, + ToDoubleFunction objMetric, + Meter otelMeter) { + super(id); + + String name = name(id, namingConvention); + observableGauge = + otelMeter + .gaugeBuilder(name) + .setDescription(Bridging.description(id)) + .setUnit(baseUnit(id)) + .buildWithCallback( + new DoubleMeasurementRecorder<>( + obj, objMetric, tagsAsAttributes(id, namingConvention))); + } + + @Override + public double value() { + UnsupportedReadLogger.logWarning(); + return Double.NaN; + } + + @Override + public Iterable measure() { + UnsupportedReadLogger.logWarning(); + return Collections.emptyList(); + } + + @Override + public void onRemove() { + observableGauge.close(); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryLongTaskTimer.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryLongTaskTimer.java new file mode 100644 index 000000000000..c2aae70c0ee2 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryLongTaskTimer.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.config.NamingConvention; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.instrument.internal.DefaultLongTaskTimer; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableDoubleUpDownCounter; +import io.opentelemetry.api.metrics.ObservableLongUpDownCounter; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +final class OpenTelemetryLongTaskTimer extends DefaultLongTaskTimer implements RemovableMeter { + + private final DistributionStatisticConfig distributionStatisticConfig; + private final ObservableLongUpDownCounter observableActiveTasks; + private final ObservableDoubleUpDownCounter observableDuration; + + OpenTelemetryLongTaskTimer( + Id id, + NamingConvention namingConvention, + Clock clock, + TimeUnit baseTimeUnit, + DistributionStatisticConfig distributionStatisticConfig, + Meter otelMeter) { + super(id, clock, baseTimeUnit, distributionStatisticConfig, false); + + this.distributionStatisticConfig = distributionStatisticConfig; + + String name = name(id, namingConvention); + Attributes attributes = tagsAsAttributes(id, namingConvention); + + this.observableActiveTasks = + otelMeter + .upDownCounterBuilder(name + ".active") + .setDescription(Bridging.description(id)) + .setUnit("tasks") + .buildWithCallback( + new LongMeasurementRecorder<>(this, DefaultLongTaskTimer::activeTasks, attributes)); + this.observableDuration = + otelMeter + .upDownCounterBuilder(name + ".duration") + .ofDoubles() + .setDescription(Bridging.description(id)) + .setUnit(TimeUnitHelper.getUnitString(baseTimeUnit)) + .buildWithCallback( + new DoubleMeasurementRecorder<>( + this, t -> t.duration(t.baseTimeUnit()), attributes)); + } + + @Override + public Iterable measure() { + UnsupportedReadLogger.logWarning(); + return Collections.emptyList(); + } + + @Override + public void onRemove() { + observableActiveTasks.close(); + observableDuration.close(); + } + + boolean isUsingMicrometerHistograms() { + return distributionStatisticConfig.isPublishingPercentiles() + || distributionStatisticConfig.isPublishingHistogram(); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeter.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeter.java new file mode 100644 index 000000000000..93a0bf07581d --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeter.java @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.baseUnit; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.statisticInstrumentName; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; + +import io.micrometer.core.instrument.AbstractMeter; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.config.NamingConvention; +import io.opentelemetry.api.common.Attributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +final class OpenTelemetryMeter extends AbstractMeter implements Meter, RemovableMeter { + + private final List observableInstruments; + + OpenTelemetryMeter( + Id id, + NamingConvention namingConvention, + Iterable measurements, + io.opentelemetry.api.metrics.Meter otelMeter) { + super(id); + Attributes attributes = tagsAsAttributes(id, namingConvention); + + List observableInstruments = new ArrayList<>(); + for (Measurement measurement : measurements) { + String name = statisticInstrumentName(id, measurement.getStatistic(), namingConvention); + String description = Bridging.description(id); + String baseUnit = baseUnit(id); + DoubleMeasurementRecorder callback = + new DoubleMeasurementRecorder<>(measurement, Measurement::getValue, attributes); + + switch (measurement.getStatistic()) { + case TOTAL: + // fall through + case TOTAL_TIME: + case COUNT: + observableInstruments.add( + otelMeter + .counterBuilder(name) + .ofDoubles() + .setDescription(description) + .setUnit(baseUnit) + .buildWithCallback(callback)); + break; + + case ACTIVE_TASKS: + observableInstruments.add( + otelMeter + .upDownCounterBuilder(name) + .ofDoubles() + .setDescription(description) + .setUnit(baseUnit) + .buildWithCallback(callback)); + break; + + case DURATION: + // fall through + case MAX: + case VALUE: + case UNKNOWN: + observableInstruments.add( + otelMeter + .gaugeBuilder(name) + .setDescription(description) + .setUnit(baseUnit) + .buildWithCallback(callback)); + break; + } + } + this.observableInstruments = observableInstruments; + } + + @Override + public Iterable measure() { + UnsupportedReadLogger.logWarning(); + return Collections.emptyList(); + } + + @Override + public void onRemove() { + try { + for (AutoCloseable observableInstrument : observableInstruments) { + observableInstrument.close(); + } + } catch (Exception e) { + throw new IllegalStateException("SDK instruments should never throw on close()", e); + } + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeterRegistry.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeterRegistry.java new file mode 100644 index 000000000000..5c4e5e342cb8 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeterRegistry.java @@ -0,0 +1,173 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.FunctionCounter; +import io.micrometer.core.instrument.FunctionTimer; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.config.NamingConvention; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.instrument.distribution.HistogramGauges; +import io.micrometer.core.instrument.distribution.pause.PauseDetector; +import io.opentelemetry.api.OpenTelemetry; +import java.util.concurrent.TimeUnit; +import java.util.function.ToDoubleFunction; +import java.util.function.ToLongFunction; +import javax.annotation.Nullable; + +/** + * A {@link MeterRegistry} implementation that forwards all the captured metrics to the {@linkplain + * io.opentelemetry.api.metrics.Meter OpenTelemetry Meter} obtained from the passed {@link + * OpenTelemetry} instance. + */ +public final class OpenTelemetryMeterRegistry extends MeterRegistry { + + /** + * Returns a new {@link OpenTelemetryMeterRegistry} configured with the given {@link + * OpenTelemetry}. + */ + public static MeterRegistry create(OpenTelemetry openTelemetry) { + return builder(openTelemetry).build(); + } + + /** + * Returns a new {@link OpenTelemetryMeterRegistryBuilder} configured with the given {@link + * OpenTelemetry}. + */ + public static OpenTelemetryMeterRegistryBuilder builder(OpenTelemetry openTelemetry) { + return new OpenTelemetryMeterRegistryBuilder(openTelemetry); + } + + private final TimeUnit baseTimeUnit; + private final io.opentelemetry.api.metrics.Meter otelMeter; + + OpenTelemetryMeterRegistry( + Clock clock, + TimeUnit baseTimeUnit, + NamingConvention namingConvention, + io.opentelemetry.api.metrics.Meter otelMeter) { + super(clock); + this.baseTimeUnit = baseTimeUnit; + this.otelMeter = otelMeter; + + this.config() + .namingConvention(namingConvention) + .onMeterRemoved(OpenTelemetryMeterRegistry::onMeterRemoved); + } + + @Override + protected Gauge newGauge(Meter.Id id, @Nullable T obj, ToDoubleFunction valueFunction) { + return new OpenTelemetryGauge<>(id, config().namingConvention(), obj, valueFunction, otelMeter); + } + + @Override + protected Counter newCounter(Meter.Id id) { + return new OpenTelemetryCounter(id, config().namingConvention(), otelMeter); + } + + @Override + protected LongTaskTimer newLongTaskTimer( + Meter.Id id, DistributionStatisticConfig distributionStatisticConfig) { + OpenTelemetryLongTaskTimer timer = + new OpenTelemetryLongTaskTimer( + id, + config().namingConvention(), + clock, + getBaseTimeUnit(), + distributionStatisticConfig, + otelMeter); + if (timer.isUsingMicrometerHistograms()) { + HistogramGauges.registerWithCommonFormat(timer, this); + } + return timer; + } + + @Override + protected Timer newTimer( + Meter.Id id, + DistributionStatisticConfig distributionStatisticConfig, + PauseDetector pauseDetector) { + OpenTelemetryTimer timer = + new OpenTelemetryTimer( + id, + config().namingConvention(), + clock, + distributionStatisticConfig, + pauseDetector, + getBaseTimeUnit(), + otelMeter); + if (timer.isUsingMicrometerHistograms()) { + HistogramGauges.registerWithCommonFormat(timer, this); + } + return timer; + } + + @Override + protected DistributionSummary newDistributionSummary( + Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, double scale) { + OpenTelemetryDistributionSummary distributionSummary = + new OpenTelemetryDistributionSummary( + id, config().namingConvention(), clock, distributionStatisticConfig, scale, otelMeter); + if (distributionSummary.isUsingMicrometerHistograms()) { + HistogramGauges.registerWithCommonFormat(distributionSummary, this); + } + return distributionSummary; + } + + @Override + protected Meter newMeter(Meter.Id id, Meter.Type type, Iterable measurements) { + return new OpenTelemetryMeter(id, config().namingConvention(), measurements, otelMeter); + } + + @Override + protected FunctionTimer newFunctionTimer( + Meter.Id id, + T obj, + ToLongFunction countFunction, + ToDoubleFunction totalTimeFunction, + TimeUnit totalTimeFunctionUnit) { + return new OpenTelemetryFunctionTimer<>( + id, + config().namingConvention(), + obj, + countFunction, + totalTimeFunction, + totalTimeFunctionUnit, + getBaseTimeUnit(), + otelMeter); + } + + @Override + protected FunctionCounter newFunctionCounter( + Meter.Id id, T obj, ToDoubleFunction countFunction) { + return new OpenTelemetryFunctionCounter<>( + id, config().namingConvention(), obj, countFunction, otelMeter); + } + + @Override + protected TimeUnit getBaseTimeUnit() { + return baseTimeUnit; + } + + @Override + protected DistributionStatisticConfig defaultHistogramConfig() { + return DistributionStatisticConfig.DEFAULT; + } + + private static void onMeterRemoved(Meter meter) { + if (meter instanceof RemovableMeter) { + ((RemovableMeter) meter).onRemove(); + } + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeterRegistryBuilder.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeterRegistryBuilder.java new file mode 100644 index 000000000000..b22b2fed63ea --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryMeterRegistryBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.config.NamingConvention; +import io.opentelemetry.api.OpenTelemetry; +import java.util.concurrent.TimeUnit; + +/** A builder of {@link OpenTelemetryMeterRegistry}. */ +public final class OpenTelemetryMeterRegistryBuilder { + + // Visible for testing + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.micrometer-1.5"; + + private final OpenTelemetry openTelemetry; + private Clock clock = Clock.SYSTEM; + private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS; + private boolean prometheusMode = false; + + OpenTelemetryMeterRegistryBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + /** Sets a custom {@link Clock}. Useful for testing. */ + public OpenTelemetryMeterRegistryBuilder setClock(Clock clock) { + this.clock = clock; + return this; + } + + /** Sets the base time unit. */ + public OpenTelemetryMeterRegistryBuilder setBaseTimeUnit(TimeUnit baseTimeUnit) { + this.baseTimeUnit = baseTimeUnit; + return this; + } + + /** + * Enables the "Prometheus mode" - this will simulate the behavior of Micrometer's {@code + * PrometheusMeterRegistry}. The instruments will be renamed to match Micrometer instrument + * naming, and the base time unit will be set to seconds. + * + *

Set this to {@code true} if you are using the Prometheus metrics exporter. + */ + public OpenTelemetryMeterRegistryBuilder setPrometheusMode(boolean prometheusMode) { + this.prometheusMode = prometheusMode; + return this; + } + + /** + * Returns a new {@link OpenTelemetryMeterRegistry} with the settings of this {@link + * OpenTelemetryMeterRegistryBuilder}. + */ + public MeterRegistry build() { + // prometheus mode overrides any unit settings with SECONDS + TimeUnit baseTimeUnit = prometheusMode ? TimeUnit.SECONDS : this.baseTimeUnit; + NamingConvention namingConvention = + prometheusMode ? PrometheusModeNamingConvention.INSTANCE : NamingConvention.identity; + + return new OpenTelemetryMeterRegistry( + clock, + baseTimeUnit, + namingConvention, + openTelemetry.getMeterProvider().get(INSTRUMENTATION_NAME)); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryTimer.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryTimer.java new file mode 100644 index 000000000000..86df5008a8f9 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/OpenTelemetryTimer.java @@ -0,0 +1,171 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.name; +import static io.opentelemetry.instrumentation.micrometer.v1_5.Bridging.tagsAsAttributes; + +import io.micrometer.core.instrument.AbstractTimer; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.config.NamingConvention; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.instrument.distribution.NoopHistogram; +import io.micrometer.core.instrument.distribution.TimeWindowMax; +import io.micrometer.core.instrument.distribution.pause.PauseDetector; +import io.micrometer.core.instrument.util.TimeUtils; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableDoubleGauge; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.DoubleAdder; +import java.util.concurrent.atomic.LongAdder; + +final class OpenTelemetryTimer extends AbstractTimer implements RemovableMeter { + + private final Measurements measurements; + private final TimeWindowMax max; + private final TimeUnit baseTimeUnit; + // TODO: use bound instruments when they're available + private final DoubleHistogram otelHistogram; + private final Attributes attributes; + private final ObservableDoubleGauge observableMax; + + private volatile boolean removed = false; + + OpenTelemetryTimer( + Id id, + NamingConvention namingConvention, + Clock clock, + DistributionStatisticConfig distributionStatisticConfig, + PauseDetector pauseDetector, + TimeUnit baseTimeUnit, + Meter otelMeter) { + super(id, clock, distributionStatisticConfig, pauseDetector, TimeUnit.MILLISECONDS, false); + + if (isUsingMicrometerHistograms()) { + measurements = new MicrometerHistogramMeasurements(); + } else { + measurements = NoopMeasurements.INSTANCE; + } + max = new TimeWindowMax(clock, distributionStatisticConfig); + + this.baseTimeUnit = baseTimeUnit; + this.attributes = tagsAsAttributes(id, namingConvention); + + String name = name(id, namingConvention); + this.otelHistogram = + otelMeter + .histogramBuilder(name) + .setDescription(Bridging.description(id)) + .setUnit(TimeUnitHelper.getUnitString(baseTimeUnit)) + .build(); + this.observableMax = + otelMeter + .gaugeBuilder(name + ".max") + .setDescription(Bridging.description(id)) + .setUnit(TimeUnitHelper.getUnitString(baseTimeUnit)) + .buildWithCallback( + new DoubleMeasurementRecorder<>(max, m -> m.poll(baseTimeUnit), attributes)); + } + + boolean isUsingMicrometerHistograms() { + return histogram != NoopHistogram.INSTANCE; + } + + @Override + protected void recordNonNegative(long amount, TimeUnit unit) { + if (!removed) { + double nanos = (double) unit.toNanos(amount); + double time = TimeUtils.nanosToUnit(nanos, baseTimeUnit); + otelHistogram.record(time, attributes); + measurements.record(nanos); + max.record(nanos, TimeUnit.NANOSECONDS); + } + } + + @Override + public long count() { + return measurements.count(); + } + + @Override + public double totalTime(TimeUnit unit) { + return measurements.totalTime(unit); + } + + @Override + public double max(TimeUnit unit) { + return max.poll(unit); + } + + @Override + public Iterable measure() { + UnsupportedReadLogger.logWarning(); + return Collections.emptyList(); + } + + @Override + public void onRemove() { + removed = true; + observableMax.close(); + } + + private interface Measurements { + void record(double nanos); + + long count(); + + double totalTime(TimeUnit unit); + } + + // if micrometer histograms are not being used then there's no need to keep any local state + // OpenTelemetry metrics bridge does not support reading measurements + enum NoopMeasurements implements Measurements { + INSTANCE; + + @Override + public void record(double nanos) {} + + @Override + public long count() { + UnsupportedReadLogger.logWarning(); + return 0; + } + + @Override + public double totalTime(TimeUnit unit) { + UnsupportedReadLogger.logWarning(); + return Double.NaN; + } + } + + // calculate count and totalTime value for the use of micrometer histograms + // kinda similar to how DropwizardTimer does that + private static final class MicrometerHistogramMeasurements implements Measurements { + + private final LongAdder count = new LongAdder(); + private final DoubleAdder totalTime = new DoubleAdder(); + + @Override + public void record(double nanos) { + count.increment(); + totalTime.add(nanos); + } + + @Override + public long count() { + return count.sum(); + } + + @Override + public double totalTime(TimeUnit unit) { + return TimeUtils.nanosToUnit(totalTime.sum(), unit); + } + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java new file mode 100644 index 000000000000..2a4143512756 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.config.NamingConvention; +import javax.annotation.Nullable; + +// This naming strategy does not replace '.' with '_', and it does not append '_total' to counter +// names - the reason behind it is that this is already done by the Prometheus exporter; see the +// io.opentelemetry.exporter.prometheus.MetricAdapter class +enum PrometheusModeNamingConvention implements NamingConvention { + INSTANCE; + + @Override + public String name(String name, Meter.Type type, @Nullable String baseUnit) { + if (type == Meter.Type.COUNTER + || type == Meter.Type.DISTRIBUTION_SUMMARY + || type == Meter.Type.GAUGE) { + if (baseUnit != null && !name.endsWith("." + baseUnit)) { + name = name + "." + baseUnit; + } + } + + if (type == Meter.Type.LONG_TASK_TIMER || type == Meter.Type.TIMER) { + if (!name.endsWith(".seconds")) { + name = name + ".seconds"; + } + } + + return name; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/RemovableMeter.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/RemovableMeter.java new file mode 100644 index 000000000000..37fde71110b2 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/RemovableMeter.java @@ -0,0 +1,11 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +interface RemovableMeter { + + void onRemove(); +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimeUnitHelper.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimeUnitHelper.java new file mode 100644 index 000000000000..326144520a18 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimeUnitHelper.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import java.util.concurrent.TimeUnit; + +final class TimeUnitHelper { + + static String getUnitString(TimeUnit unit) { + switch (unit) { + case NANOSECONDS: + return "ns"; + case MICROSECONDS: + return "us"; + case MILLISECONDS: + return "ms"; + case SECONDS: + return "s"; + case MINUTES: + return "min"; + case HOURS: + return "h"; + case DAYS: + return "d"; + } + throw new IllegalStateException("Should not ever happen"); + } + + private TimeUnitHelper() {} +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/UnsupportedReadLogger.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/UnsupportedReadLogger.java new file mode 100644 index 000000000000..529f84ce74a5 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/UnsupportedReadLogger.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import java.util.logging.Level; +import java.util.logging.Logger; + +final class UnsupportedReadLogger { + + static { + Logger logger = Logger.getLogger(OpenTelemetryMeterRegistry.class.getName()); + logger.log(Level.WARNING, "OpenTelemetry metrics bridge does not support reading measurements"); + } + + static void logWarning() { + // do nothing; the warning will be logged exactly once when this class is loaded + } + + private UnsupportedReadLogger() {} +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/CounterTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/CounterTest.java new file mode 100644 index 000000000000..c344bc3aa467 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/CounterTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class CounterTest extends AbstractCounterTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/DistributionSummaryTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/DistributionSummaryTest.java new file mode 100644 index 000000000000..1ba5b5d71bdb --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/DistributionSummaryTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class DistributionSummaryTest extends AbstractDistributionSummaryTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionCounterTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionCounterTest.java new file mode 100644 index 000000000000..938679601290 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionCounterTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class FunctionCounterTest extends AbstractFunctionCounterTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionTimerSecondsTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionTimerSecondsTest.java new file mode 100644 index 000000000000..c418be394b87 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionTimerSecondsTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.extension.RegisterExtension; + +class FunctionTimerSecondsTest extends AbstractFunctionTimerSecondsTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing) { + @Override + OpenTelemetryMeterRegistryBuilder configureOtelRegistry( + OpenTelemetryMeterRegistryBuilder registry) { + return registry.setBaseTimeUnit(TimeUnit.SECONDS); + } + }; + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionTimerTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionTimerTest.java new file mode 100644 index 000000000000..5d924bfbc92b --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/FunctionTimerTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class FunctionTimerTest extends AbstractFunctionTimerTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/GaugeTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/GaugeTest.java new file mode 100644 index 000000000000..fec02d5009b1 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/GaugeTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class GaugeTest extends AbstractGaugeTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerHistogramTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerHistogramTest.java new file mode 100644 index 000000000000..48500347fae9 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerHistogramTest.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.micrometer.core.instrument.MockClock; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class LongTaskTimerHistogramTest extends AbstractLongTaskTimerHistogramTest { + + private static final MockClock clock = new MockClock(); + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing) { + @Override + OpenTelemetryMeterRegistryBuilder configureOtelRegistry( + OpenTelemetryMeterRegistryBuilder registry) { + return registry.setClock(clock); + } + }; + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected MockClock clock() { + return clock; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerSecondsTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerSecondsTest.java new file mode 100644 index 000000000000..f92871073ad7 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerSecondsTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.extension.RegisterExtension; + +class LongTaskTimerSecondsTest extends AbstractLongTaskTimerSecondsTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing) { + @Override + OpenTelemetryMeterRegistryBuilder configureOtelRegistry( + OpenTelemetryMeterRegistryBuilder registry) { + return registry.setBaseTimeUnit(TimeUnit.SECONDS); + } + }; + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerTest.java new file mode 100644 index 000000000000..e084b46b99ce --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/LongTaskTimerTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class LongTaskTimerTest extends AbstractLongTaskTimerTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/MeterTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/MeterTest.java new file mode 100644 index 000000000000..c32730ed7747 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/MeterTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class MeterTest extends AbstractMeterTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/MicrometerTestingExtension.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/MicrometerTestingExtension.java new file mode 100644 index 000000000000..fdf2f481ea42 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/MicrometerTestingExtension.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +class MicrometerTestingExtension implements AfterEachCallback, BeforeEachCallback { + + private static final ExtensionContext.Namespace NAMESPACE = + ExtensionContext.Namespace.create(MicrometerTestingExtension.class); + + private final InstrumentationExtension testing; + + MicrometerTestingExtension(InstrumentationExtension testing) { + this.testing = testing; + } + + @Override + public void beforeEach(ExtensionContext context) { + ExtensionContext.Store store = context.getStore(NAMESPACE); + + MeterRegistry otelMeterRegistry = + configureOtelRegistry(OpenTelemetryMeterRegistry.builder(testing.getOpenTelemetry())) + .build(); + configureMeterRegistry(otelMeterRegistry); + + store.put(MeterRegistry.class, otelMeterRegistry); + + Metrics.addRegistry(otelMeterRegistry); + } + + @Override + public void afterEach(ExtensionContext context) { + ExtensionContext.Store store = context.getStore(NAMESPACE); + MeterRegistry otelMeterRegistry = store.get(MeterRegistry.class, MeterRegistry.class); + + Metrics.removeRegistry(otelMeterRegistry); + SdkMeterProviderUtil.resetForTest(testing.getOpenTelemetrySdk().getSdkMeterProvider()); + + Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove); + } + + OpenTelemetryMeterRegistryBuilder configureOtelRegistry( + OpenTelemetryMeterRegistryBuilder registry) { + return registry; + } + + MeterRegistry configureMeterRegistry(MeterRegistry registry) { + return registry; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/NamingConventionTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/NamingConventionTest.java new file mode 100644 index 000000000000..0487167ad9d1 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/NamingConventionTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class NamingConventionTest extends AbstractNamingConventionTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing) { + @Override + MeterRegistry configureMeterRegistry(MeterRegistry registry) { + registry.config().namingConvention(AbstractNamingConventionTest.namingConvention()); + return registry; + } + }; + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeTest.java new file mode 100644 index 000000000000..00ec0fd0475d --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeTest.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class PrometheusModeTest extends AbstractPrometheusModeTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing) { + @Override + OpenTelemetryMeterRegistryBuilder configureOtelRegistry( + OpenTelemetryMeterRegistryBuilder registry) { + return registry.setPrometheusMode(true); + } + }; + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimerSecondsTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimerSecondsTest.java new file mode 100644 index 000000000000..4ab97b60f392 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimerSecondsTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TimerSecondsTest extends AbstractTimerSecondsTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing) { + @Override + OpenTelemetryMeterRegistryBuilder configureOtelRegistry( + OpenTelemetryMeterRegistryBuilder registry) { + return registry.setBaseTimeUnit(TimeUnit.SECONDS); + } + }; + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimerTest.java b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimerTest.java new file mode 100644 index 000000000000..2a2b52b3512d --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/library/src/test/java/io/opentelemetry/instrumentation/micrometer/v1_5/TimerTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TimerTest extends AbstractTimerTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + static final MicrometerTestingExtension micrometerExtension = + new MicrometerTestingExtension(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/build.gradle.kts b/instrumentation/micrometer/micrometer-1.5/testing/build.gradle.kts new file mode 100644 index 000000000000..86fabbe0e11a --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + api(project(":testing-common")) + + api("io.micrometer:micrometer-core:1.5.0") +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractCounterTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractCounterTest.java new file mode 100644 index 000000000000..cbd63d1c4cf0 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractCounterTest.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public abstract class AbstractCounterTest { + + static final String INSTRUMENTATION_NAME = "io.opentelemetry.micrometer-1.5"; + + protected abstract InstrumentationExtension testing(); + + @BeforeEach + void cleanupMeters() { + Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove); + } + + @Test + void testCounter() { + // given + Counter counter = + Counter.builder("testCounter") + .description("This is a test counter") + .tags("tag", "value") + .baseUnit("items") + .register(Metrics.globalRegistry); + + // when + counter.increment(); + counter.increment(2); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testCounter", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test counter") + .hasUnit("items") + .hasDoubleSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(3) + .hasAttributes( + attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(counter); + testing().clearData(); + counter.increment(); + + // then + testing() + .waitAndAssertMetrics(INSTRUMENTATION_NAME, "testCounter", AbstractIterableAssert::isEmpty); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java new file mode 100644 index 000000000000..bf64e0d632ee --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java @@ -0,0 +1,266 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public abstract class AbstractDistributionSummaryTest { + + protected abstract InstrumentationExtension testing(); + + @BeforeEach + void cleanupMeters() { + Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove); + } + + @Test + void testMicrometerDistributionSummary() { + // given + DistributionSummary summary = + DistributionSummary.builder("testSummary") + .description("This is a test distribution summary") + .baseUnit("things") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + summary.record(1); + summary.record(2); + summary.record(4); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test distribution summary") + .hasUnit("things") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(7) + .hasCount(3) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test distribution summary") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(4) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(summary); + + // then + // Histogram is synchronous and returns previous value after removal, max is asynchronous and is + // removed completely. + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(7) + .hasCount(3) + .hasAttributes(attributeEntry("tag", "value")))))); + } + + @Test + void testMicrometerHistogram() { + // given + DistributionSummary summary = + DistributionSummary.builder("testSummary") + .description("This is a test distribution summary") + .baseUnit("things") + .tags("tag", "value") + .serviceLevelObjectives(1, 10, 100, 1000) + .distributionStatisticBufferLength(10) + .register(Metrics.globalRegistry); + + // when + summary.record(0.5); + summary.record(5); + summary.record(50); + summary.record(500); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test distribution summary") + .hasUnit("things") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + points -> + points + .hasSum(555.5) + .hasCount(4) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test distribution summary") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(500) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary.histogram", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(1) + .hasAttributes( + attributeEntry("le", "1"), + attributeEntry("tag", "value")), + point -> + point + .hasValue(2) + .hasAttributes( + attributeEntry("le", "10"), + attributeEntry("tag", "value")), + point -> + point + .hasValue(3) + .hasAttributes( + attributeEntry("le", "100"), + attributeEntry("tag", "value")), + point -> + point + .hasValue(4) + .hasAttributes( + attributeEntry("le", "1000"), + attributeEntry("tag", "value")))))); + } + + @Test + void testMicrometerPercentiles() { + // given + DistributionSummary summary = + DistributionSummary.builder("testSummary") + .description("This is a test distribution summary") + .baseUnit("things") + .tags("tag", "value") + .publishPercentiles(0.5, 0.95, 0.99) + .register(Metrics.globalRegistry); + + // when + summary.record(50); + summary.record(100); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test distribution summary") + .hasUnit("things") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(150) + .hasCount(2) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test distribution summary") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(100) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testSummary.percentile", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("phi", "0.5"), + attributeEntry("tag", "value")), + point -> + point.hasAttributes( + attributeEntry("phi", "0.95"), + attributeEntry("tag", "value")), + point -> + point.hasAttributes( + attributeEntry("phi", "0.99"), + attributeEntry("tag", "value")))))); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionCounterTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionCounterTest.java new file mode 100644 index 000000000000..36e3f43401eb --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionCounterTest.java @@ -0,0 +1,109 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.FunctionCounter; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.atomic.AtomicLong; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public abstract class AbstractFunctionCounterTest { + + protected abstract InstrumentationExtension testing(); + + @BeforeEach + void cleanupMeters() { + Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove); + } + + final AtomicLong num = new AtomicLong(12); + final AtomicLong anotherNum = new AtomicLong(13); + + @Test + void testFunctionCounter() throws InterruptedException { + // given + FunctionCounter counter = + FunctionCounter.builder("testFunctionCounter", num, AtomicLong::get) + .description("This is a test function counter") + .tags("tag", "value") + .baseUnit("items") + .register(Metrics.globalRegistry); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testFunctionCounter", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test function counter") + .hasUnit("items") + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(12) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(counter); + testing().clearData(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, "testFunctionCounter", AbstractIterableAssert::isEmpty); + } + + @Test + void functionCountersWithSameNameAndDifferentTags() { + // given + FunctionCounter.builder("testFunctionCounterWithTags", num, AtomicLong::get) + .description("First description") + .tags("tag", "1") + .baseUnit("items") + .register(Metrics.globalRegistry); + FunctionCounter.builder("testFunctionCounterWithTags", anotherNum, AtomicLong::get) + .description("ignored") + .tags("tag", "2") + .baseUnit("items") + .register(Metrics.globalRegistry); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testFunctionCounterWithTags", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("First description") + .hasUnit("items") + .hasDoubleSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(12) + .hasAttributes(attributeEntry("tag", "1")), + point -> + point + .hasValue(13) + .hasAttributes(attributeEntry("tag", "2")))))); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionTimerSecondsTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionTimerSecondsTest.java new file mode 100644 index 000000000000..e96d4db60f90 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionTimerSecondsTest.java @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.FunctionTimer; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public abstract class AbstractFunctionTimerSecondsTest { + + protected abstract InstrumentationExtension testing(); + + final TestTimer timerObj = new TestTimer(); + + @BeforeEach + void cleanupTimer() { + timerObj.reset(); + Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove); + } + + @Test + void testFunctionTimerWithBaseUnitSeconds() { + // given + FunctionTimer functionTimer = + FunctionTimer.builder( + "testFunctionTimerSeconds", + timerObj, + TestTimer::getCount, + TestTimer::getTotalTimeNanos, + TimeUnit.NANOSECONDS) + .description("This is a test function timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + timerObj.add(42, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testFunctionTimerSeconds.count", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test function timer") + .hasUnit("1") + .hasLongSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(1) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testFunctionTimerSeconds.sum", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test function timer") + .hasUnit("s") + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(42) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(functionTimer); + testing().clearData(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testFunctionTimerSeconds.count", + AbstractIterableAssert::isEmpty); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionTimerTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionTimerTest.java new file mode 100644 index 000000000000..03345a89f94e --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractFunctionTimerTest.java @@ -0,0 +1,179 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.FunctionTimer; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +abstract class AbstractFunctionTimerTest { + + protected abstract InstrumentationExtension testing(); + + final TestTimer timerObj = new TestTimer(); + final TestTimer anotherTimerObj = new TestTimer(); + + @BeforeEach + void cleanupTimers() { + timerObj.reset(); + anotherTimerObj.reset(); + Metrics.globalRegistry.forEachMeter(Metrics.globalRegistry::remove); + } + + @Test + void testFunctionTimer() { + // given + FunctionTimer functionTimer = + FunctionTimer.builder( + "testFunctionTimer", + timerObj, + TestTimer::getCount, + TestTimer::getTotalTimeNanos, + TimeUnit.NANOSECONDS) + .description("This is a test function timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + timerObj.add(42, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testFunctionTimer.count", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test function timer") + .hasUnit("1") + .hasLongSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(1) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testFunctionTimer.sum", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test function timer") + .hasUnit("ms") + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(42_000) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(functionTimer); + testing().clearData(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, "testFunctionTimer.count", AbstractIterableAssert::isEmpty); + } + + @Test + void testNanoPrecision() { + // given + FunctionTimer.builder( + "testNanoFunctionTimer", + timerObj, + TestTimer::getCount, + TestTimer::getTotalTimeNanos, + TimeUnit.NANOSECONDS) + .register(Metrics.globalRegistry); + + // when + timerObj.add(1_234_000, TimeUnit.NANOSECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testNanoFunctionTimer.sum", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasUnit("ms") + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(1.234) + .hasAttributes(Attributes.empty()))))); + } + + @Test + void functionTimersWithSameNameAndDifferentTags() { + // given + FunctionTimer.builder( + "testFunctionTimerWithTags", + timerObj, + TestTimer::getCount, + TestTimer::getTotalTimeNanos, + TimeUnit.NANOSECONDS) + .tags("tag", "1") + .register(Metrics.globalRegistry); + FunctionTimer.builder( + "testFunctionTimerWithTags", + anotherTimerObj, + TestTimer::getCount, + TestTimer::getTotalTimeNanos, + TimeUnit.NANOSECONDS) + .tags("tag", "2") + .register(Metrics.globalRegistry); + + // when + timerObj.add(12, TimeUnit.SECONDS); + anotherTimerObj.add(42, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testFunctionTimerWithTags.sum", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasUnit("ms") + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(12_000) + .hasAttributes(attributeEntry("tag", "1")), + point -> + point + .hasValue(42_000) + .hasAttributes(attributeEntry("tag", "2")))))); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractGaugeTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractGaugeTest.java new file mode 100644 index 000000000000..50a992755a0c --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractGaugeTest.java @@ -0,0 +1,141 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicLong; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.Test; + +abstract class AbstractGaugeTest { + + protected abstract InstrumentationExtension testing(); + + @Test + void testGauge() { + // given + Gauge gauge = + Gauge.builder("testGauge", () -> 42) + .description("This is a test gauge") + .tags("tag", "value") + .baseUnit("items") + .register(Metrics.globalRegistry); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testGauge", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test gauge") + .hasUnit("items") + .hasDoubleGaugeSatisfying( + doubleGauge -> + doubleGauge.hasPointsSatisfying( + point -> + point + .hasValue(42) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(gauge); + testing().clearData(); + + // then + testing() + .waitAndAssertMetrics(INSTRUMENTATION_NAME, "testGauge", AbstractIterableAssert::isEmpty); + } + + @Test + void gaugesWithSameNameAndDifferentTags() { + // given + Gauge.builder("testGaugeWithTags", () -> 12) + .description("First description wins") + .baseUnit("items") + .tags("tag", "1") + .register(Metrics.globalRegistry); + Gauge.builder("testGaugeWithTags", () -> 42) + .description("ignored") + .baseUnit("items") + .tags("tag", "2") + .register(Metrics.globalRegistry); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testGaugeWithTags", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("First description wins") + .hasUnit("items") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(12) + .hasAttributes(attributeEntry("tag", "1")), + point -> + point + .hasValue(42) + .hasAttributes(attributeEntry("tag", "2")))))); + } + + @Test + void testWeakRefGauge() throws InterruptedException { + // given + AtomicLong num = new AtomicLong(42); + Gauge.builder("testWeakRefGauge", num, AtomicLong::get) + .strongReference(false) + .register(Metrics.globalRegistry); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testWeakRefGauge", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> gauge.hasPointsSatisfying(point -> point.hasValue(42))))); + + // when + WeakReference numWeakRef = new WeakReference<>(num); + num = null; + awaitGc(numWeakRef); + testing().clearData(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, "testWeakRefGauge", AbstractIterableAssert::isEmpty); + } + + private static void awaitGc(WeakReference ref) throws InterruptedException { + while (ref.get() != null) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + System.gc(); + System.runFinalization(); + } + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerHistogramTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerHistogramTest.java new file mode 100644 index 000000000000..223f770720aa --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerHistogramTest.java @@ -0,0 +1,168 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.MockClock; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.time.Duration; +import org.junit.jupiter.api.Test; + +abstract class AbstractLongTaskTimerHistogramTest { + + protected abstract InstrumentationExtension testing(); + + protected abstract MockClock clock(); + + @Test + void testMicrometerHistogram() { + // given + LongTaskTimer timer = + LongTaskTimer.builder("testLongTaskTimerHistogram") + .description("This is a test timer") + .serviceLevelObjectives(Duration.ofMillis(100), Duration.ofMillis(1000)) + .distributionStatisticBufferLength(10) + .register(Metrics.globalRegistry); + + // when + LongTaskTimer.Sample sample1 = timer.start(); + // only active tasks count + timer.start().stop(); + clock().add(Duration.ofMillis(100)); + LongTaskTimer.Sample sample2 = timer.start(); + LongTaskTimer.Sample sample3 = timer.start(); + clock().add(Duration.ofMillis(10)); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerHistogram.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("tasks") + .hasLongSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(3) + .hasAttributes(Attributes.empty()))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerHistogram.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("ms") + .hasDoubleSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasAttributes(Attributes.empty()) + .satisfies( + pointData -> + assertThat(pointData.getValue()) + .isPositive()))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerHistogram.histogram", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasAttributes(attributeEntry("le", "100")) + .hasValue(2), + point -> + point + .hasAttributes(attributeEntry("le", "1000")) + .hasValue(3))))); + + // when + sample1.stop(); + sample2.stop(); + sample3.stop(); + + // then + // Continues to report 0 after stopped. + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerHistogram.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("tasks") + .hasLongSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(Attributes.empty()))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerHistogram.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("ms") + .hasDoubleSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(Attributes.empty()))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerHistogram.histogram", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(attributeEntry("le", "100")), + point -> + point + .hasValue(0) + .hasAttributes(attributeEntry("le", "1000")))))); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerSecondsTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerSecondsTest.java new file mode 100644 index 000000000000..352807aaa0e3 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerSecondsTest.java @@ -0,0 +1,125 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.Test; + +public abstract class AbstractLongTaskTimerSecondsTest { + + protected abstract InstrumentationExtension testing(); + + @Test + void testLongTaskTimerWithBaseUnitSeconds() throws InterruptedException { + // given + LongTaskTimer timer = + LongTaskTimer.builder("testLongTaskTimerSeconds") + .description("This is a test long task timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + LongTaskTimer.Sample sample = timer.start(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerSeconds.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test long task timer") + .hasUnit("tasks") + .hasLongSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(1) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerSeconds.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test long task timer") + .hasUnit("s") + .hasDoubleSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasAttributes(attributeEntry("tag", "value")) + .satisfies( + pointData -> + assertThat(pointData.getValue()) + .isPositive()))))); + + // when + TimeUnit.MILLISECONDS.sleep(100); + sample.stop(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerSeconds.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerSeconds.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when timer is removed from the registry + Metrics.globalRegistry.remove(timer); + testing().clearData(); + timer.start(); + + // then no tasks are active after starting a new sample + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimerSeconds.active", + AbstractIterableAssert::isEmpty); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerTest.java new file mode 100644 index 000000000000..d3ecd3ff5093 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractLongTaskTimerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Metrics; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.Test; + +public abstract class AbstractLongTaskTimerTest { + + protected abstract InstrumentationExtension testing(); + + @Test + void testLongTaskTimer() throws InterruptedException { + // given + LongTaskTimer timer = + LongTaskTimer.builder("testLongTaskTimer") + .description("This is a test long task timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + LongTaskTimer.Sample sample = timer.start(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimer.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test long task timer") + .hasUnit("tasks") + .hasLongSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(1) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimer.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test long task timer") + .hasUnit("ms") + .hasDoubleSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasAttributes(attributeEntry("tag", "value")) + .satisfies( + pointData -> + assertThat(pointData.getValue()) + .isPositive()))))); + + // when + TimeUnit.MILLISECONDS.sleep(100); + sample.stop(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimer.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testLongTaskTimer.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when timer is removed from the registry + Metrics.globalRegistry.remove(timer); + testing().clearData(); + timer.start(); + + // then no tasks are active after starting a new sample + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, "testLongTaskTimer.active", AbstractIterableAssert::isEmpty); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractMeterTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractMeterTest.java new file mode 100644 index 000000000000..61a570882e93 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractMeterTest.java @@ -0,0 +1,203 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Statistic; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.Test; + +public abstract class AbstractMeterTest { + + protected abstract InstrumentationExtension testing(); + + @Test + void testMeter() { + // given + AtomicReference number = new AtomicReference<>(12345.0); + List measurements = + Arrays.asList( + new Measurement(number::get, Statistic.TOTAL), + new Measurement(number::get, Statistic.TOTAL_TIME), + new Measurement(number::get, Statistic.COUNT), + new Measurement(number::get, Statistic.ACTIVE_TASKS), + new Measurement(number::get, Statistic.DURATION), + new Measurement(number::get, Statistic.MAX), + new Measurement(number::get, Statistic.VALUE), + new Measurement(number::get, Statistic.UNKNOWN)); + Meter meter = + Meter.builder("testMeter", Meter.Type.OTHER, measurements) + .description("This is a test meter") + .baseUnit("things") + .tag("tag", "value") + .register(Metrics.globalRegistry); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testMeter.total", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test meter") + .hasUnit("things") + .hasDoubleSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(12345) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testMeter.total_time", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test meter") + .hasUnit("things") + .hasDoubleSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(12345) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testMeter.count", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test meter") + .hasUnit("things") + .hasDoubleSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(12345) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testMeter.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test meter") + .hasUnit("things") + .hasDoubleSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(12345) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testMeter.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test meter") + .hasUnit("things") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(12345) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testMeter.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test meter") + .hasUnit("things") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(12345) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testMeter.value", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test meter") + .hasUnit("things") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(12345) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testMeter.unknown", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test meter") + .hasUnit("things") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(12345) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(meter); + testing().clearData(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, "testMeter.total", AbstractIterableAssert::isEmpty); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractNamingConventionTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractNamingConventionTest.java new file mode 100644 index 000000000000..478ec9622154 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractNamingConventionTest.java @@ -0,0 +1,279 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.config.NamingConvention; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("PreferJavaTimeOverload") +public abstract class AbstractNamingConventionTest { + + protected abstract InstrumentationExtension testing(); + + protected static NamingConvention namingConvention() { + return new NamingConvention() { + @Override + public String name(String name, Meter.Type type, String baseUnit) { + return "test." + name; + } + + @Override + public String tagKey(String key) { + return "test." + key; + } + + @Override + public String tagValue(String value) { + return "test." + value; + } + }; + } + + final AtomicLong num = new AtomicLong(42); + + @Test + void renameCounter() { + // given + Counter counter = Metrics.counter("renamedCounter", "tag", "value"); + + // when + counter.increment(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedCounter", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + } + + @Test + void renameDistributionSummary() { + // given + DistributionSummary summary = Metrics.summary("renamedSummary", "tag", "value"); + + // when + summary.record(42); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedSummary", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedSummary.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + } + + @Test + void renameFunctionCounter() { + // given + Metrics.more().counter("renamedFunctionCounter", Tags.of("tag", "value"), num); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedFunctionCounter", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + } + + @Test + void renameFunctionTimer() { + // given + Metrics.more() + .timer( + "renamedFunctionTimer", + Tags.of("tag", "value"), + num, + AtomicLong::longValue, + AtomicLong::doubleValue, + TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedFunctionTimer.count", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedFunctionTimer.sum", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + } + + @Test + void renameGauge() { + // given + Metrics.gauge("renamedGauge", Tags.of("tag", "value"), num); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedGauge", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + } + + @Test + void renameLongTaskTimer() { + // given + LongTaskTimer timer = Metrics.more().longTaskTimer("renamedLongTaskTimer", "tag", "value"); + + // when + timer.start().stop(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedLongTaskTimer.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedLongTaskTimer.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + } + + @Test + void renameTimer() { + // given + Timer timer = Metrics.timer("renamedTimer", "tag", "value"); + + // when + timer.record(10, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedTimer", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "test.renamedTimer.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("test.tag", "test.value")))))); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java new file mode 100644 index 000000000000..a844da53e2c3 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java @@ -0,0 +1,343 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.FunctionTimer; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("PreferJavaTimeOverload") +public abstract class AbstractPrometheusModeTest { + + protected abstract InstrumentationExtension testing(); + + final TestTimer timerObj = new TestTimer(); + + @Test + void testCounter() { + // given + Counter counter = + Counter.builder("testPrometheusCounter") + .description("This is a test counter") + .tags("tag", "value") + .baseUnit("items") + .register(Metrics.globalRegistry); + + // when + counter.increment(12); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusCounter.items", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test counter") + .hasUnit("items") + .hasDoubleSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(12) + .hasAttributes( + attributeEntry("tag", "value")))))); + } + + @Test + void testDistributionSummary() { + // given + DistributionSummary summary = + DistributionSummary.builder("testPrometheusSummary") + .description("This is a test summary") + .baseUnit("items") + .tag("tag", "value") + .register(Metrics.globalRegistry); + + // when + summary.record(12); + summary.record(42); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusSummary.items", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test summary") + .hasUnit("items") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(54) + .hasCount(2) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusSummary.items.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test summary") + .hasUnit("items") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(42) + .hasAttributes(attributeEntry("tag", "value")))))); + } + + @Test + void testFunctionTimer() { + // given + FunctionTimer.builder( + "testPrometheusFunctionTimer", + timerObj, + TestTimer::getCount, + TestTimer::getTotalTimeNanos, + TimeUnit.NANOSECONDS) + .description("This is a test function timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + timerObj.add(42, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusFunctionTimer.seconds.count", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test function timer") + .hasUnit("1") + .hasLongSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(1) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusFunctionTimer.seconds.sum", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test function timer") + .hasUnit("s") + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(42) + .hasAttributes(attributeEntry("tag", "value")))))); + } + + @Test + void testGauge() { + // when + Gauge.builder("testPrometheusGauge", () -> 42) + .description("This is a test gauge") + .tags("tag", "value") + .baseUnit("items") + .register(Metrics.globalRegistry); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusGauge.items", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test gauge") + .hasUnit("items") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(42) + .hasAttributes(attributeEntry("tag", "value")))))); + } + + @Test + void testLongTaskTimer() throws InterruptedException { + // given + LongTaskTimer timer = + LongTaskTimer.builder("testPrometheusLongTaskTimer") + .description("This is a test long task timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + LongTaskTimer.Sample sample = timer.start(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusLongTaskTimer.seconds.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test long task timer") + .hasUnit("tasks") + .hasLongSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasValue(1) + .hasAttributes( + attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusLongTaskTimer.seconds.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test long task timer") + .hasUnit("s") + .hasDoubleSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point + .hasAttributes(attributeEntry("tag", "value")) + .satisfies( + pointData -> + assertThat(pointData.getValue()) + .isPositive()))))); + + // when + TimeUnit.MILLISECONDS.sleep(100); + sample.stop(); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusLongTaskTimer.seconds.active", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusLongTaskTimer.seconds.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(0) + .hasAttributes(attributeEntry("tag", "value")))))); + } + + @Test + void testTimer() { + // given + Timer timer = + Timer.builder("testPrometheusTimer") + .description("This is a test timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + timer.record(1, TimeUnit.SECONDS); + timer.record(5, TimeUnit.SECONDS); + timer.record(10_789, TimeUnit.MILLISECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusTimer.seconds", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("s") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(16.789) + .hasCount(3) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testPrometheusTimer.seconds.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("s") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(10.789) + .hasAttributes(attributeEntry("tag", "value")))))); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerSecondsTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerSecondsTest.java new file mode 100644 index 000000000000..7f42c0d5a67d --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerSecondsTest.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.TimeUnit; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("PreferJavaTimeOverload") +public abstract class AbstractTimerSecondsTest { + + protected abstract InstrumentationExtension testing(); + + @Test + void testTimerWithBaseUnitSeconds() { + // given + Timer timer = + Timer.builder("testTimerSeconds") + .description("This is a test timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + timer.record(1, TimeUnit.SECONDS); + timer.record(10, TimeUnit.SECONDS); + timer.record(12_345, TimeUnit.MILLISECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimerSeconds", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("s") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(23.345) + .hasCount(3) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimerSeconds.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("s") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(12.345) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(timer); + testing().clearData(); + timer.record(12, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, "testTimerSeconds", AbstractIterableAssert::isEmpty); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java new file mode 100644 index 000000000000..84d4b07f142b --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java @@ -0,0 +1,293 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import static io.opentelemetry.instrumentation.micrometer.v1_5.AbstractCounterTest.INSTRUMENTATION_NAME; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry; + +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.assertj.core.api.AbstractIterableAssert; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("PreferJavaTimeOverload") +public abstract class AbstractTimerTest { + + protected abstract InstrumentationExtension testing(); + + @Test + void testTimer() { + // given + Timer timer = + Timer.builder("testTimer") + .description("This is a test timer") + .tags("tag", "value") + .register(Metrics.globalRegistry); + + // when + timer.record(42, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimer", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(42_000) + .hasCount(1) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimer.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(42_000) + .hasAttributes(attributeEntry("tag", "value")))))); + + // when + Metrics.globalRegistry.remove(timer); + testing().clearData(); + timer.record(12, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics(INSTRUMENTATION_NAME, "testTimer", AbstractIterableAssert::isEmpty); + } + + @Test + void testNanoPrecision() { + // given + Timer timer = Timer.builder("testNanoTimer").register(Metrics.globalRegistry); + + // when + timer.record(1_234_000, TimeUnit.NANOSECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testNanoTimer", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(1.234) + .hasCount(1) + .hasAttributes(Attributes.empty()))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testNanoTimer.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(1.234) + .hasAttributes(Attributes.empty()))))); + } + + @Test + void testMicrometerHistogram() { + // given + Timer timer = + Timer.builder("testTimer") + .description("This is a test timer") + .tags("tag", "value") + .serviceLevelObjectives( + Duration.ofSeconds(1), + Duration.ofSeconds(10), + Duration.ofSeconds(100), + Duration.ofSeconds(1000)) + .distributionStatisticBufferLength(10) + .register(Metrics.globalRegistry); + + // when + timer.record(500, TimeUnit.MILLISECONDS); + timer.record(5, TimeUnit.SECONDS); + timer.record(50, TimeUnit.SECONDS); + timer.record(500, TimeUnit.SECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimer", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(555500) + .hasCount(4) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimer.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(500000) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimer.histogram", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(1) + .hasAttributes( + attributeEntry("le", "1000"), + attributeEntry("tag", "value")), + point -> + point + .hasValue(2) + .hasAttributes( + attributeEntry("le", "10000"), + attributeEntry("tag", "value")), + point -> + point + .hasValue(3) + .hasAttributes( + attributeEntry("le", "100000"), + attributeEntry("tag", "value")), + point -> + point + .hasValue(4) + .hasAttributes( + attributeEntry("le", "1000000"), + attributeEntry("tag", "value")))))); + } + + @Test + void testMicrometerPercentiles() { + // given + Timer timer = + Timer.builder("testTimer") + .description("This is a test timer") + .tags("tag", "value") + .publishPercentiles(0.5, 0.95, 0.99) + .register(Metrics.globalRegistry); + + // when + timer.record(50, TimeUnit.MILLISECONDS); + timer.record(100, TimeUnit.MILLISECONDS); + + // then + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimer", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(150) + .hasCount(2) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimer.max", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDescription("This is a test timer") + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point + .hasValue(100) + .hasAttributes(attributeEntry("tag", "value")))))); + testing() + .waitAndAssertMetrics( + INSTRUMENTATION_NAME, + "testTimer.percentile", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasDoubleGaugeSatisfying( + gauge -> + gauge.hasPointsSatisfying( + point -> + point.hasAttributes( + attributeEntry("phi", "0.5"), + attributeEntry("tag", "value")), + point -> + point.hasAttributes( + attributeEntry("phi", "0.95"), + attributeEntry("tag", "value")), + point -> + point.hasAttributes( + attributeEntry("phi", "0.99"), + attributeEntry("tag", "value")))))); + } +} diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/TestTimer.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/TestTimer.java new file mode 100644 index 000000000000..c29c6341b579 --- /dev/null +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/TestTimer.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.micrometer.v1_5; + +import java.util.concurrent.TimeUnit; + +class TestTimer { + int count = 0; + long totalTimeNanos = 0; + + void add(long time, TimeUnit unit) { + count++; + totalTimeNanos += unit.toNanos(time); + } + + int getCount() { + return count; + } + + double getTotalTimeNanos() { + return totalTimeNanos; + } + + void reset() { + count = 0; + totalTimeNanos = 0; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a7093a514501..0ec769f07868 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -337,6 +337,8 @@ include(":instrumentation:logback:logback-mdc-1.0:library") include(":instrumentation:logback:logback-mdc-1.0:testing") include(":instrumentation:methods:javaagent") include(":instrumentation:micrometer:micrometer-1.5:javaagent") +include(":instrumentation:micrometer:micrometer-1.5:library") +include(":instrumentation:micrometer:micrometer-1.5:testing") include(":instrumentation:mongo:mongo-3.1:javaagent") include(":instrumentation:mongo:mongo-3.1:library") include(":instrumentation:mongo:mongo-3.1:testing")