diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 9dc2d328a..b00db0beb 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -17,8 +17,8 @@ val DEPENDENCY_BOMS = listOf( "org.junit:junit-bom:5.7.2", "com.linecorp.armeria:armeria-bom:1.9.1", "io.grpc:grpc-bom:1.39.0", - "io.opentelemetry:opentelemetry-bom:1.4.1", - "io.opentelemetry:opentelemetry-bom-alpha:1.4.1-alpha", + "io.opentelemetry:opentelemetry-bom:1.5.0", + "io.opentelemetry:opentelemetry-bom-alpha:1.5.0-alpha", "org.testcontainers:testcontainers-bom:1.16.0" ) diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/DoubleCounter.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/DoubleCounter.java new file mode 100644 index 000000000..a730f3b11 --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/DoubleCounter.java @@ -0,0 +1,126 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.function.Consumer; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Tags; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.BoundDoubleCounter; +import io.opentelemetry.api.metrics.DoubleCounterBuilder; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; +import io.opentelemetry.context.Context; + +final class DoubleCounter implements io.opentelemetry.api.metrics.DoubleCounter { + private final SharedMeterState state; + private final Reader factory; + + private DoubleCounter(SharedMeterState state, Reader factory) { + this.state = state; + this.factory = factory; + } + + @Override + public void add(double value) { + add(value, Attributes.empty(), Context.current()); + } + + @Override + public void add(double value, Attributes attributes) { + add(value, attributes, Context.current()); + } + + @Override + public void add(double value, Attributes attributes, Context context) { + BoundDoubleCounter bound = bind(attributes); + try { + bound.add(value, context); + } finally { + bound.unbind(); + } + } + + @Override + public BoundDoubleCounter bind(Attributes attributes) { + Tags tags = TagUtils.attributesToTags(attributes); + Counter counter = factory.get() + .tags(tags) + .register(state.meterRegistry()); + return new Bound(counter); + } + + static DoubleCounterBuilder newBuilder(SharedMeterState state, String name) { + return new Builder(state, () -> Counter.builder(name)); + } + + static DoubleCounterBuilder newBuilder(SharedMeterState state, Reader factory) { + return new Builder(state, factory); + } + + final static class Builder implements DoubleCounterBuilder { + private final SharedMeterState state; + private final Reader factory; + + Builder(SharedMeterState state, Reader factory) { + this.state = state; + this.factory = factory; + } + + @Override + public Builder setDescription(String description) { + return new Builder(state, factory.andThen(builder -> builder.description(description))); + } + + @Override + public Builder setUnit(String unit) { + return new Builder(state, factory.andThen(builder -> builder.baseUnit(unit))); + } + + @Override + public LongCounterBuilder ofLongs() { + return LongCounter.newBuilder(state, factory); + } + + @Override + public DoubleCounter build() { + return new DoubleCounter(state, factory); + } + + @Override + public void buildWithCallback(Consumer callback) { + DoubleCounter counter = build(); + callback.accept(new ObservableDoubleMeasurement() { + @Override + public void observe(double value) { + counter.add(value); + } + + @Override + public void observe(double value, Attributes attributes) { + counter.add(value, attributes); + } + }); + } + } + + final static class Bound implements BoundDoubleCounter { + private final Counter counter; + + Bound(Counter counter) { + this.counter = counter; + } + + @Override + public void add(double value) { + add(value, Context.current()); + } + + @Override + public void add(double value, Context context) { + counter.increment(value); + } + + @Override + public void unbind() {} + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/DoubleGauge.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/DoubleGauge.java new file mode 100644 index 000000000..ebefbcaef --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/DoubleGauge.java @@ -0,0 +1,50 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.micrometer.core.instrument.Gauge; +import io.opentelemetry.api.metrics.LongGaugeBuilder; +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; + +final class DoubleGauge { + + static DoubleGaugeBuilder newBuilder(SharedMeterState state, String name) { + return newBuilder(state, supplier -> Gauge.builder(name, supplier)); + } + + public static DoubleGaugeBuilder newBuilder(SharedMeterState state, Function, Gauge.Builder>> factory) { + return new DoubleGaugeBuilder(state, factory); + } + + static final class DoubleGaugeBuilder implements io.opentelemetry.api.metrics.DoubleGaugeBuilder { + private final SharedMeterState state; + private final Function, Gauge.Builder>> factory; + + DoubleGaugeBuilder(SharedMeterState state, Function, Gauge.Builder>> factory) { + this.state = state; + this.factory = factory; + } + + @Override + public DoubleGaugeBuilder setDescription(String description) { + return new DoubleGaugeBuilder(state, factory.andThen(builder -> builder.description(description))); + } + + @Override + public DoubleGaugeBuilder setUnit(String unit) { + return new DoubleGaugeBuilder(state, factory.andThen(builder -> builder.baseUnit(unit))); + } + + @Override + public LongGaugeBuilder ofLongs() { + return LongGauge.newBuilder(state, factory); + } + + @Override + public void buildWithCallback(Consumer callback) { + callback.accept(new ObservableGaugeMeasurement(state, factory)); + } + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/LongCounter.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/LongCounter.java new file mode 100644 index 000000000..11c82af1f --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/LongCounter.java @@ -0,0 +1,126 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.function.Consumer; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Tag; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.BoundLongCounter; +import io.opentelemetry.api.metrics.DoubleCounterBuilder; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.context.Context; + +final class LongCounter implements io.opentelemetry.api.metrics.LongCounter { + private final SharedMeterState state; + private final Reader factory; + + private LongCounter(SharedMeterState state, Reader factory) { + this.state = state; + this.factory = factory; + } + + @Override + public void add(long value) { + add(value, Attributes.empty(), Context.current()); + } + + @Override + public void add(long value, Attributes attributes) { + add(value, attributes, Context.current()); + } + + @Override + public void add(long value, Attributes attributes, Context context) { + BoundLongCounter bound = bind(attributes); + try { + bound.add(value, context); + } finally { + bound.unbind(); + } + } + + @Override + public BoundLongCounter bind(Attributes attributes) { + Iterable tags = TagUtils.attributesToTags(attributes); + Counter counter = factory.get() + .tags(tags) + .register(state.meterRegistry()); + return new Bound(counter); + } + + static LongCounterBuilder newBuilder(SharedMeterState state, String name) { + return new Builder(state, () -> Counter.builder(name)); + } + + static LongCounterBuilder newBuilder(SharedMeterState state, Reader factory) { + return new Builder(state, factory); + } + + final static class Builder implements LongCounterBuilder { + private final SharedMeterState state; + private final Reader factory; + + Builder(SharedMeterState state, Reader factory) { + this.state = state; + this.factory = factory; + } + + @Override + public Builder setDescription(String description) { + return new Builder(state, factory.andThen(builder -> builder.description(description))); + } + + @Override + public Builder setUnit(String unit) { + return new Builder(state, factory.andThen(builder -> builder.baseUnit(unit))); + } + + @Override + public DoubleCounterBuilder ofDoubles() { + return null; + } + + @Override + public LongCounter build() { + return new LongCounter(state, factory); + } + + @Override + public void buildWithCallback(Consumer callback) { + LongCounter counter = build(); + callback.accept(new ObservableLongMeasurement() { + @Override + public void observe(long value) { + counter.add(value); + } + + @Override + public void observe(long value, Attributes attributes) { + counter.add(value, attributes); + } + }); + } + } + + final static class Bound implements BoundLongCounter { + private final Counter counter; + + Bound(Counter counter) { + this.counter = counter; + } + + @Override + public void add(long value) { + add(value, Context.current()); + } + + @Override + public void add(long value, Context context) { + counter.increment(value); + } + + @Override + public void unbind() {} + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/LongGauge.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/LongGauge.java new file mode 100644 index 000000000..8ed5e5cf1 --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/LongGauge.java @@ -0,0 +1,50 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.micrometer.core.instrument.Gauge; +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; + +final class LongGauge { + + static LongGaugeBuilder newBuilder(SharedMeterState state, String name) { + return newBuilder(state, supplier -> Gauge.builder(name, supplier)); + } + + public static LongGaugeBuilder newBuilder(SharedMeterState state, Function, Gauge.Builder>> factory) { + return new LongGaugeBuilder(state, factory); + } + + static final class LongGaugeBuilder implements io.opentelemetry.api.metrics.LongGaugeBuilder { + private final SharedMeterState state; + private final Function, Gauge.Builder>> factory; + + LongGaugeBuilder(SharedMeterState state, Function, Gauge.Builder>> factory) { + this.state = state; + this.factory = factory; + } + + @Override + public LongGaugeBuilder setDescription(String description) { + return new LongGaugeBuilder(state, factory.andThen(builder -> builder.description(description))); + } + + @Override + public LongGaugeBuilder setUnit(String unit) { + return new LongGaugeBuilder(state, factory.andThen(builder -> builder.baseUnit(unit))); + } + + @Override + public DoubleGaugeBuilder ofDoubles() { + return DoubleGauge.newBuilder(state, factory); + } + + @Override + public void buildWithCallback(Consumer callback) { + callback.accept(new ObservableGaugeMeasurement(state, factory)); + } + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MeterRegistryMeter.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MeterRegistryMeter.java new file mode 100644 index 000000000..c006c4dfa --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MeterRegistryMeter.java @@ -0,0 +1,73 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.function.Supplier; + +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterBuilder; + +final class MeterRegistryMeter implements Meter { + private final SharedMeterState state; + + private MeterRegistryMeter(SharedMeterState state) { + this.state = state; + } + + @Override + public LongCounterBuilder counterBuilder(String name) { + return LongCounter.newBuilder(state, name); + } + + @Override + public LongUpDownCounterBuilder upDownCounterBuilder(String name) { + return null; + } + + @Override + public DoubleHistogramBuilder histogramBuilder(String name) { + return null; + } + + @Override + public DoubleGaugeBuilder gaugeBuilder(String name) { + return DoubleGauge.newBuilder(state, name); + } + + public static Builder newBuilder(Supplier meterRegistrySupplier, String instrumentationName) { + return new Builder(meterRegistrySupplier, instrumentationName); + } + + private static final class Builder implements MeterBuilder { + private final Supplier meterRegistrySupplier; + private final String instrumentationName; + private String instrumentationVersion; + private String schemaUrl; + + public Builder(Supplier meterRegistrySupplier, String instrumentationName) { + this.meterRegistrySupplier = meterRegistrySupplier; + this.instrumentationName = instrumentationName; + } + + @Override + public MeterBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public MeterBuilder setInstrumentationVersion(String instrumentationVersion) { + this.instrumentationVersion = instrumentationVersion; + return this; + } + + @Override + public Meter build() { + SharedMeterState state = new SharedMeterState(meterRegistrySupplier, instrumentationName, instrumentationVersion, schemaUrl); + return new MeterRegistryMeter(state); + } + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MeterRegistryProvider.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MeterRegistryProvider.java new file mode 100644 index 000000000..ac13dcc0b --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MeterRegistryProvider.java @@ -0,0 +1,31 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.Objects; +import java.util.function.Supplier; + +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.api.metrics.MeterBuilder; +import io.opentelemetry.api.metrics.MeterProvider; + +public final class MeterRegistryProvider implements MeterProvider { + private final Supplier meterRegistrySupplier; + + private MeterRegistryProvider(Supplier meterRegistrySupplier) { + this.meterRegistrySupplier = meterRegistrySupplier; + } + + public static MeterRegistryProvider create(MeterRegistry meterRegistry) { + Objects.requireNonNull(meterRegistry); + return new MeterRegistryProvider(() -> meterRegistry); + } + + public static MeterRegistryProvider create(Supplier meterRegistrySupplier) { + Objects.requireNonNull(meterRegistrySupplier); + return new MeterRegistryProvider(meterRegistrySupplier); + } + + @Override + public MeterBuilder meterBuilder(String instrumentationName) { + return MeterRegistryMeter.newBuilder(meterRegistrySupplier, instrumentationName); + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ObservableGaugeMeasurement.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ObservableGaugeMeasurement.java new file mode 100644 index 000000000..e0a51ad1f --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ObservableGaugeMeasurement.java @@ -0,0 +1,72 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Tags; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; + +final class ObservableGaugeMeasurement implements ObservableLongMeasurement, ObservableDoubleMeasurement { + private final SharedMeterState state; + private final Function, Gauge.Builder>> factory; + private final ConcurrentMap map; + + public ObservableGaugeMeasurement(SharedMeterState state, Function, Gauge.Builder>> factory) { + this.state = state; + this.factory = factory; + this.map = new ConcurrentHashMap<>(); + } + + @Override + public void observe(double value) { + observe(value, Attributes.empty()); + } + + @Override + public void observe(double value, Attributes attributes) { + observeImpl(value, attributes); + } + + @Override + public void observe(long value) { + observe(value, Attributes.empty()); + } + + @Override + public void observe(long value, Attributes attributes) { + observeImpl(value, attributes); + } + + private void observeImpl(Number value, Attributes attributes) { + Tags tags = TagUtils.attributesToTags(attributes); + map.computeIfAbsent(tags, k -> { + NumberContainer container = new NumberContainer(value); + factory.apply(container) + .tags(tags) + .register(state.meterRegistry()); + return container; + }).set(value); + } + + private static class NumberContainer implements Supplier { + private Number value; + + public NumberContainer(Number value) { + this.value = value; + } + + public void set(Number value) { + this.value = value; + } + + @Override + public Number get() { + return value; + } + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/Reader.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/Reader.java new file mode 100644 index 000000000..59ee9cec9 --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/Reader.java @@ -0,0 +1,11 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.function.Function; +import java.util.function.Supplier; + +@FunctionalInterface +interface Reader extends Supplier { + default Reader andThen(Function fn) { + return () -> fn.apply(get()); + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/SharedMeterState.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/SharedMeterState.java new file mode 100644 index 000000000..46c54645e --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/SharedMeterState.java @@ -0,0 +1,35 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.function.Supplier; + +import io.micrometer.core.instrument.MeterRegistry; + +final class SharedMeterState { + private final Supplier meterRegistrySupplier; + private final String instrumentationName; + private final String intrumentationVersion; + private final String schemaUrl; + + public SharedMeterState(Supplier meterRegistrySupplier, String instrumentationName, String intrumentationVersion, String schemaUrl) { + this.meterRegistrySupplier = meterRegistrySupplier; + this.instrumentationName = instrumentationName; + this.intrumentationVersion = intrumentationVersion; + this.schemaUrl = schemaUrl; + } + + public MeterRegistry meterRegistry() { + return meterRegistrySupplier.get(); + } + + public String instrumentationName() { + return instrumentationName; + } + + public String intrumentationVersion() { + return intrumentationVersion; + } + + public String schemaUrl() { + return schemaUrl; + } +} diff --git a/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/TagUtils.java b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/TagUtils.java new file mode 100644 index 000000000..3f9824c42 --- /dev/null +++ b/micrometer-shim/src/main/java/io/opentelemetry/contrib/metrics/micrometer/TagUtils.java @@ -0,0 +1,21 @@ +package io.opentelemetry.contrib.metrics.micrometer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.opentelemetry.api.common.Attributes; + +final class TagUtils { + public static Tags attributesToTags(Attributes attributes) { + if (attributes == null || attributes.isEmpty()) { + return Tags.empty(); + } + + List list = new ArrayList<>(attributes.size()); + attributes.forEach((key, value) -> list.add(Tag.of(key.getKey(), Objects.toString(value)))); + return Tags.of(list); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index f020c22d5..73affb536 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,3 +22,4 @@ include(":aws-xray") include(":dependencyManagement") include(":example") include(":jmx-metrics") +include(":micrometer-shim")