Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;

import static net.bytebuddy.matcher.ElementMatchers.named;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.extension.instrumentation.internal.AsmApi;
import java.util.Collection;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.pool.TypePool;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class AbstractCompositeMeterInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("io.micrometer.core.instrument.composite.AbstractCompositeMeter");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyTransformer(
(builder, typeDescription, classLoader, javaModule, protectionDomain) ->
builder.visit(
new AsmVisitorWrapper() {
@Override
public int mergeWriter(int flags) {
return flags;
}

@Override
public int mergeReader(int flags) {
return flags;
}

@Override
public ClassVisitor wrap(
TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassVisitor(AsmApi.VERSION, classVisitor) {
@Override
public MethodVisitor visitMethod(
int access,
String name,
String descriptor,
String signature,
String[] exceptions) {
MethodVisitor mv =
super.visitMethod(access, name, descriptor, signature, exceptions);
if ("firstChild".equals(name)) {
return new MethodVisitor(api, mv) {
@Override
public void visitMethodInsn(
int opcode,
String owner,
String name,
String descriptor,
boolean isInterface) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
if (Opcodes.INVOKEINTERFACE == opcode
&& Type.getInternalName(Collection.class).equals(owner)
&& "iterator".equals(name)
&& "()Ljava/util/Iterator;".equals(descriptor)) {
// wrap the returned iterator to filter out our MeterRegistry
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(MicrometerSingletons.class),
"wrapIterator",
"(Ljava/util/Iterator;)Ljava/util/Iterator;",
false);
}
}
};
}
return mv;
}
};
}
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@
package io.opentelemetry.javaagent.instrumentation.micrometer.v1_5;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.Collections;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public class MicrometerInstrumentationModule extends InstrumentationModule {
public class MicrometerInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {

public MicrometerInstrumentationModule() {
super("micrometer", "micrometer-1.5");
Expand All @@ -36,6 +40,15 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new MetricsInstrumentation());
return asList(new MetricsInstrumentation(), new AbstractCompositeMeterInstrumentation());
}

@Override
public void injectClasses(ClassInjector injector) {
// we use asm to call a method in MicrometerSingletons
injector
.proxyBuilder(
"io.opentelemetry.javaagent.instrumentation.micrometer.v1_5.MicrometerSingletons")
.inject(InjectionMode.CLASS_ONLY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import io.micrometer.core.instrument.MeterRegistry;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryInstrument;
import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry;
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
import java.util.Iterator;

public final class MicrometerSingletons {

Expand All @@ -34,5 +36,47 @@ public static MeterRegistry meterRegistry() {
return METER_REGISTRY;
}

// called from code generate in AbstractCompositeMeterInstrumentation
public static <T> Iterator<T> wrapIterator(Iterator<T> iterator) {
if (!iterator.hasNext()) {
return iterator;
}

class FilteringIterator implements Iterator<T> {
private final Iterator<T> delegate;
private T next;

FilteringIterator(Iterator<T> delegate) {
this.delegate = delegate;
advance();
}

private void advance() {
while (delegate.hasNext()) {
T candidate = delegate.next();
if (!(candidate instanceof OpenTelemetryInstrument)) {
next = candidate;
return;
}
}
next = null;
}

@Override
public boolean hasNext() {
return next != null;
}

@Override
public T next() {
T result = next;
advance();
return result;
}
}

return new FilteringIterator(iterator);
}

private MicrometerSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.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.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

class CompositeCounterTest {

static final String INSTRUMENTATION_NAME = "io.opentelemetry.micrometer-1.5";

@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();

@AfterEach
@BeforeEach
public void cleanup() {
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);
assertThat(counter.getClass().getName()).contains("CompositeCounter");

// when
counter.increment();

// then
// OpenTelemetryCounter returns NaN for count(), but NoopCounter returns 0
// Here we verify that OpenTelemetryCounter is filtered out when count() is called and the
// result is produced form NoopCounter. If there were multiple meter registries, the result
// would be produced from an instrument that is not from our registry. We don't test with
// multiple registries because the behavior of count() depends on the order of elements in a
// map, so instead we test that our instrument is ignored and the result comes from a fallback
// to NoopCounter.
assertThat(counter.count()).isEqualTo(0);

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(1)
.hasAttributes(attributeEntry("tag", "value"))))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
import io.opentelemetry.api.metrics.Meter;
import java.util.Collections;

final class OpenTelemetryCounter extends AbstractMeter implements Counter, RemovableMeter {
final class OpenTelemetryCounter extends AbstractMeter
implements Counter, RemovableMeter, OpenTelemetryInstrument {

// TODO: use bound instruments when they're available
private final DoubleCounter otelCounter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import java.util.concurrent.atomic.LongAdder;

final class OpenTelemetryDistributionSummary extends AbstractDistributionSummary
implements RemovableMeter {
implements RemovableMeter, OpenTelemetryInstrument {

private final Measurements measurements;
private final TimeWindowMax max;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import java.util.function.ToDoubleFunction;

final class OpenTelemetryFunctionCounter<T> extends AbstractMeter
implements FunctionCounter, RemovableMeter {
implements FunctionCounter, RemovableMeter, OpenTelemetryInstrument {

private final ObservableDoubleCounter observableCount;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.function.ToLongFunction;

final class OpenTelemetryFunctionTimer<T> extends AbstractMeter
implements FunctionTimer, RemovableMeter {
implements FunctionTimer, RemovableMeter, OpenTelemetryInstrument {
private final TimeUnit baseTimeUnit;
private final ObservableLongCounter observableCount;
private final ObservableDoubleCounter observableTotalTime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import java.util.function.ToDoubleFunction;
import javax.annotation.Nullable;

final class OpenTelemetryGauge<T> extends AbstractMeter implements Gauge, RemovableMeter {
final class OpenTelemetryGauge<T> extends AbstractMeter
implements Gauge, RemovableMeter, OpenTelemetryInstrument {

private final ObservableDoubleGauge observableGauge;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.micrometer.v1_5;

/** Marker interface for micrometer instruments that delegate to Open Telemetry instruments. */
public interface OpenTelemetryInstrument {}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
import java.util.Collections;
import java.util.concurrent.TimeUnit;

final class OpenTelemetryLongTaskTimer extends DefaultLongTaskTimer implements RemovableMeter {
final class OpenTelemetryLongTaskTimer extends DefaultLongTaskTimer
implements RemovableMeter, OpenTelemetryInstrument {

private final DistributionStatisticConfig distributionStatisticConfig;
private final ObservableLongUpDownCounter observableActiveTasks;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
import java.util.Collections;
import java.util.List;

final class OpenTelemetryMeter extends AbstractMeter implements Meter, RemovableMeter {
final class OpenTelemetryMeter extends AbstractMeter
implements Meter, RemovableMeter, OpenTelemetryInstrument {

private final List<AutoCloseable> observableInstruments;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;

final class OpenTelemetryTimer extends AbstractTimer implements RemovableMeter {
final class OpenTelemetryTimer extends AbstractTimer
implements RemovableMeter, OpenTelemetryInstrument {

private final Measurements measurements;
private final TimeWindowMax max;
Expand Down
Loading